@zip.js/zip.js 2.15.0 → 2.16.1

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.
Files changed (44) hide show
  1. package/BENCHMARKS.md +4 -2
  2. package/deno.json +1 -1
  3. package/dist/zip-core-external.js +410 -227
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +234 -109
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +552 -237
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +377 -118
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +552 -237
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +379 -120
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +554 -237
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +236 -111
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-native.js +236 -111
  20. package/dist/zip-native.min.js +1 -1
  21. package/dist/zip-web-worker-native.js +1 -1
  22. package/dist/zip-web-worker.js +1 -1
  23. package/dist/zip.js +411 -228
  24. package/dist/zip.min.js +1 -1
  25. package/eslint.config.mjs +1 -1
  26. package/index-native.cjs +379 -120
  27. package/index-native.min.js +1 -1
  28. package/index.cjs +554 -237
  29. package/index.d.cts +143 -16
  30. package/index.d.ts +143 -16
  31. package/index.min.js +1 -1
  32. package/lib/core/io.js +3 -2
  33. package/lib/core/streams/aes-crypto-stream.js +60 -17
  34. package/lib/core/streams/zip-crypto-stream.js +7 -7
  35. package/lib/core/streams/zip-entry-stream.js +146 -73
  36. package/lib/core/streams/zlib-wasm/zlib-streams.js +176 -118
  37. package/lib/core/util/compatible-streams.js +10 -5
  38. package/lib/core/version.js +1 -1
  39. package/lib/core/web-worker-inline-native.js +1 -1
  40. package/lib/core/web-worker-inline-wasm.js +1 -1
  41. package/lib/core/zip-fs.js +144 -10
  42. package/lib/core/zip-reader.js +5 -5
  43. package/lib/core/zip-writer.js +4 -2
  44. package/package.json +3 -2
@@ -223,7 +223,7 @@
223
223
  const ERR_INVALID_PASSWORD_TYPE = "Invalid password (password must be a string, rawPassword must be a Uint8Array)";
224
224
  const ERR_INVALID_PASS_THROUGH_VALUE = "Invalid passThrough option (must be a boolean or 'compressed')";
225
225
  const ERR_ABORTED = "The operation was aborted";
226
- const ABORT_ERROR_NAME = "AbortError";
226
+ const ABORT_ERROR_NAME$1 = "AbortError";
227
227
 
228
228
  function checkFunctionOption(value) {
229
229
  if (value && typeof value != FUNCTION_TYPE) {
@@ -241,7 +241,7 @@
241
241
 
242
242
  function throwIfAborted(signal) {
243
243
  if (signal && signal.aborted) {
244
- throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME) : signal.reason;
244
+ throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME$1) : signal.reason;
245
245
  }
246
246
  }
247
247
 
@@ -1283,12 +1283,12 @@
1283
1283
  class AESDecryptionStream extends TransformStream {
1284
1284
 
1285
1285
  constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly, checkAuthenticationCode = true }) {
1286
+ const aesCrypto = {};
1286
1287
  super({
1287
1288
  start() {
1288
- initAesCrypto(this, password, rawPassword, encryptionStrength);
1289
+ initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength);
1289
1290
  },
1290
1291
  async transform(chunk, controller) {
1291
- const aesCrypto = this;
1292
1292
  const {
1293
1293
  password,
1294
1294
  strength,
@@ -1307,6 +1307,9 @@
1307
1307
  } else {
1308
1308
  await ready;
1309
1309
  }
1310
+ if (aesCrypto.discarded) {
1311
+ return;
1312
+ }
1310
1313
  const output = new Uint8Array(chunk.length - AUTHENTICATION_CODE_LENGTH - ((chunk.length - AUTHENTICATION_CODE_LENGTH) % BLOCK_LENGTH));
1311
1314
  controller.enqueue(append(aesCrypto, chunk, output, 0, AUTHENTICATION_CODE_LENGTH, true));
1312
1315
  },
@@ -1315,9 +1318,12 @@
1315
1318
  engine,
1316
1319
  pendingInput,
1317
1320
  ready
1318
- } = this;
1321
+ } = aesCrypto;
1319
1322
  if (engine) {
1320
1323
  await ready;
1324
+ if (aesCrypto.discarded) {
1325
+ return;
1326
+ }
1321
1327
  const originalAuthenticationCode = subarray(pendingInput, pendingInput.length - AUTHENTICATION_CODE_LENGTH);
1322
1328
  const decryptedChunkArray = new Uint8Array(subarray(pendingInput, 0, pendingInput.length - AUTHENTICATION_CODE_LENGTH));
1323
1329
  engine.process(decryptedChunkArray, true);
@@ -1327,27 +1333,26 @@
1327
1333
  invalidAuthenticationCode |= authenticationCode[indexByte] ^ originalAuthenticationCode[indexByte];
1328
1334
  }
1329
1335
  if (invalidAuthenticationCode && checkAuthenticationCode) {
1330
- throw new Error(ERR_INVALID_AUTHENTICATION_CODE);
1336
+ controller.error(new Error(ERR_INVALID_AUTHENTICATION_CODE));
1337
+ return;
1331
1338
  }
1332
1339
  controller.enqueue(decryptedChunkArray);
