@zip.js/zip.js 2.20.0 → 2.21.0

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.
@@ -126,6 +126,10 @@
126
126
 
127
127
  const UNDEFINED_VALUE = undefined;
128
128
  const INFINITY_VALUE = Infinity;
129
+ const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
130
+ const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
131
+ const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
132
+
129
133
  const UNDEFINED_TYPE = "undefined";
130
134
  const FUNCTION_TYPE = "function";
131
135
  const OBJECT_TYPE = "object";
@@ -4775,12 +4779,9 @@
4775
4779
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
4776
4780
  const ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = "End of Zip64 central directory locator not found";
4777
4781
  const ERR_CENTRAL_DIRECTORY_NOT_FOUND = "Central directory header not found";
4778
- const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
4779
- const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
4780
4782
  const ERR_ENCRYPTED = "File contains encrypted entry";
4781
4783
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
4782
4784
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
4783
- const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
4784
4785
  const ERR_ENTRY_DATA_OUT_OF_BOUNDS = "Entry data out of bounds";
4785
4786
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
4786
4787
  const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
@@ -4845,7 +4846,8 @@
4845
4846
  Object.assign(this, {
4846
4847
  reader: new GenericReader(reader),
4847
4848
  options,
4848
- readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] }
4849
+ readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] },
4850
+ entriesMissingZip64ExtraField: new WeakSet()
4849
4851
  });
4850
4852
  }
4851
4853
 
@@ -4916,16 +4918,22 @@
4916
4918
  directoryDataOffset = getDiskOffset$1(reader, getUint32$1(endOfDirectoryLocatorView, 4)) + getBigUint64(endOfDirectoryLocatorView, 8);
4917
4919
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4918
4920
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
4919
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4920
- if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
4921
- directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
4922
- const originalDirectoryDataOffset = directoryDataOffset;
4923
- directoryDataOffset = expectedDirectoryDataOffset;
4924
- if (directoryDataOffset > originalDirectoryDataOffset) {
4925
- prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4921
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4922
+ const locatorOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
4923
+ let expectedDirectoryDataOffset = locatorOffset - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4924
+ if (!await startsWithSignature(reader, expectedDirectoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE)) {
4925
+ expectedDirectoryDataOffset = await findZip64EndOfDirectoryOffset(reader, locatorOffset);
4926
+ }
4927
+ if (expectedDirectoryDataOffset !== UNDEFINED_VALUE && expectedDirectoryDataOffset >= 0 &&
4928
+ directoryDataOffset != expectedDirectoryDataOffset) {
4929
+ const originalDirectoryDataOffset = directoryDataOffset;
4930
+ directoryDataOffset = expectedDirectoryDataOffset;
4931
+ if (directoryDataOffset > originalDirectoryDataOffset) {
4932
+ prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4933
+ }
4934
+ endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4935
+ endOfDirectoryView = getDataView(endOfDirectoryArray);
4926
4936
  }
4927
- endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4928
- endOfDirectoryView = getDataView(endOfDirectoryArray);
4929
4937
  }
4930
4938
  if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4931
4939
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
@@ -4970,10 +4978,6 @@
4970
4978
  let declaredDirectoryDataLength = directoryDataLength;
4971
4979
  const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
4972
4980
  (zip64EndOfDirectory ? zip64EndOfDirectoryLength + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
4973
- if (directoryDataOffset >= reader.size) {
4974
- prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4975
- directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4976
- }
4977
4981
  if (expectedLastDiskNumber != lastDiskNumber) {
4978
4982
  throw new Error(ERR_SPLIT_ZIP_FILE);
4979
4983
  }
@@ -4981,52 +4985,64 @@
4981
4985
  throw new Error(ERR_BAD_FORMAT);
4982
4986
  }
4983
4987
  let offset = 0;
4984
- let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
4988
+ let directoryArray = directoryDataOffset < reader.size ?
4989
+ await readUint8Array(reader, directoryDataOffset, directoryDataLength) :
4990
+ EMPTY_UINT8_ARRAY;
4985
4991
  let directoryView = getDataView(directoryArray);
