@zip.js/zip.js 2.8.29 → 2.8.31

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 (56) hide show
  1. package/BENCHMARKS.md +35 -0
  2. package/deno.json +5 -4
  3. package/dist/zip-core.js +496 -218
  4. package/dist/zip-core.min.js +1 -1
  5. package/dist/zip-fs-core.js +398 -195
  6. package/dist/zip-fs-core.min.js +1 -1
  7. package/dist/zip-fs-native.js +588 -254
  8. package/dist/zip-fs-native.min.js +1 -1
  9. package/dist/zip-fs.js +587 -253
  10. package/dist/zip-fs.min.js +1 -1
  11. package/dist/zip-legacy.js +501 -223
  12. package/dist/zip-legacy.min.js +1 -1
  13. package/dist/zip-module.wasm +0 -0
  14. package/dist/zip-native.js +504 -226
  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 +503 -225
  19. package/dist/zip.min.js +1 -1
  20. package/index-native.cjs +588 -254
  21. package/index-native.min.js +1 -1
  22. package/index.cjs +587 -253
  23. package/index.d.cts +2814 -0
  24. package/index.d.ts +364 -1
  25. package/index.min.js +1 -1
  26. package/lib/core/codec-worker.js +0 -10
  27. package/lib/core/configuration.js +21 -33
  28. package/lib/core/constants.js +3 -0
  29. package/lib/core/io.js +11 -19
  30. package/lib/core/options.js +11 -1
  31. package/lib/core/streams/aes-crypto-stream.js +16 -20
  32. package/lib/core/streams/codecs/crc32.js +39 -11
  33. package/lib/core/streams/common-crypto.js +0 -2
  34. package/lib/core/streams/zip-crypto-stream.js +12 -16
  35. package/lib/core/streams/zip-entry-stream.js +69 -19
  36. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  37. package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +1 -3
  38. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  39. package/lib/core/util/base64.js +68 -0
  40. package/lib/core/util/decode-text.js +0 -1
  41. package/lib/core/util/default-mime-type.js +0 -3
  42. package/lib/core/util/inflate.js +194 -0
  43. package/lib/core/util/mime-type.js +26 -22
  44. package/lib/core/util/opfs-temp-stream.js +139 -0
  45. package/lib/core/web-worker-inline-native.js +1 -1
  46. package/lib/core/web-worker-inline-template-native.js +5 -5
  47. package/lib/core/web-worker-inline-wasm.js +1 -1
  48. package/lib/core/zip-fs.js +61 -10
  49. package/lib/core/zip-reader.js +162 -53
  50. package/lib/core/zip-writer.js +34 -41
  51. package/lib/core/zlib-streams-inline-template.js +3 -2
  52. package/lib/core/zlib-streams-inline.js +1 -1
  53. package/lib/zip-core-base.js +7 -2
  54. package/package.json +9 -6
  55. package/reserved-property-names.js +50 -0
  56. package/lib/core/util/mini-lz.js +0 -198
@@ -82,7 +82,8 @@ import {
82
82
  COMPRESSION_METHOD_DEFLATE_64,
83
83
  FILE_ATTR_UNIX_SETUID_MASK,
84
84
  FILE_ATTR_UNIX_SETGID_MASK,
85
- FILE_ATTR_UNIX_STICKY_MASK
85
+ FILE_ATTR_UNIX_STICKY_MASK,
86
+ EMPTY_UINT8_ARRAY
86
87
  } from "./constants.js";
