@zip.js/zip.js 2.8.57 → 2.8.59

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.
@@ -68,6 +68,7 @@ import {
68
68
  ZIP64_END_OF_CENTRAL_DIR_LENGTH,
69
69
  BITFLAG_ENCRYPTED,
70
70
  BITFLAG_STRONG_ENCRYPTION,
71
+ BITFLAG_COMPRESSED_PATCHED_DATA,
71
72
  BITFLAG_MASKED_LOCAL_HEADERS,
72
73
  BITFLAG_LEVEL,
73
74
  BITFLAG_DATA_DESCRIPTOR,
@@ -93,7 +94,8 @@ import {
93
94
  FILE_ATTR_UNIX_SETGID_MASK,
94
95
  FILE_ATTR_UNIX_STICKY_MASK,
95
96
  NUMBER_TYPE,
96
- EMPTY_UINT8_ARRAY
97
+ EMPTY_UINT8_ARRAY,
98
+ MIN_DATE
97
99
  } from "./constants.js";
98
100
  import { getConfiguration } from "./configuration.js";
99
101
  import { getRegisteredCodec } from "./codec-registry.js";
@@ -195,6 +197,21 @@ const ERR_INVALID_STRICTNESS = "Invalid strictness (must be 'strict', 'balanced'
195
197
  const ERR_INVALID_FILENAME_VALIDATION = "Invalid filenameValidation (must be 'strict', 'balanced' or 'tolerant')";
196
198
  const ERR_INVALID_MAX_APPENDED_DATA_SIZE = "Invalid maxAppendedDataSize (must be a number greater than or equal to 0)";
197
199
  const ERR_UNSUPPORTED_UINT64 = "64-bit value exceeds Number.MAX_SAFE_INTEGER";
200
+ const WARNING_UNSORTED_CENTRAL_DIRECTORY = "unsorted central directory";
201
+ const WARNING_UNKNOWN_VERSION = "unknown version needed to extract";
202
+ const WARNING_COMPRESSED_PATCHED_DATA = "compressed patched data";
203
+ const WARNING_MALFORMED_EXTRA_FIELD = "malformed extra field";
204
+ const WARNING_UNKNOWN_ZIP64_EXTENSIBLE_DATA = "unknown zip64 extensible data";
205
+ const WARNING_WRAPPED_ENTRIES_COUNT = "wrapped entries count";
206
+ const WARNING_APPENDED_DATA = "appended data";
207
+ const WARNING_PREPENDED_DATA = "prepended data";
208
+ const WARNING_TRAILING_CENTRAL_DIRECTORY_DATA = "trailing central directory data";
209
+ const WARNING_DUPLICATE_FILENAME = "duplicate filename";
210
+ const WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY = "mismatched zip64 end of central directory record";
211
+ const WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG = "mismatched local file header (general purpose bit flag)";
212
+ const WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD = "mismatched local file header (compression method)";
213
+ const WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES = "mismatched local file header (crc32 or sizes)";
214
+ const MAX_KNOWN_VERSION = 63;
198
215
  const DRIVE_LETTER_REGEXP = /^[a-zA-Z]:/;
199
216
  const CHARSET_UTF8 = "utf-8";
200
217
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
@@ -245,6 +262,7 @@ class ZipReader {
245
262
  if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
246
263
  throw new Error(ERR_BAD_FORMAT);
247
264
  }
265
+ const warnings = zipReader.warnings = [];
248
266
  const strictness = getStrictness(options, zipReader.options);
249
267
  const checkAmbiguity = strictness == STRICTNESS_STRICT;
250
268
  const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
@@ -268,8 +286,12 @@ class ZipReader {
268
286
  const commentOffset = endOfDirectoryInfo.offset;
269
287
  const commentLength = getUint16(endOfDirectoryView, 20);
270
288
  const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
271
- if (reader.size - appendedDataOffset > maxAppendedDataSize) {
272
- throwAmbiguousArchive("appended data");
289
+ const appendedDataLength = reader.size - appendedDataOffset;
290
+ if (appendedDataLength > maxAppendedDataSize) {
291
+ throwAmbiguousArchive(WARNING_APPENDED_DATA);
292
+ }
293
+ if (appendedDataLength > 0) {
294
+ addWarning(warnings, WARNING_APPENDED_DATA);
273
295
  }
274
296
  let lastDiskNumber = getUint16(endOfDirectoryView, 4);
275
297
  const expectedLastDiskNumber = reader.lastDiskNumber || 0;
@@ -323,23 +345,23 @@ class ZipReader {
323
345
  }
324
346
  if (lastDiskNumber == MAX_16_BITS) {
325
347
  lastDiskNumber = getUint32(endOfDirectoryView, 16);
326
- } else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
327
- throwAmbiguousArchive("mismatched zip64 end of central directory record");
348
+ } else if (lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
349
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY);
328
350
  }
329
351
  if (diskNumber == MAX_16_BITS) {
330
352
  diskNumber = getUint32(endOfDirectoryView, 20);
331
- } else if (checkAmbiguity && diskNumber != getUint32(endOfDirectoryView, 20)) {
332
- throwAmbiguousArchive("mismatched zip64 end of central directory record");
353
+ } else if (diskNumber != getUint32(endOfDirectoryView, 20)) {
354
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY);
333
355
  }
