@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.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";
@@ -4765,12 +4769,9 @@
4765
4769
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
4766
4770
  const ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = "End of Zip64 central directory locator not found";
4767
4771
  const ERR_CENTRAL_DIRECTORY_NOT_FOUND = "Central directory header not found";
4768
- const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
4769
- const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
4770
4772
  const ERR_ENCRYPTED = "File contains encrypted entry";
4771
4773
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
4772
4774
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
4773
- const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
4774
4775
  const ERR_ENTRY_DATA_OUT_OF_BOUNDS = "Entry data out of bounds";
4775
4776
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
4776
4777
  const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
@@ -4835,7 +4836,8 @@
4835
4836
  Object.assign(this, {
4836
4837
  reader: new GenericReader(reader),
4837
4838
  options,
4838
- readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] }
4839
+ readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] },
4840
+ entriesMissingZip64ExtraField: new WeakSet()
4839
4841
  });
4840
4842
  }
4841
4843
 
@@ -4906,16 +4908,22 @@
4906
4908
  directoryDataOffset = getDiskOffset$1(reader, getUint32$1(endOfDirectoryLocatorView, 4)) + getBigUint64(endOfDirectoryLocatorView, 8);
4907
4909
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4908
4910
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
4909
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4910
- if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
4911
- directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
4912
- const originalDirectoryDataOffset = directoryDataOffset;
4913
- directoryDataOffset = expectedDirectoryDataOffset;
4914
- if (directoryDataOffset > originalDirectoryDataOffset) {
4915
- prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4911
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4912
+ const locatorOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
4913
+ let expectedDirectoryDataOffset = locatorOffset - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4914
+ if (!await startsWithSignature(reader, expectedDirectoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE)) {
4915
+ expectedDirectoryDataOffset = await findZip64EndOfDirectoryOffset(reader, locatorOffset);
4916
+ }
4917
+ if (expectedDirectoryDataOffset !== UNDEFINED_VALUE && expectedDirectoryDataOffset >= 0 &&
4918
+ directoryDataOffset != expectedDirectoryDataOffset) {
4919
+ const originalDirectoryDataOffset = directoryDataOffset;
4920
+ directoryDataOffset = expectedDirectoryDataOffset;
4921
+ if (directoryDataOffset > originalDirectoryDataOffset) {
4922
+ prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4923
+ }
4924
+ endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4925
+ endOfDirectoryView = getDataView(endOfDirectoryArray);
4916
4926
  }
4917
- endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4918
- endOfDirectoryView = getDataView(endOfDirectoryArray);
4919
4927
  }
4920
4928
  if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4921
4929
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
@@ -4960,10 +4968,6 @@
4960
4968
  let declaredDirectoryDataLength = directoryDataLength;
4961
4969
  const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
4962
4970
  (zip64EndOfDirectory ? zip64EndOfDirectoryLength + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
4963
- if (directoryDataOffset >= reader.size) {
4964
- prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4965
- directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4966
- }
4967
4971
  if (expectedLastDiskNumber != lastDiskNumber) {
4968
4972
  throw new Error(ERR_SPLIT_ZIP_FILE);
4969
4973
  }
@@ -4971,52 +4975,64 @@
4971
4975
  throw new Error(ERR_BAD_FORMAT);
4972
4976
  }
4973
4977
  let offset = 0;
4974
- let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
4978
+ let directoryArray = directoryDataOffset < reader.size ?
4979
+ await readUint8Array(reader, directoryDataOffset, directoryDataLength) :
4980
+ EMPTY_UINT8_ARRAY;
4975
4981
  let directoryView = getDataView(directoryArray);
