@openclaw/fs-safe 0.5.1 → 0.5.2

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +18 -6
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +20 -12
  5. package/dist/archive-entry.d.ts +1 -0
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +25 -5
  8. package/dist/archive-input.d.ts +7 -0
  9. package/dist/archive-input.d.ts.map +1 -1
  10. package/dist/archive-input.js +11 -1
  11. package/dist/archive-limits.js +1 -1
  12. package/dist/archive-native.d.ts.map +1 -1
  13. package/dist/archive-native.js +6 -3
  14. package/dist/archive-policy.d.ts.map +1 -1
  15. package/dist/archive-policy.js +2 -1
  16. package/dist/archive-read.d.ts.map +1 -1
  17. package/dist/archive-read.js +30 -12
  18. package/dist/archive-staging.d.ts.map +1 -1
  19. package/dist/archive-staging.js +3 -2
  20. package/dist/archive-tar-meta.d.ts.map +1 -1
  21. package/dist/archive-tar-meta.js +7 -1
  22. package/dist/archive-tar.d.ts.map +1 -1
  23. package/dist/archive-tar.js +7 -4
  24. package/dist/archive-zip-entry.d.ts +1 -0
  25. package/dist/archive-zip-entry.d.ts.map +1 -1
  26. package/dist/archive-zip-integrity.d.ts +4 -0
  27. package/dist/archive-zip-integrity.d.ts.map +1 -0
  28. package/dist/archive-zip-integrity.js +44 -0
  29. package/dist/archive-zip-preflight.d.ts.map +1 -1
  30. package/dist/archive-zip-preflight.js +6 -1
  31. package/dist/archive.d.ts.map +1 -1
  32. package/dist/archive.js +5 -2
  33. package/dist/bounded-read-stream.d.ts.map +1 -1
  34. package/dist/bounded-read-stream.js +13 -2
  35. package/dist/deny-mutations.d.ts +1 -0
  36. package/dist/deny-mutations.d.ts.map +1 -1
  37. package/dist/deny-mutations.js +6 -39
  38. package/dist/device-path.d.ts +1 -0
  39. package/dist/device-path.d.ts.map +1 -1
  40. package/dist/device-path.js +6 -6
  41. package/dist/directory-guard.d.ts.map +1 -1
  42. package/dist/directory-guard.js +5 -4
  43. package/dist/error-detail.d.ts +2 -0
  44. package/dist/error-detail.d.ts.map +1 -0
  45. package/dist/error-detail.js +4 -0
  46. package/dist/errors.d.ts.map +1 -1
  47. package/dist/errors.js +3 -0
  48. package/dist/file-hash.d.ts.map +1 -1
  49. package/dist/file-hash.js +4 -1
  50. package/dist/file-lock-sync.d.ts.map +1 -1
  51. package/dist/file-lock-sync.js +88 -35
  52. package/dist/file-store-boundary.d.ts +6 -0
  53. package/dist/file-store-boundary.d.ts.map +1 -1
  54. package/dist/file-store-boundary.js +20 -5
  55. package/dist/file-store-sync-write.d.ts +9 -0
  56. package/dist/file-store-sync-write.d.ts.map +1 -0
  57. package/dist/file-store-sync-write.js +97 -0
  58. package/dist/file-store.d.ts.map +1 -1
  59. package/dist/file-store.js +36 -128
  60. package/dist/filename.d.ts.map +1 -1
  61. package/dist/filename.js +27 -0
  62. package/dist/guarded-mkdir.d.ts.map +1 -1
  63. package/dist/guarded-mkdir.js +5 -6
  64. package/dist/install-path.js +1 -1
  65. package/dist/json-document-store.d.ts.map +1 -1
  66. package/dist/json-document-store.js +19 -13
  67. package/dist/json.d.ts.map +1 -1
  68. package/dist/json.js +1 -3
  69. package/dist/local-file-access.d.ts +3 -2
  70. package/dist/local-file-access.d.ts.map +1 -1
  71. package/dist/local-file-access.js +12 -6
  72. package/dist/local-roots.d.ts.map +1 -1
  73. package/dist/local-roots.js +35 -30
  74. package/dist/move-path.d.ts.map +1 -1
  75. package/dist/move-path.js +51 -12
  76. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  77. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  78. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  79. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  80. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  81. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  82. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  83. package/dist/native-binding.d.ts +1 -0
  84. package/dist/native-binding.d.ts.map +1 -1
  85. package/dist/native-operations.d.ts.map +1 -1
  86. package/dist/native-operations.js +14 -1
  87. package/dist/native-pinned-write.d.ts.map +1 -1
  88. package/dist/native-pinned-write.js +44 -17
  89. package/dist/output-sibling.d.ts.map +1 -1
  90. package/dist/output-sibling.js +6 -5
  91. package/dist/path-policy.d.ts.map +1 -1
  92. package/dist/path-policy.js +1 -7
  93. package/dist/path.d.ts.map +1 -1
  94. package/dist/path.js +4 -0
  95. package/dist/permission-exec.d.ts +6 -0
  96. package/dist/permission-exec.d.ts.map +1 -0
  97. package/dist/permission-exec.js +28 -0
  98. package/dist/permissions-windows.d.ts +56 -0
  99. package/dist/permissions-windows.d.ts.map +1 -0
  100. package/dist/permissions-windows.js +285 -0
  101. package/dist/permissions.d.ts +3 -46
  102. package/dist/permissions.d.ts.map +1 -1
  103. package/dist/permissions.js +17 -328
  104. package/dist/pinned-write.js +2 -1
  105. package/dist/regular-file.d.ts.map +1 -1
  106. package/dist/regular-file.js +21 -2
  107. package/dist/replace-directory.d.ts.map +1 -1
  108. package/dist/replace-directory.js +10 -1
  109. package/dist/replace-file-copy-fallback.d.ts +3 -0
  110. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  111. package/dist/replace-file-copy-fallback.js +18 -11
  112. package/dist/replace-file-descriptor.d.ts +33 -0
  113. package/dist/replace-file-descriptor.d.ts.map +1 -0
  114. package/dist/replace-file-descriptor.js +96 -0
  115. package/dist/replace-file.d.ts +9 -2
  116. package/dist/replace-file.d.ts.map +1 -1
  117. package/dist/replace-file.js +64 -72
  118. package/dist/root-context.d.ts +8 -0
  119. package/dist/root-context.d.ts.map +1 -1
  120. package/dist/root-context.js +43 -2
  121. package/dist/root-errors.d.ts +7 -0
  122. package/dist/root-errors.d.ts.map +1 -1
  123. package/dist/root-errors.js +46 -1
  124. package/dist/root-file.d.ts +1 -0
  125. package/dist/root-file.d.ts.map +1 -1
  126. package/dist/root-file.js +2 -0
  127. package/dist/root-impl.d.ts.map +1 -1
  128. package/dist/root-impl.js +213 -168
  129. package/dist/root-path-symlink.d.ts +3 -0
  130. package/dist/root-path-symlink.d.ts.map +1 -0
  131. package/dist/root-path-symlink.js +35 -0
  132. package/dist/root-path.d.ts +1 -0
  133. package/dist/root-path.d.ts.map +1 -1
  134. package/dist/root-path.js +129 -190
  135. package/dist/root-walk.d.ts.map +1 -1
  136. package/dist/root-walk.js +15 -0
  137. package/dist/safe-path-segment.d.ts +2 -0
  138. package/dist/safe-path-segment.d.ts.map +1 -1
  139. package/dist/safe-path-segment.js +12 -0
  140. package/dist/secret-file.d.ts.map +1 -1
  141. package/dist/secret-file.js +19 -3
  142. package/dist/secure-temp-dir.d.ts.map +1 -1
  143. package/dist/secure-temp-dir.js +6 -1
  144. package/dist/short-path.d.ts +2 -0
  145. package/dist/short-path.d.ts.map +1 -0
  146. package/dist/short-path.js +7 -0
  147. package/dist/sidecar-lock-acquire.d.ts +30 -0
  148. package/dist/sidecar-lock-acquire.d.ts.map +1 -0
  149. package/dist/sidecar-lock-acquire.js +260 -0
  150. package/dist/sidecar-lock-policy.d.ts +3 -0
  151. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  152. package/dist/sidecar-lock-policy.js +20 -3
  153. package/dist/sidecar-lock-reclaim.d.ts +5 -1
  154. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  155. package/dist/sidecar-lock-reclaim.js +59 -9
  156. package/dist/sidecar-lock.d.ts.map +1 -1
  157. package/dist/sidecar-lock.js +27 -226
  158. package/dist/temp-target.d.ts.map +1 -1
  159. package/dist/temp-target.js +15 -4
  160. package/dist/test-hooks.d.ts +1 -0
  161. package/dist/test-hooks.d.ts.map +1 -1
  162. package/dist/timing.d.ts +2 -0
  163. package/dist/timing.d.ts.map +1 -1
  164. package/dist/timing.js +9 -0
  165. package/dist/walk.d.ts.map +1 -1
  166. package/dist/walk.js +15 -0
  167. package/docs/advanced.md +1 -1
  168. package/docs/archive.md +27 -20
  169. package/docs/assets/readme-banner.jpg +0 -0
  170. package/docs/atomic.md +48 -19
  171. package/docs/errors.md +31 -7
  172. package/docs/file-store.md +10 -1
  173. package/docs/filename.md +17 -16
  174. package/docs/index.md +1 -0
  175. package/docs/install-path.md +9 -6
  176. package/docs/json.md +8 -2
  177. package/docs/local-roots.md +79 -103
  178. package/docs/native-helper.md +11 -5
  179. package/docs/native.md +2 -2
  180. package/docs/output.md +8 -3
  181. package/docs/path-scope.md +4 -1
  182. package/docs/path.md +7 -3
  183. package/docs/permissions.md +8 -0
  184. package/docs/public-api.md +109 -0
  185. package/docs/quickstart.md +6 -2
  186. package/docs/reading.md +13 -10
  187. package/docs/root.md +26 -4
  188. package/docs/secret-file.md +17 -4
  189. package/docs/security-model.md +21 -4
  190. package/docs/sidecar-lock.md +15 -5
  191. package/docs/temp.md +37 -5
  192. package/docs/test-hooks.md +2 -0
  193. package/docs/types.md +32 -15
  194. package/docs/writing.md +25 -12
  195. package/package.json +7 -3
  196. package/dist/archive-file-io.d.ts +0 -9
  197. package/dist/archive-file-io.d.ts.map +0 -1
  198. package/dist/archive-file-io.js +0 -11
  199. package/dist/file-store-source.d.ts +0 -5
  200. package/dist/file-store-source.d.ts.map +0 -1
  201. package/dist/file-store-source.js +0 -30
  202. package/dist/file-url.d.ts +0 -2
  203. package/dist/file-url.d.ts.map +0 -1
  204. package/dist/file-url.js +0 -1
  205. package/dist/path-stat.d.ts +0 -4
  206. package/dist/path-stat.d.ts.map +0 -1
  207. package/dist/path-stat.js +0 -15
  208. package/dist/windows-permissions-native.d.ts +0 -9
  209. package/dist/windows-permissions-native.d.ts.map +0 -1
  210. package/dist/windows-permissions-native.js +0 -31