4986
4992
  if (directoryDataLength) {
4987
- if (directoryArray.length < 4) {
4988
- throw new Error(ERR_BAD_FORMAT);
4989
- }
4990
4993
  let expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
4991
4994
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
4992
- const storedPointsAtDirectory = getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4995
+ const storedPointsAtDirectory = directoryArray.length >= 4 && (
4996
+ getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4993
4997
  Boolean(directoryEncryptionInfo && directoryEncryptionInfo.compressedSize) ||
4994
- detectEncryptedCentralDirectory(directoryView);
4998
+ detectEncryptedCentralDirectory(directoryView));
4999
+ if (!storedPointsAtDirectory && expectedDirectoryDataOffset < 0) {
5000
+ throw new Error(ERR_BAD_FORMAT);
5001
+ }
4995
5002
  let reconcile = !storedPointsAtDirectory;
4996
5003
  if (expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
4997
- const expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
4998
- if (storedPointsAtDirectory) {
4999
- reconcile = expectedPointsAtDirectory;
5000
- } else if (!expectedPointsAtDirectory) {
5004
+ let expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
5005
+ if (!expectedPointsAtDirectory) {
5001
5006
  const signatureRecordOffset = await findDigitalSignatureRecordOffset(reader, centralDirectoryEndOffset);
5002
5007
  const signedDirectoryDataOffset = signatureRecordOffset - directoryDataLength;
5003
5008
  if (signatureRecordOffset !== UNDEFINED_VALUE && signedDirectoryDataOffset >= 0 &&
5004
5009
  await pointsAtDirectory(reader, signedDirectoryDataOffset)) {
5005
5010
  expectedDirectoryDataOffset = signedDirectoryDataOffset;
5011
+ expectedPointsAtDirectory = true;
5006
5012
  }
5007
5013
  }
5014
+ if (storedPointsAtDirectory) {
5015
+ reconcile = expectedPointsAtDirectory;
5016
+ }
5008
5017
  }
5009
- if (reconcile) {
5018
+ if (reconcile && expectedDirectoryDataOffset != directoryDataOffset) {
5010
5019
  const originalDirectoryDataOffset = directoryDataOffset;
5011
5020
  directoryDataOffset = expectedDirectoryDataOffset;
5012
5021
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
5013
5022
  directoryView = getDataView(directoryArray);
5014
- if (directoryDataOffset > originalDirectoryDataOffset) {
5015
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
5023
+ const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5024
+ const localHeaderOffset = getFirstLocalHeaderOffset(directoryArray, directoryView);
5025
+ const localHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5026
+ await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE);
5027
+ const shiftedLocalHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5028
+ await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE);
5029
+ if (offsetDelta > 0 && !(localHeaderFound && !shiftedLocalHeaderFound)) {
5030
+ prependedDataLength += offsetDelta;
5016
5031
  prependedCentralDirectory = storedPointsAtDirectory;
5017
5032
  } else {
5018
5033
  reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET);
5019
- const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5020
- if (directoryArray.length >= CENTRAL_FILE_HEADER_LENGTH) {
5021
- const localHeaderOffset = getUint32$1(directoryView, 42);
5022
- if (!await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE) &&
5023
- await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE)) {
5024
- prependedDataLength += offsetDelta;
5025
- }
5034
+ if (offsetDelta < 0 && !localHeaderFound && shiftedLocalHeaderFound) {
5035
+ prependedDataLength += offsetDelta;
5026
5036
  }
5027
5037
  }
5028
5038
  }
5029
5039
  }
5040
+ if (directoryArray.length < 4) {
5041
+ throw new Error(ERR_BAD_FORMAT);
5042
+ }
5043
+ } else if (!filesLength && directoryDataOffset < centralDirectoryEndOffset && diskNumber == lastDiskNumber) {
5044
+ prependedDataLength += centralDirectoryEndOffset - directoryDataOffset;
5045
+ directoryDataOffset = centralDirectoryEndOffset;
5030
5046
  }
5031
5047
  const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset;
5032
5048
  if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
@@ -5068,8 +5084,10 @@
5068
5084
  addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
5069
5085
  }
5070
5086
  }
