@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-core.js
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
const FUNCTION_TYPE = "function";
|
|
114
114
|
const OBJECT_TYPE = "object";
|
|
115
115
|
|
|
116
|
+
const EMPTY_UINT8_ARRAY = new Uint8Array();
|
|
117
|
+
|
|
116
118
|
/*
|
|
117
119
|
Copyright (c) 2025 Gildas Lormeau. All rights reserved.
|
|
118
120
|
|
|
@@ -164,6 +166,22 @@
|
|
|
164
166
|
DecompressionStream: typeof DecompressionStream != UNDEFINED_TYPE && DecompressionStream
|
|
165
167
|
};
|
|
166
168
|
|
|
169
|
+
const CONFIGURABLE_PROPERTY_NAMES = [
|
|
170
|
+
"baseURI",
|
|
171
|
+
"wasmURI",
|
|
172
|
+
"workerURI",
|
|
173
|
+
"chunkSize",
|
|
174
|
+
"maxWorkers",
|
|
175
|
+
"terminateWorkerTimeout",
|
|
176
|
+
"workerStarvationTimeout",
|
|
177
|
+
"useCompressionStream",
|
|
178
|
+
"useWebWorkers",
|
|
179
|
+
"CompressionStream",
|
|
180
|
+
"DecompressionStream",
|
|
181
|
+
"CompressionStreamZlib",
|
|
182
|
+
"DecompressionStreamZlib"
|
|
183
|
+
];
|
|
184
|
+
|
|
167
185
|
const config = Object.assign({}, DEFAULT_CONFIGURATION);
|
|
168
186
|
|
|
169
187
|
function getConfiguration() {
|
|
@@ -175,39 +193,11 @@
|
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
function configure(configuration) {
|
|
178
|
-
const {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
workerStarvationTimeout,
|
|
184
|
-
useCompressionStream,
|
|
185
|
-
useWebWorkers,
|
|
186
|
-
CompressionStream,
|
|
187
|
-
DecompressionStream,
|
|
188
|
-
CompressionStreamZlib,
|
|
189
|
-
DecompressionStreamZlib,
|
|
190
|
-
workerURI,
|
|
191
|
-
wasmURI
|
|
192
|
-
} = configuration;
|
|
193
|
-
setIfDefined("baseURI", baseURI);
|
|
194
|
-
setIfDefined("wasmURI", wasmURI);
|
|
195
|
-
setIfDefined("workerURI", workerURI);
|
|
196
|
-
setIfDefined("chunkSize", chunkSize);
|
|
197
|
-
setIfDefined("maxWorkers", maxWorkers);
|
|
198
|
-
setIfDefined("terminateWorkerTimeout", terminateWorkerTimeout);
|
|
199
|
-
setIfDefined("workerStarvationTimeout", workerStarvationTimeout);
|
|
200
|
-
setIfDefined("useCompressionStream", useCompressionStream);
|
|
201
|
-
setIfDefined("useWebWorkers", useWebWorkers);
|
|
202
|
-
setIfDefined("CompressionStream", CompressionStream);
|
|
203
|
-
setIfDefined("DecompressionStream", DecompressionStream);
|
|
204
|
-
setIfDefined("CompressionStreamZlib", CompressionStreamZlib);
|
|
205
|
-
setIfDefined("DecompressionStreamZlib", DecompressionStreamZlib);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function setIfDefined(propertyName, propertyValue) {
|
|
209
|
-
if (propertyValue !== UNDEFINED_VALUE) {
|
|
210
|
-
config[propertyName] = propertyValue;
|
|
196
|
+
for (const propertyName of CONFIGURABLE_PROPERTY_NAMES) {
|
|
197
|
+
const propertyValue = configuration[propertyName];
|
|
198
|
+
if (propertyValue !== UNDEFINED_VALUE) {
|
|
199
|
+
config[propertyName] = propertyValue;
|
|
200
|
+
}
|
|
211
201
|
}
|
|
212
202
|
}
|
|
213
203
|
|
|
@@ -239,18 +229,30 @@
|
|
|
239
229
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
240
230
|
*/
|
|
241
231
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
232
|
+
// Slicing-by-8 CRC-32 (Intel / zlib). The eight 256-entry tables let the inner loop
|
|
233
|
+
// consume 8 bytes per iteration with a shorter dependency chain, ~4x the byte-at-a-time
|
|
234
|
+
// rate (measured ~320 -> ~1400 MB/s on 64KB chunks).
|
|
235
|
+
//
|
|
236
|
+
// Every table MUST stay a PACKED_SMI array: build with array literals (not `new Array(n)`,
|
|
237
|
+
// which is HOLEY) and store the signed int32 XOR result (no `>>> 0`). An unsigned or holey
|
|
238
|
+
// table becomes a V8 FixedDoubleArray whose every hot-loop lookup unboxes a double (~1.6x
|
|
239
|
+
// slower). Signedness is irrelevant to the result — the reads mask/shift it and the final
|
|
240
|
+
// `~crc` normalizes it. Do NOT reintroduce `>>> 0` here or switch to `new Array(256)`.
|
|
241
|
+
const T = [[], [], [], [], [], [], [], []];
|
|
242
|
+
for (let n = 0; n < 256; n++) {
|
|
243
|
+
let t = n;
|
|
245
244
|
for (let j = 0; j < 8; j++) {
|
|
246
|
-
|
|
247
|
-
t = (t >>> 1) ^ 0xEDB88320;
|
|
248
|
-
} else {
|
|
249
|
-
t = t >>> 1;
|
|
250
|
-
}
|
|
245
|
+
t = (t & 1) ? (t >>> 1) ^ 0xEDB88320 : t >>> 1;
|
|
251
246
|
}
|
|
252
|
-
|
|
247
|
+
T[0][n] = t;
|
|
253
248
|
}
|
|
249
|
+
for (let n = 0; n < 256; n++) {
|
|
250
|
+
for (let k = 1; k < 8; k++) {
|
|
251
|
+
const previous = T[k - 1][n];
|
|
252
|
+
T[k][n] = (previous >>> 8) ^ T[0][previous & 0xFF];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const [T0, T1, T2, T3, T4, T5, T6, T7] = T;
|
|
254
256
|
|
|
255
257
|
class Crc32 {
|
|
256
258
|
|
|
@@ -260,8 +262,24 @@
|
|
|
260
262
|
|
|
261
263
|
append(data) {
|
|
262
264
|
let crc = this.crc | 0;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
+
const length = data.length | 0;
|
|
266
|
+
let offset = 0;
|
|
267
|
+
// Process 8 bytes per iteration over the typed-array body. DataView.getInt32(le)
|
|
268
|
+
// reads an unaligned little-endian word as a signed int32 (no double boxing), so no
|
|
269
|
+
// alignment or endianness handling is needed; data.buffer guards non-typed inputs.
|
|
270
|
+
if (length >= 8 && data.buffer) {
|
|
271
|
+
const view = new DataView(data.buffer, data.byteOffset, length);
|
|
272
|
+
const end = length - 8;
|
|
273
|
+
for (; offset <= end; offset += 8) {
|
|
274
|
+
const a = crc ^ view.getInt32(offset, true);
|
|
275
|
+
const b = view.getInt32(offset + 4, true);
|
|
276
|
+
crc = T7[a & 0xFF] ^ T6[(a >>> 8) & 0xFF] ^ T5[(a >>> 16) & 0xFF] ^ T4[(a >>> 24) & 0xFF] ^
|
|
277
|
+
T3[b & 0xFF] ^ T2[(b >>> 8) & 0xFF] ^ T1[(b >>> 16) & 0xFF] ^ T0[(b >>> 24) & 0xFF];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Remaining tail (and non-typed inputs) byte-at-a-time with the base table.
|
|
281
|
+
for (; offset < length; offset++) {
|
|
282
|
+
crc = (crc >>> 8) ^ T0[(crc ^ data[offset]) & 0xFF];
|
|
265
283
|
}
|
|
266
284
|
this.crc = crc;
|
|
267
285
|
}
|
|
@@ -1194,8 +1212,6 @@
|
|
|
1194
1212
|
if (GET_RANDOM_VALUES_SUPPORTED) {
|
|
1195
1213
|
return crypto.getRandomValues(array);
|
|
1196
1214
|
} else {
|
|
1197
|
-
// all supported runtimes expose crypto.getRandomValues; failing hard beats
|
|
1198
|
-
// silently generating predictable encryption salts
|
|
1199
1215
|
throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
|
|
1200
1216
|
}
|
|
1201
1217
|
}
|
|
@@ -1258,12 +1274,7 @@
|
|
|
1258
1274
|
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
1259
1275
|
super({
|
|
1260
1276
|
start() {
|
|
1261
|
-
|
|
1262
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
1263
|
-
password: encodePassword(password, rawPassword),
|
|
1264
|
-
strength: encryptionStrength - 1,
|
|
1265
|
-
pending: new Uint8Array()
|
|
1266
|
-
});
|
|
1277
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
1267
1278
|
},
|
|
1268
1279
|
async transform(chunk, controller) {
|
|
1269
1280
|
const aesCrypto = this;
|
|
@@ -1298,7 +1309,7 @@
|
|
|
1298
1309
|
await ready;
|
|
1299
1310
|
const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH);
|
|
1300
1311
|
const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH);
|
|
1301
|
-
let decryptedChunkArray =
|
|
1312
|
+
let decryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
1302
1313
|
if (chunkToDecrypt.length) {
|
|
1303
1314
|
const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
|
|
1304
1315
|
hmac.update(encryptedChunk);
|
|
@@ -1306,8 +1317,6 @@
|
|
|
1306
1317
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
1307
1318
|
}
|
|
1308
1319
|
const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
|
|
1309
|
-
// branch-free comparison: accumulate the differences instead of
|
|
1310
|
-
// short-circuiting on the first mismatching byte
|
|
1311
1320
|
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
1312
1321
|
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
1313
1322
|
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
@@ -1329,12 +1338,7 @@
|
|
|
1329
1338
|
let stream;
|
|
1330
1339
|
super({
|
|
1331
1340
|
start() {
|
|
1332
|
-
|
|
1333
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
1334
|
-
password: encodePassword(password, rawPassword),
|
|
1335
|
-
strength: encryptionStrength - 1,
|
|
1336
|
-
pending: new Uint8Array()
|
|
1337
|
-
});
|
|
1341
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
1338
1342
|
},
|
|
1339
1343
|
async transform(chunk, controller) {
|
|
1340
1344
|
const aesCrypto = this;
|
|
@@ -1344,7 +1348,7 @@
|
|
|
1344
1348
|
resolveReady,
|
|
1345
1349
|
ready
|
|
1346
1350
|
} = aesCrypto;
|
|
1347
|
-
let preamble =
|
|
1351
|
+
let preamble = EMPTY_UINT8_ARRAY;
|
|
1348
1352
|
if (password) {
|
|
1349
1353
|
preamble = await createEncryptionKeys(aesCrypto, strength, password);
|
|
1350
1354
|
resolveReady();
|
|
@@ -1364,7 +1368,7 @@
|
|
|
1364
1368
|
} = this;
|
|
1365
1369
|
if (hmac && ctr) {
|
|
1366
1370
|
await ready;
|
|
1367
|
-
let encryptedChunkArray =
|
|
1371
|
+
let encryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
1368
1372
|
if (pending.length) {
|
|
1369
1373
|
const encryptedChunk = ctr.update(toBits(codecBytes, pending));
|
|
1370
1374
|
hmac.update(encryptedChunk);
|
|
@@ -1379,6 +1383,15 @@
|
|
|
1379
1383
|
}
|
|
1380
1384
|
}
|
|
1381
1385
|
|
|
1386
|
+
function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
|
|
1387
|
+
Object.assign(aesCrypto, {
|
|
1388
|
+
ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
|
|
1389
|
+
password: encodePassword(password, rawPassword),
|
|
1390
|
+
strength: encryptionStrength - 1,
|
|
1391
|
+
pending: EMPTY_UINT8_ARRAY
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1382
1395
|
function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) {
|
|
1383
1396
|
const {
|
|
1384
1397
|
ctr,
|
|
@@ -1540,20 +1553,13 @@
|
|
|
1540
1553
|
constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
|
|
1541
1554
|
super({
|
|
1542
1555
|
start() {
|
|
1543
|
-
|
|
1544
|
-
password,
|
|
1545
|
-
rawPassword,
|
|
1546
|
-
passwordVerification
|
|
1547
|
-
});
|
|
1548
|
-
createKeys(this, password, rawPassword);
|
|
1556
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
1549
1557
|
},
|
|
1550
1558
|
transform(chunk, controller) {
|
|
1551
1559
|
const zipCrypto = this;
|
|
1552
1560
|
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
1553
1561
|
const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
|
|
1554
1562
|
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
1555
|
-
// verify the password with a branch-free comparison of the verification byte, mirroring
|
|
1556
|
-
// the constant-time AES HMAC signature check
|
|
1557
1563
|
if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
|
|
1558
1564
|
throw new Error(ERR_INVALID_PASSWORD);
|
|
1559
1565
|
}
|
|
@@ -1574,12 +1580,7 @@
|
|
|
1574
1580
|
constructor({ password, rawPassword, passwordVerification }) {
|
|
1575
1581
|
super({
|
|
1576
1582
|
start() {
|
|
1577
|
-
|
|
1578
|
-
password,
|
|
1579
|
-
rawPassword,
|
|
1580
|
-
passwordVerification
|
|
1581
|
-
});
|
|
1582
|
-
createKeys(this, password, rawPassword);
|
|
1583
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
1583
1584
|
},
|
|
1584
1585
|
transform(chunk, controller) {
|
|
1585
1586
|
const zipCrypto = this;
|
|
@@ -1603,6 +1604,15 @@
|
|
|
1603
1604
|
}
|
|
1604
1605
|
}
|
|
1605
1606
|
|
|
1607
|
+
function initZipCrypto(zipCrypto, password, rawPassword, passwordVerification) {
|
|
1608
|
+
Object.assign(zipCrypto, {
|
|
1609
|
+
password,
|
|
1610
|
+
rawPassword,
|
|
1611
|
+
passwordVerification
|
|
1612
|
+
});
|
|
1613
|
+
createKeys(zipCrypto, password, rawPassword);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1606
1616
|
function decrypt(target, input) {
|
|
1607
1617
|
const output = new Uint8Array(input.length);
|
|
1608
1618
|
for (let index = 0; index < input.length; index++) {
|
|
@@ -1695,21 +1705,32 @@
|
|
|
1695
1705
|
const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
|
|
1696
1706
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
1697
1707
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
1708
|
+
const FORMAT_GZIP = "gzip";
|
|
1709
|
+
const GZIP_HEADER_LENGTH = 10;
|
|
1710
|
+
const GZIP_TRAILER_LENGTH = 8;
|
|
1698
1711
|
|
|
1699
1712
|
class DeflateStream extends TransformStream {
|
|
1700
1713
|
|
|
1701
1714
|
constructor(options, { chunkSize, CompressionStreamZlib, CompressionStream }) {
|
|
1702
1715
|
super({});
|
|
1703
|
-
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level } = options;
|
|
1716
|
+
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level, deflate64 } = options;
|
|
1704
1717
|
const stream = this;
|
|
1705
|
-
let crc32Stream, encryptionStream;
|
|
1718
|
+
let crc32Stream, encryptionStream, gzipCrc32Stream;
|
|
1706
1719
|
let readable = super.readable;
|
|
1707
|
-
|
|
1720
|
+
const useGzipCrc32 = signed && compressed && !deflate64 && (!encrypted || zipCrypto) &&
|
|
1721
|
+
Boolean(useCompressionStream && CompressionStream);
|
|
1722
|
+
if ((!encrypted || zipCrypto) && signed && !useGzipCrc32) {
|
|
1708
1723
|
crc32Stream = new Crc32Stream();
|
|
1709
1724
|
readable = pipeThrough(readable, crc32Stream);
|
|
1710
1725
|
}
|
|
1711
1726
|
if (compressed) {
|
|
1712
|
-
|
|
1727
|
+
if (useGzipCrc32) {
|
|
1728
|
+
gzipCrc32Stream = new GzipToRawDeflateStream();
|
|
1729
|
+
readable = pipeThroughBackpressured(readable, new CompressionStream(FORMAT_GZIP));
|
|
1730
|
+
readable = pipeThrough(readable, gzipCrc32Stream);
|
|
1731
|
+
} else {
|
|
1732
|
+
readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib, CompressionStream);
|
|
1733
|
+
}
|
|
1713
1734
|
}
|
|
1714
1735
|
if (encrypted) {
|
|
1715
1736
|
if (zipCrypto) {
|
|
@@ -1725,13 +1746,64 @@
|
|
|
1725
1746
|
signature = encryptionStream.signature;
|
|
1726
1747
|
}
|
|
1727
1748
|
if ((!encrypted || zipCrypto) && signed) {
|
|
1728
|
-
signature = new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
1749
|
+
signature = useGzipCrc32 ? gzipCrc32Stream.signature : new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
1729
1750
|
}
|
|
1730
1751
|
stream.signature = signature;
|
|
1731
1752
|
});
|
|
1732
1753
|
}
|
|
1733
1754
|
}
|
|
1734
1755
|
|
|
1756
|
+
class GzipToRawDeflateStream extends TransformStream {
|
|
1757
|
+
|
|
1758
|
+
constructor() {
|
|
1759
|
+
// deno-lint-ignore prefer-const
|
|
1760
|
+
let stream;
|
|
1761
|
+
let headerLeft = GZIP_HEADER_LENGTH;
|
|
1762
|
+
let tail = new Uint8Array(0);
|
|
1763
|
+
super({
|
|
1764
|
+
transform(chunk, controller) {
|
|
1765
|
+
if (headerLeft) {
|
|
1766
|
+
const dropped = Math.min(headerLeft, chunk.length);
|
|
1767
|
+
headerLeft -= dropped;
|
|
1768
|
+
chunk = chunk.subarray(dropped);
|
|
1769
|
+
if (!chunk.length) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const available = tail.length + chunk.length;
|
|
1774
|
+
if (available <= GZIP_TRAILER_LENGTH) {
|
|
1775
|
+
const pending = new Uint8Array(available);
|
|
1776
|
+
pending.set(tail);
|
|
1777
|
+
pending.set(chunk, tail.length);
|
|
1778
|
+
tail = pending;
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const emitLength = available - GZIP_TRAILER_LENGTH;
|
|
1782
|
+
const output = new Uint8Array(emitLength);
|
|
1783
|
+
const fromTail = Math.min(emitLength, tail.length);
|
|
1784
|
+
output.set(tail.subarray(0, fromTail), 0);
|
|
1785
|
+
if (emitLength > fromTail) {
|
|
1786
|
+
output.set(chunk.subarray(0, emitLength - fromTail), fromTail);
|
|
1787
|
+
}
|
|
1788
|
+
controller.enqueue(output);
|
|
1789
|
+
const nextTail = new Uint8Array(GZIP_TRAILER_LENGTH);
|
|
1790
|
+
const tailRemaining = tail.length - fromTail;
|
|
1791
|
+
if (tailRemaining) {
|
|
1792
|
+
nextTail.set(tail.subarray(fromTail), 0);
|
|
1793
|
+
}
|
|
1794
|
+
nextTail.set(chunk.subarray(emitLength - fromTail), tailRemaining);
|
|
1795
|
+
tail = nextTail;
|
|
1796
|
+
},
|
|
1797
|
+
flush() {
|
|
1798
|
+
const dataView = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);
|
|
1799
|
+
stream.signature = dataView.getUint32(0, true);
|
|
1800
|
+
stream.uncompressedSize = dataView.getUint32(4, true);
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
stream = this;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1735
1807
|
class InflateStream extends TransformStream {
|
|
1736
1808
|
|
|
1737
1809
|
constructor(options, { chunkSize, DecompressionStreamZlib, DecompressionStream }) {
|
|
@@ -1794,11 +1866,6 @@
|
|
|
1794
1866
|
throw error;
|
|
1795
1867
|
}
|
|
1796
1868
|
}
|
|
1797
|
-
// The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
|
|
1798
|
-
// backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
|
|
1799
|
-
// the whole source into them and peak memory grows with the entry size. Awaiting each write()
|
|
1800
|
-
// paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
|
|
1801
|
-
// codec already backpressures, so this is a no-op cost for it.
|
|
1802
1869
|
return pipeThroughBackpressured(readable, codecStream);
|
|
1803
1870
|
}
|
|
1804
1871
|
|
|
@@ -1806,10 +1873,6 @@
|
|
|
1806
1873
|
return readable.pipeThrough(transformStream);
|
|
1807
1874
|
}
|
|
1808
1875
|
|
|
1809
|
-
// Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
|
|
1810
|
-
// write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
|
|
1811
|
-
// Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
|
|
1812
|
-
// (surfaced through the codec's writable rejecting) cancels the source.
|
|
1813
1876
|
function pipeThroughBackpressured(readable, transformStream) {
|
|
1814
1877
|
const writer = transformStream.writable.getWriter();
|
|
1815
1878
|
const reader = readable.getReader();
|
|
@@ -1838,7 +1901,7 @@
|
|
|
1838
1901
|
try {
|
|
1839
1902
|
await writer.abort(error);
|
|
1840
1903
|
} catch {
|
|
1841
|
-
// the writable may already be errored/closed
|
|
1904
|
+
// ignored: the writable may already be errored/closed
|
|
1842
1905
|
}
|
|
1843
1906
|
}
|
|
1844
1907
|
|
|
@@ -1846,13 +1909,10 @@
|
|
|
1846
1909
|
try {
|
|
1847
1910
|
await reader.cancel(error);
|
|
1848
1911
|
} catch {
|
|
1849
|
-
// the readable may already be errored/closed
|
|
1912
|
+
// ignored: the readable may already be errored/closed
|
|
1850
1913
|
}
|
|
1851
1914
|
}
|
|
1852
1915
|
|
|
1853
|
-
// DecompressionStream implementations can fail with a message-less TypeError on malformed
|
|
1854
|
-
// input; give these errors an identifiable message (errors raised by the other stages of
|
|
1855
|
-
// the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
|
|
1856
1916
|
function mapInflateStreamError(readable) {
|
|
1857
1917
|
const reader = readable.getReader();
|
|
1858
1918
|
return new ReadableStream({
|
|
@@ -2177,9 +2237,6 @@
|
|
|
2177
2237
|
try {
|
|
2178
2238
|
await initModule(config);
|
|
2179
2239
|
} catch {
|
|
2180
|
-
// The WASM module failed to load. Fall back to the native CompressionStream only if the
|
|
2181
|
-
// zlib codec that would otherwise run depends on that module; a self-contained codec
|
|
2182
|
-
// supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
|
|
2183
2240
|
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
2184
2241
|
config.CompressionStreamZlib :
|
|
2185
2242
|
config.DecompressionStreamZlib;
|
|
@@ -2257,7 +2314,6 @@
|
|
|
2257
2314
|
return resultValue;
|
|
2258
2315
|
} catch (error) {
|
|
2259
2316
|
await closeWritable();
|
|
2260
|
-
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
2261
2317
|
abortPipe();
|
|
2262
2318
|
try {
|
|
2263
2319
|
await closed;
|
|
@@ -2282,8 +2338,6 @@
|
|
|
2282
2338
|
const abortController = new AbortController();
|
|
2283
2339
|
const { writable, readable } = new TransformStream();
|
|
2284
2340
|
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
2285
|
-
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
2286
|
-
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
2287
2341
|
closed.catch(() => { });
|
|
2288
2342
|
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
2289
2343
|
}
|
|
@@ -2376,8 +2430,6 @@
|
|
|
2376
2430
|
event.preventDefault();
|
|
2377
2431
|
}
|
|
2378
2432
|
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
2379
|
-
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
2380
|
-
// fail the current task instead of letting it hang forever
|
|
2381
2433
|
terminateWorker$1(workerData);
|
|
2382
2434
|
if (rejectResult) {
|
|
2383
2435
|
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
@@ -2429,8 +2481,6 @@
|
|
|
2429
2481
|
async function onMessage({ data }, workerData) {
|
|
2430
2482
|
const { type, value, messageId, result, error } = data;
|
|
2431
2483
|
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
2432
|
-
// continuations below can settle after the worker slot has been reassigned to a new
|
|
2433
|
-
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
2434
2484
|
const stale = () => workerData.generation != generation;
|
|
2435
2485
|
try {
|
|
2436
2486
|
if (error) {
|
|
@@ -2686,26 +2736,24 @@
|
|
|
2686
2736
|
class Reader extends Stream {
|
|
2687
2737
|
|
|
2688
2738
|
get readable() {
|
|
2739
|
+
return this.createReadable();
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
createReadable({ offset = 0, size, diskNumberStart, chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
|
|
2689
2743
|
const reader = this;
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
start() {
|
|
2693
|
-
this.chunkOffset = 0;
|
|
2694
|
-
},
|
|
2744
|
+
let chunkOffset = 0;
|
|
2745
|
+
return new ReadableStream({
|
|
2695
2746
|
async pull(controller) {
|
|
2696
|
-
const { offset = 0, size, diskNumberStart } = readable;
|
|
2697
|
-
const { chunkOffset } = this;
|
|
2698
2747
|
const dataSize = size === UNDEFINED_VALUE ? chunkSize : Math.min(chunkSize, size - chunkOffset);
|
|
2699
2748
|
const data = await readUint8Array(reader, offset + chunkOffset, dataSize, diskNumberStart);
|
|
2700
2749
|
controller.enqueue(data);
|
|
2701
2750
|
if ((chunkOffset + chunkSize > size) || (size === UNDEFINED_VALUE && !data.length && dataSize)) {
|
|
2702
2751
|
controller.close();
|
|
2703
2752
|
} else {
|
|
2704
|
-
|
|
2753
|
+
chunkOffset += chunkSize;
|
|
2705
2754
|
}
|
|
2706
2755
|
}
|
|
2707
2756
|
});
|
|
2708
|
-
return readable;
|
|
2709
2757
|
}
|
|
2710
2758
|
}
|
|
2711
2759
|
|
|
@@ -2844,8 +2892,6 @@
|
|
|
2844
2892
|
}
|
|
2845
2893
|
});
|
|
2846
2894
|
writer.blob = new Response(transformStream.readable, { headers }).blob();
|
|
2847
|
-
// mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
|
|
2848
|
-
// surfaces them
|
|
2849
2895
|
writer.blob.catch(() => { });
|
|
2850
2896
|
}
|
|
2851
2897
|
|
|
@@ -3007,7 +3053,7 @@
|
|
|
3007
3053
|
return eocdCache;
|
|
3008
3054
|
}
|
|
3009
3055
|
if (index >= size || length === 0) {
|
|
3010
|
-
return
|
|
3056
|
+
return EMPTY_UINT8_ARRAY;
|
|
3011
3057
|
} else {
|
|
3012
3058
|
if (index + length > size) {
|
|
3013
3059
|
length = size - index;
|
|
@@ -3064,8 +3110,6 @@
|
|
|
3064
3110
|
async function getRequestData(httpReader, sendRequest) {
|
|
3065
3111
|
const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
|
|
3066
3112
|
httpReader.data = new Uint8Array(await response.arrayBuffer());
|
|
3067
|
-
// the downloaded content is authoritative, e.g. the Content-Length response header
|
|
3068
|
-
// of the HEAD request can be the encoded size when the content is compressed
|
|
3069
3113
|
httpReader.size = httpReader.data.length;
|
|
3070
3114
|
}
|
|
3071
3115
|
|
|
@@ -3130,7 +3174,6 @@
|
|
|
3130
3174
|
super();
|
|
3131
3175
|
Object.assign(this, {
|
|
3132
3176
|
url,
|
|
3133
|
-
// an explicit fetch implementation takes precedence over useXHR
|
|
3134
3177
|
reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
|
|
3135
3178
|
});
|
|
3136
3179
|
}
|
|
@@ -3269,7 +3312,7 @@
|
|
|
3269
3312
|
}
|
|
3270
3313
|
}
|
|
3271
3314
|
} else {
|
|
3272
|
-
result =
|
|
3315
|
+
result = EMPTY_UINT8_ARRAY;
|
|
3273
3316
|
}
|
|
3274
3317
|
reader.lastDiskNumber = Math.max(currentDiskNumber, reader.lastDiskNumber);
|
|
3275
3318
|
return result;
|
|
@@ -3314,8 +3357,6 @@
|
|
|
3314
3357
|
writer.diskOffset += diskSourceWriter.size;
|
|
3315
3358
|
writer.diskNumber++;
|
|
3316
3359
|
diskWriter = null;
|
|
3317
|
-
// the next disk is not opened yet but its space is available; leaving a
|
|
3318
|
-
// stale value here would force a new disk for every subsequent write
|
|
3319
3360
|
writer.availableSize = writer.maxSize;
|
|
3320
3361
|
if (chunk.length > availableSize) {
|
|
3321
3362
|
await this.write(chunk.subarray(availableSize));
|
|
@@ -3495,7 +3536,6 @@
|
|
|
3495
3536
|
if (encoding && encoding.trim().toLowerCase() == "cp437") {
|
|
3496
3537
|
return decodeCP437(value);
|
|
3497
3538
|
} else {
|
|
3498
|
-
// ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
|
|
3499
3539
|
return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
|
|
3500
3540
|
}
|
|
3501
3541
|
}
|
|
@@ -3693,6 +3733,11 @@
|
|
|
3693
3733
|
const OPTION_OFFSET = "offset";
|
|
3694
3734
|
const OPTION_USDZ = "usdz";
|
|
3695
3735
|
const OPTION_UNIX_EXTRA_FIELD_TYPE = "unixExtraFieldType";
|
|
3736
|
+
const OPTION_STRICTNESS = "strictness";
|
|
3737
|
+
const OPTION_MAX_APPENDED_DATA_SIZE = "maxAppendedDataSize";
|
|
3738
|
+
const STRICTNESS_STRICT = "strict";
|
|
3739
|
+
const STRICTNESS_BALANCED = "balanced";
|
|
3740
|
+
const STRICTNESS_TOLERANT = "tolerant";
|
|
3696
3741
|
|
|
3697
3742
|
/*
|
|
3698
3743
|
Copyright (c) 2025 Gildas Lormeau. All rights reserved.
|
|
@@ -3738,8 +3783,6 @@
|
|
|
3738
3783
|
const CHARSET_UTF8 = "utf-8";
|
|
3739
3784
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
3740
3785
|
const CHARSET_CP437 = "cp437";
|
|
3741
|
-
// bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
|
|
3742
|
-
// because they are informative only
|
|
3743
3786
|
const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
|
|
3744
3787
|
const ZIP64_PROPERTIES = [
|
|
3745
3788
|
[PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
|
|
@@ -3781,8 +3824,11 @@
|
|
|
3781
3824
|
if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
|
|
3782
3825
|
throw new Error(ERR_BAD_FORMAT);
|
|
3783
3826
|
}
|
|
3784
|
-
|
|
3785
|
-
const
|
|
3827
|
+
const strictness = getStrictness(getOptionValue$1(zipReader, options, OPTION_STRICTNESS), getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY));
|
|
3828
|
+
const checkAmbiguity = strictness == STRICTNESS_STRICT;
|
|
3829
|
+
const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
|
|
3830
|
+
const maxAppendedDataSize = getMaxAppendedDataSize(getOptionValue$1(zipReader, options, OPTION_MAX_APPENDED_DATA_SIZE), strictness);
|
|
3831
|
+
const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
|
|
3786
3832
|
if (!endOfDirectoryInfo) {
|
|
3787
3833
|
const signatureArray = await readUint8Array(reader, 0, 4);
|
|
3788
3834
|
const signatureView = getDataView$1(signatureArray);
|
|
@@ -3792,14 +3838,16 @@
|
|
|
3792
3838
|
throw new Error(ERR_EOCDR_NOT_FOUND);
|
|
3793
3839
|
}
|
|
3794
3840
|
}
|
|
3841
|
+
if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
|
|
3842
|
+
throwAmbiguousArchive("multiple end of central directory records");
|
|
3843
|
+
}
|
|
3795
3844
|
const endOfDirectoryView = getDataView$1(endOfDirectoryInfo);
|
|
3796
3845
|
let directoryDataLength = getUint32(endOfDirectoryView, 12);
|
|
3797
3846
|
let directoryDataOffset = getUint32(endOfDirectoryView, 16);
|
|
3798
3847
|
const commentOffset = endOfDirectoryInfo.offset;
|
|
3799
3848
|
const commentLength = getUint16(endOfDirectoryView, 20);
|
|
3800
3849
|
const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
|
|
3801
|
-
|
|
3802
|
-
if (checkAmbiguity && appendedDataOffset != reader.size) {
|
|
3850
|
+
if (reader.size - appendedDataOffset > maxAppendedDataSize) {
|
|
3803
3851
|
throwAmbiguousArchive("appended data");
|
|
3804
3852
|
}
|
|
3805
3853
|
let lastDiskNumber = getUint16(endOfDirectoryView, 4);
|
|
@@ -3812,7 +3860,7 @@
|
|
|
3812
3860
|
if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
|
|
3813
3861
|
const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
|
|
3814
3862
|
await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
|
|
3815
|
-
|
|
3863
|
+
EMPTY_UINT8_ARRAY;
|
|
3816
3864
|
const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
|
|
3817
3865
|
if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
|
|
3818
3866
|
getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
@@ -3878,14 +3926,22 @@
|
|
|
3878
3926
|
throw new Error(ERR_BAD_FORMAT);
|
|
3879
3927
|
}
|
|
3880
3928
|
const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
|
|
3881
|
-
if (
|
|
3882
|
-
const
|
|
3883
|
-
|
|
3884
|
-
if (
|
|
3885
|
-
|
|
3929
|
+
if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
|
|
3930
|
+
const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE;
|
|
3931
|
+
let reconcile = !storedPointsAtDirectory;
|
|
3932
|
+
if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
|
|
3933
|
+
const expectedSignatureArray = await readUint8Array(reader, expectedDirectoryDataOffset, 4, diskNumber);
|
|
3934
|
+
reconcile = getUint32(getDataView$1(expectedSignatureArray), 0) == CENTRAL_FILE_HEADER_SIGNATURE;
|
|
3935
|
+
}
|
|
3936
|
+
if (reconcile) {
|
|
3937
|
+
const originalDirectoryDataOffset = directoryDataOffset;
|
|
3938
|
+
directoryDataOffset = expectedDirectoryDataOffset;
|
|
3939
|
+
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
3940
|
+
prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
|
|
3941
|
+
}
|
|
3942
|
+
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3943
|
+
directoryView = getDataView$1(directoryArray);
|
|
3886
3944
|
}
|
|
3887
|
-
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3888
|
-
directoryView = getDataView$1(directoryArray);
|
|
3889
3945
|
}
|
|
3890
3946
|
}
|
|
3891
3947
|
const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
|
|
@@ -4027,11 +4083,11 @@
|
|
|
4027
4083
|
const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
|
|
4028
4084
|
const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
|
|
4029
4085
|
if (extractPrependedData) {
|
|
4030
|
-
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) :
|
|
4086
|
+
zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : EMPTY_UINT8_ARRAY;
|
|
4031
4087
|
}
|
|
4032
|
-
zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) :
|
|
4088
|
+
zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) : EMPTY_UINT8_ARRAY;
|
|
4033
4089
|
if (extractAppendedData) {
|
|
4034
|
-
zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) :
|
|
4090
|
+
zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) : EMPTY_UINT8_ARRAY;
|
|
4035
4091
|
}
|
|
4036
4092
|
return true;
|
|
4037
4093
|
}
|
|
@@ -4142,8 +4198,8 @@
|
|
|
4142
4198
|
extraFieldLength,
|
|
4143
4199
|
filenameLength
|
|
4144
4200
|
} = localDirectory;
|
|
4145
|
-
const checkAmbiguity = getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY);
|
|
4146
|
-
let rawLocalFilename =
|
|
4201
|
+
const checkAmbiguity = getStrictness(getOptionValue$1(zipEntry, options, OPTION_STRICTNESS), getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY)) == STRICTNESS_STRICT;
|
|
4202
|
+
let rawLocalFilename = EMPTY_UINT8_ARRAY;
|
|
4147
4203
|
if (checkAmbiguity && (filenameLength || extraFieldLength)) {
|
|
4148
4204
|
const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
|
|
4149
4205
|
rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
|
|
@@ -4151,7 +4207,7 @@
|
|
|
4151
4207
|
} else {
|
|
4152
4208
|
localDirectory.rawExtraField = extraFieldLength ?
|
|
4153
4209
|
await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
|
|
4154
|
-
|
|
4210
|
+
EMPTY_UINT8_ARRAY;
|
|
4155
4211
|
}
|
|
4156
4212
|
readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
|
|
4157
4213
|
if (checkAmbiguity) {
|
|
@@ -4178,12 +4234,7 @@
|
|
|
4178
4234
|
}
|
|
4179
4235
|
const dataOffset = offset + HEADER_SIZE + filenameLength + extraFieldLength;
|
|
4180
4236
|
const size = compressedSize;
|
|
4181
|
-
const readable = reader.
|
|
4182
|
-
Object.assign(readable, {
|
|
4183
|
-
diskNumberStart,
|
|
4184
|
-
offset: dataOffset,
|
|
4185
|
-
size
|
|
4186
|
-
});
|
|
4237
|
+
const readable = reader.createReadable({ offset: dataOffset, size, diskNumberStart, chunkSize: getChunkSize(config) });
|
|
4187
4238
|
const signal = getOptionValue$1(zipEntry, options, OPTION_SIGNAL);
|
|
4188
4239
|
const checkPasswordOnly = getOptionValue$1(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
|
|
4189
4240
|
let checkOverlappingEntry = getOptionValue$1(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
|
|
@@ -4454,7 +4505,6 @@
|
|
|
4454
4505
|
const view = getDataView$1(new Uint8Array(extraField.data));
|
|
4455
4506
|
let uid, gid;
|
|
4456
4507
|
if (isInfoZip) {
|
|
4457
|
-
// Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
|
|
4458
4508
|
let offset = 0;
|
|
4459
4509
|
const version = getUint8(view, offset++);
|
|
4460
4510
|
const uidSize = getUint8(view, offset++);
|
|
@@ -4464,8 +4514,6 @@
|
|
|
4464
4514
|
gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
|
|
4465
4515
|
Object.assign(extraField, { version, uid, gid });
|
|
4466
4516
|
} else if (extraField.data.length >= 4) {
|
|
4467
|
-
// Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
|
|
4468
|
-
// carried in the external file attributes, not here)
|
|
4469
4517
|
uid = getUint16(view, 0);
|
|
4470
4518
|
gid = getUint16(view, 2);
|
|
4471
4519
|
Object.assign(extraField, { uid, gid });
|
|
@@ -4517,7 +4565,6 @@
|
|
|
4517
4565
|
timeProperties.forEach((propertyName, indexProperty) => {
|
|
4518
4566
|
if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
|
|
4519
4567
|
const time = getUint32(extraFieldView, offset);
|
|
4520
|
-
// the extended timestamp is a signed 32-bit time_t value
|
|
4521
4568
|
directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
|
|
4522
4569
|
const rawPropertyName = timeRawProperties[indexProperty];
|
|
4523
4570
|
extraFieldExtendedTimestamp[rawPropertyName] = time;
|
|
@@ -4592,26 +4639,133 @@
|
|
|
4592
4639
|
readRanges.set(index, range);
|
|
4593
4640
|
}
|
|
4594
4641
|
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4642
|
+
function getStrictness(strictness, checkAmbiguity) {
|
|
4643
|
+
if (strictness === UNDEFINED_VALUE) {
|
|
4644
|
+
return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
|
|
4645
|
+
}
|
|
4646
|
+
return strictness;
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
|
|
4650
|
+
if (maxAppendedDataSize !== UNDEFINED_VALUE) {
|
|
4651
|
+
return maxAppendedDataSize;
|
|
4652
|
+
}
|
|
4653
|
+
if (strictness == STRICTNESS_STRICT) {
|
|
4654
|
+
return 0;
|
|
4655
|
+
}
|
|
4656
|
+
if (strictness == STRICTNESS_TOLERANT) {
|
|
4657
|
+
return Infinity;
|
|
4658
|
+
}
|
|
4659
|
+
return MAX_16_BITS;
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
|
|
4663
|
+
|
|
4664
|
+
const CENTRAL_DIRECTORY_UNREACHABLE = 0;
|
|
4665
|
+
const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
|
|
4666
|
+
const CENTRAL_DIRECTORY_REACHABLE = 2;
|
|
4667
|
+
|
|
4668
|
+
async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
|
|
4669
|
+
const { size } = reader;
|
|
4670
|
+
const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
|
|
4671
|
+
const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
|
|
4672
|
+
let endOfDirectoryInfo;
|
|
4673
|
+
let plausibleEndOfDirectoryInfo;
|
|
4674
|
+
let endOfDirectoryReachingEndCount = 0;
|
|
4675
|
+
for await (const [anchoredView, anchoredOffset, anchoredArray, indexByte, offset] of scanEndOfCentralDirectory(reader, anchoredLength)) {
|
|
4676
|
+
const commentLength = getUint16(anchoredView, indexByte + 20);
|
|
4677
|
+
if (offset + END_OF_CENTRAL_DIR_LENGTH + commentLength == size) {
|
|
4678
|
+
const reachability = await getCentralDirectoryReachability(reader, anchoredView, anchoredOffset, indexByte, offset, size, remoteProbeBudget);
|
|
4679
|
+
if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
|
|
4680
|
+
if (!endOfDirectoryInfo) {
|
|
4681
|
+
endOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
|
|
4682
|
+
}
|
|
4683
|
+
endOfDirectoryReachingEndCount++;
|
|
4684
|
+
if (!rejectAmbiguous || endOfDirectoryReachingEndCount > 1) {
|
|
4685
|
+
break;
|
|
4686
|
+
}
|
|
4687
|
+
} else if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleEndOfDirectoryInfo) {
|
|
4688
|
+
plausibleEndOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
|
|
4612
4689
|
}
|
|
4613
4690
|
}
|
|
4614
4691
|
}
|
|
4692
|
+
if (!endOfDirectoryInfo) {
|
|
4693
|
+
endOfDirectoryInfo = plausibleEndOfDirectoryInfo;
|
|
4694
|
+
}
|
|
4695
|
+
if (!endOfDirectoryInfo) {
|
|
4696
|
+
endOfDirectoryInfo = await seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget);
|
|
4697
|
+
}
|
|
4698
|
+
return { endOfDirectoryInfo, endOfDirectoryReachingEndCount };
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
async function seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget) {
|
|
4702
|
+
const { size } = reader;
|
|
4703
|
+
const searchLength = Math.min(size, maxAppendedDataSize == Infinity ? size :
|
|
4704
|
+
END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS + maxAppendedDataSize);
|
|
4705
|
+
let firstSignatureInfo, plausibleInfo;
|
|
4706
|
+
for await (const [searchView, searchOffset, searchArray, indexByte, offset] of scanEndOfCentralDirectory(reader, searchLength)) {
|
|
4707
|
+
const record = getEndOfCentralDirectoryInfo(searchArray, indexByte, offset);
|
|
4708
|
+
if (!firstSignatureInfo) {
|
|
4709
|
+
firstSignatureInfo = record;
|
|
4710
|
+
}
|
|
4711
|
+
const reachability = await getCentralDirectoryReachability(reader, searchView, searchOffset, indexByte, offset, size, remoteProbeBudget);
|
|
4712
|
+
if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
|
|
4713
|
+
return record;
|
|
4714
|
+
}
|
|
4715
|
+
if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleInfo) {
|
|
4716
|
+
plausibleInfo = record;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return plausibleInfo || firstSignatureInfo;
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
async function* scanEndOfCentralDirectory(reader, scanLength) {
|
|
4723
|
+
const scanOffset = reader.size - scanLength;
|
|
4724
|
+
const scanArray = await readUint8Array(reader, scanOffset, scanLength);
|
|
4725
|
+
const scanView = getDataView$1(scanArray);
|
|
4726
|
+
for (let indexByte = scanArray.length - END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
|
|
4727
|
+
if (getUint32(scanView, indexByte) == END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
4728
|
+
yield [scanView, scanOffset, scanArray, indexByte, scanOffset + indexByte];
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
function getEndOfCentralDirectoryInfo(scanArray, indexByte, offset) {
|
|
4734
|
+
return { offset, buffer: scanArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer };
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
|
|
4738
|
+
const filesLength = getUint16(view, indexByte + 10);
|
|
4739
|
+
const directoryDataLength = getUint32(view, indexByte + 12);
|
|
4740
|
+
const directoryDataOffset = getUint32(view, indexByte + 16);
|
|
4741
|
+
if (filesLength == MAX_16_BITS || directoryDataLength == MAX_32_BITS || directoryDataOffset == MAX_32_BITS) {
|
|
4742
|
+
const locatorSignature = await readSignature(reader, view, anchoredOffset, offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, size, remoteProbeBudget);
|
|
4743
|
+
return locatorSignature == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE ? CENTRAL_DIRECTORY_REACHABLE : CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4744
|
+
}
|
|
4745
|
+
if (!filesLength && !directoryDataLength) {
|
|
4746
|
+
return CENTRAL_DIRECTORY_PLAUSIBLE;
|
|
4747
|
+
}
|
|
4748
|
+
for (const centralDirectoryOffset of [offset - directoryDataLength, directoryDataOffset]) {
|
|
4749
|
+
if (await readSignature(reader, view, anchoredOffset, centralDirectoryOffset, size, remoteProbeBudget) == CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
4750
|
+
return CENTRAL_DIRECTORY_REACHABLE;
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
return CENTRAL_DIRECTORY_UNREACHABLE;
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
async function readSignature(reader, view, anchoredOffset, signatureOffset, size, remoteProbeBudget) {
|
|
4757
|
+
if (signatureOffset < 0 || signatureOffset + 4 > size) {
|
|
4758
|
+
return UNDEFINED_VALUE;
|
|
4759
|
+
}
|
|
4760
|
+
if (signatureOffset >= anchoredOffset) {
|
|
4761
|
+
return getUint32(view, signatureOffset - anchoredOffset);
|
|
4762
|
+
}
|
|
4763
|
+
if (remoteProbeBudget.count > 0) {
|
|
4764
|
+
remoteProbeBudget.count--;
|
|
4765
|
+
const signatureArray = await readUint8Array(reader, signatureOffset, 4);
|
|
4766
|
+
return getUint32(getDataView$1(signatureArray), 0);
|
|
4767
|
+
}
|
|
4768
|
+
return UNDEFINED_VALUE;
|
|
4615
4769
|
}
|
|
4616
4770
|
|
|
4617
4771
|
function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
|
|
@@ -4626,8 +4780,6 @@
|
|
|
4626
4780
|
if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
|
|
4627
4781
|
throwAmbiguousArchive("mismatched local file header (compression method)");
|
|
4628
4782
|
}
|
|
4629
|
-
// when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
|
|
4630
|
-
// also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
|
|
4631
4783
|
if (!localDirectory.bitFlag.dataDescriptor &&
|
|
4632
4784
|
(localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
|
|
4633
4785
|
(localDirectory.signature != zipEntry.signature ||
|
|
@@ -4676,10 +4828,6 @@
|
|
|
4676
4828
|
return Number(view.getBigUint64(offset, true));
|
|
4677
4829
|
}
|
|
4678
4830
|
|
|
4679
|
-
function setUint32$1(view, offset, value) {
|
|
4680
|
-
view.setUint32(offset, value, true);
|
|
4681
|
-
}
|
|
4682
|
-
|
|
4683
4831
|
function getDataView$1(array) {
|
|
4684
4832
|
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
4685
4833
|
}
|
|
@@ -4721,7 +4869,6 @@
|
|
|
4721
4869
|
const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
|
|
4722
4870
|
const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
|
|
4723
4871
|
const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
|
|
4724
|
-
// same message as the constant exported by zip-reader.js
|
|
4725
4872
|
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
4726
4873
|
const MIN_UNIX_TIME = -2147483648;
|
|
4727
4874
|
const MAX_UNIX_TIME = 2147483647;
|
|
@@ -4804,12 +4951,12 @@
|
|
|
4804
4951
|
rawExtraField,
|
|
4805
4952
|
} = entry;
|
|
4806
4953
|
const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
|
|
4807
|
-
rawExtraFieldZip64 = rawExtraFieldZip64 ||
|
|
4808
|
-
rawExtraFieldAES = rawExtraFieldAES ||
|
|
4809
|
-
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp ||
|
|
4810
|
-
rawExtraFieldNTFS = rawExtraFieldNTFS ||
|
|
4811
|
-
rawExtraFieldUnix = rawExtraFieldUnix ||
|
|
4812
|
-
rawExtraField = rawExtraField ||
|
|
4954
|
+
rawExtraFieldZip64 = rawExtraFieldZip64 || EMPTY_UINT8_ARRAY;
|
|
4955
|
+
rawExtraFieldAES = rawExtraFieldAES || EMPTY_UINT8_ARRAY;
|
|
4956
|
+
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || EMPTY_UINT8_ARRAY;
|
|
4957
|
+
rawExtraFieldNTFS = rawExtraFieldNTFS || EMPTY_UINT8_ARRAY;
|
|
4958
|
+
rawExtraFieldUnix = rawExtraFieldUnix || EMPTY_UINT8_ARRAY;
|
|
4959
|
+
rawExtraField = rawExtraField || EMPTY_UINT8_ARRAY;
|
|
4813
4960
|
if (entry.extraFieldAES) {
|
|
4814
4961
|
compressionMethod = COMPRESSION_METHOD_AES;
|
|
4815
4962
|
}
|
|
@@ -4915,7 +5062,7 @@
|
|
|
4915
5062
|
return false;
|
|
4916
5063
|
}
|
|
4917
5064
|
|
|
4918
|
-
async close(comment =
|
|
5065
|
+
async close(comment = EMPTY_UINT8_ARRAY, options = {}) {
|
|
4919
5066
|
const zipWriter = this;
|
|
4920
5067
|
const { pendingAddFileCalls, writer } = this;
|
|
4921
5068
|
const { writable } = writer;
|
|
@@ -5214,7 +5361,7 @@
|
|
|
5214
5361
|
if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
|
|
5215
5362
|
throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
|
|
5216
5363
|
}
|
|
5217
|
-
let rawExtraField =
|
|
5364
|
+
let rawExtraField = EMPTY_UINT8_ARRAY;
|
|
5218
5365
|
const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
|
|
5219
5366
|
if (extraField) {
|
|
5220
5367
|
let extraFieldSize = 0;
|
|
@@ -5275,8 +5422,6 @@
|
|
|
5275
5422
|
let maximumCompressedSize = 0;
|
|
5276
5423
|
let uncompressedSize = 0;
|
|
5277
5424
|
if (passThrough) {
|
|
5278
|
-
// the headers of a passThrough entry describe the payload verbatim: without a
|
|
5279
|
-
// reader they would declare content that is not there
|
|
5280
5425
|
if (!reader) {
|
|
5281
5426
|
throw new Error(ERR_UNDEFINED_READER);
|
|
5282
5427
|
}
|
|
@@ -5287,8 +5432,6 @@
|
|
|
5287
5432
|
}
|
|
5288
5433
|
const zip64Enabled = zip64 === true;
|
|
5289
5434
|
const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
|
|
5290
|
-
// entries without a reader have no payload: write them as empty stored files instead of
|
|
5291
|
-
// declaring a compressed or encrypted content that does not exist
|
|
5292
5435
|
const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
|
|
5293
5436
|
if (!reader) {
|
|
5294
5437
|
level = 0;
|
|
@@ -5426,7 +5569,6 @@
|
|
|
5426
5569
|
fileEntry.diskNumberStart = writer.diskNumber;
|
|
5427
5570
|
fileEntry.offset = getSegmentOffset(zipWriter, writer);
|
|
5428
5571
|
if (usdz) {
|
|
5429
|
-
// the padding depends on the entry offset, only known now that the writer is locked
|
|
5430
5572
|
const previousMetadataSize = entryInfo.metadataSize;
|
|
5431
5573
|
appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
|
|
5432
5574
|
fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
|
|
@@ -5469,6 +5611,13 @@
|
|
|
5469
5611
|
if (releaseLockWriter) {
|
|
5470
5612
|
releaseLockWriter();
|
|
5471
5613
|
}
|
|
5614
|
+
if (bufferedWrite && fileWriter && fileWriter.dispose) {
|
|
5615
|
+
try {
|
|
5616
|
+
await fileWriter.dispose();
|
|
5617
|
+
} catch {
|
|
5618
|
+
// ignored
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5472
5621
|
}
|
|
5473
5622
|
|
|
5474
5623
|
function requestLockCurrentFileEntry() {
|
|
@@ -5488,7 +5637,7 @@
|
|
|
5488
5637
|
async function skipDiskIfNeeded() {
|
|
5489
5638
|
if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
|
|
5490
5639
|
writer.availableSize = 0;
|
|
5491
|
-
await writeData(writer,
|
|
5640
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
5492
5641
|
}
|
|
5493
5642
|
}
|
|
5494
5643
|
}
|
|
@@ -5599,8 +5748,7 @@
|
|
|
5599
5748
|
}
|
|
5600
5749
|
const { writable } = writer;
|
|
5601
5750
|
if (reader) {
|
|
5602
|
-
reader.chunkSize
|
|
5603
|
-
const readable = reader.readable;
|
|
5751
|
+
const readable = reader.createReadable ? reader.createReadable({ chunkSize: getChunkSize(config) }) : reader.readable;
|
|
5604
5752
|
const size = reader.size;
|
|
5605
5753
|
const workerOptions = {
|
|
5606
5754
|
options: {
|
|
@@ -5700,13 +5848,9 @@
|
|
|
5700
5848
|
: compressionMethod !== COMPRESSION_METHOD_STORE);
|
|
5701
5849
|
let rawLocalExtraFieldZip64;
|
|
5702
5850
|
const uncompressedFile = passThrough || !compressed;
|
|
5703
|
-
// entries without a data descriptor are always buffered (cf. getFileEntry), so the
|
|
5704
|
-
// zip64 extra field values can be patched into the local header before flushing it
|
|
5705
5851
|
const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
|
|
5706
5852
|
const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
|
|
5707
5853
|
if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
|
|
5708
|
-
// APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
|
|
5709
|
-
// uncompressed and compressed size fields
|
|
5710
5854
|
const length = 4 + 16;
|
|
5711
5855
|
const extraFieldZip64 = createRecordWriter(length);
|
|
5712
5856
|
extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
|
|
@@ -5720,7 +5864,7 @@
|
|
|
5720
5864
|
}
|
|
5721
5865
|
}
|
|
5722
5866
|
} else {
|
|
5723
|
-
rawLocalExtraFieldZip64 =
|
|
5867
|
+
rawLocalExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
5724
5868
|
}
|
|
5725
5869
|
let rawExtraFieldAES;
|
|
5726
5870
|
if (encrypted && !zipCrypto) {
|
|
@@ -5730,7 +5874,7 @@
|
|
|
5730
5874
|
rawExtraFieldAES = extraFieldAES.array;
|
|
5731
5875
|
rawExtraFieldAES[8] = encryptionStrength;
|
|
5732
5876
|
} else {
|
|
5733
|
-
rawExtraFieldAES =
|
|
5877
|
+
rawExtraFieldAES = EMPTY_UINT8_ARRAY;
|
|
5734
5878
|
}
|
|
5735
5879
|
let rawExtraFieldNTFS;
|
|
5736
5880
|
let rawExtraFieldExtendedTimestamp;
|
|
@@ -5753,8 +5897,7 @@
|
|
|
5753
5897
|
}
|
|
5754
5898
|
rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
|
|
5755
5899
|
} else {
|
|
5756
|
-
|
|
5757
|
-
rawExtraFieldExtendedTimestamp = new Uint8Array();
|
|
5900
|
+
rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5758
5901
|
}
|
|
5759
5902
|
try {
|
|
5760
5903
|
const lastModTimeNTFS = getTimeNTFS(lastModDate);
|
|
@@ -5769,10 +5912,10 @@
|
|
|
5769
5912
|
extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
|
|
5770
5913
|
rawExtraFieldNTFS = extraFieldNTFS.array;
|
|
5771
5914
|
} catch {
|
|
5772
|
-
rawExtraFieldNTFS =
|
|
5915
|
+
rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
|
|
5773
5916
|
}
|
|
5774
5917
|
} else {
|
|
5775
|
-
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp =
|
|
5918
|
+
rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
|
|
5776
5919
|
}
|
|
5777
5920
|
let rawExtraFieldUnix;
|
|
5778
5921
|
try {
|
|
@@ -5798,10 +5941,10 @@
|
|
|
5798
5941
|
extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
|
|
5799
5942
|
rawExtraFieldUnix = extraFieldUnix.array;
|
|
5800
5943
|
} else {
|
|
5801
|
-
rawExtraFieldUnix =
|
|
5944
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5802
5945
|
}
|
|
5803
5946
|
} catch {
|
|
5804
|
-
rawExtraFieldUnix =
|
|
5947
|
+
rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
|
|
5805
5948
|
}
|
|
5806
5949
|
if (compressionMethod === UNDEFINED_VALUE) {
|
|
5807
5950
|
compressionMethod = compressed ? COMPRESSION_METHOD_DEFLATE : COMPRESSION_METHOD_STORE;
|
|
@@ -5868,7 +6011,7 @@
|
|
|
5868
6011
|
version,
|
|
5869
6012
|
compressionMethod,
|
|
5870
6013
|
extraFieldExtendedTimestampFlag,
|
|
5871
|
-
rawExtraFieldZip64:
|
|
6014
|
+
rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
|
|
5872
6015
|
localExtraFieldZip64Length,
|
|
5873
6016
|
rawExtraFieldExtendedTimestamp,
|
|
5874
6017
|
rawExtraFieldNTFS,
|
|
@@ -5881,7 +6024,6 @@
|
|
|
5881
6024
|
function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
|
|
5882
6025
|
const { headerInfo } = entryInfo;
|
|
5883
6026
|
let { localHeaderArray, extraFieldLength } = headerInfo;
|
|
5884
|
-
let localHeaderArrayView;
|
|
5885
6027
|
let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
|
|
5886
6028
|
if (extraBytesLength < 4) {
|
|
5887
6029
|
extraBytesLength += 64;
|
|
@@ -5894,7 +6036,7 @@
|
|
|
5894
6036
|
headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
|
|
5895
6037
|
arraySet(localHeaderArray, previousLocalHeaderArray);
|
|
5896
6038
|
arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
|
|
5897
|
-
localHeaderArrayView = getDataView(localHeaderArray);
|
|
6039
|
+
const localHeaderArrayView = getDataView(localHeaderArray);
|
|
5898
6040
|
setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
|
|
5899
6041
|
headerInfo.localHeaderView = localHeaderArrayView;
|
|
5900
6042
|
entryInfo.metadataSize += extraBytesLength;
|
|
@@ -5902,7 +6044,7 @@
|
|
|
5902
6044
|
|
|
5903
6045
|
function packUnixId(id) {
|
|
5904
6046
|
if (id === UNDEFINED_VALUE) {
|
|
5905
|
-
return
|
|
6047
|
+
return EMPTY_UINT8_ARRAY;
|
|
5906
6048
|
} else {
|
|
5907
6049
|
const dataArray = new Uint8Array(4);
|
|
5908
6050
|
const dataView = getDataView(dataArray);
|
|
@@ -5945,7 +6087,7 @@
|
|
|
5945
6087
|
dataDescriptor,
|
|
5946
6088
|
dataDescriptorSignature
|
|
5947
6089
|
}) {
|
|
5948
|
-
let dataDescriptorArray =
|
|
6090
|
+
let dataDescriptorArray = EMPTY_UINT8_ARRAY;
|
|
5949
6091
|
let dataDescriptorView, dataDescriptorOffset = 0;
|
|
5950
6092
|
let dataDescriptorLength = zip64 ? DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH : DATA_DESCRIPTOR_RECORD_LENGTH;
|
|
5951
6093
|
if (dataDescriptorSignature) {
|
|
@@ -6029,7 +6171,6 @@
|
|
|
6029
6171
|
}
|
|
6030
6172
|
}
|
|
6031
6173
|
if (zip64 && localExtraFieldZip64Length) {
|
|
6032
|
-
// the local zip64 extra field always contains both size slots
|
|
6033
6174
|
const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
|
|
6034
6175
|
setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
|
|
6035
6176
|
setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
|
|
@@ -6083,7 +6224,7 @@
|
|
|
6083
6224
|
}
|
|
6084
6225
|
rawExtraFieldZip64 = extraFieldZip64.array;
|
|
6085
6226
|
} else {
|
|
6086
|
-
rawExtraFieldZip64 =
|
|
6227
|
+
rawExtraFieldZip64 = EMPTY_UINT8_ARRAY;
|
|
6087
6228
|
}
|
|
6088
6229
|
fileEntry.rawExtraFieldZip64 = rawExtraFieldZip64;
|
|
6089
6230
|
fileEntry.zip64Offset = zip64Offset;
|
|
@@ -6098,7 +6239,7 @@
|
|
|
6098
6239
|
extraFieldTimestamp.uint32(lastModTimeUnix);
|
|
6099
6240
|
rawExtraFieldTimestamp = extraFieldTimestamp.array;
|
|
6100
6241
|
} else {
|
|
6101
|
-
rawExtraFieldTimestamp =
|
|
6242
|
+
rawExtraFieldTimestamp = EMPTY_UINT8_ARRAY;
|
|
6102
6243
|
}
|
|
6103
6244
|
fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
|
|
6104
6245
|
const extraFieldLength = getLength(
|
|
@@ -6154,7 +6295,7 @@
|
|
|
6154
6295
|
await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
|
|
6155
6296
|
directoryDiskOffset = offset;
|
|
6156
6297
|
writer.availableSize = 0;
|
|
6157
|
-
await writeData(writer,
|
|
6298
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6158
6299
|
}
|
|
6159
6300
|
if (indexFileEntry == 0) {
|
|
6160
6301
|
cdStartDiskNumber = writer.diskNumber;
|
|
@@ -6227,7 +6368,7 @@
|
|
|
6227
6368
|
const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
|
|
6228
6369
|
if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
|
|
6229
6370
|
writer.availableSize = 0;
|
|
6230
|
-
await writeData(writer,
|
|
6371
|
+
await writeData(writer, EMPTY_UINT8_ARRAY);
|
|
6231
6372
|
}
|
|
6232
6373
|
lastDiskNumber = writer.diskNumber;
|
|
6233
6374
|
if (zip64) {
|
|
@@ -6324,7 +6465,6 @@
|
|
|
6324
6465
|
return Math.floor(date.getTime() / 1000);
|
|
6325
6466
|
}
|
|
6326
6467
|
|
|
6327
|
-
// the extended timestamp extra field stores dates as signed 32-bit time_t values
|
|
6328
6468
|
function inUnixTimeRange(timeUnix) {
|
|
6329
6469
|
return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
|
|
6330
6470
|
}
|
|
@@ -6506,6 +6646,141 @@
|
|
|
6506
6646
|
*/
|
|
6507
6647
|
|
|
6508
6648
|
|
|
6649
|
+
const DEFAULT_THRESHOLD = 1024 * 1024;
|
|
6650
|
+
const DEFAULT_DIRECTORY_NAME = ".zip.js-temp";
|
|
6651
|
+
|
|
6652
|
+
function createOPFSTempStream(options = {}) {
|
|
6653
|
+
const {
|
|
6654
|
+
thresholdBytes = DEFAULT_THRESHOLD,
|
|
6655
|
+
directoryName = DEFAULT_DIRECTORY_NAME,
|
|
6656
|
+
getDirectory = () => navigator.storage.getDirectory()
|
|
6657
|
+
} = options;
|
|
6658
|
+
let directoryHandlePromise;
|
|
6659
|
+
function getTempDirectory() {
|
|
6660
|
+
if (!directoryHandlePromise) {
|
|
6661
|
+
directoryHandlePromise = Promise.resolve(getDirectory())
|
|
6662
|
+
.then(root => root.getDirectoryHandle(directoryName, { create: true }));
|
|
6663
|
+
}
|
|
6664
|
+
return directoryHandlePromise;
|
|
6665
|
+
}
|
|
6666
|
+
return function () {
|
|
6667
|
+
const memoryChunks = [];
|
|
6668
|
+
let bufferedSize = 0;
|
|
6669
|
+
let spilled = false;
|
|
6670
|
+
let fileName, fileHandle, fileWriter, fileReader;
|
|
6671
|
+
|
|
6672
|
+
async function spillToFile() {
|
|
6673
|
+
const directoryHandle = await getTempDirectory();
|
|
6674
|
+
fileName = crypto.randomUUID();
|
|
6675
|
+
fileHandle = await directoryHandle.getFileHandle(fileName, { create: true });
|
|
6676
|
+
fileWriter = (await fileHandle.createWritable()).getWriter();
|
|
6677
|
+
spilled = true;
|
|
6678
|
+
for (const chunk of memoryChunks) {
|
|
6679
|
+
await fileWriter.write(chunk);
|
|
6680
|
+
}
|
|
6681
|
+
memoryChunks.length = 0;
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
const writable = new WritableStream({
|
|
6685
|
+
async write(chunk) {
|
|
6686
|
+
if (spilled) {
|
|
6687
|
+
await fileWriter.write(chunk);
|
|
6688
|
+
} else {
|
|
6689
|
+
memoryChunks.push(chunk);
|
|
6690
|
+
bufferedSize += chunk.length;
|
|
6691
|
+
if (bufferedSize > thresholdBytes) {
|
|
6692
|
+
await spillToFile();
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
},
|
|
6696
|
+
async close() {
|
|
6697
|
+
if (fileWriter) {
|
|
6698
|
+
await fileWriter.close();
|
|
6699
|
+
fileWriter = null;
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
});
|
|
6703
|
+
|
|
6704
|
+
let memoryIndex = 0;
|
|
6705
|
+
const readable = new ReadableStream({
|
|
6706
|
+
async pull(controller) {
|
|
6707
|
+
if (spilled) {
|
|
6708
|
+
if (!fileReader) {
|
|
6709
|
+
const file = await fileHandle.getFile();
|
|
6710
|
+
fileReader = file.stream().getReader();
|
|
6711
|
+
}
|
|
6712
|
+
const { value, done } = await fileReader.read();
|
|
6713
|
+
if (done) {
|
|
6714
|
+
controller.close();
|
|
6715
|
+
} else {
|
|
6716
|
+
controller.enqueue(value);
|
|
6717
|
+
}
|
|
6718
|
+
} else if (memoryIndex < memoryChunks.length) {
|
|
6719
|
+
controller.enqueue(memoryChunks[memoryIndex++]);
|
|
6720
|
+
} else {
|
|
6721
|
+
controller.close();
|
|
6722
|
+
}
|
|
6723
|
+
},
|
|
6724
|
+
async cancel(reason) {
|
|
6725
|
+
if (fileReader) {
|
|
6726
|
+
await fileReader.cancel(reason);
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
}, { highWaterMark: 0 });
|
|
6730
|
+
async function dispose() {
|
|
6731
|
+
if (fileWriter) {
|
|
6732
|
+
try {
|
|
6733
|
+
await fileWriter.close();
|
|
6734
|
+
} catch {
|
|
6735
|
+
// ignored
|
|
6736
|
+
}
|
|
6737
|
+
fileWriter = null;
|
|
6738
|
+
}
|
|
6739
|
+
if (fileName) {
|
|
6740
|
+
try {
|
|
6741
|
+
const directoryHandle = await getTempDirectory();
|
|
6742
|
+
await directoryHandle.removeEntry(fileName);
|
|
6743
|
+
} catch {
|
|
6744
|
+
// ignored
|
|
6745
|
+
}
|
|
6746
|
+
fileHandle = fileName = null;
|
|
6747
|
+
}
|
|
6748
|
+
memoryChunks.length = 0;
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6751
|
+
return { writable, readable, dispose };
|
|
6752
|
+
};
|
|
6753
|
+
}
|
|
6754
|
+
|
|
6755
|
+
/*
|
|
6756
|
+
Copyright (c) 2025 Gildas Lormeau. All rights reserved.
|
|
6757
|
+
|
|
6758
|
+
Redistribution and use in source and binary forms, with or without
|
|
6759
|
+
modification, are permitted provided that the following conditions are met:
|
|
6760
|
+
|
|
6761
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
6762
|
+
this list of conditions and the following disclaimer.
|
|
6763
|
+
|
|
6764
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
6765
|
+
notice, this list of conditions and the following disclaimer in
|
|
6766
|
+
the documentation and/or other materials provided with the distribution.
|
|
6767
|
+
|
|
6768
|
+
3. The names of the authors may not be used to endorse or promote products
|
|
6769
|
+
derived from this software without specific prior written permission.
|
|
6770
|
+
|
|
6771
|
+
THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
|
6772
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
6773
|
+
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
|
6774
|
+
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
6775
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
6776
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
6777
|
+
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
6778
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
6779
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
6780
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
6781
|
+
*/
|
|
6782
|
+
|
|
6783
|
+
|
|
6509
6784
|
try {
|
|
6510
6785
|
configure({ baseURI: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('zip-core.js', document.baseURI).href)) });
|
|
6511
6786
|
} catch {
|
|
@@ -6536,6 +6811,7 @@
|
|
|
6536
6811
|
exports.ERR_INVALID_SIGNATURE = ERR_INVALID_SIGNATURE;
|
|
6537
6812
|
exports.ERR_INVALID_UNCOMPRESSED_SIZE = ERR_INVALID_UNCOMPRESSED_SIZE;
|
|
6538
6813
|
exports.ERR_INVALID_VERSION = ERR_INVALID_VERSION;
|
|
6814
|
+
exports.ERR_ITERATOR_COMPLETED_TOO_SOON = ERR_ITERATOR_COMPLETED_TOO_SOON;
|
|
6539
6815
|
exports.ERR_LOCAL_FILE_HEADER_NOT_FOUND = ERR_LOCAL_FILE_HEADER_NOT_FOUND;
|
|
6540
6816
|
exports.ERR_OVERLAPPING_ENTRY = ERR_OVERLAPPING_ENTRY;
|
|
6541
6817
|
exports.ERR_SPLIT_ZIP_FILE = ERR_SPLIT_ZIP_FILE;
|
|
@@ -6544,6 +6820,7 @@
|
|
|
6544
6820
|
exports.ERR_UNSUPPORTED_COMPRESSION = ERR_UNSUPPORTED_COMPRESSION$1;
|
|
6545
6821
|
exports.ERR_UNSUPPORTED_ENCRYPTION = ERR_UNSUPPORTED_ENCRYPTION;
|
|
6546
6822
|
exports.ERR_UNSUPPORTED_FORMAT = ERR_UNSUPPORTED_FORMAT;
|
|
6823
|
+
exports.ERR_WRITER_NOT_INITIALIZED = ERR_WRITER_NOT_INITIALIZED;
|
|
6547
6824
|
exports.ERR_ZIP_NOT_EMPTY = ERR_ZIP_NOT_EMPTY;
|
|
6548
6825
|
exports.HttpRangeReader = HttpRangeReader;
|
|
6549
6826
|
exports.HttpReader = HttpReader;
|
|
@@ -6560,6 +6837,7 @@
|
|
|
6560
6837
|
exports.ZipWriter = ZipWriter;
|
|
6561
6838
|
exports.ZipWriterStream = ZipWriterStream;
|
|
6562
6839
|
exports.configure = configure;
|
|
6840
|
+
exports.createOPFSTempStream = createOPFSTempStream;
|
|
6563
6841
|
exports.getMimeType = getMimeType;
|
|
6564
6842
|
exports.terminateWorkers = terminateWorkers;
|
|
6565
6843
|
|