@zip.js/zip.js 2.8.26 → 2.8.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/BENCHMARKS.md +168 -0
  2. package/README.md +2 -2
  3. package/deno.json +1 -1
  4. package/dist/zip-core.js +1113 -574
  5. package/dist/zip-core.min.js +1 -1
  6. package/dist/zip-fs-core.js +1209 -622
  7. package/dist/zip-fs-core.min.js +1 -1
  8. package/dist/zip-fs-native.js +1270 -632
  9. package/dist/zip-fs-native.min.js +1 -1
  10. package/dist/zip-fs.js +1347 -686
  11. package/dist/zip-fs.min.js +1 -1
  12. package/dist/zip-legacy.js +1114 -575
  13. package/dist/zip-legacy.min.js +1 -1
  14. package/dist/zip-module.wasm +0 -0
  15. package/dist/zip-native.js +1116 -577
  16. package/dist/zip-native.min.js +1 -1
  17. package/dist/zip-web-worker-native.js +1 -1
  18. package/dist/zip-web-worker.js +1 -1
  19. package/dist/zip.js +1193 -631
  20. package/dist/zip.min.js +1 -1
  21. package/eslint.config.mjs +6 -1
  22. package/index-native.cjs +1270 -632
  23. package/index-native.min.js +1 -1
  24. package/index.cjs +1347 -686
  25. package/index.d.ts +2451 -2355
  26. package/index.min.js +1 -1
  27. package/lib/core/codec-pool.js +39 -1
  28. package/lib/core/codec-worker.js +130 -35
  29. package/lib/core/configuration.js +3 -0
  30. package/lib/core/constants.js +6 -0
  31. package/lib/core/io.js +55 -25
  32. package/lib/core/options.js +2 -0
  33. package/lib/core/streams/aes-crypto-stream.js +12 -12
  34. package/lib/core/streams/codec-stream.js +11 -5
  35. package/lib/core/streams/codecs/sjcl.js +1 -33
  36. package/lib/core/streams/common-crypto.js +4 -4
  37. package/lib/core/streams/zip-crypto-stream.js +25 -15
  38. package/lib/core/streams/zip-entry-stream.js +89 -5
  39. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  40. package/lib/core/streams/zlib-wasm/zlib-streams.js +79 -56
  41. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  42. package/lib/core/util/decode-cp437.js +1 -1
  43. package/lib/core/util/decode-text.js +2 -1
  44. package/lib/core/web-worker-base.js +12 -4
  45. package/lib/core/web-worker-inline-native.js +1 -1
  46. package/lib/core/web-worker-inline-wasm.js +1 -1
  47. package/lib/core/zip-fs.js +154 -55
  48. package/lib/core/zip-reader.js +185 -67
  49. package/lib/core/zip-writer.js +613 -376
  50. package/lib/core/zlib-streams-inline.js +1 -1
  51. package/lib/zip-core-reader.js +3 -1
  52. package/lib/zip-core-writer.js +1 -0
  53. package/package.json +20 -6
