@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.
- package/CHANGELOG.md +86 -0
- package/README.md +36 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +2 -8
- package/dist/advanced.d.ts +3 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +22 -23
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +4 -4
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +1 -0
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-count.d.ts.map +1 -1
- package/dist/archive-zip-count.js +21 -1
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +23 -1
- package/dist/archive-zip-loader.d.ts +2 -0
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +7 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +7 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +14 -9
- package/dist/byte-view.d.ts +3 -0
- package/dist/byte-view.d.ts.map +1 -0
- package/dist/byte-view.js +13 -0
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +6 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +70 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +7 -7
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +123 -0
- package/dist/file-cleanup.d.ts +20 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +81 -0
- package/dist/file-contents.d.ts +6 -0
- package/dist/file-contents.d.ts.map +1 -0
- package/dist/file-contents.js +40 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +16 -4
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
- package/dist/file-lock-sync-root-acquire.js +3 -0
- package/dist/file-lock-sync-root-held.d.ts +1 -2
- package/dist/file-lock-sync-root-held.d.ts.map +1 -1
- package/dist/file-lock-sync-root-held.js +7 -5
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
- package/dist/file-lock-sync-stale-admission.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -11
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/file-store.js +3 -3
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +27 -19
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +2 -5
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +2 -6
- package/dist/json-durable-queue-paths.d.ts.map +1 -1
- package/dist/json-durable-queue-paths.js +2 -24
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +10 -9
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -75
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +19 -21
- package/dist/move-path-cleanup.d.ts +5 -19
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +57 -21
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +63 -40
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +5 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +90 -40
- package/dist/native.js +2 -2
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +11 -2
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -17
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +35 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +31 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +186 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +41 -147
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts +2 -2
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +58 -98
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +35 -44
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +28 -26
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +13 -22
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +10 -16
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +9 -60
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +9 -13
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +20 -3
- package/dist/root-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.d.ts.map +1 -1
- package/dist/root-file-final-admission.js +5 -2
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +78 -27
- package/dist/root-move-noreplace.d.ts +2 -0
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-read-admission.d.ts.map +1 -1
- package/dist/root-read-admission.js +7 -2
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +15 -1
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -6
- package/dist/sidecar-lock-handle.d.ts +3 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +6 -0
- package/dist/sidecar-lock-reclaim.d.ts +1 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +11 -8
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +3 -5
- package/dist/staged-directory.d.ts +2 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +9 -9
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +2 -27
- package/dist/temp-workspace-owner.js +4 -4
- package/dist/unicode-path.d.ts.map +1 -1
- package/dist/unicode-path.js +3 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +4 -2
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +23 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -8
- package/dist/write-queue.d.ts.map +1 -1
- package/dist/write-queue.js +1 -4
- package/docs/advanced.md +71 -2
- package/docs/archive.md +102 -39
- package/docs/atomic.md +29 -5
- package/docs/config.md +6 -2
- package/docs/contributing.md +48 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +132 -0
- package/docs/durability.md +59 -0
- package/docs/file-contents.md +68 -0
- package/docs/install.md +31 -7
- package/docs/json.md +5 -4
- package/docs/local-roots.md +2 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/mutation-policy-proof.md +5 -3
- package/docs/native-helper.md +22 -9
- package/docs/native.md +47 -15
- package/docs/path.md +4 -4
- package/docs/permissions.md +37 -14
- package/docs/public-api.md +5 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +2 -2
- package/docs/regular-file.md +3 -0
- package/docs/root.md +43 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +14 -5
- package/docs/staged-file.md +9 -3
- package/docs/store.md +3 -1
- package/docs/temp.md +4 -1
- package/docs/types.md +18 -2
- package/docs/walk.md +7 -0
- package/docs/writing.md +80 -7
- package/package.json +18 -15
package/dist/archive-read.js
CHANGED
|
@@ -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 {
|
|
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,
|
|
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
|
|
27
|
-
const
|
|
28
|
-
const
|
|
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
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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;
|
|
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"}
|
package/dist/archive-tar-wasm.js
CHANGED
|
@@ -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
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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.
|
|
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":"
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/dist/archive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"
|
|
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"}
|