Skip to content

Commit 2247054

Browse files
authored
vfs: add ZipProvider
Add a node:vfs provider backed by a node:zlib ZIP archive - a ZipBuffer held in memory or a ZipFile on disk - that exposes the archive's members as a virtual filesystem tree. Directories are recognized both explicitly (a "name/" entry) and implicitly (any entry under "name/"), and a file opened for writing commits its content as a new archive entry when its handle is closed. The provider is read-only unless the backing archive is writable, and offers both asynchronous and synchronous operations. Available as vfs.ZipProvider. Signed-off-by: Philipp Dunkel <pip@pipobscure.com> PR-URL: #64915 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 3942bf7 commit 2247054

7 files changed

Lines changed: 1671 additions & 4 deletions

File tree

doc/api/vfs.md

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ Mounting a VFS only redirects supported [`node:fs`][] calls whose resolved paths
3838
are under the mount point. It does not prevent code from using other paths or
3939
other Node.js APIs to access resources available to the process.
4040
[`RealFSProvider`][] maps VFS paths under its configured root and rejects paths
41-
that resolve outside that root, but that check is not a security boundary. Do
42-
not rely on VFS to run untrusted code; use operating-system-level isolation,
43-
such as separate users, containers, or platform sandboxes, when a security
44-
boundary is required.
41+
that resolve outside that root, but that check is not a security boundary.
42+
[`ZipProvider`][] has no real file-system paths of its own to escape; its
43+
entries only ever exist within the archive's own namespace. Do not rely on VFS
44+
to run untrusted code; use operating-system-level isolation, such as separate
45+
users, containers, or platform sandboxes, when a security boundary is
46+
required.
4547

4648
## Basic usage
4749

@@ -523,6 +525,55 @@ added: v26.4.0
523525

524526
The resolved absolute path used as the root.
525527

528+
## Class: `ZipProvider`
529+
530+
<!-- YAML
531+
added: REPLACEME
532+
-->
533+
534+
A provider that exposes the entries of a ZIP archive - either a
535+
[`zlib.ZipBuffer`][] (in memory) or a [`zlib.ZipFile`][] (on disk) - through
536+
the VFS API. `provider.readonly` reflects the archive's own
537+
[`zipFile.writable`][] flag: a `ZipBuffer` is always writable, and a
538+
`ZipFile` is writable only when opened with `{ writable: true }`.
539+
540+
Directories are recognized both explicitly (an entry whose name ends in `/`)
541+
and implicitly (any entry name starting with `"<dir>/"`). `readdir()` does
542+
not support `{ recursive: true }`. Because a ZIP member cannot be edited or
543+
read in place - only fully written or fully decompressed - a file opened for
544+
writing only commits its content (as a new archive entry) when the handle is
545+
closed.
546+
547+
Every method has a synchronous counterpart (`openSync()`, `statSync()`,
548+
`readdirSync()`, and so on), backed by the equally complete synchronous
549+
surface [`zlib.ZipBuffer`][]/[`zlib.ZipFile`][] expose. As with those, the
550+
synchronous methods here block the Node.js event loop and further JavaScript
551+
execution until the operation - including any deflate/inflate pass -
552+
completes.
553+
554+
```cjs
555+
const vfs = require('node:vfs');
556+
const zlib = require('node:zlib');
557+
const { readFileSync } = require('node:fs');
558+
559+
async function main() {
560+
const zip = new zlib.ZipBuffer(readFileSync('archive.zip'));
561+
const archiveVfs = vfs.create(new vfs.ZipProvider(zip));
562+
563+
console.log(await archiveVfs.promises.readdir('/'));
564+
await archiveVfs.promises.writeFile('/new.txt', 'hello');
565+
}
566+
main();
567+
```
568+
569+
### `new ZipProvider(source)`
570+
571+
<!-- YAML
572+
added: REPLACEME
573+
-->
574+
575+
* `source` {zlib.ZipBuffer|zlib.ZipFile} An already-open archive.
576+
526577
## Implementation details
527578

528579
### `Stats` objects
@@ -544,6 +595,7 @@ fields use synthetic but stable values:
544595
[`RealFSProvider`]: #class-realfsprovider
545596
[`VirtualFileSystem`]: #class-virtualfilesystem
546597
[`VirtualProvider`]: #class-virtualprovider
598+
[`ZipProvider`]: #class-zipprovider
547599
[`fs.BigIntStats`]: fs.md#class-fsstats
548600
[`fs.Stats`]: fs.md#class-fsstats
549601
[`import.meta.resolve()`]: esm.md#importmetaresolvespecifier
@@ -555,5 +607,8 @@ fields use synthetic but stable values:
555607
[`vfs.mountPointURL`]: #vfsmountpointurl
556608
[`vfs.mountPoint`]: #vfsmountpoint
557609
[`vfs.unmount()`]: #vfsunmount
610+
[`zipFile.writable`]: zlib.md#zipfilewritable
611+
[`zlib.ZipBuffer`]: zlib.md#class-zlibzipbuffer
612+
[`zlib.ZipFile`]: zlib.md#class-zlibzipfile
558613
[loading from `node_modules` folders]: modules.md#loading-from-node_modules-folders
559614
[the global folders]: modules.md#loading-from-the-global-folders

0 commit comments

Comments
 (0)