@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";
@@ -4757,12 +4761,9 @@
4757
4761
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
4758
4762
  const ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = "End of Zip64 central directory locator not found";
4759
4763
  const ERR_CENTRAL_DIRECTORY_NOT_FOUND = "Central directory header not found";
4760
- const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
4761
- const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
4762
4764
  const ERR_ENCRYPTED = "File contains encrypted entry";
4763
4765
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
4764
4766
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
4765
- const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
4766
4767
  const ERR_ENTRY_DATA_OUT_OF_BOUNDS = "Entry data out of bounds";
4767
4768
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
4768
4769
  const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
@@ -4827,7 +4828,8 @@
4827
4828
  Object.assign(this, {
4828
4829
  reader: new GenericReader(reader),
4829
4830
  options,
4830
- readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] }
4831
+ readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] },
4832
+ entriesMissingZip64ExtraField: new WeakSet()
4831
4833
  });
4832
4834
  }
4833
4835
 
@@ -4898,16 +4900,22 @@
4898
4900
  directoryDataOffset = getDiskOffset$1(reader, getUint32$1(endOfDirectoryLocatorView, 4)) + getBigUint64(endOfDirectoryLocatorView, 8);
4899
4901
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4900
4902
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
4901
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4902
- if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
4903
- directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
4904
- const originalDirectoryDataOffset = directoryDataOffset;
4905
- directoryDataOffset = expectedDirectoryDataOffset;
4906
- if (directoryDataOffset > originalDirectoryDataOffset) {
4907
- prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4903
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4904
+ const locatorOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
4905
+ let expectedDirectoryDataOffset = locatorOffset - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4906
+ if (!await startsWithSignature(reader, expectedDirectoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE)) {
4907
+ expectedDirectoryDataOffset = await findZip64EndOfDirectoryOffset(reader, locatorOffset);
4908
+ }
4909
+ if (expectedDirectoryDataOffset !== UNDEFINED_VALUE && expectedDirectoryDataOffset >= 0 &&
4910
+ directoryDataOffset != expectedDirectoryDataOffset) {
4911
+ const originalDirectoryDataOffset = directoryDataOffset;
4912
+ directoryDataOffset = expectedDirectoryDataOffset;
4913
+ if (directoryDataOffset > originalDirectoryDataOffset) {
4914
+ prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4915
+ }
4916
+ endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4917
+ endOfDirectoryView = getDataView(endOfDirectoryArray);
4908
4918
  }
4909
- endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4910
- endOfDirectoryView = getDataView(endOfDirectoryArray);
4911
4919
  }
4912
4920
  if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4913
4921
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
@@ -4952,10 +4960,6 @@
4952
4960
  let declaredDirectoryDataLength = directoryDataLength;
4953
4961
  const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
4954
4962
  (zip64EndOfDirectory ? zip64EndOfDirectoryLength + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
4955
- if (directoryDataOffset >= reader.size) {
4956
- prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4957
- directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4958
- }
4959
4963
  if (expectedLastDiskNumber != lastDiskNumber) {
4960
4964
  throw new Error(ERR_SPLIT_ZIP_FILE);
4961
4965
  }
@@ -4963,52 +4967,64 @@
4963
4967
  throw new Error(ERR_BAD_FORMAT);
4964
4968
  }
4965
4969
  let offset = 0;
4966
- let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
4970
+ let directoryArray = directoryDataOffset < reader.size ?
4971
+ await readUint8Array(reader, directoryDataOffset, directoryDataLength) :
4972
+ EMPTY_UINT8_ARRAY;
4967
4973
  let directoryView = getDataView(directoryArray);
4968
4974
  if (directoryDataLength) {
4969
- if (directoryArray.length < 4) {
4970
- throw new Error(ERR_BAD_FORMAT);
4971
- }
4972
4975
  let expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
4973
4976
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
4974
- const storedPointsAtDirectory = getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4977
+ const storedPointsAtDirectory = directoryArray.length >= 4 && (
4978
+ getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4975
4979
  Boolean(directoryEncryptionInfo && directoryEncryptionInfo.compressedSize) ||
4976
- detectEncryptedCentralDirectory(directoryView);
4980
+ detectEncryptedCentralDirectory(directoryView));
4981
+ if (!storedPointsAtDirectory && expectedDirectoryDataOffset < 0) {
4982
+ throw new Error(ERR_BAD_FORMAT);
4983
+ }
4977
4984
  let reconcile = !storedPointsAtDirectory;
4978
4985
  if (expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
4979
- const expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
4980
- if (storedPointsAtDirectory) {
4981
- reconcile = expectedPointsAtDirectory;
4982
- } else if (!expectedPointsAtDirectory) {
4986
+ let expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
4987
+ if (!expectedPointsAtDirectory) {
4983
4988
  const signatureRecordOffset = await findDigitalSignatureRecordOffset(reader, centralDirectoryEndOffset);
4984
4989
  const signedDirectoryDataOffset = signatureRecordOffset - directoryDataLength;
4985
4990
  if (signatureRecordOffset !== UNDEFINED_VALUE && signedDirectoryDataOffset >= 0 &&
4986
4991
  await pointsAtDirectory(reader, signedDirectoryDataOffset)) {
4987
4992
  expectedDirectoryDataOffset = signedDirectoryDataOffset;
4993
+ expectedPointsAtDirectory = true;
4988
4994
  }
4989
4995
  }
4996
+ if (storedPointsAtDirectory) {
4997
+ reconcile = expectedPointsAtDirectory;
4998
+ }
4990
4999
  }
4991
- if (reconcile) {
5000
+ if (reconcile && expectedDirectoryDataOffset != directoryDataOffset) {
4992
5001
  const originalDirectoryDataOffset = directoryDataOffset;
4993
5002
  directoryDataOffset = expectedDirectoryDataOffset;
4994
5003
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
4995
5004
  directoryView = getDataView(directoryArray);
4996
- if (directoryDataOffset > originalDirectoryDataOffset) {
4997
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
5005
+ const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5006
+ const localHeaderOffset = getFirstLocalHeaderOffset(directoryArray, directoryView);
5007
+ const localHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5008
+ await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE);
5009
+ const shiftedLocalHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5010
+ await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE);
5011
+ if (offsetDelta > 0 && !(localHeaderFound && !shiftedLocalHeaderFound)) {
5012
+ prependedDataLength += offsetDelta;
4998
5013
  prependedCentralDirectory = storedPointsAtDirectory;
4999
5014
  } else {
5000
5015
  reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET);
5001
- const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5002
- if (directoryArray.length >= CENTRAL_FILE_HEADER_LENGTH) {
5003
- const localHeaderOffset = getUint32$1(directoryView, 42);
5004
- if (!await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE) &&
5005
- await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE)) {
5006
- prependedDataLength += offsetDelta;
5007
- }
5016
+ if (offsetDelta < 0 && !localHeaderFound && shiftedLocalHeaderFound) {
5017
+ prependedDataLength += offsetDelta;
5008
5018
  }
5009
5019
  }
5010
5020
  }
5011
5021
  }
5022
+ if (directoryArray.length < 4) {
5023
+ throw new Error(ERR_BAD_FORMAT);
5024
+ }
5025
+ } else if (!filesLength && directoryDataOffset < centralDirectoryEndOffset && diskNumber == lastDiskNumber) {
5026
+ prependedDataLength += centralDirectoryEndOffset - directoryDataOffset;
5027
+ directoryDataOffset = centralDirectoryEndOffset;
5012
5028
  }
5013
5029
  const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset;
5014
5030
  if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
@@ -5050,8 +5066,10 @@
5050
5066
  addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
5051
5067
  }
5052
5068
  }
5069
+ const entryOptions = Object.assign({}, zipReader.options, options, { [OPTION_STRICTNESS]: strictness });
5070
+ delete entryOptions[OPTION_CHECK_AMBIGUITY];
5053
5071
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
5054
- const fileEntry = new ZipEntry(reader, zipReader.options);
5072
+ const fileEntry = new ZipEntry(reader, entryOptions);
5055
5073
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32$1(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
5056
5074
  if (indexFile == 0 && !decryptedDirectory && (zip64EndOfDirectoryVersion2 || detectEncryptedCentralDirectory(directoryView))) {
5057
5075
  throw new Error(ERR_ENCRYPTED_CENTRAL_DIRECTORY);
@@ -5142,13 +5160,17 @@
5142
5160
  if (malformedExtraField) {
5143
5161
  addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD, filename);
5144
5162
  }
5145
- fileEntry.offset += prependedDataLength;
5146
- const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5147
- startOffset = Math.min(entryPosition, startOffset);
5148
- if (entryPosition < previousEntryPosition) {
5149
- addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5163
+ if (fileEntry.zip64ExtraFieldMissing && fileEntry.offset == MAX_32_BITS) {
5164
+ startOffset = 0;
5165
+ } else {
5166
+ fileEntry.offset += prependedDataLength;
5167
+ const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5168
+ startOffset = Math.min(entryPosition, startOffset);
5169
+ if (entryPosition < previousEntryPosition) {
5170
+ addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5171
+ }
5172
+ previousEntryPosition = entryPosition;
5150
5173
  }
5151
- previousEntryPosition = entryPosition;
5152
5174
  if ((fileEntry.version & MAX_8_BITS) > MAX_KNOWN_VERSION) {
5153
5175
  addWarning(warnings, WARNING_UNKNOWN_VERSION, filename);
5154
5176
  }
@@ -5185,12 +5207,15 @@
5185
5207
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
5186
5208
  });
