@zip.js/zip.js 2.11.4 → 2.13.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.
Files changed (52) hide show
  1. package/deno.json +1 -1
  2. package/dist/zip-core-external.js +549 -139
  3. package/dist/zip-core-external.min.js +1 -1
  4. package/dist/zip-core.js +559 -136
  5. package/dist/zip-core.min.js +1 -1
  6. package/dist/zip-fs-core-external.js +615 -260
  7. package/dist/zip-fs-core-external.min.js +1 -1
  8. package/dist/zip-fs-core.js +628 -257
  9. package/dist/zip-fs-core.min.js +1 -1
  10. package/dist/zip-fs-external.js +615 -260
  11. package/dist/zip-fs-external.min.js +1 -1
  12. package/dist/zip-fs-native.js +630 -259
  13. package/dist/zip-fs-native.min.js +1 -1
  14. package/dist/zip-fs.js +631 -260
  15. package/dist/zip-fs.min.js +1 -1
  16. package/dist/zip-legacy.js +561 -138
  17. package/dist/zip-legacy.min.js +1 -1
  18. package/dist/zip-native.js +561 -138
  19. package/dist/zip-native.min.js +1 -1
  20. package/dist/zip-web-worker-native.js +1 -1
  21. package/dist/zip-web-worker.js +1 -1
  22. package/dist/zip.js +562 -139
  23. package/dist/zip.min.js +1 -1
  24. package/index-native.cjs +630 -259
  25. package/index-native.min.js +1 -1
  26. package/index.cjs +631 -260
  27. package/index.d.cts +474 -59
  28. package/index.d.ts +474 -59
  29. package/index.min.js +1 -1
  30. package/lib/core/codec-worker-web.js +72 -22
  31. package/lib/core/codec-worker.js +18 -5
  32. package/lib/core/configuration.js +20 -5
  33. package/lib/core/constants.js +2 -0
  34. package/lib/core/io.js +13 -5
  35. package/lib/core/options.js +16 -0
  36. package/lib/core/streams/codec-stream.js +6 -0
  37. package/lib/core/util/error.js +35 -0
  38. package/lib/core/util/warnings.js +42 -0
  39. package/lib/core/version.js +1 -1
  40. package/lib/core/web-worker-base.js +46 -9
  41. package/lib/core/web-worker-inline-native.js +1 -1
  42. package/lib/core/web-worker-inline-wasm.js +1 -1
  43. package/lib/core/zip-entry.js +49 -1
  44. package/lib/core/zip-fs.js +45 -133
  45. package/lib/core/zip-reader.js +115 -52
  46. package/lib/core/zip-writer.js +244 -57
  47. package/lib/zip-core-base.js +9 -3
  48. package/lib/zip-core-reader.js +2 -0
  49. package/lib/zip-core-writer.js +6 -1
  50. package/lib/zip-module-wasm-base.js +2 -2
  51. package/package.json +5 -3
  52. package/worker-message-property-names.js +1 -1
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global WritableStream, AbortController, DOMException */
29
+ /* global WritableStream, AbortController */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
32
  import {
@@ -50,39 +50,30 @@ import {
50
50
  } from "./zip-reader.js";
51
51
  import {
52
52
  ZipWriter,
53
- getEntriesSize
53
+ getEntriesSize,
54
+ getSourceEntryOptions
54
55
  } from "./zip-writer.js";
55
56
  import {
56
57
  Entry
57
58
  } from "./zip-entry.js";
58
- import { checkSignalOption, ERR_ABORTED, ABORT_ERROR_NAME } from "./options.js";
59
+ import { checkSignalOption, throwIfAborted, checkPassThroughOption, PASS_THROUGH_COMPRESSED } from "./options.js";
59
60
  import { ProgressWatcherStream, callHandler } from "./codec-worker.js";
60
61
  import {
61
62
  UNDEFINED_VALUE,
62
63
  FUNCTION_TYPE,
63
- OBJECT_TYPE,
64
- MAX_8_BITS,
65
- MIN_DATE,
66
- MAX_DATE,
67
- EXTRAFIELD_TYPE_ZIP64,
68
- EXTRAFIELD_TYPE_AES,
69
- EXTRAFIELD_TYPE_NTFS,
70
- EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP,
71
- EXTRAFIELD_TYPE_UNICODE_PATH,
72
- EXTRAFIELD_TYPE_UNICODE_COMMENT,
73
- EXTRAFIELD_TYPE_USDZ,
74
- EXTRAFIELD_TYPE_INFOZIP,
75
- EXTRAFIELD_TYPE_UNIX,
76
- EXTRAFIELD_TYPE_UNIX_TYPE1,
77
- EXTRAFIELD_TYPE_PKWARE_UNIX
64
+ OBJECT_TYPE
78
65
  } from "./constants.js";
79
66
  import { toExactUint8Array } from "./util/array.js";
80
67
  import { toCompatibleReadable, toCompatibleWritable } from "./util/compatible-streams.js";
68
+ import { isErrorObject } from "./util/error.js";
81
69
 
82
70
  const ERR_ENTRY_EXISTS = "Entry filename already exists";
83
71
  const ERR_DUPLICATE_IMPORTED_ENTRY = "Duplicate entry filename in the imported zip file";
84
72
  const ERR_INVALID_DUPLICATES = "Invalid duplicates option (must be \"throw\", \"keep-first\" or \"keep-last\")";
85
- const ERR_ANCESTOR_ENTRY = "Entry is a ancestor of target entry";
73
+ const ERR_ANCESTOR_ENTRY = "Entry is an ancestor of target entry";
74
+ const ERR_ROOT_DIRECTORY_NOT_MOVABLE = "Root directory cannot be moved";
75
+ const ERR_TARGET_NOT_DIRECTORY = "Target entry is not a directory";
76
+ const ERR_PARENT_NOT_DIRECTORY = "Parent entry is not a directory";
86
77
  const ERR_READABLE_CONSUMED = "Readable stream already consumed";
87
78
  const DUPLICATES_THROW = "throw";
88
79
  const DUPLICATES_KEEP_FIRST = "keep-first";
@@ -90,22 +81,8 @@ const DUPLICATES_KEEP_LAST = "keep-last";
90
81
  const DUPLICATES_VALUES = new Set([DUPLICATES_THROW, DUPLICATES_KEEP_FIRST, DUPLICATES_KEEP_LAST]);
91
82
  const ERR_INVALID_PASS_THROUGH = "Invalid passThrough option (use readerOptions.passThrough or set uncompressedSize for each entry)";
92
83
  const ERR_INVALID_READER_OPTIONS = "Invalid readerOptions (must be an object)";
93
- const ERR_ZIP_CRYPTO_LAST_MOD_DATE = "The last modification date of an entry encrypted with ZipCrypto cannot be changed when passThrough is set";
84
+ const ERR_UNSUPPORTED_PASS_THROUGH_VALUE = "The 'compressed' passThrough option is only supported by Entry#getData() and ZipWriter#add()";
94
85
  const ERR_ABORT_EXPORT = "zipjs-abort-export";
95
- const INFOZIP_EXTRA_FIELD_TYPE = "infozip";
96
- const INTERPRETED_EXTRA_FIELD_TYPES = new Set([
97
- EXTRAFIELD_TYPE_ZIP64,
98
- EXTRAFIELD_TYPE_AES,
99
- EXTRAFIELD_TYPE_NTFS,
100
- EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP,
101
- EXTRAFIELD_TYPE_UNICODE_PATH,
102
- EXTRAFIELD_TYPE_UNICODE_COMMENT,
103
- EXTRAFIELD_TYPE_USDZ,
104
- EXTRAFIELD_TYPE_INFOZIP,
105
- EXTRAFIELD_TYPE_UNIX,
106
- EXTRAFIELD_TYPE_UNIX_TYPE1,
107
- EXTRAFIELD_TYPE_PKWARE_UNIX
108
- ]);
109
86
 
110
87
  class ZipEntry {
111
88
 
@@ -237,7 +214,7 @@ class ZipFileEntry extends ZipEntry {
237
214
  }));
