@zip.js/zip.js 2.8.25 → 2.8.28

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 (52) hide show
  1. package/README.md +2 -2
  2. package/deno.json +1 -1
  3. package/dist/zip-core.js +1050 -569
  4. package/dist/zip-core.min.js +1 -1
  5. package/dist/zip-fs-core.js +1146 -617
  6. package/dist/zip-fs-core.min.js +1 -1
  7. package/dist/zip-fs-native.js +1207 -627
  8. package/dist/zip-fs-native.min.js +1 -1
  9. package/dist/zip-fs.js +1279 -681
  10. package/dist/zip-fs.min.js +1 -1
  11. package/dist/zip-legacy.js +1051 -570
  12. package/dist/zip-legacy.min.js +1 -1
  13. package/dist/zip-module.wasm +0 -0
  14. package/dist/zip-native.js +1053 -572
  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 +1125 -626
  19. package/dist/zip.min.js +1 -1
  20. package/eslint.config.mjs +6 -1
  21. package/index-native.cjs +1207 -627
  22. package/index-native.min.js +1 -1
  23. package/index.cjs +1279 -681
  24. package/index.d.ts +2451 -2355
  25. package/index.min.js +1 -1
  26. package/lib/core/codec-pool.js +39 -1
  27. package/lib/core/codec-worker.js +120 -34
  28. package/lib/core/configuration.js +3 -0
  29. package/lib/core/constants.js +6 -0
  30. package/lib/core/io.js +55 -25
  31. package/lib/core/options.js +2 -0
  32. package/lib/core/streams/aes-crypto-stream.js +12 -12
  33. package/lib/core/streams/codec-stream.js +11 -5
  34. package/lib/core/streams/codecs/sjcl.js +1 -33
  35. package/lib/core/streams/common-crypto.js +4 -4
  36. package/lib/core/streams/zip-crypto-stream.js +25 -15
  37. package/lib/core/streams/zip-entry-stream.js +35 -1
  38. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  39. package/lib/core/streams/zlib-wasm/zlib-streams.js +73 -55
  40. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  41. package/lib/core/util/decode-cp437.js +1 -1
  42. package/lib/core/util/decode-text.js +2 -1
  43. package/lib/core/web-worker-base.js +12 -4
  44. package/lib/core/web-worker-inline-native.js +1 -1
  45. package/lib/core/web-worker-inline-wasm.js +1 -1
  46. package/lib/core/zip-fs.js +154 -55
  47. package/lib/core/zip-reader.js +185 -67
  48. package/lib/core/zip-writer.js +613 -376
  49. package/lib/core/zlib-streams-inline.js +1 -1
  50. package/lib/zip-core-reader.js +3 -1
  51. package/lib/zip-core-writer.js +1 -0
  52. package/package.json +20 -6
@@ -49,6 +49,7 @@
49
49
  const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
50
50
  const ZIP64_END_OF_CENTRAL_DIR_SIGNATURE = 0x06064b50;
51
51
  const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = 0x07064b50;
52
+ const CENTRAL_FILE_HEADER_LENGTH = 46;
52
53
  const END_OF_CENTRAL_DIR_LENGTH = 22;
53
54
  const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH = 20;
54
55
  const ZIP64_END_OF_CENTRAL_DIR_LENGTH = 56;
@@ -96,9 +97,11 @@
96
97
  const DIRECTORY_SIGNATURE = "/";
97
98
 
98
99
  const HEADER_SIZE = 30;
100
+ const HEADER_OFFSET_VERSION = 0;
99
101
  const HEADER_OFFSET_SIGNATURE = 10;
100
102
  const HEADER_OFFSET_COMPRESSED_SIZE = 14;
101
103
  const HEADER_OFFSET_UNCOMPRESSED_SIZE = 18;
104
+ const LOCAL_HEADER_COMMON_OFFSET = 4;
102
105
 
103
106
  const MAX_DATE = new Date(2107, 11, 31);
104
107
  const MIN_DATE = new Date(1980, 0, 1);
@@ -153,6 +156,7 @@
153
156
  chunkSize: 64 * 1024,
154
157
  maxWorkers,
155
158
  terminateWorkerTimeout: 5000,
159
+ workerStarvationTimeout: 5000,
156
160
  useWebWorkers: true,
157
161
  useCompressionStream: true,
158
162
  CompressionStream: typeof CompressionStream != UNDEFINED_TYPE && CompressionStream,
@@ -205,6 +209,7 @@
205
209
 
206
210
  const CONTENT_TYPE_TEXT_PLAIN = "text/plain";
207
211
  const HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
212
+ const HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
208
213
  const HTTP_HEADER_CONTENT_RANGE = "Content-Range";
209
214
  const HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
210
215
  const HTTP_HEADER_RANGE = "Range";
@@ -212,7 +217,7 @@
212
217
  const HTTP_METHOD_HEAD = "HEAD";
213
218
  const HTTP_METHOD_GET = "GET";
214
219
  const HTTP_RANGE_UNIT = "bytes";
215
- const DEFAULT_CHUNK_SIZE = 64 * 1024;
220
+ const DEFAULT_CHUNK_SIZE$1 = 64 * 1024;
216
221
  const DEFAULT_BUFFER_SIZE = 256 * 1024;
217
222
 
218
223
  const PROPERTY_NAME_WRITABLE = "writable";
@@ -232,7 +237,7 @@
232
237
 
233
238
  get readable() {
234
239
  const reader = this;
235
- const { chunkSize = DEFAULT_CHUNK_SIZE } = reader;
240
+ const { chunkSize = DEFAULT_CHUNK_SIZE$1 } = reader;
236
241
  const readable = new ReadableStream({
237
242
  start() {
238
243
  this.chunkOffset = 0;
@@ -329,7 +334,7 @@
329
334
 
330
335
  writeUint8Array(array) {
331
336
  const writer = this;
332
- let indexArray = 0;
337
+ let indexArray;
333
338
  let dataString = writer.pending;
334
339
  const delta = writer.pending.length;
335
340
  writer.pending = "";
@@ -339,12 +344,10 @@
339
344
  for (; indexArray < array.length; indexArray++) {
340
345
  writer.pending += String.fromCharCode(array[indexArray]);
341
346
  }
342
- if (dataString.length) {
343
- if (dataString.length > 2) {
344
- writer.data += btoa(dataString);
345
- } else {
346
- writer.pending += dataString;
347
- }
347
+ if (dataString.length > 2) {
348
+ writer.data += btoa(dataString);
349
+ } else {
350
+ writer.pending = dataString + writer.pending;
348
351
  }
349
352
  }
350
353
 
@@ -391,6 +394,9 @@
391
394
  }
392
395
  });
393
396
  writer.blob = new Response(transformStream.readable, { headers }).blob();
397
+ // mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
398
+ // surfaces them
399
+ writer.blob.catch(() => { });
394
400
  }
395
401
 
396
402
  getData() {
@@ -475,7 +481,8 @@
475
481
  preventHeadRequest,
476
482
  useRangeHeader,
477
483
  forceRangeRequests,
478
- combineSizeEocd
484
+ combineSizeEocd,
485
+ fetch
479
486
  } = options;
480
487
  options = Object.assign({}, options);
481
488
  delete options.preventHeadRequest;
@@ -483,13 +490,15 @@
483
490
  delete options.forceRangeRequests;
484
491
  delete options.combineSizeEocd;
485
492
  delete options.useXHR;
493
+ delete options.fetch;
486
494
  Object.assign(httpReader, {
487
495
  url,
488
496
  options,
489
497
  preventHeadRequest,
490
498
  useRangeHeader,
491
499
  forceRangeRequests,
492
- combineSizeEocd
500
+ combineSizeEocd,
501
+ fetch
493
502
  });
494
503
  }
495
504
 
@@ -508,7 +517,10 @@
508
517
  throw new Error(ERR_HTTP_RANGE);
509
518
  } else {
510
519
  if (combineSizeEocd) {
511
- httpReader.eocdCache = new Uint8Array(await response.arrayBuffer());
520
+ const eocdCache = new Uint8Array(await response.arrayBuffer());
521
+ if (response.status == 206 && eocdCache.length == END_OF_CENTRAL_DIR_LENGTH) {
522
+ httpReader.eocdCache = eocdCache;
523
+ }
512
524
  }
513
525
  let contentSize;
514
526
  const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
@@ -554,7 +566,18 @@
554
566
  if (response.status != 206) {
555
567
  throw new Error(ERR_HTTP_RANGE);
556
568
  }
557
- return new Uint8Array(await response.arrayBuffer());
569
+ const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
570
+ if (contentRangeHeader) {
571
+ const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
572
+ if (!Number.isNaN(rangeStart) && rangeStart != index) {
573
+ throw new Error(ERR_HTTP_RANGE);
574
+ }
575
+ }
576
+ const data = new Uint8Array(await response.arrayBuffer());
577
+ if (data.length != length) {
578
+ throw new Error(ERR_HTTP_RANGE);
579
+ }
580
+ return data;
558
581
  }
