2024-11-01 17:34:12 +01:00
|
|
|
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 {
|
|
|
|
if filesystem.billyFS == nil {
|
|
|
|
filesystem.file = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if filesystem.billyFS != nil {
|
|
|
|
billyFS := filesystem.billyFS
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(opts.Folder, 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if filesystem.file {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(opts.Folder, 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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 {
|
|
|
|
if filesystem.billyFS == nil {
|
|
|
|
filesystem.file = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if filesystem.billyFS != nil {
|
|
|
|
billyFS := filesystem.billyFS
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(opts.Folder, 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if filesystem.file {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(opts.Folder, 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if filesystem.billyFS != nil {
|
|
|
|
billyFS := filesystem.billyFS
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, zipF.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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) {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(zipF.Name)), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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) {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(zipF.Name)), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if filesystem.billyFS != nil {
|
|
|
|
billyFS := filesystem.billyFS
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, header.Name), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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) {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := billyFS.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(header.Name)), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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) {
|
2024-11-01 19:14:39 +01:00
|
|
|
if err := os.MkdirAll(filepath.Join(opts.Folder, filepath.Dir(header.Name)), 0740); err != nil {
|
2024-11-01 17:34:12 +01:00
|
|
|
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
|
|
|
|
}
|