4976
4982
  if (directoryDataLength) {
4977
- if (directoryArray.length < 4) {
4978
- throw new Error(ERR_BAD_FORMAT);
4979
- }
4980
4983
  let expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
4981
4984
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
4982
- const storedPointsAtDirectory = getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4985
+ const storedPointsAtDirectory = directoryArray.length >= 4 && (
4986
+ getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
4983
4987
  Boolean(directoryEncryptionInfo && directoryEncryptionInfo.compressedSize) ||
4984
- detectEncryptedCentralDirectory(directoryView);
4988
+ detectEncryptedCentralDirectory(directoryView));
4989
+ if (!storedPointsAtDirectory && expectedDirectoryDataOffset < 0) {
4990
+ throw new Error(ERR_BAD_FORMAT);
4991
+ }
4985
4992
  let reconcile = !storedPointsAtDirectory;
4986
4993
  if (expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
4987
- const expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
4988
- if (storedPointsAtDirectory) {
4989
- reconcile = expectedPointsAtDirectory;
4990
- } else if (!expectedPointsAtDirectory) {
4994
+ let expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
4995
+ if (!expectedPointsAtDirectory) {
4991
4996
  const signatureRecordOffset = await findDigitalSignatureRecordOffset(reader, centralDirectoryEndOffset);
4992
4997
  const signedDirectoryDataOffset = signatureRecordOffset - directoryDataLength;
4993
4998
  if (signatureRecordOffset !== UNDEFINED_VALUE && signedDirectoryDataOffset >= 0 &&
4994
4999
  await pointsAtDirectory(reader, signedDirectoryDataOffset)) {
4995
5000
  expectedDirectoryDataOffset = signedDirectoryDataOffset;
5001
+ expectedPointsAtDirectory = true;
4996
5002
  }
4997
5003
  }
5004
+ if (storedPointsAtDirectory) {
5005
+ reconcile = expectedPointsAtDirectory;
5006
+ }
4998
5007
  }
4999
- if (reconcile) {
5008
+ if (reconcile && expectedDirectoryDataOffset != directoryDataOffset) {
5000
5009
  const originalDirectoryDataOffset = directoryDataOffset;
5001
5010
  directoryDataOffset = expectedDirectoryDataOffset;
5002
5011
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
5003
5012
  directoryView = getDataView(directoryArray);
5004
- if (directoryDataOffset > originalDirectoryDataOffset) {
5005
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
5013
+ const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5014
+ const localHeaderOffset = getFirstLocalHeaderOffset(directoryArray, directoryView);
5015
+ const localHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5016
+ await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE);
5017
+ const shiftedLocalHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5018
+ await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE);
5019
+ if (offsetDelta > 0 && !(localHeaderFound && !shiftedLocalHeaderFound)) {
5020
+ prependedDataLength += offsetDelta;
5006
5021
  prependedCentralDirectory = storedPointsAtDirectory;
5007
5022
  } else {
5008
5023
  reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET);
5009
- const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5010
- if (directoryArray.length >= CENTRAL_FILE_HEADER_LENGTH) {
5011
- const localHeaderOffset = getUint32$1(directoryView, 42);
5012
- if (!await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE) &&
5013
- await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE)) {
5014
- prependedDataLength += offsetDelta;
5015
- }
5024
+ if (offsetDelta < 0 && !localHeaderFound && shiftedLocalHeaderFound) {
5025
+ prependedDataLength += offsetDelta;
5016
5026
  }
5017
5027
  }
5018
5028
  }
5019
5029
  }
5030
+ if (directoryArray.length < 4) {
5031
+ throw new Error(ERR_BAD_FORMAT);
5032
+ }
5033
+ } else if (!filesLength && directoryDataOffset < centralDirectoryEndOffset && diskNumber == lastDiskNumber) {
5034
+ prependedDataLength += centralDirectoryEndOffset - directoryDataOffset;
5035
+ directoryDataOffset = centralDirectoryEndOffset;
5020
5036
  }
5021
5037
  const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset;
5022
5038
  if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
@@ -5058,8 +5074,10 @@
5058
5074
  addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
5059
5075
  }
5060
5076
  }
5077
+ const entryOptions = Object.assign({}, zipReader.options, options, { [OPTION_STRICTNESS]: strictness });
5078
+ delete entryOptions[OPTION_CHECK_AMBIGUITY];
5061
5079
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
5062
- const fileEntry = new ZipEntry(reader, zipReader.options);
5080
+ const fileEntry = new ZipEntry(reader, entryOptions);
5063
5081
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32$1(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
5064
5082
  if (indexFile == 0 && !decryptedDirectory && (zip64EndOfDirectoryVersion2 || detectEncryptedCentralDirectory(directoryView))) {
5065
5083
  throw new Error(ERR_ENCRYPTED_CENTRAL_DIRECTORY);
@@ -5150,13 +5168,17 @@
5150
5168
  if (malformedExtraField) {
5151
5169
  addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD, filename);
5152
5170
  }
5153
- fileEntry.offset += prependedDataLength;
5154
- const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5155
- startOffset = Math.min(entryPosition, startOffset);
5156
- if (entryPosition < previousEntryPosition) {
5157
- addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5171
+ if (fileEntry.zip64ExtraFieldMissing && fileEntry.offset == MAX_32_BITS) {
5172
+ startOffset = 0;
5173
+ } else {
5174
+ fileEntry.offset += prependedDataLength;
5175
+ const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5176
+ startOffset = Math.min(entryPosition, startOffset);
5177
+ if (entryPosition < previousEntryPosition) {
5178
+ addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5179
+ }
5180
+ previousEntryPosition = entryPosition;
5158
5181
  }
5159
- previousEntryPosition = entryPosition;
5160
5182
  if ((fileEntry.version & MAX_8_BITS) > MAX_KNOWN_VERSION) {
5161
5183
  addWarning(warnings, WARNING_UNKNOWN_VERSION, filename);
5162
5184
  }
@@ -5193,12 +5215,15 @@
5193
5215
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
5194
5216
  });