559
582
  } else {
560
583
  const { data } = httpReader;
@@ -591,9 +614,9 @@
591
614
  async function getRequestData(httpReader, sendRequest) {
592
615
  const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
593
616
  httpReader.data = new Uint8Array(await response.arrayBuffer());
594
- if (!httpReader.size) {
595
- httpReader.size = httpReader.data.length;
596
- }
617
+ // the downloaded content is authoritative, e.g. the Content-Length response header
618
+ // of the HEAD request can be the encoded size when the content is compressed
619
+ httpReader.size = httpReader.data.length;
597
620
  }
598
621
 
599
622
  async function getContentLength(httpReader, sendRequest, getRequestData) {
@@ -602,7 +625,7 @@
602
625
  } else {
603
626
  const response = await sendRequest(HTTP_METHOD_HEAD, httpReader, getHeaders(httpReader));
604
627
  const contentLength = response.headers.get(HTTP_HEADER_CONTENT_LENGTH);
605
- if (contentLength) {
628
+ if (contentLength && !response.headers.get(HTTP_HEADER_CONTENT_ENCODING)) {
606
629
  httpReader.size = Number(contentLength);
607
630
  } else {
608
631
  await getRequestData(httpReader, httpReader.options);
@@ -610,8 +633,8 @@
610
633
  }
611
634
  }
612
635
 
613
- async function sendFetchRequest(method, { options, url }, headers) {
614
- const response = await fetch(url, Object.assign({}, options, { method, headers }));
636
+ async function sendFetchRequest(method, { fetch: fetchFunction = fetch, options, url }, headers) {
637
+ const response = await fetchFunction(url, Object.assign({}, options, { method, headers }));
615
638
  if (response.status < 400) {
616
639
  return response;
617
640
  } else {
@@ -657,7 +680,8 @@
657
680
  super();
658
681
  Object.assign(this, {
659
682
  url,
660
- reader: options.useXHR ? new XHRReader(url, options) : new FetchReader(url, options)
683
+ // an explicit fetch implementation takes precedence over useXHR
684
+ reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
661
685
  });
662
686
  }
663
687
 
@@ -749,7 +773,7 @@
749
773
  reader.lastDiskNumber = 0;
750
774
  reader.lastDiskOffset = 0;
751
775
  await Promise.all(readers.map(async (diskReader, indexDiskReader) => {
752
- await diskReader.init();
776
+ await initStream(diskReader);
753
777
  if (indexDiskReader != readers.length - 1) {
754
778
  reader.lastDiskOffset += diskReader.size;
755
779
  }
@@ -833,14 +857,26 @@
833
857
  writer.diskOffset += diskSourceWriter.size;
834
858
  writer.diskNumber++;
835
859
  diskWriter = null;
836
- await this.write(chunk.subarray(availableSize));
860
+ // the next disk is not opened yet but its space is available; leaving a
861
+ // stale value here would force a new disk for every subsequent write
862
+ writer.availableSize = writer.maxSize;
863
+ if (chunk.length > availableSize) {
864
+ await this.write(chunk.subarray(availableSize));
865
+ }
837
866
  } else {
838
867
  await writeChunk(chunk);
839
868
  }
840
869
  },
841
870
  async close() {
842
- await diskWriter.ready;
843
- await closeDisk();
871
+ if (diskWriter) {
872
+ await diskWriter.ready;
873
+ await closeDisk();
874
+ }
875
+ },
876
+ async abort(reason) {
877
+ if (diskWriter) {
878
+ await diskWriter.abort(reason);
879
+ }
844
880
  }
845
881
  });
846
882
  Object.defineProperty(writer, PROPERTY_NAME_WRITABLE, {
@@ -855,7 +891,6 @@
855
891
  await diskWriter.ready;
856
892
  await diskWriter.write(chunk);
857
893
  diskSourceWriter.size += chunkLength;
858
- writer.size += chunkLength;
859
894
  writer.availableSize -= chunkLength;
860
895
  }
861
896
  }
@@ -1672,37 +1707,6 @@
1672
1707
  }
1673
1708
  };
1674
1709
 
1675
- /**
1676
- * Random values
1677
- * @namespace
1678
- */
1679
- const random = {
1680
- /**
1681
- * Generate random words with pure js, cryptographically not as strong & safe as native implementation.
1682
- * @param {TypedArray} typedArray The array to fill.
1683
- * @return {TypedArray} The random values.
1684
- */
1685
- getRandomValues(typedArray) {
1686
- const words = new Uint32Array(typedArray.buffer);
1687
- const r = (m_w) => {
1688
- let m_z = 0x3ade68b1;
1689
- const mask = 0xffffffff;
1690
- return function () {
1691
- m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
1692
- m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
1693
- const result = ((((m_z << 0x10) + m_w) & mask) / 0x100000000) + .5;
1694
- return result * (Math.random() > .5 ? 1 : -1);
1695
- };
1696
- };
1697
- for (let i = 0, rcache; i < typedArray.length; i += 4) {
1698
- const _r = r((rcache || Math.random()) * 0x100000000);
1699
- rcache = _r() * 0x3ade67b7;
1700
- words[i / 4] = (_r() * 0x100000000) | 0;
1701
- }
1702
- return typedArray;
1703
- }
1704
- };
1705
-
1706
1710
  /** @fileOverview CTR mode implementation.
1707
1711
  *
1708
1712
  * Special thanks to Roy Nicholson for pointing out a bug in our
@@ -1933,12 +1937,15 @@
1933
1937
  const ERR_INVALID_PASSWORD = "Invalid password";
1934
1938
  const ERR_INVALID_SIGNATURE = "Invalid signature";
1935
1939
  const ERR_ABORT_CHECK_PASSWORD = "zipjs-abort-check-password";
1940
+ const ERR_UNSUPPORTED_CRYPTO_API = "Crypto API not supported";
1936
1941
 
1937
1942
  function getRandomValues(array) {
1938
1943
  if (GET_RANDOM_VALUES_SUPPORTED) {
1939
1944
  return crypto.getRandomValues(array);
1940
1945
  } else {
1941
- return random.getRandomValues(array);
1946
+ // all supported runtimes expose crypto.getRandomValues; failing hard beats
1947
+ // silently generating predictable encryption salts
1948
+ throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
1942
1949
  }
1943
1950
  }
1944
1951
 
@@ -1997,13 +2004,12 @@
1997
2004
 
1998
2005
  class AESDecryptionStream extends TransformStream {
1999
2006
 
2000
- constructor({ password, rawPassword, signed, encryptionStrength, checkPasswordOnly }) {
2007
+ constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
2001
2008
  super({
2002
2009
  start() {
2003
2010
  Object.assign(this, {
2004
2011
  ready: new Promise(resolve => this.resolveReady = resolve),
2005
2012
  password: encodePassword(password, rawPassword),
2006
- signed,
2007
2013
  strength: encryptionStrength - 1,
2008
2014
  pending: new Uint8Array()
2009
2015
  });
@@ -2032,7 +2038,6 @@
2032
2038
  },
2033
2039
  async flush(controller) {
2034
2040
  const {
2035
- signed,
2036
2041
  ctr,
2037
2042
  hmac,
2038
2043
  pending,
@@ -2049,13 +2054,15 @@
2049
2054
  const decryptedChunk = ctr.update(encryptedChunk);
2050
2055
  decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
2051
2056
  }
2052
- if (signed) {
2053
- const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
2054
- for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
2055
- if (signature[indexSignature] != originalSignature[indexSignature]) {
2056
- throw new Error(ERR_INVALID_SIGNATURE);
2057
- }
2058
- }
2057
+ const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
2058
+ // branch-free comparison: accumulate the differences instead of
2059
+ // short-circuiting on the first mismatching byte
2060
+ let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
2061
+ for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
2062
+ invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
2063
+ }
2064
+ if (invalidSignature) {
2065
+ throw new Error(ERR_INVALID_SIGNATURE);
2059
2066
  }
2060
2067
  controller.enqueue(decryptedChunkArray);
2061
2068
  }
@@ -2127,11 +2134,11 @@
2127
2134
  hmac,
2128
2135
  pending
2129
2136
  } = aesCrypto;
2130
- const inputLength = input.length - paddingEnd;
2131
2137
  if (pending.length) {
2132
2138
  input = concat(pending, input);
2133
- output = expand(output, inputLength - (inputLength % BLOCK_LENGTH));
2134
2139
  }
2140
+ const inputLength = input.length - paddingEnd;
2141
+ output = expand(output, paddingStart + (inputLength - (inputLength % BLOCK_LENGTH)));
2135
2142
  let offset;
2136
2143
  for (offset = 0; offset <= inputLength - BLOCK_LENGTH; offset += BLOCK_LENGTH) {
2137
2144
  const inputChunk = toBits(codecBytes, subarray(input, offset, offset + BLOCK_LENGTH));
@@ -2279,21 +2286,24 @@
2279
2286
 
2280
2287
  class ZipCryptoDecryptionStream extends TransformStream {
2281
2288
 
2282
- constructor({ password, passwordVerification, checkPasswordOnly }) {
2289
+ constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
2283
2290
  super({
2284
2291
  start() {
2285
2292
  Object.assign(this, {
2286
2293
  password,
2294
+ rawPassword,
2287
2295
  passwordVerification
2288
2296
  });
2289
- createKeys(this, password);
2297
+ createKeys(this, password, rawPassword);
2290
2298
  },
2291
2299
  transform(chunk, controller) {
2292
2300
  const zipCrypto = this;
2293
- if (zipCrypto.password) {
2301
+ if (zipCrypto.password || zipCrypto.rawPassword) {
2294
2302
  const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
2295
- zipCrypto.password = null;
2296
- if (decryptedHeader.at(-1) != zipCrypto.passwordVerification) {
2303
+ zipCrypto.password = zipCrypto.rawPassword = null;
2304
+ // verify the password with a branch-free comparison of the verification byte, mirroring
2305
+ // the constant-time AES HMAC signature check
2306
+ if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
2297
2307
  throw new Error(ERR_INVALID_PASSWORD);
2298
2308
  }
2299
2309
  chunk = chunk.subarray(HEADER_LENGTH);
@@ -2310,21 +2320,22 @@
2310
2320
 
2311
2321
  class ZipCryptoEncryptionStream extends TransformStream {
2312
2322
 
2313
- constructor({ password, passwordVerification }) {
2323
+ constructor({ password, rawPassword, passwordVerification }) {
2314
2324
  super({
2315
2325
  start() {
2316
2326
  Object.assign(this, {
2317
2327
  password,
2328
+ rawPassword,
2318
2329
  passwordVerification
2319
2330
  });
2320
- createKeys(this, password);
2331
+ createKeys(this, password, rawPassword);
2321
2332
  },
2322
2333
  transform(chunk, controller) {
2323
2334
  const zipCrypto = this;
2324
2335
  let output;
2325
2336
  let offset;
2326
- if (zipCrypto.password) {
2327
- zipCrypto.password = null;
2337
+ if (zipCrypto.password || zipCrypto.rawPassword) {
2338
+ zipCrypto.password = zipCrypto.rawPassword = null;
2328
2339
  const header = getRandomValues(new Uint8Array(HEADER_LENGTH));
2329
2340
  header[HEADER_LENGTH - 1] = zipCrypto.passwordVerification;
2330
2341
  output = new Uint8Array(chunk.length + header.length);
@@ -2359,25 +2370,31 @@
2359
2370
  return output;
2360
2371
  }
2361
2372
 
2362
- function createKeys(target, password) {
2373
+ function createKeys(target, password, rawPassword) {
2363
2374
  const keys = [0x12345678, 0x23456789, 0x34567890];
2364
2375
  Object.assign(target, {
2365
2376
  keys,
2366
2377
  crcKey0: new Crc32(keys[0]),
2367
2378
  crcKey2: new Crc32(keys[2])
2368
2379
  });
2369
- for (let index = 0; index < password.length; index++) {
2370
- updateKeys(target, password.charCodeAt(index));
2380
+ if (rawPassword) {
2381
+ for (let index = 0; index < rawPassword.length; index++) {
2382
+ updateKeys(target, rawPassword[index]);
2383
+ }
2384
+ } else {
2385
+ for (let index = 0; index < password.length; index++) {
2386
+ updateKeys(target, password.charCodeAt(index));
2387
+ }
2371
2388
  }
2372
2389
  }
2373
2390
 
2374
2391
  function updateKeys(target, byte) {
2375
- let [key0, key1, key2] = target.keys;
2392
+ let [, key1] = target.keys;
2376
2393
  target.crcKey0.append([byte]);
2377
- key0 = ~target.crcKey0.get();
2394
+ const key0 = ~target.crcKey0.get();
2378
2395
  key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1);
2379
2396
  target.crcKey2.append([key1 >>> 24]);
2380
- key2 = ~target.crcKey2.get();
2397
+ const key2 = ~target.crcKey2.get();
2381
2398
  target.keys = [key0, key1, key2];
2382
2399
  }
2383
2400
 
@@ -2424,6 +2441,7 @@
2424
2441
 
2425
2442
 
2426
2443
  const ERR_INVALID_UNCOMPRESSED_SIZE = "Invalid uncompressed size";
2444
+ const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
2427
2445
  const FORMAT_DEFLATE_RAW = "deflate-raw";
2428
2446
  const FORMAT_DEFLATE64_RAW = "deflate64-raw";
2429
2447
 
@@ -2480,6 +2498,7 @@
2480
2498
  }
2481
2499
  if (compressed) {
2482
2500
  readable = pipeThroughCommpressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamZlib, DecompressionStream);
2501
+ readable = mapInflateStreamError(readable);
2483
2502
  }
2484
2503
  if ((!encrypted || zipCrypto) && signed) {
2485
2504
  crc32Stream = new Crc32Stream();
@@ -2530,6 +2549,37 @@
2530
2549
  return readable.pipeThrough(transformStream);
2531
2550
  }
2532
2551
 
2552
+ // DecompressionStream implementations can fail with a message-less TypeError on malformed
2553
+ // input; give these errors an identifiable message (errors raised by the other stages of
2554
+ // the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
2555
+ function mapInflateStreamError(readable) {
2556
+ const reader = readable.getReader();
2557
+ return new ReadableStream({
2558
+ async pull(controller) {
2559
+ let result;
2560
+ try {
2561
+ result = await reader.read();
2562
+ } catch (error) {
2563
+ if (error && error.message) {
2564
+ throw error;
2565
+ }
2566
+ const mappedError = new Error(ERR_INVALID_COMPRESSED_DATA);
2567
+ mappedError.cause = error;
2568
+ throw mappedError;
2569
+ }
2570
+ const { value, done } = result;
2571
+ if (done) {
2572
+ controller.close();
2573
+ } else {
2574
+ controller.enqueue(value);
2575
+ }
2576
+ },
2577
+ cancel(reason) {
2578
+ return reader.cancel(reason);
2579
+ }
2580
+ });
2581
+ }
2582
+
2533
2583
  /*
2534
2584
  Copyright (c) 2022 Gildas Lormeau. All rights reserved.
2535
2585
 
@@ -2559,6 +2609,7 @@
2559
2609
  */
2560
2610
 
2561
2611
 
2612
+ const DEFAULT_CHUNK_SIZE = 64 * 1024;
2562
2613
  const MESSAGE_EVENT_TYPE = "message";
2563
2614
  const MESSAGE_START = "start";
2564
2615
  const MESSAGE_PULL = "pull";
@@ -2627,6 +2678,9 @@
2627
2678
 
2628
2679
  constructor(chunkSize) {
2629
2680
  let pendingChunk;
2681
+ if (!(chunkSize >= 1)) {
2682
+ chunkSize = DEFAULT_CHUNK_SIZE;
2683
+ }
2630
2684
  super({
2631
2685
  transform,
2632
2686
  flush(controller) {
@@ -2644,12 +2698,12 @@
2644
2698
  chunk = newChunk;
2645
2699
  pendingChunk = null;
2646
2700
  }
2647
- if (chunk.length > chunkSize) {
2648
- controller.enqueue(chunk.slice(0, chunkSize));
2649
- transform(chunk.slice(chunkSize), controller);
2650
- } else {
2651
- pendingChunk = chunk;
2701
+ let offset = 0;
2702
+ while (chunk.length - offset > chunkSize) {
2703
+ controller.enqueue(chunk.slice(offset, offset + chunkSize));
2704
+ offset += chunkSize;
2652
2705
  }
2706
+ pendingChunk = offset ? chunk.slice(offset) : chunk;
2653
2707
  }
2654
2708
  }
2655
2709
  }
@@ -2684,8 +2738,10 @@
2684
2738
 
2685
2739
 
2686
2740
  const MODULE_WORKER_OPTIONS = { type: "module" };
2741
+ const ERROR_EVENT_TYPE = "error";
2742
+ const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
2687
2743
 
2688
- let webWorkerSupported, webWorkerURI, webWorkerOptions;
2744
+ let webWorkerSupported, webWorkerSource, webWorkerURI, webWorkerOptions;
2689
2745
  let transferStreamsSupported = true;
2690
2746
  try {
2691
2747
  transferStreamsSupported = typeof structuredClone == FUNCTION_TYPE && structuredClone(new DOMException("", "AbortError")).code !== UNDEFINED_VALUE;
@@ -2700,8 +2756,9 @@
2700
2756
  const { signal } = streamOptions;
2701
2757
  Object.assign(workerData, {
2702
2758
  busy: true,
2759
+ generation: (workerData.generation || 0) + 1,
2703
2760
  readable: readable
2704
- .pipeThrough(new ChunkStream(config.chunkSize))
2761
+ .pipeThrough(new ChunkStream(getChunkSize(config)))
2705
2762
  .pipeThrough(new ProgressWatcherStream(streamOptions), { signal }),
2706
2763
  writable,
2707
2764
  options: Object.assign({}, options),
@@ -2724,15 +2781,17 @@
2724
2781
  });
2725
2782
  },
2726
2783
  onTaskFinished() {
2727
- const { resolveTerminated } = workerData;
2728
- if (resolveTerminated) {
2729
- workerData.resolveTerminated = null;
2730
- workerData.terminated = true;
2731
- workerData.worker.terminate();
2732
- resolveTerminated();
2784
+ if (workerData.busy) {
2785
+ const { resolveTerminated } = workerData;
2786
+ if (resolveTerminated) {
2787
+ workerData.resolveTerminated = null;
2788
+ workerData.terminated = true;
2789
+ workerData.worker.terminate();
2790
+ resolveTerminated();
2791
+ }
2792
+ workerData.busy = false;
2793
+ onTaskFinished(workerData);
2733
2794
  }
2734
- workerData.busy = false;
2735
- onTaskFinished(workerData);
2736
2795
  }
2737
2796
  });
2738
2797
  if (webWorkerSupported === UNDEFINED_VALUE) {
@@ -2801,6 +2860,7 @@
2801
2860
  }
2802
2861
  Object.assign(workerData, {
2803
2862
  worker,
2863
+ terminated: false,
2804
2864
  interface: {
2805
2865
  run: () => runWebWorker(workerData, { chunkSize, wasmURI, baseURI })
2806
2866
  }
@@ -2855,37 +2915,85 @@
2855
2915
  result
2856
2916
  });
2857
2917
  const { readable, options } = workerData;
2858
- const { writable, closed } = watchClosedStream(workerData.writable);
2859
- const streamsTransferred = sendMessage({
2860
- type: MESSAGE_START,
2861
- options,
2862
- config,
2863
- readable,
2864
- writable
2865
- }, workerData);
2918
+ const { writable, closed, abortPipe } = watchClosedStream(workerData.writable);
2919
+ let streamsTransferred;
2920
+ try {
2921
+ streamsTransferred = sendMessage({
2922
+ type: MESSAGE_START,
2923
+ options,
2924
+ config,
2925
+ readable,
2926
+ writable
2927
+ }, workerData);
2928
+ } catch (error) {
2929
+ abortPipe();
2930
+ try {
2931
+ await closed;
2932
+ } catch {
2933
+ // ignored
2934
+ }
2935
+ workerData.onTaskFinished();
2936
+ throw error;
2937
+ }
2866
2938
  if (!streamsTransferred) {
2867
2939
  Object.assign(workerData, {
2868
2940
  reader: readable.getReader(),
2869
2941
  writer: writable.getWriter()
2870
2942
  });
2871
2943
  }
2872
- const resultValue = await result;
2873
- if (!streamsTransferred) {
2874
- await writable.getWriter().close();
2944
+ try {
2945
+ const resultValue = await result;
2946
+ await closeWritable();
2947
+ await closed;
2948
+ return resultValue;
2949
+ } catch (error) {
2950
+ await closeWritable();
2951
+ // unblock the pipe watcher if the worker died before closing its end of the stream
2952
+ abortPipe();
2953
+ try {
2954
+ await closed;
2955
+ } catch {
2956
+ // ignored
2957
+ }
2958
+ throw error;
2959
+ }
2960
+
2961
+ async function closeWritable() {
2962
+ if (!streamsTransferred && !writable.locked) {
2963
+ try {
2964
+ await writable.getWriter().close();
2965
+ } catch {
2966
+ // ignored
2967
+ }
2968
+ }
2875
2969
  }
2876
- await closed;
2877
- return resultValue;
2878
2970
  }
2879
2971
 
2880
2972
  function watchClosedStream(writableSource) {
2973
+ const abortController = new AbortController();
2881
2974
  const { writable, readable } = new TransformStream();
2882
- const closed = readable.pipeTo(writableSource, { preventClose: true });
2883
- return { writable, closed };
2975
+ const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
2976
+ // the pipe can fail before `closed` is awaited (e.g. the destination write fails);
2977
+ // mark the rejection as handled so it cannot kill the process in the meantime
2978
+ closed.catch(() => { });
2979
+ return { writable, closed, abortPipe: () => abortController.abort() };
2980
+ }
2981
+
2982
+ function terminateWorker$1(workerData) {
2983
+ const { worker } = workerData;
2984
+ if (worker) {
2985
+ try {
2986
+ worker.terminate();
2987
+ } catch {
2988
+ // ignored
2989
+ }
2990
+ }
2991
+ workerData.interface = null;
2884
2992
  }
2885
2993
 
2886
2994
  function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true) {
2887
2995
  let worker, resolvedURI, resolvedOptions;
2888
- if (webWorkerURI === UNDEFINED_VALUE) {
2996
+ if (webWorkerURI === UNDEFINED_VALUE || webWorkerSource !== url) {
2889
2997
  // deno-lint-ignore valid-typeof
2890
2998
  const isFunctionURI = typeof url == FUNCTION_TYPE;
2891
2999
  if (isFunctionURI) {
@@ -2942,16 +3050,36 @@
2942
3050
  }
2943
3051
  }
2944
3052
  }
3053
+ webWorkerSource = url;
2945
3054
  webWorkerURI = resolvedURI;
2946
3055
  webWorkerOptions = resolvedOptions;
2947
3056
  } else {
2948
3057
  worker = new Worker(webWorkerURI, webWorkerOptions);
2949
3058
  }
2950
3059
  worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData));
3060
+ worker.addEventListener(ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
3061
+ worker.addEventListener(MESSAGE_ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
2951
3062
  return worker;
2952
3063
  }
2953
3064
 
2954
- function sendMessage(message, { worker, writer, onTaskFinished, transferStreams }) {
3065
+ function onWorkerError(event, workerData) {
3066
+ if (event.preventDefault) {
3067
+ event.preventDefault();
3068
+ }
3069
+ const { rejectResult, writer, onTaskFinished } = workerData;
3070
+ // the worker is unusable (e.g. its script failed to load); terminate it and
3071
+ // fail the current task instead of letting it hang forever
3072
+ terminateWorker$1(workerData);
3073
+ if (rejectResult) {
3074
+ rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
3075
+ if (writer) {
3076
+ writer.releaseLock();
3077
+ }
3078
+ onTaskFinished();
3079
+ }
3080
+ }
3081
+
3082
+ function sendMessage(message, { worker, writer, transferStreams }) {
2955
3083
  try {
2956
3084
  const { value, readable, writable } = message;
2957
3085
  const transferables = [];
@@ -2985,14 +3113,16 @@
2985
3113
  if (writer) {
2986
3114
  writer.releaseLock();
2987
3115
  }
2988
- onTaskFinished();
2989
3116
  throw error;
2990
3117
  }
2991
3118
  }
2992
3119
 
2993
3120
  async function onMessage({ data }, workerData) {
2994
3121
  const { type, value, messageId, result, error } = data;
2995
- const { reader, writer, resolveResult, rejectResult, onTaskFinished } = workerData;
3122
+ const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
3123
+ // continuations below can settle after the worker slot has been reassigned to a new
3124
+ // task (e.g. a pending pull read failing late); they must not touch the new task
3125
+ const stale = () => workerData.generation != generation;
2996
3126
  try {
2997
3127
  if (error) {
2998
3128
  const { message, stack, code, name, outputSize } = error;
@@ -3002,23 +3132,32 @@
3002
3132
  } else {
3003
3133
  if (type == MESSAGE_PULL) {
3004
3134
  const { value, done } = await reader.read();
3005
- sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData);
3135
+ if (!stale()) {
3136
+ sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData);
3137
+ }
3006
3138
  }
3007
3139
  if (type == MESSAGE_DATA) {
3008
3140
  await writer.ready;
3009
3141
  await writer.write(new Uint8Array(value));
3010
- sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData);
3142
+ if (!stale()) {
3143
+ sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData);
3144
+ }
3011
3145
  }
3012
3146
  if (type == MESSAGE_CLOSE) {
3013
3147
  close(null, result);
3014
3148
  }
3015
3149
  }
3016
3150
  } catch (error) {
3017
- sendMessage({ type: MESSAGE_CLOSE, messageId }, workerData);
3018
- close(error);
3151
+ if (!stale()) {
3152
+ terminateWorker$1(workerData);
3153
+ close(error);
3154
+ }
3019
3155
  }
3020
3156
 
3021
3157
  function close(error, result) {
3158
+ if (stale()) {
3159
+ return;
3160
+ }
3022
3161
  if (error) {
3023
3162
  rejectResult(error);
3024
3163
  } else {
@@ -3062,6 +3201,8 @@
3062
3201
 
3063
3202
  let pool = [];
3064
3203
  const pendingRequests = [];
3204
+ let starvationTimeout;
3205
+ let starvationDelay;
3065
3206
 
3066
3207
  let indexWorker = 0;
3067
3208
 
@@ -3088,14 +3229,20 @@
3088
3229
  pool.push(workerData);
3089
3230
  return new CodecWorker(workerData, stream, workerOptions, onTaskFinished);
3090
3231
  } else {
3091
- return new Promise(resolve => pendingRequests.push({ resolve, stream, workerOptions }));
3232
+ return new Promise(resolve => {
3233
+ pendingRequests.push({ resolve, stream, workerOptions });
3234
+ starvationDelay = config.workerStarvationTimeout;
3235
+ armStarvationTimeout();
3236
+ });
3092
3237
  }
3093
3238
  }
3094
3239
 
3095
3240
  function onTaskFinished(workerData) {
3241
+ clearStarvationTimeout();
3096
3242
  if (pendingRequests.length) {
3097
3243
  const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
3098
3244
  resolve(new CodecWorker(workerData, stream, workerOptions, onTaskFinished));
3245
+ armStarvationTimeout();
3099
3246
  } else if (workerData.worker) {
3100
3247
  clearTerminateTimeout(workerData);
3101
3248
  terminateWorker(workerData, workerOptions);
@@ -3105,6 +3252,34 @@
3105
3252
  }
3106
3253
  }
3107
3254
 