@@ -0,0 +1,3 @@
1
+ export declare function resolveSymlinkHopPath(symlinkPath: string): Promise<string>;
2
+ export declare function resolveSymlinkHopPathSync(symlinkPath: string): string;
3
+ //# sourceMappingURL=root-path-symlink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-path-symlink.d.ts","sourceRoot":"","sources":["../src/root-path-symlink.ts"],"names":[],"mappings":"AAmBA,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhF;AAED,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQrE"}
@@ -0,0 +1,35 @@
1
+ import fs from "node:fs";
2
+ import fsp from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { isNotFoundPathError, isSymlinkOpenError } from "./path.js";
6
+ import { resolvePathViaExistingAncestor, resolvePathViaExistingAncestorSync, } from "./root-path-existing.js";
7
+ function normalizeSymlinkResolutionError(error) {
8
+ if (isSymlinkOpenError(error)) {
9
+ throw new FsSafeError("symlink", "symlink path could not be resolved", {
10
+ cause: error instanceof Error ? error : undefined,
11
+ });
12
+ }
13
+ if (!isNotFoundPathError(error))
14
+ throw error;
15
+ }
16
+ export async function resolveSymlinkHopPath(symlinkPath) {
17
+ try {
18
+ return path.resolve(await fsp.realpath(symlinkPath));
19
+ }
20
+ catch (error) {
21
+ normalizeSymlinkResolutionError(error);
22
+ const linkTarget = await fsp.readlink(symlinkPath);
23
+ return resolvePathViaExistingAncestor(path.resolve(path.dirname(symlinkPath), linkTarget));
24
+ }
25
+ }
26
+ export function resolveSymlinkHopPathSync(symlinkPath) {
27
+ try {
28
+ return path.resolve(fs.realpathSync(symlinkPath));
29
+ }
30
+ catch (error) {
31
+ normalizeSymlinkResolutionError(error);
32
+ const linkTarget = fs.readlinkSync(symlinkPath);
33
+ return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
34
+ }
35
+ }
@@ -20,6 +20,7 @@ type ResolveRootPathParams = {
20
20
  boundaryLabel: string;
21
21
  intent?: RootPathIntent;
22
22
  policy?: RootPathAliasPolicy;
23
+ rejectSymlinks?: boolean;
23
24
  skipLexicalRootCheck?: boolean;
24
25
  rootCanonicalPath?: string;
25
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,kCAAkC,EAAE,MAAM,yBAAyB,CAAC;AAE7E,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,wBAAwB;aACnC,MAAM;;;;aAIN,YAAY;;;;CAIJ,CAAC;AAEX,KAAK,qBAAqB,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;CAC5B,CAAC;AAEF,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CA+B3B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB,CA+BnF"}
1
+ {"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,kCAAkC,EAAE,MAAM,yBAAyB,CAAC;AAE7E,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,wBAAwB;aACnC,MAAM;;;;aAIN,YAAY;;;;CAIJ,CAAC;AAEX,KAAK,qBAAqB,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;CAC5B,CAAC;AAEF,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAsCD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB,CAMnF"}
package/dist/root-path.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import fsp from "node:fs/promises";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
- import { isNotFoundPathError, isPathInside, isPathRelativeEscape } from "./path.js";
4
+ import { formatErrorDetail } from "./error-detail.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { assertNoNulPathInput, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
6
7
  import { resolvePathViaExistingAncestor, resolvePathViaExistingAncestorSync, } from "./root-path-existing.js";
8
+ import { resolveSymlinkHopPath, resolveSymlinkHopPathSync } from "./root-path-symlink.js";
9
+ import { assertNoDriveRelativePathSegments } from "./safe-path-segment.js";
10
+ import { shortPath } from "./short-path.js";
7
11
  export { resolvePathViaExistingAncestorSync } from "./root-path-existing.js";
8
12
  export const ROOT_PATH_ALIAS_POLICIES = {
9
13
  strict: Object.freeze({
@@ -16,6 +20,15 @@ export const ROOT_PATH_ALIAS_POLICIES = {
16
20
  }),
17
21
  };
18
22
  export async function resolveRootPath(params) {
23
+ try {
24
+ return await resolveRootPathInternal(params);
25
+ }
26
+ catch (error) {
27
+ throw sanitizeRootPathError(error);
28
+ }
29
+ }
30
+ async function resolveRootPathInternal(params) {
31
+ assertValidRootPathInputs(params);
19
32
  const rootPath = path.resolve(params.rootPath);
20
33
  const absolutePath = path.resolve(params.absolutePath);
21
34
  const rootCanonicalPath = params.rootCanonicalPath
@@ -46,6 +59,15 @@ export async function resolveRootPath(params) {
46
59
  });
47
60
  }
48
61
  export function resolveRootPathSync(params) {
62
+ try {
63
+ return resolveRootPathSyncInternal(params);
64
+ }
65
+ catch (error) {
66
+ throw sanitizeRootPathError(error);
67
+ }
68
+ }
69
+ function resolveRootPathSyncInternal(params) {
70
+ assertValidRootPathInputs(params);
49
71
  const rootPath = path.resolve(params.rootPath);
50
72
  const absolutePath = path.resolve(params.absolutePath);
51
73
  const rootCanonicalPath = params.rootCanonicalPath
@@ -75,6 +97,29 @@ export function resolveRootPathSync(params) {
75
97
  rootCanonicalPath: context.rootCanonicalPath,
76
98
  });
77
99
  }