5187
5209
  const entry = new Entry(fileEntry);
5188
- entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
5210
+ if (fileEntry.zip64ExtraFieldMissing) {
5211
+ zipReader.entriesMissingZip64ExtraField.add(entry);
5212
+ }
5213
+ entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader, options);
5189
5214
  entry.arrayBuffer = async options => {
5190
5215
  const writer = new TransformStream();
5191
5216
  const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
5192
5217
  arrayBufferPromise.catch(() => { });
5193
- await fileEntry.getData(writer, entry, zipReader.readRanges,
5218
+ await fileEntry.getData(writer, entry, zipReader,
5194
5219
  Object.assign({}, options, { preventClose: false }));
5195
5220
  return arrayBufferPromise;
5196
5221
  };
@@ -5398,8 +5423,9 @@
5398
5423
  });
5399
5424
  }
5400
5425
 
5401
- async getData(writer, fileEntry, readRanges, options = {}) {
5426
+ async getData(writer, fileEntry, zipReader, options = {}) {
5402
5427
  const zipEntry = this;
5428
+ const { readRanges, directoryOffset } = zipReader;
5403
5429
  if (zipEntry.zip64ExtraFieldMissing) {
5404
5430
  throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5405
5431
  }
@@ -5473,10 +5499,10 @@
5473
5499
  }
5474
5500
  validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5475
5501
  const { lastAccessDate, creationDate, uid, gid } = localDirectory;
5476
- if (lastAccessDate) {
5502
+ if (lastAccessDate && fileEntry.lastAccessDate === UNDEFINED_VALUE) {
5477
5503
  fileEntry.lastAccessDate = lastAccessDate;
5478
5504
  }
5479
- if (creationDate) {
5505
+ if (creationDate && fileEntry.creationDate === UNDEFINED_VALUE) {
5480
5506
  fileEntry.creationDate = creationDate;
5481
5507
  }
5482
5508
  if (uid !== UNDEFINED_VALUE && fileEntry.uid === UNDEFINED_VALUE) {
@@ -5505,7 +5531,7 @@
5505
5531
  throw new Error(ERR_ENCRYPTED);
5506
5532
  }
5507
5533
  }
5508
- if (dataOffset + compressedSize > reader.size) {
5534
+ if (dataOffset + compressedSize > Math.min(reader.size, directoryOffset)) {
5509
5535
  throw new Error(ERR_ENTRY_DATA_OUT_OF_BOUNDS);
5510
5536
  }
5511
5537
  const size = compressedSize;
@@ -5571,6 +5597,7 @@
5571
5597
  extraFieldZip64: extraFieldZip64 || localDirectory.extraFieldZip64,
5572
5598
  readRanges
5573
5599
  });
5600
+ validateDataDescriptor(zipEntry, localDirectory, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5574
5601
  }
5575
5602
  let writable, abortError, aborted;
5576
5603
  try {
@@ -5590,10 +5617,7 @@
5590
5617
  writer.size += writtenSize;
5591
5618
  }
5592
5619
  } catch (error) {
5593
- const { outputSize: failedOutputSize } = workerOptions;
5594
- if (failedOutputSize !== UNDEFINED_VALUE) {
5595
- writer.size += failedOutputSize;
5596
- } else if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5620
+ if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5597
5621
  writer.size += error.outputSize;
5598
5622
  }
5599
5623
  if (!checkPasswordOnly || !isErrorObject(error) || error.message != ERR_ABORT_CHECK_PASSWORD) {
@@ -5621,13 +5645,7 @@
5621
5645
  }