5087
+ const entryOptions = Object.assign({}, zipReader.options, options, { [OPTION_STRICTNESS]: strictness });
5088
+ delete entryOptions[OPTION_CHECK_AMBIGUITY];
5071
5089
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
5072
- const fileEntry = new ZipEntry$1(reader, zipReader.options);
5090
+ const fileEntry = new ZipEntry$1(reader, entryOptions);
5073
5091
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32$1(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
5074
5092
  if (indexFile == 0 && !decryptedDirectory && (zip64EndOfDirectoryVersion2 || detectEncryptedCentralDirectory(directoryView))) {
5075
5093
  throw new Error(ERR_ENCRYPTED_CENTRAL_DIRECTORY);
@@ -5160,13 +5178,17 @@
5160
5178
  if (malformedExtraField) {
5161
5179
  addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD, filename);
5162
5180
  }
5163
- fileEntry.offset += prependedDataLength;
5164
- const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5165
- startOffset = Math.min(entryPosition, startOffset);
5166
- if (entryPosition < previousEntryPosition) {
5167
- addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5181
+ if (fileEntry.zip64ExtraFieldMissing && fileEntry.offset == MAX_32_BITS) {
5182
+ startOffset = 0;
5183
+ } else {
5184
+ fileEntry.offset += prependedDataLength;
5185
+ const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5186
+ startOffset = Math.min(entryPosition, startOffset);
5187
+ if (entryPosition < previousEntryPosition) {
5188
+ addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5189
+ }
5190
+ previousEntryPosition = entryPosition;
5168
5191
  }
5169
- previousEntryPosition = entryPosition;
5170
5192
  if ((fileEntry.version & MAX_8_BITS) > MAX_KNOWN_VERSION) {
5171
5193
  addWarning(warnings, WARNING_UNKNOWN_VERSION, filename);
5172
5194
  }
@@ -5203,12 +5225,15 @@
5203
5225
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
5204
5226
  });
5205
5227
  const entry = new Entry(fileEntry);
5206
- entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
5228
+ if (fileEntry.zip64ExtraFieldMissing) {
5229
+ zipReader.entriesMissingZip64ExtraField.add(entry);
5230
+ }
5231
+ entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader, options);
5207
5232
  entry.arrayBuffer = async options => {
5208
5233
  const writer = new TransformStream();
5209
5234
  const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
5210
5235
  arrayBufferPromise.catch(() => { });
5211
- await fileEntry.getData(writer, entry, zipReader.readRanges,
5236
+ await fileEntry.getData(writer, entry, zipReader,
5212
5237
  Object.assign({}, options, { preventClose: false }));
5213
5238
  return arrayBufferPromise;
5214
5239
  };
@@ -5416,8 +5441,9 @@
5416
5441
  });
5417
5442
  }
5418
5443
 
5419
- async getData(writer, fileEntry, readRanges, options = {}) {
5444
+ async getData(writer, fileEntry, zipReader, options = {}) {
5420
5445
  const zipEntry = this;
5446
+ const { readRanges, directoryOffset } = zipReader;
5421
5447
  if (zipEntry.zip64ExtraFieldMissing) {
5422
5448
  throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5423
5449
  }
@@ -5491,10 +5517,10 @@
5491
5517
  }
5492
5518
  validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5493
5519
  const { lastAccessDate, creationDate, uid, gid } = localDirectory;
5494
- if (lastAccessDate) {
5520
+ if (lastAccessDate && fileEntry.lastAccessDate === UNDEFINED_VALUE) {
5495
5521
  fileEntry.lastAccessDate = lastAccessDate;
5496
5522
  }
5497
- if (creationDate) {
5523
+ if (creationDate && fileEntry.creationDate === UNDEFINED_VALUE) {
5498
5524
  fileEntry.creationDate = creationDate;
5499
5525
  }
5500
5526
  if (uid !== UNDEFINED_VALUE && fileEntry.uid === UNDEFINED_VALUE) {
@@ -5523,7 +5549,7 @@
5523
5549
  throw new Error(ERR_ENCRYPTED);
5524
5550
  }
5525
5551
  }
5526
- if (dataOffset + compressedSize > reader.size) {
5552
+ if (dataOffset + compressedSize > Math.min(reader.size, directoryOffset)) {
5527
5553
  throw new Error(ERR_ENTRY_DATA_OUT_OF_BOUNDS);
5528
5554
  }
5529
5555
  const size = compressedSize;
@@ -5589,6 +5615,7 @@
5589
5615
  extraFieldZip64: extraFieldZip64 || localDirectory.extraFieldZip64,
5590
5616
  readRanges
5591
5617
  });
5618
+ validateDataDescriptor(zipEntry, localDirectory, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5592
5619
  }
5593
5620
  let writable, abortError, aborted;
5594
5621
  try {
@@ -5608,10 +5635,7 @@
5608
5635
  writer.size += writtenSize;
5609
5636
  }
5610
5637
  } catch (error) {
5611
- const { outputSize: failedOutputSize } = workerOptions;
5612
- if (failedOutputSize !== UNDEFINED_VALUE) {
5613
- writer.size += failedOutputSize;
5614
- } else if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5638
+ if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5615
5639
  writer.size += error.outputSize;
5616
5640
  }
