What's Changed in v2.16.1
New features
- The filesystem API tries several passwords on an entry imported from a zip file when its data is read. The core
ZipReaderAPI keeps one password per reader or per call. The newpasswordsoption lists the candidates, and the newrequestPasswordoption is a function asked for a password when every candidate has failed, or when there is none: it is called with the entry being read and the error raised by the last candidate, it can return a promise, e.g. to prompt the user, and it gives up by returningundefinedornull, in which case the entry fails withERR_INVALID_PASSWORD, or withERR_ENCRYPTEDwhen nothing was tried. Both options are accepted by theimport*()methods, by thereaderOptionsof theexport*()methods and by theget*()methods of a file entry. The candidates are tried in order: thepasswordorrawPasswordoption, then the passwords that already decrypted another entry of the same zip file, most recent first, then thepasswordslist, so the wrong candidates ahead of the right one are tried once for the archive, not once per entry. When several entries are read concurrently, e.g. byexport*()withbufferedWrite, onerequestPasswordcall is pending at a time, and the other entries try its answer before asking. With candidates in play, a ZipCrypto entry, whose one-byte check accepts one wrong password in 256, is checked on its header first without reading the content, the CRC-32 of the content is then verified whatevercheckCrc32says, and any failure of that read counts as a wrong password. An AES entry reports a failure of the read as-is.ERR_INVALID_PASSWORDSandERR_INVALID_REQUEST_PASSWORDare thrown when the options have the wrong type, and the latter also when the function returns a value of another type - An error raised while exporting because the data of an entry cannot be read now carries the entry as
entryand its name relative to the exported directory asentryName, next to theentryIdit already had, and keeps its owncause, e.g. the codec error behindERR_INVALID_COMPRESSED_DATAfor an entry imported from a corrupted zip file. The export used to replace the cause with an object holding the entry, a property that was not documented
Bug fixes
- A read or write of the WebAssembly codec torn down before the end of the entry, by a cancelled readable, an aborted signal, a failing source, a corrupted body or a wrong password, kept its two 64 KB buffers and its zlib state in the 16 MB heap of the module in every browser, because the codec freed them in the
cancel()hook of aTransformStreamtransformer, which Node.js and Deno call and no browser does. That codec runs for deflate whenuseCompressionStreamisfalseor the host has no native"deflate-raw", and its AES engine runs for the encrypted entries, whose contexts live in the same heap. About 120 aborted inflates or 40 aborted deflates exhausted the heap of the worker, or of the page without workers, and every later entry failed withallocation failed. The bundled zlib-streams 1.2.3 frees them on any teardown, the AES streams release their engine context the same way, and the wrappers of the entry stream cancel the codec when their own read fails. On a page that loads web-streams-polyfill, which Firefox below 102 needs, such a read used to error without cancelling the codec, so the pair stayed allocated for the life of the page - An empty
rawPassword, i.e. an emptyUint8Array, means no raw password, like an emptypasswordstring. Reading an encrypted entry with one used to wait forever for a key that was never derived, and writing an entry with one used to encrypt it with an empty key, so it could not be read back with thepasswordstring passed next to it
Documentation
EntryErrordocuments itsentryproperty, andERR_DUPLICATE_IMPORTED_ENTRYdocuments that itscause.entryholds theEntryMetaDataof the entry that could not be imported
Tests and continuous integration
- New tests cover the password candidates, with the ZipCrypto false accept found by brute force on the encryption header, the entry and the cause carried by an export error, the WebAssembly buffers and the AES contexts released by aborted reads and writes on the browser matrix, the heap capacity of the module after those teardowns, and the empty raw password on both sides
npm run test-deno-polyfill, part ofnpm run test-ci, runs the suite on Deno with web-streams-polyfill in place of the native streams,pipeTo()andpipeThrough()removed from the platform readables andCompressionStreamandDecompressionStreamremoved, which is what the Firefox 79 job runs and the only way to reproduce a failure of that job on a developer machine- The mangling audit of the test suite lists the internal fields whose name collides with a host property, e.g. a DOM one, and therefore ships unmangled. Four were renamed, with no API change
- The browser runner prints the message of a failing test with its stack
Full Changelog: v2.16.0...v2.16.1
Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com