100
+ function sanitizeRootPathError(error) {
101
+ if (error instanceof Error) {
102
+ error.message = formatErrorDetail(error.message);
103
+ }
104
+ return error;
105
+ }
106
+ function assertValidRootPathInputs(params) {
107
+ assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
108
+ assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
109
+ assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
110
+ assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
111
+ if (params.rootCanonicalPath !== undefined) {
112
+ assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
113
+ assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
114
+ }
115
+ }
116
+ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
117
+ if (process.platform !== "win32") {
118
+ return;
119
+ }
120
+ const root = path.parse(filePath).root;
121
+ assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
122
+ }
78
123
  function isPromiseLike(value) {
79
124
  return Boolean(value &&
80
125
  (typeof value === "object" || typeof value === "function") &&
@@ -93,6 +138,15 @@ function createLexicalTraversalState(params) {
93
138
  preserveFinalSymlink: false,
94
139
  };
95
140
  }
141
+ function createLexicalTraversalContext(params) {
142
+ return {
143
+ state: createLexicalTraversalState(params),
144
+ resolveParams: params.params,
145
+ rootPath: params.rootPath,
146
+ rootCanonicalPath: params.rootCanonicalPath,
147
+ absolutePath: params.absolutePath,
148
+ };
149
+ }
96
150
  function splitTraversalSegments(value) {
97
151
  return value
98
152
  .split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
@@ -116,149 +170,97 @@ function rawPathRelativeToRoot(rootPath, candidatePath) {
116
170
  : candidatePrefix === rootWithSep;
117
171
  return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
118
172
  }
119
- function assertLexicalCursorInsideBoundary(params) {
173
+ function assertLexicalCursorInsideBoundary(context, candidatePath) {
120
174
  assertInsideBoundary({
121
- boundaryLabel: params.params.boundaryLabel,
122
- rootCanonicalPath: params.rootCanonicalPath,
123
- candidatePath: params.candidatePath,
124
- absolutePath: params.absolutePath,
175
+ boundaryLabel: context.resolveParams.boundaryLabel,
176
+ rootCanonicalPath: context.rootCanonicalPath,
177
+ candidatePath,
178
+ absolutePath: context.absolutePath,
125
179
  });
126
180
  }
127
- function applyMissingSuffixToCanonicalCursor(params) {
128
- const missingSuffix = params.state.segments.slice(params.missingFromIndex);
181
+ function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
182
+ const missingSuffix = context.state.segments.slice(missingFromIndex);
129
183
  for (const segment of missingSuffix) {
130
- advanceCanonicalCursorForSegment({
131
- state: params.state,
132
- segment,
133
- rootCanonicalPath: params.rootCanonicalPath,
134
- params: params.params,
135
- absolutePath: params.absolutePath,
136
- });
184
+ advanceCanonicalCursorForSegment(context, segment);
137
185
  }
138
186
  }
139
- function advanceCanonicalCursorForSegment(params) {
140
- params.state.canonicalCursor = path.resolve(params.state.canonicalCursor, params.segment);
141
- assertLexicalCursorInsideBoundary({
142
- params: params.params,
143
- rootCanonicalPath: params.rootCanonicalPath,
144
- candidatePath: params.state.canonicalCursor,
145
- absolutePath: params.absolutePath,
146
- });
187
+ function advanceCanonicalCursorForSegment(context, segment) {
188
+ context.state.canonicalCursor = path.resolve(context.state.canonicalCursor, segment);
189
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
147
190
  }
148
- function finalizeLexicalResolution(params) {
149
- assertLexicalCursorInsideBoundary({
150
- params: params.params,
151
- rootCanonicalPath: params.rootCanonicalPath,
152
- candidatePath: params.state.canonicalCursor,
153
- absolutePath: params.absolutePath,
154
- });
191
+ function finalizeLexicalResolution(context, kind) {
192
+ assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
155
193
  return buildResolvedRootPath({
156
- absolutePath: params.absolutePath,
157
- canonicalPath: params.state.canonicalCursor,
158
- rootPath: params.rootPath,
159
- rootCanonicalPath: params.rootCanonicalPath,
160
- kind: params.kind,
194
+ absolutePath: context.absolutePath,
195
+ canonicalPath: context.state.canonicalCursor,
196
+ rootPath: context.rootPath,
197
+ rootCanonicalPath: context.rootCanonicalPath,
198
+ kind,
161
199
  });
162
200
  }
163
- function handleLexicalLstatFailure(params) {
164
- if (!isNotFoundPathError(params.error)) {
201
+ function handleLexicalLstatFailure(context, error, missingFromIndex) {
202
+ if (!isNotFoundPathError(error)) {
165
203
  return false;
166
204
  }
167
- applyMissingSuffixToCanonicalCursor({
168
- state: params.state,
169
- missingFromIndex: params.missingFromIndex,
170
- rootCanonicalPath: params.rootCanonicalPath,
171
- params: params.resolveParams,
172
- absolutePath: params.absolutePath,
173
- });
205
+ applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
174
206
  return true;
175
207
  }
176
- function handleLexicalStatReadFailure(params) {
177
- if (handleLexicalLstatFailure({
178
- error: params.error,
179
- state: params.state,
180
- missingFromIndex: params.missingFromIndex,
181
- rootCanonicalPath: params.rootCanonicalPath,
182
- resolveParams: params.resolveParams,
183
- absolutePath: params.absolutePath,
184
- })) {
208
+ function handleLexicalStatReadFailure(context, error, missingFromIndex) {
209
+ if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
185
210
  return null;
186
211
  }
187
- throw params.error;
212
+ throw error;
188
213
  }
189
- function handleLexicalStatDisposition(params) {
214
+ function handleLexicalStatDisposition(context, params) {
190
215
  if (!params.isSymbolicLink) {
191
- advanceCanonicalCursorForSegment({
192
- state: params.state,
193
- segment: params.segment,
194
- rootCanonicalPath: params.rootCanonicalPath,
195
- params: params.resolveParams,
196
- absolutePath: params.absolutePath,
197
- });
216
+ advanceCanonicalCursorForSegment(context, params.segment);
198
217
  return "continue";
199
218
  }
200
- if (params.state.allowFinalSymlink && params.isLast) {
201
- params.state.preserveFinalSymlink = true;
202
- advanceCanonicalCursorForSegment({
203
- state: params.state,
204
- segment: params.segment,
205
- rootCanonicalPath: params.rootCanonicalPath,
206
- params: params.resolveParams,
207
- absolutePath: params.absolutePath,
208
- });
219
+ if (context.resolveParams.rejectSymlinks === true && params.isLast) {
220
+ throw new FsSafeError("symlink", "symlink path component not allowed");
221
+ }
222
+ if (context.state.allowFinalSymlink && params.isLast) {
223
+ context.state.preserveFinalSymlink = true;
224
+ advanceCanonicalCursorForSegment(context, params.segment);
209
225
  return "break";
210
226
  }
211
227
  return "resolve-link";
212
228
  }
213
- function applyResolvedSymlinkHop(params) {
214
- if (!isPathInside(params.rootCanonicalPath, params.linkCanonical)) {
229
+ function applyResolvedSymlinkHop(context, linkCanonical) {
230
+ if (!isPathInside(context.rootCanonicalPath, linkCanonical)) {
215
231
  throw symlinkEscapeError({
216
- boundaryLabel: params.boundaryLabel,
217
- rootCanonicalPath: params.rootCanonicalPath,
218
- symlinkPath: params.state.lexicalCursor,
232
+ boundaryLabel: context.resolveParams.boundaryLabel,
233
+ rootCanonicalPath: context.rootCanonicalPath,
234
+ symlinkPath: context.state.lexicalCursor,
219
235
  });
220
236
  }
221
- params.state.canonicalCursor = params.linkCanonical;
222
- params.state.lexicalCursor = params.linkCanonical;
237
+ context.state.canonicalCursor = linkCanonical;
238
+ context.state.lexicalCursor = linkCanonical;
223
239
  }
224
- function readLexicalStat(params) {
240
+ function readLexicalStat(context, params) {
225
241
  try {
226
- const stat = params.read(params.state.lexicalCursor);
242
+ const stat = params.read(context.state.lexicalCursor);
227
243
  if (isPromiseLike(stat)) {
228
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure({ ...params, error }));
244
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
229
245
  }
230
246
  return stat;
231
247
  }
232
248
  catch (error) {
233
- return handleLexicalStatReadFailure({ ...params, error });
249
+ return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
234
250
  }
235
251
  }
236
- function resolveAndApplySymlinkHop(params) {
237
- const linkCanonical = params.resolveLinkCanonical(params.state.lexicalCursor);
252
+ function resolveAndApplySymlinkHop(context, params) {
253
+ const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
238
254
  if (isPromiseLike(linkCanonical)) {
239
- return Promise.resolve(linkCanonical).then((value) => applyResolvedSymlinkHop({
240
- state: params.state,
241
- linkCanonical: value,
242
- rootCanonicalPath: params.rootCanonicalPath,
243
- boundaryLabel: params.boundaryLabel,
244
- }));
245
- }
246
- applyResolvedSymlinkHop({
247
- state: params.state,
248
- linkCanonical,
249
- rootCanonicalPath: params.rootCanonicalPath,
250
- boundaryLabel: params.boundaryLabel,
251
- });
255
+ return Promise.resolve(linkCanonical).then((value) => {
256
+ applyResolvedSymlinkHop(context, value);
257
+ });
258
+ }
259
+ applyResolvedSymlinkHop(context, linkCanonical);
252
260
  }
253
- function applyParentTraversalStep(params) {
254
- params.state.lexicalCursor = path.resolve(params.state.lexicalCursor, "..");
255
- advanceCanonicalCursorForSegment({
256
- state: params.state,
257
- segment: "..",
258
- rootCanonicalPath: params.rootCanonicalPath,
259
- params: params.resolveParams,
260
- absolutePath: params.absolutePath,
261
- });
261
+ function applyParentTraversalStep(context) {
262
+ context.state.lexicalCursor = path.resolve(context.state.lexicalCursor, "..");
263
+ advanceCanonicalCursorForSegment(context, "..");
262
264
  }
263
265
  function* iterateLexicalTraversal(state) {
264
266
  for (let idx = 0; idx < state.segments.length; idx += 1) {
@@ -268,32 +270,22 @@ function* iterateLexicalTraversal(state) {
268
270
  }
269
271
  }
270
272
  async function resolveRootPathLexicalAsync(params) {
271
- const state = createLexicalTraversalState(params);
272
- const sharedStepParams = {
273
- state,
274
- rootCanonicalPath: params.rootCanonicalPath,
275
- resolveParams: params.params,
276
- absolutePath: params.absolutePath,
277
- };
273
+ const context = createLexicalTraversalContext(params);
274
+ const { state } = context;
278
275
  for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
279
276
  if (segment === "..") {
280
- applyParentTraversalStep({
281
- ...sharedStepParams,
282
- resolveParams: params.params,
283
- });
277
+ applyParentTraversalStep(context);
284
278
  continue;
285
279
  }
286
280
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
287
- const stat = await readLexicalStat({
288
- ...sharedStepParams,
281
+ const stat = await readLexicalStat(context, {
289
282
  missingFromIndex: idx,
290
283
  read: (cursor) => fsp.lstat(cursor),
291
284
  });
292
285
  if (!stat) {
293
286
  break;
294
287
  }
295
- const disposition = handleLexicalStatDisposition({
296
- ...sharedStepParams,
288
+ const disposition = handleLexicalStatDisposition(context, {
297
289
  isSymbolicLink: stat.isSymbolicLink(),
298
290
  segment,
299
291
  isLast,
@@ -304,41 +296,29 @@ async function resolveRootPathLexicalAsync(params) {
304
296
  if (disposition === "break") {
305
297
  break;
306
298
  }
307
- await resolveAndApplySymlinkHop({
308
- state,
309
- rootCanonicalPath: params.rootCanonicalPath,
310
- boundaryLabel: params.params.boundaryLabel,
299
+ await resolveAndApplySymlinkHop(context, {
311
300
  resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
312
301
  });
302
+ if (context.resolveParams.rejectSymlinks === true) {
303
+ throw new FsSafeError("symlink", "symlink path component not allowed");
304
+ }
313
305
  }
314
306
  const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
315
- return finalizeLexicalResolution({
316
- ...params,
317
- state,
318
- kind,
319
- });
307
+ return finalizeLexicalResolution(context, kind);
320
308
  }
321
309
  function resolveRootPathLexicalSync(params) {
322
- const state = createLexicalTraversalState(params);
310
+ const context = createLexicalTraversalContext(params);
311
+ const { state } = context;
323
312
  for (let idx = 0; idx < state.segments.length; idx += 1) {
324
313
  const segment = state.segments[idx] ?? "";
325
314
  const isLast = idx === state.segments.length - 1;
326
315
  if (segment === "..") {
327
- applyParentTraversalStep({
328
- state,
329
- rootCanonicalPath: params.rootCanonicalPath,
330
- resolveParams: params.params,
331
- absolutePath: params.absolutePath,
332
- });
316
+ applyParentTraversalStep(context);
333
317
  continue;
334
318
  }
335
319
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
336
- const maybeStat = readLexicalStat({
337
- state,
320
+ const maybeStat = readLexicalStat(context, {
338
321
  missingFromIndex: idx,
339
- rootCanonicalPath: params.rootCanonicalPath,
340
- resolveParams: params.params,
341
- absolutePath: params.absolutePath,
342
322
  read: (cursor) => fs.lstatSync(cursor),
343
323
  });
344
324
  if (isPromiseLike(maybeStat)) {
@@ -348,14 +328,10 @@ function resolveRootPathLexicalSync(params) {
348
328
  if (!stat) {
349
329
  break;
350
330
  }
351
- const disposition = handleLexicalStatDisposition({
352
- state,
331
+ const disposition = handleLexicalStatDisposition(context, {
353
332
  isSymbolicLink: stat.isSymbolicLink(),
354
333
  segment,
355
334
  isLast,
356
- rootCanonicalPath: params.rootCanonicalPath,
357
- resolveParams: params.params,
358
- absolutePath: params.absolutePath,
359
335
  });
360
336
  if (disposition === "continue") {
361
337
  continue;
@@ -363,22 +339,18 @@ function resolveRootPathLexicalSync(params) {
363
339
  if (disposition === "break") {
364
340
  break;
365
341
  }
366
- const maybeApplied = resolveAndApplySymlinkHop({
367
- state,
368
- rootCanonicalPath: params.rootCanonicalPath,
369
- boundaryLabel: params.params.boundaryLabel,
342
+ const maybeApplied = resolveAndApplySymlinkHop(context, {
370
343
  resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
371
344
  });
372
345
  if (isPromiseLike(maybeApplied)) {
373
346
  throw new Error("Unexpected async symlink resolution");
374
347
  }
348
+ if (context.resolveParams.rejectSymlinks === true) {
349
+ throw new FsSafeError("symlink", "symlink path component not allowed");
350
+ }
375
351
  }
376
352
  const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
377
- return finalizeLexicalResolution({
378
- ...params,
379
- state,
380
- kind,
381
- });
353
+ return finalizeLexicalResolution(context, kind);
382
354
  }
383
355
  function resolveCanonicalOutsideLexicalPath(params) {
384
356
  return params.outsideLexicalCanonicalPath ?? params.absolutePath;
@@ -549,36 +521,3 @@ function pathEscapeError(params) {
549
521
  function symlinkEscapeError(params) {
550
522
  return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
551
523
  }
552
- function shortPath(value) {
553
- const home = os.homedir();
554
- if (value.startsWith(home)) {
555
- return `~${value.slice(home.length)}`;
556
- }
557
- return value;
558
- }
559
- async function resolveSymlinkHopPath(symlinkPath) {
560
- try {
561
- return path.resolve(await fsp.realpath(symlinkPath));
562
- }
563
- catch (error) {
564
- if (!isNotFoundPathError(error)) {
565
- throw error;
566
- }
567
- const linkTarget = await fsp.readlink(symlinkPath);
568
- const linkAbsolute = path.resolve(path.dirname(symlinkPath), linkTarget);
569
- return resolvePathViaExistingAncestor(linkAbsolute);
570
- }
571
- }
572
- function resolveSymlinkHopPathSync(symlinkPath) {
573
- try {
574
- return path.resolve(fs.realpathSync(symlinkPath));
575
- }
576
- catch (error) {
577
- if (!isNotFoundPathError(error)) {
578
- throw error;
579
- }
580
- const linkTarget = fs.readlinkSync(symlinkPath);
581
- const linkAbsolute = path.resolve(path.dirname(symlinkPath), linkTarget);
582
- return resolvePathViaExistingAncestorSync(linkAbsolute);
583
- }
584
- }
@@ -1 +1 @@
1
- {"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACnF,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAgG/B"}
1
+ {"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACnF,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAsH/B"}
package/dist/root-walk.js CHANGED
@@ -22,6 +22,17 @@ function limitEntry(relativePath) {
22
22
  return { relativePath, kind: "truncated", size: 0 };
23
23
  }
24
24
  export async function* walkRoot(root, relativePath, options) {
25
+ if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
26
+ throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
27
+ }
28
+ if (options.limitBehavior !== undefined &&
29
+ !["truncate", "throw"].includes(options.limitBehavior)) {
30
+ throw new TypeError(`invalid root walk limit behavior: ${String(options.limitBehavior)}`);
31
+ }
32
+ if (options.onDirectoryError !== undefined &&
33
+ !["throw", "skip-and-report"].includes(options.onDirectoryError)) {
34
+ throw new TypeError(`invalid root walk directory error behavior: ${String(options.onDirectoryError)}`);
35
+ }
25
36
  const maxDepth = validateBudget("maxDepth", options.maxDepth);
26
37
  const maxEntries = validateBudget("maxEntries", options.maxEntries);
27
38
  const visitedDirectories = new Set();
@@ -49,6 +60,7 @@ export async function* walkRoot(root, relativePath, options) {
49
60
  return;
50
61
  }
51
62
  visitedDirectories.add(resolvedDirectory.canonicalPath);
63
+ options.signal?.throwIfAborted();
52
64
  const listingDirectory = path
53
65
  .relative(root.rootReal, resolvedDirectory.canonicalPath)
54
66
  .split(path.sep)
@@ -56,11 +68,14 @@ export async function* walkRoot(root, relativePath, options) {
56
68
  entries = await root.list(listingDirectory, { withFileTypes: true });
57
69
  }
58
70
  catch (error) {
71
+ // Cancellation is never a recoverable directory read failure.
72
+ options.signal?.throwIfAborted();
59
73
  if ((options.onDirectoryError ?? "throw") === "throw")
60
74
  throw error;
61
75
  yield { relativePath: directory, kind: "directory-error", size: 0, error };
62
76
  return;
63
77
  }
78
+ options.signal?.throwIfAborted();
64
79
  for (const entry of entries) {
65
80
  options.signal?.throwIfAborted();
66
81
  const child = directory
@@ -2,6 +2,8 @@ export type SafePathSegmentOptions = {
2
2
  allowDotPrefix?: boolean;
3
3
  label?: string;
4
4
  };
5
+ export declare function isDriveRelativePath(value: string): boolean;
6
+ export declare function assertNoDriveRelativePathSegments(value: string, label: string): string;
5
7
  export declare function isSafePathSegment(segment: string, options?: SafePathSegmentOptions): boolean;
6
8
  export declare function assertSafePathSegment(segment: string, options?: SafePathSegmentOptions): string;
7
9
  export declare function sanitizeSafePathSegment(value: string, fallback: string, options?: SafePathSegmentOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"safe-path-segment.d.ts","sourceRoot":"","sources":["../src/safe-path-segment.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAcF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAaT;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAUR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAWR;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAeR"}
1
+ {"version":3,"file":"safe-path-segment.d.ts","sourceRoot":"","sources":["../src/safe-path-segment.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAcD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAaT;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAUR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAWR;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAeR"}
@@ -1,7 +1,19 @@
1
1
  import { FsSafeError } from "./errors.js";
2
2
  const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
3
3
  const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
4
+ // Windows treats "C:name" as relative to the drive's current directory even
5
+ // though path.win32.isAbsolute() reports false.
6
+ const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
4
7
  const HYPHEN_CHAR_CODE = 0x2d;
8
+ export function isDriveRelativePath(value) {
9
+ return DRIVE_RELATIVE_PREFIX.test(value);
10
+ }
11
+ export function assertNoDriveRelativePathSegments(value, label) {
12
+ if (value.split("/").some(isDriveRelativePath)) {
13
+ throw new FsSafeError("invalid-path", `${label} must not contain a drive letter`);
14
+ }
15
+ return value;
16
+ }
5
17
  function trimHyphenEdges(value) {
6
18
  let start = 0;
7
19
  let end = value.length;
@@ -1 +1 @@
1
- {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,6BAA6B,QAAY,CAAC;AACvD,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAqIF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAQR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAcpB;AAoID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAyDF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAExF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYzF"}
1
+ {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,6BAA6B,QAAY,CAAC;AACvD,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAoIF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAQR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAcpB;AAoID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}