zlib: add ZIP archive support to zlib (ZipFile,ZipBuffer,ZipEntry) - #64339
zlib: add ZIP archive support to zlib (ZipFile,ZipBuffer,ZipEntry)#64339pipobscure wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #64339 +/- ##
==========================================
+ Coverage 90.17% 90.31% +0.13%
==========================================
Files 746 760 +14
Lines 242776 246928 +4152
Branches 45741 46578 +837
==========================================
+ Hits 218929 223003 +4074
- Misses 15347 15411 +64
- Partials 8500 8514 +14
🚀 New features to boost your workflow:
|
This comment was marked as outdated.
This comment was marked as outdated.
0f2b879 to
4258e94
Compare
Codecov flagged low patch coverage on lib/internal/zip.js and lib/internal/vfs/providers/archive.js in nodejs#64339. Add tests exercising Zip64 extra-field parsing, DOS date/time edge cases, streaming-entry state guards, decodeMemberStream/decodeMemberSync's duplicated error branches, the ZipBuffer/ZipFile iteration protocols, several on-disk ZipFile error paths, and ArchiveFileHandle's direct read/write/stat/ truncate surface plus a handful of provider-level error branches the existing tests didn't reach.
2a56a15 to
27ab6b4
Compare
|
See also #45651 |
Thanks @bakkot !!! I think the time has come for it on the one hand, and on the other I added some „motivation“ links earlier. Here some more detail: Based on this, we can modify the loader to directly load from an archive. If we do that, we get application bundles. pipobscure#5 & pipobscure#6 Which can then in turn be used to easily create application bundles: https://github.com/pipobscure/experimental-sea So the world had changed enough that it‘s worth proposing again. |
f157686 to
233f865
Compare
|
I like this a lot. It's likely better to split this into 2 PRs, one for Zip support and one for VFS-Zip, so the Zip support could theoretically be backportable on its own. |
This comment was marked as outdated.
This comment was marked as outdated.
|
As per @mcollina I split this into two PRs. I have the vfs-provider ready to go as follow up one (as it depends on this being merged) I also made sure that the streaming side of things was actually as clean as I intended, and added a few more tests. |
I've added a bunch more tests and compared to what go/python/info-zip do. (info-zip is cli exercise, so it's not entirely clear what gets tested). I also gave zip.js an once over and concluded that it was too large a file (it originated from separate files that I've had for ages combined into one). So I split it back out so it would be easier to review. And gave that another look. |
|
Rebased on latest main to resolve the conflict |
|
The failing mac tests seem to be entirely unrelated can someone verify my deduction please. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
Everything is green, CI is ok, we are missing another @nodejs/tsc approval as this a large PR. |
targos
left a comment
There was a problem hiding this comment.
I am reviewing. You can dismiss my request for changes if I don't answer by tomorrow.
panva
left a comment
There was a problem hiding this comment.
import { createGzip } from 'node:zlib'This stable import emits ZIP experimental warning because of how our ESM facade evaluates the getters.
Add ZIP archive support to the node:zlib module through three classes and a set of helpers: - ZipEntry: a single archive member, with buffered reads (content()), bounded-memory streaming reads (contentIterator()), and create()/createStream() for building members. - ZipFile: random access to an archive backed by a file descriptor, reading members lazily without retaining their content and writing new members in place; opened with open()/openSync(). - ZipBuffer: a zero-copy, in-memory view over an archive already held in a Buffer. createZipArchive() serializes a sequence of entries into an archive byte stream, and setMaxZipContentSize() bounds the default in-memory decompression size. Every operation has both an asynchronous and a synchronous form. Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
|
I rebased on main. Then I addressed the docs issue from @marco-ippolito and the import issue by @panva . I also go a security review from @mcollina and dealt with the issues that raised. Here are the security-review issues we addressed: Numbered findings
Additional concerns
|
So we can't easily see and track the requested diff now. Unfortunate as otherwise we could review increments instead. |
Apologies. I thought the “requested modus operandi “ was to keep it to a single commit. If that’s wrong then I’ll change the approach for the future. |
No worries. FYI the commit-queue can land fixup commits (autosquash), or we can apply a label to squash, or if the PR has multiple things that can/should be backported independently (e.g. part is semver-major, part isn't) we can also land with rebase. |
contentIterator() (and ZipFile.stream()) must not apply the default getMaxZipContentSize() limit the way content()/contentSync() do. That default guards a single large buffer allocation, which the streaming path never makes: streaming is the bounded-memory way to read arbitrarily large members. Applying the ceiling there rejected legitimate reads of members larger than it - including the >4 GiB streaming case in test/pummel/test-zlib-zip-slow.js. Output stays bounded per chunk to the declared uncompressed size, and a caller that wants an explicit cap can still pass options.maxSize.
|
Loving tests right now. It caught me breaking streaming when doing the sec-fixes earlier. |
|
Can someone trigger CI, so that we can get this over the line? |
Summary
Add ZIP archive support to the node:zlib module through three classes
and a set of helpers:
bounded-memory streaming reads (contentIterator()), and
create()/createStream() for building members.
reading members lazily without retaining their content and writing
new members in place; opened with open()/openSync().
in a Buffer.
createZipArchive() serializes a sequence of entries into an archive
byte stream, and setMaxZipContentSize() bounds the default in-memory
decompression size. Every operation has both an asynchronous and a
synchronous form.
P.S.: my CLA should be on file and I wrote this myself so COO is declared