@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12
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/README.md +112 -10
- package/dist/archive/archive.d.ts +129 -0
- package/dist/archive/archive.d.ts.map +1 -0
- package/dist/archive/archive.js +223 -0
- package/dist/archive/archive.js.map +1 -0
- package/dist/archive/entries.d.ts +132 -0
- package/dist/archive/entries.d.ts.map +1 -0
- package/dist/archive/entries.js +223 -0
- package/dist/archive/entries.js.map +1 -0
- package/dist/archive/limits.d.ts +39 -0
- package/dist/archive/limits.d.ts.map +1 -0
- package/dist/archive/limits.js +59 -0
- package/dist/archive/limits.js.map +1 -0
- package/dist/archive/records.d.ts +14 -0
- package/dist/archive/records.d.ts.map +1 -0
- package/dist/archive/records.js +3 -0
- package/dist/archive/records.js.map +1 -0
- package/dist/archive/tar-reader.d.ts +18 -0
- package/dist/archive/tar-reader.d.ts.map +1 -0
- package/dist/archive/tar-reader.js +173 -0
- package/dist/archive/tar-reader.js.map +1 -0
- package/dist/archive/zip-reader.d.ts +76 -0
- package/dist/archive/zip-reader.d.ts.map +1 -0
- package/dist/archive/zip-reader.js +376 -0
- package/dist/archive/zip-reader.js.map +1 -0
- package/dist/archive.d.ts +14 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +12 -0
- package/dist/archive.js.map +1 -0
- package/dist/asset-reference.d.ts +17 -1
- package/dist/asset-reference.d.ts.map +1 -1
- package/dist/asset-reference.js +31 -7
- package/dist/asset-reference.js.map +1 -1
- package/dist/dirent-kind.d.ts +13 -40
- package/dist/dirent-kind.d.ts.map +1 -1
- package/dist/dirent-kind.js +21 -69
- package/dist/dirent-kind.js.map +1 -1
- package/dist/entrypoint.d.ts +2 -2
- package/dist/entrypoint.d.ts.map +1 -1
- package/dist/entrypoint.js +4 -3
- package/dist/entrypoint.js.map +1 -1
- package/dist/env-flag.d.ts +13 -5
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +13 -5
- package/dist/env-flag.js.map +1 -1
- package/dist/errors/confirmed-absent.d.ts +41 -0
- package/dist/errors/confirmed-absent.d.ts.map +1 -0
- package/dist/errors/confirmed-absent.js +43 -0
- package/dist/errors/confirmed-absent.js.map +1 -0
- package/dist/errors/errno-table.d.ts +177 -0
- package/dist/errors/errno-table.d.ts.map +1 -0
- package/dist/errors/errno-table.js +343 -0
- package/dist/errors/errno-table.js.map +1 -0
- package/dist/errors/fs-boundary.d.ts +36 -0
- package/dist/errors/fs-boundary.d.ts.map +1 -0
- package/dist/errors/fs-boundary.js +121 -0
- package/dist/errors/fs-boundary.js.map +1 -0
- package/dist/errors/fs-fault.d.ts +176 -0
- package/dist/errors/fs-fault.d.ts.map +1 -0
- package/dist/errors/fs-fault.js +215 -0
- package/dist/errors/fs-fault.js.map +1 -0
- package/dist/errors/git-errno.d.ts +13 -0
- package/dist/errors/git-errno.d.ts.map +1 -0
- package/dist/errors/git-errno.js +45 -0
- package/dist/errors/git-errno.js.map +1 -0
- package/dist/errors/path-present.d.ts +72 -0
- package/dist/errors/path-present.d.ts.map +1 -0
- package/dist/errors/path-present.js +110 -0
- package/dist/errors/path-present.js.map +1 -0
- package/dist/errors/rag-index-empty.d.ts +16 -0
- package/dist/errors/rag-index-empty.d.ts.map +1 -0
- package/dist/errors/rag-index-empty.js +16 -0
- package/dist/errors/rag-index-empty.js.map +1 -0
- package/dist/errors/suppressed-faults.d.ts +34 -0
- package/dist/errors/suppressed-faults.d.ts.map +1 -0
- package/dist/errors/suppressed-faults.js +66 -0
- package/dist/errors/suppressed-faults.js.map +1 -0
- package/dist/file-crawler.d.ts +13 -18
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +140 -24
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +75 -50
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +148 -158
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -4
- package/dist/fs.js.map +1 -1
- package/dist/git-snapshot.d.ts +25 -17
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +65 -11
- package/dist/git-snapshot.js.map +1 -1
- package/dist/git-tracker.d.ts +11 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +29 -6
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +7 -35
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +5 -4
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/in-order.d.ts +93 -0
- package/dist/in-order.d.ts.map +1 -0
- package/dist/in-order.js +165 -0
- package/dist/in-order.js.map +1 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/kill-process-tree.d.ts +43 -0
- package/dist/kill-process-tree.d.ts.map +1 -0
- package/dist/kill-process-tree.js +48 -0
- package/dist/kill-process-tree.js.map +1 -0
- package/dist/link-following.d.ts +115 -0
- package/dist/link-following.d.ts.map +1 -0
- package/dist/link-following.js +283 -0
- package/dist/link-following.js.map +1 -0
- package/dist/listing-refusal.d.ts +44 -0
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +79 -2
- package/dist/listing-refusal.js.map +1 -1
- package/dist/name-fold.d.ts +25 -0
- package/dist/name-fold.d.ts.map +1 -0
- package/dist/name-fold.js +40 -0
- package/dist/name-fold.js.map +1 -0
- package/dist/path-containment.d.ts +17 -0
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +28 -5
- package/dist/path-containment.js.map +1 -1
- package/dist/path-utils.d.ts +14 -17
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +37 -55
- package/dist/path-utils.js.map +1 -1
- package/dist/process.d.ts +2 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +2 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +8 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +6 -2
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +1 -1
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts.map +1 -1
- package/dist/skill-targets.js +3 -1
- package/dist/skill-targets.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +0 -15
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +11 -50
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/special-file.d.ts +18 -0
- package/dist/special-file.d.ts.map +1 -0
- package/dist/special-file.js +21 -0
- package/dist/special-file.js.map +1 -0
- package/dist/terminating-signals.d.ts +129 -0
- package/dist/terminating-signals.d.ts.map +1 -0
- package/dist/terminating-signals.js +210 -0
- package/dist/terminating-signals.js.map +1 -0
- package/dist/test-helpers.d.ts +9 -5
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +18 -21
- package/dist/test-helpers.js.map +1 -1
- package/dist/test-scratch-guard.d.ts +34 -0
- package/dist/test-scratch-guard.d.ts.map +1 -0
- package/dist/test-scratch-guard.js +45 -0
- package/dist/test-scratch-guard.js.map +1 -0
- package/dist/testing/archive-fixtures.d.ts +147 -0
- package/dist/testing/archive-fixtures.d.ts.map +1 -0
- package/dist/testing/archive-fixtures.js +449 -0
- package/dist/testing/archive-fixtures.js.map +1 -0
- package/dist/testing/executables.d.ts +6 -0
- package/dist/testing/executables.d.ts.map +1 -1
- package/dist/testing/executables.js +20 -9
- package/dist/testing/executables.js.map +1 -1
- package/dist/testing/fault-fs-preload.d.ts +16 -0
- package/dist/testing/fault-fs-preload.d.ts.map +1 -0
- package/dist/testing/fault-fs-preload.js +22 -0
- package/dist/testing/fault-fs-preload.js.map +1 -0
- package/dist/testing/fault-fs.d.ts +115 -0
- package/dist/testing/fault-fs.d.ts.map +1 -0
- package/dist/testing/fault-fs.js +411 -0
- package/dist/testing/fault-fs.js.map +1 -0
- package/dist/testing/fault-spec.d.ts +83 -0
- package/dist/testing/fault-spec.d.ts.map +1 -0
- package/dist/testing/fault-spec.js +132 -0
- package/dist/testing/fault-spec.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -1
- package/dist/testing/hostile-tree.js +3 -4
- package/dist/testing/hostile-tree.js.map +1 -1
- package/dist/testing/platform-gates.d.ts +8 -0
- package/dist/testing/platform-gates.d.ts.map +1 -1
- package/dist/testing/platform-gates.js +24 -0
- package/dist/testing/platform-gates.js.map +1 -1
- package/dist/testing/temp-dir.d.ts +31 -0
- package/dist/testing/temp-dir.d.ts.map +1 -1
- package/dist/testing/temp-dir.js +63 -0
- package/dist/testing/temp-dir.js.map +1 -1
- package/dist/testing/tier-timeout.d.ts +34 -0
- package/dist/testing/tier-timeout.d.ts.map +1 -0
- package/dist/testing/tier-timeout.js +45 -0
- package/dist/testing/tier-timeout.js.map +1 -0
- package/dist/testing/tree-snapshot.d.ts +71 -0
- package/dist/testing/tree-snapshot.d.ts.map +1 -0
- package/dist/testing/tree-snapshot.js +163 -0
- package/dist/testing/tree-snapshot.js.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +8 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +29 -0
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +64 -0
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts +58 -6
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +111 -11
- package/dist/text-file.js.map +1 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +1 -1
- package/dist/text.js.map +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +2 -10
- package/dist/timing-dump.js.map +1 -1
- package/dist/tree-change/apply-decisions.d.ts +101 -0
- package/dist/tree-change/apply-decisions.d.ts.map +1 -0
- package/dist/tree-change/apply-decisions.js +139 -0
- package/dist/tree-change/apply-decisions.js.map +1 -0
- package/dist/tree-change/apply.d.ts +98 -0
- package/dist/tree-change/apply.d.ts.map +1 -0
- package/dist/tree-change/apply.js +364 -0
- package/dist/tree-change/apply.js.map +1 -0
- package/dist/tree-change/copy-decisions.d.ts +58 -0
- package/dist/tree-change/copy-decisions.d.ts.map +1 -0
- package/dist/tree-change/copy-decisions.js +66 -0
- package/dist/tree-change/copy-decisions.js.map +1 -0
- package/dist/tree-change/copy-tree.d.ts +125 -0
- package/dist/tree-change/copy-tree.d.ts.map +1 -0
- package/dist/tree-change/copy-tree.js +279 -0
- package/dist/tree-change/copy-tree.js.map +1 -0
- package/dist/tree-change/files.d.ts +167 -0
- package/dist/tree-change/files.d.ts.map +1 -0
- package/dist/tree-change/files.js +423 -0
- package/dist/tree-change/files.js.map +1 -0
- package/dist/tree-change/followed-walk.d.ts +54 -0
- package/dist/tree-change/followed-walk.d.ts.map +1 -0
- package/dist/tree-change/followed-walk.js +81 -0
- package/dist/tree-change/followed-walk.js.map +1 -0
- package/dist/tree-change/host-chain.d.ts +48 -0
- package/dist/tree-change/host-chain.d.ts.map +1 -0
- package/dist/tree-change/host-chain.js +85 -0
- package/dist/tree-change/host-chain.js.map +1 -0
- package/dist/tree-change/identity-compare.d.ts +61 -0
- package/dist/tree-change/identity-compare.d.ts.map +1 -0
- package/dist/tree-change/identity-compare.js +115 -0
- package/dist/tree-change/identity-compare.js.map +1 -0
- package/dist/tree-change/identity.d.ts +62 -0
- package/dist/tree-change/identity.d.ts.map +1 -0
- package/dist/tree-change/identity.js +152 -0
- package/dist/tree-change/identity.js.map +1 -0
- package/dist/tree-change/index.d.ts +24 -0
- package/dist/tree-change/index.d.ts.map +1 -0
- package/dist/tree-change/index.js +14 -0
- package/dist/tree-change/index.js.map +1 -0
- package/dist/tree-change/link-chain.d.ts +180 -0
- package/dist/tree-change/link-chain.d.ts.map +1 -0
- package/dist/tree-change/link-chain.js +377 -0
- package/dist/tree-change/link-chain.js.map +1 -0
- package/dist/tree-change/link-containment.d.ts +105 -0
- package/dist/tree-change/link-containment.d.ts.map +1 -0
- package/dist/tree-change/link-containment.js +196 -0
- package/dist/tree-change/link-containment.js.map +1 -0
- package/dist/tree-change/link-walk.d.ts +49 -0
- package/dist/tree-change/link-walk.d.ts.map +1 -0
- package/dist/tree-change/link-walk.js +69 -0
- package/dist/tree-change/link-walk.js.map +1 -0
- package/dist/tree-change/plan.d.ts +206 -0
- package/dist/tree-change/plan.d.ts.map +1 -0
- package/dist/tree-change/plan.js +320 -0
- package/dist/tree-change/plan.js.map +1 -0
- package/dist/tree-change/readable-tree.d.ts +44 -0
- package/dist/tree-change/readable-tree.d.ts.map +1 -0
- package/dist/tree-change/readable-tree.js +52 -0
- package/dist/tree-change/readable-tree.js.map +1 -0
- package/dist/tree-change/rollback-error.d.ts +29 -0
- package/dist/tree-change/rollback-error.d.ts.map +1 -0
- package/dist/tree-change/rollback-error.js +31 -0
- package/dist/tree-change/rollback-error.js.map +1 -0
- package/dist/tree-change/staging-names.d.ts +27 -0
- package/dist/tree-change/staging-names.d.ts.map +1 -0
- package/dist/tree-change/staging-names.js +40 -0
- package/dist/tree-change/staging-names.js.map +1 -0
- package/dist/tree-change/tree-walk.d.ts +81 -0
- package/dist/tree-change/tree-walk.d.ts.map +1 -0
- package/dist/tree-change/tree-walk.js +212 -0
- package/dist/tree-change/tree-walk.js.map +1 -0
- package/dist/win32-namespace.d.ts +24 -0
- package/dist/win32-namespace.d.ts.map +1 -0
- package/dist/win32-namespace.js +31 -0
- package/dist/win32-namespace.js.map +1 -0
- package/dist/yaml/surgical-yaml.d.ts +12 -3
- package/dist/yaml/surgical-yaml.d.ts.map +1 -1
- package/dist/yaml/surgical-yaml.js +16 -6
- package/dist/yaml/surgical-yaml.js.map +1 -1
- package/eslint/README.md +12 -4
- package/eslint/rules/fs-binding-scan.cjs +278 -0
- package/eslint/rules/no-adhoc-errno.cjs +364 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
- package/eslint/rules/no-blind-catch.cjs +1 -1
- package/eslint/rules/no-destructive-fs.cjs +359 -0
- package/eslint/rules/no-existssync.cjs +70 -0
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
- package/eslint/rules/no-link-following-fs.cjs +68 -0
- package/eslint/rules/no-literal-process-exit.cjs +96 -8
- package/eslint/rules/no-literal-test-timeout.cjs +214 -0
- package/eslint/rules/no-raw-signal-listener.cjs +224 -0
- package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
- package/package.json +10 -2
- package/dist/errors/errno.d.ts +0 -51
- package/dist/errors/errno.d.ts.map +0 -1
- package/dist/errors/errno.js +0 -112
- package/dist/errors/errno.js.map +0 -1
- package/eslint/rules/no-fs-promises-cp.cjs +0 -43
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rule for a tree whose links are KEPT: none of them may lead outside it.
|
|
3
|
+
*
|
|
4
|
+
* A copy that preserves links carries each link's text verbatim, so a link out of the tree
|
|
5
|
+
* arrives pointing at whatever stands at that path beside the COPY — the installing user's own
|
|
6
|
+
* files — and every reader of the copy that follows links reads them as the tree's content. So a
|
|
7
|
+
* tree that came from somewhere VAT does not own (a cloned repository, a directory an operator
|
|
8
|
+
* installs from) is checked here before anything reads it.
|
|
9
|
+
*/
|
|
10
|
+
import { type FsSide } from '../errors/fs-fault.js';
|
|
11
|
+
import { type LinkChain, type PathFlavor } from './link-chain.js';
|
|
12
|
+
/** What {@link refuseEscapingLinks} checks, and how its refusal names it. */
|
|
13
|
+
export interface LinkContainmentCheck {
|
|
14
|
+
/** The tree no link may lead out of. */
|
|
15
|
+
readonly root: string;
|
|
16
|
+
/** Where in it to look; the whole of `root` when omitted. */
|
|
17
|
+
readonly subtree?: string;
|
|
18
|
+
/** A directory under `root` that is never entered (a clone's `.git`). */
|
|
19
|
+
readonly skip?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the tree is read where it stands, or will be RELOCATED (an install copies it, each
|
|
22
|
+
* link's text verbatim).
|
|
23
|
+
*
|
|
24
|
+
* - `in-place`: a link is judged by where it leads NOW. Its chain is read first, link by link,
|
|
25
|
+
* by the one resolver ({@link treeChain}), so nothing is resolved until every text on the way
|
|
26
|
+
* is: a network, absolute or drive-relative text anywhere on the way, a climb above the root,
|
|
27
|
+
* or a chain no kernel finishes, is refused. Then its real path must be inside. One that leads
|
|
28
|
+
* to nothing (a dangle, a cycle) reads nothing, so it is not refused.
|
|
29
|
+
* - `relocated`: where a link leads now proves nothing about where its text leads once the tree
|
|
30
|
+
* is somewhere else, so the text itself is walked ({@link linkTextRefusal}): relative, never
|
|
31
|
+
* above the root, through no other link, and ending at something that exists. Then the chain
|
|
32
|
+
* from it is read as in place, and only then is the kernel asked ({@link kernelRefusal}).
|
|
33
|
+
*/
|
|
34
|
+
readonly placement: 'in-place' | 'relocated';
|
|
35
|
+
/** Which side the tree is on: every probe of it the OS refuses — a listing, an `lstat`, a `readlink`, a link's target — is that side's fault. */
|
|
36
|
+
readonly side: FsSide;
|
|
37
|
+
/** Whose kernel reads the tree: bound from `process.platform` at the edge (`HOST_PATH_FLAVOR`). */
|
|
38
|
+
readonly flavor: PathFlavor;
|
|
39
|
+
/** What the tree is, for the refusal: `the cloned repository`. */
|
|
40
|
+
readonly tree: string;
|
|
41
|
+
/** What a link must stay inside, for the refusal: `the clone`. */
|
|
42
|
+
readonly within: string;
|
|
43
|
+
/** What keeping the link would do, for the refusal — a sentence without its full stop. */
|
|
44
|
+
readonly consequence: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Where `candidate` stands relative to `root`, on real paths. The root itself counts as inside
|
|
48
|
+
* (`isUnderRoot` calls a root `outside` itself, which is right for a sink and wrong here).
|
|
49
|
+
*
|
|
50
|
+
* @param root - The tree
|
|
51
|
+
* @param candidate - A path in any spelling, existing or not
|
|
52
|
+
*/
|
|
53
|
+
export declare function treeContainment(root: string, candidate: string): 'inside' | 'outside' | 'absent';
|
|
54
|
+
/**
|
|
55
|
+
* Where `path` leads in an UNTRUSTED tree, read link by link by the one resolver — `lstat` and
|
|
56
|
+
* `readlink` alone — before anything resolves it: an absolute, drive-relative or network text on
|
|
57
|
+
* the way (under either flavor: the tree may be read on the other host), a climb above `root`,
|
|
58
|
+
* or a chain longer than the hop limit that is not a cycle, is refused; a cycle leads to
|
|
59
|
+
* nothing, as a dangling link does; nothing outside `root` is probed.
|
|
60
|
+
*
|
|
61
|
+
* @param root - The tree, spelled as `path` spells it (its own components are never probed)
|
|
62
|
+
* @param path - A path below it (a link, or a subpath the operator named)
|
|
63
|
+
* @param flavor - Whose kernel will resolve it: bound from the host at the edge
|
|
64
|
+
* @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
|
|
65
|
+
*/
|
|
66
|
+
export declare function treeChain(root: string, path: string, flavor: PathFlavor): LinkChain;
|
|
67
|
+
/**
|
|
68
|
+
* The last lock on a link of a tree that will be relocated: the KERNEL's resolution of it, at
|
|
69
|
+
* the source, must exist and be inside the tree. Asked only once the chain from the link has been
|
|
70
|
+
* read and shown local ({@link treeChain}). The walk of the text ({@link linkTextRefusal}) shows
|
|
71
|
+
* the text stays inside wherever the tree goes; this shows the walk's reading of the text is the
|
|
72
|
+
* kernel's. A probe that failed because the link resolves to nothing
|
|
73
|
+
* (`isUnresolvableLinkError`: it dangles, it is part of a cycle, Windows cannot follow its reparse
|
|
74
|
+
* data) is a refusal of the link on every host: nothing can be shown about where it leads. Every
|
|
75
|
+
* other failure — the OS declining (`EACCES`, `EPERM`), running short (`EMFILE`), held (`EBUSY`) or
|
|
76
|
+
* unable to look (`EIO`, `ETIMEDOUT`, Windows' ambiguous `UNKNOWN`) — says nothing about the link:
|
|
77
|
+
* a fault on the tree, thrown classified by the one write-lane catch body
|
|
78
|
+
* (`assertUnresolvableLink`, on the side the caller names in `check.side`, found as content).
|
|
79
|
+
*
|
|
80
|
+
* Exported apart from the walk so the lock can be shown to hold ALONE: with a correct walk, a
|
|
81
|
+
* link the kernel resolves outside never reaches it from a real tree.
|
|
82
|
+
*
|
|
83
|
+
* @param root - The tree, as its canonical path
|
|
84
|
+
* @param link - The link, spelled by its real directory
|
|
85
|
+
* @param text - The link's own text, for the refusal
|
|
86
|
+
* @returns Why the link is refused, after "is a symbolic link ", or `undefined` when the kernel agrees
|
|
87
|
+
* @throws FsFaultError on `check.side` for a probe that failed for any other reason; an error that carries no errno as it was
|
|
88
|
+
*/
|
|
89
|
+
export declare function kernelRefusal(check: LinkContainmentCheck, root: string, link: string, text: string): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Refuse a tree holding a symbolic link that may not be kept: one leading outside it, and — for a
|
|
92
|
+
* tree that will be relocated — one whose text cannot be shown to stay inside wherever the tree
|
|
93
|
+
* goes ({@link LinkContainmentCheck.placement}). Every directory is walked at its REAL path, once: a
|
|
94
|
+
* link that stays inside is followed to the directory it names, so a link into a sibling cannot
|
|
95
|
+
* smuggle out a second hop, a link cycle ends, and a directory first reached through an alias still
|
|
96
|
+
* has its own links judged where they live.
|
|
97
|
+
*
|
|
98
|
+
* @param check - The tree, where in it to look, and how the refusal names it
|
|
99
|
+
* @throws VatError `COPY_LINK_ESCAPES_SOURCE` — the tree's own link, so the input's refusal —
|
|
100
|
+
* naming the first link found leading out; FsFaultError on `check.side`, found as content, for
|
|
101
|
+
* every probe of the tree the OS refused (a listing, an `lstat`, a `readlink`, a relocated link's
|
|
102
|
+
* target)
|
|
103
|
+
*/
|
|
104
|
+
export declare function refuseEscapingLinks(check: LinkContainmentCheck): void;
|
|
105
|
+
//# sourceMappingURL=link-containment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-containment.d.ts","sourceRoot":"","sources":["../../src/tree-change/link-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAA0B,KAAK,MAAM,EAAmB,MAAM,uBAAuB,CAAC;AAQ7F,OAAO,EAAqB,KAAK,SAAS,EAAiB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGpG,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,GAAG,WAAW,CAAC;IAC7C,iJAAiJ;IACjJ,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAGhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAEnF;AAyCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAavH;AAqCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAgBrE"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rule for a tree whose links are KEPT: none of them may lead outside it.
|
|
3
|
+
*
|
|
4
|
+
* A copy that preserves links carries each link's text verbatim, so a link out of the tree
|
|
5
|
+
* arrives pointing at whatever stands at that path beside the COPY — the installing user's own
|
|
6
|
+
* files — and every reader of the copy that follows links reads them as the tree's content. So a
|
|
7
|
+
* tree that came from somewhere VAT does not own (a cloned repository, a directory an operator
|
|
8
|
+
* installs from) is checked here before anything reads it.
|
|
9
|
+
*/
|
|
10
|
+
import { readdirSync, readlinkSync } from 'node:fs';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
import { fsFaultOf } from '../errors/errno-table.js';
|
|
13
|
+
import { assertUnresolvableLink, withFsFaultSync } from '../errors/fs-fault.js';
|
|
14
|
+
import { VatError } from '../errors/vat-error.js';
|
|
15
|
+
import { requireFollowedStatSync } from '../link-following.js';
|
|
16
|
+
import { canonicalPath, isUnderRoot } from '../path-containment.js';
|
|
17
|
+
import { safePath, toForwardSlash } from '../path-core.js';
|
|
18
|
+
import { COPY_LINK_ESCAPES_SOURCE_CODE } from './followed-walk.js';
|
|
19
|
+
import { hostPlaceKind, walkHostChain } from './host-chain.js';
|
|
20
|
+
import { MAX_LINK_HOPS } from './link-chain.js';
|
|
21
|
+
import { linkTextRefusal } from './link-walk.js';
|
|
22
|
+
/**
|
|
23
|
+
* Where `candidate` stands relative to `root`, on real paths. The root itself counts as inside
|
|
24
|
+
* (`isUnderRoot` calls a root `outside` itself, which is right for a sink and wrong here).
|
|
25
|
+
*
|
|
26
|
+
* @param root - The tree
|
|
27
|
+
* @param candidate - A path in any spelling, existing or not
|
|
28
|
+
*/
|
|
29
|
+
export function treeContainment(root, candidate) {
|
|
30
|
+
if (canonicalPath(candidate, root) === canonicalPath(root, root))
|
|
31
|
+
return 'inside';
|
|
32
|
+
return isUnderRoot(root, candidate);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Where `path` leads in an UNTRUSTED tree, read link by link by the one resolver — `lstat` and
|
|
36
|
+
* `readlink` alone — before anything resolves it: an absolute, drive-relative or network text on
|
|
37
|
+
* the way (under either flavor: the tree may be read on the other host), a climb above `root`,
|
|
38
|
+
* or a chain longer than the hop limit that is not a cycle, is refused; a cycle leads to
|
|
39
|
+
* nothing, as a dangling link does; nothing outside `root` is probed.
|
|
40
|
+
*
|
|
41
|
+
* @param root - The tree, spelled as `path` spells it (its own components are never probed)
|
|
42
|
+
* @param path - A path below it (a link, or a subpath the operator named)
|
|
43
|
+
* @param flavor - Whose kernel will resolve it: bound from the host at the edge
|
|
44
|
+
* @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
|
|
45
|
+
*/
|
|
46
|
+
export function treeChain(root, path, flavor) {
|
|
47
|
+
return walkHostChain(path, { flavor, root: safePath.resolve(root), outside: 'refuse' });
|
|
48
|
+
}
|
|
49
|
+
/** The refusal of the link at `link`, named relative to the tree as the filesystem spells it. */
|
|
50
|
+
function linkRefusal(check, link, what) {
|
|
51
|
+
return new VatError(COPY_LINK_ESCAPES_SOURCE_CODE, `Refusing ${check.tree}: ${safePath.relative(canonicalPath(check.root, check.root), link)} is a symbolic link ${what}. ` +
|
|
52
|
+
`${check.consequence} — replace the link with the files, or point it inside with a relative target.`);
|
|
53
|
+
}
|
|
54
|
+
/** How a refused chain reads, after "is a symbolic link "; `text` is the link's own. */
|
|
55
|
+
function describedChainRefusal(check, text, refusal) {
|
|
56
|
+
switch (refusal.why) {
|
|
57
|
+
case 'network':
|
|
58
|
+
case 'absolute':
|
|
59
|
+
case 'drive-relative':
|
|
60
|
+
return `whose target leads through an absolute path (${refusal.text}), which names a place outside ${check.within}`;
|
|
61
|
+
case 'climbs-out':
|
|
62
|
+
return `to a path outside ${check.within} (${text})`;
|
|
63
|
+
case 'hop-limit':
|
|
64
|
+
return `whose target starts a chain of more than ${MAX_LINK_HOPS.toString()} symbolic links that is not a cycle, so where it ends cannot be shown`;
|
|
65
|
+
case 'unportable':
|
|
66
|
+
return `whose target names a component no Windows host can spell (${refusal.text}): a NUL, or a name ending in a dot or a space that Windows strips`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** How each refusal of a link's text reads, after "is a symbolic link ". */
|
|
70
|
+
function describedRefusal(check, text, refusal) {
|
|
71
|
+
switch (refusal.why) {
|
|
72
|
+
case 'absolute':
|
|
73
|
+
return `with an absolute target (${text}), which would still point there once ${check.within} is copied`;
|
|
74
|
+
case 'climbs-out':
|
|
75
|
+
return `to a path outside ${check.within} (${text})`;
|
|
76
|
+
case 'through-link':
|
|
77
|
+
return `whose target passes through another symbolic link (${refusal.at}), so where ${text} leads depends on where ${check.within} is copied`;
|
|
78
|
+
case 'dangling':
|
|
79
|
+
return `to nothing (${refusal.at} does not exist), so where it would lead once ${check.within} is copied cannot be shown`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The last lock on a link of a tree that will be relocated: the KERNEL's resolution of it, at
|
|
84
|
+
* the source, must exist and be inside the tree. Asked only once the chain from the link has been
|
|
85
|
+
* read and shown local ({@link treeChain}). The walk of the text ({@link linkTextRefusal}) shows
|
|
86
|
+
* the text stays inside wherever the tree goes; this shows the walk's reading of the text is the
|
|
87
|
+
* kernel's. A probe that failed because the link resolves to nothing
|
|
88
|
+
* (`isUnresolvableLinkError`: it dangles, it is part of a cycle, Windows cannot follow its reparse
|
|
89
|
+
* data) is a refusal of the link on every host: nothing can be shown about where it leads. Every
|
|
90
|
+
* other failure — the OS declining (`EACCES`, `EPERM`), running short (`EMFILE`), held (`EBUSY`) or
|
|
91
|
+
* unable to look (`EIO`, `ETIMEDOUT`, Windows' ambiguous `UNKNOWN`) — says nothing about the link:
|
|
92
|
+
* a fault on the tree, thrown classified by the one write-lane catch body
|
|
93
|
+
* (`assertUnresolvableLink`, on the side the caller names in `check.side`, found as content).
|
|
94
|
+
*
|
|
95
|
+
* Exported apart from the walk so the lock can be shown to hold ALONE: with a correct walk, a
|
|
96
|
+
* link the kernel resolves outside never reaches it from a real tree.
|
|
97
|
+
*
|
|
98
|
+
* @param root - The tree, as its canonical path
|
|
99
|
+
* @param link - The link, spelled by its real directory
|
|
100
|
+
* @param text - The link's own text, for the refusal
|
|
101
|
+
* @returns Why the link is refused, after "is a symbolic link ", or `undefined` when the kernel agrees
|
|
102
|
+
* @throws FsFaultError on `check.side` for a probe that failed for any other reason; an error that carries no errno as it was
|
|
103
|
+
*/
|
|
104
|
+
export function kernelRefusal(check, root, link, text) {
|
|
105
|
+
try {
|
|
106
|
+
// Followed to the end, as a reader of the link would: this fails where the kernel finds nothing.
|
|
107
|
+
requireFollowedStatSync(link, root);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const fault = fsFaultOf(error);
|
|
111
|
+
if (fault === undefined)
|
|
112
|
+
throw error;
|
|
113
|
+
// The action names the link: a refusal on its chain names the place refused, which may be its target.
|
|
114
|
+
assertUnresolvableLink(error, { side: check.side, origin: 'content', action: `resolve the symbolic link ${link}`, path: link });
|
|
115
|
+
return `to nothing (${text} does not resolve: ${fault.errno}), so where it would lead once ${check.within} is copied cannot be shown`;
|
|
116
|
+
}
|
|
117
|
+
// It resolves, so its canonical path is its real one.
|
|
118
|
+
return treeContainment(root, link) === 'inside' ? undefined : `to a path outside ${check.within} (${text} resolves outside it)`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Judge the link at `link` under `check.placement` — spelled by its REAL directory, so its text is
|
|
122
|
+
* read from where the link is, never from an alias of that directory, which reads a climbing text
|
|
123
|
+
* as staying inside.
|
|
124
|
+
*
|
|
125
|
+
* Either way the CHAIN is read first ({@link treeChain}) and the kernel last, so nothing the text
|
|
126
|
+
* names is resolved until every text on the way has been read. Relocated, the text is walked
|
|
127
|
+
* through no link before that ({@link linkTextRefusal}) and the kernel is asked after it
|
|
128
|
+
* ({@link kernelRefusal}); in place, the kernel's real path must be inside.
|
|
129
|
+
*/
|
|
130
|
+
function judgeLink(check, root, link) {
|
|
131
|
+
const text = readlinkSync(link);
|
|
132
|
+
if (check.placement === 'relocated') {
|
|
133
|
+
const below = safePath.relative(root, dirname(link));
|
|
134
|
+
const textRefusal = linkTextRefusal(below === '' ? [] : toForwardSlash(below).split('/'), text, (place) => hostPlaceKind(safePath.join(root, ...place)), check.flavor);
|
|
135
|
+
if (textRefusal !== undefined)
|
|
136
|
+
return { refusal: describedRefusal(check, text, textRefusal) };
|
|
137
|
+
}
|
|
138
|
+
const chain = treeChain(root, link, check.flavor);
|
|
139
|
+
if (chain.kind === 'refused')
|
|
140
|
+
return { refusal: describedChainRefusal(check, text, chain.refusal) };
|
|
141
|
+
if (check.placement === 'relocated') {
|
|
142
|
+
// Every text on the way was read and is local: the kernel may now be asked, as the last lock.
|
|
143
|
+
const refusal = kernelRefusal(check, root, link, text);
|
|
144
|
+
if (refusal !== undefined)
|
|
145
|
+
return { refusal };
|
|
146
|
+
}
|
|
147
|
+
else if (chain.kind === 'nothing') {
|
|
148
|
+
// A dangle or a cycle, every text read: in place it reads nothing.
|
|
149
|
+
return { enter: undefined };
|
|
150
|
+
}
|
|
151
|
+
else if (treeContainment(root, link) === 'outside') {
|
|
152
|
+
return { refusal: `to a path outside ${check.within}` };
|
|
153
|
+
}
|
|
154
|
+
return { enter: chain.kind === 'resolved' && chain.final === 'directory' ? canonicalPath(link, root) : undefined };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Refuse a tree holding a symbolic link that may not be kept: one leading outside it, and — for a
|
|
158
|
+
* tree that will be relocated — one whose text cannot be shown to stay inside wherever the tree
|
|
159
|
+
* goes ({@link LinkContainmentCheck.placement}). Every directory is walked at its REAL path, once: a
|
|
160
|
+
* link that stays inside is followed to the directory it names, so a link into a sibling cannot
|
|
161
|
+
* smuggle out a second hop, a link cycle ends, and a directory first reached through an alias still
|
|
162
|
+
* has its own links judged where they live.
|
|
163
|
+
*
|
|
164
|
+
* @param check - The tree, where in it to look, and how the refusal names it
|
|
165
|
+
* @throws VatError `COPY_LINK_ESCAPES_SOURCE` — the tree's own link, so the input's refusal —
|
|
166
|
+
* naming the first link found leading out; FsFaultError on `check.side`, found as content, for
|
|
167
|
+
* every probe of the tree the OS refused (a listing, an `lstat`, a `readlink`, a relocated link's
|
|
168
|
+
* target)
|
|
169
|
+
*/
|
|
170
|
+
export function refuseEscapingLinks(check) {
|
|
171
|
+
withFsFaultSync({ side: check.side, origin: 'content', action: `examine the links of ${check.tree}`, path: check.subtree ?? check.root }, () => {
|
|
172
|
+
const skipped = check.skip === undefined ? undefined : canonicalPath(check.skip, check.root);
|
|
173
|
+
const visited = new Set();
|
|
174
|
+
const root = canonicalPath(check.root, check.root);
|
|
175
|
+
const pending = [canonicalPath(check.subtree ?? check.root, check.root)];
|
|
176
|
+
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
177
|
+
if (dir === skipped || visited.has(dir))
|
|
178
|
+
continue;
|
|
179
|
+
visited.add(dir);
|
|
180
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
181
|
+
const entryPath = safePath.join(dir, entry.name);
|
|
182
|
+
const next = entry.isSymbolicLink() ? enterOrThrow(check, root, entryPath) : entry.isDirectory() && entryPath;
|
|
183
|
+
if (typeof next === 'string')
|
|
184
|
+
pending.push(next);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** The directory a kept link leads to, for the walk to enter; throws the link's refusal. */
|
|
190
|
+
function enterOrThrow(check, root, link) {
|
|
191
|
+
const verdict = judgeLink(check, root, link);
|
|
192
|
+
if ('refusal' in verdict)
|
|
193
|
+
throw linkRefusal(check, link, verdict.refusal);
|
|
194
|
+
return verdict.enter;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=link-containment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-containment.js","sourceRoot":"","sources":["../../src/tree-change/link-containment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a symbolic link's text leads, decided by WALKING it — the rule for a link in a tree that
|
|
3
|
+
* will be RELOCATED (an install copies the tree, each link's text verbatim). Pure: the caller says
|
|
4
|
+
* what stands at each place.
|
|
5
|
+
*
|
|
6
|
+
* A lexical resolve cancels `x/..` without asking what `x` is, so `l -> up/../../../secret` beside
|
|
7
|
+
* `up -> ../..` reads as inside the tree while the kernel follows `up` first and lands three
|
|
8
|
+
* levels above it. And where such a link dangles at the source, its real path proves nothing: it
|
|
9
|
+
* resolves once the tree is copied somewhere those levels exist. So nothing is assumed:
|
|
10
|
+
*
|
|
11
|
+
* - the text is relative — an absolute one points at the same place wherever the copy lands;
|
|
12
|
+
* - walked one segment at a time from the link's own directory, the place never rises above the
|
|
13
|
+
* tree's root — not even to come back in by the tree's own name, which the copy may not keep;
|
|
14
|
+
* - no component the walk passes THROUGH is a link: a link may point at something, never through
|
|
15
|
+
* another link, whose own text decides where the rest of the walk goes;
|
|
16
|
+
* - the place it ends at exists. A link that dangles is refused, because nothing can be proved
|
|
17
|
+
* about where it lands after the copy.
|
|
18
|
+
*
|
|
19
|
+
* The walk is a MODEL of the kernel's resolution, and a model can be wrong (it once split a text
|
|
20
|
+
* on a backslash the POSIX kernel reads as part of a name). So its caller holds two more locks
|
|
21
|
+
* beside it — the one link-chain resolver (`link-chain.ts`), which reads the chain from the link to
|
|
22
|
+
* its end before anything follows it, and then the kernel's own resolution of the link at the
|
|
23
|
+
* source — and a link is kept only when all three agree.
|
|
24
|
+
*/
|
|
25
|
+
import type { PathFlavor, PlaceKind } from './link-chain.js';
|
|
26
|
+
/** Why a link's text is refused, with the place (root-relative, `/`-joined) the walk stopped at. */
|
|
27
|
+
export type LinkTextRefusal = {
|
|
28
|
+
readonly why: 'absolute';
|
|
29
|
+
} | {
|
|
30
|
+
readonly why: 'climbs-out';
|
|
31
|
+
} | {
|
|
32
|
+
readonly why: 'through-link';
|
|
33
|
+
readonly at: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly why: 'dangling';
|
|
36
|
+
readonly at: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Walk a link's text from the directory the link is in.
|
|
40
|
+
*
|
|
41
|
+
* @param linkDir - The link's REAL directory, as segments below the tree's root (`[]`: the root itself)
|
|
42
|
+
* @param text - The link's text, verbatim
|
|
43
|
+
* @param kindAt - What stands at a place below the root, never following a link there
|
|
44
|
+
* @param flavor - Whose kernel resolves the link: its separators split the text
|
|
45
|
+
* @returns `undefined` when the text leads to something that exists inside the tree, by a walk
|
|
46
|
+
* that passes through no link; otherwise why it is refused
|
|
47
|
+
*/
|
|
48
|
+
export declare function linkTextRefusal(linkDir: readonly string[], text: string, kindAt: (place: readonly string[]) => PlaceKind, flavor: PathFlavor): LinkTextRefusal | undefined;
|
|
49
|
+
//# sourceMappingURL=link-walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-walk.d.ts","sourceRoot":"","sources":["../../src/tree-change/link-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE7D,oGAAoG;AACpG,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAgBtD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAiB1K"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a symbolic link's text leads, decided by WALKING it — the rule for a link in a tree that
|
|
3
|
+
* will be RELOCATED (an install copies the tree, each link's text verbatim). Pure: the caller says
|
|
4
|
+
* what stands at each place.
|
|
5
|
+
*
|
|
6
|
+
* A lexical resolve cancels `x/..` without asking what `x` is, so `l -> up/../../../secret` beside
|
|
7
|
+
* `up -> ../..` reads as inside the tree while the kernel follows `up` first and lands three
|
|
8
|
+
* levels above it. And where such a link dangles at the source, its real path proves nothing: it
|
|
9
|
+
* resolves once the tree is copied somewhere those levels exist. So nothing is assumed:
|
|
10
|
+
*
|
|
11
|
+
* - the text is relative — an absolute one points at the same place wherever the copy lands;
|
|
12
|
+
* - walked one segment at a time from the link's own directory, the place never rises above the
|
|
13
|
+
* tree's root — not even to come back in by the tree's own name, which the copy may not keep;
|
|
14
|
+
* - no component the walk passes THROUGH is a link: a link may point at something, never through
|
|
15
|
+
* another link, whose own text decides where the rest of the walk goes;
|
|
16
|
+
* - the place it ends at exists. A link that dangles is refused, because nothing can be proved
|
|
17
|
+
* about where it lands after the copy.
|
|
18
|
+
*
|
|
19
|
+
* The walk is a MODEL of the kernel's resolution, and a model can be wrong (it once split a text
|
|
20
|
+
* on a backslash the POSIX kernel reads as part of a name). So its caller holds two more locks
|
|
21
|
+
* beside it — the one link-chain resolver (`link-chain.ts`), which reads the chain from the link to
|
|
22
|
+
* its end before anything follows it, and then the kernel's own resolution of the link at the
|
|
23
|
+
* source — and a link is kept only when all three agree.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* What separates the components of a link's text is the answer of the kernel that resolves it,
|
|
27
|
+
* not VAT's. On POSIX only `/` does — a backslash is an ordinary character of a name, so `a\b` is
|
|
28
|
+
* one component, and reading it as two would count a `..` the kernel never takes.
|
|
29
|
+
*/
|
|
30
|
+
const SEPARATOR = { posix: /\//, win32: /[/\\]/ };
|
|
31
|
+
/** A POSIX root, a UNC root or a drive prefix: absolute, or drive-relative, on some host. */
|
|
32
|
+
const ABSOLUTE = /^([/\\]|[A-Za-z]:)/;
|
|
33
|
+
/** The components of a link's text, as `flavor` splits it: `.` and empty ones dropped. */
|
|
34
|
+
function segmentsOf(text, flavor) {
|
|
35
|
+
return text.split(SEPARATOR[flavor]).filter((segment) => segment !== '' && segment !== '.');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Walk a link's text from the directory the link is in.
|
|
39
|
+
*
|
|
40
|
+
* @param linkDir - The link's REAL directory, as segments below the tree's root (`[]`: the root itself)
|
|
41
|
+
* @param text - The link's text, verbatim
|
|
42
|
+
* @param kindAt - What stands at a place below the root, never following a link there
|
|
43
|
+
* @param flavor - Whose kernel resolves the link: its separators split the text
|
|
44
|
+
* @returns `undefined` when the text leads to something that exists inside the tree, by a walk
|
|
45
|
+
* that passes through no link; otherwise why it is refused
|
|
46
|
+
*/
|
|
47
|
+
export function linkTextRefusal(linkDir, text, kindAt, flavor) {
|
|
48
|
+
if (ABSOLUTE.test(text))
|
|
49
|
+
return { why: 'absolute' };
|
|
50
|
+
const segments = segmentsOf(text, flavor);
|
|
51
|
+
const place = [...linkDir];
|
|
52
|
+
for (const [index, segment] of segments.entries()) {
|
|
53
|
+
if (segment === '..') {
|
|
54
|
+
// Every place the walk stands at is a real directory of the tree, so its parent is `place` less one.
|
|
55
|
+
if (place.pop() === undefined)
|
|
56
|
+
return { why: 'climbs-out' };
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
place.push(segment);
|
|
60
|
+
const kind = kindAt(place);
|
|
61
|
+
const last = index === segments.length - 1;
|
|
62
|
+
if (kind === 'absent' || (!last && kind === 'file'))
|
|
63
|
+
return { why: 'dangling', at: place.join('/') };
|
|
64
|
+
if (!last && kind === 'link')
|
|
65
|
+
return { why: 'through-link', at: place.join('/') };
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=link-walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-walk.js","sourceRoot":"","sources":["../../src/tree-change/link-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAWH;;;;GAIG;AACH,MAAM,SAAS,GAAyC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACxF,6FAA6F;AAC7F,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AAEtC,0FAA0F;AAC1F,SAAS,UAAU,CAAC,IAAY,EAAE,MAAkB;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B,EAAE,IAAY,EAAE,MAA+C,EAAE,MAAkB;IAC3I,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC3B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAClD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,qGAAqG;YACrG,IAAI,KAAK,CAAC,GAAG,EAAE,KAAK,SAAS;gBAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC;YAC5D,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC;YAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|