3255
+ function armStarvationTimeout() {
3256
+ if (!starvationTimeout && pendingRequests.length && Number.isFinite(starvationDelay) && starvationDelay >= 0) {
3257
+ starvationTimeout = setTimeout(onWorkerStarvation, starvationDelay);
3258
+ }
3259
+ }
3260
+
3261
+ function clearStarvationTimeout() {
3262
+ if (starvationTimeout) {
3263
+ clearTimeout(starvationTimeout);
3264
+ starvationTimeout = null;
3265
+ }
3266
+ }
3267
+
3268
+ function onWorkerStarvation() {
3269
+ starvationTimeout = null;
3270
+ if (pendingRequests.length) {
3271
+ const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
3272
+ const inlineWorkerOptions = Object.assign({}, workerOptions, { useWebWorkers: false, workerURI: UNDEFINED_VALUE });
3273
+ resolve(new CodecWorker({}, stream, inlineWorkerOptions, onInlineTaskFinished));
3274
+ armStarvationTimeout();
3275
+ }
3276
+ }
3277
+
3278
+ function onInlineTaskFinished() {
3279
+ clearStarvationTimeout();
3280
+ armStarvationTimeout();
3281
+ }
3282
+
3108
3283
  function terminateWorker(workerData, workerOptions) {
3109
3284
  const { config } = workerOptions;
3110
3285
  const { terminateWorkerTimeout } = config;
@@ -3162,7 +3337,7 @@
3162
3337
 
3163
3338
  /* global TextDecoder */
3164
3339
 
3165
- const CP437 = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");
3340
+ const CP437 = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");
3166
3341
  const VALID_CP437 = CP437.length == 256;
3167
3342
 
3168
3343
  function decodeCP437(stringValue) {
@@ -3210,7 +3385,8 @@
3210
3385
  if (encoding && encoding.trim().toLowerCase() == "cp437") {
3211
3386
  return decodeCP437(value);
3212
3387
  } else {
3213
- return new TextDecoder(encoding).decode(value);
3388
+ // ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
3389
+ return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
3214
3390
  }
3215
3391
  }
3216
3392
 
@@ -3387,6 +3563,7 @@
3387
3563
  const OPTION_CHECK_PASSWORD_ONLY = "checkPasswordOnly";
3388
3564
  const OPTION_CHECK_OVERLAPPING_ENTRY_ONLY = "checkOverlappingEntryOnly";
3389
3565
  const OPTION_CHECK_OVERLAPPING_ENTRY = "checkOverlappingEntry";
3566
+ const OPTION_CHECK_AMBIGUITY = "checkAmbiguity";
3390
3567
  const OPTION_CHECK_SIGNATURE = "checkSignature";
3391
3568
  const OPTION_USE_WEB_WORKERS = "useWebWorkers";
3392
3569
  const OPTION_USE_COMPRESSION_STREAM = "useCompressionStream";
@@ -3444,12 +3621,16 @@
3444
3621
  const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
3445
3622
  const ERR_ENCRYPTED = "File contains encrypted entry";
3446
3623
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
3447
- const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
3624
+ const ERR_UNSUPPORTED_COMPRESSION$1 = "Compression method not supported";
3448
3625
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
3449
3626
  const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
3627
+ const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
3450
3628
  const CHARSET_UTF8 = "utf-8";
3451
3629
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
3452
3630
  const CHARSET_CP437 = "cp437";
3631
+ // bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
3632
+ // because they are informative only
3633
+ const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
3453
3634
  const ZIP64_PROPERTIES = [
3454
3635
  [PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
3455
3636
  [PROPERTY_NAME_COMPRESSED_SIZE, MAX_32_BITS],
@@ -3474,7 +3655,7 @@
3474
3655
  reader: new GenericReader(reader),
3475
3656
  options,
3476
3657
  config: getConfiguration(),
3477
- readRanges: []
3658
+ readRanges: new Map()
3478
3659
  });
3479
3660
  }
3480
3661
 
@@ -3507,21 +3688,30 @@
3507
3688
  const commentOffset = endOfDirectoryInfo.offset;
3508
3689
  const commentLength = getUint16(endOfDirectoryView, 20);
3509
3690
  const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
3691
+ const checkAmbiguity = getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY);
3692
+ if (checkAmbiguity && appendedDataOffset != reader.size) {
3693
+ throwAmbiguousArchive("appended data");
3694
+ }
3510
3695
  let lastDiskNumber = getUint16(endOfDirectoryView, 4);
3511
3696
  const expectedLastDiskNumber = reader.lastDiskNumber || 0;
3512
3697
  let diskNumber = getUint16(endOfDirectoryView, 6);
3513
- let filesLength = getUint16(endOfDirectoryView, 8);
3698
+ let filesLength = getUint16(endOfDirectoryView, 10);
3514
3699
  let prependedDataLength = 0;
3515
- let startOffset = 0;
3700
+ let startOffset;
3701
+ let zip64EndOfDirectory;
3516
3702
  if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
3517
- const endOfDirectoryLocatorArray = await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH);
3703
+ const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
3704
+ await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
3705
+ new Uint8Array();
3518
3706
  const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
3519
- if (getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
3707
+ if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
3708
+ getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
3520
3709
  directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
3521
3710
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
3522
3711
  let endOfDirectoryView = getDataView$1(endOfDirectoryArray);
3523
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
3524
- if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
3712
+ const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH - (reader.lastDiskOffset || 0);
3713
+ if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
3714
+ directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
3525
3715
  const originalDirectoryDataOffset = directoryDataOffset;
3526
3716
  directoryDataOffset = expectedDirectoryDataOffset;
3527
3717
  if (directoryDataOffset > originalDirectoryDataOffset) {
@@ -3530,24 +3720,36 @@
3530
3720
  endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
3531
3721
  endOfDirectoryView = getDataView$1(endOfDirectoryArray);
3532
3722
  }
3533
- if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
3723
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
3534
3724
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
3535
3725
  }
3726
+ zip64EndOfDirectory = true;
3536
3727
  if (lastDiskNumber == MAX_16_BITS) {
3537
3728
  lastDiskNumber = getUint32(endOfDirectoryView, 16);
3729
+ } else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
3730
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
3538
3731
  }
3539
3732
  if (diskNumber == MAX_16_BITS) {
3540
3733
  diskNumber = getUint32(endOfDirectoryView, 20);
3734
+ } else if (checkAmbiguity && diskNumber != getUint32(endOfDirectoryView, 20)) {
3735
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
3541
3736
  }
3542
3737
  if (filesLength == MAX_16_BITS) {
3543
3738
  filesLength = getBigUint64(endOfDirectoryView, 32);
3739
+ } else if (checkAmbiguity && filesLength != getBigUint64(endOfDirectoryView, 32)) {
3740
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
3544
3741
  }
3545
3742
  if (directoryDataLength == MAX_32_BITS) {
3546
3743
  directoryDataLength = getBigUint64(endOfDirectoryView, 40);
3744
+ } else if (checkAmbiguity && directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
3745
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
3547
3746
  }
3548
- directoryDataOffset -= directoryDataLength;
3747
+ directoryDataOffset = getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
3549
3748
  }
3550
3749
  }
3750
+ const declaredDirectoryDataLength = directoryDataLength;
3751
+ const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
3752
+ (zip64EndOfDirectory ? ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
3551
3753
  if (directoryDataOffset >= reader.size) {
3552
3754
  prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
3553
3755
  directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
@@ -3562,8 +3764,11 @@
3562
3764
  let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
3563
3765
  let directoryView = getDataView$1(directoryArray);
3564
3766
  if (directoryDataLength) {
3565
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - directoryDataLength;
3566
- if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
3767
+ if (directoryArray.length < 4) {
3768
+ throw new Error(ERR_BAD_FORMAT);
3769
+ }
3770
+ const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
3771
+ if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
3567
3772
  const originalDirectoryDataOffset = directoryDataOffset;
3568
3773
  directoryDataOffset = expectedDirectoryDataOffset;
3569
3774
  if (directoryDataOffset > originalDirectoryDataOffset) {
@@ -3573,8 +3778,8 @@
3573
3778
  directoryView = getDataView$1(directoryArray);
3574
3779
  }
3575
3780
  }
3576
- const expectedDirectoryDataLength = endOfDirectoryInfo.offset - directoryDataOffset - (reader.lastDiskOffset || 0);
3577
- if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0) {
3781
+ const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
3782
+ if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
3578
3783
  directoryDataLength = expectedDirectoryDataLength;
3579
3784
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
3580
3785
  directoryView = getDataView$1(directoryArray);
@@ -3582,16 +3787,19 @@
3582
3787
  if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
3583
3788
  throw new Error(ERR_BAD_FORMAT);
3584
3789
  }
3790
+ startOffset = directoryDataOffset;
3585
3791
  const filenameEncoding = getOptionValue$1(zipReader, options, OPTION_FILENAME_ENCODING);
3586
3792
  const commentEncoding = getOptionValue$1(zipReader, options, OPTION_COMMENT_ENCODING);
3793
+ const filenames = checkAmbiguity ? new Set() : UNDEFINED_VALUE;
3794
+ let duplicateFilename;
3587
3795
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
3588
3796
  const fileEntry = new ZipEntry$1(reader, config, zipReader.options);
3589
- if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
3797
+ if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
3590
3798
  throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
3591
3799
  }
3592
3800
  readCommonHeader(fileEntry, directoryView, offset + 6);
3593
3801
  const languageEncodingFlag = Boolean(fileEntry.bitFlag.languageEncodingFlag);
3594
- const filenameOffset = offset + 46;
3802
+ const filenameOffset = offset + CENTRAL_FILE_HEADER_LENGTH;
3595
3803
  const extraFieldOffset = filenameOffset + fileEntry.filenameLength;
3596
3804
  const commentOffset = extraFieldOffset + fileEntry.extraFieldLength;
3597
3805
  const versionMadeBy = getUint16(directoryView, offset + 4);
@@ -3612,7 +3820,7 @@
3612
3820
  directory: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_DIR_MASK),
3613
3821
  archive: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_ARCHIVE_MASK)
3614
3822
  };
3615
- const offsetFileEntry = getUint32(directoryView, offset + 42) + prependedDataLength;
3823
+ const offsetFileEntry = getUint32(directoryView, offset + 42);
3616
3824
  const decode = getOptionValue$1(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
3617
3825
  const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
3618
3826
  const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
@@ -3625,6 +3833,7 @@
3625
3833
  comment = decodeText(rawComment, rawCommentEncoding);
3626
3834
  }
3627
3835
  Object.assign(fileEntry, {
3836
+ index: indexFile,
3628
3837
  versionMadeBy,
3629
3838
  msDosCompatible,
3630
3839
  compressedSize: 0,
@@ -3644,8 +3853,15 @@
3644
3853
  filename,
3645
3854
  comment
3646
3855
  });
3647
- startOffset = Math.max(offsetFileEntry, startOffset);
3648
3856
  readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
3857
+ fileEntry.offset += prependedDataLength;
3858
+ startOffset = Math.min(fileEntry.offset, startOffset);
3859
+ if (checkAmbiguity) {
3860
+ if (filenames.has(fileEntry.filename)) {
3861
+ duplicateFilename = true;
3862
+ }
3863
+ filenames.add(fileEntry.filename);
3864
+ }
3649
3865
  const unixExternalUpper = (fileEntry.externalFileAttributes >> 16) & MAX_16_BITS;
3650
3866
  if (fileEntry.unixMode === UNDEFINED_VALUE && (unixExternalUpper & (FILE_ATTR_UNIX_DEFAULT_MASK | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_TYPE_DIR)) != 0) {
3651
3867
  fileEntry.unixMode = unixExternalUpper;
@@ -3666,7 +3882,7 @@
3666
3882
  internalFileAttribute: fileEntry.internalFileAttributes,
3667
3883
  externalFileAttribute: fileEntry.externalFileAttributes,
3668
3884
  executable,
3669
- directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
3885
+ directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (fileEntry.filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
3670
3886
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
3671
3887
  });
3672
3888
  const entry = new Entry(fileEntry);
@@ -3689,6 +3905,15 @@
3689
3905
  }
3690
3906
  yield entry;
3691
3907
  }
3908
+ if (checkAmbiguity && offset != declaredDirectoryDataLength) {
3909
+ throwAmbiguousArchive("trailing central directory data");
3910
+ }
3911
+ if (duplicateFilename) {
3912
+ throwAmbiguousArchive("duplicate filename");
3913
+ }
3914
+ if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > 0))) {
3915
+ throwAmbiguousArchive("prepended data");
3916
+ }
3692
3917
  const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
3693
3918
  const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
