@zip.js/zip.js 2.8.30 → 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 (53) hide show
  1. package/deno.json +5 -4
  2. package/dist/zip-core.js +133 -334
  3. package/dist/zip-core.min.js +1 -1
  4. package/dist/zip-fs-core.js +115 -277
  5. package/dist/zip-fs-core.min.js +1 -1
  6. package/dist/zip-fs-native.js +170 -373
  7. package/dist/zip-fs-native.min.js +1 -1
  8. package/dist/zip-fs.js +169 -372
  9. package/dist/zip-fs.min.js +1 -1
  10. package/dist/zip-legacy.js +138 -339
  11. package/dist/zip-legacy.min.js +1 -1
  12. package/dist/zip-module.wasm +0 -0
  13. package/dist/zip-native.js +141 -342
  14. package/dist/zip-native.min.js +1 -1
  15. package/dist/zip-web-worker-native.js +1 -1
  16. package/dist/zip-web-worker.js +1 -1
  17. package/dist/zip.js +140 -341
  18. package/dist/zip.min.js +1 -1
  19. package/index-native.cjs +170 -373
  20. package/index-native.min.js +1 -1
  21. package/index.cjs +169 -372
  22. package/index.d.cts +2814 -0
  23. package/index.d.ts +238 -0
  24. package/index.min.js +1 -1
  25. package/lib/core/codec-worker.js +0 -10
  26. package/lib/core/configuration.js +21 -33
  27. package/lib/core/constants.js +3 -0
  28. package/lib/core/io.js +11 -19
  29. package/lib/core/streams/aes-crypto-stream.js +16 -20
  30. package/lib/core/streams/common-crypto.js +0 -2
  31. package/lib/core/streams/zip-crypto-stream.js +12 -16
  32. package/lib/core/streams/zip-entry-stream.js +2 -32
  33. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  34. package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +1 -3
  35. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  36. package/lib/core/util/base64.js +68 -0
  37. package/lib/core/util/decode-text.js +0 -1
  38. package/lib/core/util/default-mime-type.js +0 -3
  39. package/lib/core/util/inflate.js +194 -0
  40. package/lib/core/util/mime-type.js +26 -22
  41. package/lib/core/util/opfs-temp-stream.js +1 -37
  42. package/lib/core/web-worker-inline-native.js +1 -1
  43. package/lib/core/web-worker-inline-template-native.js +5 -5
  44. package/lib/core/web-worker-inline-wasm.js +1 -1
  45. package/lib/core/zip-fs.js +6 -13
  46. package/lib/core/zip-reader.js +48 -127
  47. package/lib/core/zip-writer.js +27 -45
  48. package/lib/core/zlib-streams-inline-template.js +3 -2
  49. package/lib/core/zlib-streams-inline.js +1 -1
  50. package/lib/zip-core-base.js +3 -1
  51. package/package.json +9 -6
  52. package/reserved-property-names.js +50 -0
  53. 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
 
@@ -1222,8 +1212,6 @@
1222
1212
  if (GET_RANDOM_VALUES_SUPPORTED) {
1223
1213
  return crypto.getRandomValues(array);
1224
1214
  } else {
1225
- // all supported runtimes expose crypto.getRandomValues; failing hard beats
1226
- // silently generating predictable encryption salts
1227
1215
  throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
1228
1216
  }
1229
1217
  }
@@ -1286,12 +1274,7 @@
1286
1274
  constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
1287
1275
  super({
1288
1276
  start() {
1289
- Object.assign(this, {
1290
- ready: new Promise(resolve => this.resolveReady = resolve),
1291
- password: encodePassword(password, rawPassword),
1292
- strength: encryptionStrength - 1,
1293
- pending: new Uint8Array()
1294
- });
1277
+ initAesCrypto(this, password, rawPassword, encryptionStrength);
1295
1278
  },
1296
1279
  async transform(chunk, controller) {
1297
1280
  const aesCrypto = this;
@@ -1326,7 +1309,7 @@
1326
1309
  await ready;
1327
1310
  const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH);
1328
1311
  const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH);
1329
- let decryptedChunkArray = new Uint8Array();
1312
+ let decryptedChunkArray = EMPTY_UINT8_ARRAY;
1330
1313
  if (chunkToDecrypt.length) {
1331
1314
  const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
1332
1315
  hmac.update(encryptedChunk);
@@ -1334,8 +1317,6 @@
1334
1317
  decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
1335
1318
  }
1336
1319
  const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
1337
- // branch-free comparison: accumulate the differences instead of
1338
- // short-circuiting on the first mismatching byte
1339
1320
  let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
1340
1321
  for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
1341
1322
  invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
@@ -1357,12 +1338,7 @@
1357
1338
  let stream;
1358
1339
  super({
1359
1340
  start() {
1360
- Object.assign(this, {
1361
- ready: new Promise(resolve => this.resolveReady = resolve),
1362
- password: encodePassword(password, rawPassword),
1363
- strength: encryptionStrength - 1,
1364
- pending: new Uint8Array()
1365
- });
1341
+ initAesCrypto(this, password, rawPassword, encryptionStrength);
1366
1342
  },
1367
1343
  async transform(chunk, controller) {
1368
1344
  const aesCrypto = this;
@@ -1372,7 +1348,7 @@
1372
1348
  resolveReady,
1373
1349
  ready
1374
1350
  } = aesCrypto;
1375
- let preamble = new Uint8Array();
1351
+ let preamble = EMPTY_UINT8_ARRAY;
1376
1352
  if (password) {
1377
1353
  preamble = await createEncryptionKeys(aesCrypto, strength, password);
1378
1354
  resolveReady();
@@ -1392,7 +1368,7 @@
1392
1368
  } = this;
1393
1369
  if (hmac && ctr) {
1394
1370
  await ready;
1395
- let encryptedChunkArray = new Uint8Array();
1371
+ let encryptedChunkArray = EMPTY_UINT8_ARRAY;
1396
1372
  if (pending.length) {
1397
1373
  const encryptedChunk = ctr.update(toBits(codecBytes, pending));
1398
1374
  hmac.update(encryptedChunk);
@@ -1407,6 +1383,15 @@
1407
1383
  }
1408
1384
  }
1409
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
+
1410
1395
  function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) {
1411
1396
  const {
1412
1397
  ctr,
@@ -1568,20 +1553,13 @@
1568
1553
  constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
1569
1554
  super({
1570
1555
  start() {
1571
- Object.assign(this, {
1572
- password,
1573
- rawPassword,
1574
- passwordVerification
1575
- });
1576
- createKeys(this, password, rawPassword);
1556
+ initZipCrypto(this, password, rawPassword, passwordVerification);
1577
1557
  },
1578
1558
  transform(chunk, controller) {
1579
1559
  const zipCrypto = this;
1580
1560
  if (zipCrypto.password || zipCrypto.rawPassword) {
1581
1561
  const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
1582
1562
  zipCrypto.password = zipCrypto.rawPassword = null;
1583
- // verify the password with a branch-free comparison of the verification byte, mirroring
1584
- // the constant-time AES HMAC signature check
1585
1563
  if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
1586
1564
  throw new Error(ERR_INVALID_PASSWORD);
1587
1565
  }
@@ -1602,12 +1580,7 @@
1602
1580
  constructor({ password, rawPassword, passwordVerification }) {
1603
1581
  super({
1604
1582
  start() {
1605
- Object.assign(this, {
1606
- password,
1607
- rawPassword,
1608
- passwordVerification
1609
- });
1610
- createKeys(this, password, rawPassword);
1583
+ initZipCrypto(this, password, rawPassword, passwordVerification);
1611
1584
  },
1612
1585
  transform(chunk, controller) {
1613
1586
  const zipCrypto = this;
@@ -1631,6 +1604,15 @@
1631
1604
  }
1632
1605
  }
1633
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
+
1634
1616
  function decrypt(target, input) {
1635
1617
  const output = new Uint8Array(input.length);
1636
1618
  for (let index = 0; index < input.length; index++) {
@@ -1735,14 +1717,6 @@
1735
1717
  const stream = this;
1736
1718
  let crc32Stream, encryptionStream, gzipCrc32Stream;
1737
1719
  let readable = super.readable;
1738
- // The gzip trailer carries a CRC-32 of the uncompressed data (same polynomial as zip),
1739
- // computed in native code for free during compression. On the native CompressionStream,
1740
- // harvest it instead of running a separate CRC pass: compress as gzip, then strip the
1741
- // fixed 10-byte header and 8-byte trailer to recover the exact raw-deflate payload
1742
- // (byte-identical to a deflate-raw compression) while capturing the CRC. The CRC becomes
1743
- // available at end-of-stream, exactly like Crc32Stream, so nothing downstream (data
1744
- // descriptor, central directory, ZipCrypto) needs it any earlier. Not applied for the
1745
- // pure-JS/WASM ports (a separate slice-by-8 CRC pass is as fast or faster there).
1746
1720
  const useGzipCrc32 = signed && compressed && !deflate64 && (!encrypted || zipCrypto) &&
1747
1721
  Boolean(useCompressionStream && CompressionStream);
1748
1722
  if ((!encrypted || zipCrypto) && signed && !useGzipCrc32) {
@@ -1779,12 +1753,6 @@
1779
1753
  }
1780
1754
  }
1781
1755
 
1782
- // Converts a gzip stream into its raw-deflate payload while capturing the CRC-32 from the gzip
1783
- // trailer. The CompressionStream gzip header is always the fixed 10-byte form (FLG=0, no optional
1784
- // fields) per the Compression Streams spec, so it is stripped by length; the trailer is the last
1785
- // 8 bytes (CRC-32 LE, then ISIZE LE). Bounded memory: at most GZIP_TRAILER_LENGTH bytes are held
1786
- // back across chunks. The signature is read big-endian-agnostically as a number, matching the
1787
- // value Crc32Stream produces, so the writer path is unchanged.
1788
1756
  class GzipToRawDeflateStream extends TransformStream {
1789
1757
 
1790
1758
  constructor() {
@@ -1810,10 +1778,6 @@
1810
1778
  tail = pending;
1811
1779
  return;
1812
1780
  }
1813
- // Emit everything except the trailing GZIP_TRAILER_LENGTH bytes as a standalone,
1814
- // right-sized Uint8Array. Consumers may read chunk.buffer directly (e.g. custom
1815
- // writers), so an aliased subarray of a larger buffer would leak the held-back
1816
- // trailer bytes. Bytes are copied exactly once, into `output` or `tail`.
1817
1781
  const emitLength = available - GZIP_TRAILER_LENGTH;
1818
1782
  const output = new Uint8Array(emitLength);
1819
1783
  const fromTail = Math.min(emitLength, tail.length);
@@ -1902,11 +1866,6 @@
1902
1866
  throw error;
1903
1867
  }
1904
1868
  }
1905
- // The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
1906
- // backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
1907
- // the whole source into them and peak memory grows with the entry size. Awaiting each write()
1908
- // paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
1909
- // codec already backpressures, so this is a no-op cost for it.
1910
1869
  return pipeThroughBackpressured(readable, codecStream);
1911
1870
  }
1912
1871
 
@@ -1914,10 +1873,6 @@
1914
1873
  return readable.pipeThrough(transformStream);
1915
1874
  }
1916
1875
 
1917
- // Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
1918
- // write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
1919
- // Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
1920
- // (surfaced through the codec's writable rejecting) cancels the source.
1921
1876
  function pipeThroughBackpressured(readable, transformStream) {
1922
1877
  const writer = transformStream.writable.getWriter();
1923
1878
  const reader = readable.getReader();
@@ -1946,7 +1901,7 @@
1946
1901
  try {
1947
1902
  await writer.abort(error);
1948
1903
  } catch {
1949
- // the writable may already be errored/closed
1904
+ // ignored: the writable may already be errored/closed
1950
1905
  }
1951
1906
  }
1952
1907
 
@@ -1954,13 +1909,10 @@
1954
1909
  try {
1955
1910
  await reader.cancel(error);
1956
1911
  } catch {
1957
- // the readable may already be errored/closed
1912
+ // ignored: the readable may already be errored/closed
1958
1913
  }
1959
1914
  }
1960
1915
 
1961
- // DecompressionStream implementations can fail with a message-less TypeError on malformed
1962
- // input; give these errors an identifiable message (errors raised by the other stages of
1963
- // the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
1964
1916
  function mapInflateStreamError(readable) {
1965
1917
  const reader = readable.getReader();
1966
1918
  return new ReadableStream({
@@ -2285,9 +2237,6 @@
2285
2237
  try {
2286
2238
  await initModule(config);
2287
2239
  } catch {
2288
- // The WASM module failed to load. Fall back to the native CompressionStream only if the
2289
- // zlib codec that would otherwise run depends on that module; a self-contained codec
2290
- // supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
2291
2240
  const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
2292
2241
  config.CompressionStreamZlib :
2293
2242
  config.DecompressionStreamZlib;
@@ -2365,7 +2314,6 @@
2365
2314
  return resultValue;
2366
2315
  } catch (error) {
2367
2316
  await closeWritable();
2368
- // unblock the pipe watcher if the worker died before closing its end of the stream
2369
2317
  abortPipe();
2370
2318
  try {
2371
2319
  await closed;
@@ -2390,8 +2338,6 @@
2390
2338
  const abortController = new AbortController();
2391
2339
  const { writable, readable } = new TransformStream();
2392
2340
  const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
2393
- // the pipe can fail before `closed` is awaited (e.g. the destination write fails);
2394
- // mark the rejection as handled so it cannot kill the process in the meantime
2395
2341
  closed.catch(() => { });
2396
2342
  return { writable, closed, abortPipe: () => abortController.abort() };
2397
2343
  }
@@ -2484,8 +2430,6 @@
2484
2430
  event.preventDefault();
2485
2431
  }
2486
2432
  const { rejectResult, writer, onTaskFinished } = workerData;
2487
- // the worker is unusable (e.g. its script failed to load); terminate it and
2488
- // fail the current task instead of letting it hang forever
2489
2433
  terminateWorker$1(workerData);
2490
2434
  if (rejectResult) {
2491
2435
  rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
@@ -2537,8 +2481,6 @@
2537
2481
  async function onMessage({ data }, workerData) {
2538
2482
  const { type, value, messageId, result, error } = data;
2539
2483
  const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
2540
- // continuations below can settle after the worker slot has been reassigned to a new
2541
- // task (e.g. a pending pull read failing late); they must not touch the new task
2542
2484
  const stale = () => workerData.generation != generation;
2543
2485
  try {
2544
2486
  if (error) {
@@ -2794,26 +2736,24 @@
2794
2736
  class Reader extends Stream {
2795
2737
 
2796
2738
  get readable() {
2739
+ return this.createReadable();
2740
+ }
2741
+
2742
+ createReadable({ offset = 0, size, diskNumberStart, chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
2797
2743
  const reader = this;
2798
- const { chunkSize = DEFAULT_CHUNK_SIZE } = reader;
2799
- const readable = new ReadableStream({
2800
- start() {
2801
- this.chunkOffset = 0;
2802
- },
2744
+ let chunkOffset = 0;
2745
+ return new ReadableStream({
2803
2746
  async pull(controller) {
2804
- const { offset = 0, size, diskNumberStart } = readable;
2805
- const { chunkOffset } = this;
2806
2747
  const dataSize = size === UNDEFINED_VALUE ? chunkSize : Math.min(chunkSize, size - chunkOffset);
2807
2748
  const data = await readUint8Array(reader, offset + chunkOffset, dataSize, diskNumberStart);
2808
2749
  controller.enqueue(data);
2809
2750
  if ((chunkOffset + chunkSize > size) || (size === UNDEFINED_VALUE && !data.length && dataSize)) {
2810
2751
  controller.close();
2811
2752
  } else {
2812
- this.chunkOffset += chunkSize;
2753
+ chunkOffset += chunkSize;
2813
2754
  }
2814
2755
  }
2815
2756
  });
2816
- return readable;
2817
2757
  }
2818
2758
  }
2819
2759
 
@@ -2952,8 +2892,6 @@
2952
2892
  }
2953
2893
  });
2954
2894
  writer.blob = new Response(transformStream.readable, { headers }).blob();
2955
- // mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
2956
- // surfaces them
2957
2895
  writer.blob.catch(() => { });
2958
2896
  }
2959
2897
 
@@ -3115,7 +3053,7 @@
3115
3053
  return eocdCache;
3116
3054
  }
3117
3055
  if (index >= size || length === 0) {
3118
- return new Uint8Array();
3056
+ return EMPTY_UINT8_ARRAY;
3119
3057
  } else {
3120
3058
  if (index + length > size) {
3121
3059
  length = size - index;
@@ -3172,8 +3110,6 @@
3172
3110
  async function getRequestData(httpReader, sendRequest) {
3173
3111
  const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
3174
3112
  httpReader.data = new Uint8Array(await response.arrayBuffer());
3175
- // the downloaded content is authoritative, e.g. the Content-Length response header
3176
- // of the HEAD request can be the encoded size when the content is compressed
3177
3113
  httpReader.size = httpReader.data.length;
3178
3114
  }
3179
3115
 
@@ -3238,7 +3174,6 @@
3238
3174
  super();
3239
3175
  Object.assign(this, {
3240
3176
  url,
3241
- // an explicit fetch implementation takes precedence over useXHR
3242
3177
  reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
3243
3178
  });
3244
3179
  }
@@ -3377,7 +3312,7 @@
3377
3312
  }
3378
3313
  }
3379
3314
  } else {
3380
- result = new Uint8Array();
3315
+ result = EMPTY_UINT8_ARRAY;
3381
3316
  }
3382
3317
  reader.lastDiskNumber = Math.max(currentDiskNumber, reader.lastDiskNumber);
3383
3318
  return result;
@@ -3422,8 +3357,6 @@
3422
3357
  writer.diskOffset += diskSourceWriter.size;
3423
3358
  writer.diskNumber++;
3424
3359
  diskWriter = null;
3425
- // the next disk is not opened yet but its space is available; leaving a
3426
- // stale value here would force a new disk for every subsequent write
3427
3360
  writer.availableSize = writer.maxSize;
3428
3361
  if (chunk.length > availableSize) {
3429
3362
  await this.write(chunk.subarray(availableSize));
@@ -3603,7 +3536,6 @@
3603
3536
  if (encoding && encoding.trim().toLowerCase() == "cp437") {
3604
3537
  return decodeCP437(value);
3605
3538
  } else {
3606
- // ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
3607
3539
  return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
3608
3540
  }
3609
3541
  }
@@ -3851,8 +3783,6 @@
3851
3783
  const CHARSET_UTF8 = "utf-8";
3852
3784
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
3853
3785
  const CHARSET_CP437 = "cp437";
3854
- // bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
3855
- // because they are informative only
3856
3786
  const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
3857
3787
  const ZIP64_PROPERTIES = [
3858
3788
  [PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
@@ -3894,7 +3824,6 @@
3894
3824
  if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
3895
3825
  throw new Error(ERR_BAD_FORMAT);
3896
3826
  }
3897
- reader.chunkSize = getChunkSize(config);
3898
3827
  const strictness = getStrictness(getOptionValue$1(zipReader, options, OPTION_STRICTNESS), getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY));
3899
3828
  const checkAmbiguity = strictness == STRICTNESS_STRICT;
3900
3829
  const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
@@ -3909,8 +3838,6 @@
3909
3838
  throw new Error(ERR_EOCDR_NOT_FOUND);
3910
3839
  }
3911
3840
  }
3912
- // two or more end-anchored records that each dereference to a valid central directory cannot be told
3913
- // apart (see comment_length ⟺ EOF): the archive is genuinely ambiguous, so refuse rather than guess
3914
3841
  if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
3915
3842
  throwAmbiguousArchive("multiple end of central directory records");
3916
3843
  }
@@ -3933,7 +3860,7 @@
3933
3860
  if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
3934
3861
  const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
3935
3862
  await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
3936
- new Uint8Array();
3863
+ EMPTY_UINT8_ARRAY;
3937
3864
  const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
3938
3865
  if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
3939
3866
  getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
@@ -4000,12 +3927,6 @@
4000
3927
  }
4001
3928
  const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
4002
3929
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
4003
- // the reconciled offset (the directory ends exactly where the canonical record begins) is anchored
4004
- // to the unforgeable end of the archive; the stored offset is not. Prefer the reconciled offset
4005
- // unless the stored one points at a directory and the reconciled one does not — that exception
4006
- // keeps a corrupt declared directory length from sending the read astray, while still moving off a
4007
- // stored offset that only looks valid because an append remnant left the previous (identically
4008
- // laid out) directory sitting there.
4009
3930
  const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE;
4010
3931
  let reconcile = !storedPointsAtDirectory;
4011
3932
  if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
@@ -4162,11 +4083,11 @@
4162
4083
  const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
4163
4084
  const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
4164
4085
  if (extractPrependedData) {
4165
- zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : new Uint8Array();
4086
+ zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : EMPTY_UINT8_ARRAY;
4166
4087
  }
4167
- 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;
4168
4089
  if (extractAppendedData) {
4169
- 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;
4170
4091
  }
4171
4092
  return true;
4172
4093
  }
@@ -4278,7 +4199,7 @@
4278
4199
  filenameLength
4279
4200
  } = localDirectory;
4280
4201
  const checkAmbiguity = getStrictness(getOptionValue$1(zipEntry, options, OPTION_STRICTNESS), getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY)) == STRICTNESS_STRICT;
4281
- let rawLocalFilename = new Uint8Array();
4202
+ let rawLocalFilename = EMPTY_UINT8_ARRAY;
4282
4203
  if (checkAmbiguity && (filenameLength || extraFieldLength)) {
4283
4204
  const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
4284
4205
  rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
@@ -4286,7 +4207,7 @@
4286
4207
  } else {
4287
4208
  localDirectory.rawExtraField = extraFieldLength ?
4288
4209
  await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
4289
- new Uint8Array();
4210
+ EMPTY_UINT8_ARRAY;
4290
4211
  }
4291
4212
  readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
4292
4213
  if (checkAmbiguity) {
@@ -4313,12 +4234,7 @@
4313
4234
  }
4314
4235
  const dataOffset = offset + HEADER_SIZE + filenameLength + extraFieldLength;
4315
4236
  const size = compressedSize;
4316
- const readable = reader.readable;
4317
- Object.assign(readable, {
4318
- diskNumberStart,
4319
- offset: dataOffset,
4320
- size
4321
- });
4237
+ const readable = reader.createReadable({ offset: dataOffset, size, diskNumberStart, chunkSize: getChunkSize(config) });
4322
4238
  const signal = getOptionValue$1(zipEntry, options, OPTION_SIGNAL);
4323
4239
  const checkPasswordOnly = getOptionValue$1(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
4324
4240
  let checkOverlappingEntry = getOptionValue$1(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
@@ -4589,7 +4505,6 @@
4589
4505
  const view = getDataView$1(new Uint8Array(extraField.data));
4590
4506
  let uid, gid;
4591
4507
  if (isInfoZip) {
4592
- // Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
4593
4508
  let offset = 0;
4594
4509
  const version = getUint8(view, offset++);
4595
4510
  const uidSize = getUint8(view, offset++);
@@ -4599,8 +4514,6 @@
4599
4514
  gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
4600
4515
  Object.assign(extraField, { version, uid, gid });
4601
4516
  } else if (extraField.data.length >= 4) {
4602
- // Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
4603
- // carried in the external file attributes, not here)
4604
4517
  uid = getUint16(view, 0);
4605
4518
  gid = getUint16(view, 2);
4606
4519
  Object.assign(extraField, { uid, gid });
@@ -4652,7 +4565,6 @@
4652
4565
  timeProperties.forEach((propertyName, indexProperty) => {
4653
4566
  if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
4654
4567
  const time = getUint32(extraFieldView, offset);
4655
- // the extended timestamp is a signed 32-bit time_t value
4656
4568
  directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
4657
4569
  const rawPropertyName = timeRawProperties[indexProperty];
4658
4570
  extraFieldExtendedTimestamp[rawPropertyName] = time;
@@ -4729,7 +4641,6 @@
4729
4641
 
4730
4642
  function getStrictness(strictness, checkAmbiguity) {
4731
4643
  if (strictness === UNDEFINED_VALUE) {
4732
- // `checkAmbiguity: true` is kept as a backward-compatible alias for the strictest mode
4733
4644
  return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
4734
4645
  }
4735
4646
  return strictness;
@@ -4740,81 +4651,45 @@
4740
4651
  return maxAppendedDataSize;
4741
4652
  }
4742
4653
  if (strictness == STRICTNESS_STRICT) {
4743
- // the comment must reach the end of the file: no trailing data tolerated
4744
4654
  return 0;
4745
4655
  }
4746
4656
  if (strictness == STRICTNESS_TOLERANT) {
4747
4657
  return Infinity;
4748
4658
  }
4749
- // balanced: tolerate up to a legal comment's worth of trailing data (a 16-bit length); beyond that the
4750
- // trailing bytes cannot hide inside a comment, so they are treated as a second thing bolted onto the file
4751
4659
  return MAX_16_BITS;
4752
4660
  }
4753
4661
 
4754
- // cap on how many out-of-window central directory probes the scan below may issue. A legitimate archive needs
4755
- // at most a couple (the canonical record, plus one more to detect genuine ambiguity); every other reachability
4756
- // check is served from the tail window already in memory. The cap keeps an archive whose comment is stuffed
4757
- // with unreachable end-anchored records from forcing an unbounded number of (potentially remote) reads.
4758
4662
  const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
4759
4663
 
4760
- // reachability rankings for an end-anchored candidate record (see getCentralDirectoryReachability)
4761
4664
  const CENTRAL_DIRECTORY_UNREACHABLE = 0;
4762
4665
  const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
4763
4666
  const CENTRAL_DIRECTORY_REACHABLE = 2;
4764
4667
 
4765
- // Locates the authoritative end of central directory record. The canonical record is the last one whose
4766
- // declared comment reaches exactly the end of the file ("end-anchored") and that points to a central directory
4767
- // ("reachable"); earlier signatures (stale append remnants, bytes embedded in a comment) are ignored. When two
4768
- // or more end-anchored records point to a central directory the archive is ambiguous, and the count is returned
4769
- // so the caller can refuse it. A record that reaches the end of the file but points to no central directory (an
4770
- // empty archive) is only "plausible": it cannot be corroborated, so it never counts towards ambiguity and is
4771
- // chosen only when no reachable record exists — otherwise an empty record forged in a comment could outrank the
4772
- // genuine directory. When no record is end-anchored (appended data or an oversized comment), falls back to the
4773
- // last record within the tolerated window that points to a directory (see seekEndOfCentralDirectory).
4774
4668
  async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
4775
4669
  const { size } = reader;
4776
- // an end-anchored record can only live within the last END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS bytes,
4777
- // since the comment length that ties it to the end of the file is a 16-bit field
4778
4670
  const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
4779
- const anchoredOffset = size - anchoredLength;
4780
- const anchoredArray = await readUint8Array(reader, anchoredOffset, anchoredLength);
4781
- const anchoredView = getDataView$1(anchoredArray);
4782
4671
  const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
4783
4672
  let endOfDirectoryInfo;
4784
4673
  let plausibleEndOfDirectoryInfo;
4785
4674
  let endOfDirectoryReachingEndCount = 0;
4786
- for (let indexByte = anchoredArray.length - END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
4787
- if (getUint32(anchoredView, indexByte) == END_OF_CENTRAL_DIR_SIGNATURE) {
4788
- const offset = anchoredOffset + indexByte;
4789
- const commentLength = getUint16(anchoredView, indexByte + 20);
4790
- // end-anchored: the declared comment extends exactly to the end of the file. The comment length is
4791
- // attacker-controlled, but the reconciliation against the (unforgeable) end of file is not.
4792
- if (offset + END_OF_CENTRAL_DIR_LENGTH + commentLength == size) {
4793
- const reachability = await getCentralDirectoryReachability(reader, anchoredView, anchoredOffset, indexByte, offset, size, remoteProbeBudget);
4794
- if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
4795
- if (!endOfDirectoryInfo) {
4796
- endOfDirectoryInfo = {
4797
- offset,
4798
- buffer: anchoredArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer
4799
- };
4800
- }
4801
- endOfDirectoryReachingEndCount++;
4802
- // the canonical record (highest offset) is found first; a second one is enough to flag ambiguity
4803
- if (!rejectAmbiguous || endOfDirectoryReachingEndCount > 1) {
4804
- break;
4805
- }
4806
- } else if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleEndOfDirectoryInfo) {
4807
- plausibleEndOfDirectoryInfo = {
4808
- offset,
4809
- buffer: anchoredArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer
4810
- };
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);
4811
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);
4812
4689
  }
4813
4690
  }
4814
4691
  }
4815
4692
  if (!endOfDirectoryInfo) {
4816
- // no record points to a directory: prefer a plausible (empty) end-anchored record before scanning the
4817
- // tolerated window for a bare signature
4818
4693
  endOfDirectoryInfo = plausibleEndOfDirectoryInfo;
4819
4694
  }
4820
4695
  if (!endOfDirectoryInfo) {
@@ -4823,69 +4698,53 @@
4823
4698
  return { endOfDirectoryInfo, endOfDirectoryReachingEndCount };
4824
4699
  }
4825
4700
 
4826
- // Fallback for findEndOfCentralDirectory when no record is end-anchored: appended data or an oversized comment
4827
- // pushed the record's declared comment past the end of the file, so its position can no longer be reconciled
4828
- // against the end of the file. Scans the tolerated window from the end and returns the first record that points
4829
- // to a central directory, so a stray signature embedded in appended data cannot hijack discovery (which the
4830
- // anchored scan already prevents for anchored records). Falls back to the first plausible (empty) record, then
4831
- // to the last bare signature, so a degenerate archive still opens as before. The window ends at the file, so the
4832
- // same in-window / metered reads as the anchored scan apply.
4833
4701
  async function seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget) {
4834
4702
  const { size } = reader;
4835
4703
  const searchLength = Math.min(size, maxAppendedDataSize == Infinity ? size :
4836
4704
  END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS + maxAppendedDataSize);
4837
- const searchOffset = size - searchLength;
4838
- const searchArray = await readUint8Array(reader, searchOffset, searchLength);
4839
- const searchView = getDataView$1(searchArray);
4840
4705
  let firstSignatureInfo, plausibleInfo;
4841
- for (let indexByte = searchArray.length - END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
4842
- if (getUint32(searchView, indexByte) == END_OF_CENTRAL_DIR_SIGNATURE) {
4843
- const offset = searchOffset + indexByte;
4844
- const record = { offset, buffer: searchArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer };
4845
- if (!firstSignatureInfo) {
4846
- firstSignatureInfo = record;
4847
- }
4848
- const reachability = await getCentralDirectoryReachability(reader, searchView, searchOffset, indexByte, offset, size, remoteProbeBudget);
4849
- if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
4850
- return record;
4851
- }
4852
- if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleInfo) {
4853
- plausibleInfo = record;
4854
- }
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;
4855
4717
  }
4856
4718
  }
4857
4719
  return plausibleInfo || firstSignatureInfo;
4858
4720
  }