1333
1340
  }
1334
- },
1335
- cancel() {
1336
- disposeEngine(this);
1337
1341
  }
1338
1342
  });
1343
+ setDisposingReadable(this, aesCrypto);
1339
1344
  }
1340
1345
  }
1341
1346
 
1342
1347
  class AESEncryptionStream extends TransformStream {
1343
1348
 
1344
1349
  constructor({ password, rawPassword, encryptionStrength }) {
1350
+ const aesCrypto = {};
1345
1351
  super({
1346
1352
  start() {
1347
- initAesCrypto(this, password, rawPassword, encryptionStrength);
1353
+ initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength);
1348
1354
  },
1349
1355
  async transform(chunk, controller) {
1350
- const aesCrypto = this;
1351
1356
  const {
1352
1357
  password,
1353
1358
  strength,
@@ -1361,6 +1366,9 @@
1361
1366
  } else {
1362
1367
  await ready;
1363
1368
  }
1369
+ if (aesCrypto.discarded) {
1370
+ return;
1371
+ }
1364
1372
  const output = new Uint8Array(preamble.length + chunk.length - (chunk.length % BLOCK_LENGTH));
1365
1373
  output.set(preamble, 0);
1366
1374
  controller.enqueue(append(aesCrypto, chunk, output, preamble.length, 0, false));
@@ -1370,19 +1378,20 @@
1370
1378
  engine,
1371
1379
  pendingInput,
1372
1380
  ready
1373
- } = this;
1381
+ } = aesCrypto;
1374
1382
  if (engine) {
1375
1383
  await ready;
1384
+ if (aesCrypto.discarded) {
1385
+ return;
1386
+ }
1376
1387
  const encryptedChunkArray = new Uint8Array(pendingInput);
1377
1388
  engine.process(encryptedChunkArray, false);
1378
1389
  const authenticationCode = subarray(engine.digest(), 0, AUTHENTICATION_CODE_LENGTH);
1379
1390
  controller.enqueue(concat(encryptedChunkArray, authenticationCode));
1380
1391
  }
1381
- },
1382
- cancel() {
1383
- disposeEngine(this);
1384
1392
  }
1385
1393
  });
1394
+ setDisposingReadable(this, aesCrypto);
1386
1395
  }
1387
1396
  }
1388
1397
 
@@ -1391,7 +1400,37 @@
1391
1400
  ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
1392
1401
  password: encodePassword(password, rawPassword),
1393
1402
  strength: encryptionStrength - 1,
1394
- pendingInput: EMPTY_UINT8_ARRAY
1403
+ pendingInput: EMPTY_UINT8_ARRAY,
1404
+ discarded: false
1405
+ });
1406
+ }
1407
+
1408
+ function setDisposingReadable(stream, aesCrypto) {
1409
+ const reader = stream.readable.getReader();
1410
+ const readable = new ReadableStream({
1411
+ async pull(controller) {
1412
+ try {
1413
+ const { value, done } = await reader.read();
1414
+ if (done) {
1415
+ controller.close();
1416
+ } else {
1417
+ controller.enqueue(value);
1418
+ }
1419
+ } catch (error) {
1420
+ disposeEngine(aesCrypto);
1421
+ reader.cancel(error).catch(() => { });
1422
+ throw error;
1423
+ }
1424
+ },
1425
+ cancel(reason) {
1426
+ disposeEngine(aesCrypto);
1427
+ return reader.cancel(reason);
1428
+ }
1429
+ });
1430
+ Object.defineProperty(stream, "readable", {
1431
+ get() {
1432
+ return readable;
1433
+ }
1395
1434
  });
1396
1435
  }
1397
1436
 
@@ -1424,7 +1463,9 @@
1424
1463
  }
1425
1464
  }
1426
1465
 
1427
- function disposeEngine({ engine }) {
1466
+ function disposeEngine(aesCrypto) {
1467
+ const { engine } = aesCrypto;
1468
+ aesCrypto.discarded = true;
1428
1469
  if (engine && engine.dispose) {
1429
1470
  engine.dispose();
1430
1471
  }
@@ -1441,6 +1482,9 @@
1441
1482
  const keyLength = KEY_LENGTH[strength];
1442
1483
  const compositeKey = await deriveKey(password, salt, keyLength * 2 + PASSWORD_VERIFICATION_LENGTH);
1443
1484
  aesCrypto.engine = createEngine(subarray(compositeKey, 0, keyLength), subarray(compositeKey, keyLength, keyLength * 2));
1485
+ if (aesCrypto.discarded) {
1486
+ disposeEngine(aesCrypto);
1487
+ }
1444
1488
  return subarray(compositeKey, keyLength * 2);
1445
1489
  }
1446
1490
 
@@ -1592,11 +1636,11 @@
1592
1636
  }
1593
1637
 
1594
1638
  function createKeys(target, password, rawPassword) {
1595
- const keys = [0x12345678, 0x23456789, 0x34567890];
1639
+ const cryptoKeys = [0x12345678, 0x23456789, 0x34567890];
1596
1640
  Object.assign(target, {
1597
- keys,
1598
- crcKey0: new Crc32(keys[0]),
1599
- crcKey2: new Crc32(keys[2])
1641
+ cryptoKeys,
1642
+ crcKey0: new Crc32(cryptoKeys[0]),
1643
+ crcKey2: new Crc32(cryptoKeys[2])
1600
1644
  });
1601
1645
  if (rawPassword) {
1602
1646
  for (let index = 0; index < rawPassword.length; index++) {
@@ -1610,17 +1654,17 @@
1610
1654
  }
1611
1655
 
1612
1656
  function updateKeys(target, byte) {
1613
- let [, key1] = target.keys;
1657
+ let [, key1] = target.cryptoKeys;
1614
1658
  target.crcKey0.append([byte]);
1615
1659
  const key0 = ~target.crcKey0.get();
1616
1660
  key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1);
1617
1661
  target.crcKey2.append([key1 >>> 24]);
1618
1662
  const key2 = ~target.crcKey2.get();
1619
- target.keys = [key0, key1, key2];
1663
+ target.cryptoKeys = [key0, key1, key2];
1620
1664
  }
1621
1665
 
1622
1666
  function getByte(target) {
1623
- const temp = target.keys[2] | 2;
1667
+ const temp = target.cryptoKeys[2] | 2;
1624
1668
  return getInt8(Math.imul(temp, (temp ^ 1)) >>> 8);
1625
1669
  }
1626
1670
 
@@ -1668,11 +1712,16 @@
1668
1712
  const reader = readable.getReader();
1669
1713
  return new ReadableStream({
1670
1714
  async pull(controller) {
1671
- const { value, done } = await reader.read();
1672
- if (done) {
1673
- controller.close();
1674
- } else {
1675
- controller.enqueue(value);
1715
+ try {
1716
+ const { value, done } = await reader.read();
1717
+ if (done) {
1718
+ controller.close();
1719
+ } else {
1720
+ controller.enqueue(value);
1721
+ }
1722
+ } catch (error) {
1723
+ reader.cancel(error).catch(() => { });
1724
+ throw error;
1676
1725
  }
1677
1726
  },
1678
1727
  cancel(reason) {
@@ -1858,7 +1907,6 @@
1858
1907
  const GZIP_HEADER_LENGTH = 10;
1859
1908
  const GZIP_TRAILER_LENGTH = 8;
1860
1909
  const GZIP_HEADER_BYTES = [0x1f, 0x8b, 0x08];
1861
- const GZIP_OUTPUT_STALL_TIMEOUT = 5000;
1862
1910
 
1863
1911
  class DeflateStream extends TransformStream {
1864
1912
 
@@ -1869,7 +1917,7 @@
1869
1917
  let crc32Stream, encryptionStream, gzipCrc32Stream;
1870
1918
  let readable = super.readable;
1871
1919
  const codecStreams = format && getCodecStreams(format);
1872
- const GzipCompressionStream = getGzipCompressionStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
1920
+ const GzipCompressionStream = getGzipCodecStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
1873
1921
  const useGzipCrc32 = computeCrc32 && compressed && !deflate64 && !codecStreams && (!encrypted || zipCrypto) && Boolean(GzipCompressionStream);
1874
1922
  if ((!encrypted || zipCrypto) && computeCrc32 && !useGzipCrc32) {
1875
1923
  crc32Stream = new Crc32Stream();
@@ -1886,6 +1934,9 @@
1886
1934
  try {
1887
1935
  readable = pipeThroughCompressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamFallback);
1888
1936
  } catch (error) {
1937
+ if (!useCompressionStream && CompressionStreamFallback) {
1938
+ throw error;
1939
+ }
1889
1940
  let gzipStream;
1890
1941
  try {
1891
1942
  gzipStream = new CompressionStream(FORMAT_GZIP);
@@ -1955,74 +2006,134 @@
1955
2006
  }
1956
2007
 
1957
2008
  function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32) {
2009
+ const writer = gzipStream.writable.getWriter();
2010
+ const reader = gzipStream.readable.getReader();
2011
+ const outputCrc32 = crc32 === UNDEFINED_VALUE ? new Crc32() : UNDEFINED_VALUE;
1958
2012
  let outputLength = 0;
1959
2013
  let inputDone = false;
1960
- let watchdogTimeout;
2014
+ let trailerWritten = false;
2015
+ let idleCheckArmed = false;
2016
+ let readCount = 0;
2017
+ let readPending = false;
1961
2018
  let resolveTrailerReady, rejectTrailerReady;
1962
2019
  const trailerReady = new Promise((resolve, reject) => {
1963
2020
  resolveTrailerReady = resolve;
1964
2021
  rejectTrailerReady = reject;
1965
2022
  });
1966
2023
  trailerReady.catch(() => { });
1967
- if (!outputSize) {
1968
- resolveTrailerReady();
1969
- }
1970
- const gzipWrapStream = new TransformStream({
1971
- start(controller) {
2024
+ pump();
2025
+ return new ReadableStream({
2026
+ async pull(controller) {
2027
+ try {
2028
+ const { value, done } = await read();
2029
+ if (done) {
2030
+ controller.close();
2031
+ } else {
2032
+ outputLength += value.length;
2033
+ if (outputLength > outputSize) {
2034
+ throw new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
2035
+ }
2036
+ if (outputCrc32) {
2037
+ outputCrc32.append(value);
2038
+ }
2039
+ controller.enqueue(value);
2040
+ }
2041
+ } catch (error) {
2042
+ rejectTrailerReady(error);
2043
+ await cancel(reader, error);
2044
+ throw trailerWritten ? getTrailerError(error) : error;
2045
+ }
2046
+ },
2047
+ cancel(reason) {
2048
+ rejectTrailerReady(reason);
2049
+ return reader.cancel(reason);
2050
+ }
2051
+ });
2052
+
2053
+ async function pump() {
2054
+ const inputReader = readable.getReader();
2055
+ try {
1972
2056
  const header = new Uint8Array(GZIP_HEADER_LENGTH);
1973
2057
  header.set(GZIP_HEADER_BYTES);
1974
- controller.enqueue(header);
1975
- },
1976
- transform(chunk, controller) {
1977
- controller.enqueue(chunk);
1978
- },
1979
- async flush(controller) {
2058
+ await writer.write(header);
2059
+ for (; ;) {
2060
+ await writer.ready;
2061
+ const { value, done } = await inputReader.read();
2062
+ if (done) {
2063
+ break;
2064
+ }
2065
+ await writer.write(value);
2066
+ }
1980
2067
  inputDone = true;
1981
- startWatchdog();
1982
- try {
2068
+ if (outputCrc32) {
2069
+ if (readPending) {
2070
+ armIdleCheck();
2071
+ }
1983
2072
  await trailerReady;
1984
- } finally {
1985
- stopWatchdog();
1986
2073
  }
1987
2074
  const trailer = new Uint8Array(GZIP_TRAILER_LENGTH);
1988
2075
  const dataView = getDataView(trailer);
1989
- dataView.setUint32(0, crc32.get(), true);
2076
+ dataView.setUint32(0, outputCrc32 ? outputCrc32.get() : crc32, true);
1990
2077
  dataView.setUint32(4, outputSize, true);
1991
- controller.enqueue(trailer);
1992
- },
1993
- cancel(reason) {
1994
- rejectTrailerReady(reason);
2078
+ trailerWritten = true;
2079
+ await writer.write(trailer);
2080
+ await writer.close();
2081
+ } catch (error) {
2082
+ await abort(writer, error);
2083
+ await cancel(inputReader, error);
1995
2084
  }
1996
- });
1997
- const outputStream = new TransformStream({
1998
- transform(chunk, controller) {
1999
- crc32.append(chunk);
2000
- outputLength += chunk.length;
2001
- if (outputLength >= outputSize) {
2002
- resolveTrailerReady();
2003
- } else if (inputDone) {
2004
- startWatchdog();
2005
- }
2006
- controller.enqueue(chunk);
2007
- },
2008
- cancel(reason) {
2009
- rejectTrailerReady(reason);
2085
+ }
2086
+
2087
+ function read() {
2088
+ readCount++;
2089
+ readPending = true;
2090
+ const result = reader.read();
2091
+ result.then(onReadSettled, onReadSettled);
2092
+ if (inputDone && outputCrc32) {
2093
+ armIdleCheck();
2010
2094
  }
2011
- });
2012
- readable = pipeThrough(readable, gzipWrapStream);
2013
- readable = pipeThroughBackpressured(readable, gzipStream);
2014
- return pipeThrough(readable, outputStream);
2095
+ return result;
2096
+ }
2015
2097
 
2016
- function startWatchdog() {
2017
- stopWatchdog();
2018
- watchdogTimeout = setTimeout(() => rejectTrailerReady(new Error(ERR_INVALID_UNCOMPRESSED_SIZE)), GZIP_OUTPUT_STALL_TIMEOUT);
2098
+ function onReadSettled() {
2099
+ readPending = false;
2019
2100
  }
2020
2101
 
2021
- function stopWatchdog() {
2022
- clearTimeout(watchdogTimeout);
2102
+ async function armIdleCheck() {
2103
+ if (!idleCheckArmed) {
2104
+ idleCheckArmed = true;
2105
+ const count = readCount;
2106
+ await nextTask();
2107
+ idleCheckArmed = false;
2108
+ if (readPending) {
2109
+ if (readCount == count) {
2110
+ resolveTrailerReady();
2111
+ } else {
2112
+ armIdleCheck();
2113
+ }
2114
+ }
2115
+ }
2116
+ }
2117
+
2118
+ function getTrailerError(error) {
2119
+ const trailerError = new Error(outputLength == outputSize ? ERR_INVALID_CRC32 : ERR_INVALID_UNCOMPRESSED_SIZE);
2120
+ trailerError.cause = error;
2121
+ return trailerError;
2023
2122
  }
2024
2123
  }
2025
2124
 
2125
+ function nextTask() {
2126
+ return new Promise(resolve => {
2127
+ const { port1, port2 } = new MessageChannel();
2128
+ port2.onmessage = () => {
2129
+ port1.close();
2130
+ port2.close();
2131
+ resolve();
2132
+ };
2133
+ port1.postMessage(UNDEFINED_VALUE);
2134
+ });
2135
+ }
2136
+
2026
2137
  class InflateStream extends TransformStream {
2027
2138
 
2028
2139
  constructor(options, { chunkSize, DecompressionStreamFallback, DecompressionStream }) {
@@ -2043,20 +2154,32 @@
2043
2154
  if (codecStreams) {
2044
2155
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }));
2045
2156
  } else {
2046
- try {
2047
- readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
2048
- } catch (error) {
2049
- if (deflate64 || outputSize === UNDEFINED_VALUE) {
2050
- throw error;
2051
- }
2052
- let gzipStream;
2157
+ let gzipStream;
2158
+ const GzipDecompressionStream = getGzipCodecStream(useCompressionStream, DecompressionStream, DecompressionStreamFallback);
2159
+ if (checkCrc32 && !deflate64 && crc32 !== UNDEFINED_VALUE && outputSize !== UNDEFINED_VALUE && GzipDecompressionStream) {
2053
2160
  try {
2054
- gzipStream = new DecompressionStream(FORMAT_GZIP);
2161
+ gzipStream = new GzipDecompressionStream(FORMAT_GZIP, { chunkSize });
2055
2162
  } catch {
2056
- throw error;
2163
+ gzipStream = UNDEFINED_VALUE;
2057
2164
  }
2058
- gzipCrc32 = new Crc32();
2059
- readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, gzipCrc32);
2165
+ }
2166
+ if (!gzipStream) {
2167
+ try {
2168
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
2169
+ } catch (error) {
2170
+ if (deflate64 || outputSize === UNDEFINED_VALUE || (!useCompressionStream && DecompressionStreamFallback)) {
2171
+ throw error;
2172
+ }
2173
+ try {
2174
+ gzipStream = new DecompressionStream(FORMAT_GZIP);
2175
+ } catch {
2176
+ throw error;
2177
+ }
2178
+ }
2179
+ }
2180
+ if (gzipStream) {
2181
+ gzipCrc32 = true;
2182
+ readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32);
2060
2183
  }
2061
2184
  }
2062
2185
  readable = mapInflateStreamError(readable);
@@ -2066,8 +2189,8 @@
2066
2189
  readable = pipeThrough(readable, crc32Stream);
2067
2190
  }
2068
2191
  setReadable(this, readable, () => {
2069
- if (checkCrc32) {
2070
- const computedCrc32 = gzipCrc32 ? gzipCrc32.get() >>> 0 : new DataView(crc32Stream.value.buffer).getUint32(0, false);
2192
+ if (crc32Stream) {
2193
+ const computedCrc32 = new DataView(crc32Stream.value.buffer).getUint32(0, false);
2071
2194
  if (crc32 != computedCrc32) {
2072
2195
  throw new Error(ERR_INVALID_CRC32);
2073
2196
  }
@@ -2124,11 +2247,11 @@
2124
2247
  return new CodecStreamClass(format, options);
2125
2248
  }
2126
2249
 
2127
- function getGzipCompressionStream(useCompressionStream, CompressionStreamNative, CompressionStreamFallback) {
2128
- if (useCompressionStream && CompressionStreamNative) {
2129
- return CompressionStreamNative;
2130
- } else if (CompressionStreamFallback && CompressionStreamFallback.requiresModule) {
2131
- return CompressionStreamFallback;
2250
+ function getGzipCodecStream(useCompressionStream, CodecStreamNative, CodecStreamFallback) {
2251
+ if (useCompressionStream && CodecStreamNative) {
2252
+ return CodecStreamNative;
2253
+ } else if (CodecStreamFallback && CodecStreamFallback.requiresModule) {
2254
+ return CodecStreamFallback;
2132
2255
  }
2133
2256
  }
2134
2257
 
@@ -2198,10 +2321,15 @@
2198
2321
  const reader = readable.getReader();
2199
2322
  return new ReadableStream({
2200
2323
  async pull(controller) {
2201
- let result;
2202
2324
  try {
2203
- result = await reader.read();
2325
+ const { value, done } = await reader.read();
2326
+ if (done) {
2327
+ controller.close();
2328
+ } else {
2329
+ controller.enqueue(value);
2330
+ }
2204
2331
  } catch (error) {
2332
+ await cancel(reader, error);
2205
2333
  if (error && error.message) {
2206
2334
  throw error;
2207
2335
  }
@@ -2209,12 +2337,6 @@
2209
2337
  mappedError.cause = error;
2210
2338
  throw mappedError;
2211
2339
  }
2212
- const { value, done } = result;
2213
- if (done) {
2214
- controller.close();
2215
- } else {
2216
- controller.enqueue(value);
2217
- }
2218
2340
  },
2219
2341
  cancel(reason) {
2220
2342
  return reader.cancel(reason);
@@ -3726,10 +3848,11 @@
3726
3848
 
3727
3849
  createReadable(options) {
3728
3850
  const reader = this;
3729
- const { useRangeHeader, forceRangeRequests, size } = reader;
3851
+ const { useRangeHeader, forceRangeRequests, size, eocdCache } = reader;
3730
3852
  if ((useRangeHeader || forceRangeRequests) && size !== UNDEFINED_VALUE) {
3731
3853
  const { offset = 0, size: readSize = size - offset } = options || {};
3732
- if (readSize > 0 && offset < size) {
3854
+ const cached = eocdCache && offset >= size - eocdCache.length;
3855
+ if (readSize > 0 && offset < size && !cached) {
3733
3856
  return createRangeReadable(reader, offset, Math.min(readSize, size - offset));
3734
3857
  }
3735
3858
  }
@@ -5288,8 +5411,8 @@
5288
5411
  const passThroughCompression = Boolean(passThrough);
5289
5412
  const passThroughEncryption = passThrough === true;
5290
5413
  checkPasswordOption(password, rawPassword);
5291
- password = password && password.length && password;
5292
- rawPassword = rawPassword && rawPassword.length && rawPassword;
5414
+ password = password && password.length ? password : UNDEFINED_VALUE;
5415
+ rawPassword = rawPassword && rawPassword.length ? rawPassword : UNDEFINED_VALUE;
5293
5416
  if (extraFieldAES) {
5294
5417
  if (extraFieldAES.originalCompressionMethod != COMPRESSION_METHOD_AES) {
5295
5418
  throw new Error(ERR_UNSUPPORTED_COMPRESSION);
@@ -6050,7 +6173,7 @@
6050
6173
  async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
6051
6174
  const { size } = reader;
6052
6175
  const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
6053
- const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
6176
+ const remoteProbeBudget = { remaining: MAX_END_OF_CENTRAL_DIR_PROBES };
6054
6177
  let endOfDirectoryInfo;
6055
6178
  let plausibleEndOfDirectoryInfo;
6056
6179
  let endOfDirectoryReachingEndCount = 0;
@@ -6143,8 +6266,8 @@
6143
6266
  if (signatureOffset >= anchoredOffset) {
6144
6267
  return getUint32$1(view, signatureOffset - anchoredOffset);
6145
6268
  }
6146
- if (remoteProbeBudget.count > 0) {
6147
- remoteProbeBudget.count--;
6269
+ if (remoteProbeBudget.remaining > 0) {
6270
+ remoteProbeBudget.remaining--;
6148
6271
  const signatureArray = await readUint8Array(reader, signatureOffset, 4);
6149
6272
  return getUint32$1(getDataView(signatureArray), 0);
6150
6273
  }
@@ -7060,9 +7183,11 @@
7060
7183
  const passThrough = checkPassThroughOption(getOptionValue(zipWriter, options, OPTION_PASS_THROUGH));
7061
7184
  const passThroughCompression = Boolean(passThrough);
7062
7185
  const passThroughEncryption = passThrough === true;
7063
- const password = getOptionValue(zipWriter, options, OPTION_PASSWORD);
7064
- const rawPassword = getOptionValue(zipWriter, options, OPTION_RAW_PASSWORD);
7186
+ let password = getOptionValue(zipWriter, options, OPTION_PASSWORD);
7187
+ let rawPassword = getOptionValue(zipWriter, options, OPTION_RAW_PASSWORD);
7065
7188
  checkPasswordOption(password, rawPassword);
7189
+ password = password && password.length ? password : UNDEFINED_VALUE;
7190
+ rawPassword = rawPassword && rawPassword.length ? rawPassword : UNDEFINED_VALUE;
7066
7191
  const encryptionStrength = getNumberOptionValue(zipWriter, options, OPTION_ENCRYPTION_STRENGTH, 3);
7067
7192
  const zipCrypto = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIPCRYPTO);
7068
7193
  const extendedTimestamp = getOptionValue(zipWriter, options, OPTION_EXTENDED_TIMESTAMP, true);
@@ -8858,7 +8983,7 @@
8858
8983
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8859
8984
  */
8860
8985
 
8861
- const VERSION = "2.15.0";
8986
+ const VERSION = "2.16.1";
8862
8987
 
8863
8988
  /*
8864
8989
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
@@ -9351,7 +9476,11 @@
9351
9476
  const ERR_INVALID_PASS_THROUGH = "Invalid passThrough option (use readerOptions.passThrough or set uncompressedSize for each entry)";
9352
9477
  const ERR_INVALID_READER_OPTIONS = "Invalid readerOptions (must be an object)";
9353
9478
  const ERR_UNSUPPORTED_PASS_THROUGH_VALUE = "The 'compressed' passThrough option is only supported by Entry#getData() and ZipWriter#add()";
9479
+ const ERR_INVALID_PASSWORDS = "Invalid passwords option (must be an array of strings)";
9480
+ const ERR_INVALID_REQUEST_PASSWORD = "Invalid requestPassword option (must be a function returning a string or undefined)";
9354
9481
  const ERR_ABORT_EXPORT = "zipjs-abort-export";
9482
+ const ABORT_ERROR_NAME = "AbortError";
9483
+ const EMPTY_RAW_PASSWORD = new Uint8Array(0);
9355
9484
 
9356
9485
  class ZipEntry {
9357
9486
 
@@ -9724,8 +9853,10 @@
9724
9853
  zipReader = new ZipReader(reader, options);
9725
9854
  }
9726
9855
  checkPassThroughValue(options.passThrough);
9856
+ checkPasswordCandidatesOptions(options);
9727
9857
  const duplicates = checkDuplicatesOption(options.duplicates);
9728
9858
  const importedEntries = [];
9859
+ const passwordState = { known: [] };
9729
9860
  const entries = await zipReader.getEntries(options);
9730
9861
  for (const entry of entries) {
9731
9862
  let parent = this;
@@ -9770,7 +9901,7 @@
9770
9901
  }
9771
9902
  importedEntries.push(addChild(parent, name, {
9772
9903
  data: entry,
9773
- Reader: getZipBlobReader(Object.assign({}, options)),
9904
+ Reader: getZipBlobReader(Object.assign({}, options), passwordState),
9774
9905
  uncompressedSize: options.passThrough ? entry.compressedSize : entry.uncompressedSize,
9775
9906
  passThrough: options.passThrough
9776
9907
  }));
@@ -10099,7 +10230,7 @@
10099
10230
  };
10100
10231
  }
10101
10232
 
10102
- function getZipBlobReader(options) {
10233
+ function getZipBlobReader(options, passwordState) {
10103
10234
  return class extends Reader {
10104
10235
 
10105
10236
  constructor(entry, options = {}) {
@@ -10112,12 +10243,12 @@
10112
10243
  const zipBlobReader = this;
10113
10244
  const readerOptions = Object.assign({}, options, zipBlobReader.options);
10114
10245
  const { checkOverlappingEntry, checkOverlappingEntryOnly } = readerOptions;
10115
- const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign(readerOptions, {
10246
+ const data = await readEntryData(zipBlobReader.entry, Object.assign(readerOptions, {
10116
10247
  checkPasswordOnly: false,
10117
10248
  checkOverlappingEntry: checkOverlappingEntryOnly || checkOverlappingEntry,
10118
10249
  checkOverlappingEntryOnly: false,
10119
10250
  preventClose: false
10120
- }));
10251
+ }), passwordState);
10121
10252
  zipBlobReader.data = data;
10122
10253
  zipBlobReader.blobReader = new BlobReader(data);
10123
10254
  zipBlobReader.size = data.size;
@@ -10179,10 +10310,9 @@
10179
10310
  await initStream(reader);
10180
10311
  } catch (error) {
10181
10312
  try {
10313
+ error.entry = child;
10182
10314
  error.entryId = child.id;
10183
- error.cause = {
10184
- entry: child
10185
- };
10315
+ error.entryName = child.getRelativeName(entry);
10186
10316
  } catch {
10187
10317
  // ignored
10188
10318
  }
@@ -10274,10 +10404,136 @@
10274
10404
  }
10275
10405
  if (readerOptions) {
10276
10406
  checkPassThroughValue(readerOptions.passThrough);
10407
+ checkPasswordCandidatesOptions(readerOptions);
10277
10408
  }
10278
10409
  return readerOptions;
10279
10410
  }
10280
10411
 
10412
+ function checkPasswordCandidatesOptions(options) {
10413
+ const { passwords, requestPassword } = options;
10414
+ if (passwords && (!Array.isArray(passwords) || passwords.some(password => typeof password != STRING_TYPE))) {
10415
+ throw new Error(ERR_INVALID_PASSWORDS);
10416
+ }
10417
+ if (requestPassword && typeof requestPassword != FUNCTION_TYPE) {
10418
+ throw new Error(ERR_INVALID_REQUEST_PASSWORD);
10419
+ }
10420
+ }
10421
+
10422
+ async function readEntryData(entry, options, passwordState) {
10423
+ checkPasswordCandidatesOptions(options);
10424
+ const { passwords, requestPassword, signal } = options;
10425
+ options = Object.assign({}, options);
10426
+ delete options.passwords;
10427
+ delete options.requestPassword;
10428
+ if (!entry.encrypted || options.passThrough || (!passwords && !requestPassword)) {
10429
+ return entry.getData(new BlobWriter(), options);
10430
+ }
10431
+ const tried = new Set();
10432
+ let error;
10433
+ while (true) {
10434
+ for (const candidate of getPasswordCandidates(options, passwordState, passwords)) {
10435
+ if (!tried.has(candidate.key)) {
10436
+ const result = await tryPasswordCandidate(candidate);
10437
+ if (result.done) {
10438
+ return result.data;
10439
+ }
10440
+ error = result.error;
10441
+ }
10442
+ }
10443
+ if (!requestPassword) {
10444
+ break;
10445
+ }
10446
+ if (passwordState.pendingPrompt) {
10447
+ await passwordState.pendingPrompt;
10448
+ continue;
10449
+ }
10450
+ let release;
10451
+ passwordState.pendingPrompt = new Promise(resolve => release = resolve);
10452
+ try {
10453
+ const answer = await requestPassword(entry, error);
10454
+ if (answer === UNDEFINED_VALUE || answer === null) {
10455
+ break;
10456
+ }
10457
+ // deno-lint-ignore valid-typeof
10458
+ if (typeof answer != STRING_TYPE) {
10459
+ throw new Error(ERR_INVALID_REQUEST_PASSWORD);
10460
+ }
10461
+ const result = await tryPasswordCandidate(getPasswordCandidate(answer));
10462
+ if (result.done) {
10463
+ return result.data;
10464
+ }
10465
+ error = result.error;
10466
+ } finally {
10467
+ passwordState.pendingPrompt = UNDEFINED_VALUE;
10468
+ release();
10469
+ }
10470
+ }
10471
+ throw new Error(tried.size ? ERR_INVALID_PASSWORD : ERR_ENCRYPTED);
10472
+
10473
+ async function tryPasswordCandidate(candidate) {
10474
+ tried.add(candidate.key);
10475
+ const candidateOptions = Object.assign({}, options, candidate.options);
10476
+ if (entry.zipCrypto) {
10477
+ candidateOptions.checkCrc32 = true;
10478
+ try {
10479
+ await entry.getData(null, Object.assign({}, candidateOptions, { checkPasswordOnly: true }));
10480
+ } catch (probeError) {
10481
+ if (isInvalidPasswordError(probeError)) {
10482
+ return { done: false, error: probeError };
10483
+ }
10484
+ throw probeError;
10485
+ }
10486
+ }
10487
+ try {
10488
+ const data = await entry.getData(new BlobWriter(), candidateOptions);
10489
+ rememberPassword(passwordState, candidate);
10490
+ return { done: true, data };
10491
+ } catch (readError) {
10492
+ if (isAbortError(readError, signal) || (!entry.zipCrypto && !isInvalidPasswordError(readError))) {
10493
+ throw readError;
10494
+ }
10495
+ return { done: false, error: readError };
10496
+ }
10497
+ }
10498
+ }
10499
+
10500
+ function isInvalidPasswordError(error) {
10501
+ return isErrorObject(error) && error.message == ERR_INVALID_PASSWORD;
10502
+ }
10503
+
10504
+ function isAbortError(error, signal) {
10505
+ return Boolean(signal && signal.aborted) || (isErrorObject(error) && error.name == ABORT_ERROR_NAME);
10506
+ }
10507
+
10508
+ function getPasswordCandidates(options, passwordState, passwords) {
10509
+ const candidates = [];
10510
+ const { password, rawPassword } = options;
10511
+ if (rawPassword && rawPassword.length) {
10512
+ candidates.push({ key: rawPassword, options: { password: "", rawPassword } });
10513
+ }
10514
+ if (password) {
10515
+ candidates.push(getPasswordCandidate(password));
10516
+ }
10517
+ candidates.push(...passwordState.known);
10518
+ if (passwords) {
10519
+ candidates.push(...passwords.filter(password => password).map(getPasswordCandidate));
10520
+ }
10521
+ return candidates;
10522
+ }
10523
+
10524
+ function getPasswordCandidate(password) {
10525
+ return { key: password, options: { password, rawPassword: EMPTY_RAW_PASSWORD } };
10526
+ }
10527
+
10528
+ function rememberPassword(passwordState, candidate) {
10529
+ const { known } = passwordState;
10530
+ const index = known.findIndex(knownCandidate => knownCandidate.key === candidate.key);
10531
+ if (index != -1) {
10532
+ known.splice(index, 1);
10533
+ }
10534
+ known.unshift(candidate);
10535
+ }
10536
+
10281
10537
  function checkPassThroughValue(passThrough) {
10282
10538
  if (checkPassThroughOption(passThrough) === PASS_THROUGH_COMPRESSED) {
10283
10539
  throw new Error(ERR_UNSUPPORTED_PASS_THROUGH_VALUE);
@@ -10415,6 +10671,7 @@
10415
10671
  async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
10416
10672
  const { onstart, onprogress, onend } = options;
10417
10673
  checkPassThroughValue(options.passThrough);
10674
+ checkPasswordCandidatesOptions(options);
10418
10675
  const readerOptions = checkReaderOptions(options.readerOptions);
10419
10676
  const abortController = new AbortController();
10420
10677
  const { signal } = abortController;
@@ -10713,11 +10970,13 @@
10713
10970
  exports.ERR_INVALID_MSDOS_ATTRIBUTES = ERR_INVALID_MSDOS_ATTRIBUTES;
10714
10971
  exports.ERR_INVALID_MSDOS_DATA = ERR_INVALID_MSDOS_DATA;
10715
10972
  exports.ERR_INVALID_PASSWORD = ERR_INVALID_PASSWORD;
10973
+ exports.ERR_INVALID_PASSWORDS = ERR_INVALID_PASSWORDS;
10716
10974
  exports.ERR_INVALID_PASSWORD_TYPE = ERR_INVALID_PASSWORD_TYPE;
10717
10975
  exports.ERR_INVALID_PASS_THROUGH = ERR_INVALID_PASS_THROUGH;
10718
10976
  exports.ERR_INVALID_PASS_THROUGH_VALUE = ERR_INVALID_PASS_THROUGH_VALUE;
10719
10977
  exports.ERR_INVALID_READER = ERR_INVALID_READER;
10720
10978
  exports.ERR_INVALID_READER_OPTIONS = ERR_INVALID_READER_OPTIONS;
10979
+ exports.ERR_INVALID_REQUEST_PASSWORD = ERR_INVALID_REQUEST_PASSWORD;
10721
10980
  exports.ERR_INVALID_SIGNAL = ERR_INVALID_SIGNAL;
10722
10981
  exports.ERR_INVALID_SIGNATURE_DATA = ERR_INVALID_SIGNATURE_DATA;
10723
10982
  exports.ERR_INVALID_STRICTNESS = ERR_INVALID_STRICTNESS;