@zip.js/zip.js 2.8.26 → 2.8.28

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.
Files changed (52) hide show
  1. package/README.md +2 -2
  2. package/deno.json +1 -1
  3. package/dist/zip-core.js +1050 -569
  4. package/dist/zip-core.min.js +1 -1
  5. package/dist/zip-fs-core.js +1146 -617
  6. package/dist/zip-fs-core.min.js +1 -1
  7. package/dist/zip-fs-native.js +1207 -627
  8. package/dist/zip-fs-native.min.js +1 -1
  9. package/dist/zip-fs.js +1279 -681
  10. package/dist/zip-fs.min.js +1 -1
  11. package/dist/zip-legacy.js +1051 -570
  12. package/dist/zip-legacy.min.js +1 -1
  13. package/dist/zip-module.wasm +0 -0
  14. package/dist/zip-native.js +1053 -572
  15. package/dist/zip-native.min.js +1 -1
  16. package/dist/zip-web-worker-native.js +1 -1
  17. package/dist/zip-web-worker.js +1 -1
  18. package/dist/zip.js +1125 -626
  19. package/dist/zip.min.js +1 -1
  20. package/eslint.config.mjs +6 -1
  21. package/index-native.cjs +1207 -627
  22. package/index-native.min.js +1 -1
  23. package/index.cjs +1279 -681
  24. package/index.d.ts +2451 -2355
  25. package/index.min.js +1 -1
  26. package/lib/core/codec-pool.js +39 -1
  27. package/lib/core/codec-worker.js +120 -34
  28. package/lib/core/configuration.js +3 -0
  29. package/lib/core/constants.js +6 -0
  30. package/lib/core/io.js +55 -25
  31. package/lib/core/options.js +2 -0
  32. package/lib/core/streams/aes-crypto-stream.js +12 -12
  33. package/lib/core/streams/codec-stream.js +11 -5
  34. package/lib/core/streams/codecs/sjcl.js +1 -33
  35. package/lib/core/streams/common-crypto.js +4 -4
  36. package/lib/core/streams/zip-crypto-stream.js +25 -15
  37. package/lib/core/streams/zip-entry-stream.js +35 -1
  38. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  39. package/lib/core/streams/zlib-wasm/zlib-streams.js +73 -55
  40. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  41. package/lib/core/util/decode-cp437.js +1 -1
  42. package/lib/core/util/decode-text.js +2 -1
  43. package/lib/core/web-worker-base.js +12 -4
  44. package/lib/core/web-worker-inline-native.js +1 -1
  45. package/lib/core/web-worker-inline-wasm.js +1 -1
  46. package/lib/core/zip-fs.js +154 -55
  47. package/lib/core/zip-reader.js +185 -67
  48. package/lib/core/zip-writer.js +613 -376
  49. package/lib/core/zlib-streams-inline.js +1 -1
  50. package/lib/zip-core-reader.js +3 -1
  51. package/lib/zip-core-writer.js +1 -0
  52. package/package.json +20 -6
@@ -54,12 +54,15 @@ import {
54
54
  } from "./zip-entry.js";
55
55
  import { UNDEFINED_VALUE } from "./constants.js";
56
56
 
