preview

package
v0.80.1 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 15 Imported by: 0

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

View Source
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.

View Source
const MaxEpubChapters = 1000

MaxEpubChapters caps how many spine items we surface — hostile EPUBs can declare an absurd spine in a few KB.

Variables

View Source
var ErrEntryNotFound = errors.New("entry not found in archive")

ErrEntryNotFound is returned when the requested entry name isn't present.

View Source
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

func ComicPages(src Source, format Format) ([]string, error)

ComicPages lists the raster image entries of a CBZ/CBR in natural reading order (page2 < page10).

func EntryContentType

func EntryContentType(name string) (contentType string, ok bool)

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

func IsComicPage(name string) bool

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

func IsImageEntry(name string) bool

IsImageEntry reports whether the entry is a raster/vector image we can show.

func NaturalLess

func NaturalLess(a, b string) bool

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

func ReadEntry(src Source, format Format, name string, capBytes int64) ([]byte, error)

ReadEntry decompresses ONE entry (exact name match) capped at capBytes. Returns ErrEntryNotFound / ErrEntryTooLarge for the handler to map to 404/413.

func ResolveEpubRef

func ResolveEpubRef(baseDir, ref string) string

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

func SafeEntryName(name string) bool

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

func SanitizeChapter(doc []byte, resolve func(ref string) (string, bool)) []byte

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.

func List

func List(src Source, format Format) (entries []Entry, truncated bool, err error)

List enumerates the regular files inside the container, truncating at MaxListEntries. Unsafe entry names (absolute, "..") are silently skipped — they're hostile by definition and have no legitimate preview use.

type Epub

type Epub struct {
	Title    string   `json:"title"`
	Chapters []string `json:"chapters"`
}

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.

func ParseEpub

func ParseEpub(src Source) (*Epub, error)

ParseEpub reads the OCF container + OPF package of an EPUB (which is a zip) and returns the spine in reading order. Pure stdlib: archive/zip + encoding/xml.

type Format

type Format string

Format identifies how to decode a container by its file name.

const (
	FormatZip     Format = "zip"
	FormatTar     Format = "tar"
	FormatTarGz   Format = "tar.gz"
	FormatRar     Format = "rar"
	FormatUnknown Format = ""
)

func DetectFormat

func DetectFormat(name string) Format

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

type Source struct {
	ReaderAt io.ReaderAt
	Size     int64
	OpenSeq  func() (io.ReadCloser, error)
}

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.

Jump to

Keyboard shortcuts

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