package archiver import ( "archive/tar" "archive/zip" "bytes" "compress/bzip2" "compress/gzip" "github.com/ulikunitz/xz" "io" "os" "path/filepath" ) // OpenFS opens an archive file from a given fs.FS and returns an Archive struct. // It takes fs.FS, Type and a path to the archive file as its parameters. // The function returns an Archive struct and an error, if any. func OpenFS(filesystem Filesystem, archiveType Type, path string) (*Archive, error) { return openArchive(filesystem, archiveType, path) } // Open opens an archive file and returns an Archive struct. // It takes a Type and a path to the archive file as its parameters. // The function returns an Archive struct and an error, if any. func Open(archiveType Type, path string) (*Archive, error) { archiveFolderPath, err := filepath.Abs(filepath.Dir(path)) if err != nil { return nil, err } return openArchive(WrapPath(archiveFolderPath), archiveType, path) } // Extract opens an archive file and extracts the contents. // It takes a Type, a path, and ExtractOptions for its parameters. // The function returns an error, if any. func Extract(archiveType Type, path string, options ExtractOptions) error { archiveFile, err := Open(archiveType, path) if err != nil { return err } defer archiveFile.Close() if err := archiveFile.Extract(options); err != nil { return err } if err := archiveFile.Close(); err != nil { return err } return nil } func openArchive(filesystem Filesystem, archiveType Type, path string) (*Archive, error) { archive := new(Archive) archive.files = make(map[string]*File) var file *bytes.Reader if filesystem.billyFS != nil { billyFS := filesystem.billyFS archiveFile, err := billyFS.Open(path) if err != nil { return nil, err } fileData, err := io.ReadAll(archiveFile) if err != nil { return nil, err } file = bytes.NewReader(fileData) } else if filesystem.fs != nil { genericFS := filesystem.fs archiveFile, err := genericFS.Open(path) if err != nil { return nil, err } fileData, err := io.ReadAll(archiveFile) if err != nil { return nil, err } file = bytes.NewReader(fileData) } else if len(filesystem.path) != 0 { archiveFile, err := os.Open(path) if err != nil { return nil, err } fileData, err := io.ReadAll(archiveFile) if err != nil { return nil, err } file = bytes.NewReader(fileData) } if file == nil { return nil, ErrArchiveFileNotFound } switch archiveType { case TarGzip: gzipReader, err := gzip.NewReader(file) if err != nil { return nil, err } tarReader := tar.NewReader(gzipReader) for { header, err := tarReader.Next() if err != nil && err != io.EOF { return nil, err } else if err == io.EOF { if _, err := file.Seek(0, io.SeekStart); err != nil { return nil, err } break } _, fileName := filepath.Split(header.Name) archive.files[header.Name] = &File{FileName: fileName, Path: header.Name, archive: archive} } if err := gzipReader.Reset(file); err != nil { return nil, err } archive.reader = gzipReader archive.tarReader = tar.NewReader(gzipReader) case TarBzip: bzipReader := bzip2.NewReader(file) tarReader := tar.NewReader(bzipReader) for { header, err := tarReader.Next() if err != nil && err != io.EOF { return nil, err } else if err == io.EOF { if _, err := file.Seek(0, io.SeekStart); err != nil { return nil, err } break } _, fileName := filepath.Split(header.Name) archive.files[header.Name] = &File{FileName: fileName, Path: header.Name, archive: archive} } bzipReader = bzip2.NewReader(file) archive.reader = bzipReader archive.tarReader = tar.NewReader(bzipReader) case TarXz: xzReader, err := xz.NewReader(file) if err != nil { return nil, err } tarReader := tar.NewReader(xzReader) for { header, err := tarReader.Next() if err != nil && err != io.EOF { return nil, err } else if err == io.EOF { if _, err := file.Seek(0, io.SeekStart); err != nil { return nil, err } break } _, fileName := filepath.Split(header.Name) archive.files[header.Name] = &File{FileName: fileName, Path: header.Name, archive: archive} } xzReader, err = xz.NewReader(file) if err != nil { return nil, err } archive.reader = xzReader archive.tarReader = tar.NewReader(xzReader) case Tar: tarReader := tar.NewReader(file) for { header, err := tarReader.Next() if err != nil && err != io.EOF { return nil, err } else if err == io.EOF { if _, err := file.Seek(0, io.SeekStart); err != nil { return nil, err } break } _, fileName := filepath.Split(header.Name) archive.files[header.Name] = &File{FileName: fileName, Path: header.Name, archive: archive} } archive.reader = file archive.tarReader = tar.NewReader(file) case Zip: reader, err := zip.NewReader(file, file.Size()) if err != nil { return nil, err } archive.reader = reader for _, file := range reader.File { _, fileName := filepath.Split(file.Name) archive.files[file.Name] = &File{FileName: fileName, Path: file.Name, archive: archive} } default: return nil, ErrArchiveTypeNotSupported } archive.Type = archiveType archive.Path = path archive.archiveFile = file return archive, nil }