@zip.js/zip.js 2.19.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.
@@ -114,7 +114,6 @@ import {
114
114
  BITFLAG_ENCRYPTED,
115
115
  BITFLAG_DATA_DESCRIPTOR,
116
116
  BITFLAG_LANG_ENCODING_FLAG,
117
- BITFLAG_LEVEL,
118
117
  BITFLAG_LEVEL_FAST_MASK,
119
118
  BITFLAG_LEVEL_SUPER_FAST_MASK,
120
119
  BITFLAG_LEVEL_MAX_MASK,
@@ -140,6 +139,9 @@ import {
140
139
  VERSION_AES,
141
140
  DIRECTORY_SIGNATURE,
142
141
  HEADER_SIZE,
142
+ ERR_LOCAL_FILE_HEADER_NOT_FOUND,
143
+ ERR_OVERLAPPING_ENTRY,
144
+ ERR_EXTRAFIELD_ZIP64_NOT_FOUND,
143
145
  HEADER_OFFSET_VERSION,
144
146
  HEADER_OFFSET_SIGNATURE,
145
147
  HEADER_OFFSET_COMPRESSED_SIZE,
@@ -342,130 +344,8 @@ class ZipWriter {
342
344
  return watchPromiseError(this, prependZipEntries(this, reader));
343
345
  }
344
346
 
345
- appendZip(reader) {
346
- return watchPromiseError(this, this.appendZipEntries(reader));
347
- }
348
-
349
- async appendZipEntries(reader) {
350
- const zipWriter = this;
351
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
352
- while (pendingAddFileCalls.size) {
353
- await Promise.allSettled(Array.from(pendingAddFileCalls));
354
- }
355
- let resolveAppendZip;
356
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
357
- pendingAddFileCalls.add(promiseAppendZip);
358
- const appendedFilenames = [];
359
- let releaseLockWriter;
360
- try {
361
- reader = new GenericReader(reader);
362
- await initStream(reader);
363
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
364
- reader = new BlobReader(await streamToBlob(reader.readable));
365
- await initStream(reader);
366
- }
367
- const { ZipReader } = await import("./zip-reader.js");
368
- const zipReader = new ZipReader(reader);
369
- const entries = await zipReader.getEntries();
370
- await zipReader.close();
371
- await initStream(zipWriter.writer);
372
- const { directoryOffset } = zipReader;
373
- entries.forEach(({ filename }) => {
374
- if (filenames.has(filename)) {
375
- throw new Error(ERR_DUPLICATED_NAME);
376
- }
377
- filenames.add(filename);
378
- appendedFilenames.push(filename);
379
- });
380
- zipWriter.writerLocked = true;
381
- const { lockWriter } = zipWriter;
382
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
383
- zipWriter.writerLocked = false;
384
- resolve();
385
- });
386
- await lockWriter;
387
- if (zipWriter.addSplitZipSignature) {
388
- delete zipWriter.addSplitZipSignature;
389
- if (!await startsWithSplitZipSignature(reader)) {
390
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
391
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
392
- }
393
- }
394
- const entryPositions = await copyZipData(zipWriter, reader, entries, directoryOffset);
395
- entries.forEach(entry => {
396
- const {
397
- version,
398
- rawLastModDate,
399
- rawFilename,
400
- bitFlag,
401
- encrypted,
402
- uncompressedSize,
403
- compressedSize,
404
- extraFieldZip64
405
- } = entry;
406
- let {
407
- compressionMethod,
408
- rawExtraField,
409
- } = entry;
410
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
411
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
412
- if (entry.extraFieldAES) {
413
- compressionMethod = COMPRESSION_METHOD_AES;
414
- }
415
- const extraFieldLength = getLength(rawExtraField);
416
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
417
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
418
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
419
- const {
420
- headerArray,
421
- headerView
422
- } = getHeaderArrayData({
423
- version,
424
- bitFlag: bitFlagValue,
425
- compressionMethod,
426
- uncompressedSize,
427
- compressedSize,
428
- rawLastModDate,
429
- rawFilename,
430
- zip64CompressedSize,
431
- zip64UncompressedSize,
432
- extraFieldLength
433
- });
434
- const { crc32 } = entry;
435
- if (crc32 !== UNDEFINED_VALUE) {
436
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
437
- }
438
- const { offset, diskNumberStart } = entryPositions.get(entry);
439
- Object.assign(entry, {
440
- zip64Enabled: true,
441
- zip64UncompressedSize,
442
- zip64CompressedSize,
443
- offset,
444
- diskNumberStart,
445
- zip64DiskNumberStart: false,
446
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
447
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
448
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
449
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
450
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
451
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
452
- rawExtraField,
453
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
454
- headerArray,
455
- headerView
456
- });
457
- fileEntries.set(entry.filename, entry);
458
- });
459
- } catch (error) {
460
- appendedFilenames.forEach(filename => filenames.delete(filename));
461
- throw error;
462
- } finally {
463
- resolveAppendZip();
464
- pendingAddFileCalls.delete(promiseAppendZip);
465
- if (releaseLockWriter) {
466
- releaseLockWriter();
467
- }
468
- }
347
+ appendZip(reader, options = {}) {
348
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
469
349
  }
470
350
 
471
351
  add(name = "", reader, options = {}) {
@@ -627,11 +507,140 @@ function watchPromiseError(zipWriter, promise) {
627
507
  return watchedPromise;
628
508
  }
629
509
 
510
+ async function appendZipEntries(zipWriter, reader, options = {}) {
511
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
512
+ const filter = checkFunctionOption(options.filter);
513
+ while (pendingAddFileCalls.size) {
514
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
515
+ }
516
+ let resolveAppendZip;
517
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
518
+ pendingAddFileCalls.add(promiseAppendZip);
519
+ const appendedFilenames = [];
520
+ let releaseLockWriter;
521
+ try {
522
+ reader = new GenericReader(reader);
523
+ await initStream(reader);
524
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
525
+ reader = new BlobReader(await streamToBlob(reader.readable));
526
+ await initStream(reader);
527
+ }
528
+ const { ZipReader, getEntryDataDescriptorLength } = await import("./zip-reader.js");
529
+ const zipReader = new ZipReader(reader);
530
+ const entries = await zipReader.getEntries();
531
+ const keptEntries = [];
532
+ for (const entry of entries) {
533
+ if (!filter || await filter(entry)) {
534
+ keptEntries.push(entry);
535
+ }
536
+ }
537
+ await zipReader.close();
538
+ await initStream(zipWriter.writer);
539
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader;
540
+ keptEntries.forEach(entry => {
541
+ const { filename } = entry;
542
+ if (entriesMissingZip64ExtraField.has(entry)) {
543
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
544
+ }
545
+ if (filenames.has(filename)) {
546
+ throw new Error(ERR_DUPLICATED_NAME);
547
+ }
548
+ filenames.add(filename);
549
+ appendedFilenames.push(filename);
550
+ });
551
+ zipWriter.writerLocked = true;
552
+ const { lockWriter } = zipWriter;
553
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
554
+ zipWriter.writerLocked = false;
555
+ resolve();
556
+ });
557
+ await lockWriter;
558
+ if (zipWriter.addSplitZipSignature) {
559
+ delete zipWriter.addSplitZipSignature;
560
+ if (filter || !await startsWithSplitZipSignature(reader)) {
561
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
562
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
563
+ }
564
+ }
565
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
566
+ keptEntries.forEach(entry => {
567
+ const {
568
+ version,
569
+ rawLastModDate,
570
+ rawFilename,
571
+ rawBitFlag,
572
+ uncompressedSize,
573
+ compressedSize,
574
+ extraFieldZip64
575
+ } = entry;
576
+ let {
577
+ compressionMethod,
578
+ rawExtraField,
579
+ } = entry;
580
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
581
+ if (entry.extraFieldAES) {
582
+ compressionMethod = COMPRESSION_METHOD_AES;
583
+ }
584
+ const extraFieldLength = getLength(rawExtraField);
585
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
586
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
587
+ const {
588
+ headerArray,
589
+ headerView
590
+ } = getHeaderArrayData({
591
+ version,
592
+ bitFlag: rawBitFlag,
593
+ compressionMethod,
594
+ uncompressedSize,
595
+ compressedSize,
596
+ rawLastModDate,
597
+ rawFilename,
598
+ zip64CompressedSize,
599
+ zip64UncompressedSize,
600
+ extraFieldLength
601
+ });
602
+ const { crc32 } = entry;
603
+ if (crc32 !== UNDEFINED_VALUE) {
604
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
605
+ }
606
+ const { offset, diskNumberStart } = entryPositions.get(entry);
607
+ Object.assign(entry, {
608
+ zip64Enabled: true,
609
+ zip64UncompressedSize,
610
+ zip64CompressedSize,
611
+ offset,
612
+ diskNumberStart,
613
+ zip64DiskNumberStart: false,
614
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
615
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
616
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
617
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
618
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
619
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
620
+ rawExtraField,
621
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
622
+ headerArray,
623
+ headerView
624
+ });
625
+ fileEntries.set(entry.filename, entry);
626
+ });
627
+ } catch (error) {
628
+ appendedFilenames.forEach(filename => filenames.delete(filename));
629
+ throw error;
630
+ } finally {
631
+ resolveAppendZip();
632
+ pendingAddFileCalls.delete(promiseAppendZip);
633
+ if (releaseLockWriter) {
634
+ releaseLockWriter();
635
+ }
636
+ }
637
+ }
638
+
630
639
  async function prependZipEntries(zipWriter, reader) {
631
640
  if (zipWriter.filenames.size) {
632
641
  throw new Error(ERR_ZIP_NOT_EMPTY);
633
642
  }
634
- await zipWriter.appendZipEntries(reader);
643
+ await appendZipEntries(zipWriter, reader);
635
644
  }
