@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.
@@ -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,
@@ -343,136 +345,7 @@ class ZipWriter {
343
345
  }
344
346
 
345
347
  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
- }
348
+ return watchPromiseError(this, appendZipEntries(this, reader, options));
476
349
  }
477
350
 
478
351
  add(name = "", reader, options = {}) {
@@ -634,11 +507,140 @@ function watchPromiseError(zipWriter, promise) {
634
507
  return watchedPromise;
635
508
  }
636
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
+
637
639
  async function prependZipEntries(zipWriter, reader) {
638
640
  if (zipWriter.filenames.size) {
639
641
  throw new Error(ERR_ZIP_NOT_EMPTY);
640
642
  }
641
- await zipWriter.appendZipEntries(reader);
643
+ await appendZipEntries(zipWriter, reader);
642
644
  }
643
645
 
644
646
  async function addFileEntry(zipWriter, name, reader, options) {
@@ -2456,7 +2458,7 @@ function removeExtraFieldZip64(rawExtraField) {
2456
2458
  return rawExtraField;
2457
2459
  }
2458
2460
 
2459
- async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact) {
2461
+ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOffset, compact, getEntryDataDescriptorLength) {
2460
2462
  const { writer } = zipWriter;
2461
2463
  const entryPositions = new Map();
2462
2464
  if (writer.closeDisk || compact) {
@@ -2464,6 +2466,27 @@ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOff
2464
2466
  getSourceOffset(reader, firstEntry) - getSourceOffset(reader, secondEntry));
2465
2467
  const regionEnds = getRegionEnds(reader, sortedEntries, directoryOffset);
2466
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
+ }
2467
2490
  let copiedOffset = 0;
2468
2491
  let lastPosition;
2469
2492
  for (let indexEntry = 0; indexEntry < sortedEntries.length; indexEntry++) {
@@ -2476,7 +2499,7 @@ async function copyZipData(zipWriter, reader, entries, keptEntries, directoryOff
2476
2499
  if (!compact) {
2477
2500
  await copyData(zipWriter, reader, copiedOffset, sourceOffset - copiedOffset);
2478
2501
  }
2479
- if (writer.closeDisk && exceedsAvailableSize(writer, await getLocalHeaderLength(reader, sourceOffset))) {
2502
+ if (writer.closeDisk && exceedsAvailableSize(writer, localHeaderLengths.get(entry))) {
2480
2503
  await writer.closeDisk();
2481
2504
  }
2482
2505
  lastPosition = {
@@ -2510,7 +2533,7 @@ function getRegionEnds(reader, sortedEntries, directoryOffset) {
2510
2533
  const sourceOffset = getSourceOffset(reader, sortedEntries[indexEntry]);
2511
2534
  const nextEntry = sortedEntries[indexEntry + 1];
2512
2535
  if (nextEntry && getSourceOffset(reader, nextEntry) > sourceOffset) {
2513
- regionEnd = getSourceOffset(reader, nextEntry);
2536
+ regionEnd = Math.min(getSourceOffset(reader, nextEntry), directoryOffset);
2514
2537
  }
2515
2538
  regionEnds[indexEntry] = regionEnd;
2516
2539
  }
@@ -2524,11 +2547,13 @@ async function copyData(zipWriter, reader, offset, size) {
2524
2547
  try {
2525
2548
  await flushBufferedData(createReadable(reader, { offset, size }), writer, UNDEFINED_VALUE, chunkLength => copiedLength += chunkLength);
2526
2549
  } catch (error) {
2527
- zipWriter.hasCorruptedEntries = true;
2528
- try {
2529
- error.corruptedEntry = true;
2530
- } catch {
2531
- // ignored
2550
+ if (copiedLength) {
2551
+ zipWriter.hasCorruptedEntries = true;
2552
+ try {
2553
+ error.corruptedEntry = true;
2554
+ } catch {
2555
+ // ignored
2556
+ }
2532
2557
  }
2533
2558
  throw error;
2534
2559
  } finally {
@@ -2539,11 +2564,16 @@ async function copyData(zipWriter, reader, offset, size) {
2539
2564
  }
2540
2565
 
2541
2566
  async function getLocalHeaderLength(reader, offset) {
2542
- 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;
2543
2570
  if (getLength(headerArray) < HEADER_SIZE) {
2544
- return HEADER_SIZE;
2571
+ return UNDEFINED_VALUE;
2545
2572
  }
2546
2573
  const headerView = getDataView(headerArray);
2574
+ if (getUint32(headerView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
2575
+ return UNDEFINED_VALUE;
2576
+ }
2547
2577
  return HEADER_SIZE +
2548
2578
  getUint16(headerView, HEADER_OFFSET_FILENAME_LENGTH + LOCAL_HEADER_COMMON_OFFSET) +
2549
2579
  getUint16(headerView, HEADER_OFFSET_EXTRAFIELD_LENGTH + LOCAL_HEADER_COMMON_OFFSET);
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.21.0",
7
7
  "type": "module",
8
8
  "keywords": [
9
9
  "zip",