@zip.js/zip.js 2.9.0 → 2.10.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.
@@ -3475,21 +3475,14 @@
3475
3475
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3476
3476
  */
3477
3477
 
3478
- /* global TextDecoder */
3479
-
3480
3478
  const CP437 = "\0\u263A\u263B\u2665\u2666\u2663\u2660\u2022\u25D8\u25CB\u25D9\u2642\u2640\u266A\u266B\u263C\u25BA\u25C4\u2195\u203C\u00B6\u00A7\u25AC\u21A8\u2191\u2193\u2192\u2190\u221F\u2194\u25B2\u25BC !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9\u00FF\u00D6\u00DC\u00A2\u00A3\u00A5\u20A7\u0192\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA\u00BF\u2310\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\u00DF\u0393\u03C0\u03A3\u03C3\u00B5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\u00B1\u2265\u2264\u2320\u2321\u00F7\u2248\u00B0\u2219\u00B7\u221A\u207F\u00B2\u25A0\u00A0".split("");
3481
- const VALID_CP437 = CP437.length == 256;
3482
3479
 
3483
3480
  function decodeCP437(stringValue) {
3484
- if (VALID_CP437) {
3485
- let result = "";
3486
- for (let indexCharacter = 0; indexCharacter < stringValue.length; indexCharacter++) {
3487
- result += CP437[stringValue[indexCharacter]];
3488
- }
3489
- return result;
3490
- } else {
3491
- return new TextDecoder().decode(stringValue);
3481
+ let result = "";
3482
+ for (let indexCharacter = 0; indexCharacter < stringValue.length; indexCharacter++) {
3483
+ result += CP437[stringValue[indexCharacter]];
3492
3484
  }
3485
+ return result;
3493
3486
  }
3494
3487
 
3495
3488
  /*
@@ -8624,7 +8617,7 @@
8624
8617
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8625
8618
  */
8626
8619
 
8627
- const VERSION = "2.9.0";
8620
+ const VERSION = "2.10.0";
8628
8621
 
8629
8622
  /*
8630
8623
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
@@ -9103,7 +9096,14 @@
9103
9096
 
9104
9097
 
9105
9098
  const ERR_ENTRY_EXISTS = "Entry filename already exists";
9099
+ const ERR_DUPLICATE_IMPORTED_ENTRY = "Duplicate entry filename in the imported zip file";
9100
+ const ERR_INVALID_DUPLICATES = "Invalid duplicates option (must be \"throw\", \"keep-first\" or \"keep-last\")";
9101
+ const ERR_ANCESTOR_ENTRY = "Entry is a ancestor of target entry";
9106
9102
  const ERR_READABLE_CONSUMED = "Readable stream already consumed";
9103
+ const DUPLICATES_THROW = "throw";
9104
+ const DUPLICATES_KEEP_FIRST = "keep-first";
9105
+ const DUPLICATES_KEEP_LAST = "keep-last";
9106
+ const DUPLICATES_VALUES = new Set([DUPLICATES_THROW, DUPLICATES_KEEP_FIRST, DUPLICATES_KEEP_LAST]);
9107
9107
  const ERR_INVALID_PASS_THROUGH = "Invalid passThrough option (use readerOptions.passThrough or set uncompressedSize for each entry)";
9108
9108
  const ERR_INVALID_READER_OPTIONS = "Invalid readerOptions (must be an object)";
9109
9109
  const ERR_ZIP_CRYPTO_LAST_MOD_DATE = "The last modification date of an entry encrypted with ZipCrypto cannot be changed when passThrough is set";
@@ -9180,12 +9180,26 @@
9180
9180
  }
9181
9181
 
9182
9182
  rename(name) {
9183
- const parent = this.parent;
9184
- if (parent && parent.getChildByName(name)) {
9185
- throw new Error(ERR_ENTRY_EXISTS);
9186
- } else {
9187
- this.name = name;
9183
+ const zipEntry = this;
9184
+ let parent = zipEntry.parent;
9185
+ if (parent) {
9186
+ const path = splitPath(name);
9187
+ if (path) {
9188
+ name = path.pop();
9189
+ parent = getPathParent(parent, path, zipEntry);
9190
+ }
9191
+ const existingChild = parent.getChildByName(name);
9192
+ if (existingChild && existingChild != zipEntry) {
9193
+ throw new Error(ERR_ENTRY_EXISTS);
9194
+ }
9195
+ if (parent != zipEntry.parent) {
9196
+ detach(zipEntry);
9197
+ zipEntry.parent = parent;
9198
+ parent.children.push(zipEntry);
9199
+ registerEntries(zipEntry.fs, zipEntry);
9200
+ }
9188
9201
  }
9202
+ zipEntry.name = name;
9189
9203
  }
9190
9204
 
9191
9205
  setOptions(options) {
@@ -9278,47 +9292,47 @@
9278
9292
  }
9279
9293
 
9280
9294
  replaceBlob(blob) {
9281
- Object.assign(this, {
9295
+ replaceContent(this, {
9282
9296
  data: blob,
9283
9297
  Reader: BlobReader,
9284
9298
  Writer: BlobWriter,
9285
- reader: null
9299
+ uncompressedSize: blob.size
9286
9300
  });
9287
9301
  }
9288
9302
 
9289
9303
  replaceText(text) {
9290
- Object.assign(this, {
9304
+ replaceContent(this, {
9291
9305
  data: text,
9292
9306
  Reader: TextReader,
9293
9307
  Writer: TextWriter,
9294
- reader: null
9308
+ uncompressedSize: getTextSize(text)
9295
9309
  });
9296
9310
  }
9297
9311
 
9298
9312
  replaceData64URI(dataURI) {
9299
- Object.assign(this, {
9313
+ replaceContent(this, {
9300
9314
  data: dataURI,
9301
9315
  Reader: Data64URIReader,
9302
9316
  Writer: Data64URIWriter,
9303
- reader: null
9317
+ uncompressedSize: getData64URISize(dataURI)
9304
9318
  });
9305
9319
  }
9306
9320
 
9307
9321
  replaceUint8Array(array) {
9308
- Object.assign(this, {
9322
+ replaceContent(this, {
9309
9323
  data: array,
9310
9324
  Reader: Uint8ArrayReader,
9311
9325
  Writer: Uint8ArrayWriter,
9312
- reader: null
9326
+ uncompressedSize: array.length
9313
9327
  });
9314
9328
  }
9315
9329
 
9316
9330
  replaceReadable(readable) {
9317
- Object.assign(this, {
9331
+ replaceContent(this, {
9318
9332
  data: null,
9319
9333
  Reader: getReadableReader(readable),
9320
9334
  Writer: null,
9321
- reader: null
9335
+ uncompressedSize: UNDEFINED_VALUE
9322
9336
  });
9323
9337
  }
9324
9338
  }
@@ -9368,17 +9382,12 @@
9368
9382
  }
9369
9383
 
9370
9384
  addData64URI(name, dataURI, options = {}) {
9371
- let dataEnd = dataURI.length;
9372
- while (dataURI.charAt(dataEnd - 1) == "=") {
9373
- dataEnd--;
9374
- }
9375
- const dataStart = dataURI.indexOf(",") + 1;
9376
9385
  return addChild(this, name, {
9377
9386
  data: dataURI,
9378
9387
  Reader: Data64URIReader,
9379
9388
  Writer: Data64URIWriter,
9380
9389
  options,
9381
- uncompressedSize: Math.floor((dataEnd - dataStart) * 0.75)
9390
+ uncompressedSize: getData64URISize(dataURI)
9382
9391
  });
9383
9392
  }
9384
9393
 
@@ -9486,6 +9495,7 @@
9486
9495
  await initStream(reader);
9487
9496
  zipReader = new ZipReader(reader, options);
9488
9497
  }
9498
+ const duplicates = checkDuplicatesOption(options.duplicates);
9489
9499
  const importedEntries = [];
9490
9500
  const entries = await zipReader.getEntries(options);
9491
9501
  for (const entry of entries) {
@@ -9493,19 +9503,42 @@
9493
9503
  try {
9494
9504
  const path = entry.filename.split("/").filter(pathPart => pathPart != "" && pathPart != ".");
9495
9505
  const name = path.pop();
9496
- path.forEach(pathPart => {
9506
+ let skippedEntry = false;
9507
+ for (const pathPart of path) {
9497
9508
  const previousParent = parent;
9498
9509
  parent = parent.getChildByName(pathPart);
9499
9510
  if (parent) {
9500
9511
  if (!parent.directory) {
9501
- throw new Error(ERR_ENTRY_EXISTS);
9512
+ if (duplicates == DUPLICATES_KEEP_FIRST) {
9513
+ skippedEntry = true;
9514
+ break;
9515
+ } else if (duplicates == DUPLICATES_KEEP_LAST) {
9516
+ this.fs.remove(parent);
9517
+ parent = new ZipDirectoryEntry(this.fs, pathPart, { data: null }, previousParent);
9518
+ importedEntries.push(parent);
9519
+ } else {
9520
+ throw new Error(ERR_DUPLICATE_IMPORTED_ENTRY);
9521
+ }
9502
9522
  }
9503
9523
  } else {
9504
9524
  parent = new ZipDirectoryEntry(this.fs, pathPart, { data: null }, previousParent);
9505
9525
  importedEntries.push(parent);
9506
9526
  }
9507
- });
9527
+ }
9528
+ if (skippedEntry) {
9529
+ continue;
9530
+ }
9508
9531
  if (!entry.directory) {
9532
+ const existingChild = parent.getChildByName(name);
9533
+ if (existingChild) {
9534
+ if (duplicates == DUPLICATES_KEEP_FIRST) {
9535
+ continue;
9536
+ } else if (duplicates == DUPLICATES_KEEP_LAST) {
9537
+ this.fs.remove(existingChild);
9538
+ } else {
9539
+ throw new Error(ERR_DUPLICATE_IMPORTED_ENTRY);
9540
+ }
9541
+ }
9509
9542
  importedEntries.push(addChild(parent, name, {
9510
9543
  data: entry,
9511
9544
  Reader: getZipBlobReader(Object.assign({}, options)),
@@ -9516,11 +9549,17 @@
9516
9549
  let directoryEntry = parent;
9517
9550
  if (name) {
9518
9551
  directoryEntry = parent.getChildByName(name);
9519
- if (directoryEntry) {
9520
- if (!directoryEntry.directory) {
9521
- throw new Error(ERR_ENTRY_EXISTS);
9552
+ if (directoryEntry && !directoryEntry.directory) {
9553
+ if (duplicates == DUPLICATES_KEEP_FIRST) {
9554
+ continue;
9555
+ } else if (duplicates == DUPLICATES_KEEP_LAST) {
9556
+ this.fs.remove(directoryEntry);
9557
+ directoryEntry = UNDEFINED_VALUE;
9558
+ } else {
9559
+ throw new Error(ERR_DUPLICATE_IMPORTED_ENTRY);
9522
9560
  }
9523
- } else {
9561
+ }
9562
+ if (!directoryEntry) {
9524
9563
  directoryEntry = new ZipDirectoryEntry(this.fs, name, { data: null }, parent);
9525
9564
  importedEntries.push(directoryEntry);
9526
9565
  }
@@ -9530,6 +9569,7 @@
9530
9569
  }
9531
9570
  }
9532
9571
  } catch (error) {
9572
+ importedEntries.reverse().forEach(importedEntry => this.fs.remove(importedEntry));
9533
9573
  try {
9534
9574
  error.cause = {
9535
9575
  entry
@@ -9648,7 +9688,7 @@
9648
9688
  }
9649
9689
  }
9650
9690
  } else {
9651
- throw new Error("Entry is a ancestor of target entry");
9691
+ throw new Error(ERR_ANCESTOR_ENTRY);
9652
9692
  }
9653
9693
  } else {
9654
9694
  throw new Error("Target entry is not a directory");
@@ -9660,7 +9700,7 @@
9660
9700
  const path = fullname.split("/");
9661
9701
  let node = this.root;
9662
9702
  for (let index = 0; node && index < path.length; index++) {
9663
- node = node.getChildByName(path[index]);
9703
+ node = node.directory ? node.getChildByName(path[index]) : UNDEFINED_VALUE;
9664
9704
  }
9665
9705
  if (!node) {
9666
9706
  node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
@@ -9722,33 +9762,27 @@
9722
9762
  }
9723
9763
 
9724
9764
  importBlob(blob, options) {
9725
- resetFS(this);
9726
- return this.root.importBlob(blob, options);
9765
+ return resetAndImport(this, root => root.importBlob(blob, options));
9727
9766
  }
9728
9767
 
9729
9768
  importData64URI(dataURI, options) {
9730
- resetFS(this);
9731
- return this.root.importData64URI(dataURI, options);
9769
+ return resetAndImport(this, root => root.importData64URI(dataURI, options));
9732
9770
  }
9733
9771
 
9734
9772
  importUint8Array(array, options) {
9735
- resetFS(this);
9736
- return this.root.importUint8Array(array, options);
9773
+ return resetAndImport(this, root => root.importUint8Array(array, options));
9737
9774
  }
9738
9775
 
9739
9776
  importHttpContent(url, options) {
9740
- resetFS(this);
9741
- return this.root.importHttpContent(url, options);
9777
+ return resetAndImport(this, root => root.importHttpContent(url, options));
9742
9778
  }
9743
9779
 
9744
9780
  importReadable(readable, options) {
9745
- resetFS(this);
9746
- return this.root.importReadable(readable, options);
9781
+ return resetAndImport(this, root => root.importReadable(readable, options));
9747
9782
  }
9748
9783
 
9749
9784
  importZip(reader, options) {
9750
- resetFS(this);
9751
- return this.root.importZip(reader, options);
9785
+ return resetAndImport(this, root => root.importZip(reader, options));
9752
9786
  }
9753
9787
 
9754
9788
  exportBlob(options) {
@@ -10068,6 +10102,25 @@
10068
10102
  return readerOptions;
10069
10103
  }
10070
10104
 
10105
+ function replaceContent(zipEntry, params) {
10106
+ const { uncompressedSize } = params;
10107
+ Object.assign(zipEntry, params, {
10108
+ reader: null,
10109
+ passThrough: UNDEFINED_VALUE,
10110
+ uncompressedSize: uncompressedSize === UNDEFINED_VALUE ? 0 : uncompressedSize,
10111
+ undeterminedSize: uncompressedSize === UNDEFINED_VALUE
10112
+ });
10113
+ }
10114
+
10115
+ function getData64URISize(dataURI) {
10116
+ let dataEnd = dataURI.length;
10117
+ while (dataURI.charAt(dataEnd - 1) == "=") {
10118
+ dataEnd--;
10119
+ }
10120
+ const dataStart = dataURI.indexOf(",") + 1;
10121
+ return Math.floor((dataEnd - dataStart) * 0.75);
10122
+ }
10123
+
10071
10124
  function isPassThrough(child, options) {
10072
10125
  const { readerOptions } = options;
10073
10126
  return Boolean(!child.directory && (child.passThrough || (readerOptions && readerOptions.passThrough)));
@@ -10357,6 +10410,15 @@
10357
10410
  fs.root = new ZipDirectoryEntry(fs);
10358
10411
  }
10359
10412
 
10413
+ function resetAndImport(fs, importFunction) {
10414
+ const { entries, entryIdCounter, root } = fs;
10415
+ resetFS(fs);
10416
+ return importFunction(fs.root).catch(error => {
10417
+ Object.assign(fs, { entries, entryIdCounter, root });
10418
+ throw error;
10419
+ });
10420
+ }
10421
+
10360
10422
  function collectChildren(directory, recursive) {
10361
10423
  const children = [];
10362
10424
  const pendingDirectories = [directory];
@@ -10385,12 +10447,54 @@
10385
10447
 
10386
10448
  function addChild(parent, name, params, directory) {
10387
10449
  if (parent.directory) {
10450
+ const path = splitPath(name);
10451
+ if (path) {
10452
+ name = path.pop();
10453
+ parent = getPathParent(parent, path);
10454
+ }
10388
10455
  return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent);
10389
10456
  } else {
10390
10457
  throw new Error("Parent entry is not a directory");
10391
10458
  }
10392
10459
  }
10393
10460
 
10461
+ function checkDuplicatesOption(duplicates) {
10462
+ if (duplicates === UNDEFINED_VALUE) {
10463
+ return DUPLICATES_THROW;
10464
+ } else if (DUPLICATES_VALUES.has(duplicates)) {
10465
+ return duplicates;
10466
+ } else {
10467
+ throw new Error(ERR_INVALID_DUPLICATES);
10468
+ }
10469
+ }
10470
+
10471
+ function splitPath(name) {
10472
+ if (name.includes("/")) {
10473
+ const path = name.split("/").filter(pathPart => pathPart != "" && pathPart != ".");
10474
+ if (path.length) {
10475
+ return path;
10476
+ }
10477
+ }
10478
+ }
10479
+
10480
+ function getPathParent(parent, path, movedEntry) {
10481
+ path.forEach(pathPart => {
10482
+ const previousParent = parent;
10483
+ parent = parent.getChildByName(pathPart);
10484
+ if (parent) {
10485
+ if (!parent.directory) {
10486
+ throw new Error(ERR_ENTRY_EXISTS);
10487
+ }
10488
+ if (movedEntry && (parent == movedEntry || parent.isDescendantOf(movedEntry))) {
10489
+ throw new Error(ERR_ANCESTOR_ENTRY);
10490
+ }
10491
+ } else {
10492
+ parent = new ZipDirectoryEntry(previousParent.fs, pathPart, { data: null }, previousParent);
10493
+ }
10494
+ });
10495
+ return parent;
10496
+ }
10497
+
10394
10498
  exports.BlobReader = BlobReader;
10395
10499
  exports.BlobWriter = BlobWriter;
10396
10500
  exports.Data64URIReader = Data64URIReader;
@@ -10400,6 +10504,7 @@
10400
10504
  exports.ERR_BAD_FORMAT = ERR_BAD_FORMAT;
10401
10505
  exports.ERR_CENTRAL_DIRECTORY_NOT_FOUND = ERR_CENTRAL_DIRECTORY_NOT_FOUND;
10402
10506
  exports.ERR_DUPLICATED_NAME = ERR_DUPLICATED_NAME;
10507
+ exports.ERR_DUPLICATE_IMPORTED_ENTRY = ERR_DUPLICATE_IMPORTED_ENTRY;
10403
10508
  exports.ERR_ENCRYPTED = ERR_ENCRYPTED;
10404
10509
  exports.ERR_ENCRYPTED_CENTRAL_DIRECTORY = ERR_ENCRYPTED_CENTRAL_DIRECTORY;
10405
10510
  exports.ERR_ENTRY_DATA_OUT_OF_BOUNDS = ERR_ENTRY_DATA_OUT_OF_BOUNDS;
@@ -10417,6 +10522,7 @@
10417
10522
  exports.ERR_INVALID_COMPRESSED_DATA = ERR_INVALID_COMPRESSED_DATA;
10418
10523
  exports.ERR_INVALID_CRC32 = ERR_INVALID_CRC32;
10419
10524
  exports.ERR_INVALID_DATE = ERR_INVALID_DATE;
10525
+ exports.ERR_INVALID_DUPLICATES = ERR_INVALID_DUPLICATES;
10420
10526
  exports.ERR_INVALID_ENCRYPTION_STRENGTH = ERR_INVALID_ENCRYPTION_STRENGTH;
10421
10527
  exports.ERR_INVALID_ENTRY_COMMENT = ERR_INVALID_ENTRY_COMMENT;
10422
10528
  exports.ERR_INVALID_ENTRY_COMMENT_TYPE = ERR_INVALID_ENTRY_COMMENT_TYPE;