636
645
 
637
646
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -2449,26 +2458,63 @@ function removeExtraFieldZip64(rawExtraField) {
2449
2458
  return rawExtraField;
2450
2459
  }
2451
2460
 
2452
- async function copyZipData(zipWriter, reader, entries, directoryOffset) {
2461
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
2453
2462
  const { writer } = zipWriter;
2454
2463
  const entryPositions = new Map();
2455
- if (writer.closeDisk) {
2464
+ if (writer.closeDisk || compact) {
2456
2465
  const sortedEntries = Array.from(entries).sort((firstEntry, secondEntry) =>
2457
2466
  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();
2467
+ const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
2468
+ const keptEntrySet = new Set(keptEntries);
2469
+ const localHeaderLengths = new Map();
2470
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
2471
+ const entry = sortedEntries[indexEntry];
2472
+ if (keptEntrySet.has(entry)) {
2473
+ const sourceOffset = getSourceOffset(reader, entry);
2474
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
2475
+ if (localHeaderLength === UNDEFINED_VALUE) {
2476
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
2477
+ }
2478
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
2479
+ if (entry.bitFlag.dataDescriptor) {
2480
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
2481
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
2482
+ }
2483
+ if (entryEnd > regionEnds[indexEntry]) {
2484
+ throw new Error(ERR_OVERLAPPING_ENTRY);
2485
+ }
2486
+ regionEnds[indexEntry] = entryEnd;
2487
+ localHeaderLengths.set(entry, localHeaderLength);
2488
+ }
2489
+ }
2490
+ let copiedOffset = 0;
2491
+ let lastPosition;
2492
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
2493
+ const entry = sortedEntries[indexEntry];
2494
+ if (keptEntrySet.has(entry)) {
2495
+ const sourceOffset = getSourceOffset(reader, entry);
2496
+ if (sourceOffset < copiedOffset) {
2497
+ entryPositions.set(entry, lastPosition);
2498
+ } else {
2499
+ if (!compact) {
2500
+ await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
2501
+ }
2502
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
2503
+ await writer.closeDisk();
2504
+ }
2505
+ lastPosition = {
2506
+ offset: getSegmentOffset(zipWriter, writer),
2507
+ diskNumberStart: getDiskNumber(writer)
2508
+ };
2509
+ entryPositions.set(entry, lastPosition);
2510
+ await copyData(zipWriter, reader, sourceOffset, regionEnds[indexEntry] - sourceOffset);
2511
+ copiedOffset = regionEnds[indexEntry];
2512
+ }
2464
2513
  }
2465
- entryPositions.set(entry, {
2466
- offset: getSegmentOffset(zipWriter, writer),
2467
- diskNumberStart: getDiskNumber(writer)
2468
- });
2469
- copiedLength = sourceOffset;
2470
2514
  }
2471
- await copyData(zipWriter, reader, copiedLength, directoryOffset - copiedLength);
2515
+ if (!compact) {
2516
+ await copyData(zipWriter, reader, copiedOffset, directoryOffset - copiedOffset);
2517
+ }
2472
2518
  } else {
2473
2519
  const baseOffset = zipWriter.offset;
2474
2520
  await copyData(zipWriter, reader, 0, directoryOffset);
@@ -2480,6 +2526,20 @@ async function copyZipData(zipWriter, reader, entries, directoryOffset) {
2480
2526
  return entryPositions;
2481
2527
  }
2482
2528
 
2529
+ function getRegionEnds(reader, sortedEntries, directoryOffset) {
2530
+ const regionEnds = new Array(sortedEntries.length);
2531
+ let regionEnd = directoryOffset;
2532
+ for (let indexEntry = sortedEntries.length - 1; indexEntry >= 0; indexEntry--) {
2533
+ const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
2534
+ const nextEntry = sortedEntries[indexEntry + 1];
2535
+ if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
2536
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
2537
+ }
2538
+ regionEnds[indexEntry] = regionEnd;
2539
+ }
2540
+ return regionEnds;
2541
+ }
2542
+
2483
2543
  async function copyData(zipWriter, reader, offset, size) {
2484
2544
  if (size > 0) {
2485
2545
  const { writer } = zipWriter;
@@ -2487,11 +2547,13 @@ async function copyData(zipWriter, reader, offset, size) {
2487
2547
  try {
2488
2548
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
2489
2549
  } catch (error) {
2490
- zipWriter.hasCorruptedEntries = true;
2491
- try {
2492
- error.corruptedEntry = true;
2493
- } catch {
2494
- // ignored
2550
+ if (copiedLength) {
2551
+ zipWriter.hasCorruptedEntries = true;
2552
+ try {
2553
+ error.corruptedEntry = true;
2554
+ } catch {
2555
+ // ignored
2556
+ }
2495
2557
  }
2496
2558
  throw error;
2497
2559
  } finally {
@@ -2502,11 +2564,16 @@ async function copyData(zipWriter, reader, offset, size) {
2502
2564
  }
2503
2565
 
2504
2566
  async function getLocalHeaderLength(reader, offset) {
2505
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
2567
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
2568
+ await readUint8Array(reader, offset, HEADER_SIZE) :
2569
+ EMPTY_UINT8_ARRAY;
2506
2570
  if (getLength(headerArray) < HEADER_SIZE) {
2507
- return HEADER_SIZE;
2571
+ return UNDEFINED_VALUE;
2508
2572
  }
2509
2573
  const headerView = getDataView(headerArray);
2574
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
2575
+ return UNDEFINED_VALUE;
2576
+ }
2510
2577
  return HEADER_SIZE +
2511
2578
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
2512
2579
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -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.21.0",
7
7
  "type": "module",
8
8
  "keywords": [
9
9
  "zip",