@zip.js/zip.js 2.8.49 → 2.8.51

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.
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global WritableStream */
29
+ /* global WritableStream, AbortController, DOMException */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
32
  import {
@@ -71,6 +71,9 @@ import { toCompatibleReadable, toCompatibleWritable } from "./util/compatible-st
71
71
 
72
72
  const ERR_ENTRY_EXISTS = "Entry filename already exists";
73
73
  const ERR_READABLE_CONSUMED = "Readable stream already consumed";
74
+ const ERR_ABORT_EXPORT = "zipjs-abort-export";
75
+ const ERR_ABORTED = "The operation was aborted";
76
+ const ABORT_ERROR_NAME = "AbortError";
74
77
  const INFOZIP_EXTRA_FIELD_TYPE = "infozip";
75
78
  const INTERPRETED_EXTRA_FIELD_TYPES = new Set([
76
79
  EXTRAFIELD_TYPE_ZIP64,
@@ -179,8 +182,9 @@ class ZipFileEntry extends ZipEntry {
179
182
  const uncompressedSize = zipEntry.data ? zipEntry.data.uncompressedSize : reader.size;
180
183
  await Promise.all([initStream(reader), initStream(writer, uncompressedSize)]);
181
184
  const { readable } = reader;
185
+ const { signal } = options;
182
186
  zipEntry.uncompressedSize = reader.size;
183
- await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable));
187
+ await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable), { signal });
184
188
  return writer.getData ? writer.getData() : writer.writable;
185
189
  }
186
190
  }
@@ -447,7 +451,7 @@ class ZipDirectoryEntry extends ZipEntry {
447
451
  for (const entry of entries) {
448
452
  let parent = this;
449
453
  try {
450
- const path = entry.filename.split("/");
454
+ const path = entry.filename.split("/").filter(pathPart => pathPart != "" && pathPart != ".");
451
455
  const name = path.pop();
452
456
  path.forEach(pathPart => {
453
457
  const previousParent = parent;
@@ -522,6 +526,10 @@ class ZipDirectoryEntry extends ZipEntry {
522
526
  }
523
527
  }
524
528
 
529
+ getChildren(options = {}) {
530
+ return collectChildren(this, options.recursive);
531
+ }
532
+
525
533
  isPasswordProtected() {
526
534
  const children = this.children;
527
535
  for (let childIndex = 0; childIndex < children.length; childIndex++) {
@@ -613,6 +621,10 @@ class FS {
613
621
  return this.root.getChildByName(name);
614
622
  }
615
623
 
624
+ getChildren(options) {
625
+ return this.root.getChildren(options);
626
+ }
627
+
616
628
  addDirectory(name, options) {
617
629
  return this.root.addDirectory(name, options);
618
630
  }
@@ -721,7 +733,7 @@ class FS {
721
733
  }
722
734
 
723
735
  const fs = { FS, ZipDirectoryEntry, ZipFileEntry };
724
- export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED };
736
+ export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED, ERR_ABORTED };
725
737
 
726
738
  function getTotalSize(entries, propertyName) {
727
739
  let size = 0;
@@ -823,6 +835,39 @@ function detach(entry) {
823
835
  }
824
836
  }
825
837
 
838
+ function forwardAbort(signal, abortController) {
839
+ if (!signal) {
840
+ return () => { };
841
+ }
842
+ if (signal.aborted) {
843
+ abortController.abort(signal.reason);
844
+ return () => { };
845
+ }
846
+ const abort = () => abortController.abort(signal.reason);
847
+ signal.addEventListener("abort", abort, { once: true });
848
+ return () => signal.removeEventListener("abort", abort);
849
+ }
850
+
851
+ function isExportAborted(error) {
852
+ return Boolean(error) && error.message == ERR_ABORT_EXPORT;
853
+ }
854
+
855
+ function aggregateEntryErrors(errors) {
856
+ const [error] = errors;
857
+ const otherErrors = errors
858
+ .slice(1)
859
+ .flatMap(otherError => [otherError, ...(otherError && otherError.entryErrors || [])])
860
+ .filter(otherError => otherError !== error);
861
+ if (otherErrors.length) {
862
+ try {
863
+ error.entryErrors = [...(error.entryErrors || []), ...otherErrors];
864
+ } catch {
865
+ // ignored
866
+ }
867
+ }
868
+ return error;
869
+ }
870
+
826
871
  function getUserExtraField(extraField) {
827
872
  if (extraField) {
828
873
  const userExtraField = new Map();
@@ -840,111 +885,110 @@ function getUserExtraField(extraField) {
840
885
  async function exportZip(zipWriter, entry, totalSize, options, readers) {
841
886
  const selectedEntry = entry;
842
887
  const entryOffsets = new Map();
843
- await process(zipWriter, entry);
888
+ if (options.bufferedWrite) {
889
+ await processChildren(entry);
890
+ } else {
891
+ for (const child of entry.getChildren({ recursive: true })) {
892
+ await addChild(child);
893
+ }
894
+ }
844
895
 
845
- async function process(zipWriter, entry) {
846
- await exportChild();
896
+ async function processChildren(entry) {
897
+ const results = await Promise.allSettled(entry.children.map(async child => {
898
+ await addChild(child);
899
+ await processChildren(child);
900
+ }));
901
+ const errorResult = results.find(result => result.status == "rejected");
902
+ if (errorResult) {
903
+ throw errorResult.reason;
904
+ }
905
+ }
847
906
 
848
- async function exportChild() {
849
- if (options.bufferedWrite) {
850
- const results = await Promise.allSettled(entry.children.map(processChild));
851
- const errorResult = results.find(result => result.status == "rejected");
852
- if (errorResult) {
853
- throw errorResult.reason;
907
+ async function addChild(child) {
908
+ const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
909
+ const childOptions = child.options || {};
910
+ let zipEntryOptions = {};
911
+ if (child.data instanceof Entry) {
912
+ const {
913
+ externalFileAttributes,
914
+ versionMadeBy,
915
+ comment,
916
+ lastModDate,
917
+ creationDate,
918
+ lastAccessDate,
919
+ uncompressedSize,
920
+ encrypted,
921
+ zipCrypto,
922
+ crc32,
923
+ compressionMethod,
924
+ extraFieldAES,
925
+ internalFileAttributes,
926
+ extraField,
927
+ uid,
928
+ gid
929
+ } = child.data;
930
+ zipEntryOptions = {
931
+ externalFileAttributes,
932
+ versionMadeBy,
933
+ comment,
934
+ lastModDate,
935
+ creationDate,
936
+ lastAccessDate,
937
+ internalFileAttributes
938
+ };
939
+ const userExtraField = getUserExtraField(extraField);
940
+ if (userExtraField) {
941
+ zipEntryOptions.extraField = userExtraField;
942
+ }
943
+ if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
944
+ Object.assign(zipEntryOptions, {
945
+ uid,
946
+ gid,
947
+ unixExtraFieldType: options.unixExtraFieldType || INFOZIP_EXTRA_FIELD_TYPE
948
+ });
949
+ }
950
+ if (child.passThrough) {
951
+ let level, encryptionStrength;
952
+ if (compressionMethod === 0) {
953
+ level = 0;
854
954
  }
855
- } else {
856
- for (const child of entry.children) {
857
- await processChild(child);
955
+ if (extraFieldAES) {
956
+ encryptionStrength = extraFieldAES.strength;
858
957
  }
859
- }
860
- }
861
-
862
- async function processChild(child) {
863
- const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
864
- const childOptions = child.options || {};
865
- let zipEntryOptions = {};
866
- if (child.data instanceof Entry) {
867
- const {
868
- externalFileAttributes,
869
- versionMadeBy,
870
- comment,
871
- lastModDate,
872
- creationDate,
873
- lastAccessDate,
874
- uncompressedSize,
958
+ zipEntryOptions = Object.assign(zipEntryOptions, {
959
+ passThrough: true,
875
960
  encrypted,
876
961
  zipCrypto,
877
962
  crc32,
878
- compressionMethod,
879
- extraFieldAES,
880
- internalFileAttributes,
881
- extraField,
882
- uid,
883
- gid
884
- } = child.data;
885
- zipEntryOptions = {
886
- externalFileAttributes,
887
- versionMadeBy,
888
- comment,
889
- lastModDate,
890
- creationDate,
891
- lastAccessDate,
892
- internalFileAttributes
893
- };
894
- const userExtraField = getUserExtraField(extraField);
895
- if (userExtraField) {
896
- zipEntryOptions.extraField = userExtraField;
897
- }
898
- if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
899
- Object.assign(zipEntryOptions, {
900
- uid,
901
- gid,
902
- unixExtraFieldType: options.unixExtraFieldType || INFOZIP_EXTRA_FIELD_TYPE
903
- });
904
- }
905
- if (child.passThrough) {
906
- let level, encryptionStrength;
907
- if (compressionMethod === 0) {
908
- level = 0;
909
- }
910
- if (extraFieldAES) {
911
- encryptionStrength = extraFieldAES.strength;
912
- }
913
- zipEntryOptions = Object.assign(zipEntryOptions, {
914
- passThrough: true,
915
- encrypted,
916
- zipCrypto,
917
- crc32,
918
- uncompressedSize,
919
- level,
920
- encryptionStrength,
921
- compressionMethod
922
- });
923
- }
963
+ uncompressedSize,
964
+ level,
965
+ encryptionStrength,
966
+ compressionMethod
967
+ });
924
968
  }
925
- await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
926
- directory: child.directory,
927
- onprogress: async indexProgress => {
928
- if (options.onprogress) {
929
- entryOffsets.set(name, indexProgress);
930
- try {
931
- await options.onprogress(Array.from(entryOffsets.values()).reduce((previousValue, currentValue) => previousValue + currentValue), totalSize);
932
- } catch {
933
- // ignored
934
- }
969
+ }
970
+ await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
971
+ directory: child.directory,
972
+ onprogress: async indexProgress => {
973
+ if (options.onprogress) {
974
+ entryOffsets.set(name, indexProgress);
975
+ try {
976
+ await options.onprogress(Array.from(entryOffsets.values()).reduce((previousValue, currentValue) => previousValue + currentValue), totalSize);
977
+ } catch {
978
+ // ignored
935
979
  }
936
980
  }
937
- }));
938
- await process(zipWriter, child);
939
- }
981
+ }
982
+ }));
940
983
  }
941
984
  }
942
985
 
943
986
  function addFileSystemHandle(zipEntry, handle, options) {
944
987
  return addFile(zipEntry, handle, []);
945
988
 
946
- async function addFile(parentEntry, handle, addedEntries) {
989
+ async function addFile(parentEntry, handle, addedEntries, parentName = "") {
947
990
  if (handle) {
991
+ const entryName = parentName ? parentName + "/" + handle.name : handle.name;
948
992
  try {
949
993
  if (handle.isFile || handle.isDirectory) {
950
994
  handle = await transformToFileSystemhandle(handle);
@@ -966,12 +1010,18 @@ function addFileSystemHandle(zipEntry, handle, options) {
966
1010
  const directoryEntry = parentEntry.addDirectory(handle.name);
967
1011
  addedEntries.push(directoryEntry);
968
1012
  for await (const childHandle of handle.values()) {
969
- await addFile(directoryEntry, childHandle, addedEntries);
1013
+ await addFile(directoryEntry, childHandle, addedEntries, entryName);
970
1014
  }
971
1015
  }
972
1016
  } catch (error) {
973
- const message = error.message + (handle ? " (" + handle.name + ")" : "");
974
- throw new Error(message, { cause: error });
1017
+ try {
1018
+ if (error.entryName === UNDEFINED_VALUE) {
1019
+ error.entryName = entryName;
1020
+ }
1021
+ } catch {
1022
+ // ignored
1023
+ }
1024
+ throw error;
975
1025
  }
976
1026
  }
977
1027
  return addedEntries;
@@ -981,15 +1031,33 @@ function addFileSystemHandle(zipEntry, handle, options) {
981
1031
  async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
982
1032
  const totalSize = getTotalSize([zipEntry], "uncompressedSize");
983
1033
  const writtenSizes = new Map();
984
- await exportChildren(zipEntry, directoryHandle);
1034
+ const abortController = new AbortController();
1035
+ const { signal } = abortController;
1036
+ const releaseSignal = forwardAbort(options.signal, abortController);
1037
+ const exportedEntryNames = [];
1038
+ let exportAborted = false;
1039
+ try {
1040
+ await exportChildren(zipEntry, directoryHandle);
1041
+ } catch (error) {
1042
+ try {
1043
+ error.exportedEntryNames = exportedEntryNames;
1044
+ } catch {
1045
+ // ignored
1046
+ }
1047
+ throw error;
1048
+ } finally {
1049
+ releaseSignal();
1050
+ }
985
1051
  return directoryHandle;
986
1052
 
987
1053
  async function exportChildren(entry, parentHandle) {
988
1054
  if (options.concurrent) {
989
1055
  const results = await Promise.allSettled(entry.children.map(child => exportChild(child, parentHandle)));
990
- const failedResult = results.find(result => result.status == "rejected");
991
- if (failedResult) {
992
- throw failedResult.reason;
1056
+ const rejectedResults = results.filter(result => result.status == "rejected");
1057
+ if (rejectedResults.length) {
1058
+ const failedResults = rejectedResults.filter(result => !isExportAborted(result.reason));
1059
+ const reportedResults = failedResults.length ? failedResults : rejectedResults;
1060
+ throw aggregateEntryErrors(reportedResults.map(result => result.reason));
993
1061
  }
994
1062
  } else {
995
1063
  for (const child of entry.children) {
@@ -999,6 +1067,12 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
999
1067
  }
1000
1068
 
1001
1069
  async function exportChild(child, parentHandle) {
1070
+ if (signal.aborted) {
1071
+ if (exportAborted || isExportAborted(signal.reason)) {
1072
+ return;
1073
+ }
1074
+ throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME) : signal.reason;
1075
+ }
1002
1076
  try {
1003
1077
  if (child.directory) {
1004
1078
  const childDirectoryHandle = await parentHandle.getDirectoryHandle(child.name, { create: true });
@@ -1006,21 +1080,37 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1006
1080
  } else {
1007
1081
  const fileHandle = await parentHandle.getFileHandle(child.name, { create: true });
1008
1082
  const writable = await fileHandle.createWritable();
1009
- await child.getData({ writable }, Object.assign({}, options, {
1010
- onprogress: async progress => {
1011
- if (options.onprogress) {
1012
- writtenSizes.set(child.id, progress);
1013
- try {
1014
- await options.onprogress(Array.from(writtenSizes.values()).reduce((previousValue, currentValue) => previousValue + currentValue, 0), totalSize);
1015
- } catch {
1016
- // ignored
1083
+ try {
1084
+ await child.getData({ writable }, Object.assign({}, options, {
1085
+ signal,
1086
+ onprogress: async progress => {
1087
+ if (options.onprogress) {
1088
+ writtenSizes.set(child.id, progress);
1089
+ try {
1090
+ await options.onprogress(Array.from(writtenSizes.values()).reduce((previousValue, currentValue) => previousValue + currentValue, 0), totalSize);
1091
+ } catch {
1092
+ // ignored
1093
+ }
1017
1094
  }
1018
1095
  }
1019
- }
1020
- }));
1096
+ }));
1097
+ } catch (error) {
1098
+ throw exportAborted ? new Error(ERR_ABORT_EXPORT) : error;
1099
+ }
1100
+ exportedEntryNames.push(child.getRelativeName(zipEntry));
1021
1101
  }
1022
1102
  } catch (error) {
1023
- throw new Error(error.message + (child ? " (" + child.name + ")" : ""), { cause: error });
1103
+ exportAborted = true;
1104
+ abortController.abort(new Error(ERR_ABORT_EXPORT));
1105
+ try {
1106
+ if (error.entryName === UNDEFINED_VALUE) {
1107
+ error.entryName = child.getRelativeName(zipEntry);
1108
+ error.entryId = child.id;
1109
+ }
1110
+ } catch {
1111
+ // ignored
1112
+ }
1113
+ throw error;
1024
1114
  }
1025
1115
  }
1026
1116
  }
@@ -1082,6 +1172,21 @@ function resetFS(fs) {
1082
1172
  fs.root = new ZipDirectoryEntry(fs);
1083
1173
  }
1084
1174
 
1175
+ function collectChildren(directory, recursive) {
1176
+ const children = [];
1177
+ const pendingDirectories = [directory];
1178
+ let directoryIndex = 0;
1179
+ while (directoryIndex < pendingDirectories.length) {
1180
+ for (const child of pendingDirectories[directoryIndex++].children) {
1181
+ children.push(child);
1182
+ if (recursive) {
1183
+ pendingDirectories.push(child);
1184
+ }
1185
+ }
1186
+ }
1187
+ return children;
1188
+ }
1189
+
1085
1190
  function registerEntries(fs, entry) {
1086
1191
  const pendingEntries = [entry];
1087
1192
  while (pendingEntries.length) {
@@ -88,6 +88,8 @@ import {
88
88
  FILE_ATTR_UNIX_SETUID_MASK,
89
89
  FILE_ATTR_UNIX_SETGID_MASK,
90
90
  FILE_ATTR_UNIX_STICKY_MASK,
91
+ NUMBER_TYPE,
92
+ STRING_TYPE,
91
93
  EMPTY_UINT8_ARRAY
92
94
  } from "./constants.js";
93
95
  import { getConfiguration } from "./configuration.js";
@@ -150,8 +152,12 @@ import {
150
152
  OPTION_CHECK_CRC32,
151
153
  OPTION_CHECK_AUTHENTICATION_CODE,
152
154
  OPTION_STRICTNESS,
155
+ OPTION_FILENAME_VALIDATION,
156
+ OPTION_NORMALIZE_FILENAME,
153
157
  OPTION_MAX_APPENDED_DATA_SIZE,
154
158
  OPTION_DECRYPT_CENTRAL_DIRECTORY,
159
+ TEXT_TYPE_FILENAME,
160
+ TEXT_TYPE_COMMENT,
155
161
  STRICTNESS_STRICT,
156
162
  STRICTNESS_BALANCED,
157
163
  STRICTNESS_TOLERANT,
@@ -174,6 +180,11 @@ const ERR_SPLIT_ZIP_FILE = "Split zip file";
174
180
  const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
175
181
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
176
182
  const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
183
+ const ERR_UNSAFE_FILENAME = "Unsafe filename";
184
+ const ERR_INVALID_STRICTNESS = "Invalid strictness (must be 'strict', 'balanced' or 'tolerant')";
185
+ const ERR_INVALID_FILENAME_VALIDATION = "Invalid filenameValidation (must be 'strict', 'balanced' or 'tolerant')";
186
+ const ERR_INVALID_MAX_APPENDED_DATA_SIZE = "Invalid maxAppendedDataSize (must be a number greater than or equal to 0)";
187
+ const DRIVE_LETTER_REGEXP = /^[a-zA-Z]:/;
177
188
  const CHARSET_UTF8 = "utf-8";
178
189
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
179
190
  const CHARSET_CP437 = "cp437";
@@ -195,6 +206,10 @@ const ZIP64_EXTRACTION = {
195
206
  bytes: 8
196
207
  }
197
208
  };
209
+ const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
210
+ const CENTRAL_DIRECTORY_UNREACHABLE = 0;
211
+ const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
212
+ const CENTRAL_DIRECTORY_REACHABLE = 2;
198
213
 
199
214
  class ZipReader {
200
215
 
@@ -223,6 +238,8 @@ class ZipReader {
223
238
  const checkAmbiguity = strictness == STRICTNESS_STRICT;
224
239
  const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
225
240
  const maxAppendedDataSize = getMaxAppendedDataSize(getOptionValue(zipReader, options, OPTION_MAX_APPENDED_DATA_SIZE), strictness);
241
+ const filenameValidation = getFilenameValidation(getOptionValue(zipReader, options, OPTION_FILENAME_VALIDATION), strictness);
242
+ const normalizeFilename = getOptionValue(zipReader, options, OPTION_NORMALIZE_FILENAME);
226
243
  const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
227
244
  if (!endOfDirectoryInfo) {
228
245
  const signatureArray = await readUint8Array(reader, 0, 4);
@@ -416,11 +433,22 @@ class ZipReader {
416
433
  const decode = getOptionValue(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
417
434
  const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
418
435
  const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
419
- let filename = decode(rawFilename, rawFilenameEncoding);
436
+ let filename = decode(rawFilename, rawFilenameEncoding, TEXT_TYPE_FILENAME);
420
437
  if (filename === UNDEFINED_VALUE) {
421
438
  filename = decodeText(rawFilename, rawFilenameEncoding);
422
439
  }
423
- let comment = decode(rawComment, rawCommentEncoding);
440
+ if (normalizeFilename) {
441
+ const normalizedFilename = normalizeFilename(filename);
442
+ if (normalizedFilename !== UNDEFINED_VALUE) {
443
+ filename = normalizedFilename;
444
+ }
445
+ }
446
+ if (isUnsafeFilename(filename, filenameValidation)) {
447
+ const error = new Error(ERR_UNSAFE_FILENAME);
448
+ error.filename = filename;
449
+ throw error;
450
+ }
451
+ let comment = decode(rawComment, rawCommentEncoding, TEXT_TYPE_COMMENT);
424
452
  if (comment === UNDEFINED_VALUE) {
425
453
  comment = decodeText(rawComment, rawCommentEncoding);
426
454
  }
@@ -481,10 +509,10 @@ class ZipReader {
481
509
  entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
482
510
  entry.arrayBuffer = async options => {
483
511
  const writer = new TransformStream();
484
- const [arrayBuffer] = await Promise.all([
485
- streamToBlob(writer.readable).then(blob => blob.arrayBuffer()),
486
- fileEntry.getData(writer, entry, zipReader.readRanges, options)]);
487
- return arrayBuffer;
512
+ const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
513
+ arrayBufferPromise.catch(() => { });
514
+ await fileEntry.getData(writer, entry, zipReader.readRanges, options);
515
+ return arrayBufferPromise;
488
516
  };
489
517
  offset = endOffset;
490
518
  const { onprogress } = options;
@@ -600,6 +628,10 @@ export {
600
628
  ERR_OVERLAPPING_ENTRY,
601
629
  ERR_AMBIGUOUS_ARCHIVE,
602
630
  ERR_ENCRYPTED_CENTRAL_DIRECTORY,
631
+ ERR_UNSAFE_FILENAME,
632
+ ERR_INVALID_STRICTNESS,
633
+ ERR_INVALID_FILENAME_VALIDATION,
634
+ ERR_INVALID_MAX_APPENDED_DATA_SIZE,
603
635
  ERR_WORKER_STARTUP_TIMEOUT
604
636
  };
605
637
 
@@ -760,7 +792,7 @@ class ZipEntry {
760
792
  readRanges
761
793
  });
762
794
  }
763
- let writable;
795
+ let writable, abortError;
764
796
  try {
765
797
  if (!checkOverlappingEntryOnly) {
766
798
  if (checkPasswordOnly) {
@@ -780,12 +812,22 @@ class ZipEntry {
780
812
  writer.size += error.outputSize;
781
813
  }
782
814
  if (!checkPasswordOnly || error.message != ERR_ABORT_CHECK_PASSWORD) {
815
+ abortError = error;
783
816
  throw error;
784
817
  }
785
818
  } finally {
786
819
  const preventClose = getOptionValue(zipEntry, options, OPTION_PREVENT_CLOSE);
787
820
  if (!preventClose && writable && !writable.locked) {
788
- await writable.getWriter().close();
821
+ const writableWriter = writable.getWriter();
822
+ if (abortError) {
823
+ try {
824
+ await writableWriter.abort(abortError);
825
+ } catch {
826
+ // the error being propagated is more relevant; ignored
827
+ }
828
+ } else {
829
+ await writableWriter.close();
830
+ }
789
831
  }
790
832
  }
791
833
  return checkPasswordOnly || checkOverlappingEntryOnly ? UNDEFINED_VALUE : writer.getData ? writer.getData() : writable;
@@ -1165,16 +1207,51 @@ function getDiskOffset(reader, diskNumber) {
1165
1207
  return reader.getDiskOffset ? reader.getDiskOffset(diskNumber) : 0;
1166
1208
  }
1167
1209
 
1210
+ function isStrictnessValue(value) {
1211
+ return value === STRICTNESS_STRICT || value === STRICTNESS_BALANCED || value === STRICTNESS_TOLERANT;
1212
+ }
1213
+
1168
1214
  function getStrictness(strictness, checkAmbiguity) {
1169
1215
  if (strictness === UNDEFINED_VALUE) {
1170
1216
  return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
1171
1217
  }
1218
+ if (!isStrictnessValue(strictness)) {
1219
+ throw new Error(ERR_INVALID_STRICTNESS);
1220
+ }
1172
1221
  return strictness;
1173
1222
  }
1174
1223
 
1224
+ function getFilenameValidation(filenameValidation, strictness) {
1225
+ if (filenameValidation === UNDEFINED_VALUE) {
1226
+ return strictness;
1227
+ }
1228
+ if (!isStrictnessValue(filenameValidation)) {
1229
+ throw new Error(ERR_INVALID_FILENAME_VALIDATION);
1230
+ }
1231
+ return filenameValidation;
1232
+ }
1233
+
1234
+ function isUnsafeFilename(filename, filenameValidation) {
1235
+ if (filenameValidation == STRICTNESS_TOLERANT) {
1236
+ return false;
1237
+ }
1238
+ const pathParts = filename.split("/");
1239
+ if (pathParts.length > 1 && pathParts[pathParts.length - 1] === "") {
1240
+ pathParts.pop();
1241
+ }
1242
+ if (pathParts.includes("..") || filename.startsWith("/") || filename.startsWith("\\\\") || DRIVE_LETTER_REGEXP.test(filename)) {
1243
+ return true;
1244
+ }
1245
+ return filenameValidation == STRICTNESS_STRICT && (pathParts.includes(".") || pathParts.includes(""));
1246
+ }
1247
+
1175
1248
  function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
1176
1249
  if (maxAppendedDataSize !== UNDEFINED_VALUE) {
1177
- return maxAppendedDataSize;
1250
+ const size = toNumber(maxAppendedDataSize);
1251
+ if (typeof size != NUMBER_TYPE || Number.isNaN(size) || size < 0) {
1252
+ throw new Error(ERR_INVALID_MAX_APPENDED_DATA_SIZE);
1253
+ }
1254
+ return size;
1178
1255
  }
1179
1256
  if (strictness == STRICTNESS_STRICT) {
1180
1257
  return 0;
@@ -1185,12 +1262,6 @@ function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
1185
1262
  return MAX_16_BITS;
1186
1263
  }
1187
1264
 
1188
- const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
1189
-
1190
- const CENTRAL_DIRECTORY_UNREACHABLE = 0;
1191
- const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
1192
- const CENTRAL_DIRECTORY_REACHABLE = 2;
1193
-
1194
1265
  async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
1195
1266
  const { size } = reader;
1196
1267
  const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
@@ -1326,6 +1397,10 @@ function getOptionValue(zipReader, options, name) {
1326
1397
  return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
1327
1398
  }
1328
1399
 
1400
+ function toNumber(value) {
1401
+ return typeof value == STRING_TYPE && value.trim() ? Number(value) : value;
1402
+ }
1403
+
1329
1404
  function getDate(timeRaw) {
1330
1405
  const date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & MAX_16_BITS;
1331
1406
  return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
@@ -1350,4 +1425,3 @@ function getUint32(view, offset) {
1350
1425
  function getBigUint64(view, offset) {
1351
1426
  return Number(view.getBigUint64(offset, true));
1352
1427
  }
1353
-