@zip.js/zip.js 2.8.30 → 2.8.32
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/BENCHMARKS.md +82 -1
- package/deno.json +5 -4
- package/dist/zip-core.js +403 -339
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +116 -278
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +440 -378
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +439 -377
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +408 -344
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +411 -347
- package/dist/zip-native.min.js +1 -1
- package/dist/zip-web-worker-native.js +1 -1
- package/dist/zip-web-worker.js +1 -1
- package/dist/zip.js +410 -346
- package/dist/zip.min.js +1 -1
- package/index-native.cjs +440 -378
- package/index-native.min.js +1 -1
- package/index.cjs +439 -377
- package/index.d.cts +2853 -0
- package/index.d.ts +278 -1
- package/index.min.js +1 -1
- package/lib/core/codec-worker.js +1 -11
- package/lib/core/configuration.js +21 -33
- package/lib/core/constants.js +3 -0
- package/lib/core/io.js +11 -19
- package/lib/core/streams/aes-crypto-stream.js +16 -20
- package/lib/core/streams/common-crypto.js +0 -2
- package/lib/core/streams/zip-crypto-stream.js +12 -16
- package/lib/core/streams/zip-entry-stream.js +2 -32
- package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
- package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +1 -3
- package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
- package/lib/core/util/base64.js +68 -0
- package/lib/core/util/blob-temp-stream.js +124 -0
- package/lib/core/util/decode-text.js +0 -1
- package/lib/core/util/default-mime-type.js +0 -3
- package/lib/core/util/inflate.js +194 -0
- package/lib/core/util/mime-type.js +26 -22
- package/lib/core/util/opfs-temp-stream.js +1 -37
- package/lib/core/util/sync-access-handle-temp-stream.js +147 -0
- package/lib/core/web-worker-inline-native.js +1 -1
- package/lib/core/web-worker-inline-template-native.js +5 -5
- package/lib/core/web-worker-inline-wasm.js +1 -1
- package/lib/core/zip-fs.js +6 -13
- package/lib/core/zip-reader.js +48 -127
- package/lib/core/zip-writer.js +27 -45
- package/lib/core/zlib-streams-inline-template.js +3 -2
- package/lib/core/zlib-streams-inline.js +1 -1
- package/lib/zip-core-base.js +10 -2
- package/package.json +9 -6
- package/reserved-property-names.js +50 -0
- package/lib/core/util/mini-lz.js +0 -198
package/dist/zip-fs-core.js
CHANGED
|
@@ -112,6 +112,8 @@
|
|
|
112
112
|
const FUNCTION_TYPE = "function";
|
|
113
113
|
const OBJECT_TYPE = "object";
|
|
114
114
|
|
|
115
|
+
const EMPTY_UINT8_ARRAY = new Uint8Array();
|
|
116
|
+
|
|
115
117
|
/*
|
|
116
118
|
Copyright (c) 2025 Gildas Lormeau. All rights reserved.
|
|
117
119
|
|
|
@@ -236,26 +238,24 @@
|
|
|
236
238
|
class Reader extends Stream {
|
|
237
239
|
|
|
238
240
|
get readable() {
|
|
241
|
+
return this.createReadable();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
createReadable({ offset = 0, size, diskNumberStart, chunkSize = DEFAULT_CHUNK_SIZE$1 } = {}) {
|
|
239
245
|
const reader = this;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
start() {
|
|
243
|
-
this.chunkOffset = 0;
|
|
244
|
-
},
|
|
246
|
+
let chunkOffset = 0;
|
|
247
|
+
return new ReadableStream({
|
|
245
248
|
async pull(controller) {
|
|
246
|
-
const { offset = 0, size, diskNumberStart } = readable;
|
|
247
|
-
const { chunkOffset } = this;
|
|
248
249
|
const dataSize = size === UNDEFINED_VALUE ? chunkSize : Math.min(chunkSize, size - chunkOffset);
|
|
249
250
|
const data = await readUint8Array(reader, offset + chunkOffset, dataSize, diskNumberStart);
|
|
250
251
|
controller.enqueue(data);
|
|
251
252
|
if ((chunkOffset + chunkSize > size) || (size === UNDEFINED_VALUE && !data.length && dataSize)) {
|
|
252
253
|
controller.close();
|
|
253
254
|
} else {
|
|
254
|
-
|
|
255
|
+
chunkOffset += chunkSize;
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
});
|
|
258
|
-
return readable;
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -394,8 +394,6 @@
|
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
writer.blob = new Response(transformStream.readable, { headers }).blob();
|
|
397
|
-
// mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
|
|
398
|
-
// surfaces them
|
|
399
397
|
writer.blob.catch(() => { });
|
|
400
398
|
}
|
|
401
399
|
|
|
@@ -557,7 +555,7 @@
|
|
|
557
555
|
return eocdCache;
|
|
558
556
|
}
|
|
559
557
|
if (index >= size || length === 0) {
|
|
560
|
-
return
|
|
558
|
+
return EMPTY_UINT8_ARRAY;
|
|
561
559
|
} else {
|
|
562
560
|
if (index + length > size) {
|
|
563
561
|
length = size - index;
|
|
@@ -614,8 +612,6 @@
|
|
|
614
612
|
async function getRequestData(httpReader, sendRequest) {
|
|
615
613
|
const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
|
|
616
614
|
httpReader.data = new Uint8Array(await response.arrayBuffer());
|
|
617
|
-
// the downloaded content is authoritative, e.g. the Content-Length response header
|
|
618
|
-
// of the HEAD request can be the encoded size when the content is compressed
|
|
619
615
|
httpReader.size = httpReader.data.length;
|
|
620
616
|
}
|
|
621
617
|
|
|
@@ -680,7 +676,6 @@
|
|
|
680
676
|
super();
|
|
681
677
|
Object.assign(this, {
|
|
682
678
|
url,
|
|
683
|
-
// an explicit fetch implementation takes precedence over useXHR
|
|
684
679
|
reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
|
|
685
680
|
});
|
|
686
681
|
}
|
|
@@ -812,7 +807,7 @@
|
|
|
812
807
|
}
|
|
813
808
|
}
|
|
814
809
|
} else {
|
|
815
|
-
result =
|
|
810
|
+
result = EMPTY_UINT8_ARRAY;
|
|
816
811
|
}
|
|
817
812
|
reader.lastDiskNumber = Math.max(currentDiskNumber, reader.lastDiskNumber);
|
|
818
813
|
return result;
|
|
@@ -857,8 +852,6 @@
|
|
|
857
852
|
writer.diskOffset += diskSourceWriter.size;
|
|
858
853
|
writer.diskNumber++;
|
|
859
854
|
diskWriter = null;
|
|
860
|
-
// the next disk is not opened yet but its space is available; leaving a
|
|
861
|
-
// stale value here would force a new disk for every subsequent write
|
|
862
855
|
writer.availableSize = writer.maxSize;
|
|
863
856
|
if (chunk.length > availableSize) {
|
|
864
857
|
await this.write(chunk.subarray(availableSize));
|
|
@@ -1971,8 +1964,6 @@
|
|
|
1971
1964
|
if (GET_RANDOM_VALUES_SUPPORTED) {
|
|
1972
1965
|
return crypto.getRandomValues(array);
|
|
1973
1966
|
} else {
|
|
1974
|
-
// all supported runtimes expose crypto.getRandomValues; failing hard beats
|
|
1975
|
-
// silently generating predictable encryption salts
|
|
1976
1967
|
throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
|
|
1977
1968
|
}
|
|
1978
1969
|
}
|
|
@@ -2035,12 +2026,7 @@
|
|
|
2035
2026
|
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
2036
2027
|
super({
|
|
2037
2028
|
start() {
|
|
2038
|
-
|
|
2039
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
2040
|
-
password: encodePassword(password, rawPassword),
|
|
2041
|
-
strength: encryptionStrength - 1,
|
|
2042
|
-
pending: new Uint8Array()
|
|
2043
|
-
});
|
|
2029
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
2044
2030
|
},
|
|
2045
2031
|
async transform(chunk, controller) {
|
|
2046
2032
|
const aesCrypto = this;
|
|
@@ -2075,7 +2061,7 @@
|
|
|
2075
2061
|
await ready;
|
|
2076
2062
|
const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH);
|
|
2077
2063
|
const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH);
|
|
2078
|
-
let decryptedChunkArray =
|
|
2064
|
+
let decryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
2079
2065
|
if (chunkToDecrypt.length) {
|
|
2080
2066
|
const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
|
|
2081
2067
|
hmac.update(encryptedChunk);
|
|
@@ -2083,8 +2069,6 @@
|
|
|
2083
2069
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
2084
2070
|
}
|
|
2085
2071
|
const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
|
|
2086
|
-
// branch-free comparison: accumulate the differences instead of
|
|
2087
|
-
// short-circuiting on the first mismatching byte
|
|
2088
2072
|
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
2089
2073
|
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
2090
2074
|
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
@@ -2106,12 +2090,7 @@
|
|
|
2106
2090
|
let stream;
|
|
2107
2091
|
super({
|
|
2108
2092
|
start() {
|
|
2109
|
-
|
|
2110
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
2111
|
-
password: encodePassword(password, rawPassword),
|
|
2112
|
-
strength: encryptionStrength - 1,
|
|
2113
|
-
pending: new Uint8Array()
|
|
2114
|
-
});
|
|
2093
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
2115
2094
|
},
|
|
2116
2095
|
async transform(chunk, controller) {
|
|
2117
2096
|
const aesCrypto = this;
|
|
@@ -2121,7 +2100,7 @@
|
|
|
2121
2100
|
resolveReady,
|
|
2122
2101
|
ready
|
|
2123
2102
|
} = aesCrypto;
|
|
2124
|
-
let preamble =
|
|
2103
|
+
let preamble = EMPTY_UINT8_ARRAY;
|
|
2125
2104
|
if (password) {
|
|
2126
2105
|
preamble = await createEncryptionKeys(aesCrypto, strength, password);
|
|
2127
2106
|
resolveReady();
|
|
@@ -2141,7 +2120,7 @@
|
|
|
2141
2120
|
} = this;
|
|
2142
2121
|
if (hmac && ctr) {
|
|
2143
2122
|
await ready;
|
|
2144
|
-
let encryptedChunkArray =
|
|
2123
|
+
let encryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
2145
2124
|
if (pending.length) {
|
|
2146
2125
|
const encryptedChunk = ctr.update(toBits(codecBytes, pending));
|
|
2147
2126
|
hmac.update(encryptedChunk);
|
|
@@ -2156,6 +2135,15 @@
|
|
|
2156
2135
|
}
|
|
2157
2136
|
}
|
|
2158
2137
|
|
|
2138
|
+
function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
|
|
2139
|
+
Object.assign(aesCrypto, {
|
|
2140
|
+
ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
|
|
2141
|
+
password: encodePassword(password, rawPassword),
|
|
2142
|
+
strength: encryptionStrength - 1,
|
|
2143
|
+
pending: EMPTY_UINT8_ARRAY
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2159
2147
|
function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) {
|
|
2160
2148
|
const {
|
|
2161
2149
|
ctr,
|
|
@@ -2317,20 +2305,13 @@
|
|
|
2317
2305
|
constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
|
|
2318
2306
|
super({
|
|
2319
2307
|
start() {
|
|
2320
|
-
|
|
2321
|
-
password,
|
|
2322
|
-
rawPassword,
|
|
2323
|
-
passwordVerification
|
|
2324
|
-
});
|
|
2325
|
-
createKeys(this, password, rawPassword);
|
|
2308
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
2326
2309
|
},
|
|
2327
2310
|
transform(chunk, controller) {
|
|
2328
2311
|
const zipCrypto = this;
|
|
2329
2312
|
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
2330
2313
|
const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
|
|
2331
2314
|
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
2332
|
-
// verify the password with a branch-free comparison of the verification byte, mirroring
|
|
2333
|
-
// the constant-time AES HMAC signature check
|
|
2334
2315
|
if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
|
|
2335
2316
|
throw new Error(ERR_INVALID_PASSWORD);
|
|
2336
2317
|
}
|
|
@@ -2351,12 +2332,7 @@
|
|
|
2351
2332
|
constructor({ password, rawPassword, passwordVerification }) {
|
|
2352
2333
|
super({
|
|
2353
2334
|
start() {
|
|
2354
|
-
|
|
2355
|
-
password,
|
|
2356
|
-
rawPassword,
|
|
2357
|
-
passwordVerification
|
|
2358
|
-
});
|
|
2359
|
-
createKeys(this, password, rawPassword);
|
|
2335
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
2360
2336
|
},
|
|
2361
2337
|
transform(chunk, controller) {
|
|
2362
2338
|
const zipCrypto = this;
|
|
@@ -2380,6 +2356,15 @@
|
|
|
2380
2356
|
}
|
|
2381
2357
|
}
|
|
2382
2358
|
|
|
2359
|
+
function initZipCrypto(zipCrypto, password, rawPassword, passwordVerification) {
|
|
2360
|
+
Object.assign(zipCrypto, {
|
|
2361
|
+
password,
|
|
2362
|
+
rawPassword,
|
|
2363
|
+
passwordVerification
|
|
2364
|
+
});
|
|
2365
|
+
createKeys(zipCrypto, password, rawPassword);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2383
2368
|
function decrypt(target, input) {
|
|
2384
2369
|
const output = new Uint8Array(input.length);
|
|
2385
2370
|
for (let index = 0; index < input.length; index++) {
|
|
@@ -2484,14 +2469,6 @@
|
|
|
2484
2469
|
const stream = this;
|
|
2485
2470
|
let crc32Stream, encryptionStream, gzipCrc32Stream;
|
|
2486
2471
|
let readable = super.readable;
|
|
2487
|
-
// The gzip trailer carries a CRC-32 of the uncompressed data (same polynomial as zip),
|
|
2488
|
-
// computed in native code for free during compression. On the native CompressionStream,
|
|
2489
|
-
// harvest it instead of running a separate CRC pass: compress as gzip, then strip the
|
|
2490
|
-
// fixed 10-byte header and 8-byte trailer to recover the exact raw-deflate payload
|
|
2491
|
-
// (byte-identical to a deflate-raw compression) while capturing the CRC. The CRC becomes
|
|
2492
|
-
// available at end-of-stream, exactly like Crc32Stream, so nothing downstream (data
|
|
2493
|
-
// descriptor, central directory, ZipCrypto) needs it any earlier. Not applied for the
|
|
2494
|
-
// pure-JS/WASM ports (a separate slice-by-8 CRC pass is as fast or faster there).
|
|
2495
2472
|
const useGzipCrc32 = signed && compressed && !deflate64 && (!encrypted || zipCrypto) &&
|
|
2496
2473
|
Boolean(useCompressionStream && CompressionStream);
|
|
2497
2474
|
if ((!encrypted || zipCrypto) && signed && !useGzipCrc32) {
|
|
@@ -2528,12 +2505,6 @@
|
|
|
2528
2505
|
}
|
|
2529
2506
|
}
|
|
2530
2507
|
|
|
2531
|
-
// Converts a gzip stream into its raw-deflate payload while capturing the CRC-32 from the gzip
|
|
2532
|
-
// trailer. The CompressionStream gzip header is always the fixed 10-byte form (FLG=0, no optional
|
|
2533
|
-
// fields) per the Compression Streams spec, so it is stripped by length; the trailer is the last
|
|
2534
|
-
// 8 bytes (CRC-32 LE, then ISIZE LE). Bounded memory: at most GZIP_TRAILER_LENGTH bytes are held
|
|
2535
|
-
// back across chunks. The signature is read big-endian-agnostically as a number, matching the
|
|
2536
|
-
// value Crc32Stream produces, so the writer path is unchanged.
|
|
2537
2508
|
class GzipToRawDeflateStream extends TransformStream {
|
|
2538
2509
|
|
|
2539
2510
|
constructor() {
|
|
@@ -2559,10 +2530,6 @@
|
|
|
2559
2530
|
tail = pending;
|
|
2560
2531
|
return;
|
|
2561
2532
|
}
|
|
2562
|
-
// Emit everything except the trailing GZIP_TRAILER_LENGTH bytes as a standalone,
|
|
2563
|
-
// right-sized Uint8Array. Consumers may read chunk.buffer directly (e.g. custom
|
|
2564
|
-
// writers), so an aliased subarray of a larger buffer would leak the held-back
|
|
2565
|
-
// trailer bytes. Bytes are copied exactly once, into `output` or `tail`.
|
|
2566
2533
|
const emitLength = available - GZIP_TRAILER_LENGTH;
|
|
2567
2534
|
const output = new Uint8Array(emitLength);
|
|
2568
2535
|
const fromTail = Math.min(emitLength, tail.length);
|
|
@@ -2651,11 +2618,6 @@
|
|
|
2651
2618
|
throw error;
|
|
2652
2619
|
}
|
|
2653
2620
|
}
|
|
2654
|
-
// The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
|
|
2655
|
-
// backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
|
|
2656
|
-
// the whole source into them and peak memory grows with the entry size. Awaiting each write()
|
|
2657
|
-
// paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
|
|
2658
|
-
// codec already backpressures, so this is a no-op cost for it.
|
|
2659
2621
|
return pipeThroughBackpressured(readable, codecStream);
|
|
2660
2622
|
}
|
|
2661
2623
|
|
|
@@ -2663,10 +2625,6 @@
|
|
|
2663
2625
|
return readable.pipeThrough(transformStream);
|
|
2664
2626
|
}
|
|
2665
2627
|
|
|
2666
|
-
// Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
|
|
2667
|
-
// write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
|
|
2668
|
-
// Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
|
|
2669
|
-
// (surfaced through the codec's writable rejecting) cancels the source.
|
|
2670
2628
|
function pipeThroughBackpressured(readable, transformStream) {
|
|
2671
2629
|
const writer = transformStream.writable.getWriter();
|
|
2672
2630
|
const reader = readable.getReader();
|
|
@@ -2695,7 +2653,7 @@
|
|
|
2695
2653
|
try {
|
|
2696
2654
|
await writer.abort(error);
|
|
2697
2655
|
} catch {
|
|
2698
|
-
// the writable may already be errored/closed
|
|
2656
|
+
// ignored: the writable may already be errored/closed
|
|
2699
2657
|
}
|
|
2700
2658
|
}
|
|
2701
2659
|
|
|
@@ -2703,13 +2661,10 @@
|
|
|
2703
2661
|
try {
|
|
2704
2662
|
await reader.cancel(error);
|
|
2705
2663
|
} catch {
|
|
2706
|
-
// the readable may already be errored/closed
|
|
2664
|
+
// ignored: the readable may already be errored/closed
|
|
2707
2665
|
}
|
|
2708
2666
|
}
|
|
2709
2667
|
|
|
2710
|
-
// DecompressionStream implementations can fail with a message-less TypeError on malformed
|
|
2711
|
-
// input; give these errors an identifiable message (errors raised by the other stages of
|
|
2712
|
-
// the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
|
|
2713
2668
|
function mapInflateStreamError(readable) {
|
|
2714
2669
|
const reader = readable.getReader();
|
|
2715
2670
|
return new ReadableStream({
|
|
@@ -3034,9 +2989,6 @@
|
|
|
3034
2989
|
try {
|
|
3035
2990
|
await initModule(config);
|
|
3036
2991
|
} catch {
|
|
3037
|
-
// The WASM module failed to load. Fall back to the native CompressionStream only if the
|
|
3038
|
-
// zlib codec that would otherwise run depends on that module; a self-contained codec
|
|
3039
|
-
// supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
|
|
3040
2992
|
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
3041
2993
|
config.CompressionStreamZlib :
|
|
3042
2994
|
config.DecompressionStreamZlib;
|
|
@@ -3114,7 +3066,6 @@
|
|
|
3114
3066
|
return resultValue;
|
|
3115
3067
|
} catch (error) {
|
|
3116
3068
|
await closeWritable();
|
|
3117
|
-
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
3118
3069
|
abortPipe();
|
|
3119
3070
|
try {
|
|
3120
3071
|
await closed;
|
|
@@ -3139,8 +3090,6 @@
|
|
|
3139
3090
|
const abortController = new AbortController();
|
|
3140
3091
|
const { writable, readable } = new TransformStream();
|
|
3141
3092
|
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
3142
|
-
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
3143
|
-
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
3144
3093
|
closed.catch(() => { });
|
|
3145
3094
|
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
3146
3095
|
}
|
|
@@ -3233,8 +3182,6 @@
|
|
|
3233
3182
|
event.preventDefault();
|
|
3234
3183
|
}
|
|
3235
3184
|
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
3236
|
-
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
3237
|
-
// fail the current task instead of letting it hang forever
|
|
3238
3185
|
terminateWorker$1(workerData);
|
|
3239
3186
|
if (rejectResult) {
|
|
3240
3187
|
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
@@ -3250,7 +3197,7 @@
|
|
|
3250
3197
|
const { value, readable, writable } = message;
|
|
3251
3198
|
const transferables = [];
|
|
3252
3199
|
if (value) {
|
|
3253
|
-
message.value = value;
|
|
3200
|
+
message.value = value.byteOffset || value.byteLength != value.buffer.byteLength ? new Uint8Array(value) : value;
|
|
3254
3201
|
transferables.push(message.value.buffer);
|
|
3255
3202
|
}
|
|
3256
3203
|
if (transferStreams && transferStreamsSupported) {
|
|
@@ -3286,8 +3233,6 @@
|
|
|
3286
3233
|
async function onMessage({ data }, workerData) {
|
|
3287
3234
|
const { type, value, messageId, result, error } = data;
|
|
3288
3235
|
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
3289
|
-
// continuations below can settle after the worker slot has been reassigned to a new
|
|
3290
|
-
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
3291
3236
|
const stale = () => workerData.generation != generation;
|
|
3292
3237
|
try {
|
|
3293
3238
|
if (error) {
|
|
@@ -3551,7 +3496,6 @@
|
|
|
3551
3496
|
if (encoding && encoding.trim().toLowerCase() == "cp437") {
|
|
3552
3497
|
return decodeCP437(value);
|
|
3553
3498
|
} else {
|
|
3554
|
-
// ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
|
|
3555
3499
|
return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
|
|
3556
3500
|
}
|
|
3557
3501
|
}
|
|
@@ -3799,8 +3743,6 @@
|
|
|
3799
3743
|
const CHARSET_UTF8 = "utf-8";
|
|
3800
3744
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
3801
3745
|
const CHARSET_CP437 = "cp437";
|
|
3802
|
-
// bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
|
|
3803
|
-
// because they are informative only
|
|
3804
3746
|
const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
|
|
3805
3747
|
const ZIP64_PROPERTIES = [
|
|
3806
3748
|
[PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
|
|
@@ -3842,7 +3784,6 @@
|
|
|
3842
3784
|
if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
|
|
3843
3785
|
throw new Error(ERR_BAD_FORMAT);
|
|
3844
3786
|
}
|
|
3845
|
-
reader.chunkSize = getChunkSize(config);
|
|
3846
3787
|
const strictness = getStrictness(getOptionValue$1(zipReader, options, OPTION_STRICTNESS), getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY));
|
|
3847
3788
|
const checkAmbiguity = strictness == STRICTNESS_STRICT;
|
|
3848
3789
|
const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
|
|
@@ -3857,8 +3798,6 @@
|
|
|
3857
3798
|
throw new Error(ERR_EOCDR_NOT_FOUND);
|
|
3858
3799
|
}
|
|
3859
3800
|
}
|
|
3860
|
-
// two or more end-anchored records that each dereference to a valid central directory cannot be told
|
|
3861
|
-
// apart (see comment_length ⟺ EOF): the archive is genuinely ambiguous, so refuse rather than guess
|
|
3862
3801
|
if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
|
|
3863
3802
|
throwAmbiguousArchive("multiple end of central directory records");
|
|
3864
3803
|
}
|
|
@@ -3881,7 +3820,7 @@
|
|
|
3881
3820
|
if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
|
|
3882
3821
|
const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
|
|
3883
3822
|
await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
|
|
3884
|
-
|
|
3823
|
+
EMPTY_UINT8_ARRAY;
|
|
3885
3824
|
const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
|
|
3886
3825
|
if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
|
|
3887
3826
|
getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
@@ -3948,12 +3887,6 @@
|
|
|
3948
3887
|
}
|
|
3949
3888
|
const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
|
|
3950
3889
|
if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
|
|
3951
|
-
// the reconciled offset (the directory ends exactly where the canonical record begins) is anchored
|
|
3952
|
-
// to the unforgeable end of the archive; the stored offset is not. Prefer the reconciled offset
|
|
3953
|
-
// unless the stored one points at a directory and the reconciled one does not — that exception
|
|
3954
|
-
// keeps a corrupt declared directory length from sending the read astray, while still moving off a
|
|
3955
|
-
// stored offset that only looks valid because an append remnant left the previous (identically
|
|
3956
|
-
// laid out) directory sitting there.
|
|
3957
3890
|
const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE;
|
|
3958
3891
|
let reconcile = !storedPointsAtDirectory;
|
|
3959
3892
|
if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
|
|
@@ -4110,11 +4043,11 @@
|
|
|
4110
4043
|
const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
|
|
4111
4044
|
const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
|
|
4112
4045
|
if (extractPrependedData) {
|
|
4113
|
-
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) :
|
|
4046
|
+
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : EMPTY_UINT8_ARRAY;
|
|
4114
4047
|
}
|
|
4115
|
-
zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) :
|
|
4048
|
+
zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) : EMPTY_UINT8_ARRAY;
|
|
4116
4049
|
if (extractAppendedData) {
|
|
4117
|
-
zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) :
|
|
4050
|
+
zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) : EMPTY_UINT8_ARRAY;
|
|
4118
4051
|
}
|
|
4119
4052
|
return true;
|
|
4120
4053
|
}
|
|
@@ -4186,7 +4119,7 @@
|
|
|
4186
4119
|
filenameLength
|
|
4187
4120
|
} = localDirectory;
|
|
4188
4121
|
const checkAmbiguity = getStrictness(getOptionValue$1(zipEntry, options, OPTION_STRICTNESS), getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY)) == STRICTNESS_STRICT;
|
|
4189
|
-
let rawLocalFilename =
|
|
4122
|
+
let rawLocalFilename = EMPTY_UINT8_ARRAY;
|
|
4190
4123
|
if (checkAmbiguity && (filenameLength || extraFieldLength)) {
|
|
4191
4124
|
const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
|
|
4192
4125
|
rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
|
|
@@ -4194,7 +4127,7 @@
|
|
|
4194
4127
|
} else {
|
|
4195
4128
|
localDirectory.rawExtraField = extraFieldLength ?
|
|
4196
4129
|
await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
|
|
4197
|
-
|
|
4130
|
+
EMPTY_UINT8_ARRAY;
|
|
4198
4131
|
}
|
|
4199
4132
|
readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
|
|
4200
4133
|
if (checkAmbiguity) {
|
|
@@ -4221,12 +4154,7 @@
|
|
|
4221
4154
|
}
|
|
4222
4155
|
const dataOffset = offset + HEADER_SIZE + filenameLength + extraFieldLength;
|
|
4223
4156
|
const size = compressedSize;
|
|
4224
|
-
const readable = reader.
|
|
4225
|
-
Object.assign(readable, {
|
|
4226
|
-
diskNumberStart,
|
|
4227
|
-
offset: dataOffset,
|
|
4228
|
-
size
|
|
4229
|
-
});
|
|
4157
|
+
const readable = reader.createReadable({ offset: dataOffset, size, diskNumberStart, chunkSize: getChunkSize(config) });
|
|
4230
4158
|
const signal = getOptionValue$1(zipEntry, options, OPTION_SIGNAL);
|
|
4231
4159
|
const checkPasswordOnly = getOptionValue$1(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
|
|
4232
4160
|
let checkOverlappingEntry = getOptionValue$1(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
|
|
@@ -4497,7 +4425,6 @@
|
|
|
4497
4425
|
const view = getDataView$1(new Uint8Array(extraField.data));
|
|
4498
4426
|
let uid, gid;
|
|
4499
4427
|
if (isInfoZip) {
|
|
4500
|
-
// Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
|
|
4501
4428
|
let offset = 0;
|
|
4502
4429
|
const version = getUint8(view, offset++);
|
|
4503
4430
|
const uidSize = getUint8(view, offset++);
|
|
@@ -4507,8 +4434,6 @@
|
|
|
4507
4434
|
gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
|
|
4508
4435
|
Object.assign(extraField, { version, uid, gid });
|
|
4509
4436
|
} else if (extraField.data.length >= 4) {
|
|
4510
|
-
// Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
|
|
4511
|
-
// carried in the external file attributes, not here)
|
|
4512
4437
|
uid = getUint16(view, 0);
|
|
4513
4438
|
gid = getUint16(view, 2);
|
|
4514
4439
|
Object.assign(extraField, { uid, gid });
|
|
@@ -4560,7 +4485,6 @@
|
|
|
4560
4485
|
timeProperties.forEach((propertyName, indexProperty) => {
|
|
4561
4486
|
if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
|
|
4562
4487
|
const time = getUint32(extraFieldView, offset);
|
|
4563
|
-
// the extended timestamp is a signed 32-bit time_t value
|
|
4564
4488
|
directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
|
|
4565
4489
|
const rawPropertyName = timeRawProperties[indexProperty];
|
|
4566
4490
|
extraFieldExtendedTimestamp[rawPropertyName] = time;
|
|
@@ -4637,7 +4561,6 @@
|
|
|
4637
4561
|
|
|
4638
4562
|
function getStrictness(strictness, checkAmbiguity) {
|
|
4639
4563
|
if (strictness === UNDEFINED_VALUE) {
|
|
4640
|
-
// `checkAmbiguity: true` is kept as a backward-compatible alias for the strictest mode
|
|
4641
4564
|
return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
|
|
4642
4565
|
}
|
|
4643
4566
|
return strictness;
|
|
@@ -4648,81 +4571,45 @@
|
|
|
4648
4571
|
return maxAppendedDataSize;
|
|
4649
4572
|
}
|
|
4650
4573
|
if (strictness == STRICTNESS_STRICT) {
|
|
4651
|
-
// the comment must reach the end of the file: no trailing data tolerated
|
|
4652
4574
|
return 0;
|
|
4653
4575
|
}
|
|
4654
4576
|
if (strictness == STRICTNESS_TOLERANT) {
|
|
4655
4577
|
return Infinity;
|
|
4656
4578
|
}
|
|
4657
|
-
// balanced: tolerate up to a legal comment's worth of trailing data (a 16-bit length); beyond that the
|
|
4658
|
-
// trailing bytes cannot hide inside a comment, so they are treated as a second thing bolted onto the file
|
|
4659
4579
|
return MAX_16_BITS;
|
|
4660
4580
|
}
|
|
4661
4581
|
|
|
4662
|
-
// cap on how many out-of-window central directory probes the scan below may issue. A legitimate archive needs
|
|
4663
|
-
// at most a couple (the canonical record, plus one more to detect genuine ambiguity); every other reachability
|
|
4664
|
-
// check is served from the tail window already in memory. The cap keeps an archive whose comment is stuffed
|
|
4665
|
-
// with unreachable end-anchored records from forcing an unbounded number of (potentially remote) reads.
|
|
4666
4582
|
const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
|
|
4667
4583
|
|
|
4668
|
-
// reachability rankings for an end-anchored candidate record (see getCentralDirectoryReachability)
|
|
4669
4584
|
const CENTRAL_DIRECTORY_UNREACHABLE = 0;
|
|
4670
4585
|
const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
|
|
4671
4586
|
const CENTRAL_DIRECTORY_REACHABLE = 2;
|
|
4672
4587
|
|
|
4673
|
-
// Locates the authoritative end of central directory record. The canonical record is the last one whose
|
|
4674
|
-
// declared comment reaches exactly the end of the file ("end-anchored") and that points to a central directory
|
|
4675
|
-
// ("reachable"); earlier signatures (stale append remnants, bytes embedded in a comment) are ignored. When two
|
|
4676
|
-
// or more end-anchored records point to a central directory the archive is ambiguous, and the count is returned
|
|
4677
|
-
// so the caller can refuse it. A record that reaches the end of the file but points to no central directory (an
|
|
4678
|
-
// empty archive) is only "plausible": it cannot be corroborated, so it never counts towards ambiguity and is
|
|
4679
|
-
// chosen only when no reachable record exists — otherwise an empty record forged in a comment could outrank the
|
|
4680
|
-
// genuine directory. When no record is end-anchored (appended data or an oversized comment), falls back to the
|
|
4681
|
-
// last record within the tolerated window that points to a directory (see seekEndOfCentralDirectory).
|
|
4682
4588
|
async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
|
|
4683
4589
|
const { size } = reader;
|
|
4684
|
-
// an end-anchored record can only live within the last END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS bytes,
|
|
4685
|
-
// since the comment length that ties it to the end of the file is a 16-bit field
|
|
4686
4590
|
const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
|
|
4687
|
-
const anchoredOffset = size - anchoredLength;
|
|
4688
|
-
const anchoredArray = await readUint8Array(reader, anchoredOffset, anchoredLength);
|
|
4689
|
-
const anchoredView = getDataView$1(anchoredArray);
|
|
4690
4591
|
const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
|
|
4691
4592
|
let endOfDirectoryInfo;
|
|
4692
4593
|
let plausibleEndOfDirectoryInfo;
|
|
4693
4594
|
let endOfDirectoryReachingEndCount = 0;
|
|
4694
|
-
for (
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
const
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
const reachability = await getCentralDirectoryReachability(reader, anchoredView, anchoredOffset, indexByte, offset, size, remoteProbeBudget);
|
|
4702
|
-
if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
|
|
4703
|
-
if (!endOfDirectoryInfo) {
|
|
4704
|
-
endOfDirectoryInfo = {
|
|
4705
|
-
offset,
|
|
4706
|
-
buffer: anchoredArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer
|
|
4707
|
-
};
|
|
4708
|
-
}
|
|
4709
|
-
endOfDirectoryReachingEndCount++;
|
|
4710
|
-
// the canonical record (highest offset) is found first; a second one is enough to flag ambiguity
|
|
4711
|
-
if (!rejectAmbiguous || endOfDirectoryReachingEndCount > 1) {
|
|
4712
|
-
break;
|
|
4713
|
-
}
|
|
4714
|
-
} else if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleEndOfDirectoryInfo) {
|
|
4715
|
-
plausibleEndOfDirectoryInfo = {
|
|
4716
|
-
offset,
|
|
4717
|
-
buffer: anchoredArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer
|
|
4718
|
-
};
|
|
4595
|
+
for await (const [anchoredView, anchoredOffset, anchoredArray, indexByte, offset] of scanEndOfCentralDirectory(reader, anchoredLength)) {
|
|
4596
|
+
const commentLength = getUint16(anchoredView, indexByte + 20);
|
|
4597
|
+
if (offset + END_OF_CENTRAL_DIR_LENGTH + commentLength == size) {
|
|
4598
|
+
const reachability = await getCentralDirectoryReachability(reader, anchoredView, anchoredOffset, indexByte, offset, size, remoteProbeBudget);
|
|
4599
|
+
if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
|
|
4600
|
+
if (!endOfDirectoryInfo) {
|
|
4601
|
+
endOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
|
|
4719
4602
|
}
|
|
4603
|
+
endOfDirectoryReachingEndCount++;
|
|
4604
|
+
if (!rejectAmbiguous || endOfDirectoryReachingEndCount > 1) {
|
|
4605
|
+
break;
|
|
4606
|
+
}
|
|
4607
|
+
} else if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleEndOfDirectoryInfo) {
|
|
4608
|
+
plausibleEndOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
|
|
4720
4609
|
}
|
|
4721
4610
|
}
|
|
4722
4611
|
}
|
|
4723
4612
|
if (!endOfDirectoryInfo) {
|
|
4724
|
-
// no record points to a directory: prefer a plausible (empty) end-anchored record before scanning the
|
|
4725
|
-
// tolerated window for a bare signature
|
|
4726
4613
|
endOfDirectoryInfo = plausibleEndOfDirectoryInfo;
|
|
4727
4614
|
}
|
|
4728
4615
|
if (!endOfDirectoryInfo) {
|
|
@@ -4731,69 +4618,53 @@
|
|
|
4731
4618
|
return { endOfDirectoryInfo, endOfDirectoryReachingEndCount };
|
|
4732
4619
|
}
|
|
4733
4620
|
|
|
4734
|
-
// Fallback for findEndOfCentralDirectory when no record is end-anchored: appended data or an oversized comment
|
|
4735
|
-
// pushed the record's declared comment past the end of the file, so its position can no longer be reconciled
|
|
4736
|
-
// against the end of the file. Scans the tolerated window from the end and returns the first record that points
|
|
4737
|
-
// to a central directory, so a stray signature embedded in appended data cannot hijack discovery (which the
|
|
4738
|
-
// anchored scan already prevents for anchored records). Falls back to the first plausible (empty) record, then
|
|
4739
|
-
// to the last bare signature, so a degenerate archive still opens as before. The window ends at the file, so the
|
|
4740
|
-
// same in-window / metered reads as the anchored scan apply.
|
|
4741
4621
|
async function seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget) {
|
|
4742
4622
|
const { size } = reader;
|
|
4743
4623
|
const searchLength = Math.min(size, maxAppendedDataSize == Infinity ? size :
|
|
4744
4624
|
END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS + maxAppendedDataSize);
|
|
4745
|
-
const searchOffset = size - searchLength;
|
|
4746
|
-
const searchArray = await readUint8Array(reader, searchOffset, searchLength);
|
|
4747
|
-
const searchView = getDataView$1(searchArray);
|
|
4748
4625
|
let firstSignatureInfo, plausibleInfo;
|
|
4749
|
-
for (
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleInfo) {
|
|
4761
|
-
plausibleInfo = record;
|
|
4762
|
-
}
|
|
4626
|
+
for await (const [searchView, searchOffset, searchArray, indexByte, offset] of scanEndOfCentralDirectory(reader, searchLength)) {
|
|
4627
|
+
const record = getEndOfCentralDirectoryInfo(searchArray, indexByte, offset);
|
|
4628
|
+
if (!firstSignatureInfo) {
|
|
4629
|
+
firstSignatureInfo = record;
|
|
4630
|
+
}
|
|
4631
|
+
const reachability = await getCentralDirectoryReachability(reader, searchView, searchOffset, indexByte, offset, size, remoteProbeBudget);
|
|
4632
|
+
if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
|
|
4633
|
+
return record;
|
|
4634
|
+
}
|
|
4635
|
+
if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleInfo) {
|
|
4636
|
+
plausibleInfo = record;
|
|
4763
4637
|
}
|
|
4764
4638
|
}
|
|
4765
4639
|
return plausibleInfo || firstSignatureInfo;
|
|
4766
4640
|
}
|
|
4767
4641
|
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4642
|
+
async function* scanEndOfCentralDirectory(reader, scanLength) {
|
|
4643
|
+
const scanOffset = reader.size - scanLength;
|
|
4644
|
+
const scanArray = await readUint8Array(reader, scanOffset, scanLength);
|
|
4645
|
+
const scanView = getDataView$1(scanArray);
|
|
4646
|
+
for (let indexByte = scanArray.length - END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
|
|
4647
|
+
if (getUint32(scanView, indexByte) == END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
4648
|
+
yield [scanView, scanOffset, scanArray, indexByte, scanOffset + indexByte];
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
function getEndOfCentralDirectoryInfo(scanArray, indexByte, offset) {
|
|
4654
|
+
return { offset, buffer: scanArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer };
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4781
4657
|
async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
|
|
4782
4658
|
const filesLength = getUint16(view, indexByte + 10);
|
|
4783
4659
|
const directoryDataLength = getUint32(view, indexByte + 12);
|
|
4784
4660
|
const directoryDataOffset = getUint32(view, indexByte + 16);
|
|
4785
4661
|
if (filesLength == MAX_16_BITS || directoryDataLength == MAX_32_BITS || directoryDataOffset == MAX_32_BITS) {
|
|
4786
|
-
// saturated fields imply a zip64 record; corroborate it with the zip64 end of central directory locator
|
|
4787
|
-
// that sits immediately before it, so saturated bytes inside a comment cannot masquerade as one
|
|
4788
4662
|
const locatorSignature = await readSignature(reader, view, anchoredOffset, offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, size, remoteProbeBudget);
|
|
4789
4663
|
return locatorSignature == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE ? CENTRAL_DIRECTORY_REACHABLE : CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4790
4664
|
}
|
|
4791
4665
|
if (!filesLength && !directoryDataLength) {
|
|
4792
|
-
// a valid empty archive has no central directory to dereference
|
|
4793
4666
|
return CENTRAL_DIRECTORY_PLAUSIBLE;
|
|
4794
4667
|
}
|
|
4795
|
-
// the central directory ends where this record starts; reconcile the stored offset against that actual
|
|
4796
|
-
// position (see stored_cd_offset ⟺ eocd_pos − cd_size) so prepended data does not hide the directory
|
|
4797
4668
|
for (const centralDirectoryOffset of [offset - directoryDataLength, directoryDataOffset]) {
|
|
4798
4669
|
if (await readSignature(reader, view, anchoredOffset, centralDirectoryOffset, size, remoteProbeBudget) == CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
4799
4670
|
return CENTRAL_DIRECTORY_REACHABLE;
|
|
@@ -4802,15 +4673,11 @@
|
|
|
4802
4673
|
return CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4803
4674
|
}
|
|
4804
4675
|
|
|
4805
|
-
// Reads a 4-byte little-endian signature at `signatureOffset`, served from the tail window (`view`, spanning
|
|
4806
|
-
// `[anchoredOffset, size)`) when it falls inside it and otherwise from a metered (possibly remote) read. Returns
|
|
4807
|
-
// UNDEFINED_VALUE when the position is out of range or the out-of-window read budget is exhausted.
|
|
4808
4676
|
async function readSignature(reader, view, anchoredOffset, signatureOffset, size, remoteProbeBudget) {
|
|
4809
4677
|
if (signatureOffset < 0 || signatureOffset + 4 > size) {
|
|
4810
4678
|
return UNDEFINED_VALUE;
|
|
4811
4679
|
}
|
|
4812
4680
|
if (signatureOffset >= anchoredOffset) {
|
|
4813
|
-
// the target sits inside the tail window already in memory: no extra read needed
|
|
4814
4681
|
return getUint32(view, signatureOffset - anchoredOffset);
|
|
4815
4682
|
}
|
|
4816
4683
|
if (remoteProbeBudget.count > 0) {
|
|
@@ -4818,7 +4685,6 @@
|
|
|
4818
4685
|
const signatureArray = await readUint8Array(reader, signatureOffset, 4);
|
|
4819
4686
|
return getUint32(getDataView$1(signatureArray), 0);
|
|
4820
4687
|
}
|
|
4821
|
-
// out of budget: leave this candidate unverified rather than issue another read
|
|
4822
4688
|
return UNDEFINED_VALUE;
|
|
4823
4689
|
}
|
|
4824
4690
|
|
|
@@ -4834,8 +4700,6 @@
|
|
|
4834
4700
|
if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
|
|
4835
4701
|
throwAmbiguousArchive("mismatched local file header (compression method)");
|
|
4836
4702
|
}
|
|
4837
|
-
// when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
|
|
4838
|
-
// also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
|
|
4839
4703
|
if (!localDirectory.bitFlag.dataDescriptor &&
|
|
4840
4704
|
(localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
|
|
4841
4705
|
(localDirectory.signature != zipEntry.signature ||
|
|
@@ -4925,7 +4789,6 @@
|
|
|
4925
4789
|
const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
|
|
4926
4790
|
const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
|
|
4927
4791
|
const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
|
|
4928
|
-
// same message as the constant exported by zip-reader.js
|
|
4929
4792
|
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
4930
4793
|
const MIN_UNIX_TIME = -2147483648;
|
|
4931
4794
|
const MAX_UNIX_TIME = 2147483647;
|
|
@@ -5008,12 +4871,12 @@
|
|
|
5008
4871
|
rawExtraField,
|
|
5009
4872
|
} = entry;
|
|
5010
4873
|
const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
|
|
5011
|
-
rawExtraFieldZip64 = rawExtraFieldZip64 ||
|
|
5012
|
-
rawExtraFieldAES = rawExtraFieldAES ||
|
|
5013
|
-
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp ||
|
|
5014
|
-
rawExtraFieldNTFS = rawExtraFieldNTFS ||
|
|
5015
|
-
rawExtraFieldUnix = rawExtraFieldUnix ||
|
|
5016
|
-
rawExtraField = rawExtraField ||
|
|
4874
|
+
rawExtraFieldZip64 = rawExtraFieldZip64 || EMPTY_UINT8_ARRAY;
|
|
4875
|
+
rawExtraFieldAES = rawExtraFieldAES || EMPTY_UINT8_ARRAY;
|
|
4876
|
+
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || EMPTY_UINT8_ARRAY;
|
|
4877
|
+
rawExtraFieldNTFS = rawExtraFieldNTFS || EMPTY_UINT8_ARRAY;
|
|
4878
|
+
rawExtraFieldUnix = rawExtraFieldUnix || EMPTY_UINT8_ARRAY;
|
|
4879
|
+
rawExtraField = rawExtraField || EMPTY_UINT8_ARRAY;
|
|
5017
4880
|
if (entry.extraFieldAES) {
|
|
5018
4881
|
compressionMethod = COMPRESSION_METHOD_AES;
|
|
5019
4882
|
}
|
|
@@ -5119,7 +4982,7 @@
|
|
|
5119
4982
|
return false;
|
|
5120
4983
|
}
|
|
5121
4984
|
|
|
5122
|
-
async close(comment =
|
|
4985
|
+
async close(comment = EMPTY_UINT8_ARRAY, options = {}) {
|
|
5123
4986
|
const zipWriter = this;
|
|
5124
4987
|
const { pendingAddFileCalls, writer } = this;
|
|
5125
4988
|
const { writable } = writer;
|
|
@@ -5356,7 +5219,7 @@
|
|
|
5356
5219
|
if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
|
|
5357
5220
|
throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
|
|
5358
5221
|
}
|
|
5359
|
-
let rawExtraField =
|
|
5222
|
+
let rawExtraField = EMPTY_UINT8_ARRAY;
|
|
5360
5223
|
const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
|
|
5361
5224
|
if (extraField) {
|
|
5362
5225
|
let extraFieldSize = 0;
|
|
@@ -5417,8 +5280,6 @@
|
|
|
5417
5280
|
let maximumCompressedSize = 0;
|
|
5418
5281
|
let uncompressedSize = 0;
|
|
5419
5282
|
if (passThrough) {
|
|
5420
|
-
// the headers of a passThrough entry describe the payload verbatim: without a
|
|
5421
|
-
// reader they would declare content that is not there
|
|
5422
5283
|
if (!reader) {
|
|
5423
5284
|
throw new Error(ERR_UNDEFINED_READER);
|
|
5424
5285
|
}
|
|
@@ -5429,8 +5290,6 @@
|
|
|
5429
5290
|
}
|
|
5430
5291
|
const zip64Enabled = zip64 === true;
|
|
5431
5292
|
const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
|
|
5432
|
-
// entries without a reader have no payload: write them as empty stored files instead of
|
|
5433
|
-
// declaring a compressed or encrypted content that does not exist
|
|
5434
5293
|
const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
|
|
5435
5294
|
if (!reader) {
|
|
5436
5295
|
level = 0;
|
|
@@ -5568,7 +5427,6 @@
|
|
|
5568
5427
|
fileEntry.diskNumberStart = writer.diskNumber;
|
|
5569
5428
|
fileEntry.offset = getSegmentOffset(zipWriter, writer);
|
|
5570
5429
|
if (usdz) {
|
|
5571
|
-
// the padding depends on the entry offset, only known now that the writer is locked
|
|
5572
5430
|
const previousMetadataSize = entryInfo.metadataSize;
|
|
5573
5431
|
appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
|
|
5574
5432
|
fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
|
|
@@ -5611,10 +5469,6 @@
|
|
|
5611
5469
|
if (releaseLockWriter) {
|
|
5612
5470
|
releaseLockWriter();
|
|
5613
5471
|
}
|
|
5614
|
-
// A buffered entry uses a temporary stream (see `createTempStream`). When that stream is
|
|
5615
|
-
// backed by a resource (a file, an OPFS handle, ...) it must be released on every exit path,
|
|
5616
|
-
// including the error path where the stream is otherwise abandoned without being closed or
|
|
5617
|
-
// cancelled. `dispose()` is optional and best-effort so it never masks the original outcome.
|
|
5618
5472
|
if (bufferedWrite && fileWriter && fileWriter.dispose) {
|
|
5619
5473
|
try {
|
|
5620
5474
|
await fileWriter.dispose();
|
|
@@ -5641,7 +5495,7 @@
|
|
|
5641
5495
|
async function skipDiskIfNeeded() {
|
|
5642
5496
|
if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
|
|
5643
5497
|
writer.availableSize = 0;
|
|
5644
|
-
await writeData(writer,
|
|
5498
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
5645
5499
|
}
|
|
5646
5500
|
}
|
|
5647
5501
|
}
|
|
@@ -5752,8 +5606,7 @@
|
|
|
5752
5606
|
}
|
|
5753
5607
|
const { writable } = writer;
|
|
5754
5608
|
if (reader) {
|
|
5755
|
-
reader.chunkSize
|
|
5756
|
-
const readable = reader.readable;
|
|
5609
|
+
const readable = reader.createReadable ? reader.createReadable({ chunkSize: getChunkSize(config) }) : reader.readable;
|
|
5757
5610
|
const size = reader.size;
|
|
5758
5611
|
const workerOptions = {
|
|
5759
5612
|
options: {
|
|
@@ -5853,13 +5706,9 @@
|
|
|
5853
5706
|
: compressionMethod !== COMPRESSION_METHOD_STORE);
|
|
5854
5707
|
let rawLocalExtraFieldZip64;
|
|
5855
5708
|
const uncompressedFile = passThrough || !compressed;
|
|
5856
|
-
// entries without a data descriptor are always buffered (cf. getFileEntry), so the
|
|
5857
|
-
// zip64 extra field values can be patched into the local header before flushing it
|
|
5858
5709
|
const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
|
|
5859
5710
|
const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
|
|
5860
5711
|
if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
|
|
5861
|
-
// APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
|
|
5862
|
-
// uncompressed and compressed size fields
|
|
5863
5712
|
const length = 4 + 16;
|
|
5864
5713
|
const extraFieldZip64 = createRecordWriter(length);
|
|
5865
5714
|
extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
|
|
@@ -5873,7 +5722,7 @@
|
|
|
5873
5722
|
}
|
|
5874
5723
|
}
|
|
5875
5724
|
} else {
|
|
5876
|
-
rawLocalExtraFieldZip64 =
|
|
5725
|
+
rawLocalExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
5877
5726
|
}
|
|
5878
5727
|
let rawExtraFieldAES;
|
|
5879
5728
|
if (encrypted && !zipCrypto) {
|
|
@@ -5883,7 +5732,7 @@
|
|
|
5883
5732
|
rawExtraFieldAES = extraFieldAES.array;
|
|
5884
5733
|
rawExtraFieldAES[8] = encryptionStrength;
|
|
5885
5734
|
} else {
|
|
5886
|
-
rawExtraFieldAES =
|
|
5735
|
+
rawExtraFieldAES = EMPTY_UINT8_ARRAY;
|
|
5887
5736
|
}
|
|
5888
5737
|
let rawExtraFieldNTFS;
|
|
5889
5738
|
let rawExtraFieldExtendedTimestamp;
|
|
@@ -5906,8 +5755,7 @@
|
|
|
5906
5755
|
}
|
|
5907
5756
|
rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
|
|
5908
5757
|
} else {
|
|
5909
|
-
|
|
5910
|
-
rawExtraFieldExtendedTimestamp = new Uint8Array();
|
|
5758
|
+
rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5911
5759
|
}
|
|
5912
5760
|
try {
|
|
5913
5761
|
const lastModTimeNTFS = getTimeNTFS(lastModDate);
|
|
@@ -5922,10 +5770,10 @@
|
|
|
5922
5770
|
extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
|
|
5923
5771
|
rawExtraFieldNTFS = extraFieldNTFS.array;
|
|
5924
5772
|
} catch {
|
|
5925
|
-
rawExtraFieldNTFS =
|
|
5773
|
+
rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
|
|
5926
5774
|
}
|
|
5927
5775
|
} else {
|
|
5928
|
-
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp =
|
|
5776
|
+
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5929
5777
|
}
|
|
5930
5778
|
let rawExtraFieldUnix;
|
|
5931
5779
|
try {
|
|
@@ -5951,10 +5799,10 @@
|
|
|
5951
5799
|
extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
|
|
5952
5800
|
rawExtraFieldUnix = extraFieldUnix.array;
|
|
5953
5801
|
} else {
|
|
5954
|
-
rawExtraFieldUnix =
|
|
5802
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5955
5803
|
}
|
|
5956
5804
|
} catch {
|
|
5957
|
-
rawExtraFieldUnix =
|
|
5805
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5958
5806
|
}
|
|
5959
5807
|
if (compressionMethod === UNDEFINED_VALUE) {
|
|
5960
5808
|
compressionMethod = compressed ? COMPRESSION_METHOD_DEFLATE : COMPRESSION_METHOD_STORE;
|
|
@@ -6021,7 +5869,7 @@
|
|
|
6021
5869
|
version,
|
|
6022
5870
|
compressionMethod,
|
|
6023
5871
|
extraFieldExtendedTimestampFlag,
|
|
6024
|
-
rawExtraFieldZip64:
|
|
5872
|
+
rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
|
|
6025
5873
|
localExtraFieldZip64Length,
|
|
6026
5874
|
rawExtraFieldExtendedTimestamp,
|
|
6027
5875
|
rawExtraFieldNTFS,
|
|
@@ -6034,7 +5882,6 @@
|
|
|
6034
5882
|
function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
|
|
6035
5883
|
const { headerInfo } = entryInfo;
|
|
6036
5884
|
let { localHeaderArray, extraFieldLength } = headerInfo;
|
|
6037
|
-
let localHeaderArrayView;
|
|
6038
5885
|
let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
|
|
6039
5886
|
if (extraBytesLength < 4) {
|
|
6040
5887
|
extraBytesLength += 64;
|
|
@@ -6047,7 +5894,7 @@
|
|
|
6047
5894
|
headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
|
|
6048
5895
|
arraySet(localHeaderArray, previousLocalHeaderArray);
|
|
6049
5896
|
arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
|
|
6050
|
-
localHeaderArrayView = getDataView(localHeaderArray);
|
|
5897
|
+
const localHeaderArrayView = getDataView(localHeaderArray);
|
|
6051
5898
|
setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
|
|
6052
5899
|
headerInfo.localHeaderView = localHeaderArrayView;
|
|
6053
5900
|
entryInfo.metadataSize += extraBytesLength;
|
|
@@ -6055,7 +5902,7 @@
|
|
|
6055
5902
|
|
|
6056
5903
|
function packUnixId(id) {
|
|
6057
5904
|
if (id === UNDEFINED_VALUE) {
|
|
6058
|
-
return
|
|
5905
|
+
return EMPTY_UINT8_ARRAY;
|
|
6059
5906
|
} else {
|
|
6060
5907
|
const dataArray = new Uint8Array(4);
|
|
6061
5908
|
const dataView = getDataView(dataArray);
|
|
@@ -6098,7 +5945,7 @@
|
|
|
6098
5945
|
dataDescriptor,
|
|
6099
5946
|
dataDescriptorSignature
|
|
6100
5947
|
}) {
|
|
6101
|
-
let dataDescriptorArray =
|
|
5948
|
+
let dataDescriptorArray = EMPTY_UINT8_ARRAY;
|
|
6102
5949
|
let dataDescriptorView, dataDescriptorOffset = 0;
|
|
6103
5950
|
let dataDescriptorLength = zip64 ? DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH : DATA_DESCRIPTOR_RECORD_LENGTH;
|
|
6104
5951
|
if (dataDescriptorSignature) {
|
|
@@ -6182,7 +6029,6 @@
|
|
|
6182
6029
|
}
|
|
6183
6030
|
}
|
|
6184
6031
|
if (zip64 && localExtraFieldZip64Length) {
|
|
6185
|
-
// the local zip64 extra field always contains both size slots
|
|
6186
6032
|
const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
|
|
6187
6033
|
setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
|
|
6188
6034
|
setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
|
|
@@ -6236,7 +6082,7 @@
|
|
|
6236
6082
|
}
|
|
6237
6083
|
rawExtraFieldZip64 = extraFieldZip64.array;
|
|
6238
6084
|
} else {
|
|
6239
|
-
rawExtraFieldZip64 =
|
|
6085
|
+
rawExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
6240
6086
|
}
|
|
6241
6087
|
fileEntry.rawExtraFieldZip64 = rawExtraFieldZip64;
|
|
6242
6088
|
fileEntry.zip64Offset = zip64Offset;
|
|
@@ -6251,7 +6097,7 @@
|
|
|
6251
6097
|
extraFieldTimestamp.uint32(lastModTimeUnix);
|
|
6252
6098
|
rawExtraFieldTimestamp = extraFieldTimestamp.array;
|
|
6253
6099
|
} else {
|
|
6254
|
-
rawExtraFieldTimestamp =
|
|
6100
|
+
rawExtraFieldTimestamp = EMPTY_UINT8_ARRAY;
|
|
6255
6101
|
}
|
|
6256
6102
|
fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
|
|
6257
6103
|
const extraFieldLength = getLength(
|
|
@@ -6307,7 +6153,7 @@
|
|
|
6307
6153
|
await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
|
|
6308
6154
|
directoryDiskOffset = offset;
|
|
6309
6155
|
writer.availableSize = 0;
|
|
6310
|
-
await writeData(writer,
|
|
6156
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6311
6157
|
}
|
|
6312
6158
|
if (indexFileEntry == 0) {
|
|
6313
6159
|
cdStartDiskNumber = writer.diskNumber;
|
|
@@ -6380,7 +6226,7 @@
|
|
|
6380
6226
|
const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
|
|
6381
6227
|
if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
|
|
6382
6228
|
writer.availableSize = 0;
|
|
6383
|
-
await writeData(writer,
|
|
6229
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6384
6230
|
}
|
|
6385
6231
|
lastDiskNumber = writer.diskNumber;
|
|
6386
6232
|
if (zip64) {
|
|
@@ -6477,7 +6323,6 @@
|
|
|
6477
6323
|
return Math.floor(date.getTime() / 1000);
|
|
6478
6324
|
}
|
|
6479
6325
|
|
|
6480
|
-
// the extended timestamp extra field stores dates as signed 32-bit time_t values
|
|
6481
6326
|
function inUnixTimeRange(timeUnix) {
|
|
6482
6327
|
return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
|
|
6483
6328
|
}
|
|
@@ -6650,8 +6495,6 @@
|
|
|
6650
6495
|
uncompressedSize: params.uncompressedSize || 0,
|
|
6651
6496
|
passThrough: params.passThrough
|
|
6652
6497
|
});
|
|
6653
|
-
// detached entries (e.g. clones) are not registered until they are attached,
|
|
6654
|
-
// otherwise they could never be garbage collected
|
|
6655
6498
|
if (parent || !fs.root) {
|
|
6656
6499
|
fs.entries[zipEntry.id] = zipEntry;
|
|
6657
6500
|
}
|
|
@@ -6720,7 +6563,7 @@
|
|
|
6720
6563
|
if (!writer || (writer.constructor == zipEntry.Writer && zipEntry.data)) {
|
|
6721
6564
|
return zipEntry.data;
|
|
6722
6565
|
} else {
|
|
6723
|
-
const reader = zipEntry.reader =
|
|
6566
|
+
const reader = zipEntry.reader = createReader(zipEntry.Reader, zipEntry.data, options);
|
|
6724
6567
|
const uncompressedSize = zipEntry.data ? zipEntry.data.uncompressedSize : reader.size;
|
|
6725
6568
|
await Promise.all([initStream(reader), initStream(writer, uncompressedSize)]);
|
|
6726
6569
|
const { readable } = reader;
|
|
@@ -6776,11 +6619,7 @@
|
|
|
6776
6619
|
}
|
|
6777
6620
|
|
|
6778
6621
|
async getArrayBuffer(options) {
|
|
6779
|
-
// this.data may be a string, a Uint8Array, a data URI or a Blob depending on how the entry
|
|
6780
|
-
// was created, so retrieve the bytes through getUint8Array rather than assuming a Blob; this
|
|
6781
|
-
// also applies the options (password, signal, onprogress...) like the other getData() getters
|
|
6782
6622
|
const array = await this.getUint8Array(options);
|
|
6783
|
-
// return the backing buffer directly when the array spans it, slicing the exact bytes otherwise
|
|
6784
6623
|
return array.byteOffset || array.byteLength != array.buffer.byteLength
|
|
6785
6624
|
? array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength)
|
|
6786
6625
|
: array.buffer;
|
|
@@ -7150,7 +6989,6 @@
|
|
|
7150
6989
|
node = node.getChildByName(path[index]);
|
|
7151
6990
|
}
|
|
7152
6991
|
if (!node) {
|
|
7153
|
-
// exclude detached entries, e.g. clones registered in `entries` but not attached
|
|
7154
6992
|
node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
|
|
7155
6993
|
entry.getRelativeName() == fullname);
|
|
7156
6994
|
}
|
|
@@ -7322,10 +7160,13 @@
|
|
|
7322
7160
|
};
|
|
7323
7161
|
}
|
|
7324
7162
|
|
|
7163
|
+
function createReader(Reader, data, options) {
|
|
7164
|
+
return Reader.prototype ? new Reader(data, options) : Reader(data, options);
|
|
7165
|
+
}
|
|
7166
|
+
|
|
7325
7167
|
async function initReaders(entry, options) {
|
|
7326
7168
|
const fileEntries = [];
|
|
7327
7169
|
const pendingEntries = [entry];
|
|
7328
|
-
// per-export map so concurrent exports do not share (and lock) the same readers
|
|
7329
7170
|
const readers = new Map();
|
|
7330
7171
|
while (pendingEntries.length) {
|
|
7331
7172
|
const pendingEntry = pendingEntries.pop();
|
|
@@ -7338,7 +7179,7 @@
|
|
|
7338
7179
|
}
|
|
7339
7180
|
}
|
|
7340
7181
|
await Promise.all(fileEntries.map(async child => {
|
|
7341
|
-
const reader = child.reader =
|
|
7182
|
+
const reader = child.reader = createReader(child.Reader, child.data, options);
|
|
7342
7183
|
readers.set(child, reader);
|
|
7343
7184
|
try {
|
|
7344
7185
|
await initStream(reader);
|
|
@@ -7503,7 +7344,6 @@
|
|
|
7503
7344
|
return directoryHandle;
|
|
7504
7345
|
|
|
7505
7346
|
async function exportChildren(entry, parentHandle) {
|
|
7506
|
-
// Separate files have no ordering constraint, so they can be written concurrently on demand.
|
|
7507
7347
|
if (options.concurrent) {
|
|
7508
7348
|
const results = await Promise.allSettled(entry.children.map(child => exportChild(child, parentHandle)));
|
|
7509
7349
|
const failedResult = results.find(result => result.status == "rejected");
|
|
@@ -7525,8 +7365,6 @@
|
|
|
7525
7365
|
} else {
|
|
7526
7366
|
const fileHandle = await parentHandle.getFileHandle(child.name, { create: true });
|
|
7527
7367
|
const writable = await fileHandle.createWritable();
|
|
7528
|
-
// getData() streams the entry's data into `writable` and closes it (a FileSystemWritableFileStream
|
|
7529
|
-
// only commits the file on close), applying options such as password, signal and onprogress.
|
|
7530
7368
|
await child.getData({ writable }, Object.assign({}, options, {
|
|
7531
7369
|
onprogress: async progress => {
|
|
7532
7370
|
if (options.onprogress) {
|