@openclaw/fs-safe 0.1.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 +15 -0
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/SECURITY.md +12 -0
- package/dist/absolute-path.d.ts +19 -0
- package/dist/absolute-path.d.ts.map +1 -0
- package/dist/absolute-path.js +98 -0
- package/dist/advanced.d.ts +22 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +24 -0
- package/dist/archive-entry.d.ts +13 -0
- package/dist/archive-entry.d.ts.map +1 -0
- package/dist/archive-entry.js +47 -0
- package/dist/archive-kind.d.ts +8 -0
- package/dist/archive-kind.d.ts.map +1 -0
- package/dist/archive-kind.js +58 -0
- package/dist/archive-limits.d.ts +40 -0
- package/dist/archive-limits.d.ts.map +1 -0
- package/dist/archive-limits.js +92 -0
- package/dist/archive-staging.d.ts +26 -0
- package/dist/archive-staging.d.ts.map +1 -0
- package/dist/archive-staging.js +171 -0
- package/dist/archive-tar.d.ts +14 -0
- package/dist/archive-tar.d.ts.map +1 -0
- package/dist/archive-tar.js +52 -0
- package/dist/archive-utils.d.ts +3 -0
- package/dist/archive-utils.d.ts.map +1 -0
- package/dist/archive-utils.js +26 -0
- package/dist/archive-zip-preflight.d.ts +7 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -0
- package/dist/archive-zip-preflight.js +170 -0
- package/dist/archive.d.ts +24 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +251 -0
- package/dist/async-lock.d.ts +2 -0
- package/dist/async-lock.d.ts.map +1 -0
- package/dist/async-lock.js +17 -0
- package/dist/atomic.d.ts +5 -0
- package/dist/atomic.d.ts.map +1 -0
- package/dist/atomic.js +4 -0
- package/dist/boundary-file-read.d.ts +44 -0
- package/dist/boundary-file-read.d.ts.map +1 -0
- package/dist/boundary-file-read.js +129 -0
- package/dist/boundary-path.d.ts +39 -0
- package/dist/boundary-path.d.ts.map +1 -0
- package/dist/boundary-path.js +598 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/file-identity.d.ts +6 -0
- package/dist/file-identity.d.ts.map +1 -0
- package/dist/file-identity.js +14 -0
- package/dist/file-lock.d.ts +20 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +34 -0
- package/dist/file-store.d.ts +66 -0
- package/dist/file-store.d.ts.map +1 -0
- package/dist/file-store.js +436 -0
- package/dist/file-url.d.ts +2 -0
- package/dist/file-url.d.ts.map +1 -0
- package/dist/file-url.js +1 -0
- package/dist/filename.d.ts +2 -0
- package/dist/filename.d.ts.map +1 -0
- package/dist/filename.js +25 -0
- package/dist/fs-pinned-path-helper.d.ts +7 -0
- package/dist/fs-pinned-path-helper.d.ts.map +1 -0
- package/dist/fs-pinned-path-helper.js +182 -0
- package/dist/fs-pinned-write-helper.d.ts +21 -0
- package/dist/fs-pinned-write-helper.d.ts.map +1 -0
- package/dist/fs-pinned-write-helper.js +263 -0
- package/dist/fs.d.ts +13 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +30 -0
- package/dist/hardlink-guards.d.ts +7 -0
- package/dist/hardlink-guards.d.ts.map +1 -0
- package/dist/hardlink-guards.js +30 -0
- package/dist/home-dir.d.ts +22 -0
- package/dist/home-dir.d.ts.map +1 -0
- package/dist/home-dir.js +107 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/install-path.d.ts +20 -0
- package/dist/install-path.d.ts.map +1 -0
- package/dist/install-path.js +94 -0
- package/dist/install-safe-path.d.ts +20 -0
- package/dist/install-safe-path.d.ts.map +1 -0
- package/dist/install-safe-path.js +94 -0
- package/dist/json-document-store.d.ts +30 -0
- package/dist/json-document-store.d.ts.map +1 -0
- package/dist/json-document-store.js +68 -0
- package/dist/json-file.d.ts +3 -0
- package/dist/json-file.d.ts.map +1 -0
- package/dist/json-file.js +123 -0
- package/dist/json-files.d.ts +20 -0
- package/dist/json-files.d.ts.map +1 -0
- package/dist/json-files.js +153 -0
- package/dist/json-store.d.ts +9 -0
- package/dist/json-store.d.ts.map +1 -0
- package/dist/json-store.js +12 -0
- package/dist/json.d.ts +17 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +198 -0
- package/dist/local-file-access.d.ts +8 -0
- package/dist/local-file-access.d.ts.map +1 -0
- package/dist/local-file-access.js +73 -0
- package/dist/local-roots.d.ts +26 -0
- package/dist/local-roots.d.ts.map +1 -0
- package/dist/local-roots.js +150 -0
- package/dist/mode.d.ts +2 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +3 -0
- package/dist/move-path.d.ts +6 -0
- package/dist/move-path.d.ts.map +1 -0
- package/dist/move-path.js +18 -0
- package/dist/path-alias-guards.d.ts +19 -0
- package/dist/path-alias-guards.d.ts.map +1 -0
- package/dist/path-alias-guards.js +21 -0
- package/dist/path-guards.d.ts +7 -0
- package/dist/path-guards.d.ts.map +1 -0
- package/dist/path-guards.js +49 -0
- package/dist/path-policy.d.ts +25 -0
- package/dist/path-policy.d.ts.map +1 -0
- package/dist/path-policy.js +50 -0
- package/dist/path-safety.d.ts +12 -0
- package/dist/path-safety.d.ts.map +1 -0
- package/dist/path-safety.js +50 -0
- package/dist/path-scope.d.ts +2 -0
- package/dist/path-scope.d.ts.map +1 -0
- package/dist/path-scope.js +1 -0
- package/dist/path.d.ts +18 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +123 -0
- package/dist/permissions-public.d.ts +2 -0
- package/dist/permissions-public.d.ts.map +1 -0
- package/dist/permissions-public.js +1 -0
- package/dist/permissions.d.ts +86 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +435 -0
- package/dist/pinned-helper.d.ts +9 -0
- package/dist/pinned-helper.d.ts.map +1 -0
- package/dist/pinned-helper.js +19 -0
- package/dist/pinned-open.d.ts +24 -0
- package/dist/pinned-open.d.ts.map +1 -0
- package/dist/pinned-open.js +71 -0
- package/dist/pinned-path.d.ts +7 -0
- package/dist/pinned-path.d.ts.map +1 -0
- package/dist/pinned-path.js +21 -0
- package/dist/pinned-python-config.d.ts +9 -0
- package/dist/pinned-python-config.d.ts.map +1 -0
- package/dist/pinned-python-config.js +39 -0
- package/dist/pinned-python.d.ts +12 -0
- package/dist/pinned-python.d.ts.map +1 -0
- package/dist/pinned-python.js +599 -0
- package/dist/pinned-write.d.ts +33 -0
- package/dist/pinned-write.d.ts.map +1 -0
- package/dist/pinned-write.js +198 -0
- package/dist/private-file-store.d.ts +60 -0
- package/dist/private-file-store.d.ts.map +1 -0
- package/dist/private-file-store.js +233 -0
- package/dist/private-temp-workspace.d.ts +39 -0
- package/dist/private-temp-workspace.d.ts.map +1 -0
- package/dist/private-temp-workspace.js +185 -0
- package/dist/regular-file.d.ts +38 -0
- package/dist/regular-file.d.ts.map +1 -0
- package/dist/regular-file.js +239 -0
- package/dist/replace-directory.d.ts +7 -0
- package/dist/replace-directory.d.ts.map +1 -0
- package/dist/replace-directory.js +32 -0
- package/dist/replace-file.d.ts +41 -0
- package/dist/replace-file.d.ts.map +1 -0
- package/dist/replace-file.js +325 -0
- package/dist/root-file.d.ts +44 -0
- package/dist/root-file.d.ts.map +1 -0
- package/dist/root-file.js +129 -0
- package/dist/root-path.d.ts +39 -0
- package/dist/root-path.d.ts.map +1 -0
- package/dist/root-path.js +598 -0
- package/dist/root-paths.d.ts +92 -0
- package/dist/root-paths.d.ts.map +1 -0
- package/dist/root-paths.js +338 -0
- package/dist/root.d.ts +108 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +1332 -0
- package/dist/safe-open-sync.d.ts +24 -0
- package/dist/safe-open-sync.d.ts.map +1 -0
- package/dist/safe-open-sync.js +71 -0
- package/dist/safe-root.d.ts +123 -0
- package/dist/safe-root.d.ts.map +1 -0
- package/dist/safe-root.js +1060 -0
- package/dist/secret-file.d.ts +17 -0
- package/dist/secret-file.d.ts.map +1 -0
- package/dist/secret-file.js +232 -0
- package/dist/secret.d.ts +2 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +1 -0
- package/dist/secure-file.d.ts +32 -0
- package/dist/secure-file.d.ts.map +1 -0
- package/dist/secure-file.js +163 -0
- package/dist/secure-temp-dir.d.ts +27 -0
- package/dist/secure-temp-dir.d.ts.map +1 -0
- package/dist/secure-temp-dir.js +155 -0
- package/dist/secure-temp-workspace.d.ts +25 -0
- package/dist/secure-temp-workspace.d.ts.map +1 -0
- package/dist/secure-temp-workspace.js +136 -0
- package/dist/sibling-temp-file.d.ts +16 -0
- package/dist/sibling-temp-file.d.ts.map +1 -0
- package/dist/sibling-temp-file.js +73 -0
- package/dist/sibling-temp-write.d.ts +8 -0
- package/dist/sibling-temp-write.d.ts.map +1 -0
- package/dist/sibling-temp-write.js +40 -0
- package/dist/sibling-temp.d.ts +23 -0
- package/dist/sibling-temp.d.ts.map +1 -0
- package/dist/sibling-temp.js +119 -0
- package/dist/sidecar-lock.d.ts +50 -0
- package/dist/sidecar-lock.d.ts.map +1 -0
- package/dist/sidecar-lock.js +235 -0
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +2 -0
- package/dist/string-coerce.d.ts +14 -0
- package/dist/string-coerce.d.ts.map +1 -0
- package/dist/string-coerce.js +72 -0
- package/dist/symlink-parents.d.ts +12 -0
- package/dist/symlink-parents.d.ts.map +1 -0
- package/dist/symlink-parents.js +78 -0
- package/dist/temp-cleanup.d.ts +6 -0
- package/dist/temp-cleanup.d.ts.map +1 -0
- package/dist/temp-cleanup.js +42 -0
- package/dist/temp-target.d.ts +28 -0
- package/dist/temp-target.d.ts.map +1 -0
- package/dist/temp-target.js +84 -0
- package/dist/temp.d.ts +3 -0
- package/dist/temp.d.ts.map +1 -0
- package/dist/temp.js +2 -0
- package/dist/test-hooks.d.ts +9 -0
- package/dist/test-hooks.d.ts.map +1 -0
- package/dist/test-hooks.js +13 -0
- package/dist/text-atomic.d.ts +7 -0
- package/dist/text-atomic.d.ts.map +1 -0
- package/dist/text-atomic.js +13 -0
- package/dist/timing.d.ts +6 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +24 -0
- package/dist/trash.d.ts +5 -0
- package/dist/trash.d.ts.map +1 -0
- package/dist/trash.js +127 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/walk.d.ts +26 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +169 -0
- package/dist/write-queue.d.ts +2 -0
- package/dist/write-queue.d.ts.map +1 -0
- package/dist/write-queue.js +18 -0
- package/package.json +117 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
5
|
+
import { root as openRoot } from "./root.js";
|
|
6
|
+
function invalidPath(scopeLabel) {
|
|
7
|
+
return {
|
|
8
|
+
ok: false,
|
|
9
|
+
error: `Invalid path: must stay within ${scopeLabel}`,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
async function resolveRealPathIfExists(targetPath) {
|
|
13
|
+
try {
|
|
14
|
+
return await fs.realpath(targetPath);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function resolveTrustedRootRealPath(rootDir) {
|
|
21
|
+
try {
|
|
22
|
+
const rootLstat = await fs.lstat(rootDir);
|
|
23
|
+
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return await fs.realpath(rootDir);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function validateCanonicalPathWithinRoot(params) {
|
|
33
|
+
try {
|
|
34
|
+
const candidateLstat = await fs.lstat(params.candidatePath);
|
|
35
|
+
if (candidateLstat.isSymbolicLink()) {
|
|
36
|
+
return "invalid";
|
|
37
|
+
}
|
|
38
|
+
if (params.expect === "directory" && !candidateLstat.isDirectory()) {
|
|
39
|
+
return "invalid";
|
|
40
|
+
}
|
|
41
|
+
if (params.expect === "file" && !candidateLstat.isFile()) {
|
|
42
|
+
return "invalid";
|
|
43
|
+
}
|
|
44
|
+
if (params.expect === "file" && candidateLstat.nlink > 1) {
|
|
45
|
+
return "invalid";
|
|
46
|
+
}
|
|
47
|
+
const candidateRealPath = await fs.realpath(params.candidatePath);
|
|
48
|
+
return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return isNotFoundPathError(err) ? "not-found" : "invalid";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function resolvePathWithinRoot(params) {
|
|
55
|
+
const root = path.resolve(params.rootDir);
|
|
56
|
+
const raw = params.requestedPath.trim();
|
|
57
|
+
if (!raw) {
|
|
58
|
+
if (!params.defaultFileName) {
|
|
59
|
+
return { ok: false, error: "path is required" };
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, path: path.join(root, params.defaultFileName) };
|
|
62
|
+
}
|
|
63
|
+
const resolved = path.resolve(root, raw);
|
|
64
|
+
const rel = path.relative(root, resolved);
|
|
65
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
66
|
+
return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, path: resolved };
|
|
69
|
+
}
|
|
70
|
+
export async function resolveWritablePathWithinRoot(params) {
|
|
71
|
+
const lexical = resolvePathWithinRoot(params);
|
|
72
|
+
if (!lexical.ok) {
|
|
73
|
+
return lexical;
|
|
74
|
+
}
|
|
75
|
+
const rootDir = path.resolve(params.rootDir);
|
|
76
|
+
const rootRealPath = await resolveTrustedRootRealPath(rootDir);
|
|
77
|
+
if (!rootRealPath) {
|
|
78
|
+
return invalidPath(params.scopeLabel);
|
|
79
|
+
}
|
|
80
|
+
const requestedPath = lexical.path;
|
|
81
|
+
const parentDir = path.dirname(requestedPath);
|
|
82
|
+
const parentStatus = await validateCanonicalPathWithinRoot({
|
|
83
|
+
rootRealPath,
|
|
84
|
+
candidatePath: parentDir,
|
|
85
|
+
expect: "directory",
|
|
86
|
+
});
|
|
87
|
+
if (parentStatus !== "ok") {
|
|
88
|
+
return invalidPath(params.scopeLabel);
|
|
89
|
+
}
|
|
90
|
+
const targetStatus = await validateCanonicalPathWithinRoot({
|
|
91
|
+
rootRealPath,
|
|
92
|
+
candidatePath: requestedPath,
|
|
93
|
+
expect: "file",
|
|
94
|
+
});
|
|
95
|
+
if (targetStatus === "invalid") {
|
|
96
|
+
return invalidPath(params.scopeLabel);
|
|
97
|
+
}
|
|
98
|
+
return lexical;
|
|
99
|
+
}
|
|
100
|
+
async function resolveNearestExistingPath(targetPath) {
|
|
101
|
+
let current = path.resolve(targetPath);
|
|
102
|
+
while (true) {
|
|
103
|
+
try {
|
|
104
|
+
await fs.lstat(current);
|
|
105
|
+
return current;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (!isNotFoundPathError(err)) {
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const parent = path.dirname(current);
|
|
113
|
+
if (parent === current) {
|
|
114
|
+
throw new Error(`failed to resolve existing path for ${targetPath}`);
|
|
115
|
+
}
|
|
116
|
+
current = parent;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function assertNoSymlinkSegments(params) {
|
|
120
|
+
const relative = path.relative(params.rootDir, params.targetPath);
|
|
121
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
122
|
+
throw new Error(`Invalid path: must stay within ${params.scopeLabel}`);
|
|
123
|
+
}
|
|
124
|
+
let current = params.rootDir;
|
|
125
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
126
|
+
current = path.join(current, segment);
|
|
127
|
+
try {
|
|
128
|
+
const stat = await fs.lstat(current);
|
|
129
|
+
if (stat.isSymbolicLink()) {
|
|
130
|
+
throw new Error(`Invalid path: must not traverse symlinks within ${params.scopeLabel}`);
|
|
131
|
+
}
|
|
132
|
+
if (!stat.isDirectory()) {
|
|
133
|
+
throw new Error(`Invalid path: existing segment must be a directory within ${params.scopeLabel}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (isNotFoundPathError(err)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export async function ensureDirectoryWithinRoot(params) {
|
|
145
|
+
const lexical = resolvePathWithinRoot({
|
|
146
|
+
rootDir: params.rootDir,
|
|
147
|
+
requestedPath: params.requestedPath,
|
|
148
|
+
scopeLabel: params.scopeLabel,
|
|
149
|
+
defaultFileName: params.defaultDirName,
|
|
150
|
+
});
|
|
151
|
+
if (!lexical.ok) {
|
|
152
|
+
return lexical;
|
|
153
|
+
}
|
|
154
|
+
const rootDir = path.resolve(params.rootDir);
|
|
155
|
+
const targetPath = path.resolve(lexical.path);
|
|
156
|
+
try {
|
|
157
|
+
const rootStat = await fs.lstat(rootDir);
|
|
158
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
159
|
+
return invalidPath(params.scopeLabel);
|
|
160
|
+
}
|
|
161
|
+
await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel: params.scopeLabel });
|
|
162
|
+
const rootReal = await fs.realpath(rootDir);
|
|
163
|
+
const nearestExistingPath = await resolveNearestExistingPath(targetPath);
|
|
164
|
+
const nearestExistingReal = await fs.realpath(nearestExistingPath);
|
|
165
|
+
if (!isPathInside(rootReal, nearestExistingReal)) {
|
|
166
|
+
return invalidPath(params.scopeLabel);
|
|
167
|
+
}
|
|
168
|
+
const relative = path.relative(rootDir, targetPath);
|
|
169
|
+
let current = rootDir;
|
|
170
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
171
|
+
current = path.join(current, segment);
|
|
172
|
+
while (true) {
|
|
173
|
+
try {
|
|
174
|
+
const stat = await fs.lstat(current);
|
|
175
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
176
|
+
return invalidPath(params.scopeLabel);
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
if (!isNotFoundPathError(err)) {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
await fs.mkdir(current, { mode: params.mode });
|
|
186
|
+
}
|
|
187
|
+
catch (mkdirErr) {
|
|
188
|
+
if (isNotFoundPathError(mkdirErr)) {
|
|
189
|
+
throw mkdirErr;
|
|
190
|
+
}
|
|
191
|
+
if (mkdirErr.code === "EEXIST") {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
throw mkdirErr;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const targetReal = await fs.realpath(targetPath);
|
|
200
|
+
if (!isPathInside(rootReal, targetReal)) {
|
|
201
|
+
return invalidPath(params.scopeLabel);
|
|
202
|
+
}
|
|
203
|
+
return { ok: true, path: targetPath };
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return invalidPath(params.scopeLabel);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function resolvePathsWithinRoot(params) {
|
|
210
|
+
const resolvedPaths = [];
|
|
211
|
+
for (const raw of params.requestedPaths) {
|
|
212
|
+
const pathResult = resolvePathWithinRoot({
|
|
213
|
+
rootDir: params.rootDir,
|
|
214
|
+
requestedPath: raw,
|
|
215
|
+
scopeLabel: params.scopeLabel,
|
|
216
|
+
});
|
|
217
|
+
if (!pathResult.ok) {
|
|
218
|
+
return { ok: false, error: pathResult.error };
|
|
219
|
+
}
|
|
220
|
+
resolvedPaths.push(pathResult.path);
|
|
221
|
+
}
|
|
222
|
+
return { ok: true, paths: resolvedPaths };
|
|
223
|
+
}
|
|
224
|
+
export async function resolveExistingPathsWithinRoot(params) {
|
|
225
|
+
return await resolveCheckedPathsWithinRoot(params, true);
|
|
226
|
+
}
|
|
227
|
+
export async function resolveStrictExistingPathsWithinRoot(params) {
|
|
228
|
+
return await resolveCheckedPathsWithinRoot(params, false);
|
|
229
|
+
}
|
|
230
|
+
export function pathScope(rootDir, options) {
|
|
231
|
+
const base = { rootDir, scopeLabel: options.label };
|
|
232
|
+
return {
|
|
233
|
+
rootDir,
|
|
234
|
+
label: options.label,
|
|
235
|
+
resolve: (requestedPath, pathOptions) => resolvePathWithinRoot({
|
|
236
|
+
...base,
|
|
237
|
+
requestedPath,
|
|
238
|
+
defaultFileName: pathOptions?.defaultName,
|
|
239
|
+
}),
|
|
240
|
+
resolveAll: (requestedPaths) => resolvePathsWithinRoot({
|
|
241
|
+
...base,
|
|
242
|
+
requestedPaths,
|
|
243
|
+
}),
|
|
244
|
+
existing: (requestedPaths) => resolveExistingPathsWithinRoot({
|
|
245
|
+
...base,
|
|
246
|
+
requestedPaths,
|
|
247
|
+
}),
|
|
248
|
+
files: (requestedPaths) => resolveStrictExistingPathsWithinRoot({
|
|
249
|
+
...base,
|
|
250
|
+
requestedPaths,
|
|
251
|
+
}),
|
|
252
|
+
writable: (requestedPath, pathOptions) => resolveWritablePathWithinRoot({
|
|
253
|
+
...base,
|
|
254
|
+
requestedPath,
|
|
255
|
+
defaultFileName: pathOptions?.defaultName,
|
|
256
|
+
}),
|
|
257
|
+
ensureDir: (requestedPath, pathOptions) => ensureDirectoryWithinRoot({
|
|
258
|
+
...base,
|
|
259
|
+
requestedPath,
|
|
260
|
+
defaultDirName: pathOptions?.defaultName,
|
|
261
|
+
mode: pathOptions?.mode,
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
266
|
+
const rootDir = path.resolve(params.rootDir);
|
|
267
|
+
const rootRealPath = await resolveRealPathIfExists(rootDir);
|
|
268
|
+
const root = rootRealPath ? await openRoot(rootDir) : undefined;
|
|
269
|
+
const isInRoot = (relativePath) => Boolean(relativePath) && !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
|
|
270
|
+
const resolveExistingRelativePath = async (requestedPath) => {
|
|
271
|
+
const raw = requestedPath.trim();
|
|
272
|
+
const lexicalPathResult = resolvePathWithinRoot({
|
|
273
|
+
rootDir,
|
|
274
|
+
requestedPath,
|
|
275
|
+
scopeLabel: params.scopeLabel,
|
|
276
|
+
});
|
|
277
|
+
if (lexicalPathResult.ok) {
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
relativePath: path.relative(rootDir, lexicalPathResult.path),
|
|
281
|
+
fallbackPath: lexicalPathResult.path,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (!rootRealPath || !raw || !path.isAbsolute(raw)) {
|
|
285
|
+
return lexicalPathResult;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const resolvedExistingPath = await fs.realpath(raw);
|
|
289
|
+
const relativePath = path.relative(rootRealPath, resolvedExistingPath);
|
|
290
|
+
if (!isInRoot(relativePath)) {
|
|
291
|
+
return lexicalPathResult;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
ok: true,
|
|
295
|
+
relativePath,
|
|
296
|
+
fallbackPath: resolvedExistingPath,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return lexicalPathResult;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const resolvedPaths = [];
|
|
304
|
+
for (const raw of params.requestedPaths) {
|
|
305
|
+
const pathResult = await resolveExistingRelativePath(raw);
|
|
306
|
+
if (!pathResult.ok) {
|
|
307
|
+
return { ok: false, error: pathResult.error };
|
|
308
|
+
}
|
|
309
|
+
let opened;
|
|
310
|
+
try {
|
|
311
|
+
if (!root) {
|
|
312
|
+
throw new FsSafeError("not-found", "root dir not found");
|
|
313
|
+
}
|
|
314
|
+
opened = await root.open(pathResult.relativePath);
|
|
315
|
+
resolvedPaths.push(opened.realPath);
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
if (allowMissingFallback && err instanceof FsSafeError && err.code === "not-found") {
|
|
319
|
+
resolvedPaths.push(pathResult.fallbackPath);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (err instanceof FsSafeError && err.code === "outside-workspace") {
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
error: `File is outside ${params.scopeLabel}`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: `Invalid path: must stay within ${params.scopeLabel} and be a regular non-symlink file`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
await opened?.handle.close().catch(() => { });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { ok: true, paths: resolvedPaths };
|
|
338
|
+
}
|
package/dist/root.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import type { DirEntry, PathStat } from "./types.js";
|
|
4
|
+
export type OpenResult = {
|
|
5
|
+
handle: FileHandle;
|
|
6
|
+
realPath: string;
|
|
7
|
+
stat: Stats;
|
|
8
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export type ReadResult = {
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
realPath: string;
|
|
13
|
+
stat: Stats;
|
|
14
|
+
};
|
|
15
|
+
export type RootOptions = {
|
|
16
|
+
rootDir: string;
|
|
17
|
+
defaults?: RootDefaults;
|
|
18
|
+
};
|
|
19
|
+
export type SymlinkPolicy = "reject" | "follow-within-root";
|
|
20
|
+
export type HardlinkPolicy = "reject" | "allow";
|
|
21
|
+
export type WritableOpenMode = "replace" | "append" | "update";
|
|
22
|
+
export type RootDefaults = {
|
|
23
|
+
hardlinks?: HardlinkPolicy;
|
|
24
|
+
maxBytes?: number;
|
|
25
|
+
mkdir?: boolean;
|
|
26
|
+
mode?: number;
|
|
27
|
+
nonBlockingRead?: boolean;
|
|
28
|
+
symlinks?: SymlinkPolicy;
|
|
29
|
+
};
|
|
30
|
+
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
31
|
+
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
32
|
+
export type RootWriteOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
33
|
+
encoding?: BufferEncoding;
|
|
34
|
+
overwrite?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type RootOpenWritableOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
37
|
+
writeMode?: WritableOpenMode;
|
|
38
|
+
};
|
|
39
|
+
export type RootCopyOptions = Pick<RootDefaults, "maxBytes" | "mkdir" | "mode"> & {
|
|
40
|
+
sourceHardlinks?: HardlinkPolicy;
|
|
41
|
+
};
|
|
42
|
+
export type RootWriteJsonOptions = RootWriteOptions & {
|
|
43
|
+
replacer?: Parameters<typeof JSON.stringify>[1];
|
|
44
|
+
space?: Parameters<typeof JSON.stringify>[2];
|
|
45
|
+
trailingNewline?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type RootCreateOptions = Omit<RootWriteOptions, "overwrite">;
|
|
48
|
+
export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite">;
|
|
49
|
+
export type RootAppendOptions = RootWriteOptions & {
|
|
50
|
+
prependNewlineIfNeeded?: boolean;
|
|
51
|
+
};
|
|
52
|
+
export declare const DEFAULT_ROOT_MAX_BYTES: number;
|
|
53
|
+
export interface Root {
|
|
54
|
+
readonly rootDir: string;
|
|
55
|
+
readonly rootReal: string;
|
|
56
|
+
readonly rootWithSep: string;
|
|
57
|
+
readonly defaults: RootDefaults;
|
|
58
|
+
resolve(relativePath: string): Promise<string>;
|
|
59
|
+
open(relativePath: string, options?: RootOpenOptions): Promise<OpenResult>;
|
|
60
|
+
read(relativePath: string, options?: RootReadOptions): Promise<ReadResult>;
|
|
61
|
+
readBytes(relativePath: string, options?: RootReadOptions): Promise<Buffer>;
|
|
62
|
+
readText(relativePath: string, options?: RootReadOptions & {
|
|
63
|
+
encoding?: BufferEncoding;
|
|
64
|
+
}): Promise<string>;
|
|
65
|
+
readJson<T = unknown>(relativePath: string, options?: RootReadOptions & {
|
|
66
|
+
encoding?: BufferEncoding;
|
|
67
|
+
}): Promise<T>;
|
|
68
|
+
readAbsolute(filePath: string, options?: RootReadOptions): Promise<ReadResult>;
|
|
69
|
+
reader(options?: RootReadOptions): (filePath: string) => Promise<Buffer>;
|
|
70
|
+
openWritable(relativePath: string, options?: RootOpenWritableOptions): Promise<WritableOpenResult>;
|
|
71
|
+
append(relativePath: string, data: string | Buffer, options?: RootAppendOptions): Promise<void>;
|
|
72
|
+
remove(relativePath: string): Promise<void>;
|
|
73
|
+
mkdir(relativePath: string): Promise<void>;
|
|
74
|
+
ensureRoot(): Promise<void>;
|
|
75
|
+
write(relativePath: string, data: string | Buffer, options?: RootWriteOptions): Promise<void>;
|
|
76
|
+
create(relativePath: string, data: string | Buffer, options?: RootCreateOptions): Promise<void>;
|
|
77
|
+
writeJson(relativePath: string, data: unknown, options?: RootWriteJsonOptions): Promise<void>;
|
|
78
|
+
createJson(relativePath: string, data: unknown, options?: RootCreateJsonOptions): Promise<void>;
|
|
79
|
+
copyIn(relativePath: string, sourcePath: string, options?: RootCopyOptions): Promise<void>;
|
|
80
|
+
exists(relativePath: string): Promise<boolean>;
|
|
81
|
+
stat(relativePath: string): Promise<PathStat>;
|
|
82
|
+
list(relativePath: string, options?: {
|
|
83
|
+
withFileTypes?: false;
|
|
84
|
+
}): Promise<string[]>;
|
|
85
|
+
list(relativePath: string, options: {
|
|
86
|
+
withFileTypes: true;
|
|
87
|
+
}): Promise<DirEntry[]>;
|
|
88
|
+
move(fromRelative: string, toRelative: string, options?: {
|
|
89
|
+
overwrite?: boolean;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export declare function root(rootDir: string, defaults?: RootDefaults): Promise<Root>;
|
|
93
|
+
export declare function readLocalFileSafely(params: {
|
|
94
|
+
filePath: string;
|
|
95
|
+
maxBytes?: number;
|
|
96
|
+
}): Promise<ReadResult>;
|
|
97
|
+
export declare function openLocalFileSafely(params: {
|
|
98
|
+
filePath: string;
|
|
99
|
+
}): Promise<OpenResult>;
|
|
100
|
+
export type WritableOpenResult = {
|
|
101
|
+
handle: FileHandle;
|
|
102
|
+
createdForWrite: boolean;
|
|
103
|
+
realPath: string;
|
|
104
|
+
stat: Stats;
|
|
105
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
106
|
+
};
|
|
107
|
+
export declare function resolveOpenedFileRealPathForHandle(handle: FileHandle, ioPath: string): Promise<string>;
|
|
108
|
+
//# sourceMappingURL=root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"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"}
|