5195
5217
  const entry = new Entry(fileEntry);
5196
- entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
5218
+ if (fileEntry.zip64ExtraFieldMissing) {
5219
+ zipReader.entriesMissingZip64ExtraField.add(entry);
5220
+ }
5221
+ entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader, options);
5197
5222
  entry.arrayBuffer = async options => {
5198
5223
  const writer = new TransformStream();
5199
5224
  const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
5200
5225
  arrayBufferPromise.catch(() => { });
5201
- await fileEntry.getData(writer, entry, zipReader.readRanges,
5226
+ await fileEntry.getData(writer, entry, zipReader,
5202
5227
  Object.assign({}, options, { preventClose: false }));
5203
5228
  return arrayBufferPromise;
5204
5229
  };
@@ -5406,8 +5431,9 @@
5406
5431
  });
5407
5432
  }
5408
5433
 
5409
- async getData(writer, fileEntry, readRanges, options = {}) {
5434
+ async getData(writer, fileEntry, zipReader, options = {}) {
5410
5435
  const zipEntry = this;
5436
+ const { readRanges, directoryOffset } = zipReader;
5411
5437
  if (zipEntry.zip64ExtraFieldMissing) {
5412
5438
  throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5413
5439
  }
@@ -5481,10 +5507,10 @@
5481
5507
  }
5482
5508
  validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5483
5509
  const { lastAccessDate, creationDate, uid, gid } = localDirectory;
5484
- if (lastAccessDate) {
5510
+ if (lastAccessDate && fileEntry.lastAccessDate === UNDEFINED_VALUE) {
5485
5511
  fileEntry.lastAccessDate = lastAccessDate;
5486
5512
  }
5487
- if (creationDate) {
5513
+ if (creationDate && fileEntry.creationDate === UNDEFINED_VALUE) {
5488
5514
  fileEntry.creationDate = creationDate;
5489
5515
  }
5490
5516
  if (uid !== UNDEFINED_VALUE && fileEntry.uid === UNDEFINED_VALUE) {
@@ -5513,7 +5539,7 @@
5513
5539
  throw new Error(ERR_ENCRYPTED);
5514
5540
  }
5515
5541
  }
5516
- if (dataOffset + compressedSize > reader.size) {
5542
+ if (dataOffset + compressedSize > Math.min(reader.size, directoryOffset)) {
5517
5543
  throw new Error(ERR_ENTRY_DATA_OUT_OF_BOUNDS);
5518
5544
  }
5519
5545
  const size = compressedSize;
@@ -5579,6 +5605,7 @@
5579
5605
  extraFieldZip64: extraFieldZip64 || localDirectory.extraFieldZip64,
5580
5606
  readRanges
5581
5607
  });
5608
+ validateDataDescriptor(zipEntry, localDirectory, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5582
5609
  }
5583
5610
  let writable, abortError, aborted;
5584
5611
  try {
@@ -5598,10 +5625,7 @@
5598
5625
  writer.size += writtenSize;
5599
5626
  }
5600
5627
  } catch (error) {
5601
- const { outputSize: failedOutputSize } = workerOptions;
5602
- if (failedOutputSize !== UNDEFINED_VALUE) {
5603
- writer.size += failedOutputSize;
5604
- } else if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5628
+ if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5605
5629
  writer.size += error.outputSize;
5606
5630
  }
5607
5631
  if (!checkPasswordOnly || !isErrorObject(error) || error.message != ERR_ABORT_CHECK_PASSWORD) {
@@ -5629,13 +5653,7 @@
5629
5653
  }
