Documentation
¶
Overview ¶
Package preview implements safe, read-only inspection of container files (zip/tar/tar.gz/rar archives, CBZ/CBR comics, EPUB books) for the universal file viewer. Everything here streams from an abstract byte source — a local file or a torrent file reader — and never touches the filesystem for extraction, so hostile entry names ("../../etc/cron.d/x") have no effect.
Hard limits guard against decompression bombs: listings are truncated at MaxListEntries and any single entry read is capped (the caller picks the cap per use case). Nested archives are never recursed into.
Index ¶
- Constants
- Variables
- func ComicPages(src Source, format Format) ([]string, error)
- func EntryContentType(name string) (contentType string, ok bool)
- func IsComicPage(name string) bool
- func IsImageEntry(name string) bool
- func NaturalLess(a, b string) bool
- func NewReaderAt(rs io.ReadSeeker) io.ReaderAt
- func NopCloser(r io.Reader) io.ReadCloser
- func ReadEntry(src Source, format Format, name string, capBytes int64) ([]byte, error)
- func ResolveEpubRef(baseDir, ref string) string
- func SafeEntryName(name string) bool
- func SanitizeChapter(doc []byte, resolve func(ref string) (string, bool)) []byte
- type Entry
- type Epub
- type Format
- type Source
Constants ¶
const ( // MaxListEntries truncates archive listings (a zip can declare millions of // entries in a few KB of central directory). MaxListEntries = 2000 // MaxEntryBytes caps a single inner-entry preview (text or image). MaxEntryBytes = 10 << 20 // 10 MiB // MaxComicPageBytes caps one comic page image (scans can be large). MaxComicPageBytes = 30 << 20 // 30 MiB // MaxChapterBytes caps one EPUB chapter document. MaxChapterBytes = 2 << 20 // 2 MiB // MaxResourceBytes caps one EPUB resource (image/css/font). MaxResourceBytes = 10 << 20 // 10 MiB )
Limits. Caps are per-request ceilings on DECOMPRESSED bytes — the zip-bomb guard. They're deliberately conservative: previews are for looking, the download button exists for everything else.
const MaxEpubChapters = 1000
MaxEpubChapters caps how many spine items we surface — hostile EPUBs can declare an absurd spine in a few KB.
Variables ¶
var ErrEntryNotFound = errors.New("entry not found in archive")
ErrEntryNotFound is returned when the requested entry name isn't present.
var ErrEntryTooLarge = errors.New("entry exceeds preview size limit")
ErrEntryTooLarge is returned when an entry exceeds the requested cap while decompressing — distinguishable from generic I/O errors so handlers can map it to 413.
Functions ¶
func ComicPages ¶
ComicPages lists the raster image entries of a CBZ/CBR in natural reading order (page2 < page10).
func EntryContentType ¶
EntryContentType classifies an inner entry name for preview serving. Returns the Content-Type to use and whether the type is allowed inline. Anything not allowed must be refused (the UI offers full-file download of the container instead — we don't proxy arbitrary bytes out of archives).
func IsComicPage ¶
IsComicPage reports whether the entry counts as a comic page (raster only — no comic ships SVG pages, and excluding it avoids the scripting headache).
func IsImageEntry ¶
IsImageEntry reports whether the entry is a raster/vector image we can show.
func NaturalLess ¶
NaturalLess compares two strings "the way humans expect": digit runs compare numerically, the rest case-insensitively. Needed for comic pages — plain lexicographic sort puts "page10.jpg" before "page2.jpg" and scrambles the reading order of most CBZ files in the wild.
func NewReaderAt ¶
func NewReaderAt(rs io.ReadSeeker) io.ReaderAt
NewReaderAt wraps rs into a goroutine-safe io.ReaderAt.
func NopCloser ¶
func NopCloser(r io.Reader) io.ReadCloser
NopCloser returns r as an io.ReadCloser with a no-op Close, preserving the Seeker interface when present (archive/tar skips entry bodies via Seek instead of reading them when its source can seek).
func ReadEntry ¶
ReadEntry decompresses ONE entry (exact name match) capped at capBytes. Returns ErrEntryNotFound / ErrEntryTooLarge for the handler to map to 404/413.
func ResolveEpubRef ¶
ResolveEpubRef resolves a (possibly URL-encoded) relative href against the directory of the referencing document, returning a zip-entry path — or "" when the ref escapes the archive or isn't a relative file reference.
func SafeEntryName ¶
SafeEntryName reports whether an archive entry name is safe to surface and match: relative, no "..", no NUL, not empty. We never write entries to disk, so this is defense in depth (it also keeps hostile names out of listings).
func SanitizeChapter ¶
SanitizeChapter neutralizes active content in an EPUB chapter document and rewrites relative resource references (images, stylesheets) through the resolve callback. resolve receives the raw relative ref and returns the URL to substitute, or ok=false to neutralize the ref entirely ("#").
Types ¶
type Entry ¶
type Entry struct {
Name string `json:"name"`
Size int64 `json:"size"`
Dir bool `json:"dir,omitempty"`
}
Entry is one item in an archive listing.
type Epub ¶
Epub is the parsed reading manifest of an EPUB: the chapter documents in spine (reading) order, plus the title. Chapter hrefs are zip-entry paths resolved against the OPF directory — ready to feed back into ReadEntry.
type Format ¶
type Format string
Format identifies how to decode a container by its file name.
func DetectFormat ¶
DetectFormat maps a container file name to its format. CBZ/CBR are just zip/rar with a different extension; EPUB is zip (handled by epub.go but listable as zip too).
type Source ¶
Source abstracts where the container bytes come from. Local files provide a real *os.File (ReaderAt for free); torrent files provide the anacrolix ReadSeeker wrapped by NewReaderAt. OpenSeq hands out a fresh reader positioned at byte 0 for the sequential formats (tar, rar) — for torrents that's a Seek(0) on the same underlying reader, which is fine because the preview handlers never interleave random and sequential access.