5617
5641
  if (!checkPasswordOnly || !isErrorObject(error) || error.message != ERR_ABORT_CHECK_PASSWORD) {
@@ -5639,13 +5663,7 @@
5639
5663
  };
5640
5664
 
5641
5665
  function detectEncryptedCentralDirectory(directoryView) {
5642
- const maxOffset = Math.min(directoryView.byteLength, 1024) - 3;
5643
- for (let offset = 0; offset < maxOffset; offset++) {
5644
- if (getUint32$1(directoryView, offset) == ARCHIVE_EXTRA_DATA_SIGNATURE) {
5645
- return true;
5646
- }
5647
- }
5648
- return false;
5666
+ return directoryView.byteLength >= 4 && getUint32$1(directoryView, 0) == ARCHIVE_EXTRA_DATA_SIGNATURE;
5649
5667
  }
5650
5668
 
5651
5669
  function getWrappedFilesLength(directoryView, directoryArray, offset) {
@@ -5682,6 +5700,46 @@
5682
5700
  }
5683
5701
  }
5684
5702
 
5703
+ async function findZip64EndOfDirectoryOffset(reader, endOffset) {
5704
+ const startOffset = Math.max(0, endOffset - (ZIP64_END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS));
5705
+ const tailArray = await readUint8Array(reader, startOffset, endOffset - startOffset);
5706
+ const tailView = getDataView(tailArray);
5707
+ for (let indexByte = tailArray.length - ZIP64_END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
5708
+ if (getUint32$1(tailView, indexByte) == ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
5709
+ indexByte + 12 + Number(tailView.getBigUint64(indexByte + 4, true)) == tailArray.length) {
5710
+ return startOffset + indexByte;
5711
+ }
5712
+ }
5713
+ }
5714
+
5715
+ function getFirstLocalHeaderOffset(directoryArray, directoryView) {
5716
+ if (directoryArray.length < CENTRAL_FILE_HEADER_LENGTH) {
5717
+ return UNDEFINED_VALUE;
5718
+ }
5719
+ const localHeaderOffset = getUint32$1(directoryView, 42);
5720
+ if (localHeaderOffset != MAX_32_BITS) {
5721
+ return localHeaderOffset;
5722
+ }
5723
+ let offsetExtraField = CENTRAL_FILE_HEADER_LENGTH + getUint16$1(directoryView, 28);
5724
+ const endExtraField = Math.min(offsetExtraField + getUint16$1(directoryView, 30), directoryArray.length);
5725
+ while (offsetExtraField + 4 <= endExtraField) {
5726
+ const type = getUint16$1(directoryView, offsetExtraField);
5727
+ const size = getUint16$1(directoryView, offsetExtraField + 2);
5728
+ if (type == EXTRAFIELD_TYPE_ZIP64) {
5729
+ let offsetValue = offsetExtraField + 4;
5730
+ if (getUint32$1(directoryView, 24) == MAX_32_BITS) {
5731
+ offsetValue += 8;
5732
+ }
5733
+ if (getUint32$1(directoryView, 20) == MAX_32_BITS) {
5734
+ offsetValue += 8;
5735
+ }
5736
+ return offsetValue + 8 <= endExtraField ? getBigUint64(directoryView, offsetValue) : UNDEFINED_VALUE;
5737
+ }
5738
+ offsetExtraField += 4 + size;
5739
+ }
5740
+ return UNDEFINED_VALUE;
5741
+ }
5742
+
5685
5743
  function readDigitalSignature(signatureRecordArray) {
5686
5744
  if (signatureRecordArray.length >= 6) {
5687
5745
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -5771,6 +5829,7 @@
5771
5829
  uncompressedSize: getUint32$1(dataView, offset + HEADER_OFFSET_UNCOMPRESSED_SIZE)
5772
5830
  });
5773
5831
  const extraFieldZip64 = extraField.get(EXTRAFIELD_TYPE_ZIP64);
