@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-fs.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";
@@ -4785,12 +4789,9 @@
4785
4789
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
4786
4790
  const ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = "End of Zip64 central directory locator not found";
4787
4791
  const ERR_CENTRAL_DIRECTORY_NOT_FOUND = "Central directory header not found";
4788
- const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
4789
- const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
4790
4792
  const ERR_ENCRYPTED = "File contains encrypted entry";
4791
4793
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
4792
4794
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
4793
- const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
4794
4795
  const ERR_ENTRY_DATA_OUT_OF_BOUNDS = "Entry data out of bounds";
4795
4796
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
4796
4797
  const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
@@ -4855,7 +4856,8 @@
4855
4856
  Object.assign(this, {
4856
4857
  reader: new GenericReader(reader),
4857
4858
  options,
4858
- readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] }
4859
+ readRanges: { indexes: new Set(), sortedRanges: [], pendingRanges: [] },
4860
+ entriesMissingZip64ExtraField: new WeakSet()
4859
4861
  });
4860
4862
  }
4861
4863
 
@@ -4926,16 +4928,22 @@
4926
4928
  directoryDataOffset = getDiskOffset$1(reader, getUint32$1(endOfDirectoryLocatorView, 4)) + getBigUint64(endOfDirectoryLocatorView, 8);
4927
4929
  let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4928
4930
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
4929
- const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4930
- if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
4931
- directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
4932
- const originalDirectoryDataOffset = directoryDataOffset;
4933
- directoryDataOffset = expectedDirectoryDataOffset;
4934
- if (directoryDataOffset > originalDirectoryDataOffset) {
4935
- prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4931
+ if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4932
+ const locatorOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
4933
+ let expectedDirectoryDataOffset = locatorOffset - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
4934
+ if (!await startsWithSignature(reader, expectedDirectoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE)) {
4935
+ expectedDirectoryDataOffset = await findZip64EndOfDirectoryOffset(reader, locatorOffset);
4936
+ }
4937
+ if (expectedDirectoryDataOffset !== UNDEFINED_VALUE && expectedDirectoryDataOffset >= 0 &&
4938
+ directoryDataOffset != expectedDirectoryDataOffset) {
4939
+ const originalDirectoryDataOffset = directoryDataOffset;
4940
+ directoryDataOffset = expectedDirectoryDataOffset;
4941
+ if (directoryDataOffset > originalDirectoryDataOffset) {
4942
+ prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
4943
+ }
4944
+ endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4945
+ endOfDirectoryView = getDataView(endOfDirectoryArray);
4936
4946
  }
4937
- endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
4938
- endOfDirectoryView = getDataView(endOfDirectoryArray);
4939
4947
  }
