@zip.js/zip.js 2.20.0 → 2.22.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,
@@ -241,6 +243,7 @@ import {
241
243
  TEXT_TYPE_COMMENT,
242
244
  ERR_INVALID_PASSWORD_TYPE,
243
245
  checkFunctionOption,
246
+ checkReaderOptionsObject,
244
247
  checkSignalOption,
245
248
  throwIfAborted,
246
249
  checkPasswordOption,
@@ -343,136 +346,7 @@ class ZipWriter {
343
346
  }
344
347
 
345
348
  appendZip(reader, options = {}) {
346
- return watchPromiseError(this, this.appendZipEntries(reader, options));
347
- }
348
-
349
- async appendZipEntries(reader, options = {}) {
350
- const zipWriter = this;
351
- const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
352
- const filter = checkFunctionOption(options.filter);
353
- while (pendingAddFileCalls.size) {
354
- await Promise.allSettled(Array.from(pendingAddFileCalls));
355
- }
356
- let resolveAppendZip;
357
- const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
358
- pendingAddFileCalls.add(promiseAppendZip);
359
- const appendedFilenames = [];
360
- let releaseLockWriter;
361
- try {
362
- reader = new GenericReader(reader);
363
- await initStream(reader);
364
- if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
365
- reader = new BlobReader(await streamToBlob(reader.readable));
366
- await initStream(reader);
367
- }
368
- const { ZipReader } = await import("./zip-reader.js");
369
- const zipReader = new ZipReader(reader);
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
- }
377
- await zipReader.close();
378
- await initStream(zipWriter.writer);
379
- const { directoryOffset } = zipReader;
380
- keptEntries.forEach(({ filename }) => {
381
- if (filenames.has(filename)) {
382
- throw new Error(ERR_DUPLICATED_NAME);
383
- }
384
- filenames.add(filename);
385
- appendedFilenames.push(filename);
386
- });
387
- zipWriter.writerLocked = true;
388
- const { lockWriter } = zipWriter;
389
- zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
390
- zipWriter.writerLocked = false;
391
- resolve();
392
- });
393
- await lockWriter;
394
- if (zipWriter.addSplitZipSignature) {
395
- delete zipWriter.addSplitZipSignature;
396
- if (!await startsWithSplitZipSignature(reader)) {
397
- await writeData(zipWriter.writer, getSplitZipSignatureArray());
398
- zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
399
- }
400
- }
401
- const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter));
402
- keptEntries.forEach(entry => {
403
- const {
404
- version,
405
- rawLastModDate,
406
- rawFilename,
407
- bitFlag,
408
- encrypted,
409
- uncompressedSize,
410
- compressedSize,
411
- extraFieldZip64
412
- } = entry;
413
- let {
414
- compressionMethod,
415
- rawExtraField,
416
- } = entry;
417
- const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
418
- rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
419
- if (entry.extraFieldAES) {
420
- compressionMethod = COMPRESSION_METHOD_AES;
421
- }
422
- const extraFieldLength = getLength(rawExtraField);
423
- const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
424
- const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
425
- const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
426
- const {
427
- headerArray,
428
- headerView
429
- } = getHeaderArrayData({
430
- version,
431
- bitFlag: bitFlagValue,
432
- compressionMethod,
433
- uncompressedSize,
434
- compressedSize,
435
- rawLastModDate,
436
- rawFilename,
437
- zip64CompressedSize,
438
- zip64UncompressedSize,
439
- extraFieldLength
440
- });
441
- const { crc32 } = entry;
442
- if (crc32 !== UNDEFINED_VALUE) {
443
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
444
- }
445
- const { offset, diskNumberStart } = entryPositions.get(entry);
446
- Object.assign(entry, {
447
- zip64Enabled: true,
448
- zip64UncompressedSize,
449
- zip64CompressedSize,
450
- offset,
451
- diskNumberStart,
452
- zip64DiskNumberStart: false,
453
- rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
454
- rawExtraFieldAES: EMPTY_UINT8_ARRAY,
455
- rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
456
- rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
457
- rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
458
- rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
459
- rawExtraField,
460
- rawCentralExtraField: EMPTY_UINT8_ARRAY,
461
- headerArray,
462
- headerView
463
- });
464
- fileEntries.set(entry.filename, entry);
465
- });
466
- } catch (error) {
467
- appendedFilenames.forEach(filename => filenames.delete(filename));
468
- throw error;
469
- } finally {
470
- resolveAppendZip();
471
- pendingAddFileCalls.delete(promiseAppendZip);
472
- if (releaseLockWriter) {
473
- releaseLockWriter();
474
- }
475
- }
349
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
476
350
  }
477
351
 
478
352
  add(name = "", reader, options = {}) {
@@ -634,11 +508,149 @@ function watchPromiseError(zipWriter, promise) {
634
508
  return watchedPromise;
635
509
  }
636
510
 
511
+ async function appendZipEntries(zipWriter, reader, options = {}) {
512
+ const { pendingAddFileCalls, filenames, fileEntries } = zipWriter;
513
+ const filter = checkFunctionOption(options.filter);
514
+ const readerOptions = checkReaderOptionsObject(options.readerOptions);
515
+ while (pendingAddFileCalls.size) {
516
+ await Promise.allSettled(Array.from(pendingAddFileCalls));
517
+ }
518
+ let resolveAppendZip;
519
+ const promiseAppendZip = new Promise(resolve => resolveAppendZip = resolve);
520
+ pendingAddFileCalls.add(promiseAppendZip);
521
+ const appendedFilenames = [];
522
+ let releaseLockWriter;
523
+ try {
524
+ reader = new GenericReader(reader);
525
+ await initStream(reader);
526
+ if (reader.size === UNDEFINED_VALUE || !reader.readUint8Array) {
527
+ reader = new BlobReader(await streamToBlob(reader.readable));
528
+ await initStream(reader);
529
+ }
530
+ const { ZipReader, getEntryDataDescriptorLength } = await import("./zip-reader.js");
531
+ const zipReader = new ZipReader(reader, readerOptions);
532
+ const keptEntries = [];
533
+ let entries;
534
+ try {
535
+ entries = await zipReader.getEntries();
536
+ for (const entry of entries) {
537
+ if (!filter || await filter(entry, getExistingEntry(fileEntries, entry.filename))) {
538
+ keptEntries.push(entry);
539
+ }
540
+ }
541
+ } finally {
542
+ await zipReader.close();
543
+ }
544
+ await initStream(zipWriter.writer);
545
+ const { directoryOffset, entriesMissingZip64ExtraField } = zipReader;
546
+ keptEntries.forEach(entry => {
547
+ const { filename } = entry;
548
+ if (entriesMissingZip64ExtraField.has(entry)) {
549
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
550
+ }
551
+ if (filenames.has(filename)) {
552
+ throw new Error(ERR_DUPLICATED_NAME);
553
+ }
554
+ filenames.add(filename);
555
+ appendedFilenames.push(filename);
556
+ });
557
+ zipWriter.writerLocked = true;
558
+ const { lockWriter } = zipWriter;
559
+ zipWriter.lockWriter = new Promise(resolve => releaseLockWriter = () => {
560
+ zipWriter.writerLocked = false;
561
+ resolve();
562
+ });
563
+ await lockWriter;
564
+ if (zipWriter.addSplitZipSignature) {
565
+ delete zipWriter.addSplitZipSignature;
566
+ if (filter || !await startsWithSplitZipSignature(reader)) {
567
+ await writeData(zipWriter.writer, getSplitZipSignatureArray());
568
+ zipWriter.offset += SPLIT_ZIP_FILE_SIGNATURE_LENGTH;
569
+ }
570
+ }
571
+ const entryPositions = await copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, Boolean(filter), getEntryDataDescriptorLength);
572
+ keptEntries.forEach(entry => {
573
+ const {
574
+ version,
575
+ rawLastModDate,
576
+ rawFilename,
577
+ rawBitFlag,
578
+ uncompressedSize,
579
+ compressedSize,
580
+ extraFieldZip64
581
+ } = entry;
582
+ let {
583
+ compressionMethod,
584
+ rawExtraField,
585
+ } = entry;
586
+ rawExtraField = removeExtraFieldZip64(rawExtraField || EMPTY_UINT8_ARRAY);
587
+ if (entry.extraFieldAES) {
588
+ compressionMethod = COMPRESSION_METHOD_AES;
589
+ }
590
+ const extraFieldLength = getLength(rawExtraField);
591
+ const zip64UncompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.uncompressedSize !== UNDEFINED_VALUE;
592
+ const zip64CompressedSize = Boolean(extraFieldZip64) && extraFieldZip64.compressedSize !== UNDEFINED_VALUE;
593
+ const {
594
+ headerArray,
595
+ headerView
596
+ } = getHeaderArrayData({
597
+ version,
598
+ bitFlag: rawBitFlag,
599
+ compressionMethod,
600
+ uncompressedSize,
601
+ compressedSize,
602
+ rawLastModDate,
603
+ rawFilename,
604
+ zip64CompressedSize,
605
+ zip64UncompressedSize,
606
+ extraFieldLength
607
+ });
608
+ const { crc32 } = entry;
609
+ if (crc32 !== UNDEFINED_VALUE) {
610
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
611
+ }
612
+ const { offset, diskNumberStart } = entryPositions.get(entry);
613
+ fileEntries.set(entry.filename, Object.assign({}, entry, {
614
+ zip64Enabled: true,
615
+ zip64UncompressedSize,
616
+ zip64CompressedSize,
617
+ offset,
618
+ diskNumberStart,
619
+ zip64DiskNumberStart: false,
620
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
621
+ rawExtraFieldAES: EMPTY_UINT8_ARRAY,
622
+ rawExtraFieldExtendedTimestamp: EMPTY_UINT8_ARRAY,
623
+ rawExtraFieldNTFS: EMPTY_UINT8_ARRAY,
624
+ rawExtraFieldUnix: EMPTY_UINT8_ARRAY,
625
+ rawCentralExtraFieldUnix: EMPTY_UINT8_ARRAY,
626
+ rawExtraField,
627
+ rawCentralExtraField: EMPTY_UINT8_ARRAY,
628
+ headerArray,
629
+ headerView
630
+ }));
631
+ });
632
+ } catch (error) {
633
+ appendedFilenames.forEach(filename => filenames.delete(filename));
634
+ throw error;
635
+ } finally {
636
+ resolveAppendZip();
637
+ pendingAddFileCalls.delete(promiseAppendZip);
638
+ if (releaseLockWriter) {
639
+ releaseLockWriter();
640
+ }
641
+ }
642
+ }
643
+
644
+ function getExistingEntry(fileEntries, filename) {
645
+ const fileEntry = fileEntries.get(filename);
646
+ return fileEntry ? new Entry(fileEntry) : UNDEFINED_VALUE;
647
+ }
648
+
637
649
  async function prependZipEntries(zipWriter, reader) {
638
650
  if (zipWriter.filenames.size) {
639
651
  throw new Error(ERR_ZIP_NOT_EMPTY);
640
652
  }
641
- await zipWriter.appendZipEntries(reader);
653
+ await appendZipEntries(zipWriter, reader);
642
654
  }
