@zip.js/zip.js 2.19.0 → 2.20.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.
@@ -26,6 +26,6 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- const VERSION = "2.19.0";
29
+ const VERSION = "2.20.0";
30
30
 
31
31
  export { VERSION };
@@ -212,6 +212,8 @@ const WARNING_APPENDED_DATA = "appended data";
212
212
  const WARNING_PREPENDED_DATA = "prepended data";
213
213
  const WARNING_PREPENDED_CENTRAL_DIRECTORY = "prepended central directory";
214
214
  const WARNING_TRAILING_CENTRAL_DIRECTORY_DATA = "trailing central directory data";
215
+ const WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET = "mismatched central directory offset";
216
+ const WARNING_MISSING_ZIP64_EXTRA_FIELD = "missing zip64 extra field";
215
217
  const WARNING_DUPLICATE_FILENAME = "duplicate filename";
216
218
  const WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY = "mismatched zip64 end of central directory record";
217
219
  const WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY = "multiple end of central directory records";
@@ -374,6 +376,8 @@ class ZipReader {
374
376
  reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY);
375
377
  }
376
378
  directoryDataOffset = getDiskOffset(reader, diskNumber) + getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
379
+ } else if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || diskNumber == MAX_16_BITS) {
380
+ throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
377
381
  }
378
382
  }
379
383
  let declaredDirectoryDataLength = directoryDataLength;
@@ -396,25 +400,44 @@ class ZipReader {
396
400
  if (directoryArray.length < 4) {
397
401
  throw new Error(ERR_BAD_FORMAT);
398
402
  }
399
- const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
403
+ let expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength;
400
404
  if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
401
405
  const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE ||
402
406
  Boolean(directoryEncryptionInfo && directoryEncryptionInfo.compressedSize) ||
403
407
  detectEncryptedCentralDirectory(directoryView);
404
408
  let reconcile = !storedPointsAtDirectory;
405
- if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
406
- const expectedSignatureArray = await readUint8Array(reader, expectedDirectoryDataOffset, 4);
407
- reconcile = getUint32(getDataView(expectedSignatureArray), 0) == CENTRAL_FILE_HEADER_SIGNATURE;
409
+ if (expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
410
+ const expectedPointsAtDirectory = await pointsAtDirectory(reader, expectedDirectoryDataOffset);
411
+ if (storedPointsAtDirectory) {
412
+ reconcile = expectedPointsAtDirectory;
413
+ } else if (!expectedPointsAtDirectory) {
414
+ const signatureRecordOffset = await findDigitalSignatureRecordOffset(reader, centralDirectoryEndOffset);
415
+ const signedDirectoryDataOffset = signatureRecordOffset - directoryDataLength;
416
+ if (signatureRecordOffset !== UNDEFINED_VALUE && signedDirectoryDataOffset >= 0 &&
417
+ await pointsAtDirectory(reader, signedDirectoryDataOffset)) {
418
+ expectedDirectoryDataOffset = signedDirectoryDataOffset;
419
+ }
420
+ }
408
421
  }
409
422
  if (reconcile) {
410
423
  const originalDirectoryDataOffset = directoryDataOffset;
411
424
  directoryDataOffset = expectedDirectoryDataOffset;
425
+ directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
426
+ directoryView = getDataView(directoryArray);
412
427
  if (directoryDataOffset > originalDirectoryDataOffset) {
413
428
  prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
414
429
  prependedCentralDirectory = storedPointsAtDirectory;
430
+ } else {
431
+ reportAmbiguity(checkAmbiguity, warnings, WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET);
432
+ const offsetDelta = directoryDataOffset - originalDirectoryDataOffset;
433
+ if (directoryArray.length >= CENTRAL_FILE_HEADER_LENGTH) {
434
+ const localHeaderOffset = getUint32(directoryView, 42);
435
+ if (!await startsWithSignature(reader, localHeaderOffset, LOCAL_FILE_HEADER_SIGNATURE) &&
436
+ await startsWithSignature(reader, localHeaderOffset + offsetDelta, LOCAL_FILE_HEADER_SIGNATURE)) {
437
+ prependedDataLength += offsetDelta;
438
+ }
439
+ }
415
440
  }
416
- directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
417
- directoryView = getDataView(directoryArray);
418
441
  }
419
442
  }
420
443
  }
@@ -525,7 +548,16 @@ class ZipReader {
525
548
  filename,
526
549
  comment
527
550
  });
528
- const malformedExtraField = readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
551
+ let malformedExtraField;
552
+ try {
553
+ malformedExtraField = readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
554
+ } catch (error) {
555
+ if (checkAmbiguity || error.message != ERR_EXTRAFIELD_ZIP64_NOT_FOUND) {
556
+ throw error;
557
+ }
558
+ fileEntry.zip64ExtraFieldMissing = true;
559
+ addWarning(warnings, WARNING_MISSING_ZIP64_EXTRA_FIELD, fileEntry.filename);
560
+ }
529
561
  filename = fileEntry.filename;
530
562
  if (normalizeFilename) {
531
563
  const normalizedFilename = normalizeFilename(filename);
@@ -567,11 +599,12 @@ class ZipReader {
567
599
  const sticky = Boolean(fileEntry.unixMode & FILE_ATTR_UNIX_STICKY_MASK);
568
600
  const unixType = fileEntry.unixMode === UNDEFINED_VALUE ? unixExternalUpper : fileEntry.unixMode;
569
601
  const symlink = (unixType & FILE_ATTR_UNIX_TYPE_MASK) == FILE_ATTR_UNIX_TYPE_SYMLINK;
570
- const executable = !symlink && ((fileEntry.unixMode !== UNDEFINED_VALUE)
571
- ? ((fileEntry.unixMode & FILE_ATTR_UNIX_EXECUTABLE_MASK) != 0)
572
- : (unixCompatible && ((unixExternalUpper & FILE_ATTR_UNIX_EXECUTABLE_MASK) != 0)));
573
602
  const modeIsDir = fileEntry.unixMode !== UNDEFINED_VALUE && ((fileEntry.unixMode & FILE_ATTR_UNIX_TYPE_MASK) == FILE_ATTR_UNIX_TYPE_DIR);
574
603
  const upperIsDir = ((unixExternalUpper & FILE_ATTR_UNIX_TYPE_MASK) == FILE_ATTR_UNIX_TYPE_DIR);
604
+ const directory = modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || fileEntry.filename.endsWith(DIRECTORY_SIGNATURE);
605
+ const executable = !symlink && !directory && ((fileEntry.unixMode !== UNDEFINED_VALUE)
606
+ ? ((fileEntry.unixMode & FILE_ATTR_UNIX_EXECUTABLE_MASK) != 0)
607
+ : (unixCompatible && ((unixExternalUpper & FILE_ATTR_UNIX_EXECUTABLE_MASK) != 0)));
575
608
  Object.assign(fileEntry, {
576
609
  setuid,
577
610
  setgid,
@@ -579,7 +612,7 @@ class ZipReader {
579
612
  symlink,
580
613
  unixExternalUpper,
581
614
  executable,
582
- directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || fileEntry.filename.endsWith(DIRECTORY_SIGNATURE),
615
+ directory,
583
616
  zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
584
617
  });
585
618
  const entry = new Entry(fileEntry);
@@ -635,10 +668,10 @@ class ZipReader {
635
668
  const extractAppendedData = getOptionValue(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
636
669
  const splitZipSignatureLength = (checkAmbiguity || extractPrependedData) && filesLength &&
637
670
  startOffset == SPLIT_ZIP_FILE_SIGNATURE_LENGTH && await startsWithSplitZipMarker(reader) ? SPLIT_ZIP_FILE_SIGNATURE_LENGTH : 0;
638
- if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > splitZipSignatureLength))) {
671
+ if (checkAmbiguity && (prependedDataLength > 0 || (filesLength && startOffset > splitZipSignatureLength))) {
639
672
  throwAmbiguousArchive(WARNING_PREPENDED_DATA);
640
673
  }
641
- if (prependedDataLength || (filesLength && startOffset > SPLIT_ZIP_FILE_SIGNATURE_LENGTH)) {
674
+ if (prependedDataLength > 0 || (filesLength && startOffset > SPLIT_ZIP_FILE_SIGNATURE_LENGTH)) {
642
675
  addWarning(warnings, WARNING_PREPENDED_DATA);
643
676
  }
644
677
  if (prependedCentralDirectory) {
@@ -827,6 +860,8 @@ export {
827
860
  WARNING_PREPENDED_DATA,
828
861
  WARNING_PREPENDED_CENTRAL_DIRECTORY,
829
862
  WARNING_TRAILING_CENTRAL_DIRECTORY_DATA,
863
+ WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET,
864
+ WARNING_MISSING_ZIP64_EXTRA_FIELD,
830
865
  WARNING_DUPLICATE_FILENAME,
831
866
  WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY,
832
867
  WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY,
@@ -847,6 +882,9 @@ class ZipEntry {
847
882
 
848
883
  async getData(writer, fileEntry, readRanges, options = {}) {
849
884
  const zipEntry = this;
885
+ if (zipEntry.zip64ExtraFieldMissing) {
886
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
887
+ }
850
888
  const config = getConfiguration();
851
889
  const {
852
890
  reader,
@@ -1084,6 +1122,30 @@ function getWrappedFilesLength(directoryView, directoryArray, offset) {
1084
1122
  return wrappedFilesLength % (MAX_16_BITS + 1) ? 0 : wrappedFilesLength;
1085
1123
  }
1086
1124
 
1125
+ function pointsAtDirectory(reader, offset) {
1126
+ return startsWithSignature(reader, offset, CENTRAL_FILE_HEADER_SIGNATURE);
1127
+ }
1128
+
1129
+ async function startsWithSignature(reader, offset, signature) {
1130
+ if (offset < 0 || offset + 4 > reader.size) {
1131
+ return false;
1132
+ }
1133
+ const signatureArray = await readUint8Array(reader, offset, 4);
1134
+ return getUint32(getDataView(signatureArray), 0) == signature;
1135
+ }
1136
+
1137
+ async function findDigitalSignatureRecordOffset(reader, endOffset) {
1138
+ const startOffset = Math.max(0, endOffset - (6 + MAX_16_BITS));
1139
+ const tailArray = await readUint8Array(reader, startOffset, endOffset - startOffset);
1140
+ const tailView = getDataView(tailArray);
1141
+ for (let indexByte = tailArray.length - 6; indexByte >= 0; indexByte--) {
1142
+ if (getUint32(tailView, indexByte) == DIGITAL_SIGNATURE_RECORD_SIGNATURE &&
1143
+ indexByte + 6 + getUint16(tailView, indexByte + 4) == tailArray.length) {
1144
+ return startOffset + indexByte;
1145
+ }
1146
+ }
1147
+ }
1148
+
1087
1149
  function readDigitalSignature(signatureRecordArray) {
1088
1150
  if (signatureRecordArray.length >= 6) {
1089
1151
  const signatureRecordView = getDataView(signatureRecordArray);
@@ -1215,6 +1277,11 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
1215
1277
  readExtraFieldUnixDates(extraFieldUnixType1, directory);
1216
1278
  directory.extraFieldUnixType1 = extraFieldUnixType1;
1217
1279
  }
1280
+ const extraFieldExtendedTimestamp = extraField.get(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1281
+ if (extraFieldExtendedTimestamp) {
1282
+ readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory);
1283
+ directory.extraFieldExtendedTimestamp = extraFieldExtendedTimestamp;
1284
+ }
1218
1285
  const extraFieldNTFS = extraField.get(EXTRAFIELD_TYPE_NTFS);
1219
1286
  if (extraFieldNTFS) {
1220
1287
  readExtraFieldNTFS(extraFieldNTFS, directory);
@@ -1231,11 +1298,6 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
1231
1298
  readExtraFieldUnix(extraFieldUnix, unixIdsRead ? {} : directory, false);
1232
1299
  directory.extraFieldUnix = extraFieldUnix;
1233
1300
  }
1234
- const extraFieldExtendedTimestamp = extraField.get(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1235
- if (extraFieldExtendedTimestamp) {
1236
- readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory);
1237
- directory.extraFieldExtendedTimestamp = extraFieldExtendedTimestamp;
1238
- }
1239
1301
  const extraFieldUSDZ = extraField.get(EXTRAFIELD_TYPE_USDZ);
1240
1302
  if (extraFieldUSDZ) {
1241
1303
  directory.extraFieldUSDZ = extraFieldUSDZ;
@@ -342,13 +342,14 @@ class ZipWriter {
342
342
  return watchPromiseError(this, prependZipEntries(this, reader));
343
343
  }
344
344
 
345
- appendZip(reader) {
346
- return watchPromiseError(this, this.appendZipEntries(reader));
345
+ appendZip(reader, options = {}) {
346
+ return watchPromiseError(this, this.appendZipEntries(reader, options));
347
347
  }
348
348
 
349
- async appendZipEntries(reader) {
349
+ async appendZipEntries(reader, options = {}) {
350
350
  const zipWriter = this;
351
351
  const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
352
+ const filter = checkFunctionOption(options.filter);
352
353
  while (pendingAddFileCalls.size) {
353
354
  await Promise.allSettled(Array.from(pendingAddFileCalls));
354
355
  }
@@ -367,10 +368,16 @@ class ZipWriter {
367
368
  const { ZipReader } = await import("./zip-reader.js");
368
369
  const zipReader = new ZipReader(reader);
369
370
  const entries = await zipReader.getEntries();
371
+ const keptEntries = [];
372
+ for (const entry of entries) {
373
+ if (!filter || await filter(entry)) {
374
+ keptEntries.push(entry);
375
+ }
376
+ }
370
377
  await zipReader.close();
371
378
  await initStream(zipWriter.writer);
372
379
  const { directoryOffset } = zipReader;
373
- entries.forEach(({ filename }) => {
380
+ keptEntries.forEach(({ filename }) => {
374
381
  if (filenames.has(filename)) {
375
382
  throw new Error(ERR_DUPLICATED_NAME);
376
383
  }
@@ -391,8 +398,8 @@ class ZipWriter {
391
398
  zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
392
399
  }
393
400
  }
394
- const entryPositions = await copyZipData(zipWriter, reader, entries, directoryOffset);
395
- entries.forEach(entry => {
401
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
402
+ keptEntries.forEach(entry => {
396
403
  const {
397
404
  version,
398
405
  rawLastModDate,
@@ -2449,26 +2456,42 @@ function removeExtraFieldZip64(rawExtraField) {
2449
2456
  return rawExtraField;
2450
2457
  }
2451
2458
 
2452
- async function copyZipData(zipWriter, reader, entries, directoryOffset) {
2459
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
2453
2460
  const { writer } = zipWriter;
2454
2461
  const entryPositions = new Map();
2455
- if (writer.closeDisk) {
2462
+ if (writer.closeDisk || compact) {
2456
2463
  const sortedEntries = Array.from(entries).sort((firstEntry, secondEntry) =>
2457
2464
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
2458
- let copiedLength = 0;
2459
- for (const entry of sortedEntries) {
2460
- const sourceOffset = getSourceOffset(reader, entry);
2461
- await copyData(zipWriter, reader, copiedLength, sourceOffset - copiedLength);
2462
- if (exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
2463
- await writer.closeDisk();
2465
+ const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
2466
+ const keptEntrySet = new Set(keptEntries);
2467
+ let copiedOffset = 0;
2468
+ let lastPosition;
2469
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
2470
+ const entry = sortedEntries[indexEntry];
2471
+ if (keptEntrySet.has(entry)) {
2472
+ const sourceOffset = getSourceOffset(reader, entry);
2473
+ if (sourceOffset < copiedOffset) {
2474
+ entryPositions.set(entry, lastPosition);
2475
+ } else {
2476
+ if (!compact) {
2477
+ await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
2478
+ }
2479
+ if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
2480
+ await writer.closeDisk();
2481
+ }
2482
+ lastPosition = {
2483
+ offset: getSegmentOffset(zipWriter, writer),
2484
+ diskNumberStart: getDiskNumber(writer)
2485
+ };
2486
+ entryPositions.set(entry, lastPosition);
2487
+ await copyData(zipWriter, reader, sourceOffset, regionEnds[indexEntry] - sourceOffset);
2488
+ copiedOffset = regionEnds[indexEntry];
2489
+ }
2464
2490
  }
2465
- entryPositions.set(entry, {
2466
- offset: getSegmentOffset(zipWriter, writer),
2467
- diskNumberStart: getDiskNumber(writer)
2468
- });
2469
- copiedLength = sourceOffset;
2470
2491
  }
2471
- await copyData(zipWriter, reader, copiedLength, directoryOffset - copiedLength);
2492
+ if (!compact) {
2493
+ await copyData(zipWriter, reader, copiedOffset, directoryOffset - copiedOffset);
2494
+ }
2472
2495
  } else {
2473
2496
  const baseOffset = zipWriter.offset;
2474
2497
  await copyData(zipWriter, reader, 0, directoryOffset);
@@ -2480,6 +2503,20 @@ async function copyZipData(zipWriter, reader, entries, directoryOffset) {
2480
2503
  return entryPositions;
2481
2504
  }
2482
2505
 
2506
+ function getRegionEnds(reader, sortedEntries, directoryOffset) {
2507
+ const regionEnds = new Array(sortedEntries.length);
2508
+ let regionEnd = directoryOffset;
2509
+ for (let indexEntry = sortedEntries.length - 1; indexEntry >= 0; indexEntry--) {
2510
+ const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
2511
+ const nextEntry = sortedEntries[indexEntry + 1];
2512
+ if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
2513
+ regionEnd = getSourceOffset(reader, nextEntry);
2514
+ }
2515
+ regionEnds[indexEntry] = regionEnd;
2516
+ }
2517
+ return regionEnds;
2518
+ }
2519
+
2483
2520
  async function copyData(zipWriter, reader, offset, size) {
2484
2521
  if (size > 0) {
2485
2522
  const { writer } = zipWriter;
@@ -68,6 +68,8 @@ export {
68
68
  WARNING_PREPENDED_DATA,
69
69
  WARNING_PREPENDED_CENTRAL_DIRECTORY,
70
70
  WARNING_TRAILING_CENTRAL_DIRECTORY_DATA,
71
+ WARNING_MISMATCHED_CENTRAL_DIRECTORY_OFFSET,
72
+ WARNING_MISSING_ZIP64_EXTRA_FIELD,
71
73
  WARNING_DUPLICATE_FILENAME,
72
74
  WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY,
73
75
  WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A JavaScript library to zip and unzip files in the browser, Deno and Node.js",
4
4
  "author": "Gildas Lormeau",
5
5
  "license": "BSD-3-Clause",
6
- "version": "2.19.0",
6
+ "version": "2.20.0",
7
7
  "type": "module",
8
8
  "keywords": [
9
9
  "zip",