4940
4948
  if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32$1(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
4941
4949
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
@@ -4980,10 +4988,6 @@
4980
4988
  let declaredDirectoryDataLength = directoryDataLength;
4981
4989
  const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
4982
4990
  (zip64EndOfDirectory ? zip64EndOfDirectoryLength + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
4983
- if (directoryDataOffset >= reader.size) {
4984
- prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4985
- directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
4986
- }
4987
4991
  if (expectedLastDiskNumber != lastDiskNumber) {
4988
4992
  throw new Error(ERR_SPLIT_ZIP_FILE);
4989
4993
  }
@@ -4991,52 +4995,64 @@
4991
4995
  throw new Error(ERR_BAD_FORMAT);
4992
4996
  }
4993
4997
  let offset = 0;
4994
- let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
4998
+ let directoryArray = directoryDataOffset < reader.size ?
4999
+ await readUint8Array(reader, directoryDataOffset, directoryDataLength) :
5000
+ EMPTY_UINT8_ARRAY;
4995
5001
  let directoryView = getDataView(directoryArray);
4996
5002
  if (directoryDataLength) {
4997
- if (directoryArray.length < 4) {
4998
- throw new Error(ERR_BAD_FORMAT);
4999
- }
5000
5003
  let expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
5001
5004
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
5002
- const storedPointsAtDirectory = getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
5005
+ const storedPointsAtDirectory = directoryArray.length >= 4 && (
5006
+ getUint32$1(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
5003
5007
  Boolean(directoryEncryptionInfo && directoryEncryptionInfo.compressedSize) ||
5004
- detectEncryptedCentralDirectory(directoryView);
5008
+ detectEncryptedCentralDirectory(directoryView));
5009
+ if (!storedPointsAtDirectory && expectedDirectoryDataOffset < 0) {
5010
+ throw new Error(ERR_BAD_FORMAT);
5011
+ }
5005
5012
  let reconcile = !storedPointsAtDirectory;
5006
5013
  if (expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
5007
- const expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
5008
- if (storedPointsAtDirectory) {
5009
- reconcile = expectedPointsAtDirectory;
5010
- } else if (!expectedPointsAtDirectory) {
5014
+ let expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
5015
+ if (!expectedPointsAtDirectory) {
5011
5016
  const signatureRecordOffset = await findDigitalSignatureRecordOffset(reader, centralDirectoryEndOffset);
5012
5017
  const signedDirectoryDataOffset = signatureRecordOffset - directoryDataLength;
5013
5018
  if (signatureRecordOffset !== UNDEFINED_VALUE && signedDirectoryDataOffset >= 0 &&
5014
5019
  await pointsAtDirectory(reader, signedDirectoryDataOffset)) {
5015
5020
  expectedDirectoryDataOffset = signedDirectoryDataOffset;
5021
+ expectedPointsAtDirectory = true;
5016
5022
  }
5017
5023
  }
5024
+ if (storedPointsAtDirectory) {
5025
+ reconcile = expectedPointsAtDirectory;
5026
+ }
5018
5027
  }
5019
- if (reconcile) {
5028
+ if (reconcile && expectedDirectoryDataOffset != directoryDataOffset) {
5020
5029
  const originalDirectoryDataOffset = directoryDataOffset;
5021
5030
  directoryDataOffset = expectedDirectoryDataOffset;
5022
5031
  directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
5023
5032
  directoryView = getDataView(directoryArray);
5024
- if (directoryDataOffset > originalDirectoryDataOffset) {
5025
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
5033
+ const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5034
+ const localHeaderOffset = getFirstLocalHeaderOffset(directoryArray, directoryView);
5035
+ const localHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5036
+ await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE);
5037
+ const shiftedLocalHeaderFound = localHeaderOffset !== UNDEFINED_VALUE &&
5038
+ await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE);
5039
+ if (offsetDelta > 0 && !(localHeaderFound && !shiftedLocalHeaderFound)) {
5040
+ prependedDataLength += offsetDelta;
5026
5041
  prependedCentralDirectory = storedPointsAtDirectory;
5027
5042
  } else {
5028
5043
  reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET);
5029
- const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
5030
- if (directoryArray.length >= CENTRAL_FILE_HEADER_LENGTH) {
5031
- const localHeaderOffset = getUint32$1(directoryView, 42);
5032
- if (!await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE) &&
5033
- await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE)) {
5034
- prependedDataLength += offsetDelta;
5035
- }
5044
+ if (offsetDelta < 0 && !localHeaderFound && shiftedLocalHeaderFound) {
5045
+ prependedDataLength += offsetDelta;
5036
5046
  }
5037
5047
  }
5038
5048
  }
5039
5049
  }
5050
+ if (directoryArray.length < 4) {
5051
+ throw new Error(ERR_BAD_FORMAT);
5052
+ }
5053
+ } else if (!filesLength && directoryDataOffset < centralDirectoryEndOffset && diskNumber == lastDiskNumber) {
5054
+ prependedDataLength += centralDirectoryEndOffset - directoryDataOffset;
5055
+ directoryDataOffset = centralDirectoryEndOffset;
5040
5056
  }
5041
5057
  const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset;
5042
5058
  if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
@@ -5078,8 +5094,10 @@
5078
5094
  addWarning(warnings, WARNING_WRAPPED_ENTRIES_COUNT);
5079
5095
  }
5080
5096
  }
5097
+ const entryOptions = Object.assign({}, zipReader.options, options, { [OPTION_STRICTNESS]: strictness });
5098
+ delete entryOptions[OPTION_CHECK_AMBIGUITY];
5081
5099
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
5082
- const fileEntry = new ZipEntry$1(reader, zipReader.options);
5100
+ const fileEntry = new ZipEntry$1(reader, entryOptions);
5083
5101
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32$1(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
5084
5102
  if (indexFile == 0 && !decryptedDirectory && (zip64EndOfDirectoryVersion2 || detectEncryptedCentralDirectory(directoryView))) {
5085
5103
  throw new Error(ERR_ENCRYPTED_CENTRAL_DIRECTORY);
@@ -5170,13 +5188,17 @@
5170
5188
  if (malformedExtraField) {
5171
5189
  addWarning(warnings, WARNING_MALFORMED_EXTRA_FIELD, filename);
5172
5190
  }
5173
- fileEntry.offset += prependedDataLength;
5174
- const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5175
- startOffset = Math.min(entryPosition, startOffset);
5176
- if (entryPosition < previousEntryPosition) {
5177
- addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5191
+ if (fileEntry.zip64ExtraFieldMissing && fileEntry.offset == MAX_32_BITS) {
5192
+ startOffset = 0;
5193
+ } else {
5194
+ fileEntry.offset += prependedDataLength;
5195
+ const entryPosition = getDiskOffset$1(reader, fileEntry.diskNumberStart) + fileEntry.offset;
5196
+ startOffset = Math.min(entryPosition, startOffset);
5197
+ if (entryPosition < previousEntryPosition) {
5198
+ addWarning(warnings, WARNING_UNSORTED_CENTRAL_DIRECTORY, filename);
5199
+ }
5200
+ previousEntryPosition = entryPosition;
5178
5201
  }
5179
- previousEntryPosition = entryPosition;
5180
5202
  if ((fileEntry.version & MAX_8_BITS) > MAX_KNOWN_VERSION) {
5181
5203
  addWarning(warnings, WARNING_UNKNOWN_VERSION, filename);
5182
5204
  }
@@ -5213,12 +5235,15 @@
5213
5235
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
5214
5236
  });
5215
5237
  const entry = new Entry(fileEntry);
5216
- entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
5238
+ if (fileEntry.zip64ExtraFieldMissing) {
5239
+ zipReader.entriesMissingZip64ExtraField.add(entry);
5240
+ }
5241
+ entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader, options);
5217
5242
  entry.arrayBuffer = async options => {
5218
5243
  const writer = new TransformStream();
5219
5244
  const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
5220
5245
  arrayBufferPromise.catch(() => { });
5221
- await fileEntry.getData(writer, entry, zipReader.readRanges,
5246
+ await fileEntry.getData(writer, entry, zipReader,
5222
5247
  Object.assign({}, options, { preventClose: false }));
5223
5248
  return arrayBufferPromise;
5224
5249
  };
@@ -5426,8 +5451,9 @@
5426
5451
  });
5427
5452
  }
5428
5453
 
5429
- async getData(writer, fileEntry, readRanges, options = {}) {
5454
+ async getData(writer, fileEntry, zipReader, options = {}) {
5430
5455
  const zipEntry = this;
5456
+ const { readRanges, directoryOffset } = zipReader;
5431
5457
  if (zipEntry.zip64ExtraFieldMissing) {
5432
5458
  throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5433
5459
  }
@@ -5501,10 +5527,10 @@
5501
5527
  }
5502
5528
  validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, checkLocalFilename, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5503
5529
  const { lastAccessDate, creationDate, uid, gid } = localDirectory;
5504
- if (lastAccessDate) {
5530
+ if (lastAccessDate && fileEntry.lastAccessDate === UNDEFINED_VALUE) {
5505
5531
  fileEntry.lastAccessDate = lastAccessDate;
5506
5532
  }
5507
- if (creationDate) {
5533
+ if (creationDate && fileEntry.creationDate === UNDEFINED_VALUE) {
5508
5534
  fileEntry.creationDate = creationDate;
5509
5535
  }
5510
5536
  if (uid !== UNDEFINED_VALUE && fileEntry.uid === UNDEFINED_VALUE) {
@@ -5533,7 +5559,7 @@
5533
5559
  throw new Error(ERR_ENCRYPTED);
5534
5560
  }
5535
5561
  }
5536
- if (dataOffset + compressedSize > reader.size) {
5562
+ if (dataOffset + compressedSize > Math.min(reader.size, directoryOffset)) {
5537
5563
  throw new Error(ERR_ENTRY_DATA_OUT_OF_BOUNDS);
5538
5564
  }
5539
5565
  const size = compressedSize;
@@ -5599,6 +5625,7 @@
5599
5625
  extraFieldZip64: extraFieldZip64 || localDirectory.extraFieldZip64,
5600
5626
  readRanges
5601
5627
  });
5628
+ validateDataDescriptor(zipEntry, localDirectory, checkLocalDirectory ? UNDEFINED_VALUE : warnings);
5602
5629
  }
5603
5630
  let writable, abortError, aborted;
5604
5631
  try {
@@ -5618,10 +5645,7 @@
5618
5645
  writer.size += writtenSize;
5619
5646
  }
5620
5647
  } catch (error) {
5621
- const { outputSize: failedOutputSize } = workerOptions;
5622
- if (failedOutputSize !== UNDEFINED_VALUE) {
5623
- writer.size += failedOutputSize;
5624
- } else if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5648
+ if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
5625
5649
  writer.size += error.outputSize;
5626
5650
  }