4859
4721
 
4860
- // Ranks how strongly an end-anchored candidate record is backed by an actual central directory, used to decide
4861
- // which record is canonical and which count towards ambiguity. It is intentionally conservative and does not
4862
- // fully parse the directory (the caller does that for the canonical record):
4863
- // - REACHABLE: the record dereferences a central directory (a central file header signature at the
4864
- // reconciled offset), or, for a zip64 record, the zip64 locator that a genuine zip64 archive places
4865
- // immediately before it. Only these count towards ambiguity.
4866
- // - PLAUSIBLE: the record describes an empty archive, which has no central directory to dereference. Its
4867
- // fields are indistinguishable from an empty end of central directory record forged inside a comment, so it
4868
- // is accepted only as a last resort and never inflates the ambiguity count.
4869
- // - UNREACHABLE: neither — stale append remnant or signature bytes embedded in a comment.
4870
- // Signatures are read from the tail window (`view`, spanning `[anchoredOffset, size)`) when they fall inside it;
4871
- // only a target below the window costs a (possibly remote) read, metered by `remoteProbeBudget` so a stuffed
4872
- // comment cannot amplify into an unbounded number of requests.
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
+
4873
4737
  async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
4874
4738
  const filesLength = getUint16(view, indexByte + 10);
4875
4739
  const directoryDataLength = getUint32(view, indexByte + 12);
4876
4740
  const directoryDataOffset = getUint32(view, indexByte + 16);
4877
4741
  if (filesLength == MAX_16_BITS || directoryDataLength == MAX_32_BITS || directoryDataOffset == MAX_32_BITS) {
4878
- // saturated fields imply a zip64 record; corroborate it with the zip64 end of central directory locator
4879
- // that sits immediately before it, so saturated bytes inside a comment cannot masquerade as one
4880
4742
  const locatorSignature = await readSignature(reader, view, anchoredOffset, offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, size, remoteProbeBudget);
4881
4743
  return locatorSignature == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE ? CENTRAL_DIRECTORY_REACHABLE : CENTRAL_DIRECTORY_UNREACHABLE;
4882
4744
  }
4883
4745
  if (!filesLength && !directoryDataLength) {
4884
- // a valid empty archive has no central directory to dereference
4885
4746
  return CENTRAL_DIRECTORY_PLAUSIBLE;
4886
4747
  }
4887
- // the central directory ends where this record starts; reconcile the stored offset against that actual
4888
- // position (see stored_cd_offset ⟺ eocd_pos − cd_size) so prepended data does not hide the directory
4889
4748
  for (const centralDirectoryOffset of [offset - directoryDataLength, directoryDataOffset]) {
4890
4749
  if (await readSignature(reader, view, anchoredOffset, centralDirectoryOffset, size, remoteProbeBudget) == CENTRAL_FILE_HEADER_SIGNATURE) {
4891
4750
  return CENTRAL_DIRECTORY_REACHABLE;
@@ -4894,15 +4753,11 @@
4894
4753
  return CENTRAL_DIRECTORY_UNREACHABLE;
4895
4754
  }
4896
4755
 
4897
- // Reads a 4-byte little-endian signature at `signatureOffset`, served from the tail window (`view`, spanning
4898
- // `[anchoredOffset, size)`) when it falls inside it and otherwise from a metered (possibly remote) read. Returns
4899
- // UNDEFINED_VALUE when the position is out of range or the out-of-window read budget is exhausted.
4900
4756
  async function readSignature(reader, view, anchoredOffset, signatureOffset, size, remoteProbeBudget) {
4901
4757
  if (signatureOffset < 0 || signatureOffset + 4 > size) {
4902
4758
  return UNDEFINED_VALUE;
4903
4759
  }
4904
4760
  if (signatureOffset >= anchoredOffset) {
4905
- // the target sits inside the tail window already in memory: no extra read needed
4906
4761
  return getUint32(view, signatureOffset - anchoredOffset);
4907
4762
  }
4908
4763
  if (remoteProbeBudget.count > 0) {
@@ -4910,7 +4765,6 @@
4910
4765
  const signatureArray = await readUint8Array(reader, signatureOffset, 4);
4911
4766
  return getUint32(getDataView$1(signatureArray), 0);
4912
4767
  }
4913
- // out of budget: leave this candidate unverified rather than issue another read
4914
4768
  return UNDEFINED_VALUE;
4915
4769
  }
4916
4770
 
@@ -4926,8 +4780,6 @@
4926
4780
  if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
4927
4781
  throwAmbiguousArchive("mismatched local file header (compression method)");
4928
4782
  }
