@openclaw/fs-safe 0.15.0 → 0.17.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.
Files changed (311) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +36 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +3 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-kind.d.ts +0 -1
  11. package/dist/archive-kind.d.ts.map +1 -1
  12. package/dist/archive-kind.js +5 -17
  13. package/dist/archive-merge.d.ts +1 -0
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +4 -4
  16. package/dist/archive-native.d.ts +1 -0
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +1 -0
  19. package/dist/archive-options.d.ts +2 -0
  20. package/dist/archive-options.d.ts.map +1 -1
  21. package/dist/archive-parser.wasm +0 -0
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +6 -7
  24. package/dist/archive-tar-stream.d.ts +3 -0
  25. package/dist/archive-tar-stream.d.ts.map +1 -1
  26. package/dist/archive-tar-stream.js +56 -37
  27. package/dist/archive-tar-wasm.d.ts +16 -4
  28. package/dist/archive-tar-wasm.d.ts.map +1 -1
  29. package/dist/archive-tar-wasm.js +134 -34
  30. package/dist/archive-zip-count.d.ts.map +1 -1
  31. package/dist/archive-zip-count.js +21 -1
  32. package/dist/archive-zip-directory.d.ts.map +1 -1
  33. package/dist/archive-zip-directory.js +23 -1
  34. package/dist/archive-zip-loader.d.ts +2 -0
  35. package/dist/archive-zip-loader.d.ts.map +1 -1
  36. package/dist/archive-zip-loader.js +7 -0
  37. package/dist/archive-zip-names.d.ts.map +1 -1
  38. package/dist/archive-zip-names.js +7 -2
  39. package/dist/archive.d.ts.map +1 -1
  40. package/dist/archive.js +14 -9
  41. package/dist/byte-view.d.ts +3 -0
  42. package/dist/byte-view.d.ts.map +1 -0
  43. package/dist/byte-view.js +13 -0
  44. package/dist/clone-metadata.d.ts +1 -0
  45. package/dist/clone-metadata.d.ts.map +1 -1
  46. package/dist/clone-metadata.js +6 -2
  47. package/dist/create-directory.d.ts +20 -0
  48. package/dist/create-directory.d.ts.map +1 -0
  49. package/dist/create-directory.js +130 -0
  50. package/dist/create-file-async.d.ts +7 -0
  51. package/dist/create-file-async.d.ts.map +1 -0
  52. package/dist/create-file-async.js +121 -0
  53. package/dist/create-file.d.ts +8 -0
  54. package/dist/create-file.d.ts.map +1 -0
  55. package/dist/create-file.js +190 -0
  56. package/dist/create-owned-file.d.ts +8 -0
  57. package/dist/create-owned-file.d.ts.map +1 -0
  58. package/dist/create-owned-file.js +16 -0
  59. package/dist/create.d.ts +4 -0
  60. package/dist/create.d.ts.map +1 -0
  61. package/dist/create.js +2 -0
  62. package/dist/creation-darwin.d.ts +6 -0
  63. package/dist/creation-darwin.d.ts.map +1 -0
  64. package/dist/creation-darwin.js +70 -0
  65. package/dist/creation-file-state.d.ts +19 -0
  66. package/dist/creation-file-state.d.ts.map +1 -0
  67. package/dist/creation-file-state.js +118 -0
  68. package/dist/creation-path.d.ts +21 -0
  69. package/dist/creation-path.d.ts.map +1 -0
  70. package/dist/creation-path.js +71 -0
  71. package/dist/creation-permissions.d.ts +19 -0
  72. package/dist/creation-permissions.d.ts.map +1 -0
  73. package/dist/creation-permissions.js +125 -0
  74. package/dist/directory-durability.d.ts +7 -7
  75. package/dist/directory-durability.d.ts.map +1 -1
  76. package/dist/directory-durability.js +22 -80
  77. package/dist/directory-guard.d.ts +3 -0
  78. package/dist/directory-guard.d.ts.map +1 -1
  79. package/dist/directory-mode-node.d.ts +2 -0
  80. package/dist/directory-mode-node.d.ts.map +1 -1
  81. package/dist/directory-mode-node.js +8 -0
  82. package/dist/directory-receipt.d.ts +24 -0
  83. package/dist/directory-receipt.d.ts.map +1 -0
  84. package/dist/directory-receipt.js +123 -0
  85. package/dist/file-cleanup.d.ts +20 -0
  86. package/dist/file-cleanup.d.ts.map +1 -0
  87. package/dist/file-cleanup.js +81 -0
  88. package/dist/file-contents.d.ts +6 -0
  89. package/dist/file-contents.d.ts.map +1 -0
  90. package/dist/file-contents.js +40 -0
  91. package/dist/file-hash.d.ts.map +1 -1
  92. package/dist/file-hash.js +16 -4
  93. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  94. package/dist/file-lock-sync-root-acquire.js +3 -0
  95. package/dist/file-lock-sync-root-held.d.ts +1 -2
  96. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  97. package/dist/file-lock-sync-root-held.js +7 -5
  98. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  99. package/dist/file-lock-sync-stale-admission.js +3 -0
  100. package/dist/file-lock-sync.d.ts.map +1 -1
  101. package/dist/file-lock-sync.js +8 -11
  102. package/dist/file-observation.d.ts +1 -1
  103. package/dist/file-observation.d.ts.map +1 -1
  104. package/dist/file-store-boundary.d.ts +2 -6
  105. package/dist/file-store-boundary.d.ts.map +1 -1
  106. package/dist/file-store-boundary.js +3 -9
  107. package/dist/file-store-sync-write.d.ts.map +1 -1
  108. package/dist/file-store-sync-write.js +2 -5
  109. package/dist/file-store.js +3 -3
  110. package/dist/guarded-mkdir.d.ts +1 -0
  111. package/dist/guarded-mkdir.d.ts.map +1 -1
  112. package/dist/guarded-mkdir.js +27 -19
  113. package/dist/install-path.d.ts.map +1 -1
  114. package/dist/install-path.js +2 -5
  115. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  116. package/dist/json-durable-queue-ownership.js +2 -6
  117. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  118. package/dist/json-durable-queue-paths.js +2 -24
  119. package/dist/json-durable-queue.d.ts.map +1 -1
  120. package/dist/json-durable-queue.js +10 -9
  121. package/dist/json.d.ts.map +1 -1
  122. package/dist/json.js +32 -75
  123. package/dist/local-roots.d.ts.map +1 -1
  124. package/dist/local-roots.js +19 -21
  125. package/dist/move-path-cleanup.d.ts +5 -19
  126. package/dist/move-path-cleanup.d.ts.map +1 -1
  127. package/dist/move-path-cleanup.js +57 -21
  128. package/dist/move-path.d.ts.map +1 -1
  129. package/dist/move-path.js +63 -40
  130. package/dist/native-binding.d.ts +11 -1
  131. package/dist/native-binding.d.ts.map +1 -1
  132. package/dist/native-fallback-warning.d.ts +4 -0
  133. package/dist/native-fallback-warning.d.ts.map +1 -0
  134. package/dist/native-fallback-warning.js +11 -0
  135. package/dist/native-operations.d.ts +0 -2
  136. package/dist/native-operations.d.ts.map +1 -1
  137. package/dist/native-operations.js +0 -24
  138. package/dist/native-parent-admission.d.ts +2 -0
  139. package/dist/native-parent-admission.d.ts.map +1 -1
  140. package/dist/native-parent-admission.js +3 -2
  141. package/dist/native-pinned-write-windows.d.ts +1 -1
  142. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  143. package/dist/native-pinned-write-windows.js +173 -28
  144. package/dist/native-pinned-write.d.ts.map +1 -1
  145. package/dist/native-pinned-write.js +19 -3
  146. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  147. package/dist/native-policy-parent-windows.js +15 -6
  148. package/dist/native-staged-file.d.ts +5 -3
  149. package/dist/native-staged-file.d.ts.map +1 -1
  150. package/dist/native-staged-file.js +90 -40
  151. package/dist/native.js +2 -2
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +11 -2
  154. package/dist/owner-dacl.d.ts.map +1 -1
  155. package/dist/owner-dacl.js +10 -4
  156. package/dist/path.d.ts.map +1 -1
  157. package/dist/path.js +2 -1
  158. package/dist/permissions.d.ts.map +1 -1
  159. package/dist/permissions.js +3 -17
  160. package/dist/pinned-write-input.d.ts +4 -0
  161. package/dist/pinned-write-input.d.ts.map +1 -0
  162. package/dist/pinned-write-input.js +35 -0
  163. package/dist/pinned-write-mode.d.ts +5 -0
  164. package/dist/pinned-write-mode.d.ts.map +1 -0
  165. package/dist/pinned-write-mode.js +31 -0
  166. package/dist/pinned-write-staged.d.ts +6 -0
  167. package/dist/pinned-write-staged.d.ts.map +1 -0
  168. package/dist/pinned-write-staged.js +186 -0
  169. package/dist/pinned-write-types.d.ts +3 -0
  170. package/dist/pinned-write-types.d.ts.map +1 -1
  171. package/dist/pinned-write.d.ts.map +1 -1
  172. package/dist/pinned-write.js +41 -147
  173. package/dist/private-directory.d.ts.map +1 -1
  174. package/dist/private-directory.js +18 -4
  175. package/dist/private-producer-handoff-sync.d.ts +14 -0
  176. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  177. package/dist/private-producer-handoff-sync.js +114 -0
  178. package/dist/private-producer-handoff.d.ts +22 -4
  179. package/dist/private-producer-handoff.d.ts.map +1 -1
  180. package/dist/private-producer-handoff.js +140 -77
  181. package/dist/publish-copy-stage.d.ts +2 -1
  182. package/dist/publish-copy-stage.d.ts.map +1 -1
  183. package/dist/publish-copy-stage.js +16 -7
  184. package/dist/publish-file.d.ts +2 -2
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +58 -98
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +35 -44
  189. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  190. package/dist/replace-file-copy-fallback.js +28 -26
  191. package/dist/replace-file-copy-source.d.ts.map +1 -1
  192. package/dist/replace-file-copy-source.js +13 -22
  193. package/dist/replace-file-descriptor.d.ts.map +1 -1
  194. package/dist/replace-file-descriptor.js +10 -16
  195. package/dist/replace-file-temp-owner.d.ts +0 -7
  196. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  197. package/dist/replace-file-temp-owner.js +9 -60
  198. package/dist/replace-file.d.ts.map +1 -1
  199. package/dist/replace-file.js +9 -13
  200. package/dist/root-create-input.d.ts +2 -1
  201. package/dist/root-create-input.d.ts.map +1 -1
  202. package/dist/root-create-input.js +13 -4
  203. package/dist/root-directory-creation.d.ts +3 -3
  204. package/dist/root-directory-creation.d.ts.map +1 -1
  205. package/dist/root-directory-creation.js +15 -3
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +20 -3
  208. package/dist/root-file-final-admission.d.ts +1 -1
  209. package/dist/root-file-final-admission.d.ts.map +1 -1
  210. package/dist/root-file-final-admission.js +5 -2
  211. package/dist/root-file.d.ts.map +1 -1
  212. package/dist/root-file.js +3 -2
  213. package/dist/root-impl.d.ts.map +1 -1
  214. package/dist/root-impl.js +78 -27
  215. package/dist/root-move-noreplace.d.ts +2 -0
  216. package/dist/root-move-noreplace.d.ts.map +1 -1
  217. package/dist/root-move-noreplace.js +22 -13
  218. package/dist/root-options.d.ts +12 -4
  219. package/dist/root-options.d.ts.map +1 -1
  220. package/dist/root-path-stat.d.ts.map +1 -1
  221. package/dist/root-path-stat.js +59 -7
  222. package/dist/root-read-admission.d.ts.map +1 -1
  223. package/dist/root-read-admission.js +7 -2
  224. package/dist/root-remove.d.ts.map +1 -1
  225. package/dist/root-remove.js +15 -1
  226. package/dist/root-write-publication.js +1 -1
  227. package/dist/secret-file.d.ts.map +1 -1
  228. package/dist/secret-file.js +1 -0
  229. package/dist/secure-file-windows.d.ts +6 -0
  230. package/dist/secure-file-windows.d.ts.map +1 -1
  231. package/dist/secure-file-windows.js +34 -117
  232. package/dist/secure-file.js +2 -2
  233. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  234. package/dist/sidecar-lock-acquire.js +4 -6
  235. package/dist/sidecar-lock-handle.d.ts +3 -0
  236. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  237. package/dist/sidecar-lock-handle.js +6 -0
  238. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  239. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  240. package/dist/sidecar-lock-reclaim.js +11 -8
  241. package/dist/sidecar-lock-root.d.ts.map +1 -1
  242. package/dist/sidecar-lock-root.js +2 -1
  243. package/dist/sidecar-lock.d.ts.map +1 -1
  244. package/dist/sidecar-lock.js +3 -5
  245. package/dist/staged-directory.d.ts +2 -2
  246. package/dist/staged-directory.d.ts.map +1 -1
  247. package/dist/staged-directory.js +6 -6
  248. package/dist/staged-file-settlement.d.ts +17 -0
  249. package/dist/staged-file-settlement.d.ts.map +1 -0
  250. package/dist/staged-file-settlement.js +57 -0
  251. package/dist/strict-file-identity.d.ts +1 -1
  252. package/dist/strict-file-identity.d.ts.map +1 -1
  253. package/dist/strict-file-identity.js +9 -9
  254. package/dist/symlink-parents.d.ts.map +1 -1
  255. package/dist/symlink-parents.js +2 -27
  256. package/dist/temp-workspace-owner.js +4 -4
  257. package/dist/unicode-path.d.ts.map +1 -1
  258. package/dist/unicode-path.js +3 -0
  259. package/dist/walk.d.ts.map +1 -1
  260. package/dist/walk.js +4 -2
  261. package/dist/windows-owner.d.ts.map +1 -1
  262. package/dist/windows-owner.js +2 -1
  263. package/dist/windows-security-bridge.cs +336 -0
  264. package/dist/windows-security-bridge.ps1 +15 -0
  265. package/dist/windows-security-command.d.ts +26 -0
  266. package/dist/windows-security-command.d.ts.map +1 -0
  267. package/dist/windows-security-command.js +363 -0
  268. package/dist/windows-security-facts.d.ts +6 -0
  269. package/dist/windows-security-facts.d.ts.map +1 -0
  270. package/dist/windows-security-facts.js +108 -0
  271. package/dist/write-file-handle.d.ts +7 -0
  272. package/dist/write-file-handle.d.ts.map +1 -1
  273. package/dist/write-file-handle.js +23 -0
  274. package/dist/write-open-flags.d.ts.map +1 -1
  275. package/dist/write-open-flags.js +1 -8
  276. package/dist/write-queue.d.ts.map +1 -1
  277. package/dist/write-queue.js +1 -4
  278. package/docs/advanced.md +71 -2
  279. package/docs/archive.md +102 -39
  280. package/docs/atomic.md +29 -5
  281. package/docs/config.md +6 -2
  282. package/docs/contributing.md +48 -4
  283. package/docs/copy.md +2 -0
  284. package/docs/creation.md +132 -0
  285. package/docs/durability.md +59 -0
  286. package/docs/file-contents.md +68 -0
  287. package/docs/install.md +31 -7
  288. package/docs/json.md +5 -4
  289. package/docs/local-roots.md +2 -0
  290. package/docs/migrating-to-0.5.md +15 -6
  291. package/docs/migrating-to-0.6.md +9 -4
  292. package/docs/mutation-policy-proof.md +5 -3
  293. package/docs/native-helper.md +22 -9
  294. package/docs/native.md +47 -15
  295. package/docs/path.md +4 -4
  296. package/docs/permissions.md +37 -14
  297. package/docs/public-api.md +5 -0
  298. package/docs/quickstart.md +1 -1
  299. package/docs/reading.md +2 -2
  300. package/docs/regular-file.md +3 -0
  301. package/docs/root.md +43 -0
  302. package/docs/secret-file.md +11 -2
  303. package/docs/secure-file.md +9 -4
  304. package/docs/sidecar-lock.md +14 -5
  305. package/docs/staged-file.md +9 -3
  306. package/docs/store.md +3 -1
  307. package/docs/temp.md +4 -1
  308. package/docs/types.md +18 -2
  309. package/docs/walk.md +7 -0
  310. package/docs/writing.md +80 -7
  311. package/package.json +18 -15
