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