5627
5651
  if (!checkPasswordOnly || !isErrorObject(error) || error.message != ERR_ABORT_CHECK_PASSWORD) {
@@ -5649,13 +5673,7 @@
5649
5673
  };
5650
5674
 
5651
5675
  function detectEncryptedCentralDirectory(directoryView) {
5652
- const maxOffset = Math.min(directoryView.byteLength, 1024) - 3;
5653
- for (let offset = 0; offset < maxOffset; offset++) {
5654
- if (getUint32$1(directoryView, offset) == ARCHIVE_EXTRA_DATA_SIGNATURE) {
5655
- return true;
5656
- }
5657
- }
5658
- return false;
5676
+ return directoryView.byteLength >= 4 && getUint32$1(directoryView, 0) == ARCHIVE_EXTRA_DATA_SIGNATURE;
5659
5677
  }
5660
5678
 
5661
5679
  function getWrappedFilesLength(directoryView, directoryArray, offset) {
@@ -5692,6 +5710,46 @@
5692
5710
  }
5693
5711
  }
5694
5712
 
5713
+ async function findZip64EndOfDirectoryOffset(reader, endOffset) {
5714
+ const startOffset = Math.max(0, endOffset - (ZIP64_END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS));
5715
+ const tailArray = await readUint8Array(reader, startOffset, endOffset - startOffset);
5716
+ const tailView = getDataView(tailArray);
5717
+ for (let indexByte = tailArray.length - ZIP64_END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
5718
+ if (getUint32$1(tailView, indexByte) == ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
5719
+ indexByte + 12 + Number(tailView.getBigUint64(indexByte + 4, true)) == tailArray.length) {
5720
+ return startOffset + indexByte;
5721
+ }
5722
+ }
5723
+ }
5724
+
5725
+ function getFirstLocalHeaderOffset(directoryArray, directoryView) {
5726
+ if (directoryArray.length < CENTRAL_FILE_HEADER_LENGTH) {
5727
+ return UNDEFINED_VALUE;
5728
+ }
5729
+ const localHeaderOffset = getUint32$1(directoryView, 42);
5730
+ if (localHeaderOffset != MAX_32_BITS) {
5731
+ return localHeaderOffset;
5732
+ }
5733
+ let offsetExtraField = CENTRAL_FILE_HEADER_LENGTH + getUint16$1(directoryView, 28);
5734
+ const endExtraField = Math.min(offsetExtraField + getUint16$1(directoryView, 30), directoryArray.length);
5735
+ while (offsetExtraField + 4 <= endExtraField) {
5736
+ const type = getUint16$1(directoryView, offsetExtraField);
5737
+ const size = getUint16$1(directoryView, offsetExtraField + 2);
5738
+ if (type == EXTRAFIELD_TYPE_ZIP64) {
5739
+ let offsetValue = offsetExtraField + 4;
5740
+ if (getUint32$1(directoryView, 24) == MAX_32_BITS) {
5741
+ offsetValue += 8;
5742
+ }
5743
+ if (getUint32$1(directoryView, 20) == MAX_32_BITS) {
5744
+ offsetValue += 8;
5745
+ }
5746
+ return offsetValue + 8 <= endExtraField ? getBigUint64(directoryView, offsetValue) : UNDEFINED_VALUE;
5747
+ }
5748
+ offsetExtraField += 4 + size;
5749
+ }
5750
+ return UNDEFINED_VALUE;
5751
+ }
5752
+
5695
5753
  function readDigitalSignature(signatureRecordArray) {
5696
5754
  if (signatureRecordArray.length >= 6) {
5697
5755
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -5781,6 +5839,7 @@
5781
5839
  uncompressedSize: getUint32$1(dataView, offset + HEADER_OFFSET_UNCOMPRESSED_SIZE)
5782
5840
  });
5783
5841
  const extraFieldZip64 = extraField.get(EXTRAFIELD_TYPE_ZIP64);
5842
+ let extraFieldZip64Missing;
5784
5843
  if (extraFieldZip64) {
5785
5844
  if (!readExtraFieldZip64(extraFieldZip64, directory, localDirectory)) {
5786
5845
  malformedExtraField = true;
@@ -5790,7 +5849,7 @@
5790
5849
  if (localDirectory) {
5791
5850
  malformedExtraField = true;
5792
5851
  } else {
5793
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5852
+ extraFieldZip64Missing = true;
5794
5853
  }
5795
5854
  }
5796
5855
  const extraFieldUnicodePath = extraField.get(EXTRAFIELD_TYPE_UNICODE_PATH);
@@ -5848,6 +5907,9 @@
5848
5907
  if (extraFieldUSDZ) {
5849
5908
  directory.extraFieldUSDZ = extraFieldUSDZ;
5850
5909
  }
5910
+ if (extraFieldZip64Missing) {
5911
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
5912
+ }
5851
5913
  return malformedExtraField;
5852
5914
  }
