@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.
Files changed (56) hide show
  1. package/BENCHMARKS.md +35 -0
  2. package/deno.json +5 -4
  3. package/dist/zip-core.js +496 -218
  4. package/dist/zip-core.min.js +1 -1
  5. package/dist/zip-fs-core.js +398 -195
  6. package/dist/zip-fs-core.min.js +1 -1
  7. package/dist/zip-fs-native.js +588 -254
  8. package/dist/zip-fs-native.min.js +1 -1
  9. package/dist/zip-fs.js +587 -253
  10. package/dist/zip-fs.min.js +1 -1
  11. package/dist/zip-legacy.js +501 -223
  12. package/dist/zip-legacy.min.js +1 -1
  13. package/dist/zip-module.wasm +0 -0
  14. package/dist/zip-native.js +504 -226
  15. package/dist/zip-native.min.js +1 -1
  16. package/dist/zip-web-worker-native.js +1 -1
  17. package/dist/zip-web-worker.js +1 -1
  18. package/dist/zip.js +503 -225
  19. package/dist/zip.min.js +1 -1
  20. package/index-native.cjs +588 -254
  21. package/index-native.min.js +1 -1
  22. package/index.cjs +587 -253
  23. package/index.d.cts +2814 -0
  24. package/index.d.ts +364 -1
  25. package/index.min.js +1 -1
  26. package/lib/core/codec-worker.js +0 -10
  27. package/lib/core/configuration.js +21 -33
  28. package/lib/core/constants.js +3 -0
  29. package/lib/core/io.js +11 -19
  30. package/lib/core/options.js +11 -1
  31. package/lib/core/streams/aes-crypto-stream.js +16 -20
  32. package/lib/core/streams/codecs/crc32.js +39 -11
  33. package/lib/core/streams/common-crypto.js +0 -2
  34. package/lib/core/streams/zip-crypto-stream.js +12 -16
  35. package/lib/core/streams/zip-entry-stream.js +69 -19
  36. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  37. package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +1 -3
  38. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  39. package/lib/core/util/base64.js +68 -0
  40. package/lib/core/util/decode-text.js +0 -1
  41. package/lib/core/util/default-mime-type.js +0 -3
  42. package/lib/core/util/inflate.js +194 -0
  43. package/lib/core/util/mime-type.js +26 -22
  44. package/lib/core/util/opfs-temp-stream.js +139 -0
  45. package/lib/core/web-worker-inline-native.js +1 -1
  46. package/lib/core/web-worker-inline-template-native.js +5 -5
  47. package/lib/core/web-worker-inline-wasm.js +1 -1
  48. package/lib/core/zip-fs.js +61 -10
  49. package/lib/core/zip-reader.js +162 -53
  50. package/lib/core/zip-writer.js +34 -41
  51. package/lib/core/zlib-streams-inline-template.js +3 -2
  52. package/lib/core/zlib-streams-inline.js +1 -1
  53. package/lib/zip-core-base.js +7 -2
  54. package/package.json +9 -6
  55. package/reserved-property-names.js +50 -0
  56. 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
- baseURI,
180
- chunkSize,
181
- maxWorkers,
182
- terminateWorkerTimeout,
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
- const table = [];
243
- for (let i = 0; i < 256; i++) {
244
- let t = i;
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
- if (t & 1) {
247
- t = (t >>> 1) ^ 0xEDB88320;
248
- } else {
249
- t = t >>> 1;
250
- }
245
+ t = (t & 1) ? (t >>> 1) ^ 0xEDB88320 : t >>> 1;
251
246
  }
252
- table[i] = t;
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
- for (let offset = 0, length = data.length | 0; offset < length; offset++) {
264
- crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
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
- Object.assign(this, {
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 = new Uint8Array();
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
- Object.assign(this, {
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 = new Uint8Array();
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 = new Uint8Array();
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
- Object.assign(this, {
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
- Object.assign(this, {
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
- if ((!encrypted || zipCrypto) && signed) {
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
- readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib, CompressionStream);
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
- const { chunkSize = DEFAULT_CHUNK_SIZE } = reader;
2691
- const readable = new ReadableStream({
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
- this.chunkOffset += chunkSize;
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 new Uint8Array();
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 = new Uint8Array();
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
- reader.chunkSize = getChunkSize(config);
3785
- const endOfDirectoryInfo = await seekSignature(reader, END_OF_CENTRAL_DIR_SIGNATURE, reader.size, END_OF_CENTRAL_DIR_LENGTH, MAX_16_BITS * 16);
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
- const checkAmbiguity = getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY);
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
- new Uint8Array();
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 (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
3882
- const originalDirectoryDataOffset = directoryDataOffset;
3883
- directoryDataOffset = expectedDirectoryDataOffset;
3884
- if (directoryDataOffset > originalDirectoryDataOffset) {
3885
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
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) : new Uint8Array();
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) : new Uint8Array();
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) : new Uint8Array();
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 = new Uint8Array();
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
- new Uint8Array();
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.readable;
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
- async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
4596
- const signatureArray = new Uint8Array(4);
4597
- const signatureView = getDataView$1(signatureArray);
4598
- setUint32$1(signatureView, 0, signature);
4599
- const maximumBytes = minimumBytes + maximumLength;
4600
- return (await seek(minimumBytes)) || await seek(Math.min(maximumBytes, startOffset));
4601
-
4602
- async function seek(length) {
4603
- const offset = startOffset - length;
4604
- const bytes = await readUint8Array(reader, offset, length);
4605
- for (let indexByte = bytes.length - minimumBytes; indexByte >= 0; indexByte--) {
4606
- if (bytes[indexByte] == signatureArray[0] && bytes[indexByte + 1] == signatureArray[1] &&
4607
- bytes[indexByte + 2] == signatureArray[2] && bytes[indexByte + 3] == signatureArray[3]) {
4608
- return {
4609
- offset: offset + indexByte,
4610
- buffer: bytes.slice(indexByte, indexByte + minimumBytes).buffer
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 || new Uint8Array();
4808
- rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
4809
- rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
4810
- rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
4811
- rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
4812
- rawExtraField = rawExtraField || new Uint8Array();
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 = new Uint8Array(), options = {}) {
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 = new Uint8Array();
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, new Uint8Array());
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 = getChunkSize(config);
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 = new Uint8Array();
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 = new Uint8Array();
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
- // the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
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 = new Uint8Array();
5915
+ rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
5773
5916
  }
5774
5917
  } else {
5775
- rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = new Uint8Array();
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 = new Uint8Array();
5944
+ rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
5802
5945
  }
5803
5946
  } catch {
5804
- rawExtraFieldUnix = new Uint8Array();
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: new Uint8Array(),
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 new Uint8Array();
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 = new Uint8Array();
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 = new Uint8Array();
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 = new Uint8Array();
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, new Uint8Array());
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, new Uint8Array());
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