Skip to content

v2.10.0

Latest

Choose a tag to compare

@gildas-lormeau gildas-lormeau released this 07 Sep 15:04

SingleFile CLI 2.10.0

New features

  • --max-appended-data-length sets the budget for the data appended after the compressed data of a self-extracting HTML file, 16361 bytes by default (it was 65535 until now). Readers tolerate trailing bytes only as far back as their end-of-archive scan reaches, and that varies by an order of magnitude: 65557 bytes for Python zipfile, 32768 for perl Archive::Zip, but 16383 for libarchive. The new default fits the narrowest window, so archives that used to fit the budget and still be unreadable by libarchive now are. Data over the limit is relocated ahead of the compressed data instead, as it already was above the old limit

CLI fixes and improvements

  • The scripts passed to --browser-script are joined with a newline. They were concatenated with nothing between them, so a file that did not end with a newline ran into the next one, and a file ending on a // comment commented out its first line
  • The options forwarded to the multi-page archive of --crawl-save-archive are derived from the list of compression options declared by single-file-core, instead of a list written by hand. A new option added to the core no longer needs a second edit here to reach that path, where it was silently ignored
  • With --browser-engine firefox, the assignment exposing SingleFile to the injected scripts is inserted after the bundle instead of being produced by rewriting it. The rewrite matched a declaration in the minified bundle with a regular expression, so a change in the way it is minified would have broken the injection silently
  • The help text of --max-resource-size says that the limit applies to every fetched resource, images, fonts, stylesheets, scripts, frames, videos and audios, and not only to the four types it used to name. The help text of --block-mixed-content says which resources it blocks

Fixes from single-file-core 1.5.125 and 1.5.126

  • An iframe holding an SVG document keeps its content. Such a document has no body, and the nesting check read body.tagName before anything else, so the frame threw and silently contributed nothing to the save
  • A sandboxed srcdoc iframe is saved as it renders. The frame answers for itself, but its answer was discarded because the parent had already fallen back to re-parsing the srcdoc attribute, which no script has run against
  • Only the winning @font-face rule is embedded when a page declares the same face more than once. Rules were de-duplicated by family and source together, so the same family, style and weight declared twice with different sources embedded both fonts, although the cascade only ever uses the last one. A source repeated inside a single rule is also embedded once, in the position its later declaration gives it
  • A @font-face rule left with no usable source is removed, instead of being written out as src: with nothing after it, which is an invalid declaration
  • Alternative font sources are narrowed by content type again. The font resources were not passed to the code that reads them, so a face whose sources carry no format() kept every alternative in the archive instead of the best one
  • A BMP or a GIF87a served without a content type is stored with its real extension. Both signatures were wrong, "NM" instead of "BM" and "GIF8;a" instead of "GIF87a", so neither format was ever recognised. The AVI signature was the one for WAV
  • With --block-mixed-content, a resource at a blob: URL is kept on an https page. The check compared the scheme against https: and a blob URL starts with blob:, although it inherits the origin of the page that created it
  • An image whose srcset could not be saved no longer keeps an empty srcset and sizes pair. Both attributes are removed, so the browser falls back to src instead of resolving an empty candidate list
  • A canvas displaying its captured drawing pins background-attachment, so a page rule setting it to fixed no longer stretches or crops the image
  • A frame the save leaves empty no longer carries a sandbox attribute that can no longer apply to anything
  • A resource fetched through the frame fallback carries the referrer of the page it belongs to
  • Image keeps a stable identity while a page is captured. The hook installed a getter that built a new function on every read, so Image === Image was false, instanceof Image failed for images the page had created, and Image.toString() revealed the wrapper. Element.prototype.getBoundingClientRect, CSS.paintWorklet.addModule and the hooked IntersectionObserver, FontFace, CSSStyleSheet and document.fonts functions report their native name, arity and source text as well
  • A self-extracting archive opened from the filesystem reads itself when the browser allows it. The whole-file read every other scheme uses was skipped outright for file:// URLs, leaving only the embedded recovery payload. Firefox performs the read and now opens archives saved without a payload; Chromium and Safari refuse it and fall back to the previous behaviour unchanged

Co-authored by Claude (Claude Code)