@@ -7,7 +7,7 @@ import { readBoundedAsync } from "./bounded-read.js";
7
7
  import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
8
8
  import { formatErrorDetail } from "./error-detail.js";
9
9
  import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
10
- import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
10
+ import { resolveArchiveKind } from "./archive-kind.js";
11
11
  import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
12
12
  import { isGzipBuffer } from "./archive-gzip-tail.js";
13
13
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
@@ -139,11 +139,11 @@ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
139
139
  throw normalizeZipIntegrityError(error);
140
140
  }
141
141
  }
142
- async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
142
+ async function readTarEntry(archiveBuffer, entryPath, maxBytes, kind) {
143
143
  const seenPaths = new Set();
144
144
  let selected;
145
145
  const limits = resolveTarMeterLimits();
146
- await inspectTar({ archiveBuffer, limits, onMember(info) {
146
+ await inspectTar({ archiveBuffer, kind, limits, onMember(info) {
147
147
  const normalized = canonicalEntryPath(info.path);
148
148
  if (seenPaths.has(normalized)) {
149
149
  throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
@@ -159,7 +159,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
159
159
  }
160
160
  if (selected.size > maxBytes)
161
161
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
162
- if (!isGzipBuffer(archiveBuffer)) {
162
+ if (kind === "tar" && !isGzipBuffer(archiveBuffer)) {
163
163
  // Complete admission already validated this private snapshot through EOF.
164
164
  // Copy the admitted payload so the result cannot expose or mutate its input.
165
165
  const end = selected.offset + selected.size;
@@ -170,7 +170,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
170
170
  return Buffer.from(archiveBuffer.subarray(selected.offset, end));
171
171
  }
172
172
  let result;
173
- await replayTar({ archiveBuffer, limits, members: [selected], async consume(member, payload) {
173
+ await replayTar({ archiveBuffer, kind, limits, members: [selected], async consume(member, payload) {
174
174
  result = await readAdmittedTarPayload(payload, member.size);
175
175
  } });
176
176
  return result;
@@ -238,7 +238,6 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
238
238
  const native = getNativeBinding();
239
239
  if (native)
240
240
  return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, zipEntries);
241
- assertPortableArchiveKind(kind);
242
241
  return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, zipEntries)
243
- : await readTarEntry(buffer, requestedEntry, options.maxBytes);
242
+ : await readTarEntry(buffer, requestedEntry, options.maxBytes, kind);
244
243
  }
@@ -1,3 +1,4 @@
1
+ import type { ArchiveKind } from "./archive-kind.js";
1
2
  import type { TarMeterLimits } from "./archive-limits.js";
2
3
  import { type AdmittedTarMember } from "./archive-tar-wasm.js";
3
4
  /** Buffers are private immutable snapshots, just like the staged file route. */
@@ -11,6 +12,7 @@ type TarInput = {
11
12
  export declare function inspectTar(params: TarInput & {
12
13
  limits: TarMeterLimits;
13
14
  signal?: AbortSignal;
15
+ kind?: Exclude<ArchiveKind, "zip">;
14
16
  onMember?: (entry: AdmittedTarMember) => void;
15
17
  }): Promise<void>;
16
18
  /** Replay in physical order, retaining at most one decoded chunk. Every range
@@ -18,6 +20,7 @@ export declare function inspectTar(params: TarInput & {
18
20
  export declare function replayTar<T extends AdmittedTarMember>(params: TarInput & {
19
21
  limits: TarMeterLimits;
20
22
  signal?: AbortSignal;
23
+ kind?: Exclude<ArchiveKind, "zip">;
21
24
  members: readonly T[];
22
25
  consume(member: T, payload: AsyncIterable<Buffer>): Promise<void>;
23
26
  }): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGhF,gFAAgF;AAChF,KAAK,QAAQ,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAuDhH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;IAClD,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;2DAC2D;AAC3D,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG;IAC9E,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
1
+ {"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmC,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGhG,gFAAgF;AAChF,KAAK,QAAQ,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AA0EhH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;IAClD,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACjF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;2DAC2D;AAC3D,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG;IAC9E,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACjF,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
@@ -1,10 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import { Readable, Writable } from "node:stream";
3
- import { pipeline } from "node:stream/promises";
3
+ import { finished, pipeline } from "node:stream/promises";
4
4
  import { createGunzip } from "node:zlib";
5
5
  import { GzipInput, isGzipBuffer, validateGzipBufferTail, validateGzipContainerTail } from "./archive-gzip-tail.js";
6
6
  import { ArchiveFormatError } from "./archive-errors.js";
7
- import { TarParserStream } from "./archive-tar-wasm.js";
7
+ import { TarParserStream, TarWasmSession } from "./archive-tar-wasm.js";
8
8
  import { readFileWindowFully } from "./positional-read.js";
9
9
  function* bufferChunks(buffer) {
10
10
  for (let offset = 0; offset < buffer.length; offset += 65536)
@@ -23,45 +23,64 @@ async function gzipFile(filePath) {
23
23
  }
24
24
  async function withTarStream(params, consume) {
25
25
  const buffer = params.archiveBuffer;
26
- const gzip = buffer !== undefined ? isGzipBuffer(buffer) : await gzipFile(params.archivePath);
27
- const parser = new TarParserStream(params.limits, params.onMember);
28
- const input = buffer !== undefined
29
- ? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
30
- : fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
31
- // Match the WASM input window for both staged files and buffered reads.
32
- const decoder = gzip ? createGunzip({ chunkSize: 65536 }) : undefined;
33
- const gzipInput = decoder ? new GzipInput(decoder) : undefined;
34
- const destroy = (error) => {
35
- input.destroy(error);
36
- gzipInput?.destroy(error);
37
- decoder?.destroy(error);
38
- parser.destroy(error);
39
- };
40
- const pumps = decoder && gzipInput
41
- ? [pipeline(decoder, parser, { signal: params.signal }), pipeline(input, gzipInput, { signal: params.signal })]
42
- : [pipeline(input, parser, { signal: params.signal })];
43
- // Either pump tears down both routes; join every pump even after the first failure.
44
- const settled = Promise.all(pumps.map((pump) => pump.then(() => undefined, (cause) => {
45
- const error = cause instanceof Error ? cause : new Error(String(cause));
46
- destroy(error);
47
- return error;
48
- }))).then((errors) => errors.find((error) => error !== undefined));
26
+ const kind = params.kind ?? "tar";
27
+ const gzip = kind === "tar" && (buffer !== undefined ? isGzipBuffer(buffer) : await gzipFile(params.archivePath));
28
+ const session = new TarWasmSession(params.limits);
49
29
  try {
50
- const result = await consume(parser);
51
- const error = await settled;
52
- if (error)
53
- throw error;
54
- if (gzipInput) {
55
- if (buffer !== undefined)
56
- await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
57
- else
58
- await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
30
+ const parser = new TarParserStream(params.limits, params.onMember, session);
31
+ const input = buffer !== undefined
32
+ ? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
33
+ : fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
34
+ // Match the WASM input window for both staged files and buffered reads.
35
+ const decoder = gzip ? createGunzip({ chunkSize: 65536 }) : undefined;
36
+ const gzipInput = decoder ? new GzipInput(decoder) : undefined;
37
+ const destroy = (error) => {
38
+ input.destroy(error);
39
+ gzipInput?.destroy(error);
40
+ decoder?.destroy(error);
41
+ parser.destroy(error);
42
+ };
43
+ // A pipeline with an async-generator stage can settle before its file's
44
+ // asynchronous close. Join the actual close events before freeing WASM.
45
+ const closed = Promise.all([input, parser, ...(decoder ? [decoder] : []), ...(gzipInput ? [gzipInput] : [])].map(stream => {
46
+ const closing = new Promise(resolve => { stream.once("close", resolve); });
47
+ return finished(stream, { cleanup: true }).then(() => undefined, (cause) => cause instanceof Error ? cause : new Error(String(cause))).then(async (error) => { await closing; return error; });
48
+ }));
49
+ const pumps = decoder && gzipInput
50
+ ? [pipeline(decoder, parser, { signal: params.signal }), pipeline(input, gzipInput, { signal: params.signal })]
51
+ : kind !== "tar"
52
+ ? [pipeline(input, (source) => session.decode(source, kind, params.signal), parser, { signal: params.signal })]
53
+ : [pipeline(input, parser, { signal: params.signal })];
54
+ // Either pump tears down both routes; join every pump even after the first failure.
55
+ const settled = Promise.all(pumps.map((pump) => pump.then(() => undefined, (cause) => {
56
+ const error = cause instanceof Error ? cause : new Error(String(cause));
57
+ destroy(error);
58
+ return error;
59
+ }))).then((errors) => errors.find((error) => error !== undefined));
60
+ try {
61
+ const result = await consume(parser);
62
+ const error = await settled;
63
+ if (error)
64
+ throw error;
65
+ const closeError = (await closed).find(error => error !== undefined);
66
+ if (closeError)
67
+ throw closeError;
68
+ if (gzipInput) {
69
+ if (buffer !== undefined)
70
+ await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
71
+ else
72
+ await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
73
+ }
74
+ return result;
75
+ }
76
+ finally {
77
+ destroy();
78
+ await settled;
79
+ await closed;
59
80
  }
60
- return result;
61
81
  }
62
82
  finally {
63
- destroy();
64
- await settled;
83
+ session.dispose();
65
84
  }
66
85
  }
67
86
  export async function inspectTar(params) {
@@ -4,13 +4,25 @@ import type { TarEntryInfo } from "./archive-tar.js";
4
4
  export type AdmittedTarMember = TarEntryInfo & {
5
5
  offset: number;
6
6
  };
7
- /** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
8
- export declare class TarParserStream extends Transform {
9
- private readonly onMember?;
7
+ /** One bounded memory domain owns both admission and decompression. Its owner
8
+ * must join the decoder and parser before disposing this shared instance. */
9
+ export declare class TarWasmSession {
10
10
  private abi;
11
- constructor(limits: TarMeterLimits, onMember?: ((entry: AdmittedTarMember) => void) | undefined);
11
+ constructor(limits: TarMeterLimits);
12
12
  private bytes;
13
13
  private text;
14
+ parse(chunk: Buffer, onMember?: (entry: AdmittedTarMember) => void): void;
15
+ finish(): void;
16
+ private codecError;
17
+ decode(source: AsyncIterable<Buffer>, kind: "tar-zstd" | "tar-bzip2", signal?: AbortSignal): AsyncGenerator<Buffer>;
18
+ dispose(): void;
19
+ }
20
+ /** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
21
+ export declare class TarParserStream extends Transform {
22
+ private readonly onMember?;
23
+ private readonly session;
24
+ private readonly ownsSession;
25
+ constructor(limits: TarMeterLimits, onMember?: ((entry: AdmittedTarMember) => void) | undefined, session?: TarWasmSession);
14
26
  _transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
15
27
  _flush(callback: TransformCallback): void;
16
28
  _destroy(error: Error | null, callback: (error: Error | null) => void): void;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAyClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAqB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
1
+ {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAmDlE;6EAC6E;AAC7E,qBAAa,cAAc;IACzB,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAMjC;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIZ,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,IAAI,CAkBxE;IACD,MAAM,IAAI,IAAI,CAEb;IACD,OAAO,CAAC,UAAU;IAIX,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CA2DzH;IACD,OAAO,IAAI,IAAI,CAOd;CACF;AAED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAF9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAAE,OAAO,CAAC,EAAE,cAAc,EAI3H;IACQ,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAG/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAGjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAIpF;CACF"}
@@ -1,6 +1,7 @@
1
1
  import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { Transform } from "node:stream";
4
+ import { setImmediate } from "node:timers/promises";
4
5
  import { ArchiveFormatError } from "./archive-errors.js";
5
6
  // Node exposes WebAssembly without DOM globals; keep the private ABI types local.
6
7
  const wasm = globalThis.WebAssembly;
@@ -24,17 +25,14 @@ function parserError(message) {
24
25
  return mapped;
25
26
  return new ArchiveFormatError(message.replace(/^archive-header-invalid:/, "invalid TAR header:"));
26
27
  }
27
- /** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
28
- export class TarParserStream extends Transform {
29
- onMember;
28
+ /** One bounded memory domain owns both admission and decompression. Its owner
29
+ * must join the decoder and parser before disposing this shared instance. */
30
+ export class TarWasmSession {
30
31
  abi;
31
- constructor(limits, onMember) {
32
- super();
33
- this.onMember = onMember;
32
+ constructor(limits) {
34
33
  this.abi = instance();
35
34
  if (this.abi.init(limits.maxEntries, limits.maxMetaEntryBytes, limits.maxDecodedBytes, limits.maxManifestBytes, Number(process.platform === "win32")) !== 0) {
36
- this.abi.dispose();
37
- this.abi = undefined;
35
+ this.dispose();
38
36
  throw new RangeError("invalid TAR parser limits");
39
37
  }
40
38
  }
@@ -50,28 +48,133 @@ export class TarParserStream extends Transform {
50
48
  const abi = this.abi;
51
49
  return utf8Decoder.decode(this.bytes(abi.text_ptr(), abi.text_len()));
52
50
  }
53
- _transform(chunk, _encoding, callback) {
54
- try {
55
- const abi = this.abi;
56
- for (let offset = 0; offset < chunk.length; offset += 65536) {
57
- const length = Math.min(65536, chunk.length - offset);
58
- this.bytes(abi.input_ptr(), length).set(chunk.subarray(offset, offset + length));
59
- // A member event can stop before the end of this already-copied inbox.
60
- for (let consumed = 0; consumed < length;) {
61
- const used = abi.push(consumed, length - consumed);
62
- if (used < 0)
63
- throw parserError(this.text());
64
- if (used === 0 || used > length - consumed)
65
- throw new ArchiveFormatError("TAR WASM made no progress");
66
- consumed += used;
67
- const type = abi.member_type();
68
- if (type >= 0)
69
- this.onMember?.({
70
- path: this.text(), type: types.get(type) ?? "Unsupported", size: abi.member_size(),
71
- mode: abi.member_mode(), offset: abi.member_offset(),
72
- });
51
+ parse(chunk, onMember) {
52
+ const abi = this.abi;
53
+ for (let offset = 0; offset < chunk.length; offset += 65536) {
54
+ const length = Math.min(65536, chunk.length - offset);
55
+ this.bytes(abi.input_ptr(), length).set(chunk.subarray(offset, offset + length));
56
+ // A member event can stop before the end of this already-copied inbox.
57
+ for (let consumed = 0; consumed < length;) {
58
+ const used = abi.push(consumed, length - consumed);
59
+ if (used < 0)
60
+ throw parserError(this.text());
61
+ if (used === 0 || used > length - consumed)
62
+ throw new ArchiveFormatError("TAR WASM made no progress");
63
+ consumed += used;
64
+ const type = abi.member_type();
65
+ if (type >= 0)
66
+ onMember?.({
67
+ path: this.text(), type: types.get(type) ?? "Unsupported", size: abi.member_size(),
68
+ mode: abi.member_mode(), offset: abi.member_offset(),
69
+ });
70
+ }
71
+ }
72
+ }
73
+ finish() {
74
+ if (this.abi.finish() !== 0)
75
+ throw parserError(this.text());
76
+ }
77
+ codecError() {
78
+ const abi = this.abi;
79
+ return new ArchiveFormatError(utf8Decoder.decode(this.bytes(abi.codec_error_ptr(), abi.codec_error_len())));
80
+ }
81
+ async *decode(source, kind, signal) {
82
+ const abi = this.abi;
83
+ signal?.throwIfAborted();
84
+ if (abi.codec_init(kind === "tar-bzip2" ? 1 : 2) !== 0)
85
+ throw this.codecError();
86
+ let work = 0;
87
+ let calls = 0;
88
+ let unconsumedBoundary = false;
89
+ const step = (input) => {
90
+ signal?.throwIfAborted();
91
+ this.bytes(abi.codec_input_ptr(), input.length).set(input);
92
+ const result = abi.codec_push(input.length);
93
+ if (result < 0)
94
+ throw this.codecError();
95
+ const consumed = abi.codec_consumed(), produced = abi.codec_produced();
96
+ if (!Number.isInteger(consumed) || consumed < 0 || consumed > input.length ||
97
+ !Number.isInteger(produced) || produced < 0 || produced > 65536 ||
98
+ (result !== 0 && result !== 1) ||
99
+ (input.length > 0 && consumed === 0 && produced === 0 && (result !== 1 || unconsumedBoundary))) {
100
+ throw new ArchiveFormatError("compressed TAR WASM made no progress");
101
+ }
102
+ if (input.length > 0)
103
+ unconsumedBoundary = consumed === 0 && produced === 0;
104
+ work += consumed + produced;
105
+ calls++;
106
+ // The parser can grow this same memory, and the next codec call reuses it.
107
+ return { consumed, output: produced ? Buffer.from(this.bytes(abi.codec_output_ptr(), produced)) : undefined };
108
+ };
109
+ const empty = Buffer.alloc(0);
110
+ // Yield even for output-free skippable frames and arbitrarily small members.
111
+ const yieldIfNeeded = async () => {
112
+ if (work >= 256 * 1024 || calls >= 32) {
113
+ work = 0;
114
+ calls = 0;
115
+ await setImmediate(undefined, { signal });
116
+ }
117
+ signal?.throwIfAborted();
118
+ };
119
+ for await (const chunk of source) {
120
+ for (let offset = 0; offset < chunk.length;) {
121
+ const { consumed, output } = step(chunk.subarray(offset, offset + 65536));
122
+ offset += consumed;
123
+ if (output)
124
+ yield output;
125
+ await yieldIfNeeded();
126
+ // A full output window can leave more decoded bytes with no new input.
127
+ if (output) {
128
+ for (;;) {
129
+ const drained = step(empty);
130
+ if (drained.output)
131
+ yield drained.output;
132
+ await yieldIfNeeded();
133
+ if (!drained.output)
134
+ break;
135
+ }
73
136
  }
74
137
  }
138
+ }
139
+ // Physical EOF, rather than a selected range or TAR EOF, ends the decoder.
140
+ for (;;) {
141
+ const drained = step(empty);
142
+ if (drained.output)
143
+ yield drained.output;
144
+ await yieldIfNeeded();
145
+ if (!drained.output)
146
+ break;
147
+ }
148
+ if (abi.codec_finish() !== 0)
149
+ throw this.codecError();
150
+ }
151
+ dispose() {
152
+ const abi = this.abi;
153
+ this.abi = undefined;
154
+ if (abi) {
155
+ try {
156
+ abi.codec_dispose();
157
+ }
158
+ finally {
159
+ abi.dispose();
160
+ }
161
+ }
162
+ }
163
+ }
164
+ /** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
165
+ export class TarParserStream extends Transform {
166
+ onMember;
167
+ session;
168
+ ownsSession;
169
+ constructor(limits, onMember, session) {
170
+ super();
171
+ this.onMember = onMember;
172
+ this.session = session ?? new TarWasmSession(limits);
173
+ this.ownsSession = session === undefined;
174
+ }
175
+ _transform(chunk, _encoding, callback) {
176
+ try {
177
+ this.session.parse(chunk, this.onMember);
75
178
  callback(null, chunk);
76
179
  }
77
180
  catch (error) {
@@ -80,8 +183,7 @@ export class TarParserStream extends Transform {
80
183
  }
81
184
  _flush(callback) {
82
185
  try {
83
- if (this.abi.finish() !== 0)
84
- throw parserError(this.text());
186
+ this.session.finish();
85
187
  callback();
86
188
  }
87
189
  catch (error) {
@@ -90,14 +192,12 @@ export class TarParserStream extends Transform {
90
192
  }
91
193
  _destroy(error, callback) {
92
194
  try {
93
- this.abi?.dispose();
195
+ if (this.ownsSession)
196
+ this.session.dispose();
94
197
  }
95
198
  catch (cause) {
96
199
  error ??= cause instanceof Error ? cause : new Error(String(cause));
97
200
  }
98
- finally {
99
- this.abi = undefined;
100
- }
101
201
  callback(error);
102
202
  }
103
203
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-count.d.ts","sourceRoot":"","sources":["../src/archive-zip-count.ts"],"names":[],"mappings":"AA4KA,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F"}
1
+ {"version":3,"file":"archive-zip-count.d.ts","sourceRoot":"","sources":["../src/archive-zip-count.ts"],"names":[],"mappings":"AA8LA,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F"}
@@ -1,5 +1,6 @@
1
1
  // Conservative public count hint; strict admission must not rely on its fallback.
2
2
  const ZIP_EOCD_SIGNATURE = 0x06054b50;
3
+ const ZIP_EOCD_SIGNATURE_BYTES = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
3
4
  const ZIP64_EOCD_SIGNATURE = 0x06064b50;
4
5
  const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
5
6
  const ZIP_EOCD_MIN_BYTES = 22;
@@ -38,7 +39,26 @@ function findZipEndOfCentralDirectory(buffer) {
38
39
  return -1;
39
40
  }
40
41
  const minOffset = Math.max(0, buffer.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_EOCD_MAX_COMMENT_BYTES);
41
- for (let offset = buffer.byteLength - ZIP_EOCD_MIN_BYTES; offset >= minOffset; offset -= 1) {
42
+ const lastOffset = buffer.byteLength - ZIP_EOCD_MIN_BYTES;
43
+ if (buffer.readUInt32LE(lastOffset) === ZIP_EOCD_SIGNATURE &&
44
+ buffer.readUInt16LE(lastOffset + ZIP_EOCD_COMMENT_LENGTH_OFFSET) === 0) {
45
+ return lastOffset;
46
+ }
47
+ const tail = buffer.subarray(minOffset);
48
+ let from = lastOffset - minOffset - 1;
49
+ // Keep the latest valid record semantics of this count hint. Bound native
50
+ // searches so dense false signatures retain the byte scan's cost.
51
+ for (let candidates = 0; candidates < 16 && from >= 0; candidates += 1) {
52
+ const found = tail.lastIndexOf(ZIP_EOCD_SIGNATURE_BYTES, from);
53
+ if (found < 0)
54
+ return -1;
55
+ const offset = minOffset + found;
56
+ const commentLength = buffer.readUInt16LE(offset + ZIP_EOCD_COMMENT_LENGTH_OFFSET);
57
+ if (offset + ZIP_EOCD_MIN_BYTES + commentLength === buffer.byteLength)
58
+ return offset;
59
+ from = found - 1;
60
+ }
61
+ for (let offset = minOffset + from; offset >= minOffset; offset -= 1) {
42
62
  if (buffer.readUInt32LE(offset) !== ZIP_EOCD_SIGNATURE) {
43
63
  continue;
44
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AA4IF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAqET"}
1
+ {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAkKF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAqET"}
@@ -19,7 +19,8 @@ function* read(offset, length, bound) {
19
19
  zipFormat("truncated record");
20
20
  return bytes;
21
21
  }
22
- function endOffset(tail) {
22
+ const END_SIGNATURE = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
23
+ function denseEndOffset(tail) {
23
24
  let found = -1;
24
25
  for (let offset = tail.length - 22; offset >= 0; offset--) {
25
26
  if (tail.readUInt32LE(offset) === 0x06054b50 && offset + 22 + tail.readUInt16LE(offset + 20) === tail.length) {
@@ -32,6 +33,27 @@ function endOffset(tail) {
32
33
  zipFormat("end record missing");
33
34
  return found;
34
35
  }
36
+ function endOffset(tail) {
37
+ let found = -1;
38
+ let from = 0;
39
+ // Sparse signatures use native search; dense attacker-controlled markers
40
+ // retain the bounded byte scan instead of one native call per marker.
41
+ for (let candidates = 0; candidates < 16; candidates++) {
42
+ const offset = tail.indexOf(END_SIGNATURE, from);
43
+ if (offset < 0 || offset > tail.length - 22) {
44
+ if (found < 0)
45
+ zipFormat("end record missing");
46
+ return found;
47
+ }
48
+ if (offset + 22 + tail.readUInt16LE(offset + 20) === tail.length) {
49
+ if (found !== -1)
50
+ zipFormat("ambiguous end records");
51
+ found = offset;
52
+ }
53
+ from = offset + 1;
54
+ }
55
+ return denseEndOffset(tail);
56
+ }
35
57
  function* zip64End(locator, locatorAt, size) {
36
58
  const advertised = zipUInt64(locator, 8);
37
59
  // Seek the advertised record, or the fixed ordinary record for a prefixed
@@ -1,7 +1,9 @@
1
1
  import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
2
+ import { type ZipEntry } from "./archive-zip-entry.js";
2
3
  export type ZipArchiveWithFiles = {
3
4
  files: Record<string, unknown>;
4
5
  };
6
+ export declare function admittedZipEntries(archive: ZipArchiveWithFiles): readonly ZipEntry[];
5
7
  /** Internal: the caller has admitted these unchanged bytes and their metadata. */
6
8
  export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, admitted: ZipDirectoryEntry[]): Promise<ZipArchiveWithFiles>;
7
9
  //# sourceMappingURL=archive-zip-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AA2BrE,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAkF9B"}
1
+ {"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGjF,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAGrE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,QAAQ,EAAE,CAEpF;AA4BD,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAqF9B"}
@@ -2,6 +2,10 @@ import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
2
  import { validateArchiveEntryPath } from "./archive-entry.js";
3
3
  import { registerAdmittedZipEntry } from "./archive-zip-entry.js";
4
4
  import { zipPathKey } from "./archive-zip-names.js";
5
+ const orderedEntries = new WeakMap();
6
+ export function admittedZipEntries(archive) {
7
+ return orderedEntries.get(archive) ?? disagreement();
8
+ }
5
9
  function disagreement() {
6
10
  throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
7
11
  }
@@ -106,6 +110,7 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
106
110
  if (remaining.size || inserted.size)
107
111
  disagreement();
108
112
  const files = Object.create(null);
113
+ const entries = [];
109
114
  for (const [name, entry, physical] of normalized) {
110
115
  const appendSlash = physical.kind === "directory" && !name.endsWith("/");
111
116
  const normalizedName = appendSlash ? `${name}/` : name;
@@ -116,8 +121,10 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
116
121
  }
117
122
  files[normalizedName] = entry;
118
123
  registerAdmittedZipEntry(entry, physical);
124
+ entries.push(entry);
119
125
  }
120
126
  archive.files = files;
127
+ orderedEntries.set(archive, entries);
121
128
  return archive;
122
129
  }
123
130
  async function importOptionalJsZip() {
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AAUD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAoBD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAuEzF"}
1
+ {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AAUD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAsBD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAyEzF"}
@@ -50,9 +50,12 @@ export function zipPathKey(name) {
50
50
  function originalName(name, rawName, flags) {
51
51
  // ASCII path syntax is encoding-independent. Do not reinterpret legacy bytes
52
52
  // as UTF-8 (native ZIP uses CP437); the selected decoder still validates its name.
53
- validateArchiveEntryPath(rawName.replace(/[\x80-\xff]/g, "_"));
53
+ const asciiName = rawName.replace(/[\x80-\xff]/g, "_");
54
+ validateArchiveEntryPath(asciiName);
54
55
  if (!(flags & 0x800))
55
56
  return undefined;
57
+ if (asciiName === rawName && !types.isSharedArrayBuffer(name.buffer))
58
+ return rawName;
56
59
  const decoded = utf8(name);
57
60
  validateArchiveEntryPath(decoded);
58
61
  return decoded;
@@ -102,7 +105,9 @@ export function admitZipNames(params) {
102
105
  interpretations.some((value) => /[/\\]$/.test(value) !== directory)) {
103
106
  zipFormat("conflicting terminal directory markers");
104
107
  }
105
- const interpretationKey = interpretations.length ? zipPathKey(interpretations[0]) : undefined;
108
+ const firstInterpretation = interpretations[0];
109
+ const interpretationKey = firstInterpretation === undefined ? undefined
110
+ : firstInterpretation === centralRaw ? centralKey : zipPathKey(firstInterpretation);
106
111
  if (interpretations.some((value) => value !== interpretations[0] && zipPathKey(value) !== interpretationKey)) {
107
112
  zipFormat("conflicting Unicode name interpretations");
108
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA6DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAmNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDjF"}