5853
5915
 
@@ -6062,13 +6124,7 @@
6062
6124
  let dataDescriptorLength = 0;
6063
6125
  if (dataDescriptor) {
6064
6126
  const zip64 = Boolean(extraFieldZip64);
6065
- const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH);
6066
- const dataDescriptorView = getDataView(dataDescriptorArray);
6067
- const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6068
- .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6069
- .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6070
- const localDataDescriptor = candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6071
- readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6127
+ const localDataDescriptor = await readLocalDataDescriptor(reader, dataOffset + compressedSize, { crc32, compressedSize, uncompressedSize, zip64 });
6072
6128
  if (localDataDescriptor) {
6073
6129
  fileEntry.localDirectory.dataDescriptor = localDataDescriptor;
6074
6130
  dataDescriptorLength = getDataDescriptorLength(localDataDescriptor.zip64, localDataDescriptor.signature);
@@ -6138,6 +6194,25 @@
6138
6194
  return mergedRanges;
6139
6195
  }
6140
6196
 
6197
+ async function readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64 }) {
6198
+ const maxLength = DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH;
6199
+ const length = Math.min(maxLength, Math.max(0, reader.size - offset));
6200
+ const dataDescriptorArray = length ? await readUint8Array(reader, offset, length) : EMPTY_UINT8_ARRAY;
6201
+ const dataDescriptorView = getDataView(dataDescriptorArray);
6202
+ const candidates = [[zip64, true], [zip64, false], [!zip64, true], [!zip64, false]]
6203
+ .map(([zip64Layout, signature]) => readDataDescriptor(dataDescriptorView, zip64Layout, signature))
6204
+ .filter(candidate => candidate && candidate.compressedSize == compressedSize && candidate.uncompressedSize == uncompressedSize);
6205
+ return candidates.find(candidate => candidate.crc32 == crc32) || candidates[0] ||
6206
+ readDataDescriptor(dataDescriptorView, zip64, true) || readDataDescriptor(dataDescriptorView, zip64, false);
6207
+ }
6208
+
6209
+ async function getEntryDataDescriptorLength(reader, offset, { crc32, compressedSize, uncompressedSize, extraFieldZip64 }) {
6210
+ const dataDescriptor = await readLocalDataDescriptor(reader, offset, { crc32, compressedSize, uncompressedSize, zip64: Boolean(extraFieldZip64) });
6211
+ if (dataDescriptor && dataDescriptor.compressedSize == compressedSize && dataDescriptor.uncompressedSize == uncompressedSize) {
6212
+ return getDataDescriptorLength(dataDescriptor.zip64, dataDescriptor.signature);
6213
+ }
6214
+ }
6215
+
6141
6216
  function readDataDescriptor(dataDescriptorView, zip64, signature) {
6142
6217
  const offset = signature ? DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH : 0;
6143
6218
  if (dataDescriptorView.byteLength < getDataDescriptorLength(zip64, signature) ||
@@ -6397,6 +6472,18 @@
6397
6472
  }
6398
6473
  }
6399
6474
 
6475
+ function validateDataDescriptor(zipEntry, localDirectory, warnings) {
6476
+ const { dataDescriptor } = localDirectory;
6477
+ const maskedLocalDirectory = zipEntry.decryptedDirectory &&
6478
+ (localDirectory.rawBitFlag & BITFLAG_MASKED_LOCAL_HEADERS) == BITFLAG_MASKED_LOCAL_HEADERS;
6479
+ if (dataDescriptor && !maskedLocalDirectory &&
6480
+ ((zipEntry.crc32 !== UNDEFINED_VALUE && dataDescriptor.crc32 != zipEntry.crc32) ||
6481
+ dataDescriptor.compressedSize != zipEntry.compressedSize ||
6482
+ dataDescriptor.uncompressedSize != zipEntry.uncompressedSize)) {
6483
+ reportAmbiguity(!warnings, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES);
6484
+ }
6485
+ }
6486
+
6400
6487
  function reportAmbiguity(reject, warnings, reason) {
6401
6488
  if (reject) {
6402
6489
  throwAmbiguousArchive(reason);
@@ -6499,6 +6586,7 @@
6499
6586
  WARNING_WRAPPED_ENTRIES_COUNT: WARNING_WRAPPED_ENTRIES_COUNT,
6500
6587
  ZipReader: ZipReader,
6501
6588
  ZipReaderStream: ZipReaderStream,
6589
+ getEntryDataDescriptorLength: getEntryDataDescriptorLength,
6502
6590
  isZipFile: isZipFile
6503
6591
  });
