@zip.js/zip.js 2.8.29 → 2.8.31
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 +35 -0
- package/deno.json +5 -4
- package/dist/zip-core.js +496 -218
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +398 -195
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +588 -254
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +587 -253
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +501 -223
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +504 -226
- 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 +503 -225
- package/dist/zip.min.js +1 -1
- package/index-native.cjs +588 -254
- package/index-native.min.js +1 -1
- package/index.cjs +587 -253
- package/index.d.cts +2814 -0
- package/index.d.ts +364 -1
- package/index.min.js +1 -1
- package/lib/core/codec-worker.js +0 -10
- package/lib/core/configuration.js +21 -33
- package/lib/core/constants.js +3 -0
- package/lib/core/io.js +11 -19
- package/lib/core/options.js +11 -1
- package/lib/core/streams/aes-crypto-stream.js +16 -20
- package/lib/core/streams/codecs/crc32.js +39 -11
- 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 +69 -19
- 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/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 +139 -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 +61 -10
- package/lib/core/zip-reader.js +162 -53
- package/lib/core/zip-writer.js +34 -41
- 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 +7 -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));
|
|
@@ -988,18 +981,30 @@
|
|
|
988
981
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
989
982
|
*/
|
|
990
983
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
984
|
+
// Slicing-by-8 CRC-32 (Intel / zlib). The eight 256-entry tables let the inner loop
|
|
985
|
+
// consume 8 bytes per iteration with a shorter dependency chain, ~4x the byte-at-a-time
|
|
986
|
+
// rate (measured ~320 -> ~1400 MB/s on 64KB chunks).
|
|
987
|
+
//
|
|
988
|
+
// Every table MUST stay a PACKED_SMI array: build with array literals (not `new Array(n)`,
|
|
989
|
+
// which is HOLEY) and store the signed int32 XOR result (no `>>> 0`). An unsigned or holey
|
|
990
|
+
// table becomes a V8 FixedDoubleArray whose every hot-loop lookup unboxes a double (~1.6x
|
|
991
|
+
// slower). Signedness is irrelevant to the result — the reads mask/shift it and the final
|
|
992
|
+
// `~crc` normalizes it. Do NOT reintroduce `>>> 0` here or switch to `new Array(256)`.
|
|
993
|
+
const T = [[], [], [], [], [], [], [], []];
|
|
994
|
+
for (let n = 0; n < 256; n++) {
|
|
995
|
+
let t = n;
|
|
994
996
|
for (let j = 0; j < 8; j++) {
|
|
995
|
-
|
|
996
|
-
t = (t >>> 1) ^ 0xEDB88320;
|
|
997
|
-
} else {
|
|
998
|
-
t = t >>> 1;
|
|
999
|
-
}
|
|
997
|
+
t = (t & 1) ? (t >>> 1) ^ 0xEDB88320 : t >>> 1;
|
|
1000
998
|
}
|
|
1001
|
-
|
|
999
|
+
T[0][n] = t;
|
|
1002
1000
|
}
|
|
1001
|
+
for (let n = 0; n < 256; n++) {
|
|
1002
|
+
for (let k = 1; k < 8; k++) {
|
|
1003
|
+
const previous = T[k - 1][n];
|
|
1004
|
+
T[k][n] = (previous >>> 8) ^ T[0][previous & 0xFF];
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const [T0, T1, T2, T3, T4, T5, T6, T7] = T;
|
|
1003
1008
|
|
|
1004
1009
|
class Crc32 {
|
|
1005
1010
|
|
|
@@ -1009,8 +1014,24 @@
|
|
|
1009
1014
|
|
|
1010
1015
|
append(data) {
|
|
1011
1016
|
let crc = this.crc | 0;
|
|
1012
|
-
|
|
1013
|
-
|
|
1017
|
+
const length = data.length | 0;
|
|
1018
|
+
let offset = 0;
|
|
1019
|
+
// Process 8 bytes per iteration over the typed-array body. DataView.getInt32(le)
|
|
1020
|
+
// reads an unaligned little-endian word as a signed int32 (no double boxing), so no
|
|
1021
|
+
// alignment or endianness handling is needed; data.buffer guards non-typed inputs.
|
|
1022
|
+
if (length >= 8 && data.buffer) {
|
|
1023
|
+
const view = new DataView(data.buffer, data.byteOffset, length);
|
|
1024
|
+
const end = length - 8;
|
|
1025
|
+
for (; offset <= end; offset += 8) {
|
|
1026
|
+
const a = crc ^ view.getInt32(offset, true);
|
|
1027
|
+
const b = view.getInt32(offset + 4, true);
|
|
1028
|
+
crc = T7[a & 0xFF] ^ T6[(a >>> 8) & 0xFF] ^ T5[(a >>> 16) & 0xFF] ^ T4[(a >>> 24) & 0xFF] ^
|
|
1029
|
+
T3[b & 0xFF] ^ T2[(b >>> 8) & 0xFF] ^ T1[(b >>> 16) & 0xFF] ^ T0[(b >>> 24) & 0xFF];
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// Remaining tail (and non-typed inputs) byte-at-a-time with the base table.
|
|
1033
|
+
for (; offset < length; offset++) {
|
|
1034
|
+
crc = (crc >>> 8) ^ T0[(crc ^ data[offset]) & 0xFF];
|
|
1014
1035
|
}
|
|
1015
1036
|
this.crc = crc;
|
|
1016
1037
|
}
|
|
@@ -1943,8 +1964,6 @@
|
|
|
1943
1964
|
if (GET_RANDOM_VALUES_SUPPORTED) {
|
|
1944
1965
|
return crypto.getRandomValues(array);
|
|
1945
1966
|
} else {
|
|
1946
|
-
// all supported runtimes expose crypto.getRandomValues; failing hard beats
|
|
1947
|
-
// silently generating predictable encryption salts
|
|
1948
1967
|
throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
|
|
1949
1968
|
}
|
|
1950
1969
|
}
|
|
@@ -2007,12 +2026,7 @@
|
|
|
2007
2026
|
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
2008
2027
|
super({
|
|
2009
2028
|
start() {
|
|
2010
|
-
|
|
2011
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
2012
|
-
password: encodePassword(password, rawPassword),
|
|
2013
|
-
strength: encryptionStrength - 1,
|
|
2014
|
-
pending: new Uint8Array()
|
|
2015
|
-
});
|
|
2029
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
2016
2030
|
},
|
|
2017
2031
|
async transform(chunk, controller) {
|
|
2018
2032
|
const aesCrypto = this;
|
|
@@ -2047,7 +2061,7 @@
|
|
|
2047
2061
|
await ready;
|
|
2048
2062
|
const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH);
|
|
2049
2063
|
const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH);
|
|
2050
|
-
let decryptedChunkArray =
|
|
2064
|
+
let decryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
2051
2065
|
if (chunkToDecrypt.length) {
|
|
2052
2066
|
const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
|
|
2053
2067
|
hmac.update(encryptedChunk);
|
|
@@ -2055,8 +2069,6 @@
|
|
|
2055
2069
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
2056
2070
|
}
|
|
2057
2071
|
const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
|
|
2058
|
-
// branch-free comparison: accumulate the differences instead of
|
|
2059
|
-
// short-circuiting on the first mismatching byte
|
|
2060
2072
|
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
2061
2073
|
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
2062
2074
|
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
@@ -2078,12 +2090,7 @@
|
|
|
2078
2090
|
let stream;
|
|
2079
2091
|
super({
|
|
2080
2092
|
start() {
|
|
2081
|
-
|
|
2082
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
2083
|
-
password: encodePassword(password, rawPassword),
|
|
2084
|
-
strength: encryptionStrength - 1,
|
|
2085
|
-
pending: new Uint8Array()
|
|
2086
|
-
});
|
|
2093
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
2087
2094
|
},
|
|
2088
2095
|
async transform(chunk, controller) {
|
|
2089
2096
|
const aesCrypto = this;
|
|
@@ -2093,7 +2100,7 @@
|
|
|
2093
2100
|
resolveReady,
|
|
2094
2101
|
ready
|
|
2095
2102
|
} = aesCrypto;
|
|
2096
|
-
let preamble =
|
|
2103
|
+
let preamble = EMPTY_UINT8_ARRAY;
|
|
2097
2104
|
if (password) {
|
|
2098
2105
|
preamble = await createEncryptionKeys(aesCrypto, strength, password);
|
|
2099
2106
|
resolveReady();
|
|
@@ -2113,7 +2120,7 @@
|
|
|
2113
2120
|
} = this;
|
|
2114
2121
|
if (hmac && ctr) {
|
|
2115
2122
|
await ready;
|
|
2116
|
-
let encryptedChunkArray =
|
|
2123
|
+
let encryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
2117
2124
|
if (pending.length) {
|
|
2118
2125
|
const encryptedChunk = ctr.update(toBits(codecBytes, pending));
|
|
2119
2126
|
hmac.update(encryptedChunk);
|
|
@@ -2128,6 +2135,15 @@
|
|
|
2128
2135
|
}
|
|
2129
2136
|
}
|
|
2130
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
|
+
|
|
2131
2147
|
function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) {
|
|
2132
2148
|
const {
|
|
2133
2149
|
ctr,
|
|
@@ -2289,20 +2305,13 @@
|
|
|
2289
2305
|
constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
|
|
2290
2306
|
super({
|
|
2291
2307
|
start() {
|
|
2292
|
-
|
|
2293
|
-
password,
|
|
2294
|
-
rawPassword,
|
|
2295
|
-
passwordVerification
|
|
2296
|
-
});
|
|
2297
|
-
createKeys(this, password, rawPassword);
|
|
2308
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
2298
2309
|
},
|
|
2299
2310
|
transform(chunk, controller) {
|
|
2300
2311
|
const zipCrypto = this;
|
|
2301
2312
|
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
2302
2313
|
const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
|
|
2303
2314
|
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
2304
|
-
// verify the password with a branch-free comparison of the verification byte, mirroring
|
|
2305
|
-
// the constant-time AES HMAC signature check
|
|
2306
2315
|
if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
|
|
2307
2316
|
throw new Error(ERR_INVALID_PASSWORD);
|
|
2308
2317
|
}
|
|
@@ -2323,12 +2332,7 @@
|
|
|
2323
2332
|
constructor({ password, rawPassword, passwordVerification }) {
|
|
2324
2333
|
super({
|
|
2325
2334
|
start() {
|
|
2326
|
-
|
|
2327
|
-
password,
|
|
2328
|
-
rawPassword,
|
|
2329
|
-
passwordVerification
|
|
2330
|
-
});
|
|
2331
|
-
createKeys(this, password, rawPassword);
|
|
2335
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
2332
2336
|
},
|
|
2333
2337
|
transform(chunk, controller) {
|
|
2334
2338
|
const zipCrypto = this;
|
|
@@ -2352,6 +2356,15 @@
|
|
|
2352
2356
|
}
|
|
2353
2357
|
}
|
|
2354
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
|
+
|
|
2355
2368
|
function decrypt(target, input) {
|
|
2356
2369
|
const output = new Uint8Array(input.length);
|
|
2357
2370
|
for (let index = 0; index < input.length; index++) {
|
|
@@ -2444,21 +2457,32 @@
|
|
|
2444
2457
|
const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
|
|
2445
2458
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
2446
2459
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
2460
|
+
const FORMAT_GZIP = "gzip";
|
|
2461
|
+
const GZIP_HEADER_LENGTH = 10;
|
|
2462
|
+
const GZIP_TRAILER_LENGTH = 8;
|
|
2447
2463
|
|
|
2448
2464
|
class DeflateStream extends TransformStream {
|
|
2449
2465
|
|
|
2450
2466
|
constructor(options, { chunkSize, CompressionStreamZlib, CompressionStream }) {
|
|
2451
2467
|
super({});
|
|
2452
|
-
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level } = options;
|
|
2468
|
+
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level, deflate64 } = options;
|
|
2453
2469
|
const stream = this;
|
|
2454
|
-
let crc32Stream, encryptionStream;
|
|
2470
|
+
let crc32Stream, encryptionStream, gzipCrc32Stream;
|
|
2455
2471
|
let readable = super.readable;
|
|
2456
|
-
|
|
2472
|
+
const useGzipCrc32 = signed && compressed && !deflate64 && (!encrypted || zipCrypto) &&
|
|
2473
|
+
Boolean(useCompressionStream && CompressionStream);
|
|
2474
|
+
if ((!encrypted || zipCrypto) && signed && !useGzipCrc32) {
|
|
2457
2475
|
crc32Stream = new Crc32Stream();
|
|
2458
2476
|
readable = pipeThrough(readable, crc32Stream);
|
|
2459
2477
|
}
|
|
2460
2478
|
if (compressed) {
|
|
2461
|
-
|
|
2479
|
+
if (useGzipCrc32) {
|
|
2480
|
+
gzipCrc32Stream = new GzipToRawDeflateStream();
|
|
2481
|
+
readable = pipeThroughBackpressured(readable, new CompressionStream(FORMAT_GZIP));
|
|
2482
|
+
readable = pipeThrough(readable, gzipCrc32Stream);
|
|
2483
|
+
} else {
|
|
2484
|
+
readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib, CompressionStream);
|
|
2485
|
+
}
|
|
2462
2486
|
}
|
|
2463
2487
|
if (encrypted) {
|
|
2464
2488
|
if (zipCrypto) {
|
|
@@ -2474,13 +2498,64 @@
|
|
|
2474
2498
|
signature = encryptionStream.signature;
|
|
2475
2499
|
}
|
|
2476
2500
|
if ((!encrypted || zipCrypto) && signed) {
|
|
2477
|
-
signature = new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
2501
|
+
signature = useGzipCrc32 ? gzipCrc32Stream.signature : new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
2478
2502
|
}
|
|
2479
2503
|
stream.signature = signature;
|
|
2480
2504
|
});
|
|
2481
2505
|
}
|
|
2482
2506
|
}
|
|
2483
2507
|
|
|
2508
|
+
class GzipToRawDeflateStream extends TransformStream {
|
|
2509
|
+
|
|
2510
|
+
constructor() {
|
|
2511
|
+
// deno-lint-ignore prefer-const
|
|
2512
|
+
let stream;
|
|
2513
|
+
let headerLeft = GZIP_HEADER_LENGTH;
|
|
2514
|
+
let tail = new Uint8Array(0);
|
|
2515
|
+
super({
|
|
2516
|
+
transform(chunk, controller) {
|
|
2517
|
+
if (headerLeft) {
|
|
2518
|
+
const dropped = Math.min(headerLeft, chunk.length);
|
|
2519
|
+
headerLeft -= dropped;
|
|
2520
|
+
chunk = chunk.subarray(dropped);
|
|
2521
|
+
if (!chunk.length) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
const available = tail.length + chunk.length;
|
|
2526
|
+
if (available <= GZIP_TRAILER_LENGTH) {
|
|
2527
|
+
const pending = new Uint8Array(available);
|
|
2528
|
+
pending.set(tail);
|
|
2529
|
+
pending.set(chunk, tail.length);
|
|
2530
|
+
tail = pending;
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
const emitLength = available - GZIP_TRAILER_LENGTH;
|
|
2534
|
+
const output = new Uint8Array(emitLength);
|
|
2535
|
+
const fromTail = Math.min(emitLength, tail.length);
|
|
2536
|
+
output.set(tail.subarray(0, fromTail), 0);
|
|
2537
|
+
if (emitLength > fromTail) {
|
|
2538
|
+
output.set(chunk.subarray(0, emitLength - fromTail), fromTail);
|
|
2539
|
+
}
|
|
2540
|
+
controller.enqueue(output);
|
|
2541
|
+
const nextTail = new Uint8Array(GZIP_TRAILER_LENGTH);
|
|
2542
|
+
const tailRemaining = tail.length - fromTail;
|
|
2543
|
+
if (tailRemaining) {
|
|
2544
|
+
nextTail.set(tail.subarray(fromTail), 0);
|
|
2545
|
+
}
|
|
2546
|
+
nextTail.set(chunk.subarray(emitLength - fromTail), tailRemaining);
|
|
2547
|
+
tail = nextTail;
|
|
2548
|
+
},
|
|
2549
|
+
flush() {
|
|
2550
|
+
const dataView = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);
|
|
2551
|
+
stream.signature = dataView.getUint32(0, true);
|
|
2552
|
+
stream.uncompressedSize = dataView.getUint32(4, true);
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
stream = this;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2484
2559
|
class InflateStream extends TransformStream {
|
|
2485
2560
|
|
|
2486
2561
|
constructor(options, { chunkSize, DecompressionStreamZlib, DecompressionStream }) {
|
|
@@ -2543,11 +2618,6 @@
|
|
|
2543
2618
|
throw error;
|
|
2544
2619
|
}
|
|
2545
2620
|
}
|
|
2546
|
-
// The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
|
|
2547
|
-
// backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
|
|
2548
|
-
// the whole source into them and peak memory grows with the entry size. Awaiting each write()
|
|
2549
|
-
// paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
|
|
2550
|
-
// codec already backpressures, so this is a no-op cost for it.
|
|
2551
2621
|
return pipeThroughBackpressured(readable, codecStream);
|
|
2552
2622
|
}
|
|
2553
2623
|
|
|
@@ -2555,10 +2625,6 @@
|
|
|
2555
2625
|
return readable.pipeThrough(transformStream);
|
|
2556
2626
|
}
|
|
2557
2627
|
|
|
2558
|
-
// Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
|
|
2559
|
-
// write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
|
|
2560
|
-
// Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
|
|
2561
|
-
// (surfaced through the codec's writable rejecting) cancels the source.
|
|
2562
2628
|
function pipeThroughBackpressured(readable, transformStream) {
|
|
2563
2629
|
const writer = transformStream.writable.getWriter();
|
|
2564
2630
|
const reader = readable.getReader();
|
|
@@ -2587,7 +2653,7 @@
|
|
|
2587
2653
|
try {
|
|
2588
2654
|
await writer.abort(error);
|
|
2589
2655
|
} catch {
|
|
2590
|
-
// the writable may already be errored/closed
|
|
2656
|
+
// ignored: the writable may already be errored/closed
|
|
2591
2657
|
}
|
|
2592
2658
|
}
|
|
2593
2659
|
|
|
@@ -2595,13 +2661,10 @@
|
|
|
2595
2661
|
try {
|
|
2596
2662
|
await reader.cancel(error);
|
|
2597
2663
|
} catch {
|
|
2598
|
-
// the readable may already be errored/closed
|
|
2664
|
+
// ignored: the readable may already be errored/closed
|
|
2599
2665
|
}
|
|
2600
2666
|
}
|
|
2601
2667
|
|
|
2602
|
-
// DecompressionStream implementations can fail with a message-less TypeError on malformed
|
|
2603
|
-
// input; give these errors an identifiable message (errors raised by the other stages of
|
|
2604
|
-
// the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
|
|
2605
2668
|
function mapInflateStreamError(readable) {
|
|
2606
2669
|
const reader = readable.getReader();
|
|
2607
2670
|
return new ReadableStream({
|
|
@@ -2926,9 +2989,6 @@
|
|
|
2926
2989
|
try {
|
|
2927
2990
|
await initModule(config);
|
|
2928
2991
|
} catch {
|
|
2929
|
-
// The WASM module failed to load. Fall back to the native CompressionStream only if the
|
|
2930
|
-
// zlib codec that would otherwise run depends on that module; a self-contained codec
|
|
2931
|
-
// supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
|
|
2932
2992
|
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
2933
2993
|
config.CompressionStreamZlib :
|
|
2934
2994
|
config.DecompressionStreamZlib;
|
|
@@ -3006,7 +3066,6 @@
|
|
|
3006
3066
|
return resultValue;
|
|
3007
3067
|
} catch (error) {
|
|
3008
3068
|
await closeWritable();
|
|
3009
|
-
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
3010
3069
|
abortPipe();
|
|
3011
3070
|
try {
|
|
3012
3071
|
await closed;
|
|
@@ -3031,8 +3090,6 @@
|
|
|
3031
3090
|
const abortController = new AbortController();
|
|
3032
3091
|
const { writable, readable } = new TransformStream();
|
|
3033
3092
|
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
3034
|
-
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
3035
|
-
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
3036
3093
|
closed.catch(() => { });
|
|
3037
3094
|
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
3038
3095
|
}
|
|
@@ -3125,8 +3182,6 @@
|
|
|
3125
3182
|
event.preventDefault();
|
|
3126
3183
|
}
|
|
3127
3184
|
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
3128
|
-
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
3129
|
-
// fail the current task instead of letting it hang forever
|
|
3130
3185
|
terminateWorker$1(workerData);
|
|
3131
3186
|
if (rejectResult) {
|
|
3132
3187
|
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
@@ -3178,8 +3233,6 @@
|
|
|
3178
3233
|
async function onMessage({ data }, workerData) {
|
|
3179
3234
|
const { type, value, messageId, result, error } = data;
|
|
3180
3235
|
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
3181
|
-
// continuations below can settle after the worker slot has been reassigned to a new
|
|
3182
|
-
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
3183
3236
|
const stale = () => workerData.generation != generation;
|
|
3184
3237
|
try {
|
|
3185
3238
|
if (error) {
|
|
@@ -3443,7 +3496,6 @@
|
|
|
3443
3496
|
if (encoding && encoding.trim().toLowerCase() == "cp437") {
|
|
3444
3497
|
return decodeCP437(value);
|
|
3445
3498
|
} else {
|
|
3446
|
-
// ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
|
|
3447
3499
|
return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
|
|
3448
3500
|
}
|
|
3449
3501
|
}
|
|
@@ -3641,6 +3693,11 @@
|
|
|
3641
3693
|
const OPTION_OFFSET = "offset";
|
|
3642
3694
|
const OPTION_USDZ = "usdz";
|
|
3643
3695
|
const OPTION_UNIX_EXTRA_FIELD_TYPE = "unixExtraFieldType";
|
|
3696
|
+
const OPTION_STRICTNESS = "strictness";
|
|
3697
|
+
const OPTION_MAX_APPENDED_DATA_SIZE = "maxAppendedDataSize";
|
|
3698
|
+
const STRICTNESS_STRICT = "strict";
|
|
3699
|
+
const STRICTNESS_BALANCED = "balanced";
|
|
3700
|
+
const STRICTNESS_TOLERANT = "tolerant";
|
|
3644
3701
|
|
|
3645
3702
|
/*
|
|
3646
3703
|
Copyright (c) 2025 Gildas Lormeau. All rights reserved.
|
|
@@ -3686,8 +3743,6 @@
|
|
|
3686
3743
|
const CHARSET_UTF8 = "utf-8";
|
|
3687
3744
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
3688
3745
|
const CHARSET_CP437 = "cp437";
|
|
3689
|
-
// bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
|
|
3690
|
-
// because they are informative only
|
|
3691
3746
|
const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
|
|
3692
3747
|
const ZIP64_PROPERTIES = [
|
|
3693
3748
|
[PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
|
|
@@ -3729,8 +3784,11 @@
|
|
|
3729
3784
|
if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
|
|
3730
3785
|
throw new Error(ERR_BAD_FORMAT);
|
|
3731
3786
|
}
|
|
3732
|
-
|
|
3733
|
-
const
|
|
3787
|
+
const strictness = getStrictness(getOptionValue$1(zipReader, options, OPTION_STRICTNESS), getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY));
|
|
3788
|
+
const checkAmbiguity = strictness == STRICTNESS_STRICT;
|
|
3789
|
+
const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
|
|
3790
|
+
const maxAppendedDataSize = getMaxAppendedDataSize(getOptionValue$1(zipReader, options, OPTION_MAX_APPENDED_DATA_SIZE), strictness);
|
|
3791
|
+
const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
|
|
3734
3792
|
if (!endOfDirectoryInfo) {
|
|
3735
3793
|
const signatureArray = await readUint8Array(reader, 0, 4);
|
|
3736
3794
|
const signatureView = getDataView$1(signatureArray);
|
|
@@ -3740,14 +3798,16 @@
|
|
|
3740
3798
|
throw new Error(ERR_EOCDR_NOT_FOUND);
|
|
3741
3799
|
}
|
|
3742
3800
|
}
|
|
3801
|
+
if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
|
|
3802
|
+
throwAmbiguousArchive("multiple end of central directory records");
|
|
3803
|
+
}
|
|
3743
3804
|
const endOfDirectoryView = getDataView$1(endOfDirectoryInfo);
|
|
3744
3805
|
let directoryDataLength = getUint32(endOfDirectoryView, 12);
|
|
3745
3806
|
let directoryDataOffset = getUint32(endOfDirectoryView, 16);
|
|
3746
3807
|
const commentOffset = endOfDirectoryInfo.offset;
|
|
3747
3808
|
const commentLength = getUint16(endOfDirectoryView, 20);
|
|
3748
3809
|
const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
|
|
3749
|
-
|
|
3750
|
-
if (checkAmbiguity && appendedDataOffset != reader.size) {
|
|
3810
|
+
if (reader.size - appendedDataOffset > maxAppendedDataSize) {
|
|
3751
3811
|
throwAmbiguousArchive("appended data");
|
|
3752
3812
|
}
|
|
3753
3813
|
let lastDiskNumber = getUint16(endOfDirectoryView, 4);
|
|
@@ -3760,7 +3820,7 @@
|
|
|
3760
3820
|
if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
|
|
3761
3821
|
const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
|
|
3762
3822
|
await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
|
|
3763
|
-
|
|
3823
|
+
EMPTY_UINT8_ARRAY;
|
|
3764
3824
|
const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
|
|
3765
3825
|
if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
|
|
3766
3826
|
getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
@@ -3826,14 +3886,22 @@
|
|
|
3826
3886
|
throw new Error(ERR_BAD_FORMAT);
|
|
3827
3887
|
}
|
|
3828
3888
|
const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
|
|
3829
|
-
if (
|
|
3830
|
-
const
|
|
3831
|
-
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
3889
|
+
if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
|
|
3890
|
+
const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE;
|
|
3891
|
+
let reconcile = !storedPointsAtDirectory;
|
|
3892
|
+
if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
|
|
3893
|
+
const expectedSignatureArray = await readUint8Array(reader, expectedDirectoryDataOffset, 4, diskNumber);
|
|
3894
|
+
reconcile = getUint32(getDataView$1(expectedSignatureArray), 0) == CENTRAL_FILE_HEADER_SIGNATURE;
|
|
3895
|
+
}
|
|
3896
|
+
if (reconcile) {
|
|
3897
|
+
const originalDirectoryDataOffset = directoryDataOffset;
|
|
3898
|
+
directoryDataOffset = expectedDirectoryDataOffset;
|
|
3899
|
+
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
3900
|
+
prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
|
|
3901
|
+
}
|
|
3902
|
+
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3903
|
+
directoryView = getDataView$1(directoryArray);
|
|
3834
3904
|
}
|
|
3835
|
-
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3836
|
-
directoryView = getDataView$1(directoryArray);
|
|
3837
3905
|
}
|
|
3838
3906
|
}
|
|
3839
3907
|
const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
|
|
@@ -3975,11 +4043,11 @@
|
|
|
3975
4043
|
const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
|
|
3976
4044
|
const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
|
|
3977
4045
|
if (extractPrependedData) {
|
|
3978
|
-
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) :
|
|
4046
|
+
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : EMPTY_UINT8_ARRAY;
|
|
3979
4047
|
}
|
|
3980
|
-
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;
|
|
3981
4049
|
if (extractAppendedData) {
|
|
3982
|
-
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;
|
|
3983
4051
|
}
|
|
3984
4052
|
return true;
|
|
3985
4053
|
}
|
|
@@ -4050,8 +4118,8 @@
|
|
|
4050
4118
|
extraFieldLength,
|
|
4051
4119
|
filenameLength
|
|
4052
4120
|
} = localDirectory;
|
|
4053
|
-
const checkAmbiguity = getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY);
|
|
4054
|
-
let rawLocalFilename =
|
|
4121
|
+
const checkAmbiguity = getStrictness(getOptionValue$1(zipEntry, options, OPTION_STRICTNESS), getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY)) == STRICTNESS_STRICT;
|
|
4122
|
+
let rawLocalFilename = EMPTY_UINT8_ARRAY;
|
|
4055
4123
|
if (checkAmbiguity && (filenameLength || extraFieldLength)) {
|
|
4056
4124
|
const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
|
|
4057
4125
|
rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
|
|
@@ -4059,7 +4127,7 @@
|
|
|
4059
4127
|
} else {
|
|
4060
4128
|
localDirectory.rawExtraField = extraFieldLength ?
|
|
4061
4129
|
await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
|
|
4062
|
-
|
|
4130
|
+
EMPTY_UINT8_ARRAY;
|
|
4063
4131
|
}
|
|
4064
4132
|
readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
|
|
4065
4133
|
if (checkAmbiguity) {
|
|
@@ -4086,12 +4154,7 @@
|
|
|
4086
4154
|
}
|
|
4087
4155
|
const dataOffset = offset + HEADER_SIZE + filenameLength + extraFieldLength;
|
|
4088
4156
|
const size = compressedSize;
|
|
4089
|
-
const readable = reader.
|
|
4090
|
-
Object.assign(readable, {
|
|
4091
|
-
diskNumberStart,
|
|
4092
|
-
offset: dataOffset,
|
|
4093
|
-
size
|
|
4094
|
-
});
|
|
4157
|
+
const readable = reader.createReadable({ offset: dataOffset, size, diskNumberStart, chunkSize: getChunkSize(config) });
|
|
4095
4158
|
const signal = getOptionValue$1(zipEntry, options, OPTION_SIGNAL);
|
|
4096
4159
|
const checkPasswordOnly = getOptionValue$1(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
|
|
4097
4160
|
let checkOverlappingEntry = getOptionValue$1(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
|
|
@@ -4362,7 +4425,6 @@
|
|
|
4362
4425
|
const view = getDataView$1(new Uint8Array(extraField.data));
|
|
4363
4426
|
let uid, gid;
|
|
4364
4427
|
if (isInfoZip) {
|
|
4365
|
-
// Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
|
|
4366
4428
|
let offset = 0;
|
|
4367
4429
|
const version = getUint8(view, offset++);
|
|
4368
4430
|
const uidSize = getUint8(view, offset++);
|
|
@@ -4372,8 +4434,6 @@
|
|
|
4372
4434
|
gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
|
|
4373
4435
|
Object.assign(extraField, { version, uid, gid });
|
|
4374
4436
|
} else if (extraField.data.length >= 4) {
|
|
4375
|
-
// Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
|
|
4376
|
-
// carried in the external file attributes, not here)
|
|
4377
4437
|
uid = getUint16(view, 0);
|
|
4378
4438
|
gid = getUint16(view, 2);
|
|
4379
4439
|
Object.assign(extraField, { uid, gid });
|
|
@@ -4425,7 +4485,6 @@
|
|
|
4425
4485
|
timeProperties.forEach((propertyName, indexProperty) => {
|
|
4426
4486
|
if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
|
|
4427
4487
|
const time = getUint32(extraFieldView, offset);
|
|
4428
|
-
// the extended timestamp is a signed 32-bit time_t value
|
|
4429
4488
|
directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
|
|
4430
4489
|
const rawPropertyName = timeRawProperties[indexProperty];
|
|
4431
4490
|
extraFieldExtendedTimestamp[rawPropertyName] = time;
|
|
@@ -4500,26 +4559,133 @@
|
|
|
4500
4559
|
readRanges.set(index, range);
|
|
4501
4560
|
}
|
|
4502
4561
|
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4562
|
+
function getStrictness(strictness, checkAmbiguity) {
|
|
4563
|
+
if (strictness === UNDEFINED_VALUE) {
|
|
4564
|
+
return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
|
|
4565
|
+
}
|
|
4566
|
+
return strictness;
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
|
|
4570
|
+
if (maxAppendedDataSize !== UNDEFINED_VALUE) {
|
|
4571
|
+
return maxAppendedDataSize;
|
|
4572
|
+
}
|
|
4573
|
+
if (strictness == STRICTNESS_STRICT) {
|
|
4574
|
+
return 0;
|
|
4575
|
+
}
|
|
4576
|
+
if (strictness == STRICTNESS_TOLERANT) {
|
|
4577
|
+
return Infinity;
|
|
4578
|
+
}
|
|
4579
|
+
return MAX_16_BITS;
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
|
|
4583
|
+
|
|
4584
|
+
const CENTRAL_DIRECTORY_UNREACHABLE = 0;
|
|
4585
|
+
const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
|
|
4586
|
+
const CENTRAL_DIRECTORY_REACHABLE = 2;
|
|
4587
|
+
|
|
4588
|
+
async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
|
|
4589
|
+
const { size } = reader;
|
|
4590
|
+
const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
|
|
4591
|
+
const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
|
|
4592
|
+
let endOfDirectoryInfo;
|
|
4593
|
+
let plausibleEndOfDirectoryInfo;
|
|
4594
|
+
let endOfDirectoryReachingEndCount = 0;
|
|
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);
|
|
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);
|
|
4520
4609
|
}
|
|
4521
4610
|
}
|
|
4522
4611
|
}
|
|
4612
|
+
if (!endOfDirectoryInfo) {
|
|
4613
|
+
endOfDirectoryInfo = plausibleEndOfDirectoryInfo;
|
|
4614
|
+
}
|
|
4615
|
+
if (!endOfDirectoryInfo) {
|
|
4616
|
+
endOfDirectoryInfo = await seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget);
|
|
4617
|
+
}
|
|
4618
|
+
return { endOfDirectoryInfo, endOfDirectoryReachingEndCount };
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
async function seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget) {
|
|
4622
|
+
const { size } = reader;
|
|
4623
|
+
const searchLength = Math.min(size, maxAppendedDataSize == Infinity ? size :
|
|
4624
|
+
END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS + maxAppendedDataSize);
|
|
4625
|
+
let firstSignatureInfo, plausibleInfo;
|
|
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;
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
return plausibleInfo || firstSignatureInfo;
|
|
4640
|
+
}
|
|
4641
|
+
|
|
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
|
+
|
|
4657
|
+
async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
|
|
4658
|
+
const filesLength = getUint16(view, indexByte + 10);
|
|
4659
|
+
const directoryDataLength = getUint32(view, indexByte + 12);
|
|
4660
|
+
const directoryDataOffset = getUint32(view, indexByte + 16);
|
|
4661
|
+
if (filesLength == MAX_16_BITS || directoryDataLength == MAX_32_BITS || directoryDataOffset == MAX_32_BITS) {
|
|
4662
|
+
const locatorSignature = await readSignature(reader, view, anchoredOffset, offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, size, remoteProbeBudget);
|
|
4663
|
+
return locatorSignature == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE ? CENTRAL_DIRECTORY_REACHABLE : CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4664
|
+
}
|
|
4665
|
+
if (!filesLength && !directoryDataLength) {
|
|
4666
|
+
return CENTRAL_DIRECTORY_PLAUSIBLE;
|
|
4667
|
+
}
|
|
4668
|
+
for (const centralDirectoryOffset of [offset - directoryDataLength, directoryDataOffset]) {
|
|
4669
|
+
if (await readSignature(reader, view, anchoredOffset, centralDirectoryOffset, size, remoteProbeBudget) == CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
4670
|
+
return CENTRAL_DIRECTORY_REACHABLE;
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
return CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
async function readSignature(reader, view, anchoredOffset, signatureOffset, size, remoteProbeBudget) {
|
|
4677
|
+
if (signatureOffset < 0 || signatureOffset + 4 > size) {
|
|
4678
|
+
return UNDEFINED_VALUE;
|
|
4679
|
+
}
|
|
4680
|
+
if (signatureOffset >= anchoredOffset) {
|
|
4681
|
+
return getUint32(view, signatureOffset - anchoredOffset);
|
|
4682
|
+
}
|
|
4683
|
+
if (remoteProbeBudget.count > 0) {
|
|
4684
|
+
remoteProbeBudget.count--;
|
|
4685
|
+
const signatureArray = await readUint8Array(reader, signatureOffset, 4);
|
|
4686
|
+
return getUint32(getDataView$1(signatureArray), 0);
|
|
4687
|
+
}
|
|
4688
|
+
return UNDEFINED_VALUE;
|
|
4523
4689
|
}
|
|
4524
4690
|
|
|
4525
4691
|
function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
|
|
@@ -4534,8 +4700,6 @@
|
|
|
4534
4700
|
if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
|
|
4535
4701
|
throwAmbiguousArchive("mismatched local file header (compression method)");
|
|
4536
4702
|
}
|
|
4537
|
-
// when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
|
|
4538
|
-
// also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
|
|
4539
4703
|
if (!localDirectory.bitFlag.dataDescriptor &&
|
|
4540
4704
|
(localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
|
|
4541
4705
|
(localDirectory.signature != zipEntry.signature ||
|
|
@@ -4584,10 +4748,6 @@
|
|
|
4584
4748
|
return Number(view.getBigUint64(offset, true));
|
|
4585
4749
|
}
|
|
4586
4750
|
|
|
4587
|
-
function setUint32$1(view, offset, value) {
|
|
4588
|
-
view.setUint32(offset, value, true);
|
|
4589
|
-
}
|
|
4590
|
-
|
|
4591
4751
|
function getDataView$1(array) {
|
|
4592
4752
|
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
4593
4753
|
}
|
|
@@ -4629,7 +4789,6 @@
|
|
|
4629
4789
|
const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
|
|
4630
4790
|
const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
|
|
4631
4791
|
const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
|
|
4632
|
-
// same message as the constant exported by zip-reader.js
|
|
4633
4792
|
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
4634
4793
|
const MIN_UNIX_TIME = -2147483648;
|
|
4635
4794
|
const MAX_UNIX_TIME = 2147483647;
|
|
@@ -4712,12 +4871,12 @@
|
|
|
4712
4871
|
rawExtraField,
|
|
4713
4872
|
} = entry;
|
|
4714
4873
|
const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
|
|
4715
|
-
rawExtraFieldZip64 = rawExtraFieldZip64 ||
|
|
4716
|
-
rawExtraFieldAES = rawExtraFieldAES ||
|
|
4717
|
-
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp ||
|
|
4718
|
-
rawExtraFieldNTFS = rawExtraFieldNTFS ||
|
|
4719
|
-
rawExtraFieldUnix = rawExtraFieldUnix ||
|
|
4720
|
-
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;
|
|
4721
4880
|
if (entry.extraFieldAES) {
|
|
4722
4881
|
compressionMethod = COMPRESSION_METHOD_AES;
|
|
4723
4882
|
}
|
|
@@ -4823,7 +4982,7 @@
|
|
|
4823
4982
|
return false;
|
|
4824
4983
|
}
|
|
4825
4984
|
|
|
4826
|
-
async close(comment =
|
|
4985
|
+
async close(comment = EMPTY_UINT8_ARRAY, options = {}) {
|
|
4827
4986
|
const zipWriter = this;
|
|
4828
4987
|
const { pendingAddFileCalls, writer } = this;
|
|
4829
4988
|
const { writable } = writer;
|
|
@@ -5060,7 +5219,7 @@
|
|
|
5060
5219
|
if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
|
|
5061
5220
|
throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
|
|
5062
5221
|
}
|
|
5063
|
-
let rawExtraField =
|
|
5222
|
+
let rawExtraField = EMPTY_UINT8_ARRAY;
|
|
5064
5223
|
const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
|
|
5065
5224
|
if (extraField) {
|
|
5066
5225
|
let extraFieldSize = 0;
|
|
@@ -5121,8 +5280,6 @@
|
|
|
5121
5280
|
let maximumCompressedSize = 0;
|
|
5122
5281
|
let uncompressedSize = 0;
|
|
5123
5282
|
if (passThrough) {
|
|
5124
|
-
// the headers of a passThrough entry describe the payload verbatim: without a
|
|
5125
|
-
// reader they would declare content that is not there
|
|
5126
5283
|
if (!reader) {
|
|
5127
5284
|
throw new Error(ERR_UNDEFINED_READER);
|
|
5128
5285
|
}
|
|
@@ -5133,8 +5290,6 @@
|
|
|
5133
5290
|
}
|
|
5134
5291
|
const zip64Enabled = zip64 === true;
|
|
5135
5292
|
const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
|
|
5136
|
-
// entries without a reader have no payload: write them as empty stored files instead of
|
|
5137
|
-
// declaring a compressed or encrypted content that does not exist
|
|
5138
5293
|
const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
|
|
5139
5294
|
if (!reader) {
|
|
5140
5295
|
level = 0;
|
|
@@ -5272,7 +5427,6 @@
|
|
|
5272
5427
|
fileEntry.diskNumberStart = writer.diskNumber;
|
|
5273
5428
|
fileEntry.offset = getSegmentOffset(zipWriter, writer);
|
|
5274
5429
|
if (usdz) {
|
|
5275
|
-
// the padding depends on the entry offset, only known now that the writer is locked
|
|
5276
5430
|
const previousMetadataSize = entryInfo.metadataSize;
|
|
5277
5431
|
appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
|
|
5278
5432
|
fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
|
|
@@ -5315,6 +5469,13 @@
|
|
|
5315
5469
|
if (releaseLockWriter) {
|
|
5316
5470
|
releaseLockWriter();
|
|
5317
5471
|
}
|
|
5472
|
+
if (bufferedWrite && fileWriter && fileWriter.dispose) {
|
|
5473
|
+
try {
|
|
5474
|
+
await fileWriter.dispose();
|
|
5475
|
+
} catch {
|
|
5476
|
+
// ignored
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5318
5479
|
}
|
|
5319
5480
|
|
|
5320
5481
|
function requestLockCurrentFileEntry() {
|
|
@@ -5334,7 +5495,7 @@
|
|
|
5334
5495
|
async function skipDiskIfNeeded() {
|
|
5335
5496
|
if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
|
|
5336
5497
|
writer.availableSize = 0;
|
|
5337
|
-
await writeData(writer,
|
|
5498
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
5338
5499
|
}
|
|
5339
5500
|
}
|
|
5340
5501
|
}
|
|
@@ -5445,8 +5606,7 @@
|
|
|
5445
5606
|
}
|
|
5446
5607
|
const { writable } = writer;
|
|
5447
5608
|
if (reader) {
|
|
5448
|
-
reader.chunkSize
|
|
5449
|
-
const readable = reader.readable;
|
|
5609
|
+
const readable = reader.createReadable ? reader.createReadable({ chunkSize: getChunkSize(config) }) : reader.readable;
|
|
5450
5610
|
const size = reader.size;
|
|
5451
5611
|
const workerOptions = {
|
|
5452
5612
|
options: {
|
|
@@ -5546,13 +5706,9 @@
|
|
|
5546
5706
|
: compressionMethod !== COMPRESSION_METHOD_STORE);
|
|
5547
5707
|
let rawLocalExtraFieldZip64;
|
|
5548
5708
|
const uncompressedFile = passThrough || !compressed;
|
|
5549
|
-
// entries without a data descriptor are always buffered (cf. getFileEntry), so the
|
|
5550
|
-
// zip64 extra field values can be patched into the local header before flushing it
|
|
5551
5709
|
const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
|
|
5552
5710
|
const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
|
|
5553
5711
|
if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
|
|
5554
|
-
// APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
|
|
5555
|
-
// uncompressed and compressed size fields
|
|
5556
5712
|
const length = 4 + 16;
|
|
5557
5713
|
const extraFieldZip64 = createRecordWriter(length);
|
|
5558
5714
|
extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
|
|
@@ -5566,7 +5722,7 @@
|
|
|
5566
5722
|
}
|
|
5567
5723
|
}
|
|
5568
5724
|
} else {
|
|
5569
|
-
rawLocalExtraFieldZip64 =
|
|
5725
|
+
rawLocalExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
5570
5726
|
}
|
|
5571
5727
|
let rawExtraFieldAES;
|
|
5572
5728
|
if (encrypted && !zipCrypto) {
|
|
@@ -5576,7 +5732,7 @@
|
|
|
5576
5732
|
rawExtraFieldAES = extraFieldAES.array;
|
|
5577
5733
|
rawExtraFieldAES[8] = encryptionStrength;
|
|
5578
5734
|
} else {
|
|
5579
|
-
rawExtraFieldAES =
|
|
5735
|
+
rawExtraFieldAES = EMPTY_UINT8_ARRAY;
|
|
5580
5736
|
}
|
|
5581
5737
|
let rawExtraFieldNTFS;
|
|
5582
5738
|
let rawExtraFieldExtendedTimestamp;
|
|
@@ -5599,8 +5755,7 @@
|
|
|
5599
5755
|
}
|
|
5600
5756
|
rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
|
|
5601
5757
|
} else {
|
|
5602
|
-
|
|
5603
|
-
rawExtraFieldExtendedTimestamp = new Uint8Array();
|
|
5758
|
+
rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5604
5759
|
}
|
|
5605
5760
|
try {
|
|
5606
5761
|
const lastModTimeNTFS = getTimeNTFS(lastModDate);
|
|
@@ -5615,10 +5770,10 @@
|
|
|
5615
5770
|
extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
|
|
5616
5771
|
rawExtraFieldNTFS = extraFieldNTFS.array;
|
|
5617
5772
|
} catch {
|
|
5618
|
-
rawExtraFieldNTFS =
|
|
5773
|
+
rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
|
|
5619
5774
|
}
|
|
5620
5775
|
} else {
|
|
5621
|
-
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp =
|
|
5776
|
+
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5622
5777
|
}
|
|
5623
5778
|
let rawExtraFieldUnix;
|
|
5624
5779
|
try {
|
|
@@ -5644,10 +5799,10 @@
|
|
|
5644
5799
|
extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
|
|
5645
5800
|
rawExtraFieldUnix = extraFieldUnix.array;
|
|
5646
5801
|
} else {
|
|
5647
|
-
rawExtraFieldUnix =
|
|
5802
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5648
5803
|
}
|
|
5649
5804
|
} catch {
|
|
5650
|
-
rawExtraFieldUnix =
|
|
5805
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5651
5806
|
}
|
|
5652
5807
|
if (compressionMethod === UNDEFINED_VALUE) {
|
|
5653
5808
|
compressionMethod = compressed ? COMPRESSION_METHOD_DEFLATE : COMPRESSION_METHOD_STORE;
|
|
@@ -5714,7 +5869,7 @@
|
|
|
5714
5869
|
version,
|
|
5715
5870
|
compressionMethod,
|
|
5716
5871
|
extraFieldExtendedTimestampFlag,
|
|
5717
|
-
rawExtraFieldZip64:
|
|
5872
|
+
rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
|
|
5718
5873
|
localExtraFieldZip64Length,
|
|
5719
5874
|
rawExtraFieldExtendedTimestamp,
|
|
5720
5875
|
rawExtraFieldNTFS,
|
|
@@ -5727,7 +5882,6 @@
|
|
|
5727
5882
|
function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
|
|
5728
5883
|
const { headerInfo } = entryInfo;
|
|
5729
5884
|
let { localHeaderArray, extraFieldLength } = headerInfo;
|
|
5730
|
-
let localHeaderArrayView;
|
|
5731
5885
|
let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
|
|
5732
5886
|
if (extraBytesLength < 4) {
|
|
5733
5887
|
extraBytesLength += 64;
|
|
@@ -5740,7 +5894,7 @@
|
|
|
5740
5894
|
headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
|
|
5741
5895
|
arraySet(localHeaderArray, previousLocalHeaderArray);
|
|
5742
5896
|
arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
|
|
5743
|
-
localHeaderArrayView = getDataView(localHeaderArray);
|
|
5897
|
+
const localHeaderArrayView = getDataView(localHeaderArray);
|
|
5744
5898
|
setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
|
|
5745
5899
|
headerInfo.localHeaderView = localHeaderArrayView;
|
|
5746
5900
|
entryInfo.metadataSize += extraBytesLength;
|
|
@@ -5748,7 +5902,7 @@
|
|
|
5748
5902
|
|
|
5749
5903
|
function packUnixId(id) {
|
|
5750
5904
|
if (id === UNDEFINED_VALUE) {
|
|
5751
|
-
return
|
|
5905
|
+
return EMPTY_UINT8_ARRAY;
|
|
5752
5906
|
} else {
|
|
5753
5907
|
const dataArray = new Uint8Array(4);
|
|
5754
5908
|
const dataView = getDataView(dataArray);
|
|
@@ -5791,7 +5945,7 @@
|
|
|
5791
5945
|
dataDescriptor,
|
|
5792
5946
|
dataDescriptorSignature
|
|
5793
5947
|
}) {
|
|
5794
|
-
let dataDescriptorArray =
|
|
5948
|
+
let dataDescriptorArray = EMPTY_UINT8_ARRAY;
|
|
5795
5949
|
let dataDescriptorView, dataDescriptorOffset = 0;
|
|
5796
5950
|
let dataDescriptorLength = zip64 ? DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH : DATA_DESCRIPTOR_RECORD_LENGTH;
|
|
5797
5951
|
if (dataDescriptorSignature) {
|
|
@@ -5875,7 +6029,6 @@
|
|
|
5875
6029
|
}
|
|
5876
6030
|
}
|
|
5877
6031
|
if (zip64 && localExtraFieldZip64Length) {
|
|
5878
|
-
// the local zip64 extra field always contains both size slots
|
|
5879
6032
|
const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
|
|
5880
6033
|
setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
|
|
5881
6034
|
setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
|
|
@@ -5929,7 +6082,7 @@
|
|
|
5929
6082
|
}
|
|
5930
6083
|
rawExtraFieldZip64 = extraFieldZip64.array;
|
|
5931
6084
|
} else {
|
|
5932
|
-
rawExtraFieldZip64 =
|
|
6085
|
+
rawExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
5933
6086
|
}
|
|
5934
6087
|
fileEntry.rawExtraFieldZip64 = rawExtraFieldZip64;
|
|
5935
6088
|
fileEntry.zip64Offset = zip64Offset;
|
|
@@ -5944,7 +6097,7 @@
|
|
|
5944
6097
|
extraFieldTimestamp.uint32(lastModTimeUnix);
|
|
5945
6098
|
rawExtraFieldTimestamp = extraFieldTimestamp.array;
|
|
5946
6099
|
} else {
|
|
5947
|
-
rawExtraFieldTimestamp =
|
|
6100
|
+
rawExtraFieldTimestamp = EMPTY_UINT8_ARRAY;
|
|
5948
6101
|
}
|
|
5949
6102
|
fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
|
|
5950
6103
|
const extraFieldLength = getLength(
|
|
@@ -6000,7 +6153,7 @@
|
|
|
6000
6153
|
await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
|
|
6001
6154
|
directoryDiskOffset = offset;
|
|
6002
6155
|
writer.availableSize = 0;
|
|
6003
|
-
await writeData(writer,
|
|
6156
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6004
6157
|
}
|
|
6005
6158
|
if (indexFileEntry == 0) {
|
|
6006
6159
|
cdStartDiskNumber = writer.diskNumber;
|
|
@@ -6073,7 +6226,7 @@
|
|
|
6073
6226
|
const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
|
|
6074
6227
|
if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
|
|
6075
6228
|
writer.availableSize = 0;
|
|
6076
|
-
await writeData(writer,
|
|
6229
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6077
6230
|
}
|
|
6078
6231
|
lastDiskNumber = writer.diskNumber;
|
|
6079
6232
|
if (zip64) {
|
|
@@ -6170,7 +6323,6 @@
|
|
|
6170
6323
|
return Math.floor(date.getTime() / 1000);
|
|
6171
6324
|
}
|
|
6172
6325
|
|
|
6173
|
-
// the extended timestamp extra field stores dates as signed 32-bit time_t values
|
|
6174
6326
|
function inUnixTimeRange(timeUnix) {
|
|
6175
6327
|
return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
|
|
6176
6328
|
}
|
|
@@ -6343,8 +6495,6 @@
|
|
|
6343
6495
|
uncompressedSize: params.uncompressedSize || 0,
|
|
6344
6496
|
passThrough: params.passThrough
|
|
6345
6497
|
});
|
|
6346
|
-
// detached entries (e.g. clones) are not registered until they are attached,
|
|
6347
|
-
// otherwise they could never be garbage collected
|
|
6348
6498
|
if (parent || !fs.root) {
|
|
6349
6499
|
fs.entries[zipEntry.id] = zipEntry;
|
|
6350
6500
|
}
|
|
@@ -6413,7 +6563,7 @@
|
|
|
6413
6563
|
if (!writer || (writer.constructor == zipEntry.Writer && zipEntry.data)) {
|
|
6414
6564
|
return zipEntry.data;
|
|
6415
6565
|
} else {
|
|
6416
|
-
const reader = zipEntry.reader =
|
|
6566
|
+
const reader = zipEntry.reader = createReader(zipEntry.Reader, zipEntry.data, options);
|
|
6417
6567
|
const uncompressedSize = zipEntry.data ? zipEntry.data.uncompressedSize : reader.size;
|
|
6418
6568
|
await Promise.all([initStream(reader), initStream(writer, uncompressedSize)]);
|
|
6419
6569
|
const { readable } = reader;
|
|
@@ -6469,11 +6619,7 @@
|
|
|
6469
6619
|
}
|
|
6470
6620
|
|
|
6471
6621
|
async getArrayBuffer(options) {
|
|
6472
|
-
// this.data may be a string, a Uint8Array, a data URI or a Blob depending on how the entry
|
|
6473
|
-
// was created, so retrieve the bytes through getUint8Array rather than assuming a Blob; this
|
|
6474
|
-
// also applies the options (password, signal, onprogress...) like the other getData() getters
|
|
6475
6622
|
const array = await this.getUint8Array(options);
|
|
6476
|
-
// return the backing buffer directly when the array spans it, slicing the exact bytes otherwise
|
|
6477
6623
|
return array.byteOffset || array.byteLength != array.buffer.byteLength
|
|
6478
6624
|
? array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength)
|
|
6479
6625
|
: array.buffer;
|
|
@@ -6679,6 +6825,10 @@
|
|
|
6679
6825
|
return writable;
|
|
6680
6826
|
}
|
|
6681
6827
|
|
|
6828
|
+
exportFileSystemHandle(handle, options = {}) {
|
|
6829
|
+
return exportFileSystemHandle(this, handle, options);
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6682
6832
|
async importZip(reader, options = {}) {
|
|
6683
6833
|
await initStream(reader);
|
|
6684
6834
|
const zipReader = new ZipReader(reader, options);
|
|
@@ -6839,7 +6989,6 @@
|
|
|
6839
6989
|
node = node.getChildByName(path[index]);
|
|
6840
6990
|
}
|
|
6841
6991
|
if (!node) {
|
|
6842
|
-
// exclude detached entries, e.g. clones registered in `entries` but not attached
|
|
6843
6992
|
node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
|
|
6844
6993
|
entry.getRelativeName() == fullname);
|
|
6845
6994
|
}
|
|
@@ -6944,6 +7093,10 @@
|
|
|
6944
7093
|
return this.root.exportWritable(writable, options);
|
|
6945
7094
|
}
|
|
6946
7095
|
|
|
7096
|
+
exportFileSystemHandle(handle, options) {
|
|
7097
|
+
return this.root.exportFileSystemHandle(handle, options);
|
|
7098
|
+
}
|
|
7099
|
+
|
|
6947
7100
|
exportZip(writer, options) {
|
|
6948
7101
|
return this.root.exportZip(writer, options);
|
|
6949
7102
|
}
|
|
@@ -7007,10 +7160,13 @@
|
|
|
7007
7160
|
};
|
|
7008
7161
|
}
|
|
7009
7162
|
|
|
7163
|
+
function createReader(Reader, data, options) {
|
|
7164
|
+
return Reader.prototype ? new Reader(data, options) : Reader(data, options);
|
|
7165
|
+
}
|
|
7166
|
+
|
|
7010
7167
|
async function initReaders(entry, options) {
|
|
7011
7168
|
const fileEntries = [];
|
|
7012
7169
|
const pendingEntries = [entry];
|
|
7013
|
-
// per-export map so concurrent exports do not share (and lock) the same readers
|
|
7014
7170
|
const readers = new Map();
|
|
7015
7171
|
while (pendingEntries.length) {
|
|
7016
7172
|
const pendingEntry = pendingEntries.pop();
|
|
@@ -7023,7 +7179,7 @@
|
|
|
7023
7179
|
}
|
|
7024
7180
|
}
|
|
7025
7181
|
await Promise.all(fileEntries.map(async child => {
|
|
7026
|
-
const reader = child.reader =
|
|
7182
|
+
const reader = child.reader = createReader(child.Reader, child.data, options);
|
|
7027
7183
|
readers.set(child, reader);
|
|
7028
7184
|
try {
|
|
7029
7185
|
await initStream(reader);
|
|
@@ -7181,6 +7337,53 @@
|
|
|
7181
7337
|
}
|
|
7182
7338
|
}
|
|
7183
7339
|
|
|
7340
|
+
async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
|
|
7341
|
+
const totalSize = getTotalSize([zipEntry], "uncompressedSize");
|
|
7342
|
+
const writtenSizes = new Map();
|
|
7343
|
+
await exportChildren(zipEntry, directoryHandle);
|
|
7344
|
+
return directoryHandle;
|
|
7345
|
+
|
|
7346
|
+
async function exportChildren(entry, parentHandle) {
|
|
7347
|
+
if (options.concurrent) {
|
|
7348
|
+
const results = await Promise.allSettled(entry.children.map(child => exportChild(child, parentHandle)));
|
|
7349
|
+
const failedResult = results.find(result => result.status == "rejected");
|
|
7350
|
+
if (failedResult) {
|
|
7351
|
+
throw failedResult.reason;
|
|
7352
|
+
}
|
|
7353
|
+
} else {
|
|
7354
|
+
for (const child of entry.children) {
|
|
7355
|
+
await exportChild(child, parentHandle);
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7360
|
+
async function exportChild(child, parentHandle) {
|
|
7361
|
+
try {
|
|
7362
|
+
if (child.directory) {
|
|
7363
|
+
const childDirectoryHandle = await parentHandle.getDirectoryHandle(child.name, { create: true });
|
|
7364
|
+
await exportChildren(child, childDirectoryHandle);
|
|
7365
|
+
} else {
|
|
7366
|
+
const fileHandle = await parentHandle.getFileHandle(child.name, { create: true });
|
|
7367
|
+
const writable = await fileHandle.createWritable();
|
|
7368
|
+
await child.getData({ writable }, Object.assign({}, options, {
|
|
7369
|
+
onprogress: async progress => {
|
|
7370
|
+
if (options.onprogress) {
|
|
7371
|
+
writtenSizes.set(child.id, progress);
|
|
7372
|
+
try {
|
|
7373
|
+
await options.onprogress(Array.from(writtenSizes.values()).reduce((previousValue, currentValue) => previousValue + currentValue, 0), totalSize);
|
|
7374
|
+
} catch {
|
|
7375
|
+
// ignored
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
}));
|
|
7380
|
+
}
|
|
7381
|
+
} catch (error) {
|
|
7382
|
+
throw new Error(error.message + (child ? " (" + child.name + ")" : ""), { cause: error });
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
|
|
7184
7387
|
async function transformToFileSystemhandle(entry) {
|
|
7185
7388
|
const handle = {
|
|
7186
7389
|
name: entry.name
|