3694
3919
  if (extractPrependedData) {
@@ -3727,6 +3952,7 @@
3727
3952
  const zipEntry = this;
3728
3953
  const {
3729
3954
  reader,
3955
+ index,
3730
3956
  offset,
3731
3957
  diskNumberStart,
3732
3958
  extraFieldAES,
@@ -3752,27 +3978,42 @@
3752
3978
  rawPassword = rawPassword && rawPassword.length && rawPassword;
3753
3979
  if (extraFieldAES) {
3754
3980
  if (extraFieldAES.originalCompressionMethod != COMPRESSION_METHOD_AES) {
3755
- throw new Error(ERR_UNSUPPORTED_COMPRESSION);
3981
+ throw new Error(ERR_UNSUPPORTED_COMPRESSION$1);
3756
3982
  }
3757
3983
  }
3758
3984
  if ((compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64) && !passThrough) {
3759
- throw new Error(ERR_UNSUPPORTED_COMPRESSION);
3985
+ throw new Error(ERR_UNSUPPORTED_COMPRESSION$1);
3760
3986
  }
3761
- if (getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
3987
+ if (dataArray.length < HEADER_SIZE || getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
3762
3988
  throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
3763
3989
  }
3764
3990
  readCommonHeader(localDirectory, dataView, 4);
3765
3991
  const {
3766
3992
  extraFieldLength,
3767
- filenameLength,
3768
- lastAccessDate,
3769
- creationDate
3993
+ filenameLength
3770
3994
  } = localDirectory;
3771
- localDirectory.rawExtraField = extraFieldLength ?
3772
- await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
3773
- new Uint8Array();
3995
+ const checkAmbiguity = getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY);
3996
+ let rawLocalFilename = new Uint8Array();
3997
+ if (checkAmbiguity && (filenameLength || extraFieldLength)) {
3998
+ const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
3999
+ rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
4000
+ localDirectory.rawExtraField = trailingDataArray.subarray(filenameLength);
4001
+ } else {
4002
+ localDirectory.rawExtraField = extraFieldLength ?
4003
+ await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
4004
+ new Uint8Array();
4005
+ }
3774
4006
  readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
3775
- Object.assign(fileEntry, { lastAccessDate, creationDate });
4007
+ if (checkAmbiguity) {
4008
+ checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename);
4009
+ }
4010
+ const { lastAccessDate, creationDate } = localDirectory;
4011
+ if (lastAccessDate) {
4012
+ fileEntry.lastAccessDate = lastAccessDate;
4013
+ }
4014
+ if (creationDate) {
4015
+ fileEntry.creationDate = creationDate;
4016
+ }
3776
4017
  const encrypted = zipEntry.encrypted && localDirectory.encrypted && !passThrough;
3777
4018
  const zipCrypto = encrypted && !extraFieldAES;
3778
4019
  if (!passThrough) {
@@ -3818,7 +4059,7 @@
3818
4059
  outputSize: passThrough ? compressedSize : uncompressedSize,
3819
4060
  signature,
3820
4061
  compressed: compressionMethod != 0 && !passThrough,
3821
- encrypted: zipEntry.encrypted && !passThrough,
4062
+ encrypted,
3822
4063
  useWebWorkers: getOptionValue$1(zipEntry, options, OPTION_USE_WEB_WORKERS),
3823
4064
  useCompressionStream,
3824
4065
  transferStreams: getOptionValue$1(zipEntry, options, OPTION_TRANSFER_STREAMS),
@@ -3832,6 +4073,7 @@
3832
4073
  await detectOverlappingEntry({
3833
4074
  reader,
3834
4075
  fileEntry,
4076
+ index,
3835
4077
  offset,
3836
4078
  diskNumberStart,
3837
4079
  signature,
@@ -3934,7 +4176,7 @@
3934
4176
  directory.extraFieldUnicodeComment = extraFieldUnicodeComment;
3935
4177
  }
3936
4178
  const extraFieldAES = extraField.get(EXTRAFIELD_TYPE_AES);
3937
- if (extraFieldAES) {
4179
+ if (extraFieldAES && extraFieldAES.data.length >= 7) {
3938
4180
  readExtraFieldAES(extraFieldAES, directory, compressionMethod);
3939
4181
  directory.extraFieldAES = extraFieldAES;
3940
4182
  } else {
@@ -3971,19 +4213,23 @@
3971
4213
  directory.zip64 = true;
3972
4214
  const extraFieldView = getDataView$1(extraFieldZip64.data);
3973
4215
  const missingProperties = ZIP64_PROPERTIES.filter(([propertyName, max]) => directory[propertyName] == max);
4216
+ const requiredLength = missingProperties.reduce((length, [, max]) => length + ZIP64_EXTRACTION[max].bytes, 0);
4217
+ if (extraFieldZip64.data.length < requiredLength) {
4218
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
4219
+ }
3974
4220
  for (let indexMissingProperty = 0, offset = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
3975
4221
  const [propertyName, max] = missingProperties[indexMissingProperty];
3976
- if (directory[propertyName] == max) {
3977
- const extraction = ZIP64_EXTRACTION[max];
3978
- directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
3979
- offset += extraction.bytes;
3980
- } else if (extraFieldZip64[propertyName]) {
3981
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
3982
- }
4222
+ const extraction = ZIP64_EXTRACTION[max];
4223
+ directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
4224
+ offset += extraction.bytes;
3983
4225
  }
3984
4226
  }
3985
4227
 
3986
4228
  function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
4229
+ if (extraFieldUnicode.data.length < 5) {
4230
+ extraFieldUnicode.valid = false;
4231
+ return;
4232
+ }
3987
4233
  const extraFieldView = getDataView$1(extraFieldUnicode.data);
3988
4234
  const crc32 = new Crc32();
3989
4235
  crc32.append(fileEntry[rawPropertyName]);
@@ -4056,32 +4302,30 @@
4056
4302
  function readExtraFieldUnix(extraField, directory, isInfoZip) {
4057
4303
  try {
4058
4304
  const view = getDataView$1(new Uint8Array(extraField.data));
4059
- let offset = 0;
4060
- const version = getUint8(view, offset++);
4061
- const uidSize = getUint8(view, offset++);
4062
- const uidBytes = extraField.data.subarray(offset, offset + uidSize);
4063
- offset += uidSize;
4064
- const uid = unpackUnixId(uidBytes);
4065
- const gidSize = getUint8(view, offset++);
4066
- const gidBytes = extraField.data.subarray(offset, offset + gidSize);
4067
- offset += gidSize;
4068
- const gid = unpackUnixId(gidBytes);
4069
- let unixMode = UNDEFINED_VALUE;
4070
- if (!isInfoZip && offset + 2 <= extraField.data.length) {
4071
- const base = extraField.data;
4072
- const modeView = new DataView(base.buffer, base.byteOffset + offset, 2);
4073
- unixMode = modeView.getUint16(0, true);
4074
- }
4075
- Object.assign(extraField, { version, uid, gid, unixMode });
4305
+ let uid, gid;
4306
+ if (isInfoZip) {
4307
+ // Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
4308
+ let offset = 0;
4309
+ const version = getUint8(view, offset++);
4310
+ const uidSize = getUint8(view, offset++);
4311
+ uid = unpackUnixId(extraField.data.subarray(offset, offset + uidSize));
4312
+ offset += uidSize;
4313
+ const gidSize = getUint8(view, offset++);
4314
+ gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
4315
+ Object.assign(extraField, { version, uid, gid });
4316
+ } else if (extraField.data.length >= 4) {
4317
+ // Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
4318
+ // carried in the external file attributes, not here)
4319
+ uid = getUint16(view, 0);
4320
+ gid = getUint16(view, 2);
4321
+ Object.assign(extraField, { uid, gid });
4322
+ }
4076
4323
  if (uid !== UNDEFINED_VALUE) {
4077
4324
  directory.uid = uid;
4078
4325
  }
4079
4326
  if (gid !== UNDEFINED_VALUE) {
4080
4327
  directory.gid = gid;
4081
4328
  }
4082
- if (unixMode !== UNDEFINED_VALUE) {
4083
- directory.unixMode = unixMode;
4084
- }
4085
4329
  } catch {
4086
4330
  // ignored
4087
4331
  }
@@ -4095,6 +4339,9 @@
4095
4339
  }
4096
4340
 
4097
4341
  function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory) {
4342
+ if (!extraFieldExtendedTimestamp.data.length) {
4343
+ return;
4344
+ }
4098
4345
  const extraFieldView = getDataView$1(extraFieldExtendedTimestamp.data);
4099
4346
  const flags = getUint8(extraFieldView, 0);
4100
4347
  const timeProperties = [];
@@ -4120,7 +4367,8 @@
4120
4367
  timeProperties.forEach((propertyName, indexProperty) => {
4121
4368
  if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
4122
4369
  const time = getUint32(extraFieldView, offset);
4123
- directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date(time * 1000);
4370
+ // the extended timestamp is a signed 32-bit time_t value
4371
+ directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
4124
4372
  const rawPropertyName = timeRawProperties[indexProperty];
4125
4373
  extraFieldExtendedTimestamp[rawPropertyName] = time;
4126
4374
  }
@@ -4131,6 +4379,7 @@
4131
4379
  async function detectOverlappingEntry({
4132
4380
  reader,
4133
4381
  fileEntry,
4382
+ index,
4134
4383
  offset,
4135
4384
  diskNumberStart,
4136
4385
  signature,
@@ -4142,10 +4391,9 @@
4142
4391
  readRanges
4143
4392
  }) {
4144
4393
  let diskOffset = 0;
4145
- if (diskNumberStart) {
4146
- for (let indexReader = 0; indexReader < diskNumberStart; indexReader++) {
4147
- const diskReader = reader.readers[indexReader];
4148
- diskOffset += diskReader.size;
4394
+ if (diskNumberStart && reader.readers) {
4395
+ for (let indexReader = 0; indexReader < Math.min(diskNumberStart, reader.readers.length); indexReader++) {
4396
+ diskOffset += reader.readers[indexReader].size;
4149
4397
  }
4150
4398
  }
4151
4399
  let dataDescriptorLength = 0;
@@ -4158,7 +4406,8 @@
4158
4406
  }
4159
4407
  if (dataDescriptorLength) {
4160
4408
  const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH, diskNumberStart);
4161
- const dataDescriptorSignature = getUint32(getDataView$1(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
4409
+ const dataDescriptorSignature = dataDescriptorArray.length == dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH &&
4410
+ getUint32(getDataView$1(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
4162
4411
  if (dataDescriptorSignature) {
4163
4412
  const readSignature = getUint32(getDataView$1(dataDescriptorArray), 4);
4164
4413
  let readCompressedSize;
@@ -4183,14 +4432,14 @@
4183
4432
  end: diskOffset + dataOffset + compressedSize + dataDescriptorLength,
4184
4433
  fileEntry
4185
4434
  };
4186
- for (const otherRange of readRanges) {
4187
- if (otherRange.fileEntry != fileEntry && range.start >= otherRange.start && range.start < otherRange.end) {
4435
+ for (const [otherIndex, otherRange] of readRanges) {
4436
+ if (otherIndex != index && range.start < otherRange.end && otherRange.start < range.end) {
4188
4437
  const error = new Error(ERR_OVERLAPPING_ENTRY);
4189
4438
  error.overlappingEntry = otherRange.fileEntry;
4190
4439
  throw error;
4191
4440
  }
4192
4441
  }
4193
- readRanges.push(range);
4442
+ readRanges.set(index, range);
4194
4443
  }
4195
4444
 
4196
4445
  async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
@@ -4215,6 +4464,35 @@
4215
4464
  }
4216
4465
  }
4217
4466
 
4467
+ function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
4468
+ const { rawFilename } = zipEntry;
4469
+ if (rawLocalFilename.length != rawFilename.length ||
4470
+ rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte])) {
4471
+ throwAmbiguousArchive("mismatched local file header (filename)");
4472
+ }
4473
+ if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
4474
+ throwAmbiguousArchive("mismatched local file header (general purpose bit flag)");
4475
+ }
4476
+ if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
4477
+ throwAmbiguousArchive("mismatched local file header (compression method)");
4478
+ }
4479
+ // when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
4480
+ // also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
4481
+ if (!localDirectory.bitFlag.dataDescriptor &&
4482
+ (localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
4483
+ (localDirectory.signature != zipEntry.signature ||
4484
+ localDirectory.compressedSize != zipEntry.compressedSize ||
4485
+ localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
4486
+ throwAmbiguousArchive("mismatched local file header (signature or sizes)");
4487
+ }
4488
+ }
4489
+
4490
+ function throwAmbiguousArchive(reason) {
4491
+ const error = new Error(ERR_AMBIGUOUS_ARCHIVE);
4492
+ error.reason = reason;
4493
+ throw error;
4494
+ }
4495
+
4218
4496
  function getOptionValue$1(zipReader, options, name) {
4219
4497
  return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
4220
4498
  }
@@ -4253,7 +4531,7 @@
4253
4531
  }
4254
4532
 
4255
4533
  function getDataView$1(array) {
4256
- return new DataView(array.buffer);
4534
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
4257
4535
  }
4258
4536
 
4259
4537
  /*
@@ -4293,13 +4571,19 @@
4293
4571
  const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
4294
4572
  const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
4295
4573
  const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
4574
+ // same message as the constant exported by zip-reader.js
4575
+ const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
4576
+ const MIN_UNIX_TIME = -2147483648;
4577
+ const MAX_UNIX_TIME = 2147483647;
4296
4578
  const ERR_UNSUPPORTED_FORMAT = "Zip64 is not supported (set the 'zip64' option to 'true')";
4297
4579
  const ERR_UNDEFINED_UNCOMPRESSED_SIZE = "Undefined uncompressed size";
4580
+ const ERR_UNDEFINED_READER = "Undefined reader";
4298
4581
  const ERR_ZIP_NOT_EMPTY = "Zip file not empty";
4299
4582
  const ERR_INVALID_UID = "Invalid uid (must be integer 0..2^32-1)";
4300
4583
  const ERR_INVALID_GID = "Invalid gid (must be integer 0..2^32-1)";
4301
4584
  const ERR_INVALID_UNIX_MODE = "Invalid UNIX mode (must be integer 0..65535)";
4302
4585
  const ERR_INVALID_UNIX_EXTRA_FIELD_TYPE = "Invalid unixExtraFieldType (must be 'infozip' or 'unix')";
4586
+ const ERR_INVALID_UNIX_ID_SIZE = "uid/gid must be 0..65535 for unixExtraFieldType 'unix' (use 'infozip' for larger ids)";
4303
4587
  const ERR_INVALID_MSDOS_ATTRIBUTES = "Invalid msdosAttributesRaw (must be integer 0..255)";
4304
4588
  const ERR_INVALID_MSDOS_DATA = "Invalid msdosAttributes (must be an object with boolean flags)";
4305
4589
 
@@ -4325,9 +4609,10 @@
4325
4609
  files: new Map(),
4326
4610
  filenames: new Set(),
4327
4611
  offset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? writer.size || writer.writable.size || 0 : options[OPTION_OFFSET],
4328
- pendingEntriesSize: 0,
4612
+ initialOffset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? 0 : options[OPTION_OFFSET] - (writer.size || writer.writable.size || 0),
4329
4613
  pendingAddFileCalls: new Set(),
4330
- bufferedWrites: 0
4614
+ bufferedWrites: 0,
4615
+ lastFileEntry: UNDEFINED_VALUE
4331
4616
  });
4332
4617
  }
4333
4618
 
@@ -4336,17 +4621,18 @@
4336
4621
  throw new Error(ERR_ZIP_NOT_EMPTY);
4337
4622
  }
4338
4623
  reader = new GenericReader(reader);
4624
+ await initStream(reader);
4339
4625
  const zipReader = new ZipReader(reader.readable);
4340
4626
  const entries = await zipReader.getEntries();
4341
4627
  await zipReader.close();
4628
+ await initStream(this.writer);
4342
4629
  await reader.readable.pipeTo(this.writer.writable, { preventClose: true, preventAbort: true });
4343
4630
  this.writer.size = this.offset = reader.size;
4344
4631
  this.filenames = new Set(entries.map(entry => entry.filename));
4345
4632
  this.files = new Map(entries.map(entry => {
4346
4633
  const {
4347
4634
  version,
4348
- compressionMethod,
4349
- lastModDate,
4635
+ rawLastModDate,
4350
4636
  lastAccessDate,
4351
4637
  creationDate,
4352
4638
  rawFilename,
@@ -4359,6 +4645,7 @@
4359
4645
  zip64
4360
4646
  } = entry;
4361
4647
  let {
4648
+ compressionMethod,
4362
4649
  rawExtraFieldZip64,
4363
4650
  rawExtraFieldAES,
4364
4651
  rawExtraFieldExtendedTimestamp,
@@ -4371,31 +4658,39 @@
4371
4658
  rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
4372
4659
  rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
4373
4660
  rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
4374
- rawExtraFieldUnix = entry.rawExtraFieldUnix || new Uint8Array();
4661
+ rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
4375
4662
  rawExtraField = rawExtraField || new Uint8Array();
4663
+ if (entry.extraFieldAES) {
4664
+ compressionMethod = COMPRESSION_METHOD_AES;
4665
+ }
4376
4666
  const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
4377
- const zip64UncompressedSize = zip64 && uncompressedSize > MAX_32_BITS;
4378
- const zip64CompressedSize = zip64 && compressedSize > MAX_32_BITS;
4667
+ const zip64UncompressedSize = zip64 && uncompressedSize >= MAX_32_BITS;
4668
+ const zip64CompressedSize = zip64 && compressedSize >= MAX_32_BITS;
4669
+ const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
4379
4670
  const {
4380
4671
  headerArray,
4381
4672
  headerView
4382
4673
  } = getHeaderArrayData({
4383
4674
  version,
4384
- bitFlag: getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod),
4675
+ bitFlag: bitFlagValue,
4385
4676
  compressionMethod,
4386
4677
  uncompressedSize,
4387
4678
  compressedSize,
4388
- lastModDate,
4679
+ rawLastModDate,
4389
4680
  rawFilename,
4390
4681
  zip64CompressedSize,
4391
4682
  zip64UncompressedSize,
4392
4683
  extraFieldLength
4393
4684
  });
4685
+ const { signature } = entry;
4686
+ if (signature !== UNDEFINED_VALUE) {
4687
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, signature);
4688
+ }
4394
4689
  Object.assign(entry, {
4395
4690
  zip64UncompressedSize,
4396
4691
  zip64CompressedSize,
4397
- zip64Offset: zip64 && this.offset - diskOffset > MAX_32_BITS,
4398
- zip64DiskNumberStart: zip64 && diskNumber > MAX_16_BITS,
4692
+ zip64Offset: zip64 && this.offset - diskOffset >= MAX_32_BITS,
4693
+ zip64DiskNumberStart: zip64 && diskNumber >= MAX_16_BITS,
4399
4694
  rawExtraFieldZip64,
4400
4695
  rawExtraFieldAES,
4401
4696
  rawExtraFieldExtendedTimestamp,
@@ -4413,6 +4708,7 @@
4413
4708
 
4414
4709
  async add(name = "", reader, options = {}) {
4415
4710
  const zipWriter = this;
4711
+ options = Object.assign({}, options);
4416
4712
  const {
4417
4713
  pendingAddFileCalls,
4418
4714
  config
@@ -4423,17 +4719,24 @@
4423
4719
  await new Promise(resolve => pendingEntries.push(resolve));
4424
4720
  }
4425
4721
  let promiseAddFile;
4722
+ let nameAdded;
4426
4723
  try {
4427
4724
  name = name.trim();
4725
+ if (getOptionValue(zipWriter, options, PROPERTY_NAME_DIRECTORY) && !name.endsWith(DIRECTORY_SIGNATURE)) {
4726
+ name += DIRECTORY_SIGNATURE;
4727
+ }
4428
4728
  if (zipWriter.filenames.has(name)) {
4429
4729
  throw new Error(ERR_DUPLICATED_NAME);
4430
4730
  }
4431
4731
  zipWriter.filenames.add(name);
4732
+ nameAdded = true;
4432
4733
  promiseAddFile = addFile(zipWriter, name, reader, options);
4433
4734
  pendingAddFileCalls.add(promiseAddFile);
4434
4735
  return await promiseAddFile;
4435
4736
  } catch (error) {
4436
- zipWriter.filenames.delete(name);
4737
+ if (nameAdded) {
4738
+ zipWriter.filenames.delete(name);
4739
+ }
4437
4740
  throw error;
4438
4741
  } finally {
4439
4742
  pendingAddFileCalls.delete(promiseAddFile);
@@ -4466,6 +4769,9 @@
4466
4769
  const zipWriter = this;
4467
4770
  const { pendingAddFileCalls, writer } = this;
4468
4771
  const { writable } = writer;
4772
+ if (getLength(comment) > MAX_16_BITS) {
4773
+ throw new Error(ERR_INVALID_COMMENT);
4774
+ }
4469
4775
  while (pendingAddFileCalls.size) {
4470
4776
  await Promise.allSettled(Array.from(pendingAddFileCalls));
4471
4777
  }
@@ -4479,6 +4785,37 @@
4479
4785
  }
4480
4786
 
4481
4787
  async function addFile(zipWriter, name, reader, options) {
4788
+ const attributesInfo = resolveAttributes(zipWriter, name, options);
4789
+ ({ name } = attributesInfo);
4790
+ const metadataInfo = resolveMetadata(zipWriter, name, options);
4791
+ const { comment } = metadataInfo;
4792
+ const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
4793
+ zipWriter.files.set(name, UNDEFINED_VALUE);
4794
+ let fileEntry;
4795
+ try {
4796
+ const sizesInfo = await resolveSizes(zipWriter, reader, metadataInfo, options);
4797
+ ({ reader } = sizesInfo);
4798
+ const { diskOffset, diskNumber } = zipWriter.writer;
4799
+ options = Object.assign({}, options, attributesInfo.resolvedOptions, metadataInfo.resolvedOptions, sizesInfo.resolvedOptions, {
4800
+ internalFileAttribute: metadataInfo.resolvedOptions.internalFileAttributes,
4801
+ externalFileAttribute: attributesInfo.resolvedOptions.externalFileAttributes,
4802
+ signature: options[PROPERTY_NAME_SIGNATURE],
4803
+ offset: zipWriter.offset - diskOffset,
4804
+ diskNumberStart: diskNumber
4805
+ });
4806
+ const headerInfo = getHeaderInfo(options);
4807
+ const dataDescriptorInfo = getDataDescriptorInfo(options);
4808
+ const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
4809
+ fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
4810
+ } catch (error) {
4811
+ zipWriter.files.delete(name);
4812
+ throw error;
4813
+ }
4814
+ Object.assign(fileEntry, { name, comment, extraField });
4815
+ return new Entry(fileEntry);
4816
+ }
4817
+
4818
+ function resolveAttributes(zipWriter, name, options) {
4482
4819
  name = name.trim();
4483
4820
  let msDosCompatible = getOptionValue(zipWriter, options, PROPERTY_NAME_MS_DOS_COMPATIBLE);
4484
4821
  let versionMadeBy = getOptionValue(zipWriter, options, PROPERTY_NAME_VERSION_MADE_BY, msDosCompatible ? 20 : 768);
@@ -4486,7 +4823,7 @@
4486
4823
  const uid = getOptionValue(zipWriter, options, PROPERTY_NAME_UID);
4487
4824
  const gid = getOptionValue(zipWriter, options, PROPERTY_NAME_GID);
4488
4825
  let unixMode = getOptionValue(zipWriter, options, PROPERTY_NAME_UNIX_MODE);
4489
- const unixExtraFieldType = getOptionValue(zipWriter, options, OPTION_UNIX_EXTRA_FIELD_TYPE);
4826
+ let unixExtraFieldType = getOptionValue(zipWriter, options, OPTION_UNIX_EXTRA_FIELD_TYPE);
4490
4827
  let setuid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETUID);
4491
4828
  let setgid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETGID);
4492
4829
  let sticky = getOptionValue(zipWriter, options, PROPERTY_NAME_STICKY);
@@ -4502,6 +4839,13 @@
4502
4839
  if (unixExtraFieldType !== UNDEFINED_VALUE && unixExtraFieldType !== INFOZIP_EXTRA_FIELD_TYPE && unixExtraFieldType !== UNIX_EXTRA_FIELD_TYPE) {
4503
4840
  throw new Error(ERR_INVALID_UNIX_EXTRA_FIELD_TYPE);
4504
4841
  }
4842
+ if (unixExtraFieldType === UNIX_EXTRA_FIELD_TYPE &&
4843
+ ((uid !== UNDEFINED_VALUE && uid > MAX_16_BITS) || (gid !== UNDEFINED_VALUE && gid > MAX_16_BITS))) {
4844
+ throw new Error(ERR_INVALID_UNIX_ID_SIZE);
4845
+ }
4846
+ if (unixExtraFieldType === UNDEFINED_VALUE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
4847
+ unixExtraFieldType = INFOZIP_EXTRA_FIELD_TYPE;
4848
+ }
4505
4849
  let msdosAttributesRaw = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES_RAW);
4506
4850
  let msdosAttributes = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES);
4507
4851
  const hasUnixMetadata = uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE || unixMode !== UNDEFINED_VALUE || unixExtraFieldType;
@@ -4572,6 +4916,27 @@
4572
4916
  if (hasMsDosProvided) {
4573
4917
  externalFileAttributes = (externalFileAttributes & MAX_32_BITS) | (msdosAttributesRaw & MAX_8_BITS);
4574
4918
  }
4919
+ return {
4920
+ name,
4921
+ resolvedOptions: {
4922
+ versionMadeBy,
4923
+ msDosCompatible,
4924
+ externalFileAttributes,
4925
+ unixExternalUpper,
4926
+ uid,
4927
+ gid,
4928
+ unixMode,
4929
+ unixExtraFieldType,
4930
+ setuid,
4931
+ setgid,
4932
+ sticky,
4933
+ msdosAttributesRaw,
4934
+ msdosAttributes
4935
+ }
4936
+ };
4937
+ }
4938
+
4939
+ function resolveMetadata(zipWriter, name, options) {
4575
4940
  const encode = getOptionValue(zipWriter, options, OPTION_ENCODE_TEXT, encodeText);
4576
4941
  let rawFilename = encode(name);
4577
4942
  if (rawFilename === UNDEFINED_VALUE) {
@@ -4610,10 +4975,14 @@
4610
4975
  const transferStreams = getOptionValue(zipWriter, options, OPTION_TRANSFER_STREAMS, true);
4611
4976
  const bufferedWrite = getOptionValue(zipWriter, options, OPTION_BUFFERED_WRITE);
4612
4977
  const createTempStream = getOptionValue(zipWriter, options, OPTION_CREATE_TEMP_STREAM);
4613
- const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE, false);
4978
+ const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE, true);
4614
4979
  const signal = getOptionValue(zipWriter, options, OPTION_SIGNAL);
4615
4980
  const useUnicodeFileNames = getOptionValue(zipWriter, options, OPTION_USE_UNICODE_FILE_NAMES, true);
4616
4981
  const compressionMethod = getOptionValue(zipWriter, options, PROPERTY_NAME_COMPRESSION_METHOD);
4982
+ if (!passThrough && compressionMethod !== UNDEFINED_VALUE &&
4983
+ compressionMethod !== COMPRESSION_METHOD_STORE && compressionMethod !== COMPRESSION_METHOD_DEFLATE) {
4984
+ throw new Error(ERR_UNSUPPORTED_COMPRESSION);
4985
+ }
4617
4986
  let level = getOptionValue(zipWriter, options, OPTION_LEVEL);
4618
4987
  let useCompressionStream = getOptionValue(zipWriter, options, OPTION_USE_COMPRESSION_STREAM);
4619
4988
  let dataDescriptor = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR);
@@ -4629,7 +4998,7 @@
4629
4998
  if (!useCompressionStream && (zipWriter.config.CompressionStream === UNDEFINED_VALUE && zipWriter.config.CompressionStreamZlib === UNDEFINED_VALUE)) {
4630
4999
  level = 0;
4631
5000
  }
4632
- let zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
5001
+ const zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
4633
5002
  if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
4634
5003
  throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
4635
5004
  }
@@ -4640,6 +5009,7 @@
4640
5009
  let offset = 0;
4641
5010
  extraField.forEach(data => extraFieldSize += 4 + getLength(data));
4642
5011
  rawExtraField = new Uint8Array(extraFieldSize);
5012
+ const rawExtraFieldView = getDataView(rawExtraField);
4643
5013
  extraField.forEach((data, type) => {
4644
5014
  if (type > MAX_16_BITS) {
4645
5015
  throw new Error(ERR_INVALID_EXTRAFIELD_TYPE);
@@ -4647,22 +5017,72 @@
4647
5017
  if (getLength(data) > MAX_16_BITS) {
4648
5018
  throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
4649
5019
  }
4650
- arraySet(rawExtraField, new Uint16Array([type]), offset);
4651
- arraySet(rawExtraField, new Uint16Array([getLength(data)]), offset + 2);
5020
+ setUint16(rawExtraFieldView, offset, type);
5021
+ setUint16(rawExtraFieldView, offset + 2, getLength(data));
4652
5022
  arraySet(rawExtraField, data, offset + 4);
4653
5023
  offset += 4 + getLength(data);
4654
5024
  });
4655
5025
  }
5026
+ return {
5027
+ comment,
5028
+ resolvedOptions: {
5029
+ rawFilename,
5030
+ rawComment,
5031
+ version,
5032
+ lastModDate,
5033
+ lastAccessDate,
5034
+ creationDate,
5035
+ internalFileAttributes,
5036
+ passThrough,
5037
+ password,
5038
+ rawPassword,
5039
+ encryptionStrength,
5040
+ zipCrypto,
5041
+ extendedTimestamp,
5042
+ keepOrder,
5043
+ useWebWorkers,
5044
+ transferStreams,
5045
+ bufferedWrite,
5046
+ createTempStream,
5047
+ dataDescriptorSignature,
5048
+ signal,
5049
+ useUnicodeFileNames,
5050
+ compressionMethod,
5051
+ level,
5052
+ useCompressionStream,
5053
+ dataDescriptor,
5054
+ zip64,
5055
+ rawExtraField
5056
+ }
5057
+ };
5058
+ }
5059
+
5060
+ async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, options) {
5061
+ const { passThrough, zipCrypto, password, rawPassword, encryptionStrength } = metadata;
5062
+ let { dataDescriptor, zip64, level, compressionMethod } = metadata;
4656
5063
  let maximumCompressedSize = 0;
4657
- let maximumEntrySize = 0;
4658
5064
  let uncompressedSize = 0;
4659
5065
  if (passThrough) {
5066
+ // the headers of a passThrough entry describe the payload verbatim: without a
5067
+ // reader they would declare content that is not there
5068
+ if (!reader) {
5069
+ throw new Error(ERR_UNDEFINED_READER);
5070
+ }
4660
5071
  uncompressedSize = options[PROPERTY_NAME_UNCOMPRESSED_SIZE];
4661
5072
  if (uncompressedSize === UNDEFINED_VALUE) {
4662
5073
  throw new Error(ERR_UNDEFINED_UNCOMPRESSED_SIZE);
4663
5074
  }
4664
5075
  }
4665
5076
  const zip64Enabled = zip64 === true;
5077
+ const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
5078
+ // entries without a reader have no payload: write them as empty stored files instead of
5079
+ // declaring a compressed or encrypted content that does not exist
5080
+ const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
5081
+ if (!reader) {
5082
+ level = 0;
5083
+ compressionMethod = COMPRESSION_METHOD_STORE;
5084
+ }
5085
+ const encryptionOverhead = encryptedEntry ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
4666
5086
  if (reader) {
4667
5087
  reader = new GenericReader(reader);
4668
5088
  await initStream(reader);
@@ -4675,16 +5095,15 @@
4675
5095
  }
4676
5096
  } else {
4677
5097
  options.uncompressedSize = uncompressedSize = reader.size;
4678
- maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
5098
+ maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
4679
5099
  }
4680
5100
  } else {
4681
5101
  options.uncompressedSize = uncompressedSize;
4682
- maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
5102
+ maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
4683
5103
  }
4684
5104
  }
4685
- const { diskOffset, diskNumber } = zipWriter.writer;
4686
- const zip64UncompressedSize = zip64Enabled || uncompressedSize > MAX_32_BITS;
4687
- const zip64CompressedSize = zip64Enabled || maximumCompressedSize > MAX_32_BITS;
5105
+ const zip64UncompressedSize = zip64Enabled || uncompressedSize >= MAX_32_BITS;
5106
+ const zip64CompressedSize = zip64Enabled || maximumCompressedSize >= MAX_32_BITS;
4688
5107
  if (zip64UncompressedSize || zip64CompressedSize) {
4689
5108
  if (zip64 === false) {
4690
5109
  throw new Error(ERR_UNSUPPORTED_FORMAT);
@@ -4693,73 +5112,19 @@
4693
5112
  }
4694
5113
  }
4695
5114
  zip64 = zip64 || false;
4696
- const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
4697
- options = Object.assign({}, options, {
4698
- rawFilename,
4699
- rawComment,
4700
- version,
4701
- versionMadeBy,
4702
- lastModDate,
4703
- lastAccessDate,
4704
- creationDate,
4705
- rawExtraField,
4706
- zip64,
4707
- zip64UncompressedSize,
4708
- zip64CompressedSize,
4709
- password,
4710
- rawPassword,
4711
- level,
4712
- useWebWorkers,
4713
- transferStreams,
4714
- encryptionStrength,
4715
- extendedTimestamp,
4716
- zipCrypto,
4717
- bufferedWrite,
4718
- createTempStream,
4719
- keepOrder,
4720
- useUnicodeFileNames,
4721
- dataDescriptor,
4722
- dataDescriptorSignature,
4723
- signal,
4724
- msDosCompatible,
4725
- internalFileAttribute: internalFileAttributes,
4726
- internalFileAttributes,
4727
- externalFileAttribute: externalFileAttributes,
4728
- externalFileAttributes,
4729
- useCompressionStream,
4730
- passThrough,
4731
- encrypted: Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted),
4732
- signature: options[PROPERTY_NAME_SIGNATURE],
4733
- compressionMethod,
4734
- uncompressedSize,
4735
- offset: zipWriter.offset - diskOffset,
4736
- diskNumberStart: diskNumber,
4737
- uid,
4738
- gid,
4739
- setuid,
4740
- setgid,
4741
- sticky,
4742
- unixMode,
4743
- msdosAttributesRaw,
4744
- msdosAttributes,
4745
- unixExternalUpper
4746
- });
4747
- const headerInfo = getHeaderInfo(options);
4748
- const dataDescriptorInfo = getDataDescriptorInfo(options);
4749
- const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
4750
- maximumEntrySize = metadataSize + maximumCompressedSize;
4751
- if (zipWriter.options[OPTION_USDZ]) {
4752
- maximumEntrySize += maximumEntrySize + 64;
4753
- }
4754
- zipWriter.pendingEntriesSize += maximumEntrySize;
4755
- let fileEntry;
4756
- try {
4757
- fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
4758
- } finally {
4759
- zipWriter.pendingEntriesSize -= maximumEntrySize;
4760
- }
4761
- Object.assign(fileEntry, { name, comment, extraField });
4762
- return new Entry(fileEntry);
5115
+ return {
5116
+ reader,
5117
+ resolvedOptions: {
5118
+ dataDescriptor,
5119
+ zip64,
5120
+ zip64UncompressedSize,
5121
+ zip64CompressedSize,
5122
+ uncompressedSize,
5123
+ level,
5124
+ compressionMethod,
5125
+ encrypted: encryptedEntry
5126
+ }
5127
+ };
4763
5128
  }
4764
5129
 
4765
5130
  async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
@@ -4776,37 +5141,40 @@
4776
5141
  headerInfo
4777
5142
  } = entryInfo;
4778
5143
  const usdz = zipWriter.options[OPTION_USDZ];
4779
- const previousFileEntry = Array.from(files.values()).pop();
5144
+ const previousFileEntry = zipWriter.lastFileEntry;
4780
5145
  let fileEntry = {};
4781
5146
  let bufferedWrite;
4782
5147
  let releaseLockWriter;
4783
5148
  let releaseLockCurrentFileEntry;
4784
5149
  let writingBufferedEntryData;
4785
5150
  let writingEntryData;
5151
+ let writerSizeBeforeEntry;
5152
+ let flushedBufferedSize = 0;
4786
5153
  let fileWriter;
4787
5154
  files.set(name, fileEntry);
5155
+ zipWriter.lastFileEntry = fileEntry;
4788
5156
  try {
4789
5157
  let lockPreviousFileEntry;
4790
5158
  if (keepOrder) {
4791
5159
  lockPreviousFileEntry = previousFileEntry && previousFileEntry.lock;
4792
5160
  requestLockCurrentFileEntry();
4793
5161
  }
4794
- if ((options.bufferedWrite || !keepOrder || zipWriter.writerLocked || zipWriter.bufferedWrites || !dataDescriptor) && !usdz) {
5162
+ if (options.bufferedWrite || !keepOrder || zipWriter.writerLocked || zipWriter.bufferedWrites || !dataDescriptor) {
5163
+ bufferedWrite = true;
5164
+ zipWriter.bufferedWrites++;
4795
5165
  if (options.createTempStream) {
4796
5166
  fileWriter = await options.createTempStream();
4797
5167
  } else {
4798
5168
  fileWriter = new TransformStream(UNDEFINED_VALUE, UNDEFINED_VALUE, { highWaterMark: INFINITY_VALUE });
4799
5169
  }
4800
5170
  fileWriter.size = 0;
4801
- bufferedWrite = true;
4802
- zipWriter.bufferedWrites++;
4803
5171
  await initStream(writer);
4804
5172
  } else {
4805
5173
  fileWriter = writer;
4806
5174
  await requestLockWriter();
4807
5175
  }
4808
5176
  await initStream(fileWriter);
4809
- const { writable, diskOffset } = writer;
5177
+ const { diskOffset } = writer;
4810
5178
  if (zipWriter.addSplitZipSignature) {
4811
5179
  delete zipWriter.addSplitZipSignature;
4812
5180
  const signatureArray = new Uint8Array(4);
@@ -4815,21 +5183,20 @@
4815
5183
  await writeData(writer, signatureArray);
4816
5184
  zipWriter.offset += 4;
4817
5185
  }
4818
- if (usdz) {
5186
+ if (usdz && !bufferedWrite) {
4819
5187
  appendExtraFieldUSDZ(entryInfo, zipWriter.offset - diskOffset);
4820
5188
  }
4821
- const {
4822
- localHeaderView,
4823
- localHeaderArray
4824
- } = headerInfo;
5189
+ const { localHeaderArray } = headerInfo;
4825
5190
  if (!bufferedWrite) {
4826
5191
  await lockPreviousFileEntry;
4827
- await skipDiskIfNeeded(writable);
5192
+ await skipDiskIfNeeded();
4828
5193
  }
4829
- const { diskNumber } = writer;
4830
- fileEntry.diskNumberStart = diskNumber;
5194
+ const diskNumberStart = writer.diskNumber;
5195
+ const entryOffset = getSegmentOffset(zipWriter, writer);
5196
+ fileEntry.diskNumberStart = diskNumberStart;
4831
5197
  if (!bufferedWrite) {
4832
5198
  writingEntryData = true;
5199
+ writerSizeBeforeEntry = writer.size;
4833
5200
  await writeData(fileWriter, localHeaderArray);
4834
5201
  }
4835
5202
  fileEntry = await createFileEntry(reader, fileWriter, fileEntry, entryInfo, zipWriter.config, options);
@@ -4842,16 +5209,24 @@
4842
5209
  await Promise.all([fileWriter.writable.getWriter().close(), lockPreviousFileEntry]);
4843
5210
  await requestLockWriter();
4844
5211
  writingBufferedEntryData = true;
5212
+ writerSizeBeforeEntry = writer.size;
5213
+ await skipDiskIfNeeded();
4845
5214
  fileEntry.diskNumberStart = writer.diskNumber;
4846
- fileEntry.offset = zipWriter.offset - writer.diskOffset;
4847
- updateLocalHeader(fileEntry, localHeaderView, options);
4848
- await skipDiskIfNeeded(writable);
4849
- await writeData(writer, localHeaderArray);
4850
- await fileWriter.readable.pipeTo(writable, { preventClose: true, preventAbort: true, signal });
5215
+ fileEntry.offset = getSegmentOffset(zipWriter, writer);
5216
+ if (usdz) {
5217
+ // the padding depends on the entry offset, only known now that the writer is locked
5218
+ const previousMetadataSize = entryInfo.metadataSize;
5219
+ appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
5220
+ fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
5221
+ }
5222
+ updateLocalHeader(fileEntry, headerInfo.localHeaderView, options);
5223
+ await writeData(writer, headerInfo.localHeaderArray);
5224
+ await flushBufferedData(fileWriter.readable, writer, signal, chunkLength => flushedBufferedSize += chunkLength);
4851
5225
  writer.size += fileWriter.size;
4852
5226
  writingBufferedEntryData = false;
4853
5227
  } else {
4854
- fileEntry.offset = zipWriter.offset - diskOffset;
5228
+ fileEntry.diskNumberStart = diskNumberStart;
5229
+ fileEntry.offset = entryOffset;
4855
5230
  }
4856
5231
  zipWriter.offset += fileEntry.size;
4857
5232
  return fileEntry;
@@ -4865,10 +5240,9 @@
4865
5240
  // ignored
4866
5241
  }
4867
5242
  }
5243
+ zipWriter.offset += writer.size - writerSizeBeforeEntry;
4868
5244
  if (bufferedWrite) {
4869
- zipWriter.offset += fileWriter.size;
4870
- } else {
4871
- zipWriter.offset = fileWriter.size;
5245
+ zipWriter.offset += flushedBufferedSize;
4872
5246
  }
4873
5247
  }
4874
5248
  files.delete(name);
@@ -4899,10 +5273,10 @@
4899
5273
  await lockWriter;
4900
5274
  }
4901
5275
 
4902
- async function skipDiskIfNeeded(writable) {
5276
+ async function skipDiskIfNeeded() {
4903
5277
  if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
4904
5278
  writer.availableSize = 0;
4905
- await writeData(writable, new Uint8Array());
5279
+ await writeData(writer, new Uint8Array());
4906
5280
  }
4907
5281
  }
4908
5282
  }
@@ -5043,6 +5417,10 @@
5043
5417
  uncompressedSize = result.inputSize;
5044
5418
  signature = result.signature;
5045
5419
  }
5420
+ if ((!zip64CompressedSize && compressedSize >= MAX_32_BITS) ||
5421
+ (!zip64UncompressedSize && uncompressedSize >= MAX_32_BITS)) {
5422
+ throw new Error(ERR_UNSUPPORTED_FORMAT);
5423
+ }
5046
5424
  } catch (error) {
5047
5425
  if (error.outputSize !== UNDEFINED_VALUE) {
5048
5426
  writer.size += error.outputSize;
@@ -5105,35 +5483,28 @@
5105
5483
  uncompressedSize
5106
5484
  } = options;
5107
5485
  let { version, compressionMethod } = options;
5108
- const compressed = !directory && (level > 0 || (level === UNDEFINED_VALUE && compressionMethod !== 0));
5486
+ const compressed = !directory && (compressionMethod === UNDEFINED_VALUE
5487
+ ? (level === UNDEFINED_VALUE || level > 0)
5488
+ : compressionMethod !== COMPRESSION_METHOD_STORE);
5109
5489
  let rawLocalExtraFieldZip64;
5110
5490
  const uncompressedFile = passThrough || !compressed;
5111
- const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
5112
- if (zip64) {
5113
- let rawLocalExtraFieldZip64Length = 4;
5114
- if (zip64UncompressedSize) {
5115
- rawLocalExtraFieldZip64Length += 8;
5116
- }
5117
- if (zip64CompressedSize) {
5118
- rawLocalExtraFieldZip64Length += 8;
5119
- }
5120
- rawLocalExtraFieldZip64 = new Uint8Array(rawLocalExtraFieldZip64Length);
5121
- const rawLocalExtraFieldZip64View = getDataView(rawLocalExtraFieldZip64);
5122
- setUint16(rawLocalExtraFieldZip64View, 0, EXTRAFIELD_TYPE_ZIP64);
5123
- setUint16(rawLocalExtraFieldZip64View, 2, getLength(rawLocalExtraFieldZip64) - 4);
5491
+ // entries without a data descriptor are always buffered (cf. getFileEntry), so the
5492
+ // zip64 extra field values can be patched into the local header before flushing it
5493
+ const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
5494
+ const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
5495
+ if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
5496
+ // APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
5497
+ // uncompressed and compressed size fields
5498
+ const length = 4 + 16;
5499
+ const extraFieldZip64 = createRecordWriter(length);
5500
+ extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
5501
+ extraFieldZip64.uint16(length - 4);
5502
+ rawLocalExtraFieldZip64 = extraFieldZip64.array;
5124
5503
  if (zip64ExtraFieldComplete) {
5125
- const rawLocalExtraFieldZip64View = getDataView(rawLocalExtraFieldZip64);
5126
- let rawLocalExtraFieldZip64Offset = 4;
5127
- if (zip64UncompressedSize) {
5128
- setBigUint64(rawLocalExtraFieldZip64View, rawLocalExtraFieldZip64Offset, BigInt(uncompressedSize));
5129
- rawLocalExtraFieldZip64Offset += 8;
5130
- }
5131
- if (zip64CompressedSize && uncompressedFile) {
5132
- setBigUint64(rawLocalExtraFieldZip64View, rawLocalExtraFieldZip64Offset, BigInt(uncompressedSize));
5133
- rawLocalExtraFieldZip64Offset += 8;
5134
- }
5135
- if (rawLocalExtraFieldZip64Offset == 4) {
5136
- rawLocalExtraFieldZip64 = new Uint8Array();
5504
+ extraFieldZip64.uint64(uncompressedSize);
5505
+ if (uncompressedFile) {
5506
+ const encryptionOverhead = encrypted ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
5507
+ extraFieldZip64.uint64(passThrough ? 0 : uncompressedSize + encryptionOverhead);
5137
5508
  }
5138
5509
  }
5139
5510
  } else {
@@ -5141,11 +5512,11 @@
5141
5512
  }
5142
5513
  let rawExtraFieldAES;
5143
5514
  if (encrypted && !zipCrypto) {
5144
- rawExtraFieldAES = new Uint8Array(getLength(EXTRAFIELD_DATA_AES) + 2);
5145
- const extraFieldAESView = getDataView(rawExtraFieldAES);
5146
- setUint16(extraFieldAESView, 0, EXTRAFIELD_TYPE_AES);
5147
- arraySet(rawExtraFieldAES, EXTRAFIELD_DATA_AES, 2);
5148
- setUint8(extraFieldAESView, 8, encryptionStrength);
5515
+ const extraFieldAES = createRecordWriter(getLength(EXTRAFIELD_DATA_AES) + 2);
5516
+ extraFieldAES.uint16(EXTRAFIELD_TYPE_AES);
5517
+ extraFieldAES.bytes(EXTRAFIELD_DATA_AES);
5518
+ rawExtraFieldAES = extraFieldAES.array;
5519
+ rawExtraFieldAES[8] = encryptionStrength;
5149
5520
  } else {
5150
5521
  rawExtraFieldAES = new Uint8Array();
5151
5522
  }
@@ -5153,33 +5524,38 @@
5153
5524
  let rawExtraFieldExtendedTimestamp;
5154
5525
  let extraFieldExtendedTimestampFlag;
5155
5526
  if (extendedTimestamp) {
5156
- rawExtraFieldExtendedTimestamp = new Uint8Array(9 + (lastAccessDate ? 4 : 0) + (creationDate ? 4 : 0));
5157
- const extraFieldExtendedTimestampView = getDataView(rawExtraFieldExtendedTimestamp);
5158
- setUint16(extraFieldExtendedTimestampView, 0, EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
5159
- setUint16(extraFieldExtendedTimestampView, 2, getLength(rawExtraFieldExtendedTimestamp) - 4);
5160
- extraFieldExtendedTimestampFlag = 0x1 + (lastAccessDate ? 0x2 : 0) + (creationDate ? 0x4 : 0);
5161
- setUint8(extraFieldExtendedTimestampView, 4, extraFieldExtendedTimestampFlag);
5162
- let offset = 5;
5163
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(lastModDate.getTime() / 1000));
5164
- offset += 4;
5165
- if (lastAccessDate) {
5166
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(lastAccessDate.getTime() / 1000));
5167
- offset += 4;
5168
- }
5169
- if (creationDate) {
5170
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(creationDate.getTime() / 1000));
5527
+ const lastModTimeUnix = getTimeUnix(lastModDate);
5528
+ if (inUnixTimeRange(lastModTimeUnix)) {
5529
+ const extraFieldTimestampLength = 9 + (lastAccessDate ? 4 : 0) + (creationDate ? 4 : 0);
5530
+ const extraFieldTimestamp = createRecordWriter(extraFieldTimestampLength);
5531
+ extraFieldExtendedTimestampFlag = 0x1 + (lastAccessDate ? 0x2 : 0) + (creationDate ? 0x4 : 0);
5532
+ extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
5533
+ extraFieldTimestamp.uint16(extraFieldTimestampLength - 4);
5534
+ extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
5535
+ extraFieldTimestamp.uint32(lastModTimeUnix);
5536
+ if (lastAccessDate) {
5537
+ extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(lastAccessDate)));
5538
+ }
5539
+ if (creationDate) {
5540
+ extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(creationDate)));
5541
+ }
5542
+ rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
5543
+ } else {
5544
+ // the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
5545
+ rawExtraFieldExtendedTimestamp = new Uint8Array();
5171
5546
  }
5172
5547
  try {
5173
- rawExtraFieldNTFS = new Uint8Array(36);
5174
- const extraFieldNTFSView = getDataView(rawExtraFieldNTFS);
5175
5548
  const lastModTimeNTFS = getTimeNTFS(lastModDate);
5176
- setUint16(extraFieldNTFSView, 0, EXTRAFIELD_TYPE_NTFS);
5177
- setUint16(extraFieldNTFSView, 2, 32);
5178
- setUint16(extraFieldNTFSView, 8, EXTRAFIELD_TYPE_NTFS_TAG1);
5179
- setUint16(extraFieldNTFSView, 10, 24);
5180
- setBigUint64(extraFieldNTFSView, 12, lastModTimeNTFS);
5181
- setBigUint64(extraFieldNTFSView, 20, getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
5182
- setBigUint64(extraFieldNTFSView, 28, getTimeNTFS(creationDate) || lastModTimeNTFS);
5549
+ const extraFieldNTFS = createRecordWriter(36);
5550
+ extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS);
5551
+ extraFieldNTFS.uint16(32);
5552
+ extraFieldNTFS.skip(4);
5553
+ extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS_TAG1);
5554
+ extraFieldNTFS.uint16(24);
5555
+ extraFieldNTFS.uint64(lastModTimeNTFS);
5556
+ extraFieldNTFS.uint64(getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
5557
+ extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
5558
+ rawExtraFieldNTFS = extraFieldNTFS.array;
5183
5559
  } catch {
5184
5560
  rawExtraFieldNTFS = new Uint8Array();
5185
5561
  }
@@ -5188,41 +5564,27 @@
5188
5564
  }
5189
5565
  let rawExtraFieldUnix;
5190
5566
  try {
5191
- const { uid, gid, unixMode, setuid, setgid, sticky, unixExtraFieldType } = options;
5192
- if (unixExtraFieldType && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE || unixMode !== UNDEFINED_VALUE)) {
5567
+ const { uid, gid, unixExtraFieldType } = options;
5568
+ if (unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
5193
5569
  const uidBytes = packUnixId(uid);
5194
5570
  const gidBytes = packUnixId(gid);
5195
- let modeArray = new Uint8Array();
5196
- if (unixExtraFieldType == UNIX_EXTRA_FIELD_TYPE && unixMode !== UNDEFINED_VALUE) {
5197
- let modeToWrite = unixMode & MAX_16_BITS;
5198
- if (setuid) {
5199
- modeToWrite |= FILE_ATTR_UNIX_SETUID_MASK;
5200
- }
5201
- if (setgid) {
5202
- modeToWrite |= FILE_ATTR_UNIX_SETGID_MASK;
5203
- }
5204
- if (sticky) {
5205
- modeToWrite |= FILE_ATTR_UNIX_STICKY_MASK;
5206
- }
5207
- modeArray = new Uint8Array(2);
5208
- const modeDataView = new DataView(modeArray.buffer);
5209
- modeDataView.setUint16(0, modeToWrite, true);
5210
- }
5211
- const payloadLength = 3 + uidBytes.length + gidBytes.length + modeArray.length;
5212
- rawExtraFieldUnix = new Uint8Array(4 + payloadLength);
5213
- const rawExtraFieldUnixView = getDataView(rawExtraFieldUnix);
5214
- setUint16(rawExtraFieldUnixView, 0, unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE ? EXTRAFIELD_TYPE_INFOZIP : EXTRAFIELD_TYPE_UNIX);
5215
- setUint16(rawExtraFieldUnixView, 2, payloadLength);
5216
- setUint8(rawExtraFieldUnixView, 4, 1);
5217
- setUint8(rawExtraFieldUnixView, 5, uidBytes.length);
5218
- let offset = 6;
5219
- arraySet(rawExtraFieldUnix, uidBytes, offset);
5220
- offset += uidBytes.length;
5221
- setUint8(rawExtraFieldUnixView, offset, gidBytes.length);
5222
- offset++;
5223
- arraySet(rawExtraFieldUnix, gidBytes, offset);
5224
- offset += gidBytes.length;
5225
- arraySet(rawExtraFieldUnix, modeArray, offset);
5571
+ const payloadLength = 3 + uidBytes.length + gidBytes.length;
5572
+ const extraFieldUnix = createRecordWriter(4 + payloadLength);
5573
+ extraFieldUnix.uint16(EXTRAFIELD_TYPE_INFOZIP);
5574
+ extraFieldUnix.uint16(payloadLength);
5575
+ extraFieldUnix.uint8(1);
5576
+ extraFieldUnix.uint8(uidBytes.length);
5577
+ extraFieldUnix.bytes(uidBytes);
5578
+ extraFieldUnix.uint8(gidBytes.length);
5579
+ extraFieldUnix.bytes(gidBytes);
5580
+ rawExtraFieldUnix = extraFieldUnix.array;
5581
+ } else if (unixExtraFieldType == UNIX_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
5582
+ const extraFieldUnix = createRecordWriter(8);
5583
+ extraFieldUnix.uint16(EXTRAFIELD_TYPE_UNIX);
5584
+ extraFieldUnix.uint16(4);
5585
+ extraFieldUnix.uint16((uid === UNDEFINED_VALUE ? 0 : uid) & MAX_16_BITS);
5586
+ extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
5587
+ rawExtraFieldUnix = extraFieldUnix.array;
5226
5588
  } else {
5227
5589
  rawExtraFieldUnix = new Uint8Array();
5228
5590
  }
@@ -5240,8 +5602,11 @@
5240
5602
  rawExtraFieldAES[9] = compressionMethod;
5241
5603
  compressionMethod = COMPRESSION_METHOD_AES;
5242
5604
  }
5243
- const localExtraFieldZip64Length = zip64ExtraFieldComplete ? getLength(rawLocalExtraFieldZip64) : 0;
5605
+ const localExtraFieldZip64Length = writeLocalExtraFieldZip64 ? getLength(rawLocalExtraFieldZip64) : 0;
5244
5606
  const extraFieldLength = localExtraFieldZip64Length + getLength(rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
5607
+ if (extraFieldLength > MAX_16_BITS) {
5608
+ throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
5609
+ }
5245
5610
  const {
5246
5611
  headerArray,
5247
5612
  headerView,
@@ -5257,29 +5622,27 @@
5257
5622
  zip64UncompressedSize,
5258
5623
  extraFieldLength
5259
5624
  });
5260
- let localHeaderOffset = HEADER_SIZE;
5261
- const localHeaderArray = new Uint8Array(localHeaderOffset + getLength(rawFilename) + extraFieldLength);
5625
+ const localHeader = createRecordWriter(HEADER_SIZE + getLength(rawFilename) + extraFieldLength);
5626
+ const localHeaderArray = localHeader.array;
5262
5627
  const localHeaderView = getDataView(localHeaderArray);
5263
- setUint32(localHeaderView, 0, LOCAL_FILE_HEADER_SIGNATURE);
5264
- arraySet(localHeaderArray, headerArray, 4);
5265
- arraySet(localHeaderArray, rawFilename, localHeaderOffset);
5266
- localHeaderOffset += getLength(rawFilename);
5267
- if (zip64ExtraFieldComplete) {
5268
- arraySet(localHeaderArray, rawLocalExtraFieldZip64, localHeaderOffset);
5269
- }
5270
- localHeaderOffset += localExtraFieldZip64Length;
5271
- arraySet(localHeaderArray, rawExtraFieldAES, localHeaderOffset);
5272
- localHeaderOffset += getLength(rawExtraFieldAES);
5273
- arraySet(localHeaderArray, rawExtraFieldExtendedTimestamp, localHeaderOffset);
5274
- localHeaderOffset += getLength(rawExtraFieldExtendedTimestamp);
5275
- arraySet(localHeaderArray, rawExtraFieldNTFS, localHeaderOffset);
5276
- localHeaderOffset += getLength(rawExtraFieldNTFS);
5277
- arraySet(localHeaderArray, rawExtraFieldUnix, localHeaderOffset);
5278
- localHeaderOffset += getLength(rawExtraFieldUnix);
5279
- arraySet(localHeaderArray, rawExtraField, localHeaderOffset);
5628
+ localHeader.uint32(LOCAL_FILE_HEADER_SIGNATURE);
5629
+ localHeader.bytes(headerArray);
5630
+ localHeader.bytes(rawFilename);
5631
+ if (writeLocalExtraFieldZip64) {
5632
+ localHeader.bytes(rawLocalExtraFieldZip64);
5633
+ }
5634
+ localHeader.bytes(rawExtraFieldAES);
5635
+ localHeader.bytes(rawExtraFieldExtendedTimestamp);
5636
+ localHeader.bytes(rawExtraFieldNTFS);
5637
+ localHeader.bytes(rawExtraFieldUnix);
5638
+ localHeader.bytes(rawExtraField);
5280
5639
  if (dataDescriptor) {
5281
- setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + 4, 0);
5282
- setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + 4, 0);
5640
+ if (!zip64CompressedSize) {
5641
+ setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
5642
+ }
5643
+ if (!zip64UncompressedSize) {
5644
+ setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
5645
+ }
5283
5646
  }
5284
5647
  return {
5285
5648
  localHeaderArray,
@@ -5306,7 +5669,7 @@
5306
5669
  function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
5307
5670
  const { headerInfo } = entryInfo;
5308
5671
  let { localHeaderArray, extraFieldLength } = headerInfo;
5309
- let localHeaderArrayView = getDataView(localHeaderArray);
5672
+ let localHeaderArrayView;
5310
5673
  let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
5311
5674
  if (extraBytesLength < 4) {
5312
5675
  extraBytesLength += 64;
@@ -5314,13 +5677,14 @@
5314
5677
  const rawExtraFieldUSDZ = new Uint8Array(extraBytesLength);
5315
5678
  const extraFieldUSDZView = getDataView(rawExtraFieldUSDZ);
5316
5679
  setUint16(extraFieldUSDZView, 0, EXTRAFIELD_TYPE_USDZ);
5317
- setUint16(extraFieldUSDZView, 2, extraBytesLength - 2);
5680
+ setUint16(extraFieldUSDZView, 2, extraBytesLength - 4);
5318
5681
  const previousLocalHeaderArray = localHeaderArray;
5319
5682
  headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
5320
5683
  arraySet(localHeaderArray, previousLocalHeaderArray);
5321
5684
  arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
5322
5685
  localHeaderArrayView = getDataView(localHeaderArray);
5323
5686
  setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
5687
+ headerInfo.localHeaderView = localHeaderArrayView;
5324
5688
  entryInfo.metadataSize += extraBytesLength;
5325
5689
  }
5326
5690
 
@@ -5443,37 +5807,32 @@
5443
5807
  }, localHeaderView, { dataDescriptor }) {
5444
5808
  if (!dataDescriptor) {
5445
5809
  if (!encrypted) {
5446
- setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + 4, signature);
5810
+ setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + LOCAL_HEADER_COMMON_OFFSET, signature);
5447
5811
  }
5448
5812
  if (!zip64CompressedSize) {
5449
- setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + 4, compressedSize);
5813
+ setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, compressedSize);
5450
5814
  }
5451
5815
  if (!zip64UncompressedSize) {
5452
- setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + 4, uncompressedSize);
5816
+ setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, uncompressedSize);
5453
5817
  }
5454
5818
  }
5455
5819
  if (zip64 && localExtraFieldZip64Length) {
5456
- let localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
5457
- if (zip64UncompressedSize) {
5458
- setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
5459
- localHeaderOffset += 8;
5460
- }
5461
- if (zip64CompressedSize) {
5462
- setBigUint64(localHeaderView, localHeaderOffset, BigInt(compressedSize));
5463
- localHeaderOffset += 8;
5464
- }
5820
+ // the local zip64 extra field always contains both size slots
5821
+ const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
5822
+ setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
5823
+ setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
5465
5824
  }
5466
5825
  }
5467
5826
 
5468
5827
 
5469
5828
  async function closeFile(zipWriter, comment, options) {
5470
- const { files, writer } = zipWriter;
5471
- const { diskOffset } = writer;
5472
- let { diskNumber } = writer;
5473
- let offset = 0;
5829
+ const directoryDataLength = createDirectoryRecords(zipWriter.files);
5830
+ const { cdStartDiskNumber, cdStartDiskOffset } = await writeDirectoryRecords(zipWriter, directoryDataLength, options);
5831
+ await writeEndOfDirectoryRecord(zipWriter, comment, options, { cdStartDiskNumber, cdStartDiskOffset, directoryDataLength });
5832
+ }
5833
+
5834
+ function createDirectoryRecords(files) {
5474
5835
  let directoryDataLength = 0;
5475
- let directoryOffset = zipWriter.offset - diskOffset;
5476
- let filesLength = files.size;
5477
5836
  for (const [, fileEntry] of files) {
5478
5837
  const {
5479
5838
  rawFilename,
@@ -5490,24 +5849,27 @@
5490
5849
  uncompressedSize,
5491
5850
  compressedSize
5492
5851
  } = fileEntry;
5493
- const zip64Offset = fileEntry.offset > MAX_32_BITS;
5494
- const zip64DiskNumberStart = fileEntry.diskNumberStart > MAX_16_BITS;
5852
+ const zip64Offset = fileEntry.offset >= MAX_32_BITS;
5853
+ const zip64DiskNumberStart = fileEntry.diskNumberStart >= MAX_16_BITS;
5495
5854
  let rawExtraFieldZip64;
5496
5855
  if (zip64Offset || zip64DiskNumberStart || zip64UncompressedSize || zip64CompressedSize) {
5497
- let length = 4;
5498
- if (zip64UncompressedSize) length += 8;
5499
- if (zip64CompressedSize) length += 8;
5500
- if (zip64Offset) length += 8;
5501
- if (zip64DiskNumberStart) length += 4;
5502
- rawExtraFieldZip64 = new Uint8Array(length);
5503
- const zip64View = getDataView(rawExtraFieldZip64);
5504
- setUint16(zip64View, 0, EXTRAFIELD_TYPE_ZIP64);
5505
- setUint16(zip64View, 2, length - 4);
5506
- let zip64FieldOffset = 4;
5507
- if (zip64UncompressedSize) { setBigUint64(zip64View, zip64FieldOffset, BigInt(uncompressedSize)); zip64FieldOffset += 8; }
5508
- if (zip64CompressedSize) { setBigUint64(zip64View, zip64FieldOffset, BigInt(compressedSize)); zip64FieldOffset += 8; }
5509
- if (zip64Offset) { setBigUint64(zip64View, zip64FieldOffset, BigInt(fileEntry.offset)); zip64FieldOffset += 8; }
5510
- if (zip64DiskNumberStart) { setUint32(zip64View, zip64FieldOffset, fileEntry.diskNumberStart); }
5856
+ const length = 4 + (zip64UncompressedSize ? 8 : 0) + (zip64CompressedSize ? 8 : 0) + (zip64Offset ? 8 : 0) + (zip64DiskNumberStart ? 4 : 0);
5857
+ const extraFieldZip64 = createRecordWriter(length);
5858
+ extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
5859
+ extraFieldZip64.uint16(length - 4);
5860
+ if (zip64UncompressedSize) {
5861
+ extraFieldZip64.uint64(uncompressedSize);
5862
+ }
5863
+ if (zip64CompressedSize) {
5864
+ extraFieldZip64.uint64(compressedSize);
5865
+ }
5866
+ if (zip64Offset) {
5867
+ extraFieldZip64.uint64(fileEntry.offset);
5868
+ }
5869
+ if (zip64DiskNumberStart) {
5870
+ extraFieldZip64.uint32(fileEntry.diskNumberStart);
5871
+ }
5872
+ rawExtraFieldZip64 = extraFieldZip64.array;
5511
5873
  } else {
5512
5874
  rawExtraFieldZip64 = new Uint8Array();
5513
5875
  }
@@ -5515,32 +5877,41 @@
5515
5877
  fileEntry.zip64Offset = zip64Offset;
5516
5878
  fileEntry.zip64DiskNumberStart = zip64DiskNumberStart;
5517
5879
  let rawExtraFieldTimestamp;
5518
- if (extendedTimestamp) {
5519
- rawExtraFieldTimestamp = new Uint8Array(9);
5520
- const extraFieldExtendedTimestampView = getDataView(rawExtraFieldTimestamp);
5521
- setUint16(extraFieldExtendedTimestampView, 0, EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
5522
- setUint16(extraFieldExtendedTimestampView, 2, 5);
5523
- setUint8(extraFieldExtendedTimestampView, 4, extraFieldExtendedTimestampFlag);
5524
- setUint32(extraFieldExtendedTimestampView, 5, Math.floor(lastModDate.getTime() / 1000));
5880
+ const lastModTimeUnix = getTimeUnix(lastModDate);
5881
+ if (extendedTimestamp && inUnixTimeRange(lastModTimeUnix)) {
5882
+ const extraFieldTimestamp = createRecordWriter(9);
5883
+ extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
5884
+ extraFieldTimestamp.uint16(5);
5885
+ extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
5886
+ extraFieldTimestamp.uint32(lastModTimeUnix);
5887
+ rawExtraFieldTimestamp = extraFieldTimestamp.array;
5525
5888
  } else {
5526
5889
  rawExtraFieldTimestamp = new Uint8Array();
5527
5890
  }
5528
5891
  fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
5529
- directoryDataLength += 46 +
5530
- getLength(
5531
- rawFilename,
5532
- rawComment,
5533
- rawExtraFieldZip64,
5534
- rawExtraFieldAES,
5535
- rawExtraFieldNTFS,
5536
- rawExtraFieldUnix,
5537
- rawExtraFieldTimestamp,
5538
- rawExtraField);
5892
+ const extraFieldLength = getLength(
5893
+ rawExtraFieldZip64,
5894
+ rawExtraFieldAES,
5895
+ rawExtraFieldNTFS,
5896
+ rawExtraFieldUnix,
5897
+ rawExtraFieldTimestamp,
5898
+ rawExtraField);
5899
+ if (extraFieldLength > MAX_16_BITS) {
5900
+ throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
5901
+ }
5902
+ directoryDataLength += CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
5539
5903
  }
5904
+ return directoryDataLength;
5905
+ }
5906
+
5907
+ async function writeDirectoryRecords(zipWriter, directoryDataLength, options) {
5908
+ const { files, writer } = zipWriter;
5540
5909
  const directoryArray = new Uint8Array(directoryDataLength);
5541
- const directoryView = getDataView(directoryArray);
5542
5910
  await initStream(writer);
5911
+ let offset = 0;
5543
5912
  let directoryDiskOffset = 0;
5913
+ let cdStartDiskNumber = writer.diskNumber;
5914
+ let cdStartDiskOffset = writer.diskOffset;
5544
5915
  for (const [indexFileEntry, fileEntry] of Array.from(files.values()).entries()) {
5545
5916
  const {
5546
5917
  offset: fileEntryOffset,
@@ -5566,52 +5937,46 @@
5566
5937
  compressedSize
5567
5938
  } = fileEntry;
5568
5939
  const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
5569
- setUint32(directoryView, offset, CENTRAL_FILE_HEADER_SIGNATURE);
5570
- setUint16(directoryView, offset + 4, versionMadeBy);
5940
+ const directoryRecordLength = CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
5941
+ if (offset + directoryRecordLength - directoryDiskOffset > writer.availableSize) {
5942
+ await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
5943
+ directoryDiskOffset = offset;
5944
+ writer.availableSize = 0;
5945
+ await writeData(writer, new Uint8Array());
5946
+ }
5947
+ if (indexFileEntry == 0) {
5948
+ cdStartDiskNumber = writer.diskNumber;
5949
+ cdStartDiskOffset = writer.diskOffset;
5950
+ }
5571
5951
  if (!zip64UncompressedSize) {
5572
5952
  setUint32(headerView, HEADER_OFFSET_UNCOMPRESSED_SIZE, uncompressedSize);
5573
5953
  }
5574
5954
  if (!zip64CompressedSize) {
5575
5955
  setUint32(headerView, HEADER_OFFSET_COMPRESSED_SIZE, compressedSize);
5576
5956
  }
5577
- arraySet(directoryArray, headerArray, offset + 6);
5578
- let directoryOffset = offset + HEADER_SIZE;
5579
- setUint16(directoryView, directoryOffset, extraFieldLength);
5580
- directoryOffset += 2;
5581
- setUint16(directoryView, directoryOffset, getLength(rawComment));
5582
- directoryOffset += 2;
5583
- setUint16(directoryView, directoryOffset, zip64DiskNumberStart ? MAX_16_BITS : diskNumberStart);
5584
- directoryOffset += 2;
5585
- setUint16(directoryView, directoryOffset, internalFileAttributes);
5586
- directoryOffset += 2;
5587
- if (externalFileAttributes) {
5588
- setUint32(directoryView, directoryOffset, externalFileAttributes);
5589
- }
5590
- directoryOffset += 4;
5591
- setUint32(directoryView, directoryOffset, zip64Offset ? MAX_32_BITS : fileEntryOffset);
5592
- directoryOffset += 4;
5593
- arraySet(directoryArray, rawFilename, directoryOffset);
5594
- directoryOffset += getLength(rawFilename);
5595
- arraySet(directoryArray, rawExtraFieldZip64, directoryOffset);
5596
- directoryOffset += getLength(rawExtraFieldZip64);
5597
- arraySet(directoryArray, rawExtraFieldAES, directoryOffset);
5598
- directoryOffset += getLength(rawExtraFieldAES);
5599
- arraySet(directoryArray, rawExtraFieldExtendedTimestamp, directoryOffset);
5600
- directoryOffset += getLength(rawExtraFieldExtendedTimestamp);
5601
- arraySet(directoryArray, rawExtraFieldNTFS, directoryOffset);
5602
- directoryOffset += getLength(rawExtraFieldNTFS);
5603
- arraySet(directoryArray, rawExtraFieldUnix, directoryOffset);
5604
- directoryOffset += getLength(rawExtraFieldUnix);
5605
- arraySet(directoryArray, rawExtraField, directoryOffset);
5606
- directoryOffset += getLength(rawExtraField);
5607
- arraySet(directoryArray, rawComment, directoryOffset);
5608
- directoryOffset += getLength(rawComment);
5609
- if (offset - directoryDiskOffset > writer.availableSize) {
5610
- writer.availableSize = 0;
5611
- await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
5612
- directoryDiskOffset = offset;
5613
- }
5614
- offset = directoryOffset;
5957
+ if ((zip64Offset || zip64DiskNumberStart) && fileEntry.version < VERSION_ZIP64) {
5958
+ setUint16(headerView, HEADER_OFFSET_VERSION, VERSION_ZIP64);
5959
+ }
5960
+ const directoryRecord = createRecordWriter(directoryRecordLength);
5961
+ directoryRecord.uint32(CENTRAL_FILE_HEADER_SIGNATURE);
5962
+ directoryRecord.uint16(versionMadeBy);
5963
+ directoryRecord.bytes(headerArray.subarray(0, HEADER_SIZE - 4 - 2));
5964
+ directoryRecord.uint16(extraFieldLength);
5965
+ directoryRecord.uint16(getLength(rawComment));
5966
+ directoryRecord.uint16(zip64DiskNumberStart ? MAX_16_BITS : diskNumberStart);
5967
+ directoryRecord.uint16(internalFileAttributes);
5968
+ directoryRecord.uint32(externalFileAttributes);
5969
+ directoryRecord.uint32(zip64Offset ? MAX_32_BITS : fileEntryOffset);
5970
+ directoryRecord.bytes(rawFilename);
5971
+ directoryRecord.bytes(rawExtraFieldZip64);
5972
+ directoryRecord.bytes(rawExtraFieldAES);
5973
+ directoryRecord.bytes(rawExtraFieldExtendedTimestamp);
5974
+ directoryRecord.bytes(rawExtraFieldNTFS);
5975
+ directoryRecord.bytes(rawExtraFieldUnix);
5976
+ directoryRecord.bytes(rawExtraField);
5977
+ directoryRecord.bytes(rawComment);
5978
+ arraySet(directoryArray, directoryRecord.array, offset);
5979
+ offset += directoryRecordLength;
5615
5980
  if (options.onprogress) {
5616
5981
  try {
5617
5982
  await options.onprogress(indexFileEntry + 1, files.size, new Entry(fileEntry));
@@ -5621,36 +5986,53 @@
5621
5986
  }
5622
5987
  }
5623
5988
  await writeData(writer, directoryDiskOffset ? directoryArray.slice(directoryDiskOffset) : directoryArray);
5989
+ return { cdStartDiskNumber, cdStartDiskOffset };
5990
+ }
5991
+
5992
+ async function writeEndOfDirectoryRecord(zipWriter, comment, options, cdInfo) {
5993
+ const { writer } = zipWriter;
5994
+ const { cdStartDiskNumber, cdStartDiskOffset } = cdInfo;
5995
+ let { directoryDataLength } = cdInfo;
5996
+ let filesLength = zipWriter.files.size;
5997
+ let diskNumber = cdStartDiskNumber;
5998
+ let directoryOffset = zipWriter.offset - cdStartDiskOffset - (cdStartDiskNumber ? zipWriter.initialOffset : 0);
5624
5999
  let lastDiskNumber = writer.diskNumber;
5625
- const { availableSize } = writer;
5626
- if (availableSize < END_OF_CENTRAL_DIR_LENGTH) {
6000
+ if (writer.availableSize < END_OF_CENTRAL_DIR_LENGTH) {
5627
6001
  lastDiskNumber++;
5628
6002
  }
5629
6003
  let zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
5630
- if (directoryOffset > MAX_32_BITS || directoryDataLength > MAX_32_BITS || filesLength > MAX_16_BITS || lastDiskNumber > MAX_16_BITS) {
6004
+ if (directoryOffset >= MAX_32_BITS || directoryDataLength >= MAX_32_BITS || filesLength >= MAX_16_BITS || lastDiskNumber >= MAX_16_BITS) {
5631
6005
  if (zip64 === false) {
5632
6006
  throw new Error(ERR_UNSUPPORTED_FORMAT);
5633
6007
  } else {
5634
6008
  zip64 = true;
5635
6009
  }
5636
6010
  }
5637
- const endOfdirectoryArray = new Uint8Array(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
5638
- const endOfdirectoryView = getDataView(endOfdirectoryArray);
5639
- offset = 0;
6011
+ const commentLength = getLength(comment);
6012
+ if (commentLength > MAX_16_BITS) {
6013
+ throw new Error(ERR_INVALID_COMMENT);
6014
+ }
6015
+ const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
6016
+ if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
6017
+ writer.availableSize = 0;
6018
+ await writeData(writer, new Uint8Array());
6019
+ }
6020
+ lastDiskNumber = writer.diskNumber;
5640
6021
  if (zip64) {
5641
- setUint32(endOfdirectoryView, 0, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE);
5642
- setBigUint64(endOfdirectoryView, 4, BigInt(44));
5643
- setUint16(endOfdirectoryView, 12, 45);
5644
- setUint16(endOfdirectoryView, 14, 45);
5645
- setUint32(endOfdirectoryView, 16, lastDiskNumber);
5646
- setUint32(endOfdirectoryView, 20, diskNumber);
5647
- setBigUint64(endOfdirectoryView, 24, BigInt(filesLength));
5648
- setBigUint64(endOfdirectoryView, 32, BigInt(filesLength));
5649
- setBigUint64(endOfdirectoryView, 40, BigInt(directoryDataLength));
5650
- setBigUint64(endOfdirectoryView, 48, BigInt(directoryOffset));
5651
- setUint32(endOfdirectoryView, 56, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE);
5652
- setBigUint64(endOfdirectoryView, 64, BigInt(directoryOffset) + BigInt(directoryDataLength));
5653
- setUint32(endOfdirectoryView, 72, lastDiskNumber + 1);
6022
+ endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_SIGNATURE);
6023
+ endOfdirectoryRecord.uint64(44);
6024
+ endOfdirectoryRecord.uint16(45);
6025
+ endOfdirectoryRecord.uint16(45);
6026
+ endOfdirectoryRecord.uint32(lastDiskNumber);
6027
+ endOfdirectoryRecord.uint32(diskNumber);
6028
+ endOfdirectoryRecord.uint64(filesLength);
6029
+ endOfdirectoryRecord.uint64(filesLength);
6030
+ endOfdirectoryRecord.uint64(directoryDataLength);
6031
+ endOfdirectoryRecord.uint64(directoryOffset);
6032
+ endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE);
6033
+ endOfdirectoryRecord.uint32(lastDiskNumber);
6034
+ endOfdirectoryRecord.uint64(BigInt(zipWriter.offset) + BigInt(directoryDataLength) - BigInt(writer.diskOffset) - BigInt(writer.diskNumber ? zipWriter.initialOffset : 0));
6035
+ endOfdirectoryRecord.uint32(lastDiskNumber + 1);
5654
6036
  const supportZip64SplitFile = getOptionValue(zipWriter, options, OPTION_SUPPORT_ZIP64_SPLIT_FILE, true);
5655
6037
  if (supportZip64SplitFile) {
5656
6038
  lastDiskNumber = MAX_16_BITS;
@@ -5659,29 +6041,40 @@
5659
6041
  filesLength = MAX_16_BITS;
5660
6042
  directoryOffset = MAX_32_BITS;
5661
6043
  directoryDataLength = MAX_32_BITS;
5662
- offset += ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
5663
- }
5664
- setUint32(endOfdirectoryView, offset, END_OF_CENTRAL_DIR_SIGNATURE);
5665
- setUint16(endOfdirectoryView, offset + 4, lastDiskNumber);
5666
- setUint16(endOfdirectoryView, offset + 6, diskNumber);
5667
- setUint16(endOfdirectoryView, offset + 8, filesLength);
5668
- setUint16(endOfdirectoryView, offset + 10, filesLength);
5669
- setUint32(endOfdirectoryView, offset + 12, directoryDataLength);
5670
- setUint32(endOfdirectoryView, offset + 16, directoryOffset);
5671
- const commentLength = getLength(comment);
5672
- if (commentLength) {
5673
- if (commentLength <= MAX_16_BITS) {
5674
- setUint16(endOfdirectoryView, offset + 20, commentLength);
5675
- } else {
5676
- throw new Error(ERR_INVALID_COMMENT);
5677
- }
5678
6044
  }
5679
- await writeData(writer, endOfdirectoryArray);
6045
+ endOfdirectoryRecord.uint32(END_OF_CENTRAL_DIR_SIGNATURE);
6046
+ endOfdirectoryRecord.uint16(lastDiskNumber);
6047
+ endOfdirectoryRecord.uint16(diskNumber);
6048
+ endOfdirectoryRecord.uint16(filesLength);
6049
+ endOfdirectoryRecord.uint16(filesLength);
6050
+ endOfdirectoryRecord.uint32(directoryDataLength);
6051
+ endOfdirectoryRecord.uint32(directoryOffset);
6052
+ endOfdirectoryRecord.uint16(commentLength);
6053
+ await writeData(writer, endOfdirectoryRecord.array);
5680
6054
  if (commentLength) {
5681
6055
  await writeData(writer, comment);
5682
6056
  }
5683
6057
  }
5684
6058
 
6059
+ function createRecordWriter(length) {
6060
+ const array = new Uint8Array(length);
6061
+ const view = getDataView(array);
6062
+ let offset = 0;
6063
+ return {
6064
+ array,
6065
+ uint8: value => { setUint8(view, offset, value); offset += 1; },
6066
+ uint16: value => { setUint16(view, offset, value); offset += 2; },
6067
+ uint32: value => { setUint32(view, offset, value); offset += 4; },
6068
+ uint64: value => { setBigUint64(view, offset, BigInt(value)); offset += 8; },
6069
+ bytes: value => { arraySet(array, value, offset); offset += getLength(value); },
6070
+ skip: count => offset += count
6071
+ };
6072
+ }
6073
+
6074
+ function getSegmentOffset(zipWriter, writer) {
6075
+ return zipWriter.offset - writer.diskOffset - (writer.diskNumber ? zipWriter.initialOffset : 0);
6076
+ }
6077
+
5685
6078
  async function writeData(writer, array) {
5686
6079
  const { writable } = writer;
5687
6080
  const streamWriter = writable.getWriter();
@@ -5694,12 +6087,40 @@
5694
6087
  }
5695
6088
  }
5696
6089
 
6090
+ async function flushBufferedData(readable, writer, signal, onChunkWritten) {
6091
+ const streamWriter = writer.writable.getWriter();
6092
+ try {
6093
+ await readable.pipeTo(new WritableStream({
6094
+ async write(chunk) {
6095
+ await streamWriter.ready;
6096
+ await streamWriter.write(chunk);
6097
+ onChunkWritten(getLength(chunk));
6098
+ }
6099
+ }), { preventClose: true, preventAbort: true, signal });
6100
+ } finally {
6101
+ streamWriter.releaseLock();
6102
+ }
6103
+ }
6104
+
5697
6105
  function getTimeNTFS(date) {
5698
6106
  if (date) {
5699
6107
  return ((BigInt(date.getTime()) + BigInt(11644473600000)) * BigInt(10000));
5700
6108
  }
5701
6109
  }
5702
6110
 
6111
+ function getTimeUnix(date) {
6112
+ return Math.floor(date.getTime() / 1000);
6113
+ }
6114
+
6115
+ // the extended timestamp extra field stores dates as signed 32-bit time_t values
6116
+ function inUnixTimeRange(timeUnix) {
6117
+ return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
6118
+ }
6119
+
6120
+ function clampUnixTime(timeUnix) {
6121
+ return Math.min(MAX_UNIX_TIME, Math.max(MIN_UNIX_TIME, timeUnix));
6122
+ }
6123
+
5703
6124
  function getOptionValue(zipWriter, options, name, defaultValue) {
5704
6125
  const result = options[name] === UNDEFINED_VALUE ? zipWriter.options[name] : options[name];
5705
6126
  return result === UNDEFINED_VALUE ? defaultValue : result;
@@ -5730,7 +6151,7 @@
5730
6151
  }
5731
6152
 
5732
6153
  function getDataView(array) {
5733
- return new DataView(array.buffer);
6154
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
5734
6155
  }
5735
6156
 
5736
6157
  function getLength(...arrayLikes) {
@@ -5746,30 +6167,39 @@
5746
6167
  uncompressedSize,
5747
6168
  compressedSize,
5748
6169
  lastModDate,
6170
+ rawLastModDate,
5749
6171
  rawFilename,
5750
6172
  zip64CompressedSize,
5751
6173
  zip64UncompressedSize,
5752
6174
  extraFieldLength
5753
6175
  }) {
5754
- const headerArray = new Uint8Array(HEADER_SIZE - 4);
6176
+ const headerRecord = createRecordWriter(HEADER_SIZE - 4);
6177
+ const headerArray = headerRecord.array;
5755
6178
  const headerView = getDataView(headerArray);
5756
- setUint16(headerView, 0, version);
5757
- setUint16(headerView, 2, bitFlag);
5758
- setUint16(headerView, 4, compressionMethod);
5759
- const dateArray = new Uint32Array(1);
5760
- const dateView = getDataView(dateArray);
5761
- setUint16(dateView, 0, (((lastModDate.getHours() << 6) | lastModDate.getMinutes()) << 5) | lastModDate.getSeconds() / 2);
5762
- setUint16(dateView, 2, ((((lastModDate.getFullYear() - 1980) << 4) | (lastModDate.getMonth() + 1)) << 5) | lastModDate.getDate());
5763
- const rawLastModDate = dateArray[0];
5764
- setUint32(headerView, 6, rawLastModDate);
6179
+ headerRecord.uint16(version);
6180
+ headerRecord.uint16(bitFlag);
6181
+ headerRecord.uint16(compressionMethod);
6182
+ if (rawLastModDate === UNDEFINED_VALUE) {
6183
+ const dateArray = new Uint32Array(1);
6184
+ const dateView = getDataView(dateArray);
6185
+ setUint16(dateView, 0, (((lastModDate.getHours() << 6) | lastModDate.getMinutes()) << 5) | lastModDate.getSeconds() / 2);
6186
+ setUint16(dateView, 2, ((((lastModDate.getFullYear() - 1980) << 4) | (lastModDate.getMonth() + 1)) << 5) | lastModDate.getDate());
6187
+ rawLastModDate = dateArray[0];
6188
+ }
6189
+ headerRecord.uint32(rawLastModDate);
6190
+ headerRecord.skip(4);
5765
6191
  if (zip64CompressedSize || compressedSize !== UNDEFINED_VALUE) {
5766
- setUint32(headerView, HEADER_OFFSET_COMPRESSED_SIZE, zip64CompressedSize ? MAX_32_BITS : compressedSize);
6192
+ headerRecord.uint32(zip64CompressedSize ? MAX_32_BITS : compressedSize);
6193
+ } else {
6194
+ headerRecord.skip(4);
5767
6195
  }
5768
6196
  if (zip64UncompressedSize || uncompressedSize !== UNDEFINED_VALUE) {
5769
- setUint32(headerView, HEADER_OFFSET_UNCOMPRESSED_SIZE, zip64UncompressedSize ? MAX_32_BITS : uncompressedSize);
6197
+ headerRecord.uint32(zip64UncompressedSize ? MAX_32_BITS : uncompressedSize);
6198
+ } else {
6199
+ headerRecord.skip(4);
5770
6200
  }
5771
- setUint16(headerView, 22, getLength(rawFilename));
5772
- setUint16(headerView, 24, extraFieldLength);
6201
+ headerRecord.uint16(getLength(rawFilename));
6202
+ headerRecord.uint16(extraFieldLength);
5773
6203
  return {
5774
6204
  headerArray,
5775
6205
  headerView,
@@ -5831,12 +6261,15 @@
5831
6261
  */
5832
6262
 
5833
6263
 
6264
+ const ERR_ENTRY_EXISTS = "Entry filename already exists";
6265
+ const ERR_READABLE_CONSUMED = "Readable stream already consumed";
6266
+
5834
6267
  class ZipEntry {
5835
6268
 
5836
6269
  constructor(fs, name, params, parent) {
5837
6270
  const zipEntry = this;
5838
6271
  if (fs.root && parent && parent.getChildByName(name)) {
5839
- throw new Error("Entry filename already exists");
6272
+ throw new Error(ERR_ENTRY_EXISTS);
5840
6273
  }
5841
6274
  if (!params) {
5842
6275
  params = {};
@@ -5846,13 +6279,17 @@
5846
6279
  name,
5847
6280
  data: params.data,
5848
6281
  options: params.options,
5849
- id: fs.entries.length,
6282
+ id: fs.entryIdCounter++,
5850
6283
  parent,
5851
6284
  children: [],
5852
6285
  uncompressedSize: params.uncompressedSize || 0,
5853
6286
  passThrough: params.passThrough
5854
6287
  });
5855
- fs.entries.push(zipEntry);
6288
+ // detached entries (e.g. clones) are not registered until they are attached,
6289
+ // otherwise they could never be garbage collected
6290
+ if (parent || !fs.root) {
6291
+ fs.entries[zipEntry.id] = zipEntry;
6292
+ }
5856
6293
  if (parent) {
5857
6294
  zipEntry.parent.children.push(zipEntry);
5858
6295
  }
@@ -5890,7 +6327,7 @@
5890
6327
  rename(name) {
5891
6328
  const parent = this.parent;
5892
6329
  if (parent && parent.getChildByName(name)) {
5893
- throw new Error("Entry filename already exists");
6330
+ throw new Error(ERR_ENTRY_EXISTS);
5894
6331
  } else {
5895
6332
  this.name = name;
5896
6333
  }
@@ -5929,16 +6366,17 @@
5929
6366
  }
5930
6367
 
5931
6368
  isPasswordProtected() {
5932
- return this.data.encrypted;
6369
+ return Boolean(this.data && this.data.encrypted);
5933
6370
  }
5934
6371
 
5935
6372
  async checkPassword(password, options = {}) {
5936
6373
  const zipEntry = this;
5937
6374
  if (zipEntry.isPasswordProtected()) {
5938
- options.password = password;
5939
- options.checkPasswordOnly = true;
5940
6375
  try {
5941
- await zipEntry.data.getData(null, options);
6376
+ await zipEntry.data.getData(null, Object.assign({}, options, {
6377
+ password,
6378
+ checkPasswordOnly: true
6379
+ }));
5942
6380
  return true;
5943
6381
  } catch (error) {
5944
6382
  if (error.message == ERR_INVALID_PASSWORD) {
@@ -5972,8 +6410,15 @@
5972
6410
  return this.getData({ writable }, options);
5973
6411
  }
5974
6412
 
5975
- getArrayBuffer(options) {
5976
- return this.data.arrayBuffer(options);
6413
+ async getArrayBuffer(options) {
6414
+ // this.data may be a string, a Uint8Array, a data URI or a Blob depending on how the entry
6415
+ // was created, so retrieve the bytes through getUint8Array rather than assuming a Blob; this
6416
+ // also applies the options (password, signal, onprogress...) like the other getData() getters
6417
+ const array = await this.getUint8Array(options);
6418
+ // return the backing buffer directly when the array spans it, slicing the exact bytes otherwise
6419
+ return array.byteOffset || array.byteLength != array.buffer.byteLength
6420
+ ? array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength)
6421
+ : array.buffer;
5977
6422
  }
5978
6423
 
5979
6424
  replaceBlob(blob) {
@@ -6015,7 +6460,7 @@
6015
6460
  replaceReadable(readable) {
6016
6461
  Object.assign(this, {
6017
6462
  data: null,
6018
- Reader: function () { return { readable }; },
6463
+ Reader: getReadableReader(readable),
6019
6464
  Writer: null,
6020
6465
  reader: null
6021
6466
  });
@@ -6031,7 +6476,7 @@
6031
6476
 
6032
6477
  clone(deepClone) {
6033
6478
  const zipEntry = this;
6034
- const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name);
6479
+ const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name, zipEntry);
6035
6480
  if (deepClone) {
6036
6481
  clonedEntry.children = zipEntry.children.map(child => {
6037
6482
  const childClone = child.clone(deepClone);
@@ -6105,7 +6550,7 @@
6105
6550
 
6106
6551
  addReadable(name, readable, options = {}) {
6107
6552
  return addChild(this, name, {
6108
- Reader: function () { return { readable }; },
6553
+ Reader: getReadableReader(readable),
6109
6554
  options
6110
6555
  });
6111
6556
  }
@@ -6119,6 +6564,7 @@
6119
6564
  }
6120
6565
 
6121
6566
  addFile(file, options = {}) {
6567
+ options = Object.assign({}, options);
6122
6568
  if (!options.lastModDate) {
6123
6569
  options.lastModDate = new Date(file.lastModified);
6124
6570
  }
@@ -6185,11 +6631,15 @@
6185
6631
  try {
6186
6632
  const path = entry.filename.split("/");
6187
6633
  const name = path.pop();
6188
- path.forEach((pathPart, pathIndex) => {
6634
+ path.forEach(pathPart => {
6189
6635
  const previousParent = parent;
6190
6636
  parent = parent.getChildByName(pathPart);
6191
- if (!parent) {
6192
- parent = new ZipDirectoryEntry(this.fs, pathPart, { data: pathIndex == path.length - 1 ? entry : null }, previousParent);
6637
+ if (parent) {
6638
+ if (!parent.directory) {
6639
+ throw new Error(ERR_ENTRY_EXISTS);
6640
+ }
6641
+ } else {
6642
+ parent = new ZipDirectoryEntry(this.fs, pathPart, { data: null }, previousParent);
6193
6643
  importedEntries.push(parent);
6194
6644
  }
6195
6645
  });
@@ -6200,6 +6650,22 @@
6200
6650
  uncompressedSize: entry.uncompressedSize,
6201
6651
  passThrough: options.passThrough
6202
6652
  }));
6653
+ } else {
6654
+ let directoryEntry = parent;
6655
+ if (name) {
6656
+ directoryEntry = parent.getChildByName(name);
6657
+ if (directoryEntry) {
6658
+ if (!directoryEntry.directory) {
6659
+ throw new Error(ERR_ENTRY_EXISTS);
6660
+ }
6661
+ } else {
6662
+ directoryEntry = new ZipDirectoryEntry(this.fs, name, { data: null }, parent);
6663
+ importedEntries.push(directoryEntry);
6664
+ }
6665
+ }
6666
+ if (directoryEntry != this && !directoryEntry.data) {
6667
+ directoryEntry.data = entry;
6668
+ }
6203
6669
  }
6204
6670
  } catch (error) {
6205
6671
  try {
@@ -6215,14 +6681,15 @@
6215
6681
  return importedEntries;
6216
6682
  }
6217
6683
 
6218
- async exportZip(writer, options) {
6684
+ async exportZip(writer, options = {}) {
6219
6685
  const zipEntry = this;
6686
+ options = Object.assign({}, options);
6220
6687
  if (options.bufferedWrite === UNDEFINED_VALUE) {
6221
6688
  options.bufferedWrite = true;
6222
6689
  }
6223
- await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
6690
+ const [readers] = await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
6224
6691
  const zipWriter = new ZipWriter(writer, options);
6225
- await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options);
6692
+ await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options, readers);
6226
6693
  await zipWriter.close();
6227
6694
  return writer.getData ? writer.getData() : writer.writable;
6228
6695
  }
@@ -6268,7 +6735,15 @@
6268
6735
 
6269
6736
  remove(entry) {
6270
6737
  detach(entry);
6271
- this.entries[entry.id] = null;
6738
+ const removedEntries = [entry];
6739
+ while (removedEntries.length) {
6740
+ const removedEntry = removedEntries.pop();
6741
+ this.entries[removedEntry.id] = null;
6742
+ for (const child of removedEntry.children) {
6743
+ removedEntries.push(child);
6744
+ }
6745
+ }
6746
+ entry.parent = UNDEFINED_VALUE;
6272
6747
  }
6273
6748
 
6274
6749
  move(entry, destination) {
@@ -6278,12 +6753,17 @@
6278
6753
  if (destination.directory) {
6279
6754
  if (!destination.isDescendantOf(entry)) {
6280
6755
  if (entry != destination) {
6281
- if (destination.getChildByName(entry.name)) {
6282
- throw new Error("Entry filename already exists");
6756
+ const existingChild = destination.getChildByName(entry.name);
6757
+ if (existingChild) {
6758
+ if (existingChild != entry) {
6759
+ throw new Error(ERR_ENTRY_EXISTS);
6760
+ }
6761
+ } else {
6762
+ detach(entry);
6763
+ entry.parent = destination;
6764
+ destination.children.push(entry);
6765
+ registerEntries(this, entry);
6283
6766
  }
6284
- detach(entry);
6285
- entry.parent = destination;
6286
- destination.children.push(entry);
6287
6767
  }
6288
6768
  } else {
6289
6769
  throw new Error("Entry is a ancestor of target entry");
@@ -6300,6 +6780,11 @@
6300
6780
  for (let index = 0; node && index < path.length; index++) {
6301
6781
  node = node.getChildByName(path[index]);
6302
6782
  }
6783
+ if (!node) {
6784
+ // exclude detached entries, e.g. clones registered in `entries` but not attached
6785
+ node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
6786
+ entry.getRelativeName() == fullname);
6787
+ }
6303
6788
  return node;
6304
6789
  }
6305
6790
 
@@ -6381,6 +6866,7 @@
6381
6866
  }
6382
6867
 
6383
6868
  importZip(reader, options) {
6869
+ resetFS(this);
6384
6870
  return this.root.importZip(reader, options);
6385
6871
  }
6386
6872
 
@@ -6400,6 +6886,10 @@
6400
6886
  return this.root.exportWritable(writable, options);
6401
6887
  }
6402
6888
 
6889
+ exportZip(writer, options) {
6890
+ return this.root.exportZip(writer, options);
6891
+ }
6892
+
6403
6893
  isPasswordProtected() {
6404
6894
  return this.root.isPasswordProtected();
6405
6895
  }
@@ -6413,15 +6903,26 @@
6413
6903
 
6414
6904
  function getTotalSize(entries, propertyName) {
6415
6905
  let size = 0;
6416
- entries.forEach(process);
6906
+ const pendingEntries = Array.from(entries);
6907
+ while (pendingEntries.length) {
6908
+ const entry = pendingEntries.pop();
6909
+ size += entry[propertyName] || 0;
6910
+ for (const child of entry.children) {
6911
+ pendingEntries.push(child);
6912
+ }
6913
+ }
6417
6914
  return size;
6915
+ }
6418
6916
 
6419
- function process(entry) {
6420
- size += entry[propertyName];
6421
- if (entry.children) {
6422
- entry.children.forEach(process);
6917
+ function getReadableReader(readable) {
6918
+ let consumed;
6919
+ return function () {
6920
+ if (consumed) {
6921
+ throw new Error(ERR_READABLE_CONSUMED);
6423
6922
  }
6424
- }
6923
+ consumed = true;
6924
+ return { readable };
6925
+ };
6425
6926
  }
6426
6927
 
6427
6928
  function getZipBlobReader(options) {
@@ -6436,7 +6937,7 @@
6436
6937
  async init() {
6437
6938
  const zipBlobReader = this;
6438
6939
  zipBlobReader.size = zipBlobReader.entry.uncompressedSize;
6439
- const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, zipBlobReader.options, options));
6940
+ const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, options, zipBlobReader.options));
6440
6941
  zipBlobReader.data = data;
6441
6942
  zipBlobReader.blobReader = new BlobReader(data);
6442
6943
  super.init();
@@ -6449,29 +6950,41 @@
6449
6950
  }
6450
6951
 
6451
6952
  async function initReaders(entry, options) {
6452
- if (entry.children.length) {
6453
- await Promise.all(entry.children.map(async child => {
6953
+ const fileEntries = [];
6954
+ const pendingEntries = [entry];
6955
+ // per-export map so concurrent exports do not share (and lock) the same readers
6956
+ const readers = new Map();
6957
+ while (pendingEntries.length) {
6958
+ const pendingEntry = pendingEntries.pop();
6959
+ for (const child of pendingEntry.children) {
6454
6960
  if (child.directory) {
6455
- await initReaders(child, options);
6961
+ pendingEntries.push(child);
6456
6962
  } else {
6457
- const reader = child.reader = new child.Reader(child.data, options);
6458
- try {
6459
- await initStream(reader);
6460
- } catch (error) {
6461
- try {
6462
- error.entryId = child.id;
6463
- error.cause = {
6464
- entry: child
6465
- };
6466
- } catch {
6467
- // ignored
6468
- }
6469
- throw error;
6470
- }
6471
- child.uncompressedSize = reader.size;
6963
+ fileEntries.push(child);
6472
6964
  }
6473
- }));
6965
+ }
6474
6966
  }
6967
+ await Promise.all(fileEntries.map(async child => {
6968
+ const reader = child.reader = new child.Reader(child.data, options);
6969
+ readers.set(child, reader);
6970
+ try {
6971
+ await initStream(reader);
6972
+ } catch (error) {
6973
+ try {
6974
+ error.entryId = child.id;
6975
+ error.cause = {
6976
+ entry: child
6977
+ };
6978
+ } catch {
6979
+ // ignored
6980
+ }
6981
+ throw error;
6982
+ }
6983
+ if (reader.size !== UNDEFINED_VALUE) {
6984
+ child.uncompressedSize = reader.size;
6985
+ }
6986
+ }));
6987
+ return readers;
6475
6988
  }
6476
6989
 
6477
6990
  function detach(entry) {
@@ -6485,7 +6998,7 @@
6485
6998
  }
6486
6999
  }
6487
7000
 
6488
- async function exportZip(zipWriter, entry, totalSize, options) {
7001
+ async function exportZip(zipWriter, entry, totalSize, options, readers) {
6489
7002
  const selectedEntry = entry;
6490
7003
  const entryOffsets = new Map();
6491
7004
  await process(zipWriter, entry);
@@ -6495,7 +7008,11 @@
6495
7008
 
6496
7009
  async function exportChild() {
6497
7010
  if (options.bufferedWrite) {
6498
- await Promise.allSettled(entry.children.map(processChild));
7011
+ const results = await Promise.allSettled(entry.children.map(processChild));
7012
+ const errorResult = results.find(result => result.status == "rejected");
7013
+ if (errorResult) {
7014
+ throw errorResult.reason;
7015
+ }
6499
7016
  } else {
6500
7017
  for (const child of entry.children) {
6501
7018
  await processChild(child);
@@ -6550,7 +7067,7 @@
6550
7067
  });
6551
7068
  }
6552
7069
  }
6553
- await zipWriter.add(name, child.reader, Object.assign({}, options, zipEntryOptions, childOptions, {
7070
+ await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
6554
7071
  directory: child.directory,
6555
7072
  onprogress: async indexProgress => {
6556
7073
  if (options.onprogress) {
@@ -6599,7 +7116,7 @@
6599
7116
  }
6600
7117
  } catch (error) {
6601
7118
  const message = error.message + (handle ? " (" + handle.name + ")" : "");
6602
- throw new Error(message);
7119
+ throw new Error(message, { cause: error });
6603
7120
  }
6604
7121
  }
6605
7122
  return addedEntries;
@@ -6659,9 +7176,21 @@
6659
7176
 
6660
7177
  function resetFS(fs) {
6661
7178
  fs.entries = [];
7179
+ fs.entryIdCounter = 0;
6662
7180
  fs.root = new ZipDirectoryEntry(fs);
6663
7181
  }
6664
7182
 
7183
+ function registerEntries(fs, entry) {
7184
+ const pendingEntries = [entry];
7185
+ while (pendingEntries.length) {
7186
+ const pendingEntry = pendingEntries.pop();
7187
+ fs.entries[pendingEntry.id] = pendingEntry;
7188
+ for (const child of pendingEntry.children) {
7189
+ pendingEntries.push(child);
7190
+ }
7191
+ }
7192
+ }
7193
+
6665
7194
  function addChild(parent, name, params, directory) {
6666
7195
  if (parent.directory) {
6667
7196
  return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent);