Documentation
¶
Overview ¶
Package archives provides in-memory archive reading and browsing capabilities.
It supports multiple archive formats including:
- ZIP (.zip, .jar, .whl, .nupkg, .egg, .vsix)
- TAR (.tar, .tar.gz, .tgz, .crate, .tar.bz2, .tar.xz, .tar.zst)
- GEM (.gem - Ruby gems with nested tar structure)
- Hex (.tar - Hex packages with nested contents.tar.gz)
- CONDA (.conda - v2 conda packages, zip of zstd tarballs)
The .apk extension is routed by content since Android packages are ZIP and Alpine packages are gzipped tar. Filenames without a recognised extension are opened by inspecting the first bytes.
The package is designed to work entirely in memory without writing to disk, making it suitable for browsing cached artifacts on-demand.
Index ¶
- Constants
- Variables
- func CommonRoot(files []FileInfo) string
- func ExtractAll(r Reader, dir string, opts ...ExtractOption) error
- type BrowseOptions
- type Browser
- type ExtractOption
- type FS
- type FileInfo
- type Reader
- func Open(filename string, content io.Reader) (Reader, error)
- func OpenBytes(filename string, content []byte) (Reader, error)
- func OpenBytesWithPrefix(filename string, content []byte, stripPrefix string) (Reader, error)
- func OpenWithPrefix(filename string, content io.Reader, stripPrefix string) (Reader, error)
- type Stream
- func OpenStream(filename string, content io.Reader, options StreamOptions) (*Stream, error)
- func OpenStreamBytes(filename string, content []byte, options StreamOptions) (*Stream, error)
- func OpenStreamBytesContext(ctx context.Context, filename string, content []byte, options StreamOptions) (*Stream, error)
- func OpenStreamContext(ctx context.Context, filename string, content io.Reader, options StreamOptions) (*Stream, error)
- type StreamEntry
- type StreamOptions
Examples ¶
Constants ¶
const ( SHA256 = "sha256" SHA512 = "sha512" SHA1 = "sha1" MD5 = "md5" )
Supported hash algorithm names for Reader.Hash.
Variables ¶
var ErrDecompressLimit = errors.New("decompressed content exceeds size limit")
var ErrEntryLimit = errors.New("archive entry count exceeds limit")
var ErrEntrySizeLimit = errors.New("archive entry exceeds size limit")
var ErrExtractLimit = errors.New("extracted bytes exceed limit")
ErrExtractLimit is returned by ExtractAll when the total decompressed bytes written would exceed the WithMaxBytes limit.
var ErrInputLimit = errors.New("archive input exceeds size limit")
var ErrIsDir = errors.New("path is a directory")
var ErrUnsafePath = errors.New("archive entry escapes target directory")
ErrUnsafePath is returned by ExtractAll when an archive entry name would resolve outside the target directory.
Functions ¶
func CommonRoot ¶ added in v0.9.0
CommonRoot returns the shared top-level directory, including its slash. Empty archives and archives containing top-level files have no common root.
func ExtractAll ¶ added in v0.5.0
func ExtractAll(r Reader, dir string, opts ...ExtractOption) error
ExtractAll writes every entry in r under dir, creating dir and any intermediate directories as needed. Entry permissions are preserved where the archive format records them; entries with no stored mode are written as 0644 (files) or 0755 (directories).
All filesystem operations are confined to dir using os.Root, so a symlink under dir cannot redirect a write outside it. Entry names are additionally validated with filepath.Localize: absolute paths, names containing ".." elements that escape dir, and platform-invalid names cause ExtractAll to return ErrUnsafePath wrapping the offending entry name. Entries that the archive marks as non-regular (symlinks, devices) are skipped.
Types ¶
type BrowseOptions ¶ added in v0.9.0
type BrowseOptions struct {
Limits StreamOptions
// StripPrefix takes precedence over DetectRoot. Include the trailing slash.
StripPrefix string
// DetectRoot strips a directory shared by every entry.
DetectRoot bool
}
BrowseOptions controls per-pass limits and path prefixes.
type Browser ¶ added in v0.9.0
type Browser struct {
// contains filtered or unexported fields
}
Browser lists directories and streams individual files without retaining expanded bodies. Finish and close each extracted file before the next call. A Browser is not safe for concurrent use.
func OpenBrowser ¶ added in v0.9.0
func OpenBrowser(filename string, content io.Reader, reopen func() (io.ReadCloser, error), options BrowseOptions) (*Browser, error)
OpenBrowser prepares an archive for browsing. The caller owns content and must keep it open until the first operation and any extracted file finish. Later TAR, gem, and Hex passes call reopen; the browser closes those readers. ZIP and conda input is buffered once and reused without calling reopen. A nil reopen is allowed, but a second sequential pass wraps errors.ErrUnsupported.
func OpenBrowserContext ¶ added in v0.9.0
func OpenBrowserContext(ctx context.Context, filename string, content io.Reader, reopen func() (io.ReadCloser, error), options BrowseOptions) (*Browser, error)
OpenBrowserContext is OpenBrowser with cancellation across every pass, including decompression of buffered input. The reopen callback must honor ctx itself if opening can block. Canceling does not close the caller's input.
func (*Browser) Extract ¶ added in v0.9.0
func (b *Browser) Extract(filePath string) (io.ReadCloser, error)
Extract streams the first matching file. The caller must close the result. Root detection requires a separate pass, so sequential input needs reopen. Missing paths wrap fs.ErrNotExist; directories wrap ErrIsDir. Skipped entries count against limits. Entries after the target and trailers are not checked.
type ExtractOption ¶ added in v0.7.0
type ExtractOption func(*extractConfig)
ExtractOption configures ExtractAll.
func WithMaxBytes ¶ added in v0.7.0
func WithMaxBytes(n int64) ExtractOption
WithMaxBytes caps the total number of decompressed bytes ExtractAll will write. The limit is enforced against bytes actually read from each entry, not header-declared sizes, so an archive whose headers under-report content still cannot exceed it. A value of zero or less disables the limit.
type FS ¶ added in v0.8.0
type FS struct {
// contains filtered or unexported fields
}
FS is a read-only filesystem over a Reader. The Reader must remain open while the filesystem and its files are in use.
func NewFS ¶ added in v0.8.0
NewFS indexes r without extracting file contents. Missing parent directories are created in the index. Leading "./" and directory trailing slashes are removed from archive names; other invalid fs paths and file/directory conflicts return fs.ErrInvalid. The first entry for each path is used. Symlinks and other special entries can be listed, but cannot be opened. Closing an opened file does not close r; the caller owns the Reader.
Example ¶
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
w, err := zw.Create("lib/hello.txt")
if err != nil {
panic(err)
}
if _, err := io.WriteString(w, "hello\n"); err != nil {
panic(err)
}
if err := zw.Close(); err != nil {
panic(err)
}
r, err := Open("package.zip", &buf)
if err != nil {
panic(err)
}
defer func() { _ = r.Close() }()
f, err := NewFS(r)
if err != nil {
panic(err)
}
err = fs.WalkDir(f, ".", func(name string, entry fs.DirEntry, err error) error {
if err == nil {
fmt.Println(name)
}
return err
})
if err != nil {
panic(err)
}
data, err := fs.ReadFile(f, "lib/hello.txt")
if err != nil {
panic(err)
}
fmt.Print(string(data))
Output: . lib lib/hello.txt hello
func (*FS) Open ¶ added in v0.8.0
Open opens a file or directory. Names follow fs.ValidPath, with "." as root.
type FileInfo ¶
type FileInfo struct {
Path string // Full path within archive
Name string // Base name
Size int64 // Uncompressed bytes; -1 for an unknown streaming size
ModTime time.Time // Modification time
IsDir bool // Whether this is a directory
Mode uint32 // fs.FileMode value: permission bits plus fs.ModeType bits
HasMode bool // Whether Mode was recorded by the archive
CompressedSize int64 // Compressed size (if available)
}
FileInfo represents metadata about a file in an archive.
type Reader ¶
type Reader interface {
// List returns all files in the archive.
List() ([]FileInfo, error)
// ListDir returns files in a specific directory path.
// Use "" or "/" for root directory.
ListDir(dirPath string) ([]FileInfo, error)
// Extract reads a specific file from the archive.
// Returns io.ReadCloser for the file content.
Extract(filePath string) (io.ReadCloser, error)
// Hash returns the hex-encoded digest of the raw archive bytes using
// the named algorithm. Supported algorithms are SHA256, SHA512, SHA1
// and MD5. The hash is computed over the original archive as passed
// to Open, not the decompressed contents.
Hash(algo string) (string, error)
// Close releases resources associated with the reader.
Close() error
}
Reader provides methods to browse and extract files from archives.
func Open ¶
Open creates an archive reader for the given content. The filename is used first to detect the archive format. If it has no supported extension, the content is checked for a supported physical format. Recognised archives are read entirely into memory. An unrecognised stream with no supported extension is rejected after reading at most 512 bytes.
func OpenBytes ¶ added in v0.3.0
OpenBytes is like Open but accepts the archive content as a byte slice. The slice is retained (not copied) for the lifetime of the Reader and must not be modified by the caller after this call.
func OpenBytesWithPrefix ¶ added in v0.3.0
OpenBytesWithPrefix is like OpenWithPrefix but accepts the archive content as a byte slice. The slice is retained (not copied) for the lifetime of the Reader and must not be modified by the caller after this call.
type Stream ¶ added in v0.8.1
type Stream struct {
// contains filtered or unexported fields
}
Stream reads entries in archive order without retaining expanded bodies. Paths and duplicate entries are preserved. A Stream is not safe for concurrent use. It does not implement the random-access Reader interface.
func OpenStream ¶ added in v0.8.1
OpenStream reads supported archives sequentially. ZIP and conda input is buffered up to MaxInputBytes for random access. Hex detection buffers an outer TAR prefix; TAR, gem, and Hex entry bodies are streamed. Compression detected by content exposes a non-TAR payload as one member named "content", with size -1. The caller owns content and must close it when needed.
func OpenStreamBytes ¶ added in v0.8.1
func OpenStreamBytes(filename string, content []byte, options StreamOptions) (*Stream, error)
OpenStreamBytes reuses content without copying it, including ZIP and conda input. The caller must not modify the slice until the stream is closed.
func OpenStreamBytesContext ¶ added in v0.9.0
func OpenStreamBytesContext(ctx context.Context, filename string, content []byte, options StreamOptions) (*Stream, error)
OpenStreamBytesContext is OpenStreamBytes with cancellation, including while decoding or skipping buffered ZIP and conda entries.
func OpenStreamContext ¶ added in v0.9.0
func OpenStreamContext(ctx context.Context, filename string, content io.Reader, options StreamOptions) (*Stream, error)
OpenStreamContext is OpenStream with cancellation during reading and decoding. Cancellation is terminal. The caller owns content; canceling does not close it.
func (*Stream) Close ¶ added in v0.8.1
Close releases resources without draining or closing the input. Unread content and compressed trailers after a TAR end marker are not validated.
func (*Stream) Next ¶ added in v0.8.1
func (s *Stream) Next() (*StreamEntry, error)
Next discards the unread body and returns the next entry, or io.EOF. Header sizes, including sparse logical sizes, count against limits before the body is exposed. Unknown sizes count as bytes are read or skipped. MaxEntries counts visible entries; nested containers have a separate MaxEntries budget. Hidden TAR extension headers are excluded. Size and entry-count limit errors return the rejected entry alongside the error. Errors remain terminal unless explicitly cleared by Recover or Skip.
func (*Stream) Read ¶ added in v0.8.1
Read reads the current entry and returns io.EOF at its end or before Next. Non-EOF errors prevent further iteration.
func (*Stream) Recover ¶ added in v0.10.0
Recover abandons a failed ZIP entry so the next call to Next can continue. TAR-based formats, including conda, return errors.ErrUnsupported, as does a stream with no pending error. Cancellation and resource-limit errors remain terminal. Recovery does not validate the discarded body.
func (*Stream) Skip ¶ added in v0.10.0
Skip abandons an unread ZIP entry without decompressing or validating it. It also clears size-limit errors returned with an entry by Next. The entry counts toward MaxEntries, but its unread body does not consume expanded bytes. Other formats, missing entries and entries already read return ErrUnsupported. Cancellation, input limits and entry-count limits remain terminal.
type StreamEntry ¶ added in v0.8.1
StreamEntry includes link metadata needed to hash TAR links. ZIP symlink targets are stored in the entry body. Hard links refer to another TAR path.
type StreamOptions ¶ added in v0.8.1
type StreamOptions struct {
MaxInputBytes int64
MaxEntryBytes int64
MaxExpandedBytes int64
MaxEntries int
}
StreamOptions limits entry bodies, including skipped entries. Zero values use 512 MiB for byte limits and 100,000 entries; negative values are invalid. Decoder buffers and archive metadata require additional memory.