5622
5646
 
5623
5647
  function detectEncryptedCentralDirectory(directoryView) {
5624
- const maxOffset = Math.min(directoryView.byteLength, 1024) - 3;
5625
- for (let offset = 0; offset < maxOffset; offset++) {
5626
- if (getUint32$1(directoryView, offset) == ARCHIVE_EXTRA_DATA_SIGNATURE) {
5627
- return true;
5628
- }
5629
- }
5630
- return false;
5648
+ return directoryView.byteLength >= 4 && getUint32$1(directoryView, 0) == ARCHIVE_EXTRA_DATA_SIGNATURE;
5631
5649
  }
5632
5650
 
5633
5651
  function getWrappedFilesLength(directoryView, directoryArray, offset) {
@@ -5664,6 +5682,46 @@
5664
5682
  }
5665
5683
  }
5666
5684
 
5685
+ async function findZip64EndOfDirectoryOffset(reader, endOffset) {
5686
+ const startOffset = Math.max(0, endOffset - (ZIP64_END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS));
5687
+ const tailArray = await readUint8Array(reader, startOffset, endOffset - startOffset);
5688
+ const tailView = getDataView(tailArray);
5689
+ for (let indexByte = tailArray.length - ZIP64_END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
5690
+ if (getUint32$1(tailView, indexByte) == ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
5691
+ indexByte + 12 + Number(tailView.getBigUint64(indexByte + 4, true)) == tailArray.length) {
5692
+ return startOffset + indexByte;
5693
+ }
5694
+ }
5695
+ }
5696
+
5697
+ function getFirstLocalHeaderOffset(directoryArray, directoryView) {
5698
+ if (directoryArray.length < CENTRAL_FILE_HEADER_LENGTH) {
5699
+ return UNDEFINED_VALUE;
5700
+ }
5701
+ const localHeaderOffset = getUint32$1(directoryView, 42);
5702
+ if (localHeaderOffset != MAX_32_BITS) {
5703
+ return localHeaderOffset;
5704
+ }
5705
+ let offsetExtraField = CENTRAL_FILE_HEADER_LENGTH + getUint16$1(directoryView, 28);
5706
+ const endExtraField = Math.min(offsetExtraField + getUint16$1(directoryView, 30), directoryArray.length);
5707
+ while (offsetExtraField + 4 <= endExtraField) {
5708
+ const type = getUint16$1(directoryView, offsetExtraField);
5709
+ const size = getUint16$1(directoryView, offsetExtraField + 2);
5710
+ if (type == EXTRAFIELD_TYPE_ZIP64) {
5711
+ let offsetValue = offsetExtraField + 4;
5712
+ if (getUint32$1(directoryView, 24) == MAX_32_BITS) {
5713
+ offsetValue += 8;
5714
+ }
5715
+ if (getUint32$1(directoryView, 20) == MAX_32_BITS) {
5716
+ offsetValue += 8;
5717
+ }
5718
+ return offsetValue + 8 <= endExtraField ? getBigUint64(directoryView, offsetValue) : UNDEFINED_VALUE;
5719
+ }
5720
+ offsetExtraField += 4 + size;
5721
+ }
5722
+ return UNDEFINED_VALUE;
5723
+ }
5724
+
5667
5725
  function readDigitalSignature(signatureRecordArray) {
5668
5726
  if (signatureRecordArray.length >= 6) {
5669
5727
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -5753,6 +5811,7 @@
5753
5811
  uncompressedSize: getUint32$1(dataView, offset + HEADER_OFFSET_UNCOMPRESSED_SIZE)
5754
5812
  });
5755
5813
  const extraFieldZip64 = extraField.get(EXTRAFIELD_TYPE_ZIP64);
5814
+ let extraFieldZip64Missing;
5756
5815
  if (extraFieldZip64) {
5757
5816
  if (!readExtraFieldZip64(extraFieldZip64, directory, localDirectory)) {
5758
5817
  malformedExtraField = true;
@@ -5762,7 +5821,7 @@
5762
5821
  if (localDirectory) {
5763
5822
  malformedExtraField = true;
5764
5823
  } else {
5765
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5824
+ extraFieldZip64Missing = true;
5766
5825
  }
5767
5826
  }
5768
5827
  const extraFieldUnicodePath = extraField.get(EXTRAFIELD_TYPE_UNICODE_PATH);
@@ -5820,6 +5879,9 @@
5820
5879
  if (extraFieldUSDZ) {
5821
5880
  directory.extraFieldUSDZ = extraFieldUSDZ;
5822
5881
  }
5882
+ if (extraFieldZip64Missing) {
5883
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5884
+ }
5823
5885
  return malformedExtraField;
5824
5886
  }
5825
5887
 
@@ -6034,13 +6096,7 @@
6034
6096
  let dataDescriptorLength = 0;
6035
6097
  if (dataDescriptor) {
6036
6098
  const zip64 = Boolean(extraFieldZip64);
6037
- const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH);
6038
- const dataDescriptorView = getDataView(dataDescriptorArray);
6039
- const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6040
- .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6041
- .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6042
- const localDataDescriptor = candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6043
- readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6099
+ const localDataDescriptor = await readLocalDataDescriptor(reader, dataOffset + compressedSize, { crc32, compressedSize, uncompressedSize, zip64 });
6044
6100
  if (localDataDescriptor) {
6045
6101
  fileEntry.localDirectory.dataDescriptor = localDataDescriptor;
6046
6102
  dataDescriptorLength = getDataDescriptorLength(localDataDescriptor.zip64, localDataDescriptor.signature);
@@ -6110,6 +6166,25 @@
6110
6166
  return mergedRanges;
6111
6167
  }
6112
6168
 
6169
+ async function readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64 }) {
6170
+ const maxLength = DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH;
6171
+ const length = Math.min(maxLength, Math.max(0, reader.size - offset));
6172
+ const dataDescriptorArray = length ? await readUint8Array(reader, offset, length) : EMPTY_UINT8_ARRAY;
6173
+ const dataDescriptorView = getDataView(dataDescriptorArray);
6174
+ const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6175
+ .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6176
+ .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6177
+ return candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6178
+ readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6179
+ }
6180
+
6181
+ async function getEntryDataDescriptorLength(reader, offset, { crc32, compressedSize, uncompressedSize, extraFieldZip64 }) {
6182
+ const dataDescriptor = await readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64: Boolean(extraFieldZip64) });
6183
+ if (dataDescriptor && dataDescriptor.compressedSize == compressedSize && dataDescriptor.uncompressedSize == uncompressedSize) {
6184
+ return getDataDescriptorLength(dataDescriptor.zip64, dataDescriptor.signature);
6185
+ }
6186
+ }
6187
+
6113
6188
  function readDataDescriptor(dataDescriptorView, zip64, signature) {
6114
6189
  const offset = signature ? DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH : 0;
6115
6190
  if (dataDescriptorView.byteLength < getDataDescriptorLength(zip64, signature) ||
@@ -6369,6 +6444,18 @@
6369
6444
  }
6370
6445
  }
6371
6446
 
6447
+ function validateDataDescriptor(zipEntry, localDirectory, warnings) {
6448
+ const { dataDescriptor } = localDirectory;
6449
+ const maskedLocalDirectory = zipEntry.decryptedDirectory &&
6450
+ (localDirectory.rawBitFlag & BITFLAG_MASKED_LOCAL_HEADERS) == BITFLAG_MASKED_LOCAL_HEADERS;
6451
+ if (dataDescriptor && !maskedLocalDirectory &&
6452
+ ((zipEntry.crc32 !== UNDEFINED_VALUE && dataDescriptor.crc32 != zipEntry.crc32) ||
6453
+ dataDescriptor.compressedSize != zipEntry.compressedSize ||
6454
+ dataDescriptor.uncompressedSize != zipEntry.uncompressedSize)) {
6455
+ reportAmbiguity(!warnings, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES);
6456
+ }
6457
+ }
6458
+
6372
6459
  function reportAmbiguity(reject, warnings, reason) {
6373
6460
  if (reject) {
6374
6461
  throwAmbiguousArchive(reason);
@@ -6471,6 +6558,7 @@
6471
6558
  WARNING_WRAPPED_ENTRIES_COUNT: WARNING_WRAPPED_ENTRIES_COUNT,
6472
6559
  ZipReader: ZipReader,
6473
6560
  ZipReaderStream: ZipReaderStream,
6561
+ getEntryDataDescriptorLength: getEntryDataDescriptorLength,
6474
6562
  isZipFile: isZipFile
6475
6563
  });
6476
6564
 
@@ -6594,136 +6682,7 @@
6594
6682
  }
6595
6683
 
6596
6684
  appendZip(reader, options = {}) {
6597
- return watchPromiseError(this, this.appendZipEntries(reader, options));
6598
- }
6599
-
6600
- async appendZipEntries(reader, options = {}) {
6601
- const zipWriter = this;
6602
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6603
- const filter = checkFunctionOption(options.filter);
6604
- while (pendingAddFileCalls.size) {
6605
- await Promise.allSettled(Array.from(pendingAddFileCalls));
6606
- }
6607
- let resolveAppendZip;
6608
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6609
- pendingAddFileCalls.add(promiseAppendZip);
6610
- const appendedFilenames = [];
6611
- let releaseLockWriter;
6612
- try {
6613
- reader = new GenericReader(reader);
6614
- await initStream(reader);
6615
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6616
- reader = new BlobReader(await streamToBlob(reader.readable));
6617
- await initStream(reader);
6618
- }
6619
- const { ZipReader } = await Promise.resolve().then(function () { return zipReader; });
6620
- const zipReader$1 = new ZipReader(reader);
6621
- const entries = await zipReader$1.getEntries();
6622
- const keptEntries = [];
6623
- for (const entry of entries) {
6624
- if (!filter || await filter(entry)) {
6625
- keptEntries.push(entry);
6626
- }
6627
- }
6628
- await zipReader$1.close();
6629
- await initStream(zipWriter.writer);
6630
- const { directoryOffset } = zipReader$1;
6631
- keptEntries.forEach(({ filename }) => {
6632
- if (filenames.has(filename)) {
6633
- throw new Error(ERR_DUPLICATED_NAME);
6634
- }
6635
- filenames.add(filename);
6636
- appendedFilenames.push(filename);
6637
- });
6638
- zipWriter.writerLocked = true;
6639
- const { lockWriter } = zipWriter;
6640
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6641
- zipWriter.writerLocked = false;
6642
- resolve();
6643
- });
6644
- await lockWriter;
6645
- if (zipWriter.addSplitZipSignature) {
6646
- delete zipWriter.addSplitZipSignature;
6647
- if (!await startsWithSplitZipSignature(reader)) {
6648
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
6649
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6650
- }
6651
- }
6652
- const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
6653
- keptEntries.forEach(entry => {
6654
- const {
6655
- version,
6656
- rawLastModDate,
6657
- rawFilename,
6658
- bitFlag,
6659
- encrypted,
6660
- uncompressedSize,
6661
- compressedSize,
6662
- extraFieldZip64
6663
- } = entry;
6664
- let {
6665
- compressionMethod,
6666
- rawExtraField,
6667
- } = entry;
6668
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
6669
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6670
- if (entry.extraFieldAES) {
6671
- compressionMethod = COMPRESSION_METHOD_AES;
6672
- }
6673
- const extraFieldLength = getLength(rawExtraField);
6674
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6675
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6676
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
6677
- const {
6678
- headerArray,
6679
- headerView
6680
- } = getHeaderArrayData({
6681
- version,
6682
- bitFlag: bitFlagValue,
6683
- compressionMethod,
6684
- uncompressedSize,
6685
- compressedSize,
6686
- rawLastModDate,
6687
- rawFilename,
6688
- zip64CompressedSize,
6689
- zip64UncompressedSize,
6690
- extraFieldLength
6691
- });
6692
- const { crc32 } = entry;
6693
- if (crc32 !== UNDEFINED_VALUE) {
6694
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6695
- }
6696
- const { offset, diskNumberStart } = entryPositions.get(entry);
6697
- Object.assign(entry, {
6698
- zip64Enabled: true,
6699
- zip64UncompressedSize,
6700
- zip64CompressedSize,
6701
- offset,
6702
- diskNumberStart,
6703
- zip64DiskNumberStart: false,
6704
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6705
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6706
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6707
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6708
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6709
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6710
- rawExtraField,
6711
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
6712
- headerArray,
6713
- headerView
6714
- });
6715
- fileEntries.set(entry.filename, entry);
6716
- });
6717
- } catch (error) {
6718
- appendedFilenames.forEach(filename => filenames.delete(filename));
6719
- throw error;
6720
- } finally {
6721
- resolveAppendZip();
6722
- pendingAddFileCalls.delete(promiseAppendZip);
6723
- if (releaseLockWriter) {
6724
- releaseLockWriter();
6725
- }
6726
- }
6685
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
6727
6686
  }
6728
6687
 
6729
6688
  add(name = "", reader, options = {}) {
@@ -6885,11 +6844,140 @@
6885
6844
  return watchedPromise;
6886
6845
  }
6887
6846
 
6847
+ async function appendZipEntries(zipWriter, reader, options = {}) {
6848
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6849
+ const filter = checkFunctionOption(options.filter);
6850
+ while (pendingAddFileCalls.size) {
6851
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
6852
+ }
6853
+ let resolveAppendZip;
6854
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6855
+ pendingAddFileCalls.add(promiseAppendZip);
6856
+ const appendedFilenames = [];
6857
+ let releaseLockWriter;
6858
+ try {
6859
+ reader = new GenericReader(reader);
6860
+ await initStream(reader);
6861
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6862
+ reader = new BlobReader(await streamToBlob(reader.readable));
6863
+ await initStream(reader);
6864
+ }
6865
+ const { ZipReader, getEntryDataDescriptorLength } = await Promise.resolve().then(function () { return zipReader; });
6866
+ const zipReader$1 = new ZipReader(reader);
6867
+ const entries = await zipReader$1.getEntries();
6868
+ const keptEntries = [];
6869
+ for (const entry of entries) {
6870
+ if (!filter || await filter(entry)) {
6871
+ keptEntries.push(entry);
6872
+ }
6873
+ }
6874
+ await zipReader$1.close();
6875
+ await initStream(zipWriter.writer);
6876
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader$1;
6877
+ keptEntries.forEach(entry => {
6878
+ const { filename } = entry;
6879
+ if (entriesMissingZip64ExtraField.has(entry)) {
6880
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
6881
+ }
6882
+ if (filenames.has(filename)) {
6883
+ throw new Error(ERR_DUPLICATED_NAME);
6884
+ }
6885
+ filenames.add(filename);
6886
+ appendedFilenames.push(filename);
6887
+ });
6888
+ zipWriter.writerLocked = true;
6889
+ const { lockWriter } = zipWriter;
6890
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6891
+ zipWriter.writerLocked = false;
6892
+ resolve();
6893
+ });
6894
+ await lockWriter;
6895
+ if (zipWriter.addSplitZipSignature) {
6896
+ delete zipWriter.addSplitZipSignature;
6897
+ if (filter || !await startsWithSplitZipSignature(reader)) {
6898
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
6899
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6900
+ }
6901
+ }
6902
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
6903
+ keptEntries.forEach(entry => {
6904
+ const {
6905
+ version,
6906
+ rawLastModDate,
6907
+ rawFilename,
6908
+ rawBitFlag,
6909
+ uncompressedSize,
6910
+ compressedSize,
6911
+ extraFieldZip64
6912
+ } = entry;
6913
+ let {
6914
+ compressionMethod,
6915
+ rawExtraField,
6916
+ } = entry;
6917
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6918
+ if (entry.extraFieldAES) {
6919
+ compressionMethod = COMPRESSION_METHOD_AES;
6920
+ }
6921
+ const extraFieldLength = getLength(rawExtraField);
6922
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6923
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6924
+ const {
6925
+ headerArray,
6926
+ headerView
6927
+ } = getHeaderArrayData({
6928
+ version,
6929
+ bitFlag: rawBitFlag,
6930
+ compressionMethod,
6931
+ uncompressedSize,
6932
+ compressedSize,
6933
+ rawLastModDate,
6934
+ rawFilename,
6935
+ zip64CompressedSize,
6936
+ zip64UncompressedSize,
6937
+ extraFieldLength
6938
+ });
6939
+ const { crc32 } = entry;
6940
+ if (crc32 !== UNDEFINED_VALUE) {
6941
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6942
+ }
6943
+ const { offset, diskNumberStart } = entryPositions.get(entry);
6944
+ Object.assign(entry, {
6945
+ zip64Enabled: true,
6946
+ zip64UncompressedSize,
6947
+ zip64CompressedSize,
6948
+ offset,
6949
+ diskNumberStart,
6950
+ zip64DiskNumberStart: false,
6951
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6952
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6953
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6954
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6955
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6956
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6957
+ rawExtraField,
6958
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
6959
+ headerArray,
6960
+ headerView
6961
+ });
6962
+ fileEntries.set(entry.filename, entry);
6963
+ });
6964
+ } catch (error) {
6965
+ appendedFilenames.forEach(filename => filenames.delete(filename));
6966
+ throw error;
6967
+ } finally {
6968
+ resolveAppendZip();
6969
+ pendingAddFileCalls.delete(promiseAppendZip);
6970
+ if (releaseLockWriter) {
6971
+ releaseLockWriter();
6972
+ }
6973
+ }
6974
+ }
6975
+
6888
6976
  async function prependZipEntries(zipWriter, reader) {
6889
6977
  if (zipWriter.filenames.size) {
6890
6978
  throw new Error(ERR_ZIP_NOT_EMPTY);
6891
6979
  }
6892
- await zipWriter.appendZipEntries(reader);
6980
+ await appendZipEntries(zipWriter, reader);
6893
6981
  }
6894
6982
 
6895
6983
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -8563,7 +8651,7 @@
8563
8651
  return rawExtraField;
8564
8652
  }
8565
8653
 
8566
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
8654
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
8567
8655
  const { writer } = zipWriter;
8568
8656
  const entryPositions = new Map();
8569
8657
  if (writer.closeDisk || compact) {
@@ -8571,6 +8659,27 @@
8571
8659
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
8572
8660
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
8573
8661
  const keptEntrySet = new Set(keptEntries);
8662
+ const localHeaderLengths = new Map();
8663
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
8664
+ const entry = sortedEntries[indexEntry];
8665
+ if (keptEntrySet.has(entry)) {
8666
+ const sourceOffset = getSourceOffset(reader, entry);
8667
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
8668
+ if (localHeaderLength === UNDEFINED_VALUE) {
8669
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
8670
+ }
8671
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
8672
+ if (entry.bitFlag.dataDescriptor) {
8673
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
8674
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
8675
+ }
8676
+ if (entryEnd > regionEnds[indexEntry]) {
8677
+ throw new Error(ERR_OVERLAPPING_ENTRY);
8678
+ }
8679
+ regionEnds[indexEntry] = entryEnd;
8680
+ localHeaderLengths.set(entry, localHeaderLength);
8681
+ }
8682
+ }
8574
8683
  let copiedOffset = 0;
8575
8684
  let lastPosition;
8576
8685
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -8583,7 +8692,7 @@
8583
8692
  if (!compact) {
8584
8693
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
8585
8694
  }
8586
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
8695
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
8587
8696
  await writer.closeDisk();
8588
8697
  }
8589
8698
  lastPosition = {
@@ -8617,7 +8726,7 @@
8617
8726
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
8618
8727
  const nextEntry = sortedEntries[indexEntry + 1];
8619
8728
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
8620
- regionEnd = getSourceOffset(reader, nextEntry);
8729
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
8621
8730
  }
8622
8731
  regionEnds[indexEntry] = regionEnd;
8623
8732
  }
@@ -8631,11 +8740,13 @@
8631
8740
  try {
8632
8741
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
8633
8742
  } catch (error) {
8634
- zipWriter.hasCorruptedEntries = true;
8635
- try {
8636
- error.corruptedEntry = true;
8637
- } catch {
8638
- // ignored
8743
+ if (copiedLength) {
8744
+ zipWriter.hasCorruptedEntries = true;
8745
+ try {
8746
+ error.corruptedEntry = true;
8747
+ } catch {
8748
+ // ignored
8749
+ }
8639
8750
  }
8640
8751
  throw error;
8641
8752
  } finally {
@@ -8646,11 +8757,16 @@
8646
8757
  }
8647
8758
 
8648
8759
  async function getLocalHeaderLength(reader, offset) {
8649
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
8760
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
8761
+ await readUint8Array(reader, offset, HEADER_SIZE) :
8762
+ EMPTY_UINT8_ARRAY;
8650
8763
  if (getLength(headerArray) < HEADER_SIZE) {
8651
- return HEADER_SIZE;
8764
+ return UNDEFINED_VALUE;
8652
8765
  }
8653
8766
  const headerView = getDataView(headerArray);
8767
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
8768
+ return UNDEFINED_VALUE;
8769
+ }
8654
8770
  return HEADER_SIZE +
8655
8771
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
8656
8772
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -9001,7 +9117,7 @@
9001
9117
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9002
9118
  */
9003
9119
 
9004
- const VERSION = "2.20.0";
9120
+ const VERSION = "2.21.0";
9005
9121
 
9006
9122
  /*
9007
9123
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.