5630
5654
 
5631
5655
  function detectEncryptedCentralDirectory(directoryView) {
5632
- const maxOffset = Math.min(directoryView.byteLength, 1024) - 3;
5633
- for (let offset = 0; offset < maxOffset; offset++) {
5634
- if (getUint32$1(directoryView, offset) == ARCHIVE_EXTRA_DATA_SIGNATURE) {
5635
- return true;
5636
- }
5637
- }
5638
- return false;
5656
+ return directoryView.byteLength >= 4 && getUint32$1(directoryView, 0) == ARCHIVE_EXTRA_DATA_SIGNATURE;
5639
5657
  }
5640
5658
 
5641
5659
  function getWrappedFilesLength(directoryView, directoryArray, offset) {
@@ -5672,6 +5690,46 @@
5672
5690
  }
5673
5691
  }
5674
5692
 
5693
+ async function findZip64EndOfDirectoryOffset(reader, endOffset) {
5694
+ const startOffset = Math.max(0, endOffset - (ZIP64_END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS));
5695
+ const tailArray = await readUint8Array(reader, startOffset, endOffset - startOffset);
5696
+ const tailView = getDataView(tailArray);
5697
+ for (let indexByte = tailArray.length - ZIP64_END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
5698
+ if (getUint32$1(tailView, indexByte) == ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
5699
+ indexByte + 12 + Number(tailView.getBigUint64(indexByte + 4, true)) == tailArray.length) {
5700
+ return startOffset + indexByte;
5701
+ }
5702
+ }
5703
+ }
5704
+
5705
+ function getFirstLocalHeaderOffset(directoryArray, directoryView) {
5706
+ if (directoryArray.length < CENTRAL_FILE_HEADER_LENGTH) {
5707
+ return UNDEFINED_VALUE;
5708
+ }
5709
+ const localHeaderOffset = getUint32$1(directoryView, 42);
5710
+ if (localHeaderOffset != MAX_32_BITS) {
5711
+ return localHeaderOffset;
5712
+ }
5713
+ let offsetExtraField = CENTRAL_FILE_HEADER_LENGTH + getUint16$1(directoryView, 28);
5714
+ const endExtraField = Math.min(offsetExtraField + getUint16$1(directoryView, 30), directoryArray.length);
5715
+ while (offsetExtraField + 4 <= endExtraField) {
5716
+ const type = getUint16$1(directoryView, offsetExtraField);
5717
+ const size = getUint16$1(directoryView, offsetExtraField + 2);
5718
+ if (type == EXTRAFIELD_TYPE_ZIP64) {
5719
+ let offsetValue = offsetExtraField + 4;
5720
+ if (getUint32$1(directoryView, 24) == MAX_32_BITS) {
5721
+ offsetValue += 8;
5722
+ }
5723
+ if (getUint32$1(directoryView, 20) == MAX_32_BITS) {
5724
+ offsetValue += 8;
5725
+ }
5726
+ return offsetValue + 8 <= endExtraField ? getBigUint64(directoryView, offsetValue) : UNDEFINED_VALUE;
5727
+ }
5728
+ offsetExtraField += 4 + size;
5729
+ }
5730
+ return UNDEFINED_VALUE;
5731
+ }
5732
+
5675
5733
  function readDigitalSignature(signatureRecordArray) {
5676
5734
  if (signatureRecordArray.length >= 6) {
5677
5735
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -5761,6 +5819,7 @@
5761
5819
  uncompressedSize: getUint32$1(dataView, offset + HEADER_OFFSET_UNCOMPRESSED_SIZE)
5762
5820
  });
5763
5821
  const extraFieldZip64 = extraField.get(EXTRAFIELD_TYPE_ZIP64);
5822
+ let extraFieldZip64Missing;
5764
5823
  if (extraFieldZip64) {
5765
5824
  if (!readExtraFieldZip64(extraFieldZip64, directory, localDirectory)) {
5766
5825
  malformedExtraField = true;
@@ -5770,7 +5829,7 @@
5770
5829
  if (localDirectory) {
5771
5830
  malformedExtraField = true;
5772
5831
  } else {
5773
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5832
+ extraFieldZip64Missing = true;
5774
5833
  }
5775
5834
  }
5776
5835
  const extraFieldUnicodePath = extraField.get(EXTRAFIELD_TYPE_UNICODE_PATH);
@@ -5828,6 +5887,9 @@
5828
5887
  if (extraFieldUSDZ) {
5829
5888
  directory.extraFieldUSDZ = extraFieldUSDZ;
5830
5889
  }
5890
+ if (extraFieldZip64Missing) {
5891
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5892
+ }
5831
5893
  return malformedExtraField;
5832
5894
  }
5833
5895
 
@@ -6042,13 +6104,7 @@
6042
6104
  let dataDescriptorLength = 0;
6043
6105
  if (dataDescriptor) {
6044
6106
  const zip64 = Boolean(extraFieldZip64);
6045
- const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH);
6046
- const dataDescriptorView = getDataView(dataDescriptorArray);
6047
- const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6048
- .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6049
- .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6050
- const localDataDescriptor = candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6051
- readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6107
+ const localDataDescriptor = await readLocalDataDescriptor(reader, dataOffset + compressedSize, { crc32, compressedSize, uncompressedSize, zip64 });
6052
6108
  if (localDataDescriptor) {
6053
6109
  fileEntry.localDirectory.dataDescriptor = localDataDescriptor;
6054
6110
  dataDescriptorLength = getDataDescriptorLength(localDataDescriptor.zip64, localDataDescriptor.signature);
@@ -6118,6 +6174,25 @@
6118
6174
  return mergedRanges;
6119
6175
  }
6120
6176
 
6177
+ async function readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64 }) {
6178
+ const maxLength = DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH;
6179
+ const length = Math.min(maxLength, Math.max(0, reader.size - offset));
6180
+ const dataDescriptorArray = length ? await readUint8Array(reader, offset, length) : EMPTY_UINT8_ARRAY;
6181
+ const dataDescriptorView = getDataView(dataDescriptorArray);
6182
+ const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6183
+ .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6184
+ .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6185
+ return candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6186
+ readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6187
+ }
6188
+
6189
+ async function getEntryDataDescriptorLength(reader, offset, { crc32, compressedSize, uncompressedSize, extraFieldZip64 }) {
6190
+ const dataDescriptor = await readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64: Boolean(extraFieldZip64) });
6191
+ if (dataDescriptor && dataDescriptor.compressedSize == compressedSize && dataDescriptor.uncompressedSize == uncompressedSize) {
6192
+ return getDataDescriptorLength(dataDescriptor.zip64, dataDescriptor.signature);
6193
+ }
6194
+ }
6195
+
6121
6196
  function readDataDescriptor(dataDescriptorView, zip64, signature) {
6122
6197
  const offset = signature ? DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH : 0;
6123
6198
  if (dataDescriptorView.byteLength < getDataDescriptorLength(zip64, signature) ||
@@ -6377,6 +6452,18 @@
6377
6452
  }
6378
6453
  }
6379
6454
 
6455
+ function validateDataDescriptor(zipEntry, localDirectory, warnings) {
6456
+ const { dataDescriptor } = localDirectory;
6457
+ const maskedLocalDirectory = zipEntry.decryptedDirectory &&
6458
+ (localDirectory.rawBitFlag & BITFLAG_MASKED_LOCAL_HEADERS) == BITFLAG_MASKED_LOCAL_HEADERS;
6459
+ if (dataDescriptor && !maskedLocalDirectory &&
6460
+ ((zipEntry.crc32 !== UNDEFINED_VALUE && dataDescriptor.crc32 != zipEntry.crc32) ||
6461
+ dataDescriptor.compressedSize != zipEntry.compressedSize ||
6462
+ dataDescriptor.uncompressedSize != zipEntry.uncompressedSize)) {
6463
+ reportAmbiguity(!warnings, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES);
6464
+ }
6465
+ }
6466
+
6380
6467
  function reportAmbiguity(reject, warnings, reason) {
6381
6468
  if (reject) {
6382
6469
  throwAmbiguousArchive(reason);
@@ -6479,6 +6566,7 @@
6479
6566
  WARNING_WRAPPED_ENTRIES_COUNT: WARNING_WRAPPED_ENTRIES_COUNT,
6480
6567
  ZipReader: ZipReader,
6481
6568
  ZipReaderStream: ZipReaderStream,
6569
+ getEntryDataDescriptorLength: getEntryDataDescriptorLength,
6482
6570
  isZipFile: isZipFile
6483
6571
  });
6484
6572
 
@@ -6602,136 +6690,7 @@
6602
6690
  }
6603
6691
 
6604
6692
  appendZip(reader, options = {}) {
6605
- return watchPromiseError(this, this.appendZipEntries(reader, options));
6606
- }
6607
-
6608
- async appendZipEntries(reader, options = {}) {
6609
- const zipWriter = this;
6610
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6611
- const filter = checkFunctionOption(options.filter);
6612
- while (pendingAddFileCalls.size) {
6613
- await Promise.allSettled(Array.from(pendingAddFileCalls));
6614
- }
6615
- let resolveAppendZip;
6616
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6617
- pendingAddFileCalls.add(promiseAppendZip);
6618
- const appendedFilenames = [];
6619
- let releaseLockWriter;
6620
- try {
6621
- reader = new GenericReader(reader);
6622
- await initStream(reader);
6623
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6624
- reader = new BlobReader(await streamToBlob(reader.readable));
6625
- await initStream(reader);
6626
- }
6627
- const { ZipReader } = await Promise.resolve().then(function () { return zipReader; });
6628
- const zipReader$1 = new ZipReader(reader);
6629
- const entries = await zipReader$1.getEntries();
6630
- const keptEntries = [];
6631
- for (const entry of entries) {
6632
- if (!filter || await filter(entry)) {
6633
- keptEntries.push(entry);
6634
- }
6635
- }
6636
- await zipReader$1.close();
6637
- await initStream(zipWriter.writer);
6638
- const { directoryOffset } = zipReader$1;
6639
- keptEntries.forEach(({ filename }) => {
6640
- if (filenames.has(filename)) {
6641
- throw new Error(ERR_DUPLICATED_NAME);
6642
- }
6643
- filenames.add(filename);
6644
- appendedFilenames.push(filename);
6645
- });
6646
- zipWriter.writerLocked = true;
6647
- const { lockWriter } = zipWriter;
6648
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6649
- zipWriter.writerLocked = false;
6650
- resolve();
6651
- });
6652
- await lockWriter;
6653
- if (zipWriter.addSplitZipSignature) {
6654
- delete zipWriter.addSplitZipSignature;
6655
- if (!await startsWithSplitZipSignature(reader)) {
6656
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
6657
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6658
- }
6659
- }
6660
- const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
6661
- keptEntries.forEach(entry => {
6662
- const {
6663
- version,
6664
- rawLastModDate,
6665
- rawFilename,
6666
- bitFlag,
6667
- encrypted,
6668
- uncompressedSize,
6669
- compressedSize,
6670
- extraFieldZip64
6671
- } = entry;
6672
- let {
6673
- compressionMethod,
6674
- rawExtraField,
6675
- } = entry;
6676
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
6677
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6678
- if (entry.extraFieldAES) {
6679
- compressionMethod = COMPRESSION_METHOD_AES;
6680
- }
6681
- const extraFieldLength = getLength(rawExtraField);
6682
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6683
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6684
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
6685
- const {
6686
- headerArray,
6687
- headerView
6688
- } = getHeaderArrayData({
6689
- version,
6690
- bitFlag: bitFlagValue,
6691
- compressionMethod,
6692
- uncompressedSize,
6693
- compressedSize,
6694
- rawLastModDate,
6695
- rawFilename,
6696
- zip64CompressedSize,
6697
- zip64UncompressedSize,
6698
- extraFieldLength
6699
- });
6700
- const { crc32 } = entry;
6701
- if (crc32 !== UNDEFINED_VALUE) {
6702
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6703
- }
6704
- const { offset, diskNumberStart } = entryPositions.get(entry);
6705
- Object.assign(entry, {
6706
- zip64Enabled: true,
6707
- zip64UncompressedSize,
6708
- zip64CompressedSize,
6709
- offset,
6710
- diskNumberStart,
6711
- zip64DiskNumberStart: false,
6712
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6713
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6714
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6715
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6716
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6717
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6718
- rawExtraField,
6719
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
6720
- headerArray,
6721
- headerView
6722
- });
6723
- fileEntries.set(entry.filename, entry);
6724
- });
6725
- } catch (error) {
6726
- appendedFilenames.forEach(filename => filenames.delete(filename));
6727
- throw error;
6728
- } finally {
6729
- resolveAppendZip();
6730
- pendingAddFileCalls.delete(promiseAppendZip);
6731
- if (releaseLockWriter) {
6732
- releaseLockWriter();
6733
- }
6734
- }
6693
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
6735
6694
  }
6736
6695
 
6737
6696
  add(name = "", reader, options = {}) {
@@ -6893,11 +6852,140 @@
6893
6852
  return watchedPromise;
6894
6853
  }
6895
6854
 
6855
+ async function appendZipEntries(zipWriter, reader, options = {}) {
6856
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6857
+ const filter = checkFunctionOption(options.filter);
6858
+ while (pendingAddFileCalls.size) {
6859
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
6860
+ }
6861
+ let resolveAppendZip;
6862
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6863
+ pendingAddFileCalls.add(promiseAppendZip);
6864
+ const appendedFilenames = [];
6865
+ let releaseLockWriter;
6866
+ try {
6867
+ reader = new GenericReader(reader);
6868
+ await initStream(reader);
6869
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6870
+ reader = new BlobReader(await streamToBlob(reader.readable));
6871
+ await initStream(reader);
6872
+ }
6873
+ const { ZipReader, getEntryDataDescriptorLength } = await Promise.resolve().then(function () { return zipReader; });
6874
+ const zipReader$1 = new ZipReader(reader);
6875
+ const entries = await zipReader$1.getEntries();
6876
+ const keptEntries = [];
6877
+ for (const entry of entries) {
6878
+ if (!filter || await filter(entry)) {
6879
+ keptEntries.push(entry);
6880
+ }
6881
+ }
6882
+ await zipReader$1.close();
6883
+ await initStream(zipWriter.writer);
6884
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader$1;
6885
+ keptEntries.forEach(entry => {
6886
+ const { filename } = entry;
6887
+ if (entriesMissingZip64ExtraField.has(entry)) {
6888
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
6889
+ }
6890
+ if (filenames.has(filename)) {
6891
+ throw new Error(ERR_DUPLICATED_NAME);
6892
+ }
6893
+ filenames.add(filename);
6894
+ appendedFilenames.push(filename);
6895
+ });
6896
+ zipWriter.writerLocked = true;
6897
+ const { lockWriter } = zipWriter;
6898
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6899
+ zipWriter.writerLocked = false;
6900
+ resolve();
6901
+ });
6902
+ await lockWriter;
6903
+ if (zipWriter.addSplitZipSignature) {
6904
+ delete zipWriter.addSplitZipSignature;
6905
+ if (filter || !await startsWithSplitZipSignature(reader)) {
6906
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
6907
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6908
+ }
6909
+ }
6910
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
6911
+ keptEntries.forEach(entry => {
6912
+ const {
6913
+ version,
6914
+ rawLastModDate,
6915
+ rawFilename,
6916
+ rawBitFlag,
6917
+ uncompressedSize,
6918
+ compressedSize,
6919
+ extraFieldZip64
6920
+ } = entry;
6921
+ let {
6922
+ compressionMethod,
6923
+ rawExtraField,
6924
+ } = entry;
6925
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6926
+ if (entry.extraFieldAES) {
6927
+ compressionMethod = COMPRESSION_METHOD_AES;
6928
+ }
6929
+ const extraFieldLength = getLength(rawExtraField);
6930
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6931
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6932
+ const {
6933
+ headerArray,
6934
+ headerView
6935
+ } = getHeaderArrayData({
6936
+ version,
6937
+ bitFlag: rawBitFlag,
6938
+ compressionMethod,
6939
+ uncompressedSize,
6940
+ compressedSize,
6941
+ rawLastModDate,
6942
+ rawFilename,
6943
+ zip64CompressedSize,
6944
+ zip64UncompressedSize,
6945
+ extraFieldLength
6946
+ });
6947
+ const { crc32 } = entry;
6948
+ if (crc32 !== UNDEFINED_VALUE) {
6949
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6950
+ }
6951
+ const { offset, diskNumberStart } = entryPositions.get(entry);
6952
+ Object.assign(entry, {
6953
+ zip64Enabled: true,
6954
+ zip64UncompressedSize,
6955
+ zip64CompressedSize,
6956
+ offset,
6957
+ diskNumberStart,
6958
+ zip64DiskNumberStart: false,
6959
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6960
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6961
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6962
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6963
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6964
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6965
+ rawExtraField,
6966
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
6967
+ headerArray,
6968
+ headerView
6969
+ });
6970
+ fileEntries.set(entry.filename, entry);
6971
+ });
6972
+ } catch (error) {
6973
+ appendedFilenames.forEach(filename => filenames.delete(filename));
6974
+ throw error;
6975
+ } finally {
6976
+ resolveAppendZip();
6977
+ pendingAddFileCalls.delete(promiseAppendZip);
6978
+ if (releaseLockWriter) {
6979
+ releaseLockWriter();
6980
+ }
6981
+ }
6982
+ }
6983
+
6896
6984
  async function prependZipEntries(zipWriter, reader) {
6897
6985
  if (zipWriter.filenames.size) {
6898
6986
  throw new Error(ERR_ZIP_NOT_EMPTY);
6899
6987
  }
6900
- await zipWriter.appendZipEntries(reader);
6988
+ await appendZipEntries(zipWriter, reader);
6901
6989
  }
6902
6990
 
6903
6991
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -8571,7 +8659,7 @@
8571
8659
  return rawExtraField;
8572
8660
  }
8573
8661
 
8574
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
8662
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
8575
8663
  const { writer } = zipWriter;
8576
8664
  const entryPositions = new Map();
8577
8665
  if (writer.closeDisk || compact) {
@@ -8579,6 +8667,27 @@
8579
8667
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
8580
8668
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
8581
8669
  const keptEntrySet = new Set(keptEntries);
8670
+ const localHeaderLengths = new Map();
8671
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
8672
+ const entry = sortedEntries[indexEntry];
8673
+ if (keptEntrySet.has(entry)) {
8674
+ const sourceOffset = getSourceOffset(reader, entry);
8675
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
8676
+ if (localHeaderLength === UNDEFINED_VALUE) {
8677
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
8678
+ }
8679
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
8680
+ if (entry.bitFlag.dataDescriptor) {
8681
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
8682
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
8683
+ }
8684
+ if (entryEnd > regionEnds[indexEntry]) {
8685
+ throw new Error(ERR_OVERLAPPING_ENTRY);
8686
+ }
8687
+ regionEnds[indexEntry] = entryEnd;
8688
+ localHeaderLengths.set(entry, localHeaderLength);
8689
+ }
8690
+ }
8582
8691
  let copiedOffset = 0;
8583
8692
  let lastPosition;
8584
8693
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -8591,7 +8700,7 @@
8591
8700
  if (!compact) {
8592
8701
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
8593
8702
  }
8594
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
8703
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
8595
8704
  await writer.closeDisk();
8596
8705
  }
8597
8706
  lastPosition = {
@@ -8625,7 +8734,7 @@
8625
8734
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
8626
8735
  const nextEntry = sortedEntries[indexEntry + 1];
8627
8736
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
8628
- regionEnd = getSourceOffset(reader, nextEntry);
8737
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
8629
8738
  }
8630
8739
  regionEnds[indexEntry] = regionEnd;
8631
8740
  }
@@ -8639,11 +8748,13 @@
8639
8748
  try {
8640
8749
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
8641
8750
  } catch (error) {
8642
- zipWriter.hasCorruptedEntries = true;
8643
- try {
8644
- error.corruptedEntry = true;
8645
- } catch {
8646
- // ignored
8751
+ if (copiedLength) {
8752
+ zipWriter.hasCorruptedEntries = true;
8753
+ try {
8754
+ error.corruptedEntry = true;
8755
+ } catch {
8756
+ // ignored
8757
+ }
8647
8758
  }
8648
8759
  throw error;
8649
8760
  } finally {
@@ -8654,11 +8765,16 @@
8654
8765
  }
8655
8766
 
8656
8767
  async function getLocalHeaderLength(reader, offset) {
8657
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
8768
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
8769
+ await readUint8Array(reader, offset, HEADER_SIZE) :
8770
+ EMPTY_UINT8_ARRAY;
8658
8771
  if (getLength(headerArray) < HEADER_SIZE) {
8659
- return HEADER_SIZE;
8772
+ return UNDEFINED_VALUE;
8660
8773
  }
8661
8774
  const headerView = getDataView(headerArray);
8775
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
8776
+ return UNDEFINED_VALUE;
8777
+ }
8662
8778
  return HEADER_SIZE +
8663
8779
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
8664
8780
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -9009,7 +9125,7 @@
9009
9125
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9010
9126
  */
9011
9127
 
9012
- const VERSION = "2.20.0";
9128
+ const VERSION = "2.21.0";
9013
9129
 
9014
9130
  /*
9015
9131
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.