334
356
  if (filesLength == MAX_16_BITS) {
335
357
  filesLength = getBigUint64(endOfDirectoryView, 32);
336
- } else if (checkAmbiguity && filesLength != getBigUint64(endOfDirectoryView, 32)) {
337
- throwAmbiguousArchive("mismatched zip64 end of central directory record");
358
+ } else if (filesLength != getBigUint64(endOfDirectoryView, 32)) {
359
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY);
338
360
  }
339
361
  if (directoryDataLength == MAX_32_BITS) {
340
362
  directoryDataLength = getBigUint64(endOfDirectoryView, 40);
341
- } else if (checkAmbiguity && directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
342
- throwAmbiguousArchive("mismatched zip64 end of central directory record");
363
+ } else if (directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
364
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY);
343
365
  }
344
366
  directoryDataOffset = getDiskOffset(reader, diskNumber) + getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
345
367
  }
@@ -408,11 +430,23 @@ class ZipReader {
408
430
  declaredDirectoryDataLength = directoryArray.length;
409
431
  decryptedDirectory = true;
410
432
  }
433
+ if (directoryEncryptionInfo && !decryptedDirectory &&
434
+ (directoryArray.length < 4 || getUint32(directoryView, 0) == CENTRAL_FILE_HEADER_SIGNATURE)) {
435
+ addWarning(warnings, WARNING_UNKNOWN_ZIP64_EXTENSIBLE_DATA);
436
+ }
411
437
  startOffset = directoryDataOffset;
412
438
  const filenameEncoding = getOptionValue(zipReader, options, OPTION_FILENAME_ENCODING);
413
439
  const commentEncoding = getOptionValue(zipReader, options, OPTION_COMMENT_ENCODING);
414
- const filenames = checkAmbiguity ? new Set() : UNDEFINED_VALUE;
440
+ const filenames = new Set();
415
441
  let duplicateFilename;
442
+ let previousEntryPosition = -1;
443
+ const recoverWrappedFilesLength = !checkAmbiguity && !zip64EndOfDirectory;
444
+ if (!filesLength && recoverWrappedFilesLength) {
445
+ filesLength = getWrappedFilesLength(directoryView, directoryArray, offset);
446
+ if (filesLength) {
447
+ addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
448
+ }
449
+ }
416
450
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
417
451
  const fileEntry = new ZipEntry(reader, zipReader.options);
418
452
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
@@ -490,15 +524,26 @@ class ZipReader {
490
524
  filename,
491
525
  comment
492
526
  });
493
- readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
527
+ if (readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6)) {
528
+ addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD, filename);
529
+ }
494
530
  fileEntry.offset += prependedDataLength;