57
+ const ERR_ENTRY_EXISTS = "Entry filename already exists";
58
+ const ERR_READABLE_CONSUMED = "Readable stream already consumed";
59
+
57
60
  class ZipEntry {
58
61
 
59
62
  constructor(fs, name, params, parent) {
60
63
  const zipEntry = this;
61
64
  if (fs.root && parent && parent.getChildByName(name)) {
62
- throw new Error("Entry filename already exists");
65
+ throw new Error(ERR_ENTRY_EXISTS);
63
66
  }
64
67
  if (!params) {
65
68
  params = {};
@@ -69,13 +72,17 @@ class ZipEntry {
69
72
  name,
70
73
  data: params.data,
71
74
  options: params.options,
72
- id: fs.entries.length,
75
+ id: fs.entryIdCounter++,
73
76
  parent,
74
77
  children: [],
75
78
  uncompressedSize: params.uncompressedSize || 0,
76
79
  passThrough: params.passThrough
77
80
  });
78
- fs.entries.push(zipEntry);
81
+ // detached entries (e.g. clones) are not registered until they are attached,
82
+ // otherwise they could never be garbage collected
83
+ if (parent || !fs.root) {
84
+ fs.entries[zipEntry.id] = zipEntry;
85
+ }
79
86
  if (parent) {
80
87
  zipEntry.parent.children.push(zipEntry);
81
88
  }
@@ -113,7 +120,7 @@ class ZipEntry {
113
120
  rename(name) {
114
121
  const parent = this.parent;
115
122
  if (parent && parent.getChildByName(name)) {
116
- throw new Error("Entry filename already exists");
123
+ throw new Error(ERR_ENTRY_EXISTS);
117
124
  } else {
118
125
  this.name = name;
119
126
  }
@@ -152,16 +159,17 @@ class ZipFileEntry extends ZipEntry {
152
159
  }
153
160
 
154
161
  isPasswordProtected() {
155
- return this.data.encrypted;
162
+ return Boolean(this.data && this.data.encrypted);
156
163
  }
157
164
 
158
165
  async checkPassword(password, options = {}) {
159
166
  const zipEntry = this;
160
167
  if (zipEntry.isPasswordProtected()) {
161
- options.password = password;
162
- options.checkPasswordOnly = true;
163
168
  try {
164
- await zipEntry.data.getData(null, options);
169
+ await zipEntry.data.getData(null, Object.assign({}, options, {
170
+ password,
171
+ checkPasswordOnly: true
172
+ }));
165
173
  return true;
166
174
  } catch (error) {
167
175
  if (error.message == ERR_INVALID_PASSWORD) {
@@ -195,8 +203,15 @@ class ZipFileEntry extends ZipEntry {
195
203
  return this.getData({ writable }, options);
196
204
  }
197
205
 
198
- getArrayBuffer(options) {
199
- return this.data.arrayBuffer(options);
206
+ async getArrayBuffer(options) {
207
+ // this.data may be a string, a Uint8Array, a data URI or a Blob depending on how the entry
208
+ // was created, so retrieve the bytes through getUint8Array rather than assuming a Blob; this
209
+ // also applies the options (password, signal, onprogress...) like the other getData() getters
210
+ const array = await this.getUint8Array(options);
211
+ // return the backing buffer directly when the array spans it, slicing the exact bytes otherwise
212
+ return array.byteOffset || array.byteLength != array.buffer.byteLength
213
+ ? array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength)
214
+ : array.buffer;
200
215
  }
201
216
 
202
217
  replaceBlob(blob) {
@@ -238,7 +253,7 @@ class ZipFileEntry extends ZipEntry {
238
253
  replaceReadable(readable) {
239
254
  Object.assign(this, {
240
255
  data: null,
241
- Reader: function () { return { readable }; },
256
+ Reader: getReadableReader(readable),
242
257
  Writer: null,
243
258
  reader: null
244
259
  });
@@ -254,7 +269,7 @@ class ZipDirectoryEntry extends ZipEntry {
254
269
 
255
270
  clone(deepClone) {
256
271
  const zipEntry = this;
257
- const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name);
272
+ const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name, zipEntry);
258
273
  if (deepClone) {
259
274
  clonedEntry.children = zipEntry.children.map(child => {
260
275
  const childClone = child.clone(deepClone);
@@ -328,7 +343,7 @@ class ZipDirectoryEntry extends ZipEntry {
328
343
 
329
344
  addReadable(name, readable, options = {}) {
330
345
  return addChild(this, name, {
331
- Reader: function () { return { readable }; },
346
+ Reader: getReadableReader(readable),
332
347
  options
333
348
  });
334
349
  }
@@ -342,6 +357,7 @@ class ZipDirectoryEntry extends ZipEntry {
342
357
  }
343
358
 
344
359
  addFile(file, options = {}) {
360
+ options = Object.assign({}, options);
345
361
  if (!options.lastModDate) {
346
362
  options.lastModDate = new Date(file.lastModified);
347
363
  }
@@ -408,11 +424,15 @@ class ZipDirectoryEntry extends ZipEntry {
408
424
  try {
409
425
  const path = entry.filename.split("/");
410
426
  const name = path.pop();
411
- path.forEach((pathPart, pathIndex) => {
427
+ path.forEach(pathPart => {
412
428
  const previousParent = parent;
413
429
  parent = parent.getChildByName(pathPart);
414
- if (!parent) {
415
- parent = new ZipDirectoryEntry(this.fs, pathPart, { data: pathIndex == path.length - 1 ? entry : null }, previousParent);
430
+ if (parent) {
431
+ if (!parent.directory) {
432
+ throw new Error(ERR_ENTRY_EXISTS);
433
+ }
434
+ } else {
435
+ parent = new ZipDirectoryEntry(this.fs, pathPart, { data: null }, previousParent);
416
436
  importedEntries.push(parent);
417
437
  }
418
438
  });
@@ -423,6 +443,22 @@ class ZipDirectoryEntry extends ZipEntry {
423
443
  uncompressedSize: entry.uncompressedSize,
424
444
  passThrough: options.passThrough
425
445
  }));
446
+ } else {
447
+ let directoryEntry = parent;
448
+ if (name) {
449
+ directoryEntry = parent.getChildByName(name);
450
+ if (directoryEntry) {
451
+ if (!directoryEntry.directory) {
452
+ throw new Error(ERR_ENTRY_EXISTS);
453
+ }
454
+ } else {
455
+ directoryEntry = new ZipDirectoryEntry(this.fs, name, { data: null }, parent);
456
+ importedEntries.push(directoryEntry);
457
+ }
458
+ }
459
+ if (directoryEntry != this && !directoryEntry.data) {
460
+ directoryEntry.data = entry;
461
+ }
426
462
  }
427
463
  } catch (error) {
428
464
  try {
@@ -438,14 +474,15 @@ class ZipDirectoryEntry extends ZipEntry {
438
474
  return importedEntries;
439
475
  }
440
476
 
441
- async exportZip(writer, options) {
477
+ async exportZip(writer, options = {}) {
442
478
  const zipEntry = this;
479
+ options = Object.assign({}, options);
443
480
  if (options.bufferedWrite === UNDEFINED_VALUE) {
444
481
  options.bufferedWrite = true;
445
482
  }
446
- await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
483
+ const [readers] = await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
447
484
  const zipWriter = new ZipWriter(writer, options);
448
- await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options);
485
+ await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options, readers);
449
486
  await zipWriter.close();
450
487
  return writer.getData ? writer.getData() : writer.writable;
451
488
  }
@@ -491,7 +528,15 @@ class FS {
491
528
 
492
529
  remove(entry) {
493
530
  detach(entry);
494
- this.entries[entry.id] = null;
531
+ const removedEntries = [entry];
532
+ while (removedEntries.length) {
533
+ const removedEntry = removedEntries.pop();
534
+ this.entries[removedEntry.id] = null;
535
+ for (const child of removedEntry.children) {
536
+ removedEntries.push(child);
537
+ }
538
+ }
539
+ entry.parent = UNDEFINED_VALUE;
495
540
  }
496
541
 
497
542
  move(entry, destination) {
@@ -501,12 +546,17 @@ class FS {
501
546
  if (destination.directory) {
502
547
  if (!destination.isDescendantOf(entry)) {
503
548
  if (entry != destination) {
504
- if (destination.getChildByName(entry.name)) {
505
- throw new Error("Entry filename already exists");
549
+ const existingChild = destination.getChildByName(entry.name);
550
+ if (existingChild) {
551
+ if (existingChild != entry) {
552
+ throw new Error(ERR_ENTRY_EXISTS);
553
+ }
554
+ } else {
555
+ detach(entry);
556
+ entry.parent = destination;
557
+ destination.children.push(entry);
558
+ registerEntries(this, entry);
506
559
  }
507
- detach(entry);
508
- entry.parent = destination;
509
- destination.children.push(entry);
510
560
  }
511
561
  } else {
512
562
  throw new Error("Entry is a ancestor of target entry");
@@ -523,6 +573,11 @@ class FS {
523
573
  for (let index = 0; node && index < path.length; index++) {
524
574
  node = node.getChildByName(path[index]);
525
575
  }
576
+ if (!node) {
577
+ // exclude detached entries, e.g. clones registered in `entries` but not attached
578
+ node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
579
+ entry.getRelativeName() == fullname);
580
+ }
526
581
  return node;
527
582
  }
528
583
 
@@ -604,6 +659,7 @@ class FS {
604
659
  }
605
660
 
606
661
  importZip(reader, options) {
662
+ resetFS(this);
607
663
  return this.root.importZip(reader, options);
608
664
  }
609
665
 
@@ -623,6 +679,10 @@ class FS {
623
679
  return this.root.exportWritable(writable, options);
624
680
  }
625
681
 
682
+ exportZip(writer, options) {
683
+ return this.root.exportZip(writer, options);
684
+ }
685
+
626
686
  isPasswordProtected() {
627
687
  return this.root.isPasswordProtected();
628
688
  }
@@ -637,15 +697,26 @@ export { fs };
637
697
 
638
698
  function getTotalSize(entries, propertyName) {
639
699
  let size = 0;
640
- entries.forEach(process);
700
+ const pendingEntries = Array.from(entries);
701
+ while (pendingEntries.length) {
702
+ const entry = pendingEntries.pop();
703
+ size += entry[propertyName] || 0;
704
+ for (const child of entry.children) {
705
+ pendingEntries.push(child);
706
+ }
707
+ }
641
708
  return size;
709
+ }
642
710
 
643
- function process(entry) {
644
- size += entry[propertyName];
645
- if (entry.children) {
646
- entry.children.forEach(process);
711
+ function getReadableReader(readable) {
712
+ let consumed;
713
+ return function () {
714
+ if (consumed) {
715
+ throw new Error(ERR_READABLE_CONSUMED);
647
716
  }
648
- }
717
+ consumed = true;
718
+ return { readable };
719
+ };
649
720
  }
650
721
 
651
722
  function getZipBlobReader(options) {
@@ -660,7 +731,7 @@ function getZipBlobReader(options) {
660
731
  async init() {
661
732
  const zipBlobReader = this;
662
733
  zipBlobReader.size = zipBlobReader.entry.uncompressedSize;
663
- const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, zipBlobReader.options, options));
734
+ const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, options, zipBlobReader.options));
664
735
  zipBlobReader.data = data;
665
736
  zipBlobReader.blobReader = new BlobReader(data);
666
737
  super.init();
@@ -673,29 +744,41 @@ function getZipBlobReader(options) {
673
744
  }
674
745
 
675
746
  async function initReaders(entry, options) {
676
- if (entry.children.length) {
677
- await Promise.all(entry.children.map(async child => {
747
+ const fileEntries = [];
748
+ const pendingEntries = [entry];
749
+ // per-export map so concurrent exports do not share (and lock) the same readers
750
+ const readers = new Map();
751
+ while (pendingEntries.length) {
752
+ const pendingEntry = pendingEntries.pop();
753
+ for (const child of pendingEntry.children) {
678
754
  if (child.directory) {
679
- await initReaders(child, options);
755
+ pendingEntries.push(child);
680
756
  } else {
681
- const reader = child.reader = new child.Reader(child.data, options);
682
- try {
683
- await initStream(reader);
684
- } catch (error) {
685
- try {
686
- error.entryId = child.id;
687
- error.cause = {
688
- entry: child
689
- };
690
- } catch {
691
- // ignored
692
- }
693
- throw error;
694
- }
695
- child.uncompressedSize = reader.size;
757
+ fileEntries.push(child);
696
758
  }
697
- }));
759
+ }
698
760
  }
761
+ await Promise.all(fileEntries.map(async child => {
762
+ const reader = child.reader = new child.Reader(child.data, options);
763
+ readers.set(child, reader);
764
+ try {
765
+ await initStream(reader);
766
+ } catch (error) {
767
+ try {
768
+ error.entryId = child.id;
769
+ error.cause = {
770
+ entry: child
771
+ };
772
+ } catch {
773
+ // ignored
774
+ }
775
+ throw error;
776
+ }
777
+ if (reader.size !== UNDEFINED_VALUE) {
778
+ child.uncompressedSize = reader.size;
779
+ }
780
+ }));
781
+ return readers;
699
782
  }
700
783
 
701
784
  function detach(entry) {
@@ -709,7 +792,7 @@ function detach(entry) {
709
792
  }
710
793
  }
711
794
 
712
- async function exportZip(zipWriter, entry, totalSize, options) {
795
+ async function exportZip(zipWriter, entry, totalSize, options, readers) {
713
796
  const selectedEntry = entry;
714
797
  const entryOffsets = new Map();
715
798
  await process(zipWriter, entry);
@@ -719,7 +802,11 @@ async function exportZip(zipWriter, entry, totalSize, options) {
719
802
 
720
803
  async function exportChild() {
721
804
  if (options.bufferedWrite) {
722
- await Promise.allSettled(entry.children.map(processChild));
805
+ const results = await Promise.allSettled(entry.children.map(processChild));
806
+ const errorResult = results.find(result => result.status == "rejected");
807
+ if (errorResult) {
808
+ throw errorResult.reason;
809
+ }
723
810
  } else {
724
811
  for (const child of entry.children) {
725
812
  await processChild(child);
@@ -774,7 +861,7 @@ async function exportZip(zipWriter, entry, totalSize, options) {
774
861
  });
775
862
  }
776
863
  }
777
- await zipWriter.add(name, child.reader, Object.assign({}, options, zipEntryOptions, childOptions, {
864
+ await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
778
865
  directory: child.directory,
779
866
  onprogress: async indexProgress => {
780
867
  if (options.onprogress) {
@@ -823,7 +910,7 @@ function addFileSystemHandle(zipEntry, handle, options) {
823
910
  }
824
911
  } catch (error) {
825
912
  const message = error.message + (handle ? " (" + handle.name + ")" : "");
826
- throw new Error(message);
913
+ throw new Error(message, { cause: error });
827
914
  }
828
915
  }
829
916
  return addedEntries;
@@ -883,9 +970,21 @@ async function transformToFileSystemhandles(entry) {
883
970
 
884
971
  function resetFS(fs) {
885
972
  fs.entries = [];
973
+ fs.entryIdCounter = 0;
886
974
  fs.root = new ZipDirectoryEntry(fs);
887
975
  }
888
976
 
977
+ function registerEntries(fs, entry) {
978
+ const pendingEntries = [entry];
979
+ while (pendingEntries.length) {
980
+ const pendingEntry = pendingEntries.pop();
981
+ fs.entries[pendingEntry.id] = pendingEntry;
982
+ for (const child of pendingEntry.children) {
983
+ pendingEntries.push(child);
984
+ }
985
+ }
986
+ }
987
+
889
988
  function addChild(parent, name, params, directory) {
890
989
  if (parent.directory) {
891
990
  return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent);