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