4929
- // when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
4930
- // also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
4931
4783
  if (!localDirectory.bitFlag.dataDescriptor &&
4932
4784
  (localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
4933
4785
  (localDirectory.signature != zipEntry.signature ||
@@ -5017,7 +4869,6 @@
5017
4869
  const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
5018
4870
  const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
5019
4871
  const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
5020
- // same message as the constant exported by zip-reader.js
5021
4872
  const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
5022
4873
  const MIN_UNIX_TIME = -2147483648;
5023
4874
  const MAX_UNIX_TIME = 2147483647;
@@ -5100,12 +4951,12 @@
5100
4951
  rawExtraField,
5101
4952
  } = entry;
5102
4953
  const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
5103
- rawExtraFieldZip64 = rawExtraFieldZip64 || new Uint8Array();
5104
- rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
5105
- rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
5106
- rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
5107
- rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
5108
- 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;
5109
4960
  if (entry.extraFieldAES) {
5110
4961
  compressionMethod = COMPRESSION_METHOD_AES;
5111
4962
  }
@@ -5211,7 +5062,7 @@
5211
5062
  return false;
5212
5063
  }
5213
5064
 
5214
- async close(comment = new Uint8Array(), options = {}) {
5065
+ async close(comment = EMPTY_UINT8_ARRAY, options = {}) {
5215
5066
  const zipWriter = this;
5216
5067
  const { pendingAddFileCalls, writer } = this;
5217
5068
  const { writable } = writer;
@@ -5510,7 +5361,7 @@
5510
5361
  if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
5511
5362
  throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
5512
5363
  }
5513
- let rawExtraField = new Uint8Array();
5364
+ let rawExtraField = EMPTY_UINT8_ARRAY;
5514
5365
  const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
5515
5366
  if (extraField) {
5516
5367
  let extraFieldSize = 0;
@@ -5571,8 +5422,6 @@
5571
5422
  let maximumCompressedSize = 0;
5572
5423
  let uncompressedSize = 0;
5573
5424
  if (passThrough) {
5574
- // the headers of a passThrough entry describe the payload verbatim: without a
5575
- // reader they would declare content that is not there
5576
5425
  if (!reader) {
5577
5426
  throw new Error(ERR_UNDEFINED_READER);
5578
5427
  }
@@ -5583,8 +5432,6 @@
5583
5432
  }
5584
5433
  const zip64Enabled = zip64 === true;
5585
5434
  const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
5586
- // entries without a reader have no payload: write them as empty stored files instead of
5587
- // declaring a compressed or encrypted content that does not exist
5588
5435
  const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
5589
5436
  if (!reader) {
5590
5437
  level = 0;
@@ -5722,7 +5569,6 @@
5722
5569
  fileEntry.diskNumberStart = writer.diskNumber;
5723
5570
  fileEntry.offset = getSegmentOffset(zipWriter, writer);
5724
5571
  if (usdz) {
5725
- // the padding depends on the entry offset, only known now that the writer is locked
5726
5572
  const previousMetadataSize = entryInfo.metadataSize;
5727
5573
  appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
5728
5574
  fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
@@ -5765,10 +5611,6 @@
5765
5611
  if (releaseLockWriter) {
5766
5612
  releaseLockWriter();
5767
5613
  }
5768
- // A buffered entry uses a temporary stream (see `createTempStream`). When that stream is
5769
- // backed by a resource (a file, an OPFS handle, ...) it must be released on every exit path,
5770
- // including the error path where the stream is otherwise abandoned without being closed or
5771
- // cancelled. `dispose()` is optional and best-effort so it never masks the original outcome.
5772
5614
  if (bufferedWrite && fileWriter && fileWriter.dispose) {
5773
5615
  try {
5774
5616
  await fileWriter.dispose();
@@ -5795,7 +5637,7 @@
5795
5637
  async function skipDiskIfNeeded() {
5796
5638
  if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
5797
5639
  writer.availableSize = 0;
5798
- await writeData(writer, new Uint8Array());
5640
+ await writeData(writer, EMPTY_UINT8_ARRAY);
5799
5641
  }
5800
5642
  }
5801
5643
  }
@@ -5906,8 +5748,7 @@
5906
5748
  }
5907
5749
  const { writable } = writer;
5908
5750
  if (reader) {
5909
- reader.chunkSize = getChunkSize(config);
5910
- const readable = reader.readable;
5751
+ const readable = reader.createReadable ? reader.createReadable({ chunkSize: getChunkSize(config) }) : reader.readable;
5911
5752
  const size = reader.size;
5912
5753
  const workerOptions = {
5913
5754
  options: {
@@ -6007,13 +5848,9 @@
6007
5848
  : compressionMethod !== COMPRESSION_METHOD_STORE);
6008
5849
  let rawLocalExtraFieldZip64;
6009
5850
  const uncompressedFile = passThrough || !compressed;
6010
- // entries without a data descriptor are always buffered (cf. getFileEntry), so the
6011
- // zip64 extra field values can be patched into the local header before flushing it
6012
5851
  const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
6013
5852
  const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
6014
5853
  if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
6015
- // APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
6016
- // uncompressed and compressed size fields
6017
5854
  const length = 4 + 16;
6018
5855
  const extraFieldZip64 = createRecordWriter(length);
6019
5856
  extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
@@ -6027,7 +5864,7 @@
6027
5864
  }
6028
5865
  }
6029
5866
  } else {
6030
- rawLocalExtraFieldZip64 = new Uint8Array();
5867
+ rawLocalExtraFieldZip64 = EMPTY_UINT8_ARRAY;
6031
5868
  }
6032
5869
  let rawExtraFieldAES;
6033
5870
  if (encrypted && !zipCrypto) {
@@ -6037,7 +5874,7 @@
6037
5874
  rawExtraFieldAES = extraFieldAES.array;
6038
5875
  rawExtraFieldAES[8] = encryptionStrength;
6039
5876
  } else {
6040
- rawExtraFieldAES = new Uint8Array();
5877
+ rawExtraFieldAES = EMPTY_UINT8_ARRAY;
6041
5878
  }
6042
5879
  let rawExtraFieldNTFS;
6043
5880
  let rawExtraFieldExtendedTimestamp;
@@ -6060,8 +5897,7 @@
6060
5897
  }
6061
5898
  rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
6062
5899
  } else {
6063
- // the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
6064
- rawExtraFieldExtendedTimestamp = new Uint8Array();
5900
+ rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
6065
5901
  }
6066
5902
  try {
6067
5903
  const lastModTimeNTFS = getTimeNTFS(lastModDate);
@@ -6076,10 +5912,10 @@
6076
5912
  extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
6077
5913
  rawExtraFieldNTFS = extraFieldNTFS.array;
6078
5914
  } catch {
6079
- rawExtraFieldNTFS = new Uint8Array();
5915
+ rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
6080
5916
  }
6081
5917
  } else {
6082
- rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = new Uint8Array();
5918
+ rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
6083
5919
  }