5832
+ let extraFieldZip64Missing;
5774
5833
  if (extraFieldZip64) {
5775
5834
  if (!readExtraFieldZip64(extraFieldZip64, directory, localDirectory)) {
5776
5835
  malformedExtraField = true;
@@ -5780,7 +5839,7 @@
5780
5839
  if (localDirectory) {
5781
5840
  malformedExtraField = true;
5782
5841
  } else {
5783
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5842
+ extraFieldZip64Missing = true;
5784
5843
  }
5785
5844
  }
5786
5845
  const extraFieldUnicodePath = extraField.get(EXTRAFIELD_TYPE_UNICODE_PATH);
@@ -5838,6 +5897,9 @@
5838
5897
  if (extraFieldUSDZ) {
5839
5898
  directory.extraFieldUSDZ = extraFieldUSDZ;
5840
5899
  }
5900
+ if (extraFieldZip64Missing) {
5901
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5902
+ }
5841
5903
  return malformedExtraField;
5842
5904
  }
5843
5905
 
@@ -6052,13 +6114,7 @@
6052
6114
  let dataDescriptorLength = 0;
6053
6115
  if (dataDescriptor) {
6054
6116
  const zip64 = Boolean(extraFieldZip64);
6055
- const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH);
6056
- const dataDescriptorView = getDataView(dataDescriptorArray);
6057
- const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6058
- .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6059
- .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6060
- const localDataDescriptor = candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6061
- readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6117
+ const localDataDescriptor = await readLocalDataDescriptor(reader, dataOffset + compressedSize, { crc32, compressedSize, uncompressedSize, zip64 });
6062
6118
  if (localDataDescriptor) {
6063
6119
  fileEntry.localDirectory.dataDescriptor = localDataDescriptor;
6064
6120
  dataDescriptorLength = getDataDescriptorLength(localDataDescriptor.zip64, localDataDescriptor.signature);
@@ -6128,6 +6184,25 @@
6128
6184
  return mergedRanges;
6129
6185
  }
6130
6186
 
6187
+ async function readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64 }) {
6188
+ const maxLength = DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH;
6189
+ const length = Math.min(maxLength, Math.max(0, reader.size - offset));
6190
+ const dataDescriptorArray = length ? await readUint8Array(reader, offset, length) : EMPTY_UINT8_ARRAY;
6191
+ const dataDescriptorView = getDataView(dataDescriptorArray);
6192
+ const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6193
+ .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6194
+ .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6195
+ return candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6196
+ readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6197
+ }
6198
+
6199
+ async function getEntryDataDescriptorLength(reader, offset, { crc32, compressedSize, uncompressedSize, extraFieldZip64 }) {
6200
+ const dataDescriptor = await readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64: Boolean(extraFieldZip64) });
6201
+ if (dataDescriptor && dataDescriptor.compressedSize == compressedSize && dataDescriptor.uncompressedSize == uncompressedSize) {
6202
+ return getDataDescriptorLength(dataDescriptor.zip64, dataDescriptor.signature);
6203
+ }
6204
+ }
6205
+
6131
6206
  function readDataDescriptor(dataDescriptorView, zip64, signature) {
6132
6207
  const offset = signature ? DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH : 0;
6133
6208
  if (dataDescriptorView.byteLength < getDataDescriptorLength(zip64, signature) ||
@@ -6387,6 +6462,18 @@
6387
6462
  }
6388
6463
  }
6389
6464
 
6465
+ function validateDataDescriptor(zipEntry, localDirectory, warnings) {
6466
+ const { dataDescriptor } = localDirectory;
6467
+ const maskedLocalDirectory = zipEntry.decryptedDirectory &&
6468
+ (localDirectory.rawBitFlag & BITFLAG_MASKED_LOCAL_HEADERS) == BITFLAG_MASKED_LOCAL_HEADERS;
6469
+ if (dataDescriptor && !maskedLocalDirectory &&
6470
+ ((zipEntry.crc32 !== UNDEFINED_VALUE && dataDescriptor.crc32 != zipEntry.crc32) ||
6471
+ dataDescriptor.compressedSize != zipEntry.compressedSize ||
6472
+ dataDescriptor.uncompressedSize != zipEntry.uncompressedSize)) {
6473
+ reportAmbiguity(!warnings, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES);
6474
+ }
6475
+ }
6476
+
6390
6477
  function reportAmbiguity(reject, warnings, reason) {
6391
6478
  if (reject) {
6392
6479
  throwAmbiguousArchive(reason);
@@ -6489,6 +6576,7 @@
6489
6576
  WARNING_WRAPPED_ENTRIES_COUNT: WARNING_WRAPPED_ENTRIES_COUNT,
6490
6577
  ZipReader: ZipReader,
6491
6578
  ZipReaderStream: ZipReaderStream,
6579
+ getEntryDataDescriptorLength: getEntryDataDescriptorLength,
6492
6580
  isZipFile: isZipFile
6493
6581
  });
6494
6582
 
@@ -6612,136 +6700,7 @@
6612
6700
  }
6613
6701
 
6614
6702
  appendZip(reader, options = {}) {
6615
- return watchPromiseError(this, this.appendZipEntries(reader, options));
6616
- }
6617
-
6618
- async appendZipEntries(reader, options = {}) {
6619
- const zipWriter = this;
6620
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6621
- const filter = checkFunctionOption(options.filter);
6622
- while (pendingAddFileCalls.size) {
6623
- await Promise.allSettled(Array.from(pendingAddFileCalls));
6624
- }
6625
- let resolveAppendZip;
6626
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6627
- pendingAddFileCalls.add(promiseAppendZip);
6628
- const appendedFilenames = [];
6629
- let releaseLockWriter;
6630
- try {
6631
- reader = new GenericReader(reader);
6632
- await initStream(reader);
6633
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6634
- reader = new BlobReader(await streamToBlob(reader.readable));
6635
- await initStream(reader);
6636
- }
6637
- const { ZipReader } = await Promise.resolve().then(function () { return zipReader; });
6638
- const zipReader$1 = new ZipReader(reader);
6639
- const entries = await zipReader$1.getEntries();
6640
- const keptEntries = [];
6641
- for (const entry of entries) {
6642
- if (!filter || await filter(entry)) {
6643
- keptEntries.push(entry);
6644
- }
6645
- }
6646
- await zipReader$1.close();
6647
- await initStream(zipWriter.writer);
6648
- const { directoryOffset } = zipReader$1;
6649
- keptEntries.forEach(({ filename }) => {
6650
- if (filenames.has(filename)) {
6651
- throw new Error(ERR_DUPLICATED_NAME);
6652
- }
6653
- filenames.add(filename);
6654
- appendedFilenames.push(filename);
6655
- });
6656
- zipWriter.writerLocked = true;
6657
- const { lockWriter } = zipWriter;
6658
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6659
- zipWriter.writerLocked = false;
6660
- resolve();
6661
- });
6662
- await lockWriter;
6663
- if (zipWriter.addSplitZipSignature) {
6664
- delete zipWriter.addSplitZipSignature;
6665
- if (!await startsWithSplitZipSignature(reader)) {
6666
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
6667
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6668
- }
6669
- }
6670
- const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
6671
- keptEntries.forEach(entry => {
6672
- const {
6673
- version,
6674
- rawLastModDate,
6675
- rawFilename,
6676
- bitFlag,
6677
- encrypted,
6678
- uncompressedSize,
6679
- compressedSize,
6680
- extraFieldZip64
6681
- } = entry;
6682
- let {
6683
- compressionMethod,
6684
- rawExtraField,
6685
- } = entry;
6686
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
6687
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6688
- if (entry.extraFieldAES) {
6689
- compressionMethod = COMPRESSION_METHOD_AES;
6690
- }
6691
- const extraFieldLength = getLength(rawExtraField);
6692
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6693
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6694
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
6695
- const {
6696
- headerArray,
6697
- headerView
6698
- } = getHeaderArrayData({
6699
- version,
6700
- bitFlag: bitFlagValue,
6701
- compressionMethod,
6702
- uncompressedSize,
6703
- compressedSize,
6704
- rawLastModDate,
6705
- rawFilename,
6706
- zip64CompressedSize,
6707
- zip64UncompressedSize,
6708
- extraFieldLength
6709
- });
6710
- const { crc32 } = entry;
6711
- if (crc32 !== UNDEFINED_VALUE) {
6712
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6713
- }
6714
- const { offset, diskNumberStart } = entryPositions.get(entry);
6715
- Object.assign(entry, {
6716
- zip64Enabled: true,
6717
- zip64UncompressedSize,
6718
- zip64CompressedSize,
6719
- offset,
6720
- diskNumberStart,
6721
- zip64DiskNumberStart: false,
6722
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6723
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6724
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6725
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6726
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6727
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6728
- rawExtraField,
6729
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
6730
- headerArray,
6731
- headerView
6732
- });
6733
- fileEntries.set(entry.filename, entry);
6734
- });
6735
- } catch (error) {
6736
- appendedFilenames.forEach(filename => filenames.delete(filename));
6737
- throw error;
6738
- } finally {
6739
- resolveAppendZip();
6740
- pendingAddFileCalls.delete(promiseAppendZip);
6741
- if (releaseLockWriter) {
6742
- releaseLockWriter();
6743
- }
6744
- }
6703
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
6745
6704
  }
6746
6705
 
6747
6706
  add(name = "", reader, options = {}) {
@@ -6903,11 +6862,140 @@
6903
6862
  return watchedPromise;
6904
6863
  }
6905
6864
 
6865
+ async function appendZipEntries(zipWriter, reader, options = {}) {
6866
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6867
+ const filter = checkFunctionOption(options.filter);
6868
+ while (pendingAddFileCalls.size) {
6869
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
6870
+ }
6871
+ let resolveAppendZip;
6872
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6873
+ pendingAddFileCalls.add(promiseAppendZip);
6874
+ const appendedFilenames = [];
6875
+ let releaseLockWriter;
6876
+ try {
6877
+ reader = new GenericReader(reader);
6878
+ await initStream(reader);
6879
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6880
+ reader = new BlobReader(await streamToBlob(reader.readable));
6881
+ await initStream(reader);
6882
+ }
6883
+ const { ZipReader, getEntryDataDescriptorLength } = await Promise.resolve().then(function () { return zipReader; });
6884
+ const zipReader$1 = new ZipReader(reader);
6885
+ const entries = await zipReader$1.getEntries();
6886
+ const keptEntries = [];
6887
+ for (const entry of entries) {
6888
+ if (!filter || await filter(entry)) {
6889
+ keptEntries.push(entry);
6890
+ }
6891
+ }
6892
+ await zipReader$1.close();
6893
+ await initStream(zipWriter.writer);
6894
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader$1;
6895
+ keptEntries.forEach(entry => {
6896
+ const { filename } = entry;
6897
+ if (entriesMissingZip64ExtraField.has(entry)) {
6898
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
6899
+ }
6900
+ if (filenames.has(filename)) {
6901
+ throw new Error(ERR_DUPLICATED_NAME);
6902
+ }
6903
+ filenames.add(filename);
6904
+ appendedFilenames.push(filename);
6905
+ });
6906
+ zipWriter.writerLocked = true;
6907
+ const { lockWriter } = zipWriter;
6908
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6909
+ zipWriter.writerLocked = false;
6910
+ resolve();
6911
+ });
6912
+ await lockWriter;
6913
+ if (zipWriter.addSplitZipSignature) {
6914
+ delete zipWriter.addSplitZipSignature;
6915
+ if (filter || !await startsWithSplitZipSignature(reader)) {
6916
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
6917
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6918
+ }
6919
+ }
6920
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
6921
+ keptEntries.forEach(entry => {
6922
+ const {
6923
+ version,
6924
+ rawLastModDate,
6925
+ rawFilename,
6926
+ rawBitFlag,
6927
+ uncompressedSize,
6928
+ compressedSize,
6929
+ extraFieldZip64
6930
+ } = entry;
6931
+ let {
6932
+ compressionMethod,
6933
+ rawExtraField,
6934
+ } = entry;
6935
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6936
+ if (entry.extraFieldAES) {
6937
+ compressionMethod = COMPRESSION_METHOD_AES;
6938
+ }
6939
+ const extraFieldLength = getLength(rawExtraField);
6940
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6941
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6942
+ const {
6943
+ headerArray,
6944
+ headerView
6945
+ } = getHeaderArrayData({
6946
+ version,
6947
+ bitFlag: rawBitFlag,
6948
+ compressionMethod,
6949
+ uncompressedSize,
6950
+ compressedSize,
6951
+ rawLastModDate,
6952
+ rawFilename,
6953
+ zip64CompressedSize,
6954
+ zip64UncompressedSize,
6955
+ extraFieldLength
6956
+ });
6957
+ const { crc32 } = entry;
6958
+ if (crc32 !== UNDEFINED_VALUE) {
6959
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6960
+ }
6961
+ const { offset, diskNumberStart } = entryPositions.get(entry);
6962
+ Object.assign(entry, {
6963
+ zip64Enabled: true,
6964
+ zip64UncompressedSize,
6965
+ zip64CompressedSize,
6966
+ offset,
6967
+ diskNumberStart,
6968
+ zip64DiskNumberStart: false,
6969
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6970
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6971
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6972
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6973
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6974
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6975
+ rawExtraField,
6976
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
6977
+ headerArray,
6978
+ headerView
6979
+ });
6980
+ fileEntries.set(entry.filename, entry);
6981
+ });
6982
+ } catch (error) {
6983
+ appendedFilenames.forEach(filename => filenames.delete(filename));
6984
+ throw error;
6985
+ } finally {
6986
+ resolveAppendZip();
6987
+ pendingAddFileCalls.delete(promiseAppendZip);
6988
+ if (releaseLockWriter) {
6989
+ releaseLockWriter();
6990
+ }
6991
+ }
6992
+ }
6993
+
6906
6994
  async function prependZipEntries(zipWriter, reader) {
6907
6995
  if (zipWriter.filenames.size) {
6908
6996
  throw new Error(ERR_ZIP_NOT_EMPTY);
6909
6997
  }
6910
- await zipWriter.appendZipEntries(reader);
6998
+ await appendZipEntries(zipWriter, reader);
6911
6999
  }