6504
6592
 
@@ -6622,136 +6710,7 @@
6622
6710
  }
6623
6711
 
6624
6712
  appendZip(reader, options = {}) {
6625
- return watchPromiseError(this, this.appendZipEntries(reader, options));
6626
- }
6627
-
6628
- async appendZipEntries(reader, options = {}) {
6629
- const zipWriter = this;
6630
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6631
- const filter = checkFunctionOption(options.filter);
6632
- while (pendingAddFileCalls.size) {
6633
- await Promise.allSettled(Array.from(pendingAddFileCalls));
6634
- }
6635
- let resolveAppendZip;
6636
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6637
- pendingAddFileCalls.add(promiseAppendZip);
6638
- const appendedFilenames = [];
6639
- let releaseLockWriter;
6640
- try {
6641
- reader = new GenericReader(reader);
6642
- await initStream(reader);
6643
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6644
- reader = new BlobReader(await streamToBlob(reader.readable));
6645
- await initStream(reader);
6646
- }
6647
- const { ZipReader } = await Promise.resolve().then(function () { return zipReader; });
6648
- const zipReader$1 = new ZipReader(reader);
6649
- const entries = await zipReader$1.getEntries();
6650
- const keptEntries = [];
6651
- for (const entry of entries) {
6652
- if (!filter || await filter(entry)) {
6653
- keptEntries.push(entry);
6654
- }
6655
- }
6656
- await zipReader$1.close();
6657
- await initStream(zipWriter.writer);
6658
- const { directoryOffset } = zipReader$1;
6659
- keptEntries.forEach(({ filename }) => {
6660
- if (filenames.has(filename)) {
6661
- throw new Error(ERR_DUPLICATED_NAME);
6662
- }
6663
- filenames.add(filename);
6664
- appendedFilenames.push(filename);
6665
- });
6666
- zipWriter.writerLocked = true;
6667
- const { lockWriter } = zipWriter;
6668
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6669
- zipWriter.writerLocked = false;
6670
- resolve();
6671
- });
6672
- await lockWriter;
6673
- if (zipWriter.addSplitZipSignature) {
6674
- delete zipWriter.addSplitZipSignature;
6675
- if (!await startsWithSplitZipSignature(reader)) {
6676
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
6677
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6678
- }
6679
- }
6680
- const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
6681
- keptEntries.forEach(entry => {
6682
- const {
6683
- version,
6684
- rawLastModDate,
6685
- rawFilename,
6686
- bitFlag,
6687
- encrypted,
6688
- uncompressedSize,
6689
- compressedSize,
6690
- extraFieldZip64
6691
- } = entry;
6692
- let {
6693
- compressionMethod,
6694
- rawExtraField,
6695
- } = entry;
6696
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
6697
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6698
- if (entry.extraFieldAES) {
6699
- compressionMethod = COMPRESSION_METHOD_AES;
6700
- }
6701
- const extraFieldLength = getLength(rawExtraField);
6702
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6703
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6704
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
6705
- const {
6706
- headerArray,
6707
- headerView
6708
- } = getHeaderArrayData({
6709
- version,
6710
- bitFlag: bitFlagValue,
6711
- compressionMethod,
6712
- uncompressedSize,
6713
- compressedSize,
6714
- rawLastModDate,
6715
- rawFilename,
6716
- zip64CompressedSize,
6717
- zip64UncompressedSize,
6718
- extraFieldLength
6719
- });
6720
- const { crc32 } = entry;
6721
- if (crc32 !== UNDEFINED_VALUE) {
6722
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6723
- }
6724
- const { offset, diskNumberStart } = entryPositions.get(entry);
6725
- Object.assign(entry, {
6726
- zip64Enabled: true,
6727
- zip64UncompressedSize,
6728
- zip64CompressedSize,
6729
- offset,
6730
- diskNumberStart,
6731
- zip64DiskNumberStart: false,
6732
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6733
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6734
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6735
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6736
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6737
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6738
- rawExtraField,
6739
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
6740
- headerArray,
6741
- headerView
6742
- });
6743
- fileEntries.set(entry.filename, entry);
6744
- });
6745
- } catch (error) {
6746
- appendedFilenames.forEach(filename => filenames.delete(filename));
6747
- throw error;
6748
- } finally {
6749
- resolveAppendZip();
6750
- pendingAddFileCalls.delete(promiseAppendZip);
6751
- if (releaseLockWriter) {
6752
- releaseLockWriter();
6753
- }
6754
- }
6713
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
6755
6714
  }