6084
5920
  let rawExtraFieldUnix;
6085
5921
  try {
@@ -6105,10 +5941,10 @@
6105
5941
  extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
6106
5942
  rawExtraFieldUnix = extraFieldUnix.array;
6107
5943
  } else {
6108
- rawExtraFieldUnix = new Uint8Array();
5944
+ rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
6109
5945
  }
6110
5946
  } catch {
6111
- rawExtraFieldUnix = new Uint8Array();
5947
+ rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
6112
5948
  }
6113
5949
  if (compressionMethod === UNDEFINED_VALUE) {
6114
5950
  compressionMethod = compressed ? COMPRESSION_METHOD_DEFLATE : COMPRESSION_METHOD_STORE;
@@ -6175,7 +6011,7 @@
6175
6011
  version,
6176
6012
  compressionMethod,
6177
6013
  extraFieldExtendedTimestampFlag,
6178
- rawExtraFieldZip64: new Uint8Array(),
6014
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6179
6015
  localExtraFieldZip64Length,
6180
6016
  rawExtraFieldExtendedTimestamp,
6181
6017
  rawExtraFieldNTFS,
@@ -6188,7 +6024,6 @@
6188
6024
  function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
6189
6025
  const { headerInfo } = entryInfo;
6190
6026
  let { localHeaderArray, extraFieldLength } = headerInfo;
6191
- let localHeaderArrayView;
6192
6027
  let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
6193
6028
  if (extraBytesLength < 4) {
6194
6029
  extraBytesLength += 64;
@@ -6201,7 +6036,7 @@
6201
6036
  headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
6202
6037
  arraySet(localHeaderArray, previousLocalHeaderArray);
6203
6038
  arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
6204
- localHeaderArrayView = getDataView(localHeaderArray);
6039
+ const localHeaderArrayView = getDataView(localHeaderArray);
6205
6040
  setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
6206
6041
  headerInfo.localHeaderView = localHeaderArrayView;
6207
6042
  entryInfo.metadataSize += extraBytesLength;
@@ -6209,7 +6044,7 @@
6209
6044
 
6210
6045
  function packUnixId(id) {
6211
6046
  if (id === UNDEFINED_VALUE) {
6212
- return new Uint8Array();
6047
+ return EMPTY_UINT8_ARRAY;
6213
6048
  } else {
6214
6049
  const dataArray = new Uint8Array(4);
6215
6050
  const dataView = getDataView(dataArray);
@@ -6252,7 +6087,7 @@
6252
6087
  dataDescriptor,
6253
6088
  dataDescriptorSignature
6254
6089
  }) {
6255
- let dataDescriptorArray = new Uint8Array();
6090
+ let dataDescriptorArray = EMPTY_UINT8_ARRAY;
6256
6091
  let dataDescriptorView, dataDescriptorOffset = 0;
6257
6092
  let dataDescriptorLength = zip64 ? DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH : DATA_DESCRIPTOR_RECORD_LENGTH;
6258
6093
  if (dataDescriptorSignature) {
@@ -6336,7 +6171,6 @@
6336
6171
  }
6337
6172
  }
6338
6173
  if (zip64 && localExtraFieldZip64Length) {
6339
- // the local zip64 extra field always contains both size slots
6340
6174
  const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
6341
6175
  setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
6342
6176
  setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
@@ -6390,7 +6224,7 @@
6390
6224
  }
6391
6225
  rawExtraFieldZip64 = extraFieldZip64.array;
6392
6226
  } else {
6393
- rawExtraFieldZip64 = new Uint8Array();
6227
+ rawExtraFieldZip64 = EMPTY_UINT8_ARRAY;
6394
6228
  }
6395
6229
  fileEntry.rawExtraFieldZip64 = rawExtraFieldZip64;
6396
6230
  fileEntry.zip64Offset = zip64Offset;
@@ -6405,7 +6239,7 @@
6405
6239
  extraFieldTimestamp.uint32(lastModTimeUnix);
6406
6240
  rawExtraFieldTimestamp = extraFieldTimestamp.array;
6407
6241
  } else {
6408
- rawExtraFieldTimestamp = new Uint8Array();
6242
+ rawExtraFieldTimestamp = EMPTY_UINT8_ARRAY;
6409
6243
  }