87
88
  import {
88
89
  getConfiguration,
@@ -138,6 +139,11 @@ import {
138
139
  OPTION_CHECK_OVERLAPPING_ENTRY,
139
140
  OPTION_CHECK_AMBIGUITY,
140
141
  OPTION_CHECK_SIGNATURE,
142
+ OPTION_STRICTNESS,
143
+ OPTION_MAX_APPENDED_DATA_SIZE,
144
+ STRICTNESS_STRICT,
145
+ STRICTNESS_BALANCED,
146
+ STRICTNESS_TOLERANT,
141
147
  OPTION_USE_WEB_WORKERS,
142
148
  OPTION_USE_COMPRESSION_STREAM,
143
149
  OPTION_TRANSFER_STREAMS,
@@ -159,8 +165,6 @@ const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
159
165
  const CHARSET_UTF8 = "utf-8";
160
166
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
161
167
  const CHARSET_CP437 = "cp437";
162
- // bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
163
- // because they are informative only
164
168
  const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
165
169
  const ZIP64_PROPERTIES = [
166
170
  [PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
@@ -202,8 +206,11 @@ class ZipReader {
202
206
  if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
203
207
  throw new Error(ERR_BAD_FORMAT);
204
208
  }
205
- reader.chunkSize = getChunkSize(config);
206
- const endOfDirectoryInfo = await seekSignature(reader, END_OF_CENTRAL_DIR_SIGNATURE, reader.size, END_OF_CENTRAL_DIR_LENGTH, MAX_16_BITS * 16);
209
+ const strictness = getStrictness(getOptionValue(zipReader, options, OPTION_STRICTNESS), getOptionValue(zipReader, options, OPTION_CHECK_AMBIGUITY));
210
+ const checkAmbiguity = strictness == STRICTNESS_STRICT;
211
+ const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
212
+ const maxAppendedDataSize = getMaxAppendedDataSize(getOptionValue(zipReader, options, OPTION_MAX_APPENDED_DATA_SIZE), strictness);
213
+ const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
207
214
  if (!endOfDirectoryInfo) {
208
215
  const signatureArray = await readUint8Array(reader, 0, 4);
209
216
  const signatureView = getDataView(signatureArray);
@@ -213,14 +220,16 @@ class ZipReader {
213
220
  throw new Error(ERR_EOCDR_NOT_FOUND);
214
221
  }
215
222
  }
223
+ if (rejectAmbiguousEndOfDirectory && endOfDirectoryReachingEndCount > 1) {
224
+ throwAmbiguousArchive("multiple end of central directory records");
225
+ }
216
226
  const endOfDirectoryView = getDataView(endOfDirectoryInfo);
217
227
  let directoryDataLength = getUint32(endOfDirectoryView, 12);
218
228
  let directoryDataOffset = getUint32(endOfDirectoryView, 16);
219
229
  const commentOffset = endOfDirectoryInfo.offset;
220
230
  const commentLength = getUint16(endOfDirectoryView, 20);
221
231
  const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
222
- const checkAmbiguity = getOptionValue(zipReader, options, OPTION_CHECK_AMBIGUITY);
223
- if (checkAmbiguity && appendedDataOffset != reader.size) {
232
+ if (reader.size - appendedDataOffset > maxAppendedDataSize) {
224
233
  throwAmbiguousArchive("appended data");
225
234
  }
226
235
  let lastDiskNumber = getUint16(endOfDirectoryView, 4);
@@ -233,7 +242,7 @@ class ZipReader {
233
242
  if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
234
243
  const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
235
244
  await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
236
- new Uint8Array();
245
+ EMPTY_UINT8_ARRAY;
237
246
  const endOfDirectoryLocatorView = getDataView(endOfDirectoryLocatorArray);
238
247
  if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
239
248
  getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
@@ -299,14 +308,22 @@ class ZipReader {
299
308
  throw new Error(ERR_BAD_FORMAT);
300
309
  }
301
310
  const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
302
- if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
303
- const originalDirectoryDataOffset = directoryDataOffset;
304
- directoryDataOffset = expectedDirectoryDataOffset;
305
- if (directoryDataOffset > originalDirectoryDataOffset) {
306
- prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
311
+ if (directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
312
+ const storedPointsAtDirectory = getUint32(directoryView, offset) == CENTRAL_FILE_HEADER_SIGNATURE;
313
+ let reconcile = !storedPointsAtDirectory;
314
+ if (!reconcile && expectedDirectoryDataOffset >= 0 && expectedDirectoryDataOffset + 4 <= reader.size) {
315
+ const expectedSignatureArray = await readUint8Array(reader, expectedDirectoryDataOffset, 4, diskNumber);
316
+ reconcile = getUint32(getDataView(expectedSignatureArray), 0) == CENTRAL_FILE_HEADER_SIGNATURE;
317
+ }
318
+ if (reconcile) {
319
+ const originalDirectoryDataOffset = directoryDataOffset;
320
+ directoryDataOffset = expectedDirectoryDataOffset;
321
+ if (directoryDataOffset > originalDirectoryDataOffset) {
322
+ prependedDataLength += directoryDataOffset - originalDirectoryDataOffset;
323
+ }
324
+ directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
325
+ directoryView = getDataView(directoryArray);
307
326
  }
308
- directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
309
- directoryView = getDataView(directoryArray);
310
327
  }
311
328
  }
312
329
  const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
@@ -448,11 +465,11 @@ class ZipReader {
448
465
  const extractPrependedData = getOptionValue(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
449
466
  const extractAppendedData = getOptionValue(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
450
467
  if (extractPrependedData) {
451
- zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : new Uint8Array();
468
+ zipReader.prependedData = startOffset > 0 ? await readUint8Array(reader, 0, startOffset) : EMPTY_UINT8_ARRAY;
452
469
  }
453
- zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) : new Uint8Array();
470
+ zipReader.comment = commentLength ? await readUint8Array(reader, commentOffset + END_OF_CENTRAL_DIR_LENGTH, commentLength) : EMPTY_UINT8_ARRAY;
454
471
  if (extractAppendedData) {
455
- zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) : new Uint8Array();
472
+ zipReader.appendedData = appendedDataOffset < reader.size ? await readUint8Array(reader, appendedDataOffset, reader.size - appendedDataOffset) : EMPTY_UINT8_ARRAY;
456
473
  }
457
474
  return true;
458
475
  }
@@ -584,8 +601,8 @@ class ZipEntry {
584
601
  extraFieldLength,
585
602
  filenameLength
586
603
  } = localDirectory;
587
- const checkAmbiguity = getOptionValue(zipEntry, options, OPTION_CHECK_AMBIGUITY);
588
- let rawLocalFilename = new Uint8Array();
604
+ const checkAmbiguity = getStrictness(getOptionValue(zipEntry, options, OPTION_STRICTNESS), getOptionValue(zipEntry, options, OPTION_CHECK_AMBIGUITY)) == STRICTNESS_STRICT;
605
+ let rawLocalFilename = EMPTY_UINT8_ARRAY;
589
606
  if (checkAmbiguity && (filenameLength || extraFieldLength)) {
590
607
  const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
591
608
  rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
@@ -593,7 +610,7 @@ class ZipEntry {
593
610
  } else {
594
611
  localDirectory.rawExtraField = extraFieldLength ?
595
612
  await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
596
- new Uint8Array();
613
+ EMPTY_UINT8_ARRAY;
597
614
  }
598
615
  readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
599
616
  if (checkAmbiguity) {
@@ -620,12 +637,7 @@ class ZipEntry {
620
637
  }
621
638
  const dataOffset = offset + HEADER_SIZE + filenameLength + extraFieldLength;
622
639
  const size = compressedSize;
623
- const readable = reader.readable;
624
- Object.assign(readable, {
625
- diskNumberStart,
626
- offset: dataOffset,
627
- size
628
- });
640
+ const readable = reader.createReadable({ offset: dataOffset, size, diskNumberStart, chunkSize: getChunkSize(config) });
629
641
  const signal = getOptionValue(zipEntry, options, OPTION_SIGNAL);
630
642
  const checkPasswordOnly = getOptionValue(zipEntry, options, OPTION_CHECK_PASSWORD_ONLY);
631
643
  let checkOverlappingEntry = getOptionValue(zipEntry, options, OPTION_CHECK_OVERLAPPING_ENTRY);
@@ -896,7 +908,6 @@ function readExtraFieldUnix(extraField, directory, isInfoZip) {
896
908
  const view = getDataView(new Uint8Array(extraField.data));
897
909
  let uid, gid;
898
910
  if (isInfoZip) {
899
- // Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
900
911
  let offset = 0;
901
912
  const version = getUint8(view, offset++);
902
913
  const uidSize = getUint8(view, offset++);
@@ -906,8 +917,6 @@ function readExtraFieldUnix(extraField, directory, isInfoZip) {
906
917
  gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
907
918
  Object.assign(extraField, { version, uid, gid });
908
919
  } else if (extraField.data.length >= 4) {
909
- // Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
910
- // carried in the external file attributes, not here)
911
920
  uid = getUint16(view, 0);
912
921
  gid = getUint16(view, 2);
913
922
  Object.assign(extraField, { uid, gid });
@@ -959,7 +968,6 @@ function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory,
959
968
  timeProperties.forEach((propertyName, indexProperty) => {
960
969
  if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
961
970
  const time = getUint32(extraFieldView, offset);
962
- // the extended timestamp is a signed 32-bit time_t value
963
971
  directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
964
972
  const rawPropertyName = timeRawProperties[indexProperty];
965
973
  extraFieldExtendedTimestamp[rawPropertyName] = time;
@@ -1034,26 +1042,133 @@ async function detectOverlappingEntry({
1034
1042
  readRanges.set(index, range);
1035
1043
  }
1036
1044
 
1037
- async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
1038
- const signatureArray = new Uint8Array(4);
1039
- const signatureView = getDataView(signatureArray);
1040
- setUint32(signatureView, 0, signature);
1041
- const maximumBytes = minimumBytes + maximumLength;
1042
- return (await seek(minimumBytes)) || await seek(Math.min(maximumBytes, startOffset));
1045
+ function getStrictness(strictness, checkAmbiguity) {
1046
+ if (strictness === UNDEFINED_VALUE) {
1047
+ return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
1048
+ }
1049
+ return strictness;
1050
+ }
1043
1051
 
1044
- async function seek(length) {
1045
- const offset = startOffset - length;
1046
- const bytes = await readUint8Array(reader, offset, length);
1047
- for (let indexByte = bytes.length - minimumBytes; indexByte >= 0; indexByte--) {
1048
- if (bytes[indexByte] == signatureArray[0] && bytes[indexByte + 1] == signatureArray[1] &&
1049
- bytes[indexByte + 2] == signatureArray[2] && bytes[indexByte + 3] == signatureArray[3]) {
1050
- return {
1051
- offset: offset + indexByte,
1052
- buffer: bytes.slice(indexByte, indexByte + minimumBytes).buffer
1053
- };
1052
+ function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
1053
+ if (maxAppendedDataSize !== UNDEFINED_VALUE) {
1054
+ return maxAppendedDataSize;
1055
+ }
1056
+ if (strictness == STRICTNESS_STRICT) {
1057
+ return 0;
1058
+ }
1059
+ if (strictness == STRICTNESS_TOLERANT) {
1060
+ return Infinity;
1061
+ }
1062
+ return MAX_16_BITS;
1063
+ }
1064
+
1065
+ const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
1066
+
1067
+ const CENTRAL_DIRECTORY_UNREACHABLE = 0;
1068
+ const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
1069
+ const CENTRAL_DIRECTORY_REACHABLE = 2;
1070
+
1071
+ async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
1072
+ const { size } = reader;
1073
+ const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
1074
+ const remoteProbeBudget = { count: MAX_END_OF_CENTRAL_DIR_PROBES };
1075
+ let endOfDirectoryInfo;
1076
+ let plausibleEndOfDirectoryInfo;
1077
+ let endOfDirectoryReachingEndCount = 0;
1078
+ for await (const [anchoredView, anchoredOffset, anchoredArray, indexByte, offset] of scanEndOfCentralDirectory(reader, anchoredLength)) {
1079
+ const commentLength = getUint16(anchoredView, indexByte + 20);
1080
+ if (offset + END_OF_CENTRAL_DIR_LENGTH + commentLength == size) {
1081
+ const reachability = await getCentralDirectoryReachability(reader, anchoredView, anchoredOffset, indexByte, offset, size, remoteProbeBudget);
1082
+ if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
1083
+ if (!endOfDirectoryInfo) {
1084
+ endOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
1085
+ }
1086
+ endOfDirectoryReachingEndCount++;
1087
+ if (!rejectAmbiguous || endOfDirectoryReachingEndCount > 1) {
1088
+ break;
1089
+ }
1090
+ } else if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleEndOfDirectoryInfo) {
1091
+ plausibleEndOfDirectoryInfo = getEndOfCentralDirectoryInfo(anchoredArray, indexByte, offset);
1054
1092
  }
1055
1093
  }
1056
1094
  }
1095
+ if (!endOfDirectoryInfo) {
1096
+ endOfDirectoryInfo = plausibleEndOfDirectoryInfo;
1097
+ }
1098
+ if (!endOfDirectoryInfo) {
1099
+ endOfDirectoryInfo = await seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget);
1100
+ }
1101
+ return { endOfDirectoryInfo, endOfDirectoryReachingEndCount };
1102
+ }
1103
+
1104
+ async function seekEndOfCentralDirectory(reader, maxAppendedDataSize, remoteProbeBudget) {
1105
+ const { size } = reader;
1106
+ const searchLength = Math.min(size, maxAppendedDataSize == Infinity ? size :
1107
+ END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS + maxAppendedDataSize);
1108
+ let firstSignatureInfo, plausibleInfo;
1109
+ for await (const [searchView, searchOffset, searchArray, indexByte, offset] of scanEndOfCentralDirectory(reader, searchLength)) {
1110
+ const record = getEndOfCentralDirectoryInfo(searchArray, indexByte, offset);
1111
+ if (!firstSignatureInfo) {
1112
+ firstSignatureInfo = record;
1113
+ }
1114
+ const reachability = await getCentralDirectoryReachability(reader, searchView, searchOffset, indexByte, offset, size, remoteProbeBudget);
1115
+ if (reachability == CENTRAL_DIRECTORY_REACHABLE) {
1116
+ return record;
1117
+ }
1118
+ if (reachability == CENTRAL_DIRECTORY_PLAUSIBLE && !plausibleInfo) {
1119
+ plausibleInfo = record;
1120
+ }
1121
+ }
1122
+ return plausibleInfo || firstSignatureInfo;
1123
+ }
1124
+
1125
+ async function* scanEndOfCentralDirectory(reader, scanLength) {
1126
+ const scanOffset = reader.size - scanLength;
1127
+ const scanArray = await readUint8Array(reader, scanOffset, scanLength);
1128
+ const scanView = getDataView(scanArray);
1129
+ for (let indexByte = scanArray.length - END_OF_CENTRAL_DIR_LENGTH; indexByte >= 0; indexByte--) {
1130
+ if (getUint32(scanView, indexByte) == END_OF_CENTRAL_DIR_SIGNATURE) {
1131
+ yield [scanView, scanOffset, scanArray, indexByte, scanOffset + indexByte];
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ function getEndOfCentralDirectoryInfo(scanArray, indexByte, offset) {
1137
+ return { offset, buffer: scanArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer };
1138
+ }
1139
+
1140
+ async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
1141
+ const filesLength = getUint16(view, indexByte + 10);
1142
+ const directoryDataLength = getUint32(view, indexByte + 12);
1143
+ const directoryDataOffset = getUint32(view, indexByte + 16);
1144
+ if (filesLength == MAX_16_BITS || directoryDataLength == MAX_32_BITS || directoryDataOffset == MAX_32_BITS) {
1145
+ const locatorSignature = await readSignature(reader, view, anchoredOffset, offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, size, remoteProbeBudget);
1146
+ return locatorSignature == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE ? CENTRAL_DIRECTORY_REACHABLE : CENTRAL_DIRECTORY_UNREACHABLE;
1147
+ }
1148
+ if (!filesLength && !directoryDataLength) {
1149
+ return CENTRAL_DIRECTORY_PLAUSIBLE;
1150
+ }
1151
+ for (const centralDirectoryOffset of [offset - directoryDataLength, directoryDataOffset]) {
1152
+ if (await readSignature(reader, view, anchoredOffset, centralDirectoryOffset, size, remoteProbeBudget) == CENTRAL_FILE_HEADER_SIGNATURE) {
1153
+ return CENTRAL_DIRECTORY_REACHABLE;
1154
+ }
1155
+ }
1156
+ return CENTRAL_DIRECTORY_UNREACHABLE;
1157
+ }
1158
+
1159
+ async function readSignature(reader, view, anchoredOffset, signatureOffset, size, remoteProbeBudget) {
1160
+ if (signatureOffset < 0 || signatureOffset + 4 > size) {
1161
+ return UNDEFINED_VALUE;
1162
+ }
1163
+ if (signatureOffset >= anchoredOffset) {
1164
+ return getUint32(view, signatureOffset - anchoredOffset);
1165
+ }
1166
+ if (remoteProbeBudget.count > 0) {
1167
+ remoteProbeBudget.count--;
1168
+ const signatureArray = await readUint8Array(reader, signatureOffset, 4);
1169
+ return getUint32(getDataView(signatureArray), 0);
1170
+ }
1171
+ return UNDEFINED_VALUE;
1057
1172
  }
1058
1173
 
1059
1174
  function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
@@ -1068,8 +1183,6 @@ function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
1068
1183
  if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
1069
1184
  throwAmbiguousArchive("mismatched local file header (compression method)");
1070
1185
  }
1071
- // when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
1072
- // also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
1073
1186
  if (!localDirectory.bitFlag.dataDescriptor &&
1074
1187
  (localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
1075
1188
  (localDirectory.signature != zipEntry.signature ||
@@ -1118,10 +1231,6 @@ function getBigUint64(view, offset) {
1118
1231
  return Number(view.getBigUint64(offset, true));
1119
1232
  }
1120
1233
 
1121
- function setUint32(view, offset, value) {
1122
- view.setUint32(offset, value, true);
1123
- }
1124
-
1125
1234
  function getDataView(array) {
1126
1235
  return new DataView(array.buffer, array.byteOffset, array.byteLength);
1127
1236
  }
@@ -137,7 +137,8 @@ import {
137
137
  MAX_DATE,
138
138
  UNDEFINED_VALUE,
139
139
  INFINITY_VALUE,
140
- OBJECT_TYPE
140
+ OBJECT_TYPE,
141
+ EMPTY_UINT8_ARRAY
141
142
  } from "./constants.js";
142
143
  import {
143
144
  getConfiguration,
@@ -218,7 +219,6 @@ const ERR_INVALID_VERSION = "Version exceeds 65535";
218
219
  const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
219
220
  const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
220
221
  const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
221
- // same message as the constant exported by zip-reader.js
222
222
  const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
223
223
  const MIN_UNIX_TIME = -2147483648;
224
224
  const MAX_UNIX_TIME = 2147483647;
@@ -301,12 +301,12 @@ class ZipWriter {
301
301
  rawExtraField,
302
302
  } = entry;
303
303
  const { level, languageEncodingFlag, dataDescriptor } = bitFlag;
304
- rawExtraFieldZip64 = rawExtraFieldZip64 || new Uint8Array();
305
- rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
306
- rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
307
- rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
308
- rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
309
- rawExtraField = rawExtraField || new Uint8Array();
304
+ rawExtraFieldZip64 = rawExtraFieldZip64 || EMPTY_UINT8_ARRAY;
305
+ rawExtraFieldAES = rawExtraFieldAES || EMPTY_UINT8_ARRAY;
306
+ rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || EMPTY_UINT8_ARRAY;
307
+ rawExtraFieldNTFS = rawExtraFieldNTFS || EMPTY_UINT8_ARRAY;
308
+ rawExtraFieldUnix = rawExtraFieldUnix || EMPTY_UINT8_ARRAY;
309
+ rawExtraField = rawExtraField || EMPTY_UINT8_ARRAY;
310
310
  if (entry.extraFieldAES) {
311
311
  compressionMethod = COMPRESSION_METHOD_AES;
312
312
  }
@@ -412,7 +412,7 @@ class ZipWriter {
412
412
  return false;
413
413
  }
414
414
 
415
- async close(comment = new Uint8Array(), options = {}) {
415
+ async close(comment = EMPTY_UINT8_ARRAY, options = {}) {
416
416
  const zipWriter = this;
417
417
  const { pendingAddFileCalls, writer } = this;
418
418
  const { writable } = writer;
@@ -728,7 +728,7 @@ function resolveMetadata(zipWriter, name, options) {
728
728
  if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
729
729
  throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
730
730
  }
731
- let rawExtraField = new Uint8Array();
731
+ let rawExtraField = EMPTY_UINT8_ARRAY;
732
732
  const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
733
733
  if (extraField) {
734
734
  let extraFieldSize = 0;
@@ -789,8 +789,6 @@ async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, op
789
789
  let maximumCompressedSize = 0;
790
790
  let uncompressedSize = 0;
791
791
  if (passThrough) {
792
- // the headers of a passThrough entry describe the payload verbatim: without a
793
- // reader they would declare content that is not there
794
792
  if (!reader) {
795
793
  throw new Error(ERR_UNDEFINED_READER);
796
794
  }
@@ -801,8 +799,6 @@ async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, op
801
799
  }
802
800
  const zip64Enabled = zip64 === true;
803
801
  const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
804
- // entries without a reader have no payload: write them as empty stored files instead of
805
- // declaring a compressed or encrypted content that does not exist
806
802
  const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
807
803
  if (!reader) {
808
804
  level = 0;
@@ -940,7 +936,6 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
940
936
  fileEntry.diskNumberStart = writer.diskNumber;
941
937
  fileEntry.offset = getSegmentOffset(zipWriter, writer);
942
938
  if (usdz) {
943
- // the padding depends on the entry offset, only known now that the writer is locked
944
939
  const previousMetadataSize = entryInfo.metadataSize;
945
940
  appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
946
941
  fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
@@ -983,6 +978,13 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
983
978
  if (releaseLockWriter) {
984
979
  releaseLockWriter();
985
980
  }
981
+ if (bufferedWrite && fileWriter && fileWriter.dispose) {
982
+ try {
983
+ await fileWriter.dispose();
984
+ } catch {
985
+ // ignored
986
+ }
987
+ }
986
988
  }
987
989
 
988
990
  function requestLockCurrentFileEntry() {
@@ -1002,7 +1004,7 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
1002
1004
  async function skipDiskIfNeeded() {
1003
1005
  if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
1004
1006
  writer.availableSize = 0;
1005
- await writeData(writer, new Uint8Array());
1007
+ await writeData(writer, EMPTY_UINT8_ARRAY);
1006
1008
  }
1007
1009
  }
1008
1010
  }
@@ -1113,8 +1115,7 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1113
1115
  }
1114
1116
  const { writable } = writer;
1115
1117
  if (reader) {
1116
- reader.chunkSize = getChunkSize(config);
1117
- const readable = reader.readable;
1118
+ const readable = reader.createReadable ? reader.createReadable({ chunkSize: getChunkSize(config) }) : reader.readable;
1118
1119
  const size = reader.size;
1119
1120
  const workerOptions = {
1120
1121
  options: {
@@ -1214,13 +1215,9 @@ function getHeaderInfo(options) {
1214
1215
  : compressionMethod !== COMPRESSION_METHOD_STORE);
1215
1216
  let rawLocalExtraFieldZip64;
1216
1217
  const uncompressedFile = passThrough || !compressed;
1217
- // entries without a data descriptor are always buffered (cf. getFileEntry), so the
1218
- // zip64 extra field values can be patched into the local header before flushing it
1219
1218
  const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
1220
1219
  const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
1221
1220
  if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
1222
- // APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
1223
- // uncompressed and compressed size fields
1224
1221
  const length = 4 + 16;
1225
1222
  const extraFieldZip64 = createRecordWriter(length);
1226
1223
  extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
@@ -1234,7 +1231,7 @@ function getHeaderInfo(options) {
1234
1231
  }
1235
1232
  }
1236
1233
  } else {
1237
- rawLocalExtraFieldZip64 = new Uint8Array();
1234
+ rawLocalExtraFieldZip64 = EMPTY_UINT8_ARRAY;
1238
1235
  }
1239
1236
  let rawExtraFieldAES;
1240
1237
  if (encrypted && !zipCrypto) {
@@ -1244,7 +1241,7 @@ function getHeaderInfo(options) {
1244
1241
  rawExtraFieldAES = extraFieldAES.array;
1245
1242
  rawExtraFieldAES[8] = encryptionStrength;
1246
1243
  } else {
1247
- rawExtraFieldAES = new Uint8Array();
1244
+ rawExtraFieldAES = EMPTY_UINT8_ARRAY;
1248
1245
  }
1249
1246
  let rawExtraFieldNTFS;
1250
1247
  let rawExtraFieldExtendedTimestamp;
@@ -1267,8 +1264,7 @@ function getHeaderInfo(options) {
1267
1264
  }
1268
1265
  rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
1269
1266
  } else {
1270
- // the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
1271
- rawExtraFieldExtendedTimestamp = new Uint8Array();
1267
+ rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
1272
1268
  }
1273
1269
  try {
1274
1270
  const lastModTimeNTFS = getTimeNTFS(lastModDate);
@@ -1283,10 +1279,10 @@ function getHeaderInfo(options) {
1283
1279
  extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
1284
1280
  rawExtraFieldNTFS = extraFieldNTFS.array;
1285
1281
  } catch {
1286
- rawExtraFieldNTFS = new Uint8Array();
1282
+ rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
1287
1283
  }
1288
1284
  } else {
1289
- rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = new Uint8Array();
1285
+ rawExtraFieldNTFS = rawExtraFieldExtendedTimestamp = EMPTY_UINT8_ARRAY;
1290
1286
  }
1291
1287
  let rawExtraFieldUnix;
1292
1288
  try {
@@ -1312,10 +1308,10 @@ function getHeaderInfo(options) {
1312
1308
  extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
1313
1309
  rawExtraFieldUnix = extraFieldUnix.array;
1314
1310
  } else {
1315
- rawExtraFieldUnix = new Uint8Array();
1311
+ rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
1316
1312
  }
1317
1313
  } catch {
1318
- rawExtraFieldUnix = new Uint8Array();
1314
+ rawExtraFieldUnix = EMPTY_UINT8_ARRAY;
1319
1315
  }
1320
1316
  if (compressionMethod === UNDEFINED_VALUE) {
1321
1317
  compressionMethod = compressed ? COMPRESSION_METHOD_DEFLATE : COMPRESSION_METHOD_STORE;
@@ -1382,7 +1378,7 @@ function getHeaderInfo(options) {
1382
1378
  version,
1383
1379
  compressionMethod,
1384
1380
  extraFieldExtendedTimestampFlag,
1385
- rawExtraFieldZip64: new Uint8Array(),
1381
+ rawExtraFieldZip64: EMPTY_UINT8_ARRAY,
1386
1382
  localExtraFieldZip64Length,
1387
1383
  rawExtraFieldExtendedTimestamp,
1388
1384
  rawExtraFieldNTFS,
@@ -1395,7 +1391,6 @@ function getHeaderInfo(options) {
1395
1391
  function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
1396
1392
  const { headerInfo } = entryInfo;
1397
1393
  let { localHeaderArray, extraFieldLength } = headerInfo;
1398
- let localHeaderArrayView;
1399
1394
  let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
1400
1395
  if (extraBytesLength < 4) {
1401
1396
  extraBytesLength += 64;
@@ -1408,7 +1403,7 @@ function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
1408
1403
  headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
1409
1404
  arraySet(localHeaderArray, previousLocalHeaderArray);
1410
1405
  arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
1411
- localHeaderArrayView = getDataView(localHeaderArray);
1406
+ const localHeaderArrayView = getDataView(localHeaderArray);
1412
1407
  setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
1413
1408
  headerInfo.localHeaderView = localHeaderArrayView;
1414
1409
  entryInfo.metadataSize += extraBytesLength;
@@ -1416,7 +1411,7 @@ function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
1416
1411
 
1417
1412
  function packUnixId(id) {
1418
1413
  if (id === UNDEFINED_VALUE) {
1419
- return new Uint8Array();
1414
+ return EMPTY_UINT8_ARRAY;
1420
1415
  } else {
1421
1416
  const dataArray = new Uint8Array(4);
1422
1417
  const dataView = getDataView(dataArray);
@@ -1459,7 +1454,7 @@ function getDataDescriptorInfo({
1459
1454
  dataDescriptor,
1460
1455
  dataDescriptorSignature
1461
1456
  }) {
1462
- let dataDescriptorArray = new Uint8Array();
1457
+ let dataDescriptorArray = EMPTY_UINT8_ARRAY;
1463
1458
  let dataDescriptorView, dataDescriptorOffset = 0;
1464
1459
  let dataDescriptorLength = zip64 ? DATA_DESCRIPTOR_RECORD_ZIP_64_LENGTH : DATA_DESCRIPTOR_RECORD_LENGTH;
1465
1460
  if (dataDescriptorSignature) {
@@ -1543,7 +1538,6 @@ function updateLocalHeader({
1543
1538
  }
1544
1539
  }
1545
1540
  if (zip64 && localExtraFieldZip64Length) {
1546
- // the local zip64 extra field always contains both size slots
1547
1541
  const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
1548
1542
  setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
1549
1543
  setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
@@ -1597,7 +1591,7 @@ function createDirectoryRecords(files) {
1597
1591
  }
1598
1592
  rawExtraFieldZip64 = extraFieldZip64.array;
1599
1593
  } else {
1600
- rawExtraFieldZip64 = new Uint8Array();
1594
+ rawExtraFieldZip64 = EMPTY_UINT8_ARRAY;
1601
1595
  }
1602
1596
  fileEntry.rawExtraFieldZip64 = rawExtraFieldZip64;
1603
1597
  fileEntry.zip64Offset = zip64Offset;
@@ -1612,7 +1606,7 @@ function createDirectoryRecords(files) {
1612
1606
  extraFieldTimestamp.uint32(lastModTimeUnix);
1613
1607
  rawExtraFieldTimestamp = extraFieldTimestamp.array;
1614
1608
  } else {
1615
- rawExtraFieldTimestamp = new Uint8Array();
1609
+ rawExtraFieldTimestamp = EMPTY_UINT8_ARRAY;
1616
1610
  }
1617
1611
  fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
1618
1612
  const extraFieldLength = getLength(
@@ -1668,7 +1662,7 @@ async function writeDirectoryRecords(zipWriter, directoryDataLength, options) {
1668
1662
  await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
1669
1663
  directoryDiskOffset = offset;
1670
1664
  writer.availableSize = 0;
1671
- await writeData(writer, new Uint8Array());
1665
+ await writeData(writer, EMPTY_UINT8_ARRAY);
1672
1666
  }
1673
1667
  if (indexFileEntry == 0) {
1674
1668
  cdStartDiskNumber = writer.diskNumber;
@@ -1741,7 +1735,7 @@ async function writeEndOfDirectoryRecord(zipWriter, comment, options, cdInfo) {
1741
1735
  const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
1742
1736
  if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
1743
1737
  writer.availableSize = 0;
1744
- await writeData(writer, new Uint8Array());
1738
+ await writeData(writer, EMPTY_UINT8_ARRAY);
1745
1739
  }
1746
1740
  lastDiskNumber = writer.diskNumber;
1747
1741
  if (zip64) {
@@ -1838,7 +1832,6 @@ function getTimeUnix(date) {
1838
1832
  return Math.floor(date.getTime() / 1000);
1839
1833
  }
1840
1834
 
1841
- // the extended timestamp extra field stores dates as signed 32-bit time_t values
1842
1835
  function inUnixTimeRange(timeUnix) {
1843
1836
  return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
1844
1837
  }
@@ -28,7 +28,8 @@
28
28
 
29
29
  /* eslint-disable quotes */
30
30
 
31
- import { decompress } from "./util/mini-lz.js";
31
+ import { inflateRaw } from "./util/inflate.js";
32
+ import { base64Decode, base64Encode } from "./util/base64.js";
32
33
 
33
34
  export function configureZlibModule(configure) {
34
35
  const data = `__wasmBinary__`;
@@ -36,7 +37,7 @@ export function configureZlibModule(configure) {
36
37
  configure({
37
38
  wasmURI: () => {
38
39
  if (!dataURI) {
39
- dataURI = "data:application/wasm;base64," + decompress(data);
40
+ dataURI = "data:application/wasm;base64," + base64Encode(inflateRaw(base64Decode(data)));
40
41
  }
41
42
  return dataURI;
42
43
  }