@openclaw/fs-safe 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +17 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-gzip-tail.d.ts +2 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +20 -3
- package/dist/archive-merge.js +1 -1
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +3 -2
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +67 -66
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +20 -8
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +2 -1
- package/dist/bounded-read.d.ts +7 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +73 -43
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +23 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +91 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +10 -1
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +27 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +191 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +123 -0
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-hash.d.ts +6 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +44 -8
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +2 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +1 -0
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +8 -4
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +2 -6
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +13 -14
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +22 -24
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +55 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +26 -31
- package/dist/mutation-authority.d.ts +8 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +24 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +18 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +1 -0
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +3 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +38 -48
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +5 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +50 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +6 -5
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +51 -26
- package/dist/root-directory-list.d.ts +24 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +201 -0
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +3 -0
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +6 -50
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +330 -210
- package/dist/root-options.d.ts +66 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +56 -1
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-symlink-policy.d.ts +13 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +1 -4
- package/dist/root.d.ts +3 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +4 -4
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +2 -1
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +3 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.js +2 -2
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +6 -26
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +6 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +25 -0
- package/docs/advanced.md +18 -4
- package/docs/archive.md +22 -7
- package/docs/atomic.md +7 -0
- package/docs/contributing.md +7 -0
- package/docs/copy.md +86 -0
- package/docs/durability.md +17 -2
- package/docs/errors.md +1 -1
- package/docs/local-roots.md +8 -1
- package/docs/native.md +14 -1
- package/docs/permissions.md +20 -10
- package/docs/positional-read.md +63 -0
- package/docs/root.md +166 -4
- package/docs/security-model.md +14 -0
- package/docs/timing.md +2 -0
- package/docs/types.md +18 -11
- package/docs/walk.md +54 -5
- package/docs/writing.md +20 -2
- package/package.json +13 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.10.0 - 2026-09-13
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Faster ZIP and TAR reads:** read archive members without temporary disk snapshots, reuse admitted native buffers, and reduce integrity-check and decompression overhead while retaining full validation.
|
|
10
|
+
- **Directory copies with native acceleration:** use `copyTree` to prefer or require APFS clones, Btrfs snapshots, or parallel ReFS/XFS reflinks, or choose portable byte copying for controlled, immutable templates and checkouts.
|
|
11
|
+
- **Guarded file copies:** copy from checked Root sources with exclusive publication, cancellation that waits for writes, and optional native cloning; copied data stays independent of its source.
|
|
12
|
+
- **Faster Windows byte copies:** reuse bounded buffers and parallelize directory copying, with native transfers between checked handles in automatic mode.
|
|
13
|
+
- **Reuse buffers for file reads:** new async and sync positional readers fill caller-owned buffers without changing the file's current offset; hashing gains byte limits and cancellation.
|
|
14
|
+
- **Walk large directories incrementally:** Root walks bound metadata work by the entry budget, with an opt-in filesystem-order stream for directories too wide to enumerate up front.
|
|
15
|
+
- **Recheck live access authority:** Root mutations can verify caller-owned leases or cancellation immediately before dispatch, and new symlink policies allow contained parent-directory aliases while rejecting final symlinks.
|
|
16
|
+
|
|
17
|
+
### Compatibility and upgrade notes
|
|
18
|
+
|
|
19
|
+
- **Ambiguous paths may now reject.** Reads preserve actual symlink and parent traversal instead of normalizing it away; mutations reject ambiguous symlink/parent combinations. Use the new explicit parent-alias policies when that behavior is intended.
|
|
20
|
+
- **Mutation failures are reported more accurately.** Failed parent-directory checks after fallback moves and removals now reject instead of reporting success. Treat rejection after dispatch as a potentially completed mutation, not proof that nothing changed.
|
|
21
|
+
- **Windows fallback writes honor durability settings.** Root defaults and per-call overrides now apply to JavaScript write/create fallbacks. Use `durable: false` explicitly for reconstructible data when syncing is unnecessary.
|
|
22
|
+
|
|
23
|
+
### Archive reads
|
|
24
|
+
|
|
25
|
+
- Retain admitted ZIP input buffers and parsed directories across native inspection and selected-member reads, removing disk staging and archive handoff copies. JavaScript ZIP reads also consume the admitted in-memory archive directly.
|
|
26
|
+
- Retain native TAR input and admitted member offsets. Plain TAR copies only the selected range after complete validation; gzip, zstd, and bzip2 replay bounded decompression with framing, trailer, padding, and limit checks intact. JavaScript TAR/gzip reads also avoid disk staging, with gzip output chunks matched to the WASM input window.
|
|
27
|
+
- Accelerate ZIP integrity checks with Node's native CRC32 on Node 22.2 and newer, retaining checksum validation and compatibility with earlier Node 22 versions. Reuse the strict UTF-8 decoder for TAR metadata without changing filename validation or BOM handling.
|
|
28
|
+
- Give each native archive pass its own abort signal so completed inspection cannot mask an extraction deadline.
|
|
29
|
+
|
|
30
|
+
### File reads, hashing, and directory walks
|
|
31
|
+
|
|
32
|
+
- Add `readFileWindowFully()` and `readFileWindowFullySync()` to fill caller-owned buffers through short reads, stop at EOF, and preserve descriptor offsets and ownership. Async cancellation waits for the pending read to settle before the buffer can be reused.
|
|
33
|
+
- Bound speculative allocations for large or exhausted files and accelerate synchronous bounded reads of regular files up to 16 MiB without extra chunk copies; retain one-read async performance through the default Root byte budget.
|
|
34
|
+
- Add byte limits and cooperative cancellation to `sha256File`, preserving descriptor ownership and waiting for native work to stop before rejecting. Batch larger JavaScript hash reads in bounded buffers up to 256 KiB to reduce filesystem calls.
|
|
35
|
+
- Bound Root walk metadata batches by the global entry budget and stop batches at directories and symlinks before descent. Add `order: "filesystem"` for incremental directory streaming with bounded lookahead; sorted traversal remains the default, and unbounded sorted walks retain fast snapshots.
|
|
36
|
+
- Reduce walk overhead by sharing equivalent directory checks and synchronous entry classification, reusing joined paths, and avoiding an extra promise per async entry. Preserve both operation and close failures during disposal, and report a followed symlink target's size consistently in filters and returned metadata.
|
|
37
|
+
- Speed up POSIX containment and filename helpers while preserving traversal rejection, Unicode handling, reserved names, and collision-resistant install names.
|
|
38
|
+
|
|
39
|
+
### File and directory copying
|
|
40
|
+
|
|
41
|
+
- Extend `Root.copyIn` with guarded Root sources, exclusive publication, settled cancellation, exact publication receipts, and optional native file cloning while keeping copied data independent. Share `clone: "auto" | "always" | "never"` with `copyTree`; Root keeps its `"never"` default, which uses ordinary reads and writes without clone or copy-offload calls.
|
|
42
|
+
- Add `copyTree` in `@openclaw/fs-safe/copy` with `clone: "auto"` (default), `"always"`, and `"never"` policies. Automatic copying prefers native cloning and falls back to byte copying only when the binding or filesystem capability is unavailable, or cloning cannot cross filesystems; strict cloning never falls back, and ordinary copying avoids clone and copy-offload calls. Destinations must be absent, and cancellation waits for admitted writes to settle.
|
|
43
|
+
- Speed up Windows directory byte copies with bounded parallelism, reusable 1 MiB buffers, and native transfers between checked handles in automatic mode. Honor copy concurrency across portable backends and settle admitted writes before reporting failures or cancellation.
|
|
44
|
+
- Support APFS directory clones, Btrfs subvolume preparation and snapshots, and parallel ReFS/XFS reflinks through `probeTreeClone` and `createCloneSource`. Preserve XFS file and directory extended attributes and ACLs, and restore directory timestamps after APFS bulk cloning.
|
|
45
|
+
- Add batched `readCloneFileMetadata` for APFS clone IDs and file metadata. These are point-in-time observations, not authorization or proof that later contents remain unchanged.
|
|
46
|
+
- Document metadata and filesystem limits: APFS directory cloning does not guarantee descendant ACL preservation or inheritance, Btrfs snapshots omit nested subvolume contents, and ReFS rejects unsupported reparse points and alternate data streams. Portable copying does not promise ownership, ACL, extended-attribute, alternate-stream, or sparse-layout preservation. Callers retain responsibility for source immutability, permission policy, and recovery after a failed or aborted copy.
|
|
47
|
+
|
|
48
|
+
### Root policies and path safety
|
|
49
|
+
|
|
50
|
+
- Add composed Root `assertBeforeMutation` callbacks that recheck live caller authority immediately before mutation dispatch, including each buffered-write chunk and direct file removal. Root-level and per-call checks both apply; refusal errors and owned cleanup are preserved across native and JavaScript writers.
|
|
51
|
+
- Add `symlinks: "follow-parents-within-root"` for reads and opt-in `mutationSymlinks` policies, following contained directory aliases while rejecting final symlinks at absolute root entry, open, and publication boundaries.
|
|
52
|
+
- Preserve checked canonical and raw traversal in Root, absolute-path, local-root, and `openRootFile`/`openRootFileSync` reads, including home expansion and parent components that cancel a missing prefix. Validation can no longer normalize away a rejected link or select a different in-root file; local `requireFile` reads retain final-symlink rejection.
|
|
53
|
+
- Pin Root directory identities as bigint values, reject indistinguishable numeric inode replacements, and fail closed after bounded retries when Windows root identity remains unknown. Require a verified opened identity before inheriting an existing write target's mode.
|
|
54
|
+
- Preserve Linux native directory-only open flags so the kernel rejects non-directories before FIFO blocking or truncation, while removing the redundant post-open stat.
|
|
55
|
+
- Expand leading home-directory prefixes before resolving parent segments, so `~/../file` resolves against the home directory's parent; keep other tildes literal. Shorten only the home directory and its descendants in error messages, preserving similarly prefixed sibling paths.
|
|
56
|
+
- Resolve Windows ACL principal names such as `constructor` and `__proto__` as real dictionary keys, preserving SID lookup and translated entries.
|
|
57
|
+
- Preserve Unicode Windows paths during fallback permission inspection by reading structured SID and access-mask facts instead of localized command output.
|
|
58
|
+
|
|
59
|
+
### Writes, moves, and cleanup
|
|
60
|
+
|
|
61
|
+
- Check the complete encoded newline before Root append, avoiding duplicated or missing separators in UTF-16LE files on Windows and other platforms.
|
|
62
|
+
- Preserve unowned staging replacements after Windows fallback write or sync failures, and retain exact parent-directory identities so owned partial-file cleanup works even when Windows directory indexes exceed numeric precision.
|
|
63
|
+
- Report failed post-operation parent checks after fallback moves and removals, including moved source parents, instead of silently reporting success.
|
|
64
|
+
- Close publication descriptors when initial inspection fails and relinquish descriptor numbers before potentially failing closes, preventing cleanup from closing a reused descriptor.
|
|
65
|
+
- Keep move-fallback publication and cleanup tied to the initially admitted staging identity, preserve substituted paths after copy failures, and reject writes that make no progress.
|
|
66
|
+
- Preserve recreated temporary paths after exit cleanup observes the original name missing; absence no longer authorizes removal of a later replacement.
|
|
67
|
+
- Allow explicitly authorized filesystem-root descendants in Trash moves and keep reservation-directory names bounded for long source filenames.
|
|
68
|
+
|
|
69
|
+
### Locks and durable queues
|
|
70
|
+
|
|
71
|
+
- Honor finite timeouts and retry delays above Node's single-timer limit by rearming bounded timers instead of expiring after approximately 1 ms.
|
|
72
|
+
- Keep zero-delay lock retries finite when a large backoff factor overflows, preserving retry and timeout budgets.
|
|
73
|
+
- Skip invalid delivered-marker names during durable queue batch loading, preserving malformed files while continuing to load valid pending entries.
|
|
74
|
+
- Read durable queue entries through the shared bounded buffer and admitted file-size hint, reducing filesystem calls and chunk copies while retaining exact identity and byte-limit checks.
|
|
75
|
+
|
|
76
|
+
### Validation and maintenance
|
|
77
|
+
|
|
78
|
+
- Add a method-by-method benchmark with callable API coverage checks, native/fallback reports, and fixture setup outside measurement.
|
|
79
|
+
- Give the full documentation-build smoke test a bounded longer runtime on slow Windows runners. Validate retained native archive buffers across concurrent reads and forced garbage collection; use efficient byte comparisons in large-buffer coverage tests without relaxing timeouts.
|
|
80
|
+
|
|
3
81
|
## 0.9.0 - 2026-09-11
|
|
4
82
|
|
|
5
83
|
**Highlights:** Faster bulk extraction and configurable durability, with Windows filesystem fixes and reliable mixed-version lock cleanup.
|
package/README.md
CHANGED
|
@@ -133,6 +133,11 @@ await fs.remove("notes/archive/today.txt");
|
|
|
133
133
|
|
|
134
134
|
`root()` takes the trusted directory; relative paths in subsequent calls are resolved against it. Defaults you pass to `root()` apply to every call below; per-call options override them.
|
|
135
135
|
|
|
136
|
+
`copyIn()` also accepts `{ root: sourceRoot, relativePath }`, `overwrite: false`,
|
|
137
|
+
and `clone: "auto"` for guarded, exclusive file copies with optional native
|
|
138
|
+
copy-on-write acceleration. Byte limits, cancellation, and publication receipts
|
|
139
|
+
are described in the [Root copy contract](docs/root.md#writes).
|
|
140
|
+
|
|
136
141
|
When you need metadata or a `FileHandle`:
|
|
137
142
|
|
|
138
143
|
```ts
|
|
@@ -148,6 +153,17 @@ await fs.create("notes/README.md", "seed\n"); // throws if it already exists
|
|
|
148
153
|
|
|
149
154
|
`write()` replaces file contents by default; pass `{ overwrite: false }` or use `create()` when an existing file should be an error. `move()` defaults to no clobber because it can otherwise delete an unrelated target while also consuming the source. Pass `{ overwrite: true }` when replacing the target is intended.
|
|
150
155
|
|
|
156
|
+
Mutating methods accept `assertBeforeMutation: () => void` for live lease or
|
|
157
|
+
cancellation checks immediately before filesystem dispatch. Root defaults and
|
|
158
|
+
per-call checks compose; cleanup and already-dispatched work still settle.
|
|
159
|
+
See [live mutation authority](docs/root.md#live-mutation-authority) for the exact
|
|
160
|
+
scope, including raw writable handles and lock bookkeeping.
|
|
161
|
+
For workspaces with directory aliases, use `symlinks: "follow-parents-within-root"`
|
|
162
|
+
on reads and `mutationSymlinks: "follow-parents-within-root"` on mutations or root
|
|
163
|
+
defaults. Contained parent symlinks are resolved by the library, while a final
|
|
164
|
+
symlink is rejected. Read policy and mutation policy are separate; omitting
|
|
165
|
+
`mutationSymlinks` preserves the existing mutation behavior. See [root policies](docs/root.md#defaults-vs-per-call-options).
|
|
166
|
+
|
|
151
167
|
Use `ensureRoot()` when a computed relative directory target resolves to the root itself (`""` or `"."`) and you want the operation to be accepted. `root()` still requires the trusted root directory to already exist.
|
|
152
168
|
|
|
153
169
|
## Reading
|
|
@@ -229,6 +245,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
229
245
|
| `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
|
|
230
246
|
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
231
247
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
248
|
+
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, and XFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
232
249
|
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, native gzip/zstd/bzip2, and bounded entry reads |
|
|
233
250
|
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, install paths, filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
234
251
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
2
|
export { stageFileInDirectory, type StagedFile, type StagedFileReceipt, type PublishedFileReceipt, type StagedFilePublication, type StagedFileCleanupReceipt, type StagedFileFailureDetails, } from "./native-staged-file.js";
|
|
3
3
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
4
|
+
export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
|
|
4
5
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
|
|
5
6
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
|
|
6
7
|
export { sameFileIdentity, type FileIdentityStat } from "./file-identity.js";
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
package/dist/advanced.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export { createAsyncLock } from "./async-lock.js";
|
|
5
5
|
export { stageFileInDirectory, } from "./native-staged-file.js";
|
|
6
6
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
7
|
+
export { readFileWindowFully, readFileWindowFullySync, } from "./positional-read.js";
|
|
7
8
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, } from "./device-path.js";
|
|
8
9
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, } from "./absolute-path.js";
|
|
9
10
|
export { sameFileIdentity } from "./file-identity.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"AAYA,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOpE"}
|
package/dist/archive-crc32.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import zlib from "node:zlib";
|
|
2
|
+
const nativeCrc32 = zlib.crc32;
|
|
3
|
+
// Node 22.0–22.1 are supported but predate zlib.crc32.
|
|
4
|
+
const CRC32_TABLE = nativeCrc32 ? undefined : Array.from({ length: 256 }, (_, index) => {
|
|
2
5
|
let value = index;
|
|
3
6
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
4
7
|
value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
@@ -6,6 +9,8 @@ const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
|
6
9
|
return value >>> 0;
|
|
7
10
|
});
|
|
8
11
|
export function updateCrc32(previous, buffer) {
|
|
12
|
+
if (nativeCrc32)
|
|
13
|
+
return nativeCrc32(buffer, previous >>> 0);
|
|
9
14
|
let crc = previous ^ -1;
|
|
10
15
|
for (const byte of buffer) {
|
|
11
16
|
crc = (crc >>> 8) ^ (CRC32_TABLE[(crc ^ byte) & 0xff] ?? 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,sBAAsB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACjE,2BAA2B,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAsB,gCAAgC,CAAC,CAAC,EACtD,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAWD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAST;AAED,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAuDD,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
|
package/dist/archive-deadline.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scheduleTimeout } from "./timing.js";
|
|
1
2
|
export async function ownExtractionDestinationMutation(deadline, run) {
|
|
2
3
|
return deadline ? await deadline.ownDestinationMutation(run) : await run();
|
|
3
4
|
}
|
|
@@ -68,16 +69,14 @@ function createExtractionDeadline(timeoutMs, label) {
|
|
|
68
69
|
dispose: () => undefined,
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
const
|
|
72
|
+
const cancelTimeout = scheduleTimeout(() => {
|
|
72
73
|
controller.abort(timeoutError);
|
|
73
74
|
}, timeoutMs);
|
|
74
75
|
return {
|
|
75
76
|
signal: controller.signal,
|
|
76
77
|
check,
|
|
77
78
|
...mutationOwner,
|
|
78
|
-
dispose:
|
|
79
|
-
clearTimeout(timeoutId);
|
|
80
|
-
},
|
|
79
|
+
dispose: cancelTimeout,
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
export async function withExtractionDeadline(timeoutMs, label, run) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Writable } from "node:stream";
|
|
2
2
|
import type { Gunzip } from "node:zlib";
|
|
3
|
+
/** Validate the same physical suffix when admission owns an in-memory input. */
|
|
4
|
+
export declare function validateGzipBufferTail(input: Buffer, consumed: number, signal?: AbortSignal): Promise<void>;
|
|
3
5
|
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
4
6
|
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
5
7
|
export declare class GzipInput extends Writable {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,gFAAgF;AAChF,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAWjH;AAED;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBvH"}
|
|
@@ -2,6 +2,25 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { Writable } from "node:stream";
|
|
4
4
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
5
|
+
function assertConsumedBoundary(consumed, size) {
|
|
6
|
+
if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
|
|
7
|
+
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Validate the same physical suffix when admission owns an in-memory input. */
|
|
11
|
+
export async function validateGzipBufferTail(input, consumed, signal) {
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
assertConsumedBoundary(consumed, input.length);
|
|
14
|
+
for (let position = consumed; position < input.length; position += 65536) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
if (input.subarray(position, position + 65536).some(byte => byte !== 0)) {
|
|
17
|
+
throw new ArchiveFormatError("nonzero gzip container padding");
|
|
18
|
+
}
|
|
19
|
+
if (position + 65536 < input.length)
|
|
20
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
21
|
+
}
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
}
|
|
5
24
|
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
6
25
|
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
7
26
|
export class GzipInput extends Writable {
|
|
@@ -50,9 +69,7 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
|
|
|
50
69
|
const handle = await fs.open(filePath, "r");
|
|
51
70
|
try {
|
|
52
71
|
const { size } = fsSync.fstatSync(handle.fd);
|
|
53
|
-
|
|
54
|
-
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
55
|
-
}
|
|
72
|
+
assertConsumedBoundary(consumed, size);
|
|
56
73
|
if (consumed === size)
|
|
57
74
|
return;
|
|
58
75
|
const buffer = Buffer.allocUnsafe(65536);
|
package/dist/archive-merge.js
CHANGED
|
@@ -172,7 +172,7 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
catch (error) {
|
|
175
|
-
// copyIn
|
|
175
|
+
// copyIn cleans unpublished stages; merge never gains rollback authority.
|
|
176
176
|
if (error instanceof FsSafeError && (error.code === "hardlink" || error.code === "path-alias")) {
|
|
177
177
|
throw createArchiveSymlinkTraversalError(originalPath);
|
|
178
178
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FhB"}
|
package/dist/archive-native.js
CHANGED
|
@@ -40,8 +40,9 @@ export async function extractNativeArchive(params) {
|
|
|
40
40
|
destinationRealDir,
|
|
41
41
|
run: async (stagingDir) => {
|
|
42
42
|
params.deadline.check();
|
|
43
|
+
// N-API retains completed task state on its signal; each pass needs its own.
|
|
43
44
|
const manifest = await params.binding
|
|
44
|
-
.inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, params.deadline.signal)
|
|
45
|
+
.inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, AbortSignal.any([params.deadline.signal]))
|
|
45
46
|
.catch(throwMappedNativeArchiveError);
|
|
46
47
|
params.deadline.check();
|
|
47
48
|
assertArchiveEntryCountWithinLimit(manifest.length, limits);
|
|
@@ -84,7 +85,7 @@ export async function extractNativeArchive(params) {
|
|
|
84
85
|
(typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
|
|
85
86
|
try {
|
|
86
87
|
params.deadline.check();
|
|
87
|
-
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan.map((entry) => ({ ...entry, mode: entry.kind === "directory" ? 0o700 : 0o600 })), tarLimits, params.deadline.signal).catch(throwMappedNativeArchiveError);
|
|
88
|
+
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan.map((entry) => ({ ...entry, mode: entry.kind === "directory" ? 0o700 : 0o600 })), tarLimits, AbortSignal.any([params.deadline.signal])).catch(throwMappedNativeArchiveError);
|
|
88
89
|
}
|
|
89
90
|
finally {
|
|
90
91
|
await directory.close().catch(() => undefined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiOpG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -2,7 +2,7 @@ import { classifyArchiveParserError } from "./archive-parser-errors.js";
|
|
|
2
2
|
import fsSync from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
|
-
import {
|
|
5
|
+
import { readBoundedAsync } from "./bounded-read.js";
|
|
6
6
|
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
7
7
|
import { formatErrorDetail } from "./error-detail.js";
|
|
8
8
|
import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
@@ -17,7 +17,6 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
|
17
17
|
import { getNativeBinding } from "./native.js";
|
|
18
18
|
import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
19
19
|
import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
|
|
20
|
-
import { tempFile } from "./temp-target.js";
|
|
21
20
|
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
22
21
|
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
23
22
|
function canonicalEntryPath(entryPath) {
|
|
@@ -63,7 +62,7 @@ async function readStreamBounded(stream, maxBytes) {
|
|
|
63
62
|
}
|
|
64
63
|
return Buffer.concat(chunks, total);
|
|
65
64
|
}
|
|
66
|
-
async function
|
|
65
|
+
async function readArchiveInput(archivePath) {
|
|
67
66
|
const resolved = fsSync.realpathSync.native(archivePath);
|
|
68
67
|
const before = await inspectFileIdentity(async () => {
|
|
69
68
|
const stat = fsSync.lstatSync(archivePath, { bigint: true });
|
|
@@ -73,9 +72,7 @@ async function stageArchiveInput(archivePath) {
|
|
|
73
72
|
return stat;
|
|
74
73
|
});
|
|
75
74
|
const handle = await fs.open(resolved, resolveReadOpenFlags());
|
|
76
|
-
let staged;
|
|
77
75
|
try {
|
|
78
|
-
staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
|
|
79
76
|
const opened = await inspectFileIdentity(async () => {
|
|
80
77
|
const stat = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
81
78
|
if (!stat.isFile())
|
|
@@ -88,12 +85,14 @@ async function stageArchiveInput(archivePath) {
|
|
|
88
85
|
throw new Error("archive changed during validation");
|
|
89
86
|
return stat;
|
|
90
87
|
}, opened);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
88
|
+
// Native archive workers borrow this private buffer. A pooled allocation could
|
|
89
|
+
// share its ArrayBuffer with unrelated JS buffers while the worker runs.
|
|
90
|
+
return await readBoundedAsync(DEFAULT_MAX_ARCHIVE_BYTES_ZIP, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, { unpooled: true, observeRegularFileSize: () => {
|
|
91
|
+
const size = fsSync.fstatSync(handle.fd).size;
|
|
92
|
+
return Number.isSafeInteger(size) && size >= 0 ? size : undefined;
|
|
93
|
+
} });
|
|
94
94
|
}
|
|
95
95
|
catch (error) {
|
|
96
|
-
await staged?.cleanup().catch(() => undefined);
|
|
97
96
|
if (error instanceof FsSafeError && error.code === "path-mismatch") {
|
|
98
97
|
throw new FsSafeError("path-mismatch", "archive changed during validation", { cause: error });
|
|
99
98
|
}
|
|
@@ -134,11 +133,11 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
|
|
|
134
133
|
stream.once("error", (error) => integrity.destroy(normalizeZipIntegrityError(error)));
|
|
135
134
|
return await readStreamBounded(stream.pipe(integrity), maxBytes);
|
|
136
135
|
}
|
|
137
|
-
async function readTarEntry(
|
|
136
|
+
async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
138
137
|
const seenPaths = new Set();
|
|
139
138
|
let selected;
|
|
140
139
|
const limits = resolveTarMeterLimits();
|
|
141
|
-
await inspectTar({
|
|
140
|
+
await inspectTar({ archiveBuffer, limits, onMember(info) {
|
|
142
141
|
const normalized = canonicalEntryPath(info.path);
|
|
143
142
|
if (seenPaths.has(normalized)) {
|
|
144
143
|
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
@@ -155,11 +154,57 @@ async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
|
155
154
|
if (selected.size > maxBytes)
|
|
156
155
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
157
156
|
let result;
|
|
158
|
-
await replayTar({
|
|
157
|
+
await replayTar({ archiveBuffer, limits, members: [selected], async consume(_member, payload) {
|
|
159
158
|
result = await readStreamBounded(payload, maxBytes);
|
|
160
159
|
} });
|
|
161
160
|
return result;
|
|
162
161
|
}
|
|
162
|
+
function selectNativeEntry(manifest, requested, displayPath, physicalCount) {
|
|
163
|
+
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
164
|
+
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
165
|
+
}
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
let selected;
|
|
168
|
+
for (const entry of manifest) {
|
|
169
|
+
const normalized = canonicalEntryPath(entry.path);
|
|
170
|
+
if (seen.has(normalized)) {
|
|
171
|
+
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
172
|
+
}
|
|
173
|
+
seen.add(normalized);
|
|
174
|
+
if (normalized === requested) {
|
|
175
|
+
if (entry.kind !== "file")
|
|
176
|
+
throw new Error(`archive entry is not a file: ${formatErrorDetail(displayPath)}`);
|
|
177
|
+
selected = entry;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!selected)
|
|
181
|
+
throw new Error(`archive entry not found: ${formatErrorDetail(displayPath)}`);
|
|
182
|
+
return selected;
|
|
183
|
+
}
|
|
184
|
+
function throwNativeReadError(error) {
|
|
185
|
+
if (error instanceof Error) {
|
|
186
|
+
const mapped = classifyArchiveParserError(error.message, { cause: error });
|
|
187
|
+
if (mapped)
|
|
188
|
+
throw mapped;
|
|
189
|
+
if (isArchiveFormatErrorMessage(error.message))
|
|
190
|
+
throw new ArchiveFormatError(error.message, { cause: error });
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
async function readNativeBufferEntry(native, buffer, kind, requested, displayPath, maxBytes, physicalCount) {
|
|
195
|
+
try {
|
|
196
|
+
const signal = new AbortController().signal;
|
|
197
|
+
const limits = resolveTarMeterLimits();
|
|
198
|
+
const reader = kind === "zip"
|
|
199
|
+
? await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]))
|
|
200
|
+
: await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
|
|
201
|
+
const selected = selectNativeEntry(reader.entries, requested, displayPath, physicalCount);
|
|
202
|
+
return await reader.readEntry(selected.index, maxBytes, AbortSignal.any([signal]));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throwNativeReadError(error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
163
208
|
export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
164
209
|
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
|
|
165
210
|
throw new RangeError("maxBytes must be a non-negative safe integer");
|
|
@@ -169,58 +214,14 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
169
214
|
throw new Error(`unsupported archive: ${archivePath}`);
|
|
170
215
|
}
|
|
171
216
|
const requestedEntry = normalizedRequestedEntry(entryPath);
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const manifest = await native.inspectArchiveNative(staged.path, kind, limits, signal);
|
|
183
|
-
let rawEntryPath;
|
|
184
|
-
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
185
|
-
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
186
|
-
}
|
|
187
|
-
const seenPaths = new Set();
|
|
188
|
-
for (const entry of manifest) {
|
|
189
|
-
const normalized = canonicalEntryPath(entry.path);
|
|
190
|
-
if (seenPaths.has(normalized)) {
|
|
191
|
-
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
192
|
-
}
|
|
193
|
-
seenPaths.add(normalized);
|
|
194
|
-
if (normalized === requestedEntry) {
|
|
195
|
-
if (entry.kind !== "file") {
|
|
196
|
-
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
197
|
-
}
|
|
198
|
-
rawEntryPath = entry.path;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (!rawEntryPath) {
|
|
202
|
-
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
203
|
-
}
|
|
204
|
-
return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, limits, signal);
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
if (error instanceof Error) {
|
|
208
|
-
const mapped = classifyArchiveParserError(error.message, { cause: error });
|
|
209
|
-
if (mapped)
|
|
210
|
-
throw mapped;
|
|
211
|
-
}
|
|
212
|
-
if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
|
|
213
|
-
throw new ArchiveFormatError(error.message, { cause: error });
|
|
214
|
-
}
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
assertPortableArchiveKind(kind);
|
|
219
|
-
return kind === "zip"
|
|
220
|
-
? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
|
|
221
|
-
: await readTarEntry(staged.path, requestedEntry, options.maxBytes);
|
|
222
|
-
}
|
|
223
|
-
finally {
|
|
224
|
-
await staged.cleanup();
|
|
225
|
-
}
|
|
217
|
+
const buffer = await readArchiveInput(archivePath);
|
|
218
|
+
const physicalCount = kind === "zip"
|
|
219
|
+
? admitZipBuffer(buffer, resolveExtractLimits())
|
|
220
|
+
: undefined;
|
|
221
|
+
const native = getNativeBinding();
|
|
222
|
+
if (native)
|
|
223
|
+
return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, physicalCount);
|
|
224
|
+
assertPortableArchiveKind(kind);
|
|
225
|
+
return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes)
|
|
226
|
+
: await readTarEntry(buffer, requestedEntry, options.maxBytes);
|
|
226
227
|
}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import type { TarMeterLimits } from "./archive-limits.js";
|
|
2
2
|
import { type AdmittedTarMember } from "./archive-tar-wasm.js";
|
|
3
|
-
|
|
3
|
+
/** Buffers are private immutable snapshots, just like the staged file route. */
|
|
4
|
+
type TarInput = {
|
|
4
5
|
archivePath: string;
|
|
6
|
+
archiveBuffer?: never;
|
|
7
|
+
} | {
|
|
8
|
+
archiveBuffer: Buffer;
|
|
9
|
+
archivePath?: never;
|
|
10
|
+
};
|
|
11
|
+
export declare function inspectTar(params: TarInput & {
|
|
5
12
|
limits: TarMeterLimits;
|
|
6
13
|
signal?: AbortSignal;
|
|
7
14
|
onMember?: (entry: AdmittedTarMember) => void;
|
|
8
15
|
}): Promise<void>;
|
|
9
16
|
/** Replay in physical order, retaining at most one decoded chunk. Every range
|
|
10
|
-
* comes from complete admission of the immutable
|
|
11
|
-
export declare function replayTar<T extends AdmittedTarMember>(params: {
|
|
12
|
-
archivePath: string;
|
|
17
|
+
* comes from complete admission of the immutable input. */
|
|
18
|
+
export declare function replayTar<T extends AdmittedTarMember>(params: TarInput & {
|
|
13
19
|
limits: TarMeterLimits;
|
|
14
20
|
signal?: AbortSignal;
|
|
15
21
|
members: readonly T[];
|
|
16
22
|
consume(member: T, payload: AsyncIterable<Buffer>): Promise<void>;
|
|
17
23
|
}): Promise<void>;
|
|
24
|
+
export {};
|
|
18
25
|
//# sourceMappingURL=archive-tar-stream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,gFAAgF;AAChF,KAAK,QAAQ,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAuDhH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;IAClD,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;2DAC2D;AAC3D,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG;IAC9E,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { Writable } from "node:stream";
|
|
2
|
+
import { Readable, Writable } from "node:stream";
|
|
3
3
|
import { pipeline } from "node:stream/promises";
|
|
4
4
|
import { createGunzip } from "node:zlib";
|
|
5
|
-
import { GzipInput, validateGzipContainerTail } from "./archive-gzip-tail.js";
|
|
5
|
+
import { GzipInput, validateGzipBufferTail, validateGzipContainerTail } from "./archive-gzip-tail.js";
|
|
6
6
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
7
7
|
import { TarParserStream } from "./archive-tar-wasm.js";
|
|
8
|
+
function* bufferChunks(buffer) {
|
|
9
|
+
for (let offset = 0; offset < buffer.length; offset += 65536)
|
|
10
|
+
yield buffer.subarray(offset, offset + 65536);
|
|
11
|
+
}
|
|
8
12
|
async function gzipFile(filePath) {
|
|
9
13
|
const handle = await fs.promises.open(filePath, "r");
|
|
10
14
|
try {
|
|
@@ -17,10 +21,14 @@ async function gzipFile(filePath) {
|
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
async function withTarStream(params, consume) {
|
|
20
|
-
const
|
|
24
|
+
const buffer = params.archiveBuffer;
|
|
25
|
+
const gzip = buffer !== undefined ? buffer[0] === 31 && buffer[1] === 139 : await gzipFile(params.archivePath);
|
|
21
26
|
const parser = new TarParserStream(params.limits, params.onMember);
|
|
22
|
-
const input =
|
|
23
|
-
|
|
27
|
+
const input = buffer !== undefined
|
|
28
|
+
? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
|
|
29
|
+
: fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
|
|
30
|
+
// Match the WASM input window for buffered reads instead of emitting 16 KiB chunks.
|
|
31
|
+
const decoder = gzip ? createGunzip(buffer === undefined ? undefined : { chunkSize: 65536 }) : undefined;
|
|
24
32
|
const gzipInput = decoder ? new GzipInput(decoder) : undefined;
|
|
25
33
|
const destroy = (error) => {
|
|
26
34
|
input.destroy(error);
|
|
@@ -42,8 +50,12 @@ async function withTarStream(params, consume) {
|
|
|
42
50
|
const error = await settled;
|
|
43
51
|
if (error)
|
|
44
52
|
throw error;
|
|
45
|
-
if (gzipInput)
|
|
46
|
-
|
|
53
|
+
if (gzipInput) {
|
|
54
|
+
if (buffer !== undefined)
|
|
55
|
+
await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
|
|
56
|
+
else
|
|
57
|
+
await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
|
|
58
|
+
}
|
|
47
59
|
return result;
|
|
48
60
|
}
|
|
49
61
|
finally {
|
|
@@ -57,7 +69,7 @@ export async function inspectTar(params) {
|
|
|
57
69
|
});
|
|
58
70
|
}
|
|
59
71
|
/** Replay in physical order, retaining at most one decoded chunk. Every range
|
|
60
|
-
* comes from complete admission of the immutable
|
|
72
|
+
* comes from complete admission of the immutable input. */
|
|
61
73
|
export async function replayTar(params) {
|
|
62
74
|
await withTarStream(params, async (parser) => {
|
|
63
75
|
const iterator = parser[Symbol.asyncIterator]();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAyClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAkB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
|