archiver/extract.go

365 lines
8.5 KiB
Go

package archiver
import (
"archive/tar"
"archive/zip"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
)
type extractOptions struct {
Perms os.FileMode
Overwrite bool
Folder string
NotPreserveFileStructure bool
Filter *regexp.Regexp
File *File
}
func extract(filesystem Filesystem, opts ExtractOptions, archive *Archive) error {
archive.mu.Lock()
defer archive.mu.Unlock()
if filesystem.billyFS == nil {
filesystem.file = true
}
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(opts.Folder, 0740); err != nil {
return err
}
} else if filesystem.file {
if err := os.MkdirAll(opts.Folder, 0740); err != nil {
return err
}
}
extOptions := extractOptions{
Folder: opts.Folder,
NotPreserveFileStructure: opts.NotPreserveFileStructure,
Overwrite: opts.Overwrite,
Filter: opts.Filter,
}
switch archive.Type {
case Tar, TarGzip, TarBzip, TarXz:
return tarExtract(filesystem, extOptions, archive)
case Zip:
return zipExtract(filesystem, extOptions, archive)
default:
return ErrArchiveTypeNotSupported
}
}
func extractFile(filesystem Filesystem, opts ExtractFileOptions, file *File) error {
file.archive.mu.Lock()
defer file.archive.mu.Unlock()
if filesystem.billyFS == nil {
filesystem.file = true
}
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(opts.Folder, 0740); err != nil {
return err
}
} else if filesystem.file {
if err := os.MkdirAll(opts.Folder, 0740); err != nil {
return err
}
}
extOptions := extractOptions{
Folder: opts.Folder,
NotPreserveFileStructure: opts.NotPreserveFileStructure,
Overwrite: opts.Overwrite,
File: file,
}
switch file.archive.Type {
case Tar, TarGzip, TarBzip, TarXz:
return tarExtract(filesystem, extOptions, file.archive)
case Zip:
return zipExtract(filesystem, extOptions, file.archive)
default:
return ErrArchiveTypeNotSupported
}
}
func zipExtract(filesystem Filesystem, opts extractOptions, archive *Archive) error {
if filesystem.billyFS == nil {
filesystem.file = true
}
for _, zipF := range archive.reader.(*zip.Reader).File {
var splitPath []string
if opts.File != nil {
splitPath = strings.Split(opts.File.Path, "/")
} else {
splitPath = strings.Split(zipF.Name, "/")
}
splitPath = splitPath[:len(splitPath)-1]
if !opts.NotPreserveFileStructure && zipF.FileInfo().IsDir() {
if opts.File != nil {
isFound := false
for _, folder := range splitPath {
if folder == filepath.Base(zipF.Name) {
isFound = true
break
}
}
if isFound {
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
return err
}
}
continue
}
} else {
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
return err
}
}
continue
}
}
if opts.File != nil && zipF.Name != opts.File.Path {
continue
}
if opts.File == nil && opts.Filter != nil {
if !opts.Filter.MatchString(zipF.Name) {
continue
}
}
var filePath string
if !opts.NotPreserveFileStructure {
filePath = filepath.Join(opts.Folder, zipF.Name)
} else {
filePath = filepath.Join(opts.Folder, filepath.Base(zipF.Name))
}
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if _, err := billyFS.Stat(filepath.Join(opts.Folder, filepath.Dir(zipF.Name))); err != nil {
if errors.Is(err, fs.ErrNotExist) {
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(zipF.Name)), 0740); err != nil {
return err
}
} else {
return err
}
}
} else {
if _, err := os.Stat(filepath.Join(opts.Folder, filepath.Dir(zipF.Name))); err != nil {
if errors.Is(err, fs.ErrNotExist) {
if err := os.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(zipF.Name)), 0740); err != nil {
return err
}
} else {
return err
}
}
}
if opts.Perms == 0 {
opts.Perms = zipF.FileInfo().Mode()
}
var file io.WriteCloser
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
var err error
file, err = billyFS.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, opts.Perms)
if err != nil {
return nil
}
} else {
var err error
file, err = os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, opts.Perms)
if err != nil {
return err
}
}
archiveFile, err := zipF.Open()
if err != nil {
return err
}
if _, err := io.Copy(file, archiveFile); err != nil {
return err
}
if err := archiveFile.Close(); err != nil {
return err
}
if err := file.Close(); err != nil {
return err
}
if opts.File != nil && zipF.Name == opts.File.Path {
break
}
}
return nil
}
func tarExtract(filesystem Filesystem, opts extractOptions, archive *Archive) error {
defer tarCleanup(archive)
if filesystem.billyFS == nil {
filesystem.file = true
}
for {
header, err := archive.tarReader.Next()
if err != nil && err != io.EOF {
return err
} else if err == io.EOF {
break
}
var splitPath []string
if opts.File != nil {
splitPath = strings.Split(opts.File.Path, "/")
} else {
splitPath = strings.Split(header.Name, "/")
}
splitPath = splitPath[:len(splitPath)-1]
if !opts.NotPreserveFileStructure && header.Typeflag == tar.TypeDir {
if opts.File != nil {
isFound := false
for _, folder := range splitPath {
if folder == filepath.Base(header.Name) {
isFound = true
break
}
}
if isFound {
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
return err
}
}
continue
}
} else {
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
return err
}
}
continue
}
}
if opts.File != nil && header.Name != opts.File.Path {
continue
}
if opts.File == nil && opts.Filter != nil {
if !opts.Filter.MatchString(header.Name) {
continue
}
}
var filePath string
if !opts.NotPreserveFileStructure {
filePath = filepath.Join(opts.Folder, header.Name)
} else {
filePath = filepath.Join(opts.Folder, filepath.Base(header.Name))
}
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
if _, err := billyFS.Stat(filepath.Join(opts.Folder, filepath.Dir(header.Name))); err != nil {
if errors.Is(err, fs.ErrNotExist) {
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(header.Name)), 0740); err != nil {
return err
}
} else {
return err
}
}
} else {
if _, err := os.Stat(filepath.Join(opts.Folder, filepath.Dir(header.Name))); err != nil {
if errors.Is(err, fs.ErrNotExist) {
if err := os.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(header.Name)), 0740); err != nil {
return err
}
} else {
return err
}
}
}
if opts.Perms == 0 {
opts.Perms = header.FileInfo().Mode()
}
var file io.WriteCloser
if filesystem.billyFS != nil {
billyFS := filesystem.billyFS
var err error
file, err = billyFS.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, opts.Perms)
if err != nil {
return nil
}
} else {
var err error
file, err = os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, opts.Perms)
if err != nil {
return err
}
}
if _, err = io.Copy(file, archive.tarReader); err != nil {
return err
}
if err := file.Close(); err != nil {
return err
}
if opts.File != nil && header.Name == opts.File.Path {
break
}
}
return nil
}