6410
6244
  fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
6411
6245
  const extraFieldLength = getLength(
@@ -6461,7 +6295,7 @@
6461
6295
  await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
6462
6296
  directoryDiskOffset = offset;
6463
6297
  writer.availableSize = 0;
6464
- await writeData(writer, new Uint8Array());
6298
+ await writeData(writer, EMPTY_UINT8_ARRAY);
6465
6299
  }
6466
6300
  if (indexFileEntry == 0) {
6467
6301
  cdStartDiskNumber = writer.diskNumber;
@@ -6534,7 +6368,7 @@
6534
6368
  const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
6535
6369
  if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
6536
6370
  writer.availableSize = 0;
6537
- await writeData(writer, new Uint8Array());
6371
+ await writeData(writer, EMPTY_UINT8_ARRAY);
6538
6372
  }
6539
6373
  lastDiskNumber = writer.diskNumber;
6540
6374
  if (zip64) {
@@ -6631,7 +6465,6 @@
6631
6465
  return Math.floor(date.getTime() / 1000);
6632
6466
  }
6633
6467
 
6634
- // the extended timestamp extra field stores dates as signed 32-bit time_t values
6635
6468
  function inUnixTimeRange(timeUnix) {
6636
6469
  return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
6637
6470
  }
@@ -6816,36 +6649,12 @@
6816
6649
  const DEFAULT_THRESHOLD = 1024 * 1024;
