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 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 { 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 }