Skip to content

v2.8.28

Choose a tag to compare

@gildas-lormeau gildas-lormeau released this 13 Jul 15:41

What's Changed

This release contains an unusually large number of fixes resulting from an in-depth audit of the codebase, along with several new features. All fixes are covered by new regression tests, and the produced archives were validated against external tools (7z, unzip, ditto, Python).

New features

  • Add the checkAmbiguity option to ZipReader to detect and reject ambiguous archives: concatenated archives, trailing central directory records, mismatched zip64 records, duplicate filenames, and local file headers contradicting the central directory
  • Add the fetch option to HttpReader and HttpRangeReader in #655
  • Add the workerStarvationTimeout option to configure() and run starved codecs without worker to prevent deadlocks
  • Support the offset option with split archives
  • Support the rawPassword option with ZipCrypto
  • Add index.cjs/index-native.cjs subpath exports and a react-native condition in package.json in #651
  • Add the ERR_AMBIGUOUS_ARCHIVE, ERR_INVALID_COMPRESSED_DATA and ERR_UNDEFINED_READER error constants

Deflate64

  • Fix decompression of entries containing matches longer than 258 bytes (length code 285) in #661 — the wasm fix was contributed by @Chagrins in gildas-lormeau/zlib-streams#1
  • Add the deflate64 option to the reserved properties of inline web workers (web workers silently decompressed Deflate64 entries with the deflate32 codec)

Security

  • Always verify the AES authentication code when decrypting entries
  • Compare the ZipCrypto password verification byte in constant time
  • Throw ERR_UNSUPPORTED_CRYPTO_API instead of falling back to an insecure pseudo-random generator when the Web Crypto API is unavailable, and remove the unused pseudo-random fallback from sjcl.js
  • Detect overlapping entries regardless of check order, and when reading entries multiple times
  • Throw zip.js errors instead of RangeError on malformed archives

Behavior changes

No API was removed or changed, but some intentional behavior changes are worth noting:

  • dataDescriptorSignature now defaults to true: archives grow by 4 bytes per entry when data descriptors are used, and output bytes differ (fixes extraction with macOS ditto)
  • add() with passThrough set and no reader throws ERR_UNDEFINED_READER instead of silently producing corrupt output
  • add() with an unsupported compressionMethod throws instead of producing an unreadable entry
  • Extra fields totaling over 64KB throw instead of writing a corrupt header
  • add(name) without reader now writes a well-defined empty stored entry
  • Extended timestamps outside the signed 32-bit range are omitted (the NTFS field carries the exact value); reads treat the field as signed per the spec
  • FS.getById() no longer returns detached entries, and options objects passed to add(), addFile(), exportZip() and HttpRangeReader are no longer mutated
  • The FS type declaration no longer pretends to extend ZipDirectoryEntry (the runtime never did); TypeScript code relying on the incorrect declaration may need adjusting

ZipReader fixes

  • Fix reading split zip files with a central directory spanning multiple segments (zip32 and zip64)
  • Fix reading prepended data with zip64 offsets
  • Fix extracting prepended data with multiple entries
  • Fix CRC-32 and AES compression method in prepended entries
  • Read the entry count from the total field in the end of central directory record
  • Fix getData() resetting lastAccessDate and creationDate on entries
  • Derive the directory flag from the final filename after unicode path override
  • Propagate entry data errors in ZipReaderStream
  • Fix AES decryption buffering
  • Fix cp437/BOM text decoding and harden crypto helpers
  • Fix central directory, encrypted flag and timestamp handling
  • Support readers returning subarray views
  • Avoid mutating caller options in checkPassword

ZipWriter fixes

  • Fix zip64 detection for encrypted entries
  • Fix the "version needed" field for zip64 offset entries
  • Throw an error when sizes overflow without zip64
  • Use zip64 when values equal the 32-bit/16-bit sentinels
  • Write the local zip64 extra field for streamed zip64 entries
  • Fix the local zip64 compressed size for encrypted STORE and passthrough entries
  • Account for the zip64 trailer in the central directory length
  • Fix crash and offsets when headers cross split segment boundaries
  • Fix double-counted size in the split data writer and exact-fill handling in SplitDataWriter
  • Fix duplicate filename check after a failed add()
  • Fix offset tracking after failed entry writes
  • Advance the offset by the bytes actually flushed on aborted buffered writes
  • Preserve add() call order for concurrently added entries
  • Fix deadlock between concurrent add() calls and createTempStream
  • Propagate close errors in ZipWriterStream transform
  • Fix directory name duplicates, comment validation and ZipWriterStream errors
  • Fix prependZip compression level bits and compressionMethod/level consistency
  • Fix USDZ extra field length off by 2
  • Write the Info-ZIP Unix (0x7855) extra field per spec (fixed 2-byte uid/gid)
  • Write extra field type and length as 16-bit little-endian
  • Harden entry metadata written by ZipWriter

Web workers fixes

  • Release the writable lock when a worker task fails
  • Fix worker slot double-release and stale messages on task failure
  • Fix variable shadowing breaking error.outputSize in web workers
  • Handle worker loading errors and honor workerURI configuration changes
  • Reset the terminated flag when creating a new worker
  • Fix codec stream issues with small chunk sizes, opaque errors and wasm memory leaks

Filesystem (zip.fs) fixes

  • Fix exportZip() silently dropping entries when a reader fails
  • Fix importZip() directory metadata, name collisions and read options
  • Reset the filesystem tree in FS.importZip() like other imports
  • Fix remove() leaving descendant entries registered in the filesystem
  • Register entries only when they are attached to the tree
  • Avoid stack overflows when exporting deeply nested trees
  • find(): fall back to exact full-name match for verbatim slash names
  • getArrayBuffer(): read via getUint8Array so all entry data types work
  • Do not mutate the options object passed to exportZip()
  • Add FS.exportZip() delegation and default its options
  • Fix the FS type declaration to match its actual runtime surface

I/O fixes

  • Fix byte order in Data64URIWriter with sub-3-byte writes
  • Fix HTTP, split disk and blob I/O issues, including HttpReader end-of-central-directory caching

Performance

  • Make ChunkStream iterative and tolerate invalid chunk sizes (supersedes #660 — reading a 50MB file with a small chunk size went from ~4900ms to ~80ms)
  • Avoid copying the entry map on each add()

Documentation

  • Document that entry filenames must use forward slashes as separator in #654
  • Fix dash/dot switchup in README by @KTibow in #650

Credits

  • Most of the bugs fixed in this release were found and fixed by Claude (Fable) during several in-depth reviews of the codebase, which also produced the new regression tests
  • @Chagrins fixed the length-code extra bits bug in the Inflate wasm implementation (gildas-lormeau/zlib-streams#1), part of the fix for #661

Full Changelog: v2.8.26...v2.8.28