@openclaw/fs-safe 0.14.0 → 0.15.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 +50 -0
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +2 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +7 -11
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +18 -10
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +5 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 - 2026-09-18
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- Enforce Root authority throughout synchronous lock admission, stale recovery, and cleanup; keep asynchronous reclaim guards inside the Root capability.
|
|
8
|
+
- Preserve directory and archive-source identity across publication, callbacks, and recovery, and reject corrupt portable ZIP payloads and metadata.
|
|
9
|
+
- Support asynchronous walk filters, configurable-depth suffix probing, and bounded synchronous descriptor copying.
|
|
10
|
+
|
|
11
|
+
### Compatibility and upgrade notes
|
|
12
|
+
|
|
13
|
+
- Directory replacement now requires compatible native identity-fenced, no-replace support, including when the destination is absent. Disabled, missing, or older helpers reject before target-parent creation.
|
|
14
|
+
- Treat ordinary post-dispatch native rename errors as indeterminate. Preserve staged files and directory backups when a rename may have committed before its reply was lost; automatic retry and rollback require explicit evidence that dispatch never occurred.
|
|
15
|
+
- Synchronous Root locks require a genuine registered Root handle and enforce its policies. Interrupted asynchronous Root reclaim guards can remain after process exit or reset; recover them only after an application-owned liveness check proves the attempt ended.
|
|
16
|
+
- Root lock authority checks add measurable overhead. Removing redundant observations reduces some cost, but these paths remain slower than the earlier implementation that did not enforce the same boundary.
|
|
17
|
+
|
|
18
|
+
### Security and correctness
|
|
19
|
+
|
|
20
|
+
- Serialize sidecar admission across raw and Root-based synchronous callers, retain separate cleanup authority, and recheck asynchronous authorization at the final stale-removal boundary.
|
|
21
|
+
- Keep asynchronous Root reclaim-guard creation, token/byte ownership checks, and removal inside the capability. Preserve unsettled guards instead of following replaced parents through raw cleanup.
|
|
22
|
+
- Create native Root lock records through retained-parent exclusive opens so racing contenders retry normally. Ordinary Root writes retain private staging. Treat raw reclaim guards disappearing or changing during collision inspection as contention.
|
|
23
|
+
- Verify reclaim-guard ownership after the final sidecar snapshot and parser, preserving mutation authorization and parser failures. Evaluate stale age after snapshot parsing and reject cleanup when exact identity is unknown.
|
|
24
|
+
- Retain source-root and child-directory authority during archive merges and bind file identity and mode to the admitted copy descriptor, including exact large Windows identities.
|
|
25
|
+
- Preserve ZIP payloads and physical entry metadata during portable loading so extraction rejects corrupt empty content and honors admitted directory, symlink, and special-file kinds without reordering callbacks.
|
|
26
|
+
- Snapshot FileStore policies before asynchronous work, streams, source reads, and JSON serialization; snapshot JsonStore durability/newline options before queued mutations and updater callbacks.
|
|
27
|
+
- Snapshot external-output and sibling-temp options before asynchronous setup while preserving callback receivers.
|
|
28
|
+
- Enforce Windows native parent mutation policies before creating each missing component and retain originating native descriptor ownership through cleanup.
|
|
29
|
+
- Preserve files and symlinks replacing an observed empty FileStore directory, and leave directories that become nonempty untouched.
|
|
30
|
+
- Recheck child entries before walk descent so symlink replacements during filters honor the selected policy.
|
|
31
|
+
- Keep Windows owner/ACL diagnostics bounded and fail-closed for hostile thrown values. Preserve ordinary diagnostics and the original cause without invoking getters, proxy traps, coercion, or altered buffer properties.
|
|
32
|
+
|
|
33
|
+
### APIs and failure handling
|
|
34
|
+
|
|
35
|
+
- Allow asynchronous `include`/`descend` callbacks in `walkDirectory()` and `entryFilter` in `Root.walk()`, retaining serial selection, callback receivers, budgets, cancellation, and post-callback identity checks.
|
|
36
|
+
- Add `maxDepth` to `probePathSuffixAliasesSync()` with proportional budgets, a 32,768-forward-observation ceiling, unchanged defaults and string limits, and complete owned cleanup.
|
|
37
|
+
- Add `copyFileDescriptorSync()` for bounded positional transfers between caller-owned regular-file descriptors, preserving cursors, destination suffixes, descriptor lifetime, and caller-owned durability/publication.
|
|
38
|
+
- Preserve the first tree-copy operation failure, including falsy values and existing codes, while closing each owned resource once. Successful operations retain output-before-input and source-before-parent close-failure precedence.
|
|
39
|
+
- Preserve falsy atomic operation, cleanup, close, restoration, temporary-workspace removal, and Windows native cleanup failures; consume retained handles before close to prevent repeated release.
|
|
40
|
+
- Propagate portable ZIP fallback-close failures before publication and copy-fallback writer close failures after successful unsynchronized writes, including restoration with an absent destination.
|
|
41
|
+
- Preserve synchronous destination-admission failures when best-effort close also fails, and preserve directory-mode authority/deadline failures before and after dispatch.
|
|
42
|
+
|
|
43
|
+
### Performance and maintenance
|
|
44
|
+
|
|
45
|
+
- Bound path-prefix queue consumption on long paths while preserving short-path behavior and resolution checks.
|
|
46
|
+
- Reuse eligible Windows parent-creation receipts, equivalent Root identity observations, and guarded regular-file unlink paths while retaining final identity and policy checks.
|
|
47
|
+
- Consolidate native copy ownership, regular/secret read admission, copy-fallback admission, and temporary-workspace setup and settlement; remove redundant wrappers, write-admission storage, and permission-diagnostic work.
|
|
48
|
+
- Bind public copy, ZIP extraction, atomic settlement, and permission benchmarks to verified workload receipts; keep setup, content checks, and cleanup verification outside timing.
|
|
49
|
+
- Update @napi-rs/cli to 3.10.0 and the locked Node type definitions to 26.6.0, and align Vitest/coverage on 5.0.1.
|
|
50
|
+
- Expand security-owner coverage to native/archive code, platform packages, executable benchmarks, and build/test/release configuration.
|
|
51
|
+
- Raise publication and release-proof job ceilings to 90 minutes, retaining bounded registry retries and fail-closed artifact, signature, and provenance checks.
|
|
52
|
+
|
|
3
53
|
## 0.14.0 - 2026-09-17
|
|
4
54
|
|
|
5
55
|
### Highlights
|
package/README.md
CHANGED
|
@@ -260,7 +260,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
260
260
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
261
261
|
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, XFS, and ZFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
262
262
|
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, native gzip/zstd/bzip2, and bounded entry reads |
|
|
263
|
-
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle copying](docs/copy.md#borrowed-filehandle-transfers), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [suffix-alias probing](docs/path-suffix-aliases.md), [in-place writes](docs/in-place-write.md), [versioned install-ID encoding](docs/install-path.md#safepathsegmenthashedv2), filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
263
|
+
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle and descriptor copying](docs/copy.md#borrowed-filehandle-transfers), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [suffix-alias probing](docs/path-suffix-aliases.md), [in-place writes](docs/in-place-write.md), [versioned install-ID encoding](docs/install-path.md#safepathsegmenthashedv2), filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
264
264
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
|
265
265
|
| `@openclaw/fs-safe/types` | shared types: `DirEntry`, `PathStat`, … |
|
|
266
266
|
| `@openclaw/fs-safe/test-hooks` | hooks the test suite uses to inject races; registration requires `NODE_ENV=test` or `VITEST=true` |
|
|
@@ -517,12 +517,18 @@ for (const file of scan.entries) {
|
|
|
517
517
|
|
|
518
518
|
Check `scan.truncated` before treating the result as complete, and `scan.failedDirs` to tell an incomplete scan (a directory that could not be read) from an empty one before pruning state from the listing.
|
|
519
519
|
|
|
520
|
+
`walkDirectory()` accepts asynchronous `include` and `descend` callbacks through `AsyncWalkDirectoryOptions`, so a marker lookup can prune a directory before its children are read. Decisions remain serial and retain the options object as their `this` receiver; `walkDirectorySync()` and its options remain synchronous. See [Directory walking](docs/walk.md) for callback timing, JavaScript result compatibility, and error handling.
|
|
521
|
+
|
|
520
522
|
For caller-controlled paths, `Root.walk()` is the root-bounded async iterator.
|
|
521
523
|
It supports entry/depth budgets, in-root symlink following, cancellation, and a
|
|
522
524
|
truncation marker (or typed error) when a budget is reached. Its `entryFilter`
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
525
|
+
accepts `"include"`, `"skip"`, or `"skip-subtree"`, directly or through a Promise.
|
|
526
|
+
After an awaited decision resolves, the walk rechecks cancellation and the
|
|
527
|
+
current listing directory and Root identities before using it. Pending callbacks
|
|
528
|
+
settle before cancellation or iterator disposal completes. Callback failures
|
|
529
|
+
reject the walk. `onDirectoryError: "skip-and-report"` yields typed `"directory-error"` markers
|
|
530
|
+
for directory read or identity-check failures while preserving entries from
|
|
531
|
+
readable subtrees.
|
|
526
532
|
|
|
527
533
|
## Archive extraction
|
|
528
534
|
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
|
-
export { copyFileHandle, type CopyFileHandleOptions } from "./file-handle-transfer.js";
|
|
2
|
+
export { copyFileHandle, copyFileDescriptorSync, type CopyFileHandleOptions } from "./file-handle-transfer.js";
|
|
3
3
|
export { overwriteFileHandle, type OverwriteFileHandleOptions } from "./overwrite-file-handle.js";
|
|
4
4
|
export { probePathCaseInsensitiveSync, type ProbePathCaseOptions } from "./path-case.js";
|
|
5
5
|
export { resolvePathPrefixSync, type ResolvedPathPrefix } from "./path-prefix.js";
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC/G,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,4BAA4B,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,KAAK,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAC1G,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,uBAAuB,EACvB,iCAAiC,EACjC,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,2BAA2B,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,GAClC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EAClC,KAAK,oCAAoC,GAC1C,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,wBAAwB,EACxB,kCAAkC,EAClC,eAAe,EACf,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,SAAS,EACT,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EACtB,oCAAoC,EACpC,6BAA6B,EAC7B,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,6BAA6B,GACnC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,QAAQ,EACb,QAAQ,EACR,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
|
package/dist/advanced.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// public subpaths; prefer root/json/store/temp/archive unless you are building a
|
|
3
3
|
// higher-level primitive.
|
|
4
4
|
export { createAsyncLock } from "./async-lock.js";
|
|
5
|
-
export { copyFileHandle } from "./file-handle-transfer.js";
|
|
5
|
+
export { copyFileHandle, copyFileDescriptorSync } from "./file-handle-transfer.js";
|
|
6
6
|
export { overwriteFileHandle } from "./overwrite-file-handle.js";
|
|
7
7
|
export { probePathCaseInsensitiveSync } from "./path-case.js";
|
|
8
8
|
export { resolvePathPrefixSync } from "./path-prefix.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAClG,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAClG,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAiB9B,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AACF,KAAK,kBAAkB,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACtE,gBAAgB,EAAE,qBAAqB,CAAC;CACzC,CAAC;AAEF,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,kBAAkB,GAAG;IAAE,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAS1F"}
|
package/dist/archive-merge.js
CHANGED
|
@@ -5,16 +5,19 @@ import { ownExtractionDestinationMutation } from "./archive-deadline.js";
|
|
|
5
5
|
import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createDirectoryIdentityGuard, createArchiveSymlinkTraversalError, preparePrivateArchiveOutputPath, } from "./archive-staging.js";
|
|
6
6
|
import {} from "./directory-mode-owner.js";
|
|
7
7
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
8
|
+
import { assertSyncDirectoryGuard, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
8
9
|
import { FsSafeError } from "./errors.js";
|
|
9
10
|
import { formatErrorDetail } from "./error-detail.js";
|
|
10
|
-
import { isPathInside } from "./path.js";
|
|
11
|
+
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
11
12
|
import { rootFromDirectoryGuard } from "./root-impl.js";
|
|
13
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
12
14
|
import { realpathSync } from "./realpath.js";
|
|
13
15
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
14
|
-
import { onCopyPublication } from "./copy-publication.js";
|
|
16
|
+
import { onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
15
17
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
16
18
|
import { finalizeArchivePublication } from "./archive-durability.js";
|
|
17
19
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
20
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
18
21
|
export async function mergePlannedArchiveIntoDestination(params) {
|
|
19
22
|
assertMergePathInputs(params);
|
|
20
23
|
await mergeTree(params, params.entries, params.durable === true);
|
|
@@ -58,6 +61,33 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
58
61
|
plan.set(stagedPath, entry);
|
|
59
62
|
}
|
|
60
63
|
const ancestors = [];
|
|
64
|
+
const sourceAncestors = [];
|
|
65
|
+
const assertSourceDirectory = (guard, canonical = false) => {
|
|
66
|
+
try {
|
|
67
|
+
// Child receipts already retain canonical ancestry. An exact no-follow
|
|
68
|
+
// observation checks each named association without re-running realpath.
|
|
69
|
+
if (canonical)
|
|
70
|
+
assertSyncDirectoryGuard(guard);
|
|
71
|
+
else
|
|
72
|
+
inspectDirectoryIdentitySync(guard.dir, guard.stat);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof FsSafeError || isNotFoundPathError(error)) {
|
|
76
|
+
throw createArchiveSymlinkTraversalError(path.relative(params.sourceDir, guard.dir));
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
check();
|
|
81
|
+
};
|
|
82
|
+
const assertSourceFrontier = () => {
|
|
83
|
+
const current = sourceAncestors.at(-1);
|
|
84
|
+
if (current)
|
|
85
|
+
assertSourceDirectory(current);
|
|
86
|
+
};
|
|
87
|
+
const assertSourceAncestors = () => {
|
|
88
|
+
for (const ancestor of sourceAncestors)
|
|
89
|
+
assertSourceDirectory(ancestor);
|
|
90
|
+
};
|
|
61
91
|
const assertGuards = async () => {
|
|
62
92
|
await assertDirectoryIdentityGuard(destinationGuard);
|
|
63
93
|
check();
|
|
@@ -72,15 +102,21 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
72
102
|
};
|
|
73
103
|
const walk = async (sourceDir) => {
|
|
74
104
|
await assertGuards();
|
|
105
|
+
assertSourceFrontier();
|
|
75
106
|
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
76
107
|
check();
|
|
108
|
+
if (entries.length === 0) {
|
|
109
|
+
await assertGuards();
|
|
110
|
+
assertSourceFrontier();
|
|
111
|
+
}
|
|
77
112
|
for (const entry of entries) {
|
|
78
113
|
await assertGuards();
|
|
114
|
+
assertSourceFrontier();
|
|
79
115
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
80
116
|
const relPath = path.relative(params.sourceDir, sourcePath);
|
|
81
117
|
const originalPath = relPath.split(path.sep).join("/");
|
|
82
118
|
const destinationPath = path.join(destinationDir, relPath);
|
|
83
|
-
const sourceStat = fsSync.lstatSync(sourcePath);
|
|
119
|
+
const sourceStat = inspectFileIdentitySync(() => fsSync.lstatSync(sourcePath, { bigint: true }));
|
|
84
120
|
check();
|
|
85
121
|
if (sourceStat.isSymbolicLink())
|
|
86
122
|
throw createArchiveSymlinkTraversalError(originalPath);
|
|
@@ -96,61 +132,73 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
96
132
|
if (plan && ((planned && planned.kind !== kind) || (!planned && kind === "file"))) {
|
|
97
133
|
throw new FsSafeError("path-mismatch", "archive staging disagrees with the admitted publication plan");
|
|
98
134
|
}
|
|
99
|
-
|
|
135
|
+
let mode = plan ? planned?.mode ?? 0o755 : Number(sourceStat.mode & 511n);
|
|
100
136
|
await preparePrivateArchiveOutputPath({
|
|
101
137
|
destinationDir, destinationRealDir, deadline: params.deadline,
|
|
102
138
|
relPath, outPath: destinationPath, originalPath, isDirectory: kind === "directory",
|
|
103
139
|
}, assertGuards, destinationGuard);
|
|
104
140
|
check();
|
|
105
141
|
if (kind === "directory") {
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const guard = await createDirectoryIdentityGuard(destinationPath);
|
|
118
|
-
check();
|
|
119
|
-
await owner.verify(check);
|
|
120
|
-
ancestors.push({ guard, owner });
|
|
121
|
-
try {
|
|
122
|
-
await walk(sourcePath);
|
|
123
|
-
if (publication) {
|
|
124
|
-
await assertGuards();
|
|
125
|
-
publishedDirectories.push({ guard, mode, parents: ancestors.slice(0, -1).map((ancestor) => ancestor.guard) });
|
|
126
|
-
return;
|
|
142
|
+
// Retain the observation made before output preparation: recapturing
|
|
143
|
+
// here could silently authorize a replacement source directory.
|
|
144
|
+
sourceAncestors.push({ dir: sourcePath, realPath: sourceReal, stat: sourceStat });
|
|
145
|
+
try {
|
|
146
|
+
// Ownership spans open, descendants, finalization and close, including timeout.
|
|
147
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
148
|
+
await assertGuards();
|
|
149
|
+
assertSourceFrontier();
|
|
150
|
+
const owner = await pinNodeDirectoryForMode(destinationPath).catch((error) => {
|
|
151
|
+
if (error instanceof FsSafeError && (error.code === "not-file" || error.code === "path-mismatch")) {
|
|
152
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
127
153
|
}
|
|
128
|
-
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
129
157
|
check();
|
|
130
|
-
await
|
|
131
|
-
// Do not recursively verify this owner from inside its serialized apply.
|
|
132
|
-
ancestors.pop();
|
|
133
|
-
await owner.apply(mode, { check, beforeChmod: async () => {
|
|
134
|
-
await assertGuards();
|
|
135
|
-
await assertDirectoryIdentityGuard(guard);
|
|
136
|
-
check();
|
|
137
|
-
await assertResolvedInsideDestination({
|
|
138
|
-
destinationRealDir, targetPath: destinationPath, originalPath,
|
|
139
|
-
});
|
|
140
|
-
check();
|
|
141
|
-
} });
|
|
158
|
+
const guard = await createDirectoryIdentityGuard(destinationPath);
|
|
142
159
|
check();
|
|
143
|
-
await
|
|
160
|
+
await owner.verify(check);
|
|
161
|
+
ancestors.push({ guard, owner });
|
|
162
|
+
try {
|
|
163
|
+
await walk(sourcePath);
|
|
164
|
+
if (publication) {
|
|
165
|
+
await assertGuards();
|
|
166
|
+
assertSourceAncestors();
|
|
167
|
+
publishedDirectories.push({ guard, mode, parents: ancestors.slice(0, -1).map((ancestor) => ancestor.guard) });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", destinationPath);
|
|
171
|
+
check();
|
|
172
|
+
await assertGuards();
|
|
173
|
+
// Do not recursively verify this owner from inside its serialized apply.
|
|
174
|
+
ancestors.pop();
|
|
175
|
+
await owner.apply(mode, { check, beforeChmod: async () => {
|
|
176
|
+
await assertGuards();
|
|
177
|
+
assertSourceAncestors();
|
|
178
|
+
await assertDirectoryIdentityGuard(guard);
|
|
179
|
+
check();
|
|
180
|
+
await assertResolvedInsideDestination({
|
|
181
|
+
destinationRealDir, targetPath: destinationPath, originalPath,
|
|
182
|
+
});
|
|
183
|
+
check();
|
|
184
|
+
} });
|
|
185
|
+
check();
|
|
186
|
+
await assertGuards();
|
|
187
|
+
assertSourceAncestors();
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (ancestors.at(-1)?.owner === owner)
|
|
191
|
+
ancestors.pop();
|
|
192
|
+
}
|
|
144
193
|
}
|
|
145
194
|
finally {
|
|
146
|
-
|
|
147
|
-
ancestors.pop();
|
|
195
|
+
await owner.close();
|
|
148
196
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
sourceAncestors.pop();
|
|
201
|
+
}
|
|
154
202
|
}
|
|
155
203
|
else {
|
|
156
204
|
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
@@ -158,6 +206,25 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
158
206
|
try {
|
|
159
207
|
const options = {
|
|
160
208
|
mkdir: true, mode, durable: publication ? false : true,
|
|
209
|
+
[onCopySourceAdmission]: (identity, realPath) => {
|
|
210
|
+
assertSourceDirectory(sourceGuard, true);
|
|
211
|
+
assertSourceFrontier();
|
|
212
|
+
inspectFileIdentitySync(() => identity, sourceStat);
|
|
213
|
+
if (!admitPathInsideRoot({
|
|
214
|
+
rootPath: sourceGuard.realPath, candidatePath: realPath, rootIdentity: sourceGuard.stat,
|
|
215
|
+
}))
|
|
216
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
217
|
+
// Public merge permissions come from the admitted descriptor;
|
|
218
|
+
// extraction keeps the separately admitted publication plan.
|
|
219
|
+
if (!plan)
|
|
220
|
+
mode = Number(identity.mode & 511n);
|
|
221
|
+
return { mode, verify() {
|
|
222
|
+
// Only copy publication boundaries scan the active chain.
|
|
223
|
+
// Destination mkdir callbacks must not multiply this work.
|
|
224
|
+
assertSourceDirectory(sourceGuard, true);
|
|
225
|
+
assertSourceAncestors();
|
|
226
|
+
} };
|
|
227
|
+
},
|
|
161
228
|
};
|
|
162
229
|
if (publication && durable) {
|
|
163
230
|
options[onCopyPublication] = async (fd, identity) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqOpG,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
|
@@ -13,6 +13,7 @@ import { isGzipBuffer } from "./archive-gzip-tail.js";
|
|
|
13
13
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
14
14
|
import { loadAdmittedZipArchive } from "./archive-zip-loader.js";
|
|
15
15
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
16
|
+
import { isZipSymlinkEntry, zipEntryKind } from "./archive-zip-entry.js";
|
|
16
17
|
import { FsSafeError } from "./errors.js";
|
|
17
18
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
18
19
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
@@ -22,8 +23,6 @@ import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
|
22
23
|
import { validateNativeZipManifest } from "./archive-zip-manifest.js";
|
|
23
24
|
import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
|
|
24
25
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
25
|
-
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
26
|
-
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
27
26
|
function canonicalEntryPath(entryPath) {
|
|
28
27
|
validateArchiveEntryPath(entryPath, { escapeLabel: "archive root" });
|
|
29
28
|
return stripArchivePath(entryPath, 0) ?? "";
|
|
@@ -109,10 +108,11 @@ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
|
|
|
109
108
|
if (!entry || entry.dir) {
|
|
110
109
|
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
111
110
|
}
|
|
112
|
-
if (
|
|
113
|
-
(entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE) {
|
|
111
|
+
if (isZipSymlinkEntry(entry)) {
|
|
114
112
|
throw new Error(`archive entry is a link: ${formatErrorDetail(entryPath)}`);
|
|
115
113
|
}
|
|
114
|
+
if (zipEntryKind(entry) !== "file")
|
|
115
|
+
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
116
116
|
const integrity = createZipIntegrityTransform(entry);
|
|
117
117
|
const stream = typeof entry.nodeStream === "function"
|
|
118
118
|
? entry.nodeStream()
|
|
@@ -10,8 +10,12 @@ export type ZipDirectoryEntry = {
|
|
|
10
10
|
creatorSystem: number;
|
|
11
11
|
externalAttributes: number;
|
|
12
12
|
size: number;
|
|
13
|
+
compressedSize: number;
|
|
14
|
+
compressionMethod: number;
|
|
15
|
+
crc32: number;
|
|
13
16
|
path?: string;
|
|
14
17
|
portableKey: string;
|
|
18
|
+
portableDirectory: boolean;
|
|
15
19
|
kind: ArchiveEntryKind;
|
|
16
20
|
};
|
|
17
21
|
export declare function scanZipDirectory(size: number, limits: ResolvedArchiveExtractLimits, onEntry?: (entry: ZipDirectoryEntry) => void): ZipScan;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AA4IF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AA4IF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAqET"}
|
|
@@ -224,6 +224,8 @@ export function* scanZipDirectory(size, limits, onEntry) {
|
|
|
224
224
|
onEntry?.({
|
|
225
225
|
index: count - 1, creatorSystem: central[5], externalAttributes: central.readUInt32LE(38),
|
|
226
226
|
size: values.uncompressed, path: admittedName.path, portableKey: admittedName.portableKey,
|
|
227
|
+
compressedSize: values.compressed, compressionMethod: central.readUInt16LE(10), crc32: crc,
|
|
228
|
+
portableDirectory: admittedName.portableDirectory,
|
|
227
229
|
kind: entryKind(central.readUInt32LE(38), admittedName.directory),
|
|
228
230
|
});
|
|
229
231
|
at = next;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { type ArchiveEntryModePolicy } from "./archive-policy.js";
|
|
1
|
+
import { type ArchiveEntryModePolicy, type ArchiveEntryKind } from "./archive-policy.js";
|
|
2
|
+
import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
|
|
2
3
|
export type ZipEntry = {
|
|
3
4
|
name: string;
|
|
4
5
|
dir: boolean;
|
|
5
6
|
unixPermissions?: number | null;
|
|
7
|
+
dosPermissions?: number | null;
|
|
6
8
|
_data?: {
|
|
7
9
|
crc32?: number;
|
|
8
10
|
uncompressedSize?: number;
|
|
@@ -10,12 +12,14 @@ export type ZipEntry = {
|
|
|
10
12
|
nodeStream?: () => NodeJS.ReadableStream;
|
|
11
13
|
async: (type: "nodebuffer") => Promise<Buffer>;
|
|
12
14
|
};
|
|
15
|
+
/** Internal: register only after the complete decoder/admission association. */
|
|
16
|
+
export declare function registerAdmittedZipEntry(entry: ZipEntry, physical: ZipDirectoryEntry): void;
|
|
13
17
|
export declare function zipEntryIntegrityMetadata(entry: ZipEntry): {
|
|
14
18
|
crc32?: number;
|
|
15
19
|
uncompressedSize?: number;
|
|
16
20
|
} | undefined;
|
|
17
|
-
export declare function hasDeferredEmptyZipData(entry: ZipEntry): boolean;
|
|
18
21
|
export declare function isZipSymlinkEntry(entry: ZipEntry): boolean;
|
|
22
|
+
export declare function zipEntryKind(entry: ZipEntry): ArchiveEntryKind;
|
|
19
23
|
export declare function zipEntryMode(entry: ZipEntry, policy: ArchiveEntryModePolicy | undefined): number;
|
|
20
24
|
export declare function zipEntryDeclaredSize(entry: ZipEntry): number;
|
|
21
25
|
//# sourceMappingURL=archive-zip-entry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,
|
|
1
|
+
{"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7E,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAIF,gFAAgF;AAChF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAQ3F;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI3D;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,gBAAgB,CAM9D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,MAAM,CAOR;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
import { resolveArchiveEntryMode, } from "./archive-policy.js";
|
|
2
|
+
const admittedMetadata = new WeakMap();
|
|
3
|
+
/** Internal: register only after the complete decoder/admission association. */
|
|
4
|
+
export function registerAdmittedZipEntry(entry, physical) {
|
|
5
|
+
const mode = physical.creatorSystem === 3 ? physical.externalAttributes >>> 16 : undefined;
|
|
6
|
+
admittedMetadata.set(entry, { kind: physical.kind, mode, size: physical.size });
|
|
7
|
+
entry.dir = physical.kind === "directory";
|
|
8
|
+
// Previously unsupported non-UNIX symlinks must also be recognizable to
|
|
9
|
+
// public preflight consumers using JSZip's conventional type inspection.
|
|
10
|
+
entry.unixPermissions = mode ?? (physical.kind === "symlink" ? 0o120000 : null);
|
|
11
|
+
entry.dosPermissions = physical.creatorSystem === 0 ? physical.externalAttributes & 0x3f : null;
|
|
12
|
+
}
|
|
2
13
|
export function zipEntryIntegrityMetadata(entry) {
|
|
3
14
|
const data = entry._data;
|
|
4
15
|
if (!data || "then" in data)
|
|
5
16
|
return undefined;
|
|
6
17
|
return data;
|
|
7
18
|
}
|
|
8
|
-
export function hasDeferredEmptyZipData(entry) {
|
|
9
|
-
const data = entry._data;
|
|
10
|
-
return Boolean(data && "then" in data);
|
|
11
|
-
}
|
|
12
19
|
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
13
20
|
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
14
21
|
export function isZipSymlinkEntry(entry) {
|
|
15
|
-
return (
|
|
16
|
-
|
|
22
|
+
return zipEntryKind(entry) === "symlink";
|
|
23
|
+
}
|
|
24
|
+
export function zipEntryKind(entry) {
|
|
25
|
+
const metadata = admittedMetadata.get(entry);
|
|
26
|
+
if (metadata)
|
|
27
|
+
return metadata.kind;
|
|
28
|
+
return typeof entry.unixPermissions === "number" &&
|
|
29
|
+
(entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE
|
|
30
|
+
? "symlink" : entry.dir ? "directory" : "file";
|
|
17
31
|
}
|
|
18
32
|
export function zipEntryMode(entry, policy) {
|
|
33
|
+
const metadata = admittedMetadata.get(entry);
|
|
19
34
|
return resolveArchiveEntryMode({
|
|
20
35
|
kind: entry.dir ? "directory" : "file",
|
|
21
|
-
archivedMode: entry.unixPermissions,
|
|
36
|
+
archivedMode: metadata ? metadata.mode : entry.unixPermissions,
|
|
22
37
|
policy,
|
|
23
38
|
});
|
|
24
39
|
}
|
|
25
40
|
export function zipEntryDeclaredSize(entry) {
|
|
26
|
-
return Math.max(0, Math.floor(zipEntryIntegrityMetadata(entry)?.uncompressedSize ?? 0));
|
|
41
|
+
return admittedMetadata.get(entry)?.size ?? Math.max(0, Math.floor(zipEntryIntegrityMetadata(entry)?.uncompressedSize ?? 0));
|
|
27
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAEL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAIhC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAQhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CA+BtE"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { zipEntryIntegrityMetadata, } from "./archive-zip-entry.js";
|
|
4
4
|
import { updateCrc32 } from "./archive-crc32.js";
|
|
5
5
|
export function normalizeZipIntegrityError(error) {
|
|
6
6
|
if (error instanceof Error &&
|
|
@@ -11,9 +11,8 @@ export function normalizeZipIntegrityError(error) {
|
|
|
11
11
|
}
|
|
12
12
|
export function createZipIntegrityTransform(entry) {
|
|
13
13
|
const metadata = zipEntryIntegrityMetadata(entry);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const expectedSize = deferredEmpty ? 0 : metadata?.uncompressedSize;
|
|
14
|
+
const expectedCrc32 = metadata?.crc32;
|
|
15
|
+
const expectedSize = metadata?.uncompressedSize;
|
|
17
16
|
if (typeof expectedCrc32 !== "number" ||
|
|
18
17
|
!Number.isInteger(expectedCrc32) ||
|
|
19
18
|
typeof expectedSize !== "number" ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,MAAM,MAAM,mBAAmB,GAAG;
|
|
1
|
+
{"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AA2BrE,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAkF9B"}
|