6817
6650
  const DEFAULT_DIRECTORY_NAME = ".zip.js-temp";
6818
6651
 
6819
- // Builds a `createTempStream` factory (see `ZipWriter`'s option of the same name) that spills the
6820
- // data of a buffered entry to the Origin Private File System (OPFS) instead of keeping it in memory.
6821
- //
6822
- // It is meant for the buffered-write path (keep-order concurrent `add()`, non-seekable output, ...),
6823
- // where the compressed data of an entry has to be held until the writer is free. In memory that data
6824
- // grows with the entry size; here it is written to an OPFS file and streamed back, so peak memory
6825
- // stays bounded.
6826
- //
6827
- // It is hybrid on purpose: an entry stays fully in memory until it exceeds `thresholdBytes`, and only
6828
- // then spills to a file. Small entries never touch the disk, so a workload made of many tiny entries
6829
- // keeps the speed of the in-memory default; only genuinely large entries pay for (and benefit from)
6830
- // the file round-trip.
6831
- //
6832
- // OPFS is a browser/worker feature; there is no fallback here. Feature-detect
6833
- // `navigator.storage.getDirectory` (or inject `getDirectory`) before using it, and let the writer use
6834
- // its in-memory default elsewhere.
6835
- //
6836
- // Options:
6837
- // thresholdBytes spill to a file once a buffered entry exceeds this size (default 1 MiB).
6838
- // directoryName name of the OPFS sub-directory holding the temp files (default ".zip.js-temp").
6839
- // getDirectory returns (or resolves to) the root `FileSystemDirectoryHandle`. Defaults to
6840
- // `navigator.storage.getDirectory()`. Inject it to run inside a worker with a
6841
- // pre-obtained handle, or to test against a mock.
6842
6652
  function createOPFSTempStream(options = {}) {
6843
6653
  const {
6844
6654
  thresholdBytes = DEFAULT_THRESHOLD,
6845
6655
  directoryName = DEFAULT_DIRECTORY_NAME,
6846
6656
  getDirectory = () => navigator.storage.getDirectory()
6847
6657
  } = options;
6848
- // The temp directory is resolved once and shared by every entry of this factory.
6849
6658
  let directoryHandlePromise;
6850
6659
  function getTempDirectory() {
6851
6660
  if (!directoryHandlePromise) {
@@ -6854,15 +6663,12 @@
6854
6663
  }
6855
6664
  return directoryHandlePromise;
6856
6665
  }
6857
- return async function () {
6666
+ return function () {
6858
6667
  const memoryChunks = [];
6859
6668
  let bufferedSize = 0;
6860
6669
  let spilled = false;
6861
6670
  let fileName, fileHandle, fileWriter, fileReader;
6862
6671
 
6863
- // Move whatever is buffered in memory to a fresh OPFS file, then keep writing to that file.
6864
- // `FileSystemWritableFileStream` is a `WritableStream`, so it is driven through the standard
6865
- // writer API rather than its non-standard `write()`/`seek()` convenience methods.
6866
6672
  async function spillToFile() {
6867
6673
  const directoryHandle = await getTempDirectory();
6868
6674
  fileName = crypto.randomUUID();
@@ -6872,7 +6678,6 @@
6872
6678
  for (const chunk of memoryChunks) {
6873
6679
  await fileWriter.write(chunk);
6874
6680
  }
6875
- // Release the in-memory copy; from now on data lives in the file.
6876
6681
  memoryChunks.length = 0;
6877
6682
  }
6878
6683
 
@@ -6896,9 +6701,6 @@
6896
6701
  }
6897
6702
  });
6898
6703
 
6899
- // The writer always closes `writable` before reading `readable`, so `spilled` is final by the
6900
- // time `pull` runs. It is read lazily here (not in `start`, which runs at construction time,
6901
- // before anything is written and before `spilled` is known).
6902
6704
  let memoryIndex = 0;
6903
6705
  const readable = new ReadableStream({
6904
6706
  async pull(controller) {
@@ -6925,11 +6727,6 @@
6925
6727
  }
6926
6728
  }
6927
6729
  }, { highWaterMark: 0 });
6928
- // highWaterMark 0: do not pull (and possibly close on an empty buffer) until the consumer
6929
- // actually reads, which the writer only does once the data has been fully written.
6930
-
6931
- // Called by the writer on every exit path (success, error, abort). Best-effort: it must never
6932
- // throw. Closes any still-open file writer, then deletes the temp file.
6933
6730
  async function dispose() {
6934
6731
  if (fileWriter) {
6935
6732
  try {
@@ -7014,6 +6811,7 @@
7014
6811
  exports.ERR_INVALID_SIGNATURE = ERR_INVALID_SIGNATURE;
7015
6812
  exports.ERR_INVALID_UNCOMPRESSED_SIZE = ERR_INVALID_UNCOMPRESSED_SIZE;
7016
6813
  exports.ERR_INVALID_VERSION = ERR_INVALID_VERSION;
6814
+ exports.ERR_ITERATOR_COMPLETED_TOO_SOON = ERR_ITERATOR_COMPLETED_TOO_SOON;
7017
6815
  exports.ERR_LOCAL_FILE_HEADER_NOT_FOUND = ERR_LOCAL_FILE_HEADER_NOT_FOUND;
7018
6816
  exports.ERR_OVERLAPPING_ENTRY = ERR_OVERLAPPING_ENTRY;
7019
6817
  exports.ERR_SPLIT_ZIP_FILE = ERR_SPLIT_ZIP_FILE;
@@ -7022,6 +6820,7 @@
7022
6820
  exports.ERR_UNSUPPORTED_COMPRESSION = ERR_UNSUPPORTED_COMPRESSION$1;
7023
6821
  exports.ERR_UNSUPPORTED_ENCRYPTION = ERR_UNSUPPORTED_ENCRYPTION;
7024
6822
  exports.ERR_UNSUPPORTED_FORMAT = ERR_UNSUPPORTED_FORMAT;
6823
+ exports.ERR_WRITER_NOT_INITIALIZED = ERR_WRITER_NOT_INITIALIZED;
7025
6824
  exports.ERR_ZIP_NOT_EMPTY = ERR_ZIP_NOT_EMPTY;
7026
6825
  exports.HttpRangeReader = HttpRangeReader;
7027
6826
  exports.HttpReader = HttpReader;