495
- startOffset = Math.min(getDiskOffset(reader, fileEntry.diskNumberStart) + fileEntry.offset, startOffset);
496
- if (checkAmbiguity) {
497
- if (filenames.has(fileEntry.filename)) {
498
- duplicateFilename = true;
499
- }
500
- filenames.add(fileEntry.filename);
531
+ const entryPosition = getDiskOffset(reader, fileEntry.diskNumberStart) + fileEntry.offset;
532
+ startOffset = Math.min(entryPosition, startOffset);
533
+ if (entryPosition < previousEntryPosition) {
534
+ addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
535
+ }
536
+ previousEntryPosition = entryPosition;
537
+ if ((fileEntry.version & MAX_8_BITS) > MAX_KNOWN_VERSION) {
538
+ addWarning(warnings, WARNING_UNKNOWN_VERSION, filename);
539
+ }
540
+ if ((fileEntry.rawBitFlag & BITFLAG_COMPRESSED_PATCHED_DATA) == BITFLAG_COMPRESSED_PATCHED_DATA) {
541
+ addWarning(warnings, WARNING_COMPRESSED_PATCHED_DATA, filename);
542
+ }
543
+ if (filenames.has(fileEntry.filename)) {
544
+ duplicateFilename = true;
501
545
  }
546
+ filenames.add(fileEntry.filename);
502
547
  const unixExternalUpper = (fileEntry.externalFileAttributes >> 16) & MAX_16_BITS;
503
548
  if (fileEntry.unixMode === UNDEFINED_VALUE && (unixExternalUpper & (FILE_ATTR_UNIX_DEFAULT_MASK | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_TYPE_DIR)) != 0) {
504
549
  fileEntry.unixMode = unixExternalUpper;
@@ -522,7 +567,7 @@ class ZipReader {
522
567
  internalFileAttribute: fileEntry.internalFileAttributes,
523
568
  externalFileAttribute: fileEntry.externalFileAttributes,
524
569
  executable,
525
- directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (fileEntry.filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
570
+ directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || fileEntry.filename.endsWith(DIRECTORY_SIGNATURE),
526
571
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
527
572
  });
528
573
  const entry = new Entry(fileEntry);
@@ -535,6 +580,13 @@ class ZipReader {
535
580
  return arrayBufferPromise;
536
581
  };
537
582
  offset = endOffset;
583
+ if (indexFile == filesLength - 1 && recoverWrappedFilesLength) {
584
+ const wrappedFilesLength = getWrappedFilesLength(directoryView, directoryArray, offset);
585
+ if (wrappedFilesLength) {
586
+ filesLength += wrappedFilesLength;
587
+ addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
588
+ }
589
+ }
538
590
  const { onprogress } = options;
539
591
  if (onprogress) {
540
592
  try {
@@ -559,18 +611,22 @@ class ZipReader {
559
611
  zipReader.digitalSignature = digitalSignature;
560
612
  offsetAfterSignature = offset + 6 + digitalSignature.length;
561
613
  }
562
- if (checkAmbiguity && offset != declaredDirectoryDataLength && offsetAfterSignature != declaredDirectoryDataLength) {
563
- throwAmbiguousArchive("trailing central directory data");
614
+ if ((offset != declaredDirectoryDataLength && offsetAfterSignature != declaredDirectoryDataLength) ||
615
+ (!decryptedDirectory && offset != directoryDataLength && offsetAfterSignature != directoryDataLength)) {
616
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_TRAILING_CENTRAL_DIRECTORY_DATA);
564
617
  }
565
618
  if (duplicateFilename) {
566
- throwAmbiguousArchive("duplicate filename");
619
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_DUPLICATE_FILENAME);
567
620
  }
568
621
  const extractPrependedData = getOptionValue(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
569
622
  const extractAppendedData = getOptionValue(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
570
623
  const splitZipSignatureLength = (checkAmbiguity || extractPrependedData) && filesLength &&
571
624
  startOffset == SPLIT_ZIP_FILE_SIGNATURE_LENGTH && await startsWithSplitZipMarker(reader) ? SPLIT_ZIP_FILE_SIGNATURE_LENGTH : 0;
572
625
  if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > splitZipSignatureLength))) {
573
- throwAmbiguousArchive("prepended data");
626
+ throwAmbiguousArchive(WARNING_PREPENDED_DATA);
627
+ }
628
+ if (prependedDataLength || (filesLength && startOffset > SPLIT_ZIP_FILE_SIGNATURE_LENGTH)) {
629
+ addWarning(warnings, WARNING_PREPENDED_DATA);
574
630
  }
575
631
  if (extractPrependedData) {
576
632
  zipReader.prependedData = startOffset > splitZipSignatureLength ?
@@ -640,8 +696,31 @@ class ZipReaderStream {
640
696
  }
641
697
  }
642
698
 
699
+ async function isZipFile(reader, options = {}) {
700
+ reader = new GenericReader(reader);
701
+ await initStream(reader);
702
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
703
+ reader = new BlobReader(await streamToBlob(reader.readable));
704
+ await initStream(reader);
705
+ }
706
+ if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
707
+ return false;
708
+ }
709
+ const strictness = getStrictness(options, {});
710
+ const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
711
+ const maxAppendedDataSize = getMaxAppendedDataSize(options[OPTION_MAX_APPENDED_DATA_SIZE], strictness);
712
+ const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
713
+ if (!endOfDirectoryInfo || (strictness == STRICTNESS_STRICT && endOfDirectoryReachingEndCount > 1)) {
714
+ return false;
715
+ }
716
+ const commentLength = getUint16(getDataView(endOfDirectoryInfo), 20);
717
+ const appendedDataOffset = endOfDirectoryInfo.offset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
718
+ return reader.size - appendedDataOffset <= maxAppendedDataSize;
719
+ }
720
+
643
721
  export {
644
722
  ZipReader,
723
+ isZipFile,
645
724
  ZipReaderStream,
646
725
  ERR_BAD_FORMAT,
647
726
  ERR_EOCDR_NOT_FOUND,
@@ -668,7 +747,21 @@ export {
668
747
  ERR_INVALID_FILENAME_VALIDATION,
669
748
  ERR_INVALID_MAX_APPENDED_DATA_SIZE,
670
749
  ERR_UNSUPPORTED_UINT64,
671
- ERR_WORKER_STARTUP_TIMEOUT
750
+ ERR_WORKER_STARTUP_TIMEOUT,
751
+ WARNING_UNSORTED_CENTRAL_DIRECTORY,
752
+ WARNING_UNKNOWN_VERSION,
753
+ WARNING_COMPRESSED_PATCHED_DATA,
754
+ WARNING_MALFORMED_EXTRA_FIELD,
755
+ WARNING_UNKNOWN_ZIP64_EXTENSIBLE_DATA,
756
+ WARNING_WRAPPED_ENTRIES_COUNT,
757
+ WARNING_APPENDED_DATA,
758
+ WARNING_PREPENDED_DATA,
759
+ WARNING_TRAILING_CENTRAL_DIRECTORY_DATA,
760
+ WARNING_DUPLICATE_FILENAME,
761
+ WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY,
762
+ WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG,
763
+ WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD,
764
+ WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES
672
765
  };
673
766
 
674
767
  class ZipEntry {
@@ -702,6 +795,7 @@ class ZipEntry {
702
795
  dataDescriptor
703
796
  } = bitFlag;
704
797
  const localDirectory = fileEntry.localDirectory = {};
798
+ const warnings = fileEntry.warnings = [];
705
799
  const localHeaderOffset = getDiskOffset(reader, diskNumberStart) + offset;
706
800
  const dataArray = await readUint8Array(reader, localHeaderOffset, HEADER_SIZE);
707
801
  const dataView = getDataView(dataArray);
@@ -724,6 +818,7 @@ class ZipEntry {
724
818
  extraFieldLength,
725
819
  filenameLength
726
820
  } = localDirectory;
821
+ const dataOffset = localDirectory.dataOffset = localHeaderOffset + HEADER_SIZE + filenameLength + extraFieldLength;
727
822
  const checkLocalDirectoryOption = getOptionValue(zipEntry, options, OPTION_CHECK_LOCAL_DIRECTORY);
728
823
  const entryStrictness = getStrictness(options, zipEntry.options);
729
824
  const checkLocalDirectory = getCheckLocalDirectory(checkLocalDirectoryOption, entryStrictness);
@@ -741,10 +836,10 @@ class ZipEntry {
741
836
  if (checkLocalFilename) {
742
837
  localDirectory.rawFilename = rawLocalFilename;
743
838
  }
744
- readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
745
- if (checkLocalDirectory) {
746
- validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename);
839
+ if (readCommonFooter(zipEntry, localDirectory, dataView, 4, true)) {
840
+ addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD);
747
841
  }
842
+ validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
748
843
  const { lastAccessDate, creationDate, uid, gid } = localDirectory;
749
844
  if (lastAccessDate) {
750
845
  fileEntry.lastAccessDate = lastAccessDate;
@@ -777,7 +872,6 @@ class ZipEntry {
777
872
  throw new Error(ERR_ENCRYPTED);
778
873
  }
779
874
  }
780
- const dataOffset = localHeaderOffset + HEADER_SIZE + filenameLength + extraFieldLength;
781
875
  if (dataOffset + compressedSize > reader.size) {
782
876
  throw new Error(ERR_ENTRY_DATA_OUT_OF_BOUNDS);
783
877
  }
@@ -897,6 +991,16 @@ function detectEncryptedCentralDirectory(directoryView) {
897
991
  return false;
898
992
  }
899
993
 
994
+ function getWrappedFilesLength(directoryView, directoryArray, offset) {
995
+ let wrappedFilesLength = 0;
996
+ while (offset + CENTRAL_FILE_HEADER_LENGTH <= directoryArray.length && getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE) {
997
+ offset += CENTRAL_FILE_HEADER_LENGTH +
998
+ getUint16(directoryView, offset + 28) + getUint16(directoryView, offset + 30) + getUint16(directoryView, offset + 32);
999
+ wrappedFilesLength++;
1000
+ }
1001
+ return wrappedFilesLength % (MAX_16_BITS + 1) ? 0 : wrappedFilesLength;
1002
+ }
1003
+
900
1004
  function readDigitalSignature(signatureRecordArray) {
901
1005
  if (signatureRecordArray.length >= 6) {
902
1006
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -961,6 +1065,7 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
961
1065
  const extraField = directory.extraField = new Map();
962
1066
  const rawExtraFieldView = getDataView(rawExtraField);
963
1067
  let offsetExtraField = 0;
1068
+ let malformedExtraField = false;
964
1069
  try {
965
1070
  while (offsetExtraField < rawExtraField.length) {
966
1071
  const type = getUint16(rawExtraFieldView, offsetExtraField);
@@ -972,7 +1077,10 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
972
1077
  offsetExtraField += 4 + size;
973
1078
  }
974
1079
  } catch {
975
- // ignored
1080
+ malformedExtraField = true;
1081
+ }
1082
+ if (offsetExtraField > rawExtraField.length) {
1083
+ malformedExtraField = true;
976
1084
  }
977
1085
  const compressionMethod = getUint16(dataView, offset + 4);
978
1086
  Object.assign(directory, {
@@ -1040,6 +1148,7 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
1040
1148
  if (extraFieldUSDZ) {
1041
1149
  directory.extraFieldUSDZ = extraFieldUSDZ;
1042
1150
  }
1151
+ return malformedExtraField;
1043
1152
  }
1044
1153
 
1045
1154
  function readExtraFieldZip64(extraFieldZip64, directory) {
@@ -1069,10 +1178,11 @@ function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName,
1069
1178
  const computedCrc32View = getDataView(new Uint8Array(4));
1070
1179
  computedCrc32View.setUint32(0, computedCrc32.get(), true);
1071
1180
  const nameCrc32 = getUint32(extraFieldView, 1);
1181
+ const version = getUint8(extraFieldView, 0);
1072
1182
  Object.assign(extraFieldUnicode, {
1073
- version: getUint8(extraFieldView, 0),
1183
+ version,
1074
1184
  [propertyName]: decodeText(extraFieldUnicode.data.subarray(5)),
1075
- valid: !fileEntry.bitFlag.languageEncodingFlag && nameCrc32 == getUint32(computedCrc32View, 0)
1185
+ valid: version == 1 && !fileEntry.bitFlag.languageEncodingFlag && nameCrc32 == getUint32(computedCrc32View, 0)
1076
1186
  });
1077
1187
  if (extraFieldUnicode.valid) {
1078
1188
  directory[propertyName] = extraFieldUnicode[propertyName];
@@ -1500,27 +1610,46 @@ async function readSignature(reader, view, anchoredOffset, signatureOffset, size
1500
1610
  return UNDEFINED_VALUE;
1501
1611
  }
1502
1612
 
1503
- function validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename) {
1613
+ function validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, warnings) {
1504
1614
  const { rawFilename } = zipEntry;
1615
+ const reject = !warnings;
1505
1616
  const maskedLocalDirectory = zipEntry.decryptedDirectory &&
1506
1617
  (localDirectory.rawBitFlag & BITFLAG_MASKED_LOCAL_HEADERS) == BITFLAG_MASKED_LOCAL_HEADERS;
1507
1618
  if (checkLocalFilename && !maskedLocalDirectory &&
1508
1619
  (rawLocalFilename.length != rawFilename.length ||
1509
1620
  rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte]))) {
1510
- throwAmbiguousArchive("mismatched local file header (filename)");
1621
+ reportAmbiguity(reject, warnings, "mismatched local file header (filename)");
1511
1622
  }
1512
1623
  if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
1513
- throwAmbiguousArchive("mismatched local file header (general purpose bit flag)");
1624
+ reportAmbiguity(reject, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG);
1514
1625
  }
1515
1626
  if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
1516
- throwAmbiguousArchive("mismatched local file header (compression method)");
1627
+ reportAmbiguity(reject, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD);
1517
1628
  }
1518
1629
  if (!localDirectory.bitFlag.dataDescriptor && !maskedLocalDirectory &&
1519
1630
  (localDirectory.crc32 || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
1520
1631
  (localDirectory.crc32 != zipEntry.crc32 ||
1521
1632
  localDirectory.compressedSize != zipEntry.compressedSize ||
1522
1633
  localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
1523
- throwAmbiguousArchive("mismatched local file header (crc32 or sizes)");
1634
+ reportAmbiguity(reject, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES);
1635
+ }
1636
+ }
1637
+
1638
+ function reportAmbiguity(reject, warnings, reason) {
1639
+ if (reject) {
1640
+ throwAmbiguousArchive(reason);
1641
+ } else {
1642
+ addWarning(warnings, reason);
1643
+ }
1644
+ }
1645
+
1646
+ function addWarning(warnings, reason, filename) {
1647
+ if (!warnings.some(warning => warning.reason == reason)) {
1648
+ const warning = { reason };
1649
+ if (filename !== UNDEFINED_VALUE) {
1650
+ warning.filename = filename;
1651
+ }
1652
+ warnings.push(warning);
1524
1653
  }
1525
1654
  }
1526
1655
 
@@ -1541,7 +1670,8 @@ function getFunctionOptionValue(zipReader, options, name) {
1541
1670
 
1542
1671
  function getDate(timeRaw) {
1543
1672
  const date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & MAX_16_BITS;
1544
- return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
1673
+ const result = new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
1674
+ return result < MIN_DATE ? MIN_DATE : result;
1545
1675
  }
1546
1676
 
1547
1677
  function getDateNTFS(timeRaw) {