// Package extract iterates over the contents of common archive formats // (zip, tar, tar.gz, 7z, rar) without extracting to disk. package extract import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "errors" "fmt" "io" "path/filepath" "strings" "github.com/bodgit/sevenzip" "github.com/nwaples/rardecode/v2" ) type Entry struct { Name string Data []byte } type WalkFn func(Entry) error // Walk dispatches by extension and calls fn for each file entry. func Walk(name string, data []byte, fn WalkFn) error { ext := strings.ToLower(filepath.Ext(name)) switch ext { case ".zip": return walkZip(data, fn) case ".tar": return walkTar(bytes.NewReader(data), fn) case ".gz", ".tgz": return walkTarGz(data, fn) case ".7z": return walk7z(data, fn) case ".rar": return walkRar(data, fn) } return fmt.Errorf("unsupported archive: %s", ext) } // IsArchive reports whether name has an extension we know how to extract. func IsArchive(name string) bool { switch strings.ToLower(filepath.Ext(name)) { case ".zip", ".tar", ".gz", ".tgz", ".7z", ".rar": return true } return false } func walkZip(data []byte, fn WalkFn) error { r, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return err } for _, f := range r.File { if f.FileInfo().IsDir() { continue } rc, err := f.Open() if err != nil { return err } buf, err := io.ReadAll(rc) rc.Close() if err != nil { return err } if err := fn(Entry{Name: f.Name, Data: buf}); err != nil { return err } } return nil } func walkTar(r io.Reader, fn WalkFn) error { tr := tar.NewReader(r) for { hdr, err := tr.Next() if errors.Is(err, io.EOF) { return nil } if err != nil { return err } if hdr.Typeflag != tar.TypeReg { continue } buf, err := io.ReadAll(tr) if err != nil { return err } if err := fn(Entry{Name: hdr.Name, Data: buf}); err != nil { return err } } } func walkTarGz(data []byte, fn WalkFn) error { gz, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { return err } defer gz.Close() return walkTar(gz, fn) } func walk7z(data []byte, fn WalkFn) error { r, err := sevenzip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return err } for _, f := range r.File { if f.FileInfo().IsDir() { continue } rc, err := f.Open() if err != nil { return err } buf, err := io.ReadAll(rc) rc.Close() if err != nil { return err } if err := fn(Entry{Name: f.Name, Data: buf}); err != nil { return err } } return nil } func walkRar(data []byte, fn WalkFn) error { r, err := rardecode.NewReader(bytes.NewReader(data)) if err != nil { return err } for { hdr, err := r.Next() if errors.Is(err, io.EOF) { return nil } if err != nil { return err } if hdr.IsDir { continue } buf, err := io.ReadAll(r) if err != nil { return err } if err := fn(Entry{Name: hdr.Name, Data: buf}); err != nil { return err } } }