6912
7000
 
6913
7001
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -8679,7 +8767,7 @@
8679
8767
  return rawExtraField;
8680
8768
  }
8681
8769
 
8682
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
8770
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
8683
8771
  const { writer } = zipWriter;
8684
8772
  const entryPositions = new Map();
8685
8773
  if (writer.closeDisk || compact) {
@@ -8687,6 +8775,27 @@
8687
8775
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
8688
8776
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
8689
8777
  const keptEntrySet = new Set(keptEntries);
8778
+ const localHeaderLengths = new Map();
8779
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
8780
+ const entry = sortedEntries[indexEntry];
8781
+ if (keptEntrySet.has(entry)) {
8782
+ const sourceOffset = getSourceOffset(reader, entry);
8783
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
8784
+ if (localHeaderLength === UNDEFINED_VALUE) {
8785
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
8786
+ }
8787
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
8788
+ if (entry.bitFlag.dataDescriptor) {
8789
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
8790
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
8791
+ }
8792
+ if (entryEnd > regionEnds[indexEntry]) {
8793
+ throw new Error(ERR_OVERLAPPING_ENTRY);
8794
+ }
8795
+ regionEnds[indexEntry] = entryEnd;
8796
+ localHeaderLengths.set(entry, localHeaderLength);
8797
+ }
8798
+ }
8690
8799
  let copiedOffset = 0;
8691
8800
  let lastPosition;
8692
8801
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -8699,7 +8808,7 @@
8699
8808
  if (!compact) {
8700
8809
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
8701
8810
  }
8702
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
8811
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
8703
8812
  await writer.closeDisk();
8704
8813
  }
8705
8814
  lastPosition = {
@@ -8733,7 +8842,7 @@
8733
8842
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
8734
8843
  const nextEntry = sortedEntries[indexEntry + 1];
8735
8844
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
8736
- regionEnd = getSourceOffset(reader, nextEntry);
8845
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
8737
8846
  }
8738
8847
  regionEnds[indexEntry] = regionEnd;
8739
8848
  }
@@ -8747,11 +8856,13 @@
8747
8856
  try {
8748
8857
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
8749
8858
  } catch (error) {
8750
- zipWriter.hasCorruptedEntries = true;
8751
- try {
8752
- error.corruptedEntry = true;
8753
- } catch {
8754
- // ignored
8859
+ if (copiedLength) {
8860
+ zipWriter.hasCorruptedEntries = true;
8861
+ try {
8862
+ error.corruptedEntry = true;
8863
+ } catch {
8864
+ // ignored
8865
+ }
8755
8866
  }
8756
8867
  throw error;
8757
8868
  } finally {
@@ -8762,11 +8873,16 @@
8762
8873
  }
8763
8874
 
8764
8875
  async function getLocalHeaderLength(reader, offset) {
8765
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
8876
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
8877
+ await readUint8Array(reader, offset, HEADER_SIZE) :
8878
+ EMPTY_UINT8_ARRAY;
8766
8879
  if (getLength(headerArray) < HEADER_SIZE) {
8767
- return HEADER_SIZE;
8880
+ return UNDEFINED_VALUE;
8768
8881
  }
8769
8882
  const headerView = getDataView(headerArray);
8883
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
8884
+ return UNDEFINED_VALUE;
8885
+ }
8770
8886
  return HEADER_SIZE +
8771
8887
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
8772
8888
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -9117,7 +9233,7 @@
9117
9233
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9118
9234
  */
9119
9235
 
9120
- const VERSION = "2.20.0";
9236
+ const VERSION = "2.21.0";
9121
9237
 
9122
9238
  /*
9123
9239
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.