643
655
 
644
656
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -2456,7 +2468,7 @@ function removeExtraFieldZip64(rawExtraField) {
2456
2468
  return rawExtraField;
2457
2469
  }
2458
2470
 
2459
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
2471
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
2460
2472
  const { writer } = zipWriter;
2461
2473
  const entryPositions = new Map();
2462
2474
  if (writer.closeDisk || compact) {
@@ -2464,6 +2476,27 @@ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOff
2464
2476
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
2465
2477
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
2466
2478
  const keptEntrySet = new Set(keptEntries);
2479
+ const localHeaderLengths = new Map();
2480
+ for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
2481
+ const entry = sortedEntries[indexEntry];
2482
+ if (keptEntrySet.has(entry)) {
2483
+ const sourceOffset = getSourceOffset(reader, entry);
2484
+ const localHeaderLength = await getLocalHeaderLength(reader, sourceOffset);
2485
+ if (localHeaderLength === UNDEFINED_VALUE) {
2486
+ throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
2487
+ }
2488
+ let entryEnd = sourceOffset + localHeaderLength + entry.compressedSize;
2489
+ if (entry.bitFlag.dataDescriptor) {
2490
+ const dataDescriptorLength = await getEntryDataDescriptorLength(reader, entryEnd, entry);
2491
+ entryEnd = dataDescriptorLength === UNDEFINED_VALUE ? Math.max(entryEnd, regionEnds[indexEntry]) : entryEnd + dataDescriptorLength;
2492
+ }
2493
+ if (entryEnd > regionEnds[indexEntry]) {
2494
+ throw new Error(ERR_OVERLAPPING_ENTRY);
2495
+ }
2496
+ regionEnds[indexEntry] = entryEnd;
2497
+ localHeaderLengths.set(entry, localHeaderLength);
2498
+ }
2499
+ }
2467
2500
  let copiedOffset = 0;