6756
6715
 
6757
6716
  add(name = "", reader, options = {}) {
@@ -6913,11 +6872,140 @@
6913
6872
  return watchedPromise;
6914
6873
  }
6915
6874
 
6875
+ async function appendZipEntries(zipWriter, reader, options = {}) {
6876
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
6877
+ const filter = checkFunctionOption(options.filter);
6878
+ while (pendingAddFileCalls.size) {
6879
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
6880
+ }
6881
+ let resolveAppendZip;
6882
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
6883
+ pendingAddFileCalls.add(promiseAppendZip);
6884
+ const appendedFilenames = [];
6885
+ let releaseLockWriter;
6886
+ try {
6887
+ reader = new GenericReader(reader);
6888
+ await initStream(reader);
6889
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
6890
+ reader = new BlobReader(await streamToBlob(reader.readable));
6891
+ await initStream(reader);
6892
+ }
6893
+ const { ZipReader, getEntryDataDescriptorLength } = await Promise.resolve().then(function () { return zipReader; });
6894
+ const zipReader$1 = new ZipReader(reader);
6895
+ const entries = await zipReader$1.getEntries();
6896
+ const keptEntries = [];
6897
+ for (const entry of entries) {
6898
+ if (!filter || await filter(entry)) {
6899
+ keptEntries.push(entry);
6900
+ }
6901
+ }
6902
+ await zipReader$1.close();
6903
+ await initStream(zipWriter.writer);
6904
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader$1;
6905
+ keptEntries.forEach(entry => {
6906
+ const { filename } = entry;
6907
+ if (entriesMissingZip64ExtraField.has(entry)) {
6908
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
6909
+ }
6910
+ if (filenames.has(filename)) {
6911
+ throw new Error(ERR_DUPLICATED_NAME);
6912
+ }
6913
+ filenames.add(filename);
6914
+ appendedFilenames.push(filename);
6915
+ });
6916
+ zipWriter.writerLocked = true;
6917
+ const { lockWriter } = zipWriter;
6918
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
6919
+ zipWriter.writerLocked = false;
6920
+ resolve();
6921
+ });
6922
+ await lockWriter;
6923
+ if (zipWriter.addSplitZipSignature) {
6924
+ delete zipWriter.addSplitZipSignature;
6925
+ if (filter || !await startsWithSplitZipSignature(reader)) {
6926
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
6927
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
6928
+ }
6929
+ }
6930
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
6931
+ keptEntries.forEach(entry => {
6932
+ const {
6933
+ version,
6934
+ rawLastModDate,
6935
+ rawFilename,
6936
+ rawBitFlag,
6937
+ uncompressedSize,
6938
+ compressedSize,
6939
+ extraFieldZip64
6940
+ } = entry;
6941
+ let {
6942
+ compressionMethod,
6943
+ rawExtraField,
6944
+ } = entry;
6945
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
6946
+ if (entry.extraFieldAES) {
6947
+ compressionMethod = COMPRESSION_METHOD_AES;
6948
+ }
6949
+ const extraFieldLength = getLength(rawExtraField);
6950
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
6951
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
6952
+ const {
6953
+ headerArray,
6954
+ headerView
6955
+ } = getHeaderArrayData({
6956
+ version,
6957
+ bitFlag: rawBitFlag,
6958
+ compressionMethod,
6959
+ uncompressedSize,
6960
+ compressedSize,
6961
+ rawLastModDate,
6962
+ rawFilename,
6963
+ zip64CompressedSize,
6964
+ zip64UncompressedSize,
6965
+ extraFieldLength
6966
+ });
6967
+ const { crc32 } = entry;
6968
+ if (crc32 !== UNDEFINED_VALUE) {
6969
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
6970
+ }
6971
+ const { offset, diskNumberStart } = entryPositions.get(entry);
6972
+ Object.assign(entry, {
6973
+ zip64Enabled: true,
6974
+ zip64UncompressedSize,
6975
+ zip64CompressedSize,
6976
+ offset,
6977
+ diskNumberStart,
6978
+ zip64DiskNumberStart: false,
6979
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
6980
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
6981
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
6982
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
6983
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
6984
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
6985
+ rawExtraField,
6986
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
6987
+ headerArray,
6988
+ headerView
6989
+ });
6990
+ fileEntries.set(entry.filename, entry);
6991
+ });
6992
+ } catch (error) {
6993
+ appendedFilenames.forEach(filename => filenames.delete(filename));
6994
+ throw error;
6995
+ } finally {
6996
+ resolveAppendZip();
6997
+ pendingAddFileCalls.delete(promiseAppendZip);
6998
+ if (releaseLockWriter) {
6999
+ releaseLockWriter();
7000
+ }
7001
+ }
7002
+ }
7003
+
6916
7004
  async function prependZipEntries(zipWriter, reader) {
6917
7005
  if (zipWriter.filenames.size) {
6918
7006
  throw new Error(ERR_ZIP_NOT_EMPTY);
6919
7007
  }
6920
- await zipWriter.appendZipEntries(reader);
7008
+ await appendZipEntries(zipWriter, reader);
6921
7009
  }
