Help us shape the future of Go! Take the 2026 Go Developer Survey

archive

package
v0.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package archive reads a user-supplied archive under a policy, and hands back entries whose names are already safe keys.

Every application that accepts an archive needs the same defences, and getting any one of them wrong is a vulnerability rather than a bug, so they are here rather than in each app:

  1. an entry count cap, before anything is read;
  2. a declared-uncompressed-size cap, per entry and in total, read from the **central directory** — a bomb is refused on its claim rather than by inflating it to find out;
  3. the entry name never becomes a path. Zip takes no path of any kind and returns a key, so a name cannot reach a file operation;
  4. a name-length cap. The name does not reach a path on a correct design, but it does reach the metadata and from there a consumer's asset loader, so it is bounded and checked for text that disguises itself (control characters, a right-to-left override);
  5. collision detection on the key. This is the one that **corrupts instead of failing**: "walk/frame.png" and "jump/frame.png" collapse to one key once the directory is dropped, so an app writes both frames under one name while its metadata describes one, putting every later frame off by one and delivering a wrong result as a correct one.

What stays with the application is the per-entry content rule: what a frame may contain, how big an image may be, which types are acceptable as input. This package has no opinion on any of that.

The entries are metadata only until Entry.Open is called, and that reader is held to the size the entry declared, so the declared total bounds what an archive can ever yield.

Index

Constants

View Source
const DefaultMaxNameLength = 255

DefaultMaxNameLength bounds an entry key when Policy.MaxNameLength is zero. It is the longest name most filesystems and asset loaders accept.

Variables

View Source
var (
	// ErrPolicy reports an unusable Policy: a configuration error.
	ErrPolicy = errors.New("archive: policy needs MaxEntries, MaxTotalSize and MaxEntrySize above zero")
	// ErrTooManyEntries reports more entries than the policy allows.
	ErrTooManyEntries = errors.New("archive: too many entries")
	// ErrTooLarge reports a declared size over the policy, per entry or in
	// total. It is decided from the central directory, so an archive that
	// claims to be a bomb is refused without being inflated.
	ErrTooLarge = errors.New("archive: declared size over the limit")
	// ErrNameTooLong reports an entry key longer than the policy allows.
	ErrNameTooLong = errors.New("archive: entry name too long")
	// ErrUnsafeName reports a key that is not usable as one: empty, a path
	// of any kind, or text that disguises itself.
	ErrUnsafeName = errors.New("archive: unsafe entry name")
	// ErrDuplicateKey reports two entries resolving to one key. Refused
	// rather than overwritten: silently keeping the last one corrupts the
	// result instead of failing it.
	ErrDuplicateKey = errors.New("archive: duplicate entry key")
)

Errors a caller distinguishes: ErrPolicy is the application's own mistake and belongs in a 500, the rest are the client's and belong in a 422 with a message of the handler's choosing. None of them carries the entry's raw name, which is attacker-controlled text that a naive handler would put in a response body.

Functions

This section is empty.

Types

type Entry

type Entry struct {
	// Key identifies the entry. It is derived, never the raw name.
	Key string
	// Size is the uncompressed size the archive declares, already checked
	// against the policy.
	Size int64
	// contains filtered or unexported fields
}

Entry is one archive member whose key is already safe to use as an identifier: non-empty, no separators, no "." or "..", within the length cap, valid UTF-8 without control characters, and unique in the archive.

func Zip

func Zip(r io.ReaderAt, size int64, p Policy) ([]Entry, error)

Zip reads the central directory of a ZIP archive and returns its entries under p, without decompressing anything. It takes no path: a reader, a size and a policy in, safe keys out, which is what keeps an entry name from ever being an argument to a file operation.

The archive is read in full before any entry is returned, so a policy violation anywhere refuses the whole archive. An application that wants a partial result should split the archive instead.

func (Entry) Open

func (e Entry) Open() (io.ReadCloser, error)

Open reads the entry's content, bounded by the size it declared: a stream that tries to yield more is cut off with an error. archive/zip already rejects the simple inconsistency of a central directory that disagrees with the local header, so this is defence in depth, and it is what lets the declared total bound what the archive can ever yield.

Close it before opening the next entry's reader.

type Policy

type Policy struct {
	// MaxEntries is the most entries an archive may hold (required).
	MaxEntries int
	// MaxTotalSize is the largest sum of declared uncompressed sizes
	// (required). Many entries just under MaxEntrySize are a bomb too.
	MaxTotalSize int64
	// MaxEntrySize is the largest declared uncompressed size of one entry
	// (required).
	MaxEntrySize int64
	// MaxNameLength bounds the key in bytes (DefaultMaxNameLength when 0).
	MaxNameLength int
	// Key derives an entry's key from its name; the default is the name's
	// last element, which is what drops the directory. Whatever it
	// returns is validated like any other key, so an application cannot
	// opt out of the safe-key guarantee by supplying its own.
	Key func(name string) string
}

Policy bounds what Zip accepts. The three caps are required: there is no safe default for "how much of this archive may this application hold", and guessing one is how a bomb gets through.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL