@openclaw/fs-safe 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -0
- package/dist/archive-gzip-tail.d.ts +18 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -0
- package/dist/archive-gzip-tail.js +74 -0
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +18 -60
- package/dist/archive-tar-extract.d.ts +11 -0
- package/dist/archive-tar-extract.d.ts.map +1 -0
- package/dist/archive-tar-extract.js +49 -0
- package/dist/archive-tar-stream.d.ts +18 -0
- package/dist/archive-tar-stream.d.ts.map +1 -0
- package/dist/archive-tar-stream.js +95 -0
- package/dist/archive-tar-wasm.d.ts +18 -0
- package/dist/archive-tar-wasm.d.ts.map +1 -0
- package/dist/archive-tar-wasm.js +102 -0
- package/dist/archive-tar.d.ts +0 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +0 -22
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +2 -109
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -2
- package/dist/directory-mode-owner.d.ts.map +1 -1
- package/dist/directory-mode-owner.js +3 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +2 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -0
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -0
- package/docs/archive.md +66 -61
- package/docs/contributing.md +35 -1
- package/docs/install.md +1 -1
- package/docs/native-helper.md +5 -0
- package/docs/native.md +16 -10
- package/docs/secret-file.md +5 -0
- package/package.json +19 -16
- package/dist/archive-tar-admission.d.ts +0 -7
- package/dist/archive-tar-admission.d.ts.map +0 -1
- package/dist/archive-tar-admission.js +0 -43
- package/dist/archive-tar-gnu.d.ts +0 -2
- package/dist/archive-tar-gnu.d.ts.map +0 -1
- package/dist/archive-tar-gnu.js +0 -20
- package/dist/archive-tar-header.d.ts +0 -8
- package/dist/archive-tar-header.d.ts.map +0 -1
- package/dist/archive-tar-header.js +0 -47
- package/dist/archive-tar-meta.d.ts +0 -34
- package/dist/archive-tar-meta.d.ts.map +0 -1
- package/dist/archive-tar-meta.js +0 -277
- package/dist/archive-tar-pax.d.ts +0 -8
- package/dist/archive-tar-pax.d.ts.map +0 -1
- package/dist/archive-tar-pax.js +0 -100
- package/dist/archive-tar-runtime.d.ts +0 -49
- package/dist/archive-tar-runtime.d.ts.map +0 -1
- package/dist/archive-tar-runtime.js +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2 - 2026-09-05
|
|
4
|
+
|
|
5
|
+
**Highlights:** TAR/gzip extraction now shares one parser across native and fallback modes, improving compatibility with system-tar output while keeping admission and publication guarded.
|
|
6
|
+
|
|
7
|
+
- Reject reserved archive device names and ignored-space aliases on Windows while preserving ordinary POSIX members, and guard secret reads with the documented `device-path` rejection. Thanks @SebTardif.
|
|
8
|
+
- Unify native and guarded JavaScript TAR admission on one Rust core, bundle its WASM build, and accept strict UTF-8/newline PAX paths without a runtime `tar` dependency; retain bounded framing, raw-field validation, and guarded publication.
|
|
9
|
+
- Accept bounded all-zero gzip container padding from system-tar stdout after validated member trailers, and reject nonzero data hidden after padding on both native and guarded JavaScript TAR routes.
|
|
10
|
+
- Fix `replaceFileAtomic({ dirMode })` rejecting a raw `fs.stat` mode: directory modes are masked to permission bits (`0o7777`) before application and verification, matching chmod semantics; 0.8.0 regressed this input tolerance with `directory final mode could not be verified`.
|
|
11
|
+
- Standardize malformed TAR mode fields on the native zero fallback while preserving ordinary octal, absent, zero, and safe GNU binary modes.
|
|
12
|
+
- Refresh the native binding build tool to `@napi-rs/cli` 3.9.0 and the test/coverage toolchain to Vitest 5.0.0. Thanks @dependabot.
|
|
13
|
+
|
|
3
14
|
## 0.8.1 - 2026-09-04
|
|
4
15
|
|
|
5
16
|
- Add `retainOnExit` to sidecar lock acquisition so deliberately retained ownership records (for example fail-closed build locks) survive natural process exit; default process-exit release behavior is unchanged.
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ This is a **library-level guardrail**, not OS-level isolation. It does not repla
|
|
|
57
57
|
pnpm add @openclaw/fs-safe
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and non-archive fallback-capable operations work in `auto` or `off`. Native-only features remain unavailable, and operations needing the binding in `require` mode fail with `helper-unavailable`.
|
|
60
|
+
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and non-archive fallback-capable operations work in `auto` or `off`. Native-only features remain unavailable, and operations needing the binding in `require` mode fail with `helper-unavailable`. TAR/gzip fallback uses the bundled WASM build of the same Rust parser as native and works with optional dependencies omitted. ZIP fallback still needs optional `jszip`. See the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
61
61
|
|
|
62
62
|
The package installs one prebuilt native binding for the current supported target. It
|
|
63
63
|
supplies fd-relative and atomic no-replace primitives that Node does not expose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"AAMA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,IAAI,CAoEN;AAGD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1F;AAED,wBAAgB,8BAA8B,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAgBlG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAWT"}
|
package/dist/archive-entry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
3
|
+
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
3
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
4
5
|
import { resolveSafeBaseDir } from "./path.js";
|
|
5
6
|
export function isWindowsDrivePath(value) {
|
|
@@ -25,6 +26,10 @@ export function validateArchiveEntryPath(entryPath, params) {
|
|
|
25
26
|
slashNormalized.split("/").some((segment) => segment.includes(":"))) {
|
|
26
27
|
throw new ArchiveSecurityError("entry-path", `archive entry uses a Windows alternate data stream path: ${formatErrorDetail(entryPath)}`);
|
|
27
28
|
}
|
|
29
|
+
if (process.platform === "win32" &&
|
|
30
|
+
slashNormalized.split("/").some((segment) => isWindowsReservedDeviceName(segment))) {
|
|
31
|
+
throw new ArchiveSecurityError("entry-path", `archive entry uses a reserved device path: ${formatErrorDetail(entryPath)}`);
|
|
32
|
+
}
|
|
28
33
|
const normalized = path.posix.normalize(slashNormalized);
|
|
29
34
|
if (normalized.split("/").some((segment) => Math.max(Buffer.byteLength(segment.normalize("NFC")), Buffer.byteLength(segment.normalize("NFD"))) > 255)) {
|
|
30
35
|
throw new ArchiveSecurityError("entry-path", `archive entry has an overlong path component: ${formatErrorDetail(entryPath)}`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
import type { Gunzip } from "node:zlib";
|
|
3
|
+
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
4
|
+
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
5
|
+
export declare class GzipInput extends Writable {
|
|
6
|
+
private readonly decoder;
|
|
7
|
+
private position;
|
|
8
|
+
private firstUnused;
|
|
9
|
+
constructor(decoder: Gunzip);
|
|
10
|
+
get tailOffset(): number;
|
|
11
|
+
_write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
|
|
12
|
+
_final(callback: (error?: Error | null) => void): void;
|
|
13
|
+
_destroy(error: Error | null, callback: (error: Error | null) => void): void;
|
|
14
|
+
}
|
|
15
|
+
/** Check the immutable staged suffix from the first unused physical byte,
|
|
16
|
+
* including later chunks the decoder may have consumed, before returning. */
|
|
17
|
+
export declare function validateGzipContainerTail(filePath: string, consumed: number, signal?: AbortSignal): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=archive-gzip-tail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBvH"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
4
|
+
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
5
|
+
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
6
|
+
export class GzipInput extends Writable {
|
|
7
|
+
decoder;
|
|
8
|
+
position = 0;
|
|
9
|
+
firstUnused;
|
|
10
|
+
constructor(decoder) {
|
|
11
|
+
super({ highWaterMark: 65536 });
|
|
12
|
+
this.decoder = decoder;
|
|
13
|
+
}
|
|
14
|
+
get tailOffset() {
|
|
15
|
+
return this.firstUnused ?? this.position;
|
|
16
|
+
}
|
|
17
|
+
_write(chunk, _encoding, callback) {
|
|
18
|
+
const before = this.decoder.bytesWritten;
|
|
19
|
+
this.decoder.write(chunk, (error) => {
|
|
20
|
+
if (error) {
|
|
21
|
+
callback(error);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const used = this.decoder.bytesWritten - before;
|
|
25
|
+
if (!Number.isSafeInteger(used) || used < 0 || used > chunk.length ||
|
|
26
|
+
!Number.isSafeInteger(this.position + chunk.length)) {
|
|
27
|
+
callback(new ArchiveFormatError("invalid gzip consumed-input boundary"));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (used < chunk.length)
|
|
31
|
+
this.firstUnused ??= this.position + used;
|
|
32
|
+
this.position += chunk.length;
|
|
33
|
+
callback();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
_final(callback) {
|
|
37
|
+
this.decoder.end(callback);
|
|
38
|
+
}
|
|
39
|
+
_destroy(error, callback) {
|
|
40
|
+
if (error || !this.writableFinished)
|
|
41
|
+
this.decoder.destroy(error ?? undefined);
|
|
42
|
+
callback(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Check the immutable staged suffix from the first unused physical byte,
|
|
46
|
+
* including later chunks the decoder may have consumed, before returning. */
|
|
47
|
+
export async function validateGzipContainerTail(filePath, consumed, signal) {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
const handle = await fs.open(filePath, "r");
|
|
50
|
+
try {
|
|
51
|
+
const { size } = await handle.stat();
|
|
52
|
+
if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
|
|
53
|
+
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
54
|
+
}
|
|
55
|
+
if (consumed === size)
|
|
56
|
+
return;
|
|
57
|
+
const buffer = Buffer.allocUnsafe(65536);
|
|
58
|
+
let position = consumed;
|
|
59
|
+
while (position < size) {
|
|
60
|
+
signal?.throwIfAborted();
|
|
61
|
+
const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, size - position), position);
|
|
62
|
+
signal?.throwIfAborted();
|
|
63
|
+
if (bytesRead === 0)
|
|
64
|
+
throw new ArchiveFormatError("truncated gzip container padding");
|
|
65
|
+
if (buffer.subarray(0, bytesRead).some((byte) => byte !== 0)) {
|
|
66
|
+
throw new ArchiveFormatError("nonzero gzip container padding");
|
|
67
|
+
}
|
|
68
|
+
position += bytesRead;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await handle.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkLzE,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAuFjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -5,10 +5,8 @@ import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage,
|
|
|
5
5
|
import { formatErrorDetail } from "./error-detail.js";
|
|
6
6
|
import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
7
7
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
8
|
-
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP,
|
|
9
|
-
import {
|
|
10
|
-
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
11
|
-
import { importOptionalTar, NODE_TAR_RATIO_DISABLED, normalizeTarParserError } from "./archive-tar-runtime.js";
|
|
8
|
+
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
|
|
9
|
+
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
12
10
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
13
11
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
14
12
|
import { FsSafeError } from "./errors.js";
|
|
@@ -135,70 +133,30 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
|
|
|
135
133
|
return await readStreamBounded(stream.pipe(integrity), maxBytes);
|
|
136
134
|
}
|
|
137
135
|
async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
138
|
-
const tar = await importOptionalTar();
|
|
139
136
|
const seenPaths = new Set();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
onMember(info) {
|
|
137
|
+
let selected;
|
|
138
|
+
const limits = resolveTarMeterLimits();
|
|
139
|
+
await inspectTar({ archivePath, limits, onMember(info) {
|
|
144
140
|
const normalized = canonicalEntryPath(info.path);
|
|
145
141
|
if (seenPaths.has(normalized)) {
|
|
146
142
|
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
147
143
|
}
|
|
148
144
|
seenPaths.add(normalized);
|
|
149
|
-
if (normalized === entryPath
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
let matched;
|
|
155
|
-
let entryError;
|
|
156
|
-
try {
|
|
157
|
-
await tar.t({
|
|
158
|
-
file: archivePath,
|
|
159
|
-
strict: true,
|
|
160
|
-
maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
|
|
161
|
-
maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
|
|
162
|
-
onReadEntry(entry) {
|
|
163
|
-
const info = readTarEntryInfo(entry);
|
|
164
|
-
let normalized;
|
|
165
|
-
try {
|
|
166
|
-
normalized = canonicalEntryPath(info.path);
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
// Throws escaping node-tar's callback do not reject its promise.
|
|
170
|
-
entryError ??= error instanceof Error ? error : new Error(String(error));
|
|
171
|
-
entry.resume();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (normalized !== entryPath) {
|
|
175
|
-
entry.resume();
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (info.type !== "File" && info.type !== "OldFile" && info.type !== "ContiguousFile") {
|
|
179
|
-
entryError ??= new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
180
|
-
entry.resume();
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (info.size > maxBytes) {
|
|
184
|
-
entryError ??= new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
185
|
-
entry.resume();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
matched = readStreamBounded(entry, maxBytes);
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
throw normalizeTarParserError(error);
|
|
194
|
-
}
|
|
195
|
-
if (entryError) {
|
|
196
|
-
throw entryError;
|
|
197
|
-
}
|
|
198
|
-
if (!matched) {
|
|
145
|
+
if (normalized === entryPath)
|
|
146
|
+
selected = info;
|
|
147
|
+
} });
|
|
148
|
+
if (!selected)
|
|
199
149
|
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
150
|
+
if (!["File", "OldFile", "ContiguousFile"].includes(selected.type)) {
|
|
151
|
+
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
200
152
|
}
|
|
201
|
-
|
|
153
|
+
if (selected.size > maxBytes)
|
|
154
|
+
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
155
|
+
let result;
|
|
156
|
+
await replayTar({ archivePath, limits, members: [selected], async consume(_member, payload) {
|
|
157
|
+
result = await readStreamBounded(payload, maxBytes);
|
|
158
|
+
} });
|
|
159
|
+
return result;
|
|
202
160
|
}
|
|
203
161
|
export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
204
162
|
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
2
|
+
import type { ResolvedArchiveExtractLimits, TarMeterLimits } from "./archive-limits.js";
|
|
3
|
+
import type { ExtractArchiveOptions } from "./archive-options.js";
|
|
4
|
+
export declare function extractWasmTar(params: {
|
|
5
|
+
archivePath: string;
|
|
6
|
+
options: ExtractArchiveOptions;
|
|
7
|
+
limits: ResolvedArchiveExtractLimits;
|
|
8
|
+
tarLimits: TarMeterLimits;
|
|
9
|
+
deadline: ExtractionDeadline;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=archive-tar-extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-extract.d.ts","sourceRoot":"","sources":["../src/archive-tar-extract.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,4BAA4B,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAExF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAQlE,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,qBAAqB,CAAC;IAAC,MAAM,EAAE,4BAA4B,CAAC;IAC1F,SAAS,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAC;CACzD,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { stripArchivePath } from "./archive-entry.js";
|
|
5
|
+
import {} from "./archive-deadline.js";
|
|
6
|
+
import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
|
|
7
|
+
import { resolveArchiveEntryMode } from "./archive-policy.js";
|
|
8
|
+
import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination } from "./archive-staging.js";
|
|
9
|
+
import { createTarEntryPreflightChecker } from "./archive-tar.js";
|
|
10
|
+
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
11
|
+
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
12
|
+
export async function extractWasmTar(params) {
|
|
13
|
+
const { options, deadline, tarLimits } = params;
|
|
14
|
+
const manifest = [];
|
|
15
|
+
await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
|
|
16
|
+
onMember: (entry) => { manifest.push(entry); } });
|
|
17
|
+
deadline.check();
|
|
18
|
+
const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
|
|
19
|
+
await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
|
|
20
|
+
const stagingDir = await fs.realpath(stagingPath);
|
|
21
|
+
const check = createTarEntryPreflightChecker({ rootDir: destinationRealDir,
|
|
22
|
+
stripComponents: options.stripComponents, limits: params.limits,
|
|
23
|
+
entryFilter: options.entryFilter, onFiltered: options.onFiltered });
|
|
24
|
+
const strip = Math.max(0, Math.floor(options.stripComponents ?? 0));
|
|
25
|
+
const accepted = manifest.filter((entry) => { deadline.check(); return check(entry); }).map((entry) => {
|
|
26
|
+
const kind = entry.type === "Directory" || entry.type === "GNUDumpDir" ? "directory" : "file";
|
|
27
|
+
return { ...entry, path: stripArchivePath(entry.path, strip), kind,
|
|
28
|
+
mode: resolveArchiveEntryMode({ kind, archivedMode: entry.mode, policy: options.entryModes }) };
|
|
29
|
+
});
|
|
30
|
+
await replayTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal, members: accepted,
|
|
31
|
+
async consume(member, payload) {
|
|
32
|
+
deadline.check();
|
|
33
|
+
await preparePrivateArchiveOutputPath({ destinationDir: stagingDir, destinationRealDir: stagingDir,
|
|
34
|
+
relPath: member.path, outPath: path.join(stagingDir, member.path), originalPath: member.path,
|
|
35
|
+
isDirectory: member.kind === "directory", deadline });
|
|
36
|
+
if (member.kind === "file") {
|
|
37
|
+
await runPinnedWriteHelper({ rootPath: stagingDir, relativeParentPath: path.posix.dirname(member.path),
|
|
38
|
+
basename: path.posix.basename(member.path), mkdir: false, mode: 0o600, overwrite: false,
|
|
39
|
+
maxBytes: member.size, input: { kind: "stream", stream: Readable.from(payload) } });
|
|
40
|
+
}
|
|
41
|
+
deadline.check();
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
deadline.check();
|
|
45
|
+
await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
|
|
46
|
+
destinationDir: options.destDir, destinationRealDir, deadline });
|
|
47
|
+
deadline.check();
|
|
48
|
+
} });
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TarMeterLimits } from "./archive-limits.js";
|
|
2
|
+
import { type AdmittedTarMember } from "./archive-tar-wasm.js";
|
|
3
|
+
export declare function inspectTar(params: {
|
|
4
|
+
archivePath: string;
|
|
5
|
+
limits: TarMeterLimits;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
onMember?: (entry: AdmittedTarMember) => void;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
/** Replay in physical order, retaining at most one decoded chunk. Every range
|
|
10
|
+
* comes from complete admission of the immutable staged input. */
|
|
11
|
+
export declare function replayTar<T extends AdmittedTarMember>(params: {
|
|
12
|
+
archivePath: string;
|
|
13
|
+
limits: TarMeterLimits;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
members: readonly T[];
|
|
16
|
+
consume(member: T, payload: AsyncIterable<Buffer>): Promise<void>;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=archive-tar-stream.d.ts.map
|
|
@@ -0,0 +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;AA4ChF,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;kEACkE;AAClE,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE;IACnE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAClE,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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { pipeline } from "node:stream/promises";
|
|
4
|
+
import { createGunzip } from "node:zlib";
|
|
5
|
+
import { GzipInput, validateGzipContainerTail } from "./archive-gzip-tail.js";
|
|
6
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
7
|
+
import { TarParserStream } from "./archive-tar-wasm.js";
|
|
8
|
+
async function gzipFile(filePath) {
|
|
9
|
+
const handle = await fs.promises.open(filePath, "r");
|
|
10
|
+
try {
|
|
11
|
+
const magic = Buffer.alloc(2);
|
|
12
|
+
const { bytesRead } = await handle.read(magic, 0, 2, 0);
|
|
13
|
+
return bytesRead === 2 && magic[0] === 31 && magic[1] === 139;
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
await handle.close();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function withTarStream(params, consume) {
|
|
20
|
+
const gzip = await gzipFile(params.archivePath);
|
|
21
|
+
const parser = new TarParserStream(params.limits, params.onMember);
|
|
22
|
+
const input = fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
|
|
23
|
+
const decoder = gzip ? createGunzip() : undefined;
|
|
24
|
+
const gzipInput = decoder ? new GzipInput(decoder) : undefined;
|
|
25
|
+
const destroy = (error) => {
|
|
26
|
+
input.destroy(error);
|
|
27
|
+
gzipInput?.destroy(error);
|
|
28
|
+
decoder?.destroy(error);
|
|
29
|
+
parser.destroy(error);
|
|
30
|
+
};
|
|
31
|
+
const pumps = decoder && gzipInput
|
|
32
|
+
? [pipeline(decoder, parser, { signal: params.signal }), pipeline(input, gzipInput, { signal: params.signal })]
|
|
33
|
+
: [pipeline(input, parser, { signal: params.signal })];
|
|
34
|
+
// Either pump tears down both routes; join every pump even after the first failure.
|
|
35
|
+
const settled = Promise.all(pumps.map((pump) => pump.then(() => undefined, (cause) => {
|
|
36
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
37
|
+
destroy(error);
|
|
38
|
+
return error;
|
|
39
|
+
}))).then((errors) => errors.find((error) => error !== undefined));
|
|
40
|
+
try {
|
|
41
|
+
const result = await consume(parser);
|
|
42
|
+
const error = await settled;
|
|
43
|
+
if (error)
|
|
44
|
+
throw error;
|
|
45
|
+
if (gzipInput)
|
|
46
|
+
await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
destroy();
|
|
51
|
+
await settled;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function inspectTar(params) {
|
|
55
|
+
await withTarStream(params, async (parser) => {
|
|
56
|
+
await pipeline(parser, new Writable({ write(_chunk, _encoding, callback) { callback(); } }));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Replay in physical order, retaining at most one decoded chunk. Every range
|
|
60
|
+
* comes from complete admission of the immutable staged input. */
|
|
61
|
+
export async function replayTar(params) {
|
|
62
|
+
await withTarStream(params, async (parser) => {
|
|
63
|
+
const iterator = parser[Symbol.asyncIterator]();
|
|
64
|
+
let chunk = Buffer.alloc(0);
|
|
65
|
+
let position = 0;
|
|
66
|
+
async function* take(length) {
|
|
67
|
+
while (length > 0) {
|
|
68
|
+
if (!chunk.length) {
|
|
69
|
+
const next = await iterator.next();
|
|
70
|
+
if (next.done)
|
|
71
|
+
throw new ArchiveFormatError("truncated admitted TAR range");
|
|
72
|
+
chunk = next.value;
|
|
73
|
+
}
|
|
74
|
+
const count = Math.min(length, chunk.length);
|
|
75
|
+
const bytes = chunk.subarray(0, count);
|
|
76
|
+
chunk = chunk.subarray(count);
|
|
77
|
+
position += count;
|
|
78
|
+
length -= count;
|
|
79
|
+
yield bytes;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const member of params.members) {
|
|
83
|
+
if (member.offset < position)
|
|
84
|
+
throw new ArchiveFormatError("invalid admitted TAR range order");
|
|
85
|
+
for await (const _ of take(member.offset - position)) { /* Skip admitted gaps. */ }
|
|
86
|
+
const payload = take(member.size);
|
|
87
|
+
await params.consume(member, payload);
|
|
88
|
+
for await (const _ of payload) { /* Directories may carry ignored dump data. */ }
|
|
89
|
+
if (position !== member.offset + member.size)
|
|
90
|
+
throw new ArchiveFormatError("incomplete TAR range consumption");
|
|
91
|
+
}
|
|
92
|
+
// Includes unrequested/skipped members, trailer checks, and physical EOF.
|
|
93
|
+
while (!(await iterator.next()).done) { /* Drain the bounded parser stream. */ }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Transform, type TransformCallback } from "node:stream";
|
|
2
|
+
import { type TarMeterLimits } from "./archive-limits.js";
|
|
3
|
+
import type { TarEntryInfo } from "./archive-tar.js";
|
|
4
|
+
export type AdmittedTarMember = TarEntryInfo & {
|
|
5
|
+
offset: number;
|
|
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?;
|
|
10
|
+
private abi;
|
|
11
|
+
constructor(limits: TarMeterLimits, onMember?: ((entry: AdmittedTarMember) => void) | undefined);
|
|
12
|
+
private bytes;
|
|
13
|
+
private text;
|
|
14
|
+
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
|
|
15
|
+
_flush(callback: TransformCallback): void;
|
|
16
|
+
_destroy(error: Error | null, callback: (error: Error | null) => void): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=archive-tar-wasm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAA+C,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACvG,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,CAkB/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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { Transform } from "node:stream";
|
|
3
|
+
import { ArchiveFormatError, ArchiveSecurityError, isArchiveTarPathErrorMessage } from "./archive-errors.js";
|
|
4
|
+
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError } from "./archive-limits.js";
|
|
5
|
+
// Node exposes WebAssembly without DOM globals; keep the private ABI types local.
|
|
6
|
+
const wasm = globalThis.WebAssembly;
|
|
7
|
+
let compiled;
|
|
8
|
+
function instance() {
|
|
9
|
+
// src tests and dist consumers resolve the same generated package artifact.
|
|
10
|
+
compiled ??= new wasm.Module(readFileSync(new URL("../dist/archive-parser.wasm", import.meta.url)));
|
|
11
|
+
if (wasm.Module.imports(compiled).length)
|
|
12
|
+
throw new Error("TAR WASM unexpectedly requires host imports");
|
|
13
|
+
return new wasm.Instance(compiled).exports;
|
|
14
|
+
}
|
|
15
|
+
const types = new Map([
|
|
16
|
+
[0, "File"], [48, "File"], [49, "Link"], [50, "SymbolicLink"],
|
|
17
|
+
[51, "CharacterDevice"], [52, "BlockDevice"], [53, "Directory"],
|
|
18
|
+
[54, "FIFO"], [55, "ContiguousFile"], [68, "GNUDumpDir"],
|
|
19
|
+
]);
|
|
20
|
+
function parserError(message) {
|
|
21
|
+
if (isArchiveTarPathErrorMessage(message))
|
|
22
|
+
return new ArchiveSecurityError("entry-path", message);
|
|
23
|
+
for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
|
|
24
|
+
if (message.includes(code))
|
|
25
|
+
return new ArchiveLimitError(code);
|
|
26
|
+
}
|
|
27
|
+
return new ArchiveFormatError(message.replace(/^archive-header-invalid:/, "invalid TAR header:"));
|
|
28
|
+
}
|
|
29
|
+
/** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
|
|
30
|
+
export class TarParserStream extends Transform {
|
|
31
|
+
onMember;
|
|
32
|
+
abi;
|
|
33
|
+
constructor(limits, onMember) {
|
|
34
|
+
super();
|
|
35
|
+
this.onMember = onMember;
|
|
36
|
+
this.abi = instance();
|
|
37
|
+
if (this.abi.init(limits.maxEntries, limits.maxMetaEntryBytes, limits.maxDecodedBytes, limits.maxManifestBytes, Number(process.platform === "win32")) !== 0) {
|
|
38
|
+
this.abi.dispose();
|
|
39
|
+
this.abi = undefined;
|
|
40
|
+
throw new RangeError("invalid TAR parser limits");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
bytes(pointer, length) {
|
|
44
|
+
const memory = this.abi.memory.buffer;
|
|
45
|
+
if (!Number.isInteger(pointer) || !Number.isInteger(length) || pointer < 0 || length < 0 ||
|
|
46
|
+
pointer > memory.byteLength || length > memory.byteLength - pointer) {
|
|
47
|
+
throw new ArchiveFormatError("invalid TAR WASM memory range");
|
|
48
|
+
}
|
|
49
|
+
return new Uint8Array(memory, pointer, length);
|
|
50
|
+
}
|
|
51
|
+
text() {
|
|
52
|
+
const abi = this.abi;
|
|
53
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(this.bytes(abi.text_ptr(), abi.text_len()));
|
|
54
|
+
}
|
|
55
|
+
_transform(chunk, _encoding, callback) {
|
|
56
|
+
try {
|
|
57
|
+
const abi = this.abi;
|
|
58
|
+
for (let offset = 0; offset < chunk.length;) {
|
|
59
|
+
const length = Math.min(65536, chunk.length - offset);
|
|
60
|
+
this.bytes(abi.input_ptr(), length).set(chunk.subarray(offset, offset + length));
|
|
61
|
+
const used = abi.push(length);
|
|
62
|
+
if (used < 0)
|
|
63
|
+
throw parserError(this.text());
|
|
64
|
+
if (used === 0 || used > length)
|
|
65
|
+
throw new ArchiveFormatError("TAR WASM made no progress");
|
|
66
|
+
offset += 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
|
+
});
|
|
73
|
+
}
|
|
74
|
+
callback(null, chunk);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_flush(callback) {
|
|
81
|
+
try {
|
|
82
|
+
if (this.abi.finish() !== 0)
|
|
83
|
+
throw parserError(this.text());
|
|
84
|
+
callback();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
_destroy(error, callback) {
|
|
91
|
+
try {
|
|
92
|
+
this.abi?.dispose();
|
|
93
|
+
}
|
|
94
|
+
catch (cause) {
|
|
95
|
+
error ??= cause instanceof Error ? cause : new Error(String(cause));
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
this.abi = undefined;
|
|
99
|
+
}
|
|
100
|
+
callback(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/archive-tar.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWvF,wBAAgB,
|
|
1
|
+
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWvF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAqDnC"}
|
package/dist/archive-tar.js
CHANGED
|
@@ -11,28 +11,6 @@ const BLOCKED_TAR_ENTRY_TYPES = new Set([
|
|
|
11
11
|
"FIFO",
|
|
12
12
|
"Socket",
|
|
13
13
|
]);
|
|
14
|
-
export function readTarEntryInfo(entry) {
|
|
15
|
-
const p = typeof entry === "object" && entry !== null && "path" in entry
|
|
16
|
-
? String(entry.path)
|
|
17
|
-
: "";
|
|
18
|
-
const t = typeof entry === "object" && entry !== null && "type" in entry
|
|
19
|
-
? String(entry.type)
|
|
20
|
-
: "";
|
|
21
|
-
const s = typeof entry === "object" &&
|
|
22
|
-
entry !== null &&
|
|
23
|
-
"size" in entry &&
|
|
24
|
-
typeof entry.size === "number" &&
|
|
25
|
-
Number.isFinite(entry.size)
|
|
26
|
-
? Math.max(0, Math.floor(entry.size))
|
|
27
|
-
: 0;
|
|
28
|
-
const mode = typeof entry === "object" &&
|
|
29
|
-
entry !== null &&
|
|
30
|
-
"mode" in entry &&
|
|
31
|
-
typeof entry.mode === "number"
|
|
32
|
-
? entry.mode
|
|
33
|
-
: undefined;
|
|
34
|
-
return { path: p, type: t, size: s, mode };
|
|
35
|
-
}
|
|
36
14
|
export function createTarEntryPreflightChecker(params) {
|
|
37
15
|
const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
|
|
38
16
|
const limits = resolveExtractLimits(params.limits);
|
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":"AAwDA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,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,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;AA6MpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEjF"}
|