@@ -43,6 +43,7 @@ import {
43
43
  DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH,
44
44
  LOCAL_FILE_HEADER_SIGNATURE,
45
45
  CENTRAL_FILE_HEADER_SIGNATURE,
46
+ CENTRAL_FILE_HEADER_LENGTH,
46
47
  END_OF_CENTRAL_DIR_SIGNATURE,
47
48
  ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE,
48
49
  ZIP64_END_OF_CENTRAL_DIR_SIGNATURE,
@@ -93,6 +94,7 @@ import {
93
94
  ERR_INVALID_SIGNATURE,
94
95
  ERR_INVALID_PASSWORD,
95
96
  ERR_INVALID_UNCOMPRESSED_SIZE,
97
+ ERR_INVALID_COMPRESSED_DATA,
96
98
  ERR_ABORT_CHECK_PASSWORD
97
99
  } from "./codec-pool.js";
98
100
  import {
@@ -134,6 +136,7 @@ import {
134
136
  OPTION_CHECK_PASSWORD_ONLY,
135
137
  OPTION_CHECK_OVERLAPPING_ENTRY_ONLY,
136
138
  OPTION_CHECK_OVERLAPPING_ENTRY,
139
+ OPTION_CHECK_AMBIGUITY,
137
140
  OPTION_CHECK_SIGNATURE,
138
141
  OPTION_USE_WEB_WORKERS,
139
142
  OPTION_USE_COMPRESSION_STREAM,
@@ -152,9 +155,13 @@ const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
152
155
  const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
153
156
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
154
157
  const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
158
+ const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
155
159
  const CHARSET_UTF8 = "utf-8";
156
160
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
157
161
  const CHARSET_CP437 = "cp437";
162
+ // bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
163
+ // because they are informative only
164
+ const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
158
165
  const ZIP64_PROPERTIES = [
159
166
  [PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
160
167
  [PROPERTY_NAME_COMPRESSED_SIZE, MAX_32_BITS],
@@ -179,7 +186,7 @@ class ZipReader {
179
186
  reader: new GenericReader(reader),
180
187
  options,
181
188
  config: getConfiguration(),
182
- readRanges: []
189
+ readRanges: new Map()
183
190
  });
184
191
  }
185
192
 
@@ -212,21 +219,30 @@ class ZipReader {
212
219
  const commentOffset = endOfDirectoryInfo.offset;
213
220
  const commentLength = getUint16(endOfDirectoryView, 20);
214
221
  const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
222
+ const checkAmbiguity = getOptionValue(zipReader, options, OPTION_CHECK_AMBIGUITY);
223
+ if (checkAmbiguity && appendedDataOffset != reader.size) {
224
+ throwAmbiguousArchive("appended data");
225
+ }
215
226
  let lastDiskNumber = getUint16(endOfDirectoryView, 4);
216
227
  const expectedLastDiskNumber = reader.lastDiskNumber || 0;
217
228
  let diskNumber = getUint16(endOfDirectoryView, 6);
218
- let filesLength = getUint16(endOfDirectoryView, 8);
229
+ let filesLength = getUint16(endOfDirectoryView, 10);
219
230
  let prependedDataLength = 0;
220
- let startOffset = 0;
231
+ let startOffset;
232
+ let zip64EndOfDirectory;
221
233
  if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
222
- const endOfDirectoryLocatorArray = await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH);
234
+ const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
235
+ await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
236
+ new Uint8Array();
223
237
  const endOfDirectoryLocatorView = getDataView(endOfDirectoryLocatorArray);
224
- if (getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
238
+ if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
239
+ getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
225
240
  directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
226
241
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
227
242
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
228
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
229
- if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
243
+ const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH - (reader.lastDiskOffset || 0);
244
+ if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
245
+ directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
230
246
  const originalDirectoryDataOffset = directoryDataOffset;
231
247
  directoryDataOffset = expectedDirectoryDataOffset;
232
248
  if (directoryDataOffset > originalDirectoryDataOffset) {
@@ -235,24 +251,36 @@ class ZipReader {
235
251
  endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
236
252
  endOfDirectoryView = getDataView(endOfDirectoryArray);
237
253
  }
238
- if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
254
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
239
255
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
240
256
  }
257
+ zip64EndOfDirectory = true;
241
258
  if (lastDiskNumber == MAX_16_BITS) {
242
259
  lastDiskNumber = getUint32(endOfDirectoryView, 16);
260
+ } else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
261
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
243
262
  }
244
263
  if (diskNumber == MAX_16_BITS) {
245
264
  diskNumber = getUint32(endOfDirectoryView, 20);
265
+ } else if (checkAmbiguity && diskNumber != getUint32(endOfDirectoryView, 20)) {
266
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
246
267
  }
247
268
  if (filesLength == MAX_16_BITS) {
248
269
  filesLength = getBigUint64(endOfDirectoryView, 32);
270
+ } else if (checkAmbiguity && filesLength != getBigUint64(endOfDirectoryView, 32)) {
271
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
249
272
  }
250
273
  if (directoryDataLength == MAX_32_BITS) {
251
274
  directoryDataLength = getBigUint64(endOfDirectoryView, 40);
275
+ } else if (checkAmbiguity && directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
276
+ throwAmbiguousArchive("mismatched zip64 end of central directory record");
252
277
  }
253
- directoryDataOffset -= directoryDataLength;
278
+ directoryDataOffset = getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
254
279
  }
255
280
  }
281
+ const declaredDirectoryDataLength = directoryDataLength;
282
+ const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
283
+ (zip64EndOfDirectory ? ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
256
284
  if (directoryDataOffset >= reader.size) {
257
285
  prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
258
286
  directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
@@ -267,8 +295,11 @@ class ZipReader {
267
295
  let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
268
296
  let directoryView = getDataView(directoryArray);
269
297
  if (directoryDataLength) {
270
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - directoryDataLength;
271
- if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
298
+ if (directoryArray.length < 4) {
299
+ throw new Error(ERR_BAD_FORMAT);
300
+ }
301
+ const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
302
+ if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
272
303
  const originalDirectoryDataOffset = directoryDataOffset;
273
304
  directoryDataOffset = expectedDirectoryDataOffset;
274
305
  if (directoryDataOffset > originalDirectoryDataOffset) {
@@ -278,8 +309,8 @@ class ZipReader {
278
309
  directoryView = getDataView(directoryArray);
279
310
  }
280
311
  }
281
- const expectedDirectoryDataLength = endOfDirectoryInfo.offset - directoryDataOffset - (reader.lastDiskOffset || 0);
282
- if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0) {
312
+ const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
313
+ if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
283
314
  directoryDataLength = expectedDirectoryDataLength;
284
315
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
285
316
  directoryView = getDataView(directoryArray);
@@ -287,16 +318,19 @@ class ZipReader {
287
318
  if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
288
319
  throw new Error(ERR_BAD_FORMAT);
289
320
  }
321
+ startOffset = directoryDataOffset;
290
322
  const filenameEncoding = getOptionValue(zipReader, options, OPTION_FILENAME_ENCODING);
291
323
  const commentEncoding = getOptionValue(zipReader, options, OPTION_COMMENT_ENCODING);
324
+ const filenames = checkAmbiguity ? new Set() : UNDEFINED_VALUE;
325
+ let duplicateFilename;
292
326
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
293
327
  const fileEntry = new ZipEntry(reader, config, zipReader.options);
294
- if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
328
+ if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
295
329
  throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
296
330
  }
297
331
  readCommonHeader(fileEntry, directoryView, offset + 6);
298
332
  const languageEncodingFlag = Boolean(fileEntry.bitFlag.languageEncodingFlag);
299
- const filenameOffset = offset + 46;
333
+ const filenameOffset = offset + CENTRAL_FILE_HEADER_LENGTH;
300
334
  const extraFieldOffset = filenameOffset + fileEntry.filenameLength;
301
335
  const commentOffset = extraFieldOffset + fileEntry.extraFieldLength;
302
336
  const versionMadeBy = getUint16(directoryView, offset + 4);
@@ -317,7 +351,7 @@ class ZipReader {
317
351
  directory: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_DIR_MASK),
318
352
  archive: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_ARCHIVE_MASK)
319
353
  };
320
- const offsetFileEntry = getUint32(directoryView, offset + 42) + prependedDataLength;
354
+ const offsetFileEntry = getUint32(directoryView, offset + 42);
321
355
  const decode = getOptionValue(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
322
356
  const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
323
357
  const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
@@ -330,6 +364,7 @@ class ZipReader {
330
364
  comment = decodeText(rawComment, rawCommentEncoding);
331
365
  }
332
366
  Object.assign(fileEntry, {
367
+ index: indexFile,
333
368
  versionMadeBy,
334
369
  msDosCompatible,
335
370
  compressedSize: 0,
@@ -349,8 +384,15 @@ class ZipReader {
349
384
  filename,
350
385
  comment
351
386
  });
352
- startOffset = Math.max(offsetFileEntry, startOffset);
353
387
  readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
388
+ fileEntry.offset += prependedDataLength;
389
+ startOffset = Math.min(fileEntry.offset, startOffset);
390
+ if (checkAmbiguity) {
391
+ if (filenames.has(fileEntry.filename)) {
392
+ duplicateFilename = true;
393
+ }
394
+ filenames.add(fileEntry.filename);
395
+ }
354
396
  const unixExternalUpper = (fileEntry.externalFileAttributes >> 16) & MAX_16_BITS;
355
397
  if (fileEntry.unixMode === UNDEFINED_VALUE && (unixExternalUpper & (FILE_ATTR_UNIX_DEFAULT_MASK | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_TYPE_DIR)) != 0) {
356
398
  fileEntry.unixMode = unixExternalUpper;
@@ -371,7 +413,7 @@ class ZipReader {
371
413
  internalFileAttribute: fileEntry.internalFileAttributes,
372
414
  externalFileAttribute: fileEntry.externalFileAttributes,
373
415
  executable,
374
- directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
416
+ directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (fileEntry.filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
375
417
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
376
418
  });
377
419
  const entry = new Entry(fileEntry);
@@ -394,6 +436,15 @@ class ZipReader {
394
436
  }
395
437
  yield entry;
396
438
  }
439
+ if (checkAmbiguity && offset != declaredDirectoryDataLength) {
440
+ throwAmbiguousArchive("trailing central directory data");
441
+ }
442
+ if (duplicateFilename) {
443
+ throwAmbiguousArchive("duplicate filename");
444
+ }
445
+ if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > 0))) {
446
+ throwAmbiguousArchive("prepended data");
447
+ }
397
448
  const extractPrependedData = getOptionValue(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
398
449
  const extractAppendedData = getOptionValue(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
399
450
  if (extractPrependedData) {
@@ -433,9 +484,21 @@ class ZipReaderStream {
433
484
  readable: (function () {
434
485
  const { readable, writable } = new TransformStream();
435
486
  if (value.getData) {
436
- value.getData(writable);
487
+ getData();
437
488
  return readable;
438
489
  }
490
+
491
+ async function getData() {
492
+ try {
493
+ await value.getData(writable);
494
+ } catch (error) {
495
+ try {
496
+ await writable.abort(error);
497
+ } catch {
498
+ // ignored
499
+ }
500
+ }
501
+ }
439
502
  })()
440
503
  };
441
504
  delete chunk.getData;
@@ -461,8 +524,10 @@ export {
461
524
  ERR_INVALID_SIGNATURE,
462
525
  ERR_INVALID_PASSWORD,
463
526
  ERR_INVALID_UNCOMPRESSED_SIZE,
527
+ ERR_INVALID_COMPRESSED_DATA,
464
528
  ERR_SPLIT_ZIP_FILE,
465
- ERR_OVERLAPPING_ENTRY
529
+ ERR_OVERLAPPING_ENTRY,
530
+ ERR_AMBIGUOUS_ARCHIVE
466
531
  };
467
532
 
468
533
  class ZipEntry {
@@ -479,6 +544,7 @@ class ZipEntry {
479
544
  const zipEntry = this;
480
545
  const {
481
546
  reader,
547
+ index,
482
548
  offset,
483
549
  diskNumberStart,
484
550
  extraFieldAES,
@@ -510,21 +576,36 @@ class ZipEntry {
510
576
  if ((compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64) && !passThrough) {
511
577
  throw new Error(ERR_UNSUPPORTED_COMPRESSION);
512
578
  }
513
- if (getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
579
+ if (dataArray.length < HEADER_SIZE || getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
514
580
  throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
515
581
  }
516
582
  readCommonHeader(localDirectory, dataView, 4);
517
583
  const {
518
584
  extraFieldLength,
519
- filenameLength,
520
- lastAccessDate,
521
- creationDate
585
+ filenameLength
522
586
  } = localDirectory;
523
- localDirectory.rawExtraField = extraFieldLength ?
524
- await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
525
- new Uint8Array();
587
+ const checkAmbiguity = getOptionValue(zipEntry, options, OPTION_CHECK_AMBIGUITY);
588
+ let rawLocalFilename = new Uint8Array();
589
+ if (checkAmbiguity && (filenameLength || extraFieldLength)) {
590
+ const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
591
+ rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
592
+ localDirectory.rawExtraField = trailingDataArray.subarray(filenameLength);
593
+ } else {
594
+ localDirectory.rawExtraField = extraFieldLength ?
595
+ await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
596
+ new Uint8Array();
597
+ }
526
598
  readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
527
- Object.assign(fileEntry, { lastAccessDate, creationDate });
599
+ if (checkAmbiguity) {
600
+ checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename);
601
+ }
602
+ const { lastAccessDate, creationDate } = localDirectory;
603
+ if (lastAccessDate) {
604
+ fileEntry.lastAccessDate = lastAccessDate;
605
+ }
606
+ if (creationDate) {
607
+ fileEntry.creationDate = creationDate;
608
+ }
528
609
  const encrypted = zipEntry.encrypted && localDirectory.encrypted && !passThrough;
529
610
  const zipCrypto = encrypted && !extraFieldAES;
530
611
  if (!passThrough) {
@@ -570,7 +651,7 @@ class ZipEntry {
570
651
  outputSize: passThrough ? compressedSize : uncompressedSize,
571
652
  signature,
572
653
  compressed: compressionMethod != 0 && !passThrough,
573
- encrypted: zipEntry.encrypted && !passThrough,
654
+ encrypted,
574
655
  useWebWorkers: getOptionValue(zipEntry, options, OPTION_USE_WEB_WORKERS),
575
656
  useCompressionStream,
576
657
  transferStreams: getOptionValue(zipEntry, options, OPTION_TRANSFER_STREAMS),
@@ -584,6 +665,7 @@ class ZipEntry {
584
665
  await detectOverlappingEntry({
585
666
  reader,
586
667
  fileEntry,
668
+ index,
587
669
  offset,
588
670
  diskNumberStart,
589
671
  signature,
@@ -686,7 +768,7 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
686
768
  directory.extraFieldUnicodeComment = extraFieldUnicodeComment;
687
769
  }
688
770
  const extraFieldAES = extraField.get(EXTRAFIELD_TYPE_AES);
689
- if (extraFieldAES) {
771
+ if (extraFieldAES && extraFieldAES.data.length >= 7) {
690
772
  readExtraFieldAES(extraFieldAES, directory, compressionMethod);
691
773
  directory.extraFieldAES = extraFieldAES;
692
774
  } else {
@@ -723,19 +805,23 @@ function readExtraFieldZip64(extraFieldZip64, directory) {
723
805
  directory.zip64 = true;
724
806
  const extraFieldView = getDataView(extraFieldZip64.data);
725
807
  const missingProperties = ZIP64_PROPERTIES.filter(([propertyName, max]) => directory[propertyName] == max);
808
+ const requiredLength = missingProperties.reduce((length, [, max]) => length + ZIP64_EXTRACTION[max].bytes, 0);
809
+ if (extraFieldZip64.data.length < requiredLength) {
810
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
811
+ }
726
812
  for (let indexMissingProperty = 0, offset = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
727
813
  const [propertyName, max] = missingProperties[indexMissingProperty];
728
- if (directory[propertyName] == max) {
729
- const extraction = ZIP64_EXTRACTION[max];
730
- directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
731
- offset += extraction.bytes;
732
- } else if (extraFieldZip64[propertyName]) {
733
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
734
- }
814
+ const extraction = ZIP64_EXTRACTION[max];
815
+ directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
816
+ offset += extraction.bytes;
735
817
  }
736
818
  }
737
819
 
738
820
  function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
821
+ if (extraFieldUnicode.data.length < 5) {
822
+ extraFieldUnicode.valid = false;
823
+ return;
824
+ }
739
825
  const extraFieldView = getDataView(extraFieldUnicode.data);
740
826
  const crc32 = new Crc32();
741
827
  crc32.append(fileEntry[rawPropertyName]);
@@ -808,32 +894,30 @@ function readExtraFieldNTFS(extraFieldNTFS, directory) {
808
894
  function readExtraFieldUnix(extraField, directory, isInfoZip) {
809
895
  try {
810
896
  const view = getDataView(new Uint8Array(extraField.data));
811
- let offset = 0;
812
- const version = getUint8(view, offset++);
813
- const uidSize = getUint8(view, offset++);
814
- const uidBytes = extraField.data.subarray(offset, offset + uidSize);
815
- offset += uidSize;
816
- const uid = unpackUnixId(uidBytes);
817
- const gidSize = getUint8(view, offset++);
818
- const gidBytes = extraField.data.subarray(offset, offset + gidSize);
819
- offset += gidSize;
820
- const gid = unpackUnixId(gidBytes);
821
- let unixMode = UNDEFINED_VALUE;
822
- if (!isInfoZip && offset + 2 <= extraField.data.length) {
823
- const base = extraField.data;
824
- const modeView = new DataView(base.buffer, base.byteOffset + offset, 2);
825
- unixMode = modeView.getUint16(0, true);
826
- }
827
- Object.assign(extraField, { version, uid, gid, unixMode });
897
+ let uid, gid;
898
+ if (isInfoZip) {
899
+ // Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
900
+ let offset = 0;
901
+ const version = getUint8(view, offset++);
902
+ const uidSize = getUint8(view, offset++);
903
+ uid = unpackUnixId(extraField.data.subarray(offset, offset + uidSize));
904
+ offset += uidSize;
905
+ const gidSize = getUint8(view, offset++);
906
+ gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
907
+ Object.assign(extraField, { version, uid, gid });
908
+ } else if (extraField.data.length >= 4) {
909
+ // Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
910
+ // carried in the external file attributes, not here)
911
+ uid = getUint16(view, 0);
912
+ gid = getUint16(view, 2);
913
+ Object.assign(extraField, { uid, gid });
914
+ }
828
915
  if (uid !== UNDEFINED_VALUE) {
829
916
  directory.uid = uid;
830
917
  }
831
918
  if (gid !== UNDEFINED_VALUE) {
832
919
  directory.gid = gid;
833
920
  }
834
- if (unixMode !== UNDEFINED_VALUE) {
835
- directory.unixMode = unixMode;
836
- }
837
921
  } catch {
838
922
  // ignored
839
923
  }
@@ -847,6 +931,9 @@ function unpackUnixId(bytes) {
847
931
  }
848
932
 
849
933
  function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory) {
934
+ if (!extraFieldExtendedTimestamp.data.length) {
935
+ return;
936
+ }
850
937
  const extraFieldView = getDataView(extraFieldExtendedTimestamp.data);
851
938
  const flags = getUint8(extraFieldView, 0);
852
939
  const timeProperties = [];
@@ -872,7 +959,8 @@ function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory,
872
959
  timeProperties.forEach((propertyName, indexProperty) => {
873
960
  if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
874
961
  const time = getUint32(extraFieldView, offset);
875
- directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date(time * 1000);
962
+ // the extended timestamp is a signed 32-bit time_t value
963
+ directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
876
964
  const rawPropertyName = timeRawProperties[indexProperty];
877
965
  extraFieldExtendedTimestamp[rawPropertyName] = time;
878
966
  }
@@ -883,6 +971,7 @@ function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory,
883
971
  async function detectOverlappingEntry({
884
972
  reader,
885
973
  fileEntry,
974
+ index,
886
975
  offset,
887
976
  diskNumberStart,
888
977
  signature,
@@ -894,10 +983,9 @@ async function detectOverlappingEntry({
894
983
  readRanges
895
984
  }) {
896
985
  let diskOffset = 0;
897
- if (diskNumberStart) {
898
- for (let indexReader = 0; indexReader < diskNumberStart; indexReader++) {
899
- const diskReader = reader.readers[indexReader];
900
- diskOffset += diskReader.size;
986
+ if (diskNumberStart && reader.readers) {
987
+ for (let indexReader = 0; indexReader < Math.min(diskNumberStart, reader.readers.length); indexReader++) {
988
+ diskOffset += reader.readers[indexReader].size;
901
989
  }
902
990
  }
903
991
  let dataDescriptorLength = 0;
@@ -910,7 +998,8 @@ async function detectOverlappingEntry({
910
998
  }
911
999
  if (dataDescriptorLength) {
912
1000
  const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH, diskNumberStart);
913
- const dataDescriptorSignature = getUint32(getDataView(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
1001
+ const dataDescriptorSignature = dataDescriptorArray.length == dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH &&
1002
+ getUint32(getDataView(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
914
1003
  if (dataDescriptorSignature) {
915
1004
  const readSignature = getUint32(getDataView(dataDescriptorArray), 4);
916
1005
  let readCompressedSize;
@@ -935,14 +1024,14 @@ async function detectOverlappingEntry({
935
1024
  end: diskOffset + dataOffset + compressedSize + dataDescriptorLength,
936
1025
  fileEntry
937
1026
  };
938
- for (const otherRange of readRanges) {
939
- if (otherRange.fileEntry != fileEntry && range.start >= otherRange.start && range.start < otherRange.end) {
1027
+ for (const [otherIndex, otherRange] of readRanges) {
1028
+ if (otherIndex != index && range.start < otherRange.end && otherRange.start < range.end) {
940
1029
  const error = new Error(ERR_OVERLAPPING_ENTRY);
941
1030
  error.overlappingEntry = otherRange.fileEntry;
942
1031
  throw error;
943
1032
  }
944
1033
  }
945
- readRanges.push(range);
1034
+ readRanges.set(index, range);
946
1035
  }
947
1036
 
948
1037
  async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
@@ -967,6 +1056,35 @@ async function seekSignature(reader, signature, startOffset, minimumBytes, maxim
967
1056
  }
968
1057
  }
969
1058
 
1059
+ function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
1060
+ const { rawFilename } = zipEntry;
1061
+ if (rawLocalFilename.length != rawFilename.length ||
1062
+ rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte])) {
1063
+ throwAmbiguousArchive("mismatched local file header (filename)");
1064
+ }
1065
+ if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
1066
+ throwAmbiguousArchive("mismatched local file header (general purpose bit flag)");
1067
+ }
1068
+ if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
1069
+ throwAmbiguousArchive("mismatched local file header (compression method)");
1070
+ }
1071
+ // when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
1072
+ // also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
1073
+ if (!localDirectory.bitFlag.dataDescriptor &&
1074
+ (localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
1075
+ (localDirectory.signature != zipEntry.signature ||
1076
+ localDirectory.compressedSize != zipEntry.compressedSize ||
1077
+ localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
1078
+ throwAmbiguousArchive("mismatched local file header (signature or sizes)");
1079
+ }
1080
+ }
1081
+
1082
+ function throwAmbiguousArchive(reason) {
1083
+ const error = new Error(ERR_AMBIGUOUS_ARCHIVE);
1084
+ error.reason = reason;
1085
+ throw error;
1086
+ }
1087
+
970
1088
  function getOptionValue(zipReader, options, name) {
971
1089
  return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
972
1090
  }
@@ -1005,5 +1123,5 @@ function setUint32(view, offset, value) {
1005
1123
  }
1006
1124
 
1007
1125
  function getDataView(array) {
1008
- return new DataView(array.buffer);
1126
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
1009
1127
  }