6922
7010
 
6923
7011
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -8689,7 +8777,7 @@
8689
8777
  return rawExtraField;
8690
8778
  }
8691
8779
 
8692
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
8780
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
8693
8781
  const { writer } = zipWriter;
8694
8782
  const entryPositions = new Map();
8695
8783
  if (writer.closeDisk || compact) {
@@ -8697,6 +8785,27 @@
8697
8785
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
8698
8786
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
8699
8787
  const keptEntrySet = new Set(keptEntries);
8788
+ const localHeaderLengths = new Map();
8789
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
8790
+ const entry = sortedEntries[indexEntry];
8791
+ if (keptEntrySet.has(entry)) {
8792
+ const sourceOffset = getSourceOffset(reader, entry);
8793
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
8794
+ if (localHeaderLength === UNDEFINED_VALUE) {
8795
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
8796
+ }
8797
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
8798
+ if (entry.bitFlag.dataDescriptor) {
8799
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
8800
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
8801
+ }
8802
+ if (entryEnd > regionEnds[indexEntry]) {
8803
+ throw new Error(ERR_OVERLAPPING_ENTRY);
8804
+ }
8805
+ regionEnds[indexEntry] = entryEnd;
8806
+ localHeaderLengths.set(entry, localHeaderLength);
8807
+ }
8808
+ }
8700
8809
  let copiedOffset = 0;
8701
8810
  let lastPosition;
8702
8811
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -8709,7 +8818,7 @@
8709
8818
  if (!compact) {
8710
8819
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
8711
8820
  }
8712
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
8821
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
8713
8822
  await writer.closeDisk();
8714
8823
  }
8715
8824
  lastPosition = {
@@ -8743,7 +8852,7 @@
8743
8852
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
8744
8853
  const nextEntry = sortedEntries[indexEntry + 1];
8745
8854
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
8746
- regionEnd = getSourceOffset(reader, nextEntry);
8855
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
8747
8856
  }
8748
8857
  regionEnds[indexEntry] = regionEnd;
8749
8858
  }
@@ -8757,11 +8866,13 @@
8757
8866
  try {
8758
8867
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
8759
8868
  } catch (error) {
8760
- zipWriter.hasCorruptedEntries = true;
8761
- try {
8762
- error.corruptedEntry = true;
8763
- } catch {
8764
- // ignored
8869
+ if (copiedLength) {
8870
+ zipWriter.hasCorruptedEntries = true;
8871
+ try {
8872
+ error.corruptedEntry = true;
8873
+ } catch {
8874
+ // ignored
8875
+ }
8765
8876
  }
8766
8877
  throw error;
8767
8878
  } finally {
@@ -8772,11 +8883,16 @@
8772
8883
  }
8773
8884
 
8774
8885
  async function getLocalHeaderLength(reader, offset) {
8775
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
8886
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
8887
+ await readUint8Array(reader, offset, HEADER_SIZE) :
8888
+ EMPTY_UINT8_ARRAY;
8776
8889
  if (getLength(headerArray) < HEADER_SIZE) {
8777
- return HEADER_SIZE;
8890
+ return UNDEFINED_VALUE;
8778
8891
  }
8779
8892
  const headerView = getDataView(headerArray);
8893
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
8894
+ return UNDEFINED_VALUE;
8895
+ }
8780
8896
  return HEADER_SIZE +
8781
8897
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
8782
8898
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -9127,7 +9243,7 @@
9127
9243
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9128
9244
  */
9129
9245
 
9130
- const VERSION = "2.20.0";
9246
+ const VERSION = "2.21.0";
9131
9247
 
9132
9248
  /*
9133
9249
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.