238
215
  return true;
239
216
  } catch (error) {
240
- if (error.message == ERR_INVALID_PASSWORD) {
217
+ if (isErrorObject(error) && error.message == ERR_INVALID_PASSWORD) {
241
218
  return false;
242
219
  } else {
243
220
  throw error;
@@ -477,6 +454,7 @@ class ZipDirectoryEntry extends ZipEntry {
477
454
  await initStream(reader);
478
455
  zipReader = new ZipReader(reader, options);
479
456
  }
457
+ checkPassThroughValue(options.passThrough);
480
458
  const duplicates = checkDuplicatesOption(options.duplicates);
481
459
  const importedEntries = [];
482
460
  const entries = await zipReader.getEntries(options);
@@ -567,18 +545,25 @@ class ZipDirectoryEntry extends ZipEntry {
567
545
 
568
546
  async exportZip(writer, options = {}) {
569
547
  const zipEntry = this;
548
+ const zipWriterProvided = Boolean(writer) && typeof writer.add == FUNCTION_TYPE;
570
549
  options = Object.assign({}, options);
571
- if (options.bufferedWrite === UNDEFINED_VALUE) {
550
+ if (!zipWriterProvided && options.bufferedWrite === UNDEFINED_VALUE) {
572
551
  options.bufferedWrite = true;
573
552
  }
574
- const [readers] = await Promise.all([initReaders(zipEntry, checkReaderOptions(options.readerOptions)), initStream(writer)]);
575
- const zipWriter = new ZipWriter(writer, options);
553
+ const [readers] = await Promise.all([
554
+ initReaders(zipEntry, checkReaderOptions(options.readerOptions)),
555
+ zipWriterProvided ? UNDEFINED_VALUE : initStream(writer)
556
+ ]);
557
+ const zipWriter = zipWriterProvided ? writer : new ZipWriter(writer, options);
576
558
  await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], getUncompressedSize), options, readers);
559
+ if (zipWriterProvided) {
560
+ return zipWriter;
561
+ }
577
562
  await zipWriter.close(options.globalComment);
578
563
  return writer.getData ? writer.getData() : writer.writable;
579
564
  }
580
565
 
581
- getExportedSize(options = {}) {
566
+ async getExportedSize(options = {}) {
582
567
  const zipEntry = this;
583
568
  options = Object.assign({}, options);
584
569
  checkReaderOptions(options.readerOptions);
@@ -593,7 +578,7 @@ class ZipDirectoryEntry extends ZipEntry {
593
578
  const writeOrderGuaranteed = !options.bufferedWrite ||
594
579
  (entries.every(entry => entry.options.keepOrder !== false) &&
595
580
  children.every(child => isImplicitDirectory(child) || !child.children.length));
596
- return getEntriesSize(options, entries, writeOrderGuaranteed, options.globalComment);
581
+ return await getEntriesSize(options, entries, writeOrderGuaranteed, options.globalComment);
597
582
  }
598
583
 
599
584
  getChildByName(name) {
@@ -654,7 +639,7 @@ class ZipFS {
654
639
 
655
640
  move(entry, destination) {
656
641
  if (entry == this.root) {
657
- throw new Error("Root directory cannot be moved");
642
+ throw new Error(ERR_ROOT_DIRECTORY_NOT_MOVABLE);
658
643
  } else {
659
644
  if (destination.directory) {
660
645
  if (!destination.isDescendantOf(entry)) {
@@ -675,7 +660,7 @@ class ZipFS {
675
660
  throw new Error(ERR_ANCESTOR_ENTRY);
676
661
  }
677
662
  } else {
678
- throw new Error("Target entry is not a directory");
663
+ throw new Error(ERR_TARGET_NOT_DIRECTORY);
679
664
  }
680
665
  }
681
666
  }
@@ -807,7 +792,7 @@ class ZipFS {
807
792
  }
808
793
 
809
794
  const fs = { FS: ZipFS, ZipDirectoryEntry, ZipFileEntry };
810
- export { ZipFS, ZipEntry, ZipFileEntry, ZipDirectoryEntry, fs, ERR_ENTRY_EXISTS, ERR_DUPLICATE_IMPORTED_ENTRY, ERR_INVALID_DUPLICATES, ERR_READABLE_CONSUMED, ERR_INVALID_PASS_THROUGH, ERR_INVALID_READER_OPTIONS, ERR_ZIP_CRYPTO_LAST_MOD_DATE, ERR_ABORTED };
795
+ export { ZipFS, ZipEntry, ZipFileEntry, ZipDirectoryEntry, fs, ERR_ENTRY_EXISTS, ERR_DUPLICATE_IMPORTED_ENTRY, ERR_INVALID_DUPLICATES, ERR_READABLE_CONSUMED, ERR_INVALID_PASS_THROUGH, ERR_UNSUPPORTED_PASS_THROUGH_VALUE, ERR_INVALID_READER_OPTIONS, ERR_ANCESTOR_ENTRY, ERR_ROOT_DIRECTORY_NOT_MOVABLE, ERR_TARGET_NOT_DIRECTORY, ERR_PARENT_NOT_DIRECTORY };
811
796
 
812
797
  function getTotalSize(entries, getEntrySize) {
813
798
  let size = 0;
@@ -989,70 +974,11 @@ function getChildEntryOptions(child, selectedEntry, options) {
989
974
  let zipEntryMetadata = {};
990
975
  let passThroughOptions = {};
991
976
  if (child.data instanceof Entry) {
992
- const {
993
- externalFileAttributes,
994
- versionMadeBy,
995
- comment,
996
- lastModDate,
997
- rawLastModDate,
998
- creationDate,
999
- lastAccessDate,
1000
- uncompressedSize,
1001
- encrypted,
1002
- zipCrypto,
1003
- crc32,
1004
- compressionMethod,
1005
- extraFieldAES,
1006
- internalFileAttributes,
1007
- extraField,
1008
- bitFlag,
1009
- uid,
1010
- gid
1011
- } = child.data;
1012
- zipEntryMetadata = {
1013
- externalFileAttributes,
1014
- versionMadeBy,
1015
- comment,
1016
- lastModDate,
1017
- creationDate,
1018
- lastAccessDate,
1019
- internalFileAttributes
1020
- };
1021
- const userExtraField = getUserExtraField(extraField);
1022
- if (userExtraField) {
1023
- zipEntryMetadata.extraField = userExtraField;
1024
- }
1025
- if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
1026
- Object.assign(zipEntryMetadata, {
1027
- uid,
1028
- gid,
1029
- unixExtraFieldType: INFOZIP_EXTRA_FIELD_TYPE
1030
- });
1031
- }
1032
- if (isPassThrough(child, options)) {
1033
- let encryptionStrength;
1034
- if (extraFieldAES) {
1035
- encryptionStrength = extraFieldAES.strength;
1036
- }
1037
- passThroughOptions = {
1038
- passThrough: true,
1039
- encrypted,
1040
- zipCrypto,
1041
- crc32,
1042
- uncompressedSize,
1043
- encryptionStrength,
1044
- compressionMethod
1045
- };
1046
- if (bitFlag) {
1047
- passThroughOptions.dataDescriptor = bitFlag.dataDescriptor;
1048
- }
1049
- const lastModDateOverride = childOptions.lastModDate === UNDEFINED_VALUE ? options.lastModDate : childOptions.lastModDate;
1050
- if (lastModDateOverride === UNDEFINED_VALUE) {
1051
- passThroughOptions.rawLastModDate = rawLastModDate;
1052
- } else if (zipCrypto && (!bitFlag || bitFlag.dataDescriptor) && lastModDateOverride instanceof Date &&
1053
- getDosTimeHighByte(lastModDateOverride) != ((rawLastModDate >>> 8) & MAX_8_BITS)) {
1054
- throw new Error(ERR_ZIP_CRYPTO_LAST_MOD_DATE);
1055
- }
977
+ const passThrough = isPassThrough(child, options);
978
+ const lastModDateOverride = childOptions.lastModDate === UNDEFINED_VALUE ? options.lastModDate : childOptions.lastModDate;
979
+ ({ entryOptions: zipEntryMetadata, passThroughOptions } = getSourceEntryOptions(child.data, passThrough, lastModDateOverride));
980
+ if (passThrough) {
981
+ passThroughOptions.passThrough = true;
1056
982
  }
1057
983
  }
1058
984
  const entryOptions = Object.assign({ lastModDate: child.defaultLastModDate }, zipEntryMetadata, options, childOptions, passThroughOptions, { directory: child.directory });
@@ -1062,16 +988,6 @@ function getChildEntryOptions(child, selectedEntry, options) {
1062
988
  return { name, entryOptions };
1063
989
  }
1064
990
 
1065
- function getDosTimeHighByte(lastModDate) {
1066
- let dosLastModDate = new Date(Math.ceil(Math.floor(lastModDate.getTime() / 1000) / 2) * 2000);
1067
- if (dosLastModDate < MIN_DATE) {
1068
- dosLastModDate = MIN_DATE;
1069
- } else if (dosLastModDate > MAX_DATE) {
1070
- dosLastModDate = MAX_DATE;
1071
- }
1072
- return ((dosLastModDate.getHours() << 3) | (dosLastModDate.getMinutes() >> 3)) & MAX_8_BITS;
1073
- }
1074
-
1075
991
  function getDeterminedSize(child, passThrough) {
1076
992
  const { reader } = child;
1077
993
  if (reader && reader.size !== UNDEFINED_VALUE) {
@@ -1084,9 +1000,18 @@ function checkReaderOptions(readerOptions) {
1084
1000
  if (readerOptions && (typeof readerOptions != OBJECT_TYPE || Array.isArray(readerOptions))) {
1085
1001
  throw new Error(ERR_INVALID_READER_OPTIONS);
1086
1002
  }
1003
+ if (readerOptions) {
1004
+ checkPassThroughValue(readerOptions.passThrough);
1005
+ }
1087
1006
  return readerOptions;
1088
1007
  }
1089
1008
 
1009
+ function checkPassThroughValue(passThrough) {
1010
+ if (checkPassThroughOption(passThrough) === PASS_THROUGH_COMPRESSED) {
1011
+ throw new Error(ERR_UNSUPPORTED_PASS_THROUGH_VALUE);
1012
+ }
1013
+ }
1014
+
1090
1015
  function replaceContent(zipEntry, params) {
1091
1016
  const { uncompressedSize } = params;
1092
1017
  Object.assign(zipEntry, params, {
@@ -1115,20 +1040,6 @@ function isImplicitDirectory(child) {
1115
1040
  return child.directory && child.data === null;
1116
1041
  }
1117
1042
 
1118
- function getUserExtraField(extraField) {
1119
- if (extraField) {
1120
- const userExtraField = new Map();
1121
- extraField.forEach((field, type) => {
1122
- if (!INTERPRETED_EXTRA_FIELD_TYPES.has(type)) {
1123
- userExtraField.set(type, field.data);
1124
- }
1125
- });
1126
- if (userExtraField.size) {
1127
- return userExtraField;
1128
- }
1129
- }
1130
- }
1131
-
1132
1043
  async function exportZip(zipWriter, entry, totalSize, options, readers) {
1133
1044
  const { onstart, onprogress, onend, onentryprogress } = options;
1134
1045
  const selectedEntry = entry;
@@ -1231,6 +1142,7 @@ function addFileSystemHandle(zipEntry, handle, options) {
1231
1142
 
1232
1143
  async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1233
1144
  const { onstart, onprogress, onend } = options;
1145
+ checkPassThroughValue(options.passThrough);
1234
1146
  const readerOptions = checkReaderOptions(options.readerOptions);
1235
1147
  const abortController = new AbortController();
1236
1148
  const { signal } = abortController;
@@ -1306,7 +1218,7 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1306
1218
  if (exportAborted || isExportAborted(signal.reason)) {
1307
1219
  return;
1308
1220
  }
1309
- throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME) : signal.reason;
1221
+ throwIfAborted(signal);
1310
1222
  }
1311
1223
  try {
1312
1224
  if (child.directory) {
@@ -1439,7 +1351,7 @@ function addChild(parent, name, params, directory) {
1439
1351
  }
1440
1352
  return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent);
1441
1353
  } else {
1442
- throw new Error("Parent entry is not a directory");
1354
+ throw new Error(ERR_PARENT_NOT_DIRECTORY);
1443
1355
  }
1444
1356
  }
1445
1357
 
@@ -95,6 +95,7 @@ import {
95
95
  FILE_ATTR_UNIX_STICKY_MASK,
96
96
  NUMBER_TYPE,
97
97
  EMPTY_UINT8_ARRAY,
98
+ SYMBOL_ASYNC_DISPOSE,
98
99
  MIN_DATE
99
100
  } from "./constants.js";
100
101
  import { getConfiguration } from "./configuration.js";
@@ -120,7 +121,9 @@ import {
120
121
  } from "./io.js";
121
122
  import { decodeText } from "./util/decode-text.js";
122
123
  import { getDataView } from "./util/array.js";
124
+ import { addWarning } from "./util/warnings.js";
123
125
  import { toCompatibleReadable, streamToBlob } from "./util/compatible-streams.js";
126
+ import { isErrorObject } from "./util/error.js";
124
127
  import { Crc32 } from "./streams/codecs/crc32.js";
125
128
  import {
126
129
  PROPERTY_NAME_RAW_FILENAME,
@@ -137,7 +140,8 @@ import {
137
140
  PROPERTY_NAME_RAW_LAST_ACCESS_DATE,
138
141
  PROPERTY_NAME_CREATION_DATE,
139
142
  PROPERTY_NAME_RAW_CREATION_DATE,
140
- Entry
143
+ Entry,
144
+ getEncryptionOverhead
141
145
  } from "./zip-entry.js";
142
146
  import {
143
147
  OPTION_FILENAME_ENCODING,
@@ -154,6 +158,7 @@ import {
154
158
  OPTION_CHECK_OVERLAPPING_ENTRY,
155
159
  OPTION_CHECK_AMBIGUITY,
156
160
  OPTION_CHECK_LOCAL_DIRECTORY,
161
+ OPTION_CHECK_LOCAL_FILENAME,
157
162
  OPTION_CHECK_SIGNATURE,
158
163
  OPTION_CHECK_CRC32,
159
164
  OPTION_CHECK_AUTHENTICATION_CODE,
@@ -175,6 +180,7 @@ import {
175
180
  checkSignalOption,
176
181
  throwIfAborted,
177
182
  checkPasswordOption,
183
+ checkPassThroughOption,
178
184
  toNumber
179
185
  } from "./options.js";
180
186
 
@@ -208,6 +214,8 @@ const WARNING_PREPENDED_CENTRAL_DIRECTORY = "prepended central directory";
208
214
  const WARNING_TRAILING_CENTRAL_DIRECTORY_DATA = "trailing central directory data";
209
215
  const WARNING_DUPLICATE_FILENAME = "duplicate filename";
210
216
  const WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY = "mismatched zip64 end of central directory record";
217
+ const WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY = "multiple end of central directory records";
218
+ const WARNING_MISMATCHED_LOCAL_FILE_HEADER_FILENAME = "mismatched local file header (filename)";
211
219
  const WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG = "mismatched local file header (general purpose bit flag)";
212
220
  const WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD = "mismatched local file header (compression method)";
213
221
  const WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES = "mismatched local file header (crc32 or sizes)";
@@ -278,7 +286,7 @@ class ZipReader {
278
286
  }
279
287
  }
280
288
  if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
281
- throwAmbiguousArchive("multiple end of central directory records");
289
+ throwAmbiguousArchive(WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY);
282
290
  }
283
291
  const endOfDirectoryView = getDataView(endOfDirectoryInfo);
284
292
  let directoryDataLength = getUint32(endOfDirectoryView, 12);
@@ -576,7 +584,8 @@ class ZipReader {
576
584
  const writer = new TransformStream();
577
585
  const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
578
586
  arrayBufferPromise.catch(() => { });
579
- await fileEntry.getData(writer, entry, zipReader.readRanges, options);
587
+ await fileEntry.getData(writer, entry, zipReader.readRanges,
588
+ Object.assign({}, options, { preventClose: false }));
580
589
  return arrayBufferPromise;
581
590
  };
582
591
  offset = endOffset;
@@ -657,49 +666,101 @@ class ZipReader {
657
666
  await reader.readable.cancel();
658
667
  }
659
668
  }
669
+
670
+ [SYMBOL_ASYNC_DISPOSE]() {
671
+ return this.close();
672
+ }
660
673
  }
661
674
 
662
675
  class ZipReaderStream {
663
676
 
664
677
  constructor(options = {}) {
665
- const { readable, writable } = new TransformStream();
666
- const gen = new ZipReader(readable, options).getEntriesGenerator();
678
+ let sourceController;
679
+ const { readable, writable } = new TransformStream({
680
+ start(controller) {
681
+ sourceController = controller;
682
+ }
683
+ });
684
+ const zipReader = new ZipReader(readable, options);
685
+ const gen = zipReader.getEntriesGenerator();
686
+ const pendingEntries = new Set();
667
687
  this.readable = new ReadableStream({
668
688
  async pull(controller) {
669
689
  const { done, value } = await gen.next();
670
690
  if (done)
671
691
  return controller.close();
672
- const entryReadable = (function () {
673
- const { readable, writable } = new TransformStream();
674
- if (value.getData) {
675
- getData();
676
- return readable;
677
- }
678
-
679
- async function getData() {
680
- try {
681
- await value.getData(writable);
682
- } catch (error) {
683
- try {
684
- await writable.abort(error);
685
- } catch {
686
- // ignored
687
- }
688
- }
689
- }
690
- })();
692
+ const entryStream = createEntryStream(value, pendingEntries);
691
693
  const chunk = {
692
694
  ...value,
693
- readable: entryReadable
695
+ readable: entryStream.readable
694
696
  };
695
697
  delete chunk.getData;
698
+ Object.defineProperties(chunk, {
699
+ localDirectory: {
700
+ get: () => value.localDirectory,
701
+ enumerable: true
702
+ },
703
+ warnings: {
704
+ get: () => value.warnings,
705
+ enumerable: true
706
+ }
707
+ });
696
708
  controller.enqueue(chunk);
709
+ },
710
+ async cancel(reason) {
711
+ const entryStreams = Array.from(pendingEntries);
712
+ pendingEntries.clear();
713
+ sourceController.error(reason);
714
+ await Promise.allSettled(entryStreams.map(entryStream => entryStream.cancel(reason)));
715
+ await Promise.allSettled([gen.return(), zipReader.close()]);
697
716
  }
698
717
  });
699
718
  this.writable = writable;
700
719
  }
701
720
  }
702
721
 
722
+ function createEntryStream(entry, pendingEntries) {
723
+ const { readable, writable } = new TransformStream();
724
+ let dataReader;
725
+ const entryStream = {
726
+ cancel: async reason => {
727
+ pendingEntries.delete(entryStream);
728
+ await (dataReader ? dataReader.cancel(reason) : readable.cancel(reason));
729
+ }
730
+ };
731
+ entryStream.readable = new ReadableStream({
732
+ async pull(controller) {
733
+ if (!dataReader) {
734
+ dataReader = readable.getReader();
735
+ pendingEntries.add(entryStream);
736
+ getData();
737
+ }
738
+ const { done, value } = await dataReader.read();
739
+ if (done) {
740
+ controller.close();
741
+ } else {
742
+ controller.enqueue(value);
743
+ }
744
+ },
745
+ cancel: reason => entryStream.cancel(reason)
746
+ }, { highWaterMark: 0 });
747
+ return entryStream;
748
+
749
+ async function getData() {
750
+ try {
751
+ await entry.getData(writable, { preventClose: false });
752
+ } catch (error) {
753
+ try {
754
+ await writable.abort(error);
755
+ } catch {
756
+ // ignored
757
+ }
758
+ } finally {
759
+ pendingEntries.delete(entryStream);
760
+ }
761
+ }
762
+ }
763
+
703
764
  async function isZipFile(reader, options = {}) {
704
765
  reader = new GenericReader(reader);
705
766
  await initStream(reader);
@@ -763,6 +824,8 @@ export {
763
824
  WARNING_TRAILING_CENTRAL_DIRECTORY_DATA,
764
825
  WARNING_DUPLICATE_FILENAME,
765
826
  WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY,
827
+ WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY,
828
+ WARNING_MISMATCHED_LOCAL_FILE_HEADER_FILENAME,
766
829
  WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG,
767
830
  WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD,
768
831
  WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES
@@ -805,7 +868,9 @@ class ZipEntry {
805
868
  const dataView = getDataView(dataArray);
806
869
  let password = getOptionValue(zipEntry, options, OPTION_PASSWORD);
807
870
  let rawPassword = getOptionValue(zipEntry, options, OPTION_RAW_PASSWORD);
808
- const passThrough = getOptionValue(zipEntry, options, OPTION_PASS_THROUGH);
871
+ const passThrough = checkPassThroughOption(getOptionValue(zipEntry, options, OPTION_PASS_THROUGH));
872
+ const passThroughCompression = Boolean(passThrough);
873
+ const passThroughEncryption = passThrough === true;
809
874
  checkPasswordOption(password, rawPassword);
810
875
  password = password && password.length && password;
811
876
  rawPassword = rawPassword && rawPassword.length && rawPassword;
@@ -826,7 +891,9 @@ class ZipEntry {
826
891
  const checkLocalDirectoryOption = getOptionValue(zipEntry, options, OPTION_CHECK_LOCAL_DIRECTORY);
827
892
  const entryStrictness = getStrictness(options, zipEntry.options);
828
893
  const checkLocalDirectory = getCheckLocalDirectory(checkLocalDirectoryOption, entryStrictness);
829
- const checkLocalFilename = getCheckLocalFilename(checkLocalDirectoryOption, entryStrictness);
894
+ const checkLocalFilenameOption = getOptionValue(zipEntry, options, OPTION_CHECK_LOCAL_FILENAME);
895
+ const checkLocalFilename = getCheckLocalFilename(
896
+ checkLocalFilenameOption === UNDEFINED_VALUE ? checkLocalDirectoryOption : checkLocalFilenameOption, entryStrictness);
830
897
  let rawLocalFilename = EMPTY_UINT8_ARRAY;
831
898
  if (checkLocalFilename && (filenameLength || extraFieldLength)) {
832
899
  const trailingDataArray = await readUint8Array(reader, localHeaderOffset + HEADER_SIZE, filenameLength + extraFieldLength);
@@ -857,16 +924,17 @@ class ZipEntry {
857
924
  if (gid !== UNDEFINED_VALUE && fileEntry.gid === UNDEFINED_VALUE) {
858
925
  fileEntry.gid = gid;
859
926
  }
860
- const encrypted = zipEntry.encrypted && localDirectory.encrypted && !passThrough;
927
+ const checkPasswordOnly = getOptionValue(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
928
+ const encrypted = zipEntry.encrypted && localDirectory.encrypted && (!passThroughEncryption || checkPasswordOnly);
861
929
  const zipCrypto = encrypted && !extraFieldAES;
862
- if (!passThrough) {
930
+ if (!passThroughEncryption) {
863
931
  fileEntry.zipCrypto = zipCrypto;
864
932
  }
865
933
  if (encrypted && (localDirectory.rawBitFlag & BITFLAG_STRONG_ENCRYPTION) == BITFLAG_STRONG_ENCRYPTION) {
866
934
  throw new Error(ERR_UNSUPPORTED_ENCRYPTION);
867
935
  }
868
- const registeredCodec = passThrough ? UNDEFINED_VALUE : getRegisteredCodec(compressionMethod);
869
- if (compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64 && !registeredCodec && !passThrough) {
936
+ const registeredCodec = passThroughCompression ? UNDEFINED_VALUE : getRegisteredCodec(compressionMethod);
937
+ if (compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64 && !registeredCodec && !passThroughCompression) {
870
938
  throw new Error(ERR_UNSUPPORTED_COMPRESSION);
871
939
  }
872
940
  if (encrypted) {
@@ -883,15 +951,16 @@ class ZipEntry {
883
951
  const readable = toCompatibleReadable(reader.createReadable({ offset: dataOffset, size }));
884
952
  const signal = checkSignalOption(getOptionValue(zipEntry, options, OPTION_SIGNAL));
885
953
  throwIfAborted(signal);
886
- const checkPasswordOnly = getOptionValue(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
887
954
  let checkOverlappingEntry = getOptionValue(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
888
955
  const checkOverlappingEntryOnly = getOptionValue(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY_ONLY);
889
956
  if (checkOverlappingEntryOnly) {
890
957
  checkOverlappingEntry = true;
891
958
  }
892
959
  const { onstart, onprogress, onend } = options;
893
- const compressed = compressionMethod != COMPRESSION_METHOD_STORE && !passThrough;
894
- const outputSize = passThrough ? compressedSize : uncompressedSize;
960
+ const compressed = compressionMethod != COMPRESSION_METHOD_STORE && !passThroughCompression;
961
+ const outputSize = passThroughCompression ?
962
+ compressedSize - getEncryptionOverhead(encrypted, zipCrypto, extraFieldAES && extraFieldAES.strength) :
963
+ uncompressedSize;
895
964
  const deflate64 = compressionMethod == COMPRESSION_METHOD_DEFLATE_64;
896
965
  let useCompressionStream = getOptionValue(zipEntry, options, OPTION_USE_COMPRESSION_STREAM);
897
966
  if (deflate64) {
@@ -900,7 +969,7 @@ class ZipEntry {
900
969
  const checkCrc32Option = getOptionValue(zipEntry, options, OPTION_CHECK_CRC32);
901
970
  const checkCrc32 = (checkCrc32Option === UNDEFINED_VALUE ?
902
971
  getOptionValue(zipEntry, options, OPTION_CHECK_SIGNATURE) :
903
- checkCrc32Option) && !passThrough &&
972
+ checkCrc32Option) && !passThroughCompression &&
904
973
  (!encrypted || zipCrypto || (extraFieldAES && extraFieldAES.vendorVersion == VENDOR_VERSION_AE_1));
905
974
  const workerOptions = {
906
975
  options: {
@@ -944,7 +1013,7 @@ class ZipEntry {
944
1013
  readRanges
945
1014
  });
946
1015
  }
947
- let writable, abortError;
1016
+ let writable, abortError, aborted;
948
1017
  try {
949
1018
  if (!checkOverlappingEntryOnly) {
950
1019
  if (checkPasswordOnly) {
@@ -954,24 +1023,28 @@ class ZipEntry {
954
1023
  await initStream(writer, getDecodableOutputSize(outputSize, compressedSize, compressed));
955
1024
  ({ writable } = writer);
956
1025
  const { outputSize: writtenSize } = await runWorker({ readable, writable }, workerOptions);
957
- writer.size += writtenSize;
958
1026
  if (writtenSize != outputSize) {
959
- throw new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
1027
+ throw Object.assign(new Error(ERR_INVALID_UNCOMPRESSED_SIZE), { outputSize: writtenSize });
960
1028
  }
1029
+ writer.size += writtenSize;
961
1030
  }
962
1031
  } catch (error) {
963
- if (error.outputSize !== UNDEFINED_VALUE) {
1032
+ const { outputSize: failedOutputSize } = workerOptions;
1033
+ if (failedOutputSize !== UNDEFINED_VALUE) {
1034
+ writer.size += failedOutputSize;
1035
+ } else if (isErrorObject(error) && error.outputSize !== UNDEFINED_VALUE) {
964
1036
  writer.size += error.outputSize;
965
1037
  }
966
- if (!checkPasswordOnly || error.message != ERR_ABORT_CHECK_PASSWORD) {
1038
+ if (!checkPasswordOnly || !isErrorObject(error) || error.message != ERR_ABORT_CHECK_PASSWORD) {
967
1039
  abortError = error;
1040
+ aborted = true;
968
1041
  throw error;
969
1042
  }
970
1043
  } finally {
971
1044
  const preventClose = !ownsWritable(writer) && getOptionValue(zipEntry, options, OPTION_PREVENT_CLOSE);
972
1045
  if (!preventClose && writable && !writable.locked) {
973
1046
  const writableWriter = writable.getWriter();
974
- if (abortError) {
1047
+ if (aborted) {
975
1048
  try {
976
1049
  await writableWriter.abort(abortError);
977
1050
  } catch {
@@ -1623,7 +1696,7 @@ function validateLocalDirectory(zipEntry, localDirectory, rawLocalFilename, chec
1623
1696
  if (checkLocalFilename && !maskedLocalDirectory &&
1624
1697
  (rawLocalFilename.length != rawFilename.length ||
1625
1698
  rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte]))) {
1626
- reportAmbiguity(reject, warnings, "mismatched local file header (filename)");
1699
+ reportAmbiguity(reject, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_FILENAME);
1627
1700
  }
1628
1701
  if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
1629
1702
  reportAmbiguity(reject, warnings, WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG);
@@ -1648,16 +1721,6 @@ function reportAmbiguity(reject, warnings, reason) {
1648
1721
  }
1649
1722
  }
1650
1723
 
1651
- function addWarning(warnings, reason, filename) {
1652
- if (!warnings.some(warning => warning.reason == reason)) {
1653
- const warning = { reason };
1654
- if (filename !== UNDEFINED_VALUE) {
1655
- warning.filename = filename;
1656
- }
1657
- warnings.push(warning);
1658
- }
1659
- }
1660
-
1661
1724
  function throwAmbiguousArchive(reason) {
1662
1725
  const error = new Error(ERR_AMBIGUOUS_ARCHIVE);
1663
1726
  error.reason = reason;