2468
2501
  let lastPosition;
2469
2502
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -2476,7 +2509,7 @@ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOff
2476
2509
  if (!compact) {
2477
2510
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
2478
2511
  }
2479
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
2512
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
2480
2513
  await writer.closeDisk();
2481
2514
  }
2482
2515
  lastPosition = {
@@ -2510,7 +2543,7 @@ function getRegionEnds(reader, sortedEntries, directoryOffset) {
2510
2543
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
2511
2544
  const nextEntry = sortedEntries[indexEntry + 1];
2512
2545
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
2513
- regionEnd = getSourceOffset(reader, nextEntry);
2546
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
2514
2547
  }
2515
2548
  regionEnds[indexEntry] = regionEnd;
2516
2549
  }
@@ -2524,11 +2557,13 @@ async function copyData(zipWriter, reader, offset, size) {
2524
2557
  try {
2525
2558
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
2526
2559
  } catch (error) {
2527
- zipWriter.hasCorruptedEntries = true;
2528
- try {
2529
- error.corruptedEntry = true;
2530
- } catch {
2531
- // ignored
2560
+ if (copiedLength) {
2561
+ zipWriter.hasCorruptedEntries = true;
2562
+ try {
2563
+ error.corruptedEntry = true;
2564
+ } catch {
2565
+ // ignored
2566
+ }
2532
2567
  }
2533
2568
  throw error;
2534
2569
  } finally {
@@ -2539,11 +2574,16 @@ async function copyData(zipWriter, reader, offset, size) {
2539
2574
  }
2540
2575
 
2541
2576
  async function getLocalHeaderLength(reader, offset) {
2542
- const headerArray = await readUint8Array(reader, offset, HEADER_SIZE);
2577
+ const headerArray = offset >= 0 && offset + HEADER_SIZE <= reader.size ?
2578
+ await readUint8Array(reader, offset, HEADER_SIZE) :
2579
+ EMPTY_UINT8_ARRAY;
2543
2580
  if (getLength(headerArray) < HEADER_SIZE) {
2544
- return HEADER_SIZE;
2581
+ return UNDEFINED_VALUE;
2545
2582
  }
2546
2583
  const headerView = getDataView(headerArray);
2584
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
2585
+ return UNDEFINED_VALUE;
2586
+ }
2547
2587
  return HEADER_SIZE +
2548
2588
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
2549
2589
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
@@ -60,6 +60,7 @@ export {
60
60
  } from "./core/io.js";
61
61
  export {
62
62
  ERR_INVALID_FUNCTION_OPTION,
63
+ ERR_INVALID_READER_OPTIONS,
63
64
  ERR_INVALID_SIGNAL,
64
65
  ERR_INVALID_PASS_THROUGH_VALUE,
65
66
  ERR_ABORTED
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.20.0",
6
+ "version": "2.22.0",
7
7
  "type": "module",
8
8
  "keywords": [
9
9
  "zip",