@openclaw/fs-safe 0.11.0 → 0.13.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 +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +55 -46
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +50 -17
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.js +2 -2
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +12 -2
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +21 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +35 -6
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +152 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +33 -29
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +182 -33
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +316 -325
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +133 -4
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +20 -16
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +28 -9
- package/docs/native.md +46 -10
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
|
+
import { isPathRelativeEscape } from "./path.js";
|
|
4
|
+
import { realpathSync } from "./realpath.js";
|
|
5
|
+
function windowsComparablePath(value, normalize) {
|
|
6
|
+
const original = normalize
|
|
7
|
+
? path.win32.normalize(value.replaceAll("/", "\\"))
|
|
8
|
+
: value.replaceAll("/", "\\");
|
|
9
|
+
if (/^\\\\\?\\UNC\\/i.test(original)) {
|
|
10
|
+
return {
|
|
11
|
+
original,
|
|
12
|
+
comparison: `\\\\${original.slice(8)}`,
|
|
13
|
+
originalIndex: (comparisonIndex) => comparisonIndex + 6,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (original.startsWith("\\\\?\\")) {
|
|
17
|
+
return {
|
|
18
|
+
original,
|
|
19
|
+
comparison: original.slice(4),
|
|
20
|
+
originalIndex: (comparisonIndex) => comparisonIndex + 4,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { original, comparison: original, originalIndex: (comparisonIndex) => comparisonIndex };
|
|
24
|
+
}
|
|
25
|
+
function trimWindowsTrailingSeparators(value) {
|
|
26
|
+
const rootLength = path.win32.parse(value).root.length;
|
|
27
|
+
let end = value.length;
|
|
28
|
+
while (end > rootLength && value[end - 1] === "\\")
|
|
29
|
+
end -= 1;
|
|
30
|
+
return value.slice(0, end);
|
|
31
|
+
}
|
|
32
|
+
function trimWindowsLeadingSeparators(value) {
|
|
33
|
+
let start = 0;
|
|
34
|
+
while (value[start] === "\\")
|
|
35
|
+
start += 1;
|
|
36
|
+
return value.slice(start);
|
|
37
|
+
}
|
|
38
|
+
function exactWindowsRootPrefix(rootPath, candidatePath) {
|
|
39
|
+
const root = trimWindowsTrailingSeparators(rootPath.replaceAll("/", "\\"));
|
|
40
|
+
const candidate = candidatePath.replaceAll("/", "\\");
|
|
41
|
+
let relativePath;
|
|
42
|
+
if (candidate === root) {
|
|
43
|
+
relativePath = "";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const rootWithSep = root.endsWith("\\") ? root : `${root}\\`;
|
|
47
|
+
if (!candidate.startsWith(rootWithSep))
|
|
48
|
+
return undefined;
|
|
49
|
+
relativePath = trimWindowsLeadingSeparators(candidate.slice(rootWithSep.length));
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
admission: "exact",
|
|
53
|
+
candidateRootPath: root,
|
|
54
|
+
path: relativePath === "" ? root : `${root}${root.endsWith("\\") ? "" : "\\"}${relativePath}`,
|
|
55
|
+
relativePath,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function windowsRootPrefix(rootPath, candidatePath) {
|
|
59
|
+
const exact = exactWindowsRootPrefix(rootPath, candidatePath);
|
|
60
|
+
if (exact)
|
|
61
|
+
return exact;
|
|
62
|
+
const trusted = windowsComparablePath(rootPath, true);
|
|
63
|
+
const supplied = windowsComparablePath(candidatePath, false);
|
|
64
|
+
const root = trimWindowsTrailingSeparators(trusted.original);
|
|
65
|
+
const rootComparison = trimWindowsTrailingSeparators(trusted.comparison);
|
|
66
|
+
const candidate = supplied.comparison;
|
|
67
|
+
if (!path.win32.isAbsolute(supplied.original))
|
|
68
|
+
return undefined;
|
|
69
|
+
let relativePath;
|
|
70
|
+
let candidateRootPath;
|
|
71
|
+
let admission;
|
|
72
|
+
if (candidate === rootComparison) {
|
|
73
|
+
relativePath = "";
|
|
74
|
+
candidateRootPath = supplied.original;
|
|
75
|
+
admission = "exact";
|
|
76
|
+
}
|
|
77
|
+
else if (candidate.toLowerCase() === rootComparison.toLowerCase()) {
|
|
78
|
+
relativePath = "";
|
|
79
|
+
candidateRootPath = supplied.original;
|
|
80
|
+
admission = "identity";
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const rootWithSep = rootComparison.endsWith("\\") ? rootComparison : `${rootComparison}\\`;
|
|
84
|
+
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
85
|
+
if (candidatePrefix === rootWithSep) {
|
|
86
|
+
admission = "exact";
|
|
87
|
+
}
|
|
88
|
+
else if (candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()) {
|
|
89
|
+
admission = "identity";
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
relativePath = trimWindowsLeadingSeparators(candidate.slice(rootWithSep.length));
|
|
95
|
+
candidateRootPath = supplied.original.slice(0, supplied.originalIndex(rootComparison.length));
|
|
96
|
+
}
|
|
97
|
+
const rebasedPath = relativePath === ""
|
|
98
|
+
? root
|
|
99
|
+
: `${root}${root.endsWith("\\") ? "" : "\\"}${relativePath}`;
|
|
100
|
+
return { candidateRootPath, path: rebasedPath, relativePath, admission };
|
|
101
|
+
}
|
|
102
|
+
function exactRootIdentity(rootPath, expected) {
|
|
103
|
+
if (typeof expected?.dev === "bigint" && typeof expected.ino === "bigint") {
|
|
104
|
+
return { dev: expected.dev, ino: expected.ino };
|
|
105
|
+
}
|
|
106
|
+
const observed = inspectDirectoryIdentitySync(rootPath);
|
|
107
|
+
return { dev: observed.dev, ino: observed.ino };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Admit a path at a trusted directory boundary.
|
|
111
|
+
*
|
|
112
|
+
* On Windows, an exact structural prefix is allocation-only and performs no
|
|
113
|
+
* filesystem observation. A match which depends on case folding is accepted
|
|
114
|
+
* only when the candidate prefix names the exact trusted root object. Accepted
|
|
115
|
+
* paths are always returned under the trusted root spelling.
|
|
116
|
+
*/
|
|
117
|
+
export function admitPathInsideRoot(params) {
|
|
118
|
+
// Some platform-fallback tests intentionally spoof process.platform while
|
|
119
|
+
// retaining Node's POSIX path module. A single-slash absolute root remains
|
|
120
|
+
// unambiguously POSIX; double-slash and slash-backslash roots are reserved
|
|
121
|
+
// for the synthetic Windows namespace cases below.
|
|
122
|
+
const usesPosixBoundary = process.platform !== "win32" || (path.sep === "/" &&
|
|
123
|
+
params.rootPath.startsWith("/") &&
|
|
124
|
+
params.rootPath[1] !== "/" &&
|
|
125
|
+
params.rootPath[1] !== "\\");
|
|
126
|
+
if (usesPosixBoundary) {
|
|
127
|
+
if (params.rootPath.startsWith("/") &&
|
|
128
|
+
params.candidatePath.startsWith("/") &&
|
|
129
|
+
!params.candidatePath.includes("/..") &&
|
|
130
|
+
(params.candidatePath === params.rootPath ||
|
|
131
|
+
(params.candidatePath.startsWith(params.rootPath) &&
|
|
132
|
+
(params.rootPath.endsWith("/") || params.candidatePath[params.rootPath.length] === "/")))) {
|
|
133
|
+
const relativeStart = params.rootPath.endsWith("/")
|
|
134
|
+
? params.rootPath.length
|
|
135
|
+
: params.rootPath.length + 1;
|
|
136
|
+
return {
|
|
137
|
+
admission: "exact",
|
|
138
|
+
path: params.candidatePath,
|
|
139
|
+
relativePath: params.candidatePath === params.rootPath
|
|
140
|
+
? ""
|
|
141
|
+
: params.candidatePath.slice(relativeStart),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const root = path.resolve(params.rootPath);
|
|
145
|
+
const candidate = path.resolve(params.candidatePath);
|
|
146
|
+
const relativePath = path.relative(root, candidate);
|
|
147
|
+
if (isPathRelativeEscape(relativePath))
|
|
148
|
+
return undefined;
|
|
149
|
+
return { path: candidate, relativePath: relativePath === "." ? "" : relativePath, admission: "exact" };
|
|
150
|
+
}
|
|
151
|
+
const match = windowsRootPrefix(params.rootPath, params.candidatePath);
|
|
152
|
+
if (!match)
|
|
153
|
+
return undefined;
|
|
154
|
+
const admitted = () => ({
|
|
155
|
+
admission: match.admission,
|
|
156
|
+
path: match.path,
|
|
157
|
+
relativePath: match.relativePath,
|
|
158
|
+
});
|
|
159
|
+
if (match.admission === "exact")
|
|
160
|
+
return admitted();
|
|
161
|
+
const cached = params.identityCache?.get(match.candidateRootPath);
|
|
162
|
+
if (cached !== undefined)
|
|
163
|
+
return cached ? admitted() : undefined;
|
|
164
|
+
try {
|
|
165
|
+
const expected = exactRootIdentity(params.rootPath, params.rootIdentity);
|
|
166
|
+
const candidateRootPath = params.resolveCandidateRoot
|
|
167
|
+
? realpathSync.native(match.candidateRootPath)
|
|
168
|
+
: match.candidateRootPath;
|
|
169
|
+
if (params.inspectCandidateRoot) {
|
|
170
|
+
params.inspectCandidateRoot(candidateRootPath, expected);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
inspectDirectoryIdentitySync(candidateRootPath, expected);
|
|
174
|
+
}
|
|
175
|
+
params.identityCache?.set(match.candidateRootPath, true);
|
|
176
|
+
return admitted();
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
params.identityCache?.set(match.candidateRootPath, false);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
package/dist/root-context.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import fs, { type BigIntStats } from "node:fs";
|
|
2
|
+
import { type AsyncDirectoryGuard, type DirectoryObservationGuard } from "./directory-guard.js";
|
|
1
3
|
export type RootContext = {
|
|
2
4
|
rootDir: string;
|
|
5
|
+
rootGuard?: AsyncDirectoryGuard<BigIntStats>;
|
|
3
6
|
rootIdentity: {
|
|
4
7
|
dev: number;
|
|
5
8
|
ino: number;
|
|
@@ -16,7 +19,15 @@ export declare function assertValidRootDestinationPath(relativePath: string): vo
|
|
|
16
19
|
export declare function expandRelativePathWithHome(relativePath: string): Promise<string>;
|
|
17
20
|
export declare function resolveRootContext(rootDir: string): Promise<RootContext>;
|
|
18
21
|
export declare function rootRelativeReadPath(root: RootContext, filePath: string): string;
|
|
19
|
-
export declare function
|
|
22
|
+
export declare function assertRootIdentityCurrentSync(root: RootContext, observe?: (stat: fs.BigIntStats) => void): void;
|
|
23
|
+
export declare function assertRootIdentityCurrent(root: RootContext, observe?: (stat: fs.BigIntStats) => void): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Observe the current Root with an exact, operation-local receipt.
|
|
26
|
+
*
|
|
27
|
+
* This is deliberately separate from {@link assertRootIdentityCurrent}: callers
|
|
28
|
+
* must not retain the returned guard beyond the operation that requested it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createRootObservationGuard(root: RootContext): Promise<DirectoryObservationGuard | undefined>;
|
|
20
31
|
export declare function resolvePathInRoot(root: RootContext, relativePath: string, options?: {
|
|
21
32
|
aliasErrorCode?: "outside-workspace" | "path-alias";
|
|
22
33
|
allowFinalSymlink?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
|
package/dist/root-context.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { extendDirectoryObservationGuard, inspectDirectoryIdentitySync, inspectDirectoryObservationSync, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { sameFileIdentity } from "./file-identity.js";
|
|
7
7
|
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, } from "./path.js";
|
|
8
8
|
import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath } from "./root-path.js";
|
|
9
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
9
10
|
import { outsideWorkspaceError, rootPathChangedError } from "./root-errors.js";
|
|
10
11
|
import { isDriveRelativePath } from "./safe-path-segment.js";
|
|
11
12
|
import { realpathSync } from "./realpath.js";
|
|
12
13
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
14
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathFromBasePreservingWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
15
|
+
function hasExactRootIdentity(identity) {
|
|
16
|
+
return typeof identity.dev === "bigint" && typeof identity.ino === "bigint";
|
|
17
|
+
}
|
|
13
18
|
export const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
|
|
14
19
|
export function assertValidRootRelativePath(relativePath) {
|
|
15
20
|
assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
21
|
+
assertNoWindowsPathAlias(relativePath, "filesystem", "relative path uses a Windows filesystem namespace alias");
|
|
16
22
|
}
|
|
17
23
|
export function assertValidRootDestinationPath(relativePath) {
|
|
18
24
|
assertValidRootRelativePath(relativePath);
|
|
@@ -26,29 +32,42 @@ const WINDOWS_PARENT_COMPONENT = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
|
26
32
|
export async function expandRelativePathWithHome(relativePath) {
|
|
27
33
|
const homeOnly = relativePath === "~";
|
|
28
34
|
const homePrefix = relativePath.startsWith("~/") || (path.sep === "\\" && relativePath.startsWith("~\\"));
|
|
29
|
-
if (!homeOnly && !homePrefix)
|
|
35
|
+
if (!homeOnly && !homePrefix) {
|
|
36
|
+
assertNoWindowsPathAlias(relativePath, "filesystem", "relative path uses a Windows filesystem namespace alias");
|
|
30
37
|
return relativePath;
|
|
38
|
+
}
|
|
31
39
|
const rawHome = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
40
|
+
assertNoWindowsPathAlias(rawHome, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
32
41
|
if (cachedHomePath?.raw !== rawHome) {
|
|
33
42
|
let realHome = rawHome;
|
|
34
43
|
try {
|
|
35
|
-
realHome = realpathSync.native(rawHome);
|
|
44
|
+
realHome = realpathSync.native(pathForWindowsFilesystem(rawHome));
|
|
36
45
|
}
|
|
37
46
|
catch {
|
|
38
47
|
// If the home dir cannot be canonicalized, keep lexical expansion behavior.
|
|
39
48
|
}
|
|
40
49
|
cachedHomePath = { raw: rawHome, real: realHome };
|
|
41
50
|
}
|
|
42
|
-
|
|
51
|
+
if (homeOnly) {
|
|
52
|
+
assertNoWindowsPathAlias(cachedHomePath.real, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
53
|
+
return cachedHomePath.real;
|
|
54
|
+
}
|
|
55
|
+
const expanded = `${ensureTrailingSep(cachedHomePath.real)}${relativePath.slice(2)}`;
|
|
56
|
+
assertNoWindowsPathAlias(expanded, "filesystem", "expanded path uses a Windows filesystem namespace alias");
|
|
57
|
+
return expanded;
|
|
43
58
|
}
|
|
44
59
|
export async function resolveRootContext(rootDir) {
|
|
45
60
|
assertNoNulPathInput(rootDir, "root dir contains a NUL byte");
|
|
46
|
-
|
|
61
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
|
|
62
|
+
const lexicalRoot = resolvePathPreservingWindowsRoot(rootDir);
|
|
63
|
+
assertNoWindowsPathAlias(lexicalRoot, "filesystem", "root dir uses a Windows filesystem namespace alias");
|
|
47
64
|
let rootReal;
|
|
65
|
+
let rootStat;
|
|
48
66
|
let rootIdentity;
|
|
49
67
|
try {
|
|
50
|
-
rootReal = realpathSync.native(rootDir);
|
|
51
|
-
|
|
68
|
+
rootReal = realpathSync.native(pathForWindowsFilesystem(rootDir));
|
|
69
|
+
assertNoWindowsPathAlias(rootReal, "filesystem", "canonical root path uses a Windows filesystem namespace alias");
|
|
70
|
+
rootStat = await inspectFileIdentity(() => {
|
|
52
71
|
const stat = fs.statSync(rootReal, { bigint: true });
|
|
53
72
|
if (!stat.isDirectory())
|
|
54
73
|
throw new FsSafeError("invalid-path", "root dir is not a directory");
|
|
@@ -67,6 +86,7 @@ export async function resolveRootContext(rootDir) {
|
|
|
67
86
|
}
|
|
68
87
|
return {
|
|
69
88
|
rootDir: lexicalRoot,
|
|
89
|
+
rootGuard: { dir: rootReal, realPath: rootReal, stat: rootStat },
|
|
70
90
|
rootIdentity,
|
|
71
91
|
rootReal,
|
|
72
92
|
rootWithSep: ensureTrailingSep(rootReal),
|
|
@@ -77,11 +97,21 @@ export function rootRelativeReadPath(root, filePath) {
|
|
|
77
97
|
if (!absoluteInput)
|
|
78
98
|
return filePath;
|
|
79
99
|
const raw = process.platform === "win32" ? filePath.replaceAll("/", path.sep) : filePath;
|
|
80
|
-
|
|
100
|
+
const bases = root.rootDir === root.rootReal ? [root.rootReal] : [root.rootDir, root.rootReal];
|
|
101
|
+
for (const base of bases) {
|
|
102
|
+
if (process.platform === "win32") {
|
|
103
|
+
const admitted = admitPathInsideRoot({
|
|
104
|
+
rootPath: base,
|
|
105
|
+
candidatePath: raw,
|
|
106
|
+
rootIdentity: root.rootIdentity,
|
|
107
|
+
resolveCandidateRoot: base === root.rootDir && root.rootDir !== root.rootReal,
|
|
108
|
+
});
|
|
109
|
+
if (admitted)
|
|
110
|
+
return admitted.relativePath;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
81
113
|
const prefix = ensureTrailingSep(base);
|
|
82
|
-
const matches =
|
|
83
|
-
? raw.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase()
|
|
84
|
-
: raw.startsWith(prefix);
|
|
114
|
+
const matches = raw.startsWith(prefix);
|
|
85
115
|
if (matches) {
|
|
86
116
|
let start = prefix.length;
|
|
87
117
|
while (raw[start] === path.sep)
|
|
@@ -91,11 +121,12 @@ export function rootRelativeReadPath(root, filePath) {
|
|
|
91
121
|
}
|
|
92
122
|
return raw;
|
|
93
123
|
}
|
|
94
|
-
export
|
|
124
|
+
export function assertRootIdentityCurrentSync(root, observe) {
|
|
95
125
|
let current;
|
|
96
126
|
try {
|
|
97
127
|
if (typeof root.rootIdentity.dev === "bigint" && typeof root.rootIdentity.ino === "bigint") {
|
|
98
|
-
|
|
128
|
+
const stat = inspectDirectoryIdentitySync(root.rootReal, { dev: root.rootIdentity.dev, ino: root.rootIdentity.ino });
|
|
129
|
+
observe?.(stat);
|
|
99
130
|
return;
|
|
100
131
|
}
|
|
101
132
|
current = fs.lstatSync(root.rootReal);
|
|
@@ -109,11 +140,36 @@ export async function assertRootIdentityCurrent(root) {
|
|
|
109
140
|
throw rootPathChangedError();
|
|
110
141
|
}
|
|
111
142
|
}
|
|
143
|
+
export async function assertRootIdentityCurrent(root, observe) {
|
|
144
|
+
assertRootIdentityCurrentSync(root, observe);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Observe the current Root with an exact, operation-local receipt.
|
|
148
|
+
*
|
|
149
|
+
* This is deliberately separate from {@link assertRootIdentityCurrent}: callers
|
|
150
|
+
* must not retain the returned guard beyond the operation that requested it.
|
|
151
|
+
*/
|
|
152
|
+
export async function createRootObservationGuard(root) {
|
|
153
|
+
const rootIdentity = root.rootIdentity;
|
|
154
|
+
if (!hasExactRootIdentity(rootIdentity)) {
|
|
155
|
+
await assertRootIdentityCurrent(root);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const observed = await inspectDirectoryObservationSync(root.rootReal, rootIdentity);
|
|
160
|
+
return extendDirectoryObservationGuard(observed, root.rootReal, root.rootReal);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw rootPathChangedError(error instanceof Error ? error : undefined);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
112
166
|
export async function resolvePathInRoot(root, relativePath, options) {
|
|
113
167
|
assertValidRootRelativePath(relativePath);
|
|
114
168
|
await assertRootIdentityCurrent(root);
|
|
115
169
|
const expanded = await expandRelativePathWithHome(relativePath);
|
|
116
|
-
|
|
170
|
+
assertNoWindowsPathAlias(expanded, "filesystem", "expanded path uses a Windows filesystem namespace alias");
|
|
171
|
+
let resolved = resolvePathFromBasePreservingWindowsRoot(root.rootWithSep, expanded);
|
|
172
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "resolved path uses a Windows filesystem namespace alias");
|
|
117
173
|
if (!options?.resolveCanonical && !isPathInside(root.rootWithSep, resolved)) {
|
|
118
174
|
throw outsideWorkspaceError();
|
|
119
175
|
}
|
|
@@ -123,11 +179,13 @@ export async function resolvePathInRoot(root, relativePath, options) {
|
|
|
123
179
|
const rawAbsolutePath = path.isAbsolute(expanded)
|
|
124
180
|
? expanded
|
|
125
181
|
: `${root.rootWithSep}${expanded}`;
|
|
182
|
+
assertNoWindowsPathAlias(rawAbsolutePath, "filesystem", "resolved path uses a Windows filesystem namespace alias");
|
|
126
183
|
try {
|
|
127
184
|
const resolution = {
|
|
128
185
|
absolutePath: rawAbsolutePath,
|
|
129
186
|
rootPath: root.rootReal,
|
|
130
187
|
rootCanonicalPath: root.rootReal,
|
|
188
|
+
rootIdentity: root.rootIdentity,
|
|
131
189
|
boundaryLabel: "root",
|
|
132
190
|
policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
133
191
|
rejectSymlinks: options?.rejectSymlinks,
|
|
@@ -142,8 +200,23 @@ export async function resolvePathInRoot(root, relativePath, options) {
|
|
|
142
200
|
throw new FsSafeError("path-alias", "parent traversal resolves differently through a symlink");
|
|
143
201
|
}
|
|
144
202
|
}
|
|
145
|
-
if (options?.resolveCanonical)
|
|
203
|
+
if (options?.resolveCanonical) {
|
|
146
204
|
resolved = checked.canonicalPath;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// resolveRootPath preserves ordinary caller spelling as part of its
|
|
208
|
+
// public receipt. Re-admit the exact normalized spelling before it is
|
|
209
|
+
// reused for Root I/O: on a case-sensitive Windows directory it may
|
|
210
|
+
// otherwise name a distinct case-folded sibling after raw traversal.
|
|
211
|
+
const admitted = admitPathInsideRoot({
|
|
212
|
+
rootPath: root.rootReal,
|
|
213
|
+
candidatePath: checked.absolutePath,
|
|
214
|
+
rootIdentity: root.rootIdentity,
|
|
215
|
+
});
|
|
216
|
+
if (!admitted)
|
|
217
|
+
throw outsideWorkspaceError();
|
|
218
|
+
resolved = admitted.path;
|
|
219
|
+
}
|
|
147
220
|
}
|
|
148
221
|
catch (error) {
|
|
149
222
|
if (error instanceof FsSafeError && error.code === "symlink") {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
|
|
2
|
+
import type { RootContext } from "./root-context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Complete the common Root.mkdir case with one exact existing parent. This is
|
|
5
|
+
* optional evidence: every uncertain probe returns to the ordered root walk.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tryMkdirAtExactParent(root: RootContext, targetPath: string, mutationAdmission: PinnedWriteMutationAdmission): Promise<boolean>;
|
|
8
|
+
export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission): Promise<string>;
|
|
9
|
+
export declare function mkdirPathFallback(root: RootContext, resolved: {
|
|
10
|
+
rootReal: string;
|
|
11
|
+
resolved: string;
|
|
12
|
+
}, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=root-directory-creation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-directory-creation.d.ts","sourceRoot":"","sources":["../src/root-directory-creation.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAoDrD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,4BAA4B,GAC9C,OAAO,CAAC,OAAO,CAAC,CAoGlB;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,cAAc,UAAQ,EACtB,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,IAAI,CAAC,CAuBf"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
|
+
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
|
|
7
|
+
import { hasNodeErrorCode } from "./path.js";
|
|
8
|
+
import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
|
|
9
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
10
|
+
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
11
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
12
|
+
function mutationWalkOptions(mutationAdmission, operationTarget, assertBeforeMutation) {
|
|
13
|
+
const session = assertBeforeMutation === undefined
|
|
14
|
+
? mutationAdmission.beginSharedParentWalk?.()
|
|
15
|
+
: undefined;
|
|
16
|
+
return {
|
|
17
|
+
dispose: () => session?.dispose(),
|
|
18
|
+
options: {
|
|
19
|
+
rejectSymlinks: mutationAdmission.rejectParentSymlinks,
|
|
20
|
+
revalidateParentAfterBeforeComponent: true,
|
|
21
|
+
retainedTargetPath: session?.retainedTargetPath,
|
|
22
|
+
synchronousAuthorizationIncludesFence: session !== undefined,
|
|
23
|
+
afterCreateComponent: session?.advanceCreatedDirectory,
|
|
24
|
+
beforeCreateComponent: (componentPath, prospectiveDirectory, retainedTargetPath, parent) => {
|
|
25
|
+
const targetPath = retainedTargetPath ?? operationTarget(prospectiveDirectory);
|
|
26
|
+
const request = Object.freeze({
|
|
27
|
+
targetPath,
|
|
28
|
+
mutationPath: componentPath,
|
|
29
|
+
phase: "parent-create",
|
|
30
|
+
});
|
|
31
|
+
return session?.tryAuthorizeAtParent(request, parent) ??
|
|
32
|
+
(session?.authorize(request) ?? mutationAdmission.authorize(request));
|
|
33
|
+
},
|
|
34
|
+
beforeUseComponent: async (_componentPath, prospectiveDirectory, retainedTargetPath) => {
|
|
35
|
+
const targetPath = retainedTargetPath ?? operationTarget(prospectiveDirectory);
|
|
36
|
+
const request = Object.freeze({
|
|
37
|
+
targetPath,
|
|
38
|
+
mutationPath: targetPath,
|
|
39
|
+
phase: "parent",
|
|
40
|
+
});
|
|
41
|
+
await (session?.authorize(request) ?? mutationAdmission.authorize(request));
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Complete the common Root.mkdir case with one exact existing parent. This is
|
|
48
|
+
* optional evidence: every uncertain probe returns to the ordered root walk.
|
|
49
|
+
*/
|
|
50
|
+
export async function tryMkdirAtExactParent(root, targetPath, mutationAdmission) {
|
|
51
|
+
if (getFsSafeTestHooks()?.beforeRootFallbackMutation)
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof root.rootIdentity.dev !== "bigint" ||
|
|
54
|
+
typeof root.rootIdentity.ino !== "bigint" ||
|
|
55
|
+
root.rootIdentity.dev === 0n || root.rootIdentity.ino === 0n)
|
|
56
|
+
return false;
|
|
57
|
+
const rootIdentity = Object.freeze({
|
|
58
|
+
dev: root.rootIdentity.dev,
|
|
59
|
+
ino: root.rootIdentity.ino,
|
|
60
|
+
});
|
|
61
|
+
const session = mutationAdmission.beginSharedParentWalk?.();
|
|
62
|
+
if (!session)
|
|
63
|
+
return false;
|
|
64
|
+
try {
|
|
65
|
+
if (path.resolve(session.retainedTargetPath) !== path.resolve(targetPath))
|
|
66
|
+
return false;
|
|
67
|
+
const parentPath = path.dirname(targetPath);
|
|
68
|
+
let parentGuard;
|
|
69
|
+
try {
|
|
70
|
+
parentGuard = await createAsyncDirectoryGuard(parentPath, { bigint: true });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
fsSync.lstatSync(targetPath, { bigint: true });
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const admittedParent = admitPathInsideRoot({
|
|
84
|
+
rootPath: root.rootReal,
|
|
85
|
+
candidatePath: parentPath,
|
|
86
|
+
rootIdentity,
|
|
87
|
+
});
|
|
88
|
+
if (admittedParent?.admission !== "exact" || admittedParent.path !== parentPath ||
|
|
89
|
+
parentGuard.realPath !== parentPath)
|
|
90
|
+
return false;
|
|
91
|
+
await session.authorize(Object.freeze({
|
|
92
|
+
targetPath,
|
|
93
|
+
mutationPath: targetPath,
|
|
94
|
+
phase: "parent",
|
|
95
|
+
}));
|
|
96
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
97
|
+
try {
|
|
98
|
+
fsSync.lstatSync(targetPath, { bigint: true });
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
assertSyncDirectoryGuard(parentGuard);
|
|
106
|
+
const createAdmission = session.tryAuthorizeAtParent(Object.freeze({
|
|
107
|
+
targetPath,
|
|
108
|
+
mutationPath: targetPath,
|
|
109
|
+
phase: "parent-create",
|
|
110
|
+
}), checkedMutationDirectory(parentPath, parentGuard.realPath, parentGuard.stat));
|
|
111
|
+
if (!createAdmission)
|
|
112
|
+
return false;
|
|
113
|
+
try {
|
|
114
|
+
await fs.mkdir(targetPath);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (hasNodeErrorCode(error, "EEXIST"))
|
|
118
|
+
return false;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
const observeCreated = async () => {
|
|
122
|
+
const childGuard = await createAsyncDirectoryGuard(targetPath, { bigint: true });
|
|
123
|
+
const parentAfter = inspectDirectoryIdentitySync(parentPath, parentGuard.stat);
|
|
124
|
+
assertDirectoryIdentitySync(root.rootReal, rootIdentity);
|
|
125
|
+
const admittedChild = admitPathInsideRoot({
|
|
126
|
+
rootPath: root.rootReal,
|
|
127
|
+
candidatePath: childGuard.realPath,
|
|
128
|
+
rootIdentity,
|
|
129
|
+
});
|
|
130
|
+
if (childGuard.realPath !== targetPath || childGuard.dir !== targetPath ||
|
|
131
|
+
admittedChild?.admission !== "exact" || admittedChild.path !== targetPath) {
|
|
132
|
+
throw new FsSafeError("path-mismatch", "created directory changed during operation");
|
|
133
|
+
}
|
|
134
|
+
return { childGuard, parentAfter };
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const created = await observeCreated();
|
|
138
|
+
if (session.advanceCreatedDirectory(Object.freeze({
|
|
139
|
+
admission: createAdmission,
|
|
140
|
+
parent: checkedMutationDirectory(parentPath, parentGuard.realPath, created.parentAfter),
|
|
141
|
+
child: checkedMutationDirectory(targetPath, created.childGuard.realPath, created.childGuard.stat),
|
|
142
|
+
})))
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Post-create observations are optional receipt evidence. The directory
|
|
147
|
+
// was already created, so renew ordered admission without a second mkdir.
|
|
148
|
+
}
|
|
149
|
+
await session.authorize(Object.freeze({
|
|
150
|
+
targetPath,
|
|
151
|
+
mutationPath: targetPath,
|
|
152
|
+
phase: "parent",
|
|
153
|
+
}));
|
|
154
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
155
|
+
assertDirectoryIdentitySync(root.rootReal, rootIdentity);
|
|
156
|
+
assertFinalSymlinkRejected(targetPath, true);
|
|
157
|
+
const refreshed = await observeCreated();
|
|
158
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
159
|
+
await assertAsyncDirectoryGuard(refreshed.childGuard);
|
|
160
|
+
assertDirectoryIdentitySync(root.rootReal, rootIdentity);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
session.dispose();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export async function prepareRootWriteTarget(root, targetPath, assertBeforeMutation, mutationAdmission) {
|
|
168
|
+
const basename = path.basename(targetPath);
|
|
169
|
+
const baseParams = {
|
|
170
|
+
rootReal: root.rootReal,
|
|
171
|
+
rootIdentity: root.rootIdentity,
|
|
172
|
+
targetPath: path.dirname(targetPath),
|
|
173
|
+
assertBeforeMutation,
|
|
174
|
+
};
|
|
175
|
+
const mutationWalk = mutationAdmission
|
|
176
|
+
? mutationWalkOptions(mutationAdmission, (prospectiveParent) => path.join(prospectiveParent, basename), assertBeforeMutation)
|
|
177
|
+
: undefined;
|
|
178
|
+
let parentPath;
|
|
179
|
+
try {
|
|
180
|
+
parentPath = await mkdirPathComponentsWithGuards(mutationWalk ? {
|
|
181
|
+
...baseParams,
|
|
182
|
+
...mutationWalk.options,
|
|
183
|
+
} : baseParams);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
mutationWalk?.dispose();
|
|
187
|
+
}
|
|
188
|
+
// Continue through the guarded walk's real parent instead of re-entering
|
|
189
|
+
// the original path through a symlinked component.
|
|
190
|
+
return path.join(parentPath, basename);
|
|
191
|
+
}
|
|
192
|
+
export async function mkdirPathFallback(root, resolved, assertBeforeMutation, rejectSymlinks = false, mutationAdmission) {
|
|
193
|
+
const baseParams = {
|
|
194
|
+
rootReal: resolved.rootReal, targetPath: resolved.resolved, assertBeforeMutation,
|
|
195
|
+
rootIdentity: root.rootIdentity,
|
|
196
|
+
rejectSymlinks,
|
|
197
|
+
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
198
|
+
};
|
|
199
|
+
const mutationWalk = mutationAdmission
|
|
200
|
+
? mutationWalkOptions(mutationAdmission, (prospectiveDirectory) => prospectiveDirectory, assertBeforeMutation)
|
|
201
|
+
: undefined;
|
|
202
|
+
try {
|
|
203
|
+
await mkdirPathComponentsWithGuards(mutationWalk ? {
|
|
204
|
+
...baseParams,
|
|
205
|
+
...mutationWalk.options,
|
|
206
|
+
rejectSymlinks: rejectSymlinks || mutationAdmission.rejectParentSymlinks,
|
|
207
|
+
} : baseParams);
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
mutationWalk?.dispose();
|
|
211
|
+
}
|
|
212
|
+
}
|