@zip.js/zip.js 2.8.43 → 2.8.45

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/README.md +0 -37
  2. package/deno.json +21 -1
  3. package/dist/zip-core-external.js +194 -159
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +194 -131
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +246 -167
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +244 -139
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +267 -1761
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +269 -1735
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +268 -1734
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +196 -133
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-native.js +196 -133
  20. package/dist/zip-native.min.js +1 -1
  21. package/dist/zip-web-worker-native.js +1 -1
  22. package/dist/zip-web-worker.js +1 -1
  23. package/dist/zip.js +195 -132
  24. package/dist/zip.min.js +1 -1
  25. package/eslint.config.mjs +31 -2
  26. package/index-native.cjs +269 -1735
  27. package/index-native.min.js +1 -1
  28. package/index.cjs +268 -1734
  29. package/index.d.cts +129 -8
  30. package/index.d.ts +129 -8
  31. package/index.min.js +1 -1
  32. package/lib/core/codec-pool.js +6 -2
  33. package/lib/core/codec-worker.js +2 -2
  34. package/lib/core/constants.js +4 -0
  35. package/lib/core/options.js +6 -0
  36. package/lib/core/streams/aes-crypto-stream.js +18 -21
  37. package/lib/core/streams/codec-stream.js +6 -2
  38. package/lib/core/streams/common-crypto.js +2 -0
  39. package/lib/core/streams/zip-entry-stream.js +16 -17
  40. package/lib/core/util/mime-type-data.js +3 -0
  41. package/lib/core/util/mime-type.js +23 -1596
  42. package/lib/core/web-worker-base.js +2 -2
  43. package/lib/core/web-worker-inline-native.js +1 -1
  44. package/lib/core/web-worker-inline-wasm.js +1 -1
  45. package/lib/core/web-worker-wasm.js +4 -8
  46. package/lib/core/zip-entry.js +11 -1
  47. package/lib/core/zip-fs.js +62 -5
  48. package/lib/core/zip-reader.js +80 -43
  49. package/lib/core/zip-writer.js +68 -45
  50. package/lib/zip-core-external.js +2 -0
  51. package/lib/zip-core-reader.js +2 -0
  52. package/lib/zip-fs-core-external.js +2 -0
  53. package/lib/zip-fs-external.js +2 -0
  54. package/lib/zip-mime-types.js +2 -0
  55. package/package.json +9 -3
  56. package/worker-message-property-names.js +1 -1
@@ -57,6 +57,8 @@ import {
57
57
  EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP,
58
58
  EXTRAFIELD_TYPE_INFOZIP,
59
59
  EXTRAFIELD_TYPE_UNIX,
60
+ EXTRAFIELD_TYPE_UNIX_TYPE1,
61
+ EXTRAFIELD_TYPE_PKWARE_UNIX,
60
62
  EXTRAFIELD_TYPE_USDZ,
61
63
  END_OF_CENTRAL_DIR_LENGTH,
62
64
  ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH,
@@ -93,6 +95,8 @@ import {
93
95
  runWorker,
94
96
  CODEC_INFLATE,
95
97
  ERR_INVALID_SIGNATURE,
98
+ ERR_INVALID_CRC32,
99
+ ERR_INVALID_AUTHENTICATION_CODE,
96
100
  ERR_INVALID_PASSWORD,
97
101
  ERR_INVALID_UNCOMPRESSED_SIZE,
98
102
  ERR_INVALID_COMPRESSED_DATA,
@@ -141,6 +145,8 @@ import {
141
145
  OPTION_CHECK_OVERLAPPING_ENTRY,
142
146
  OPTION_CHECK_AMBIGUITY,
143
147
  OPTION_CHECK_SIGNATURE,
148
+ OPTION_CHECK_CRC32,
149
+ OPTION_CHECK_AUTHENTICATION_CODE,
144
150
  OPTION_STRICTNESS,
145
151
  OPTION_MAX_APPENDED_DATA_SIZE,
146
152
  STRICTNESS_STRICT,
@@ -169,6 +175,7 @@ const CHARSET_UTF8 = "utf-8";
169
175
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
170
176
  const CHARSET_CP437 = "cp437";
171
177
  const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
178
+ const VENDOR_VERSION_AE_1 = 1;
172
179
  const ZIP64_PROPERTIES = [
173
180
  [PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
174
181
  [PROPERTY_NAME_COMPRESSED_SIZE, MAX_32_BITS],
@@ -551,6 +558,8 @@ export {
551
558
  ERR_UNSUPPORTED_ENCRYPTION,
552
559
  ERR_UNSUPPORTED_COMPRESSION,
553
560
  ERR_INVALID_SIGNATURE,
561
+ ERR_INVALID_CRC32,
562
+ ERR_INVALID_AUTHENTICATION_CODE,
554
563
  ERR_INVALID_PASSWORD,
555
564
  ERR_INVALID_UNCOMPRESSED_SIZE,
556
565
  ERR_INVALID_COMPRESSED_DATA,
@@ -583,7 +592,7 @@ class ZipEntry {
583
592
  config,
584
593
  bitFlag,
585
594
  rawBitFlag,
586
- signature,
595
+ crc32,
587
596
  rawLastModDate,
588
597
  uncompressedSize,
589
598
  compressedSize
@@ -670,6 +679,11 @@ class ZipEntry {
670
679
  if (deflate64) {
671
680
  useCompressionStream = false;
672
681
  }
682
+ const checkCrc32Option = getOptionValue(zipEntry, options, OPTION_CHECK_CRC32);
683
+ const checkCrc32 = (checkCrc32Option === UNDEFINED_VALUE ?
684
+ getOptionValue(zipEntry, options, OPTION_CHECK_SIGNATURE) :
685
+ checkCrc32Option) && !passThrough &&
686
+ (!encrypted || zipCrypto || (extraFieldAES && extraFieldAES.vendorVersion == VENDOR_VERSION_AE_1));
673
687
  const workerOptions = {
674
688
  options: {
675
689
  codecType: CODEC_INFLATE,
@@ -677,10 +691,11 @@ class ZipEntry {
677
691
  rawPassword,
678
692
  zipCrypto,
679
693
  encryptionStrength: extraFieldAES && extraFieldAES.strength,
680
- signed: getOptionValue(zipEntry, options, OPTION_CHECK_SIGNATURE) && !passThrough,
681
- passwordVerification: zipCrypto && (dataDescriptor ? ((rawLastModDate >>> 8) & MAX_8_BITS) : ((signature >>> 24) & MAX_8_BITS)),
694
+ checkCrc32,
695
+ checkAuthenticationCode: getOptionValue(zipEntry, options, OPTION_CHECK_AUTHENTICATION_CODE),
696
+ passwordVerification: zipCrypto && (dataDescriptor ? ((rawLastModDate >>> 8) & MAX_8_BITS) : ((crc32 >>> 24) & MAX_8_BITS)),
682
697
  outputSize: passThrough ? compressedSize : uncompressedSize,
683
- signature,
698
+ crc32,
684
699
  compressed: compressionMethod != 0 && !passThrough,
685
700
  encrypted,
686
701
  useWebWorkers: getOptionValue(zipEntry, options, OPTION_USE_WEB_WORKERS),
@@ -702,7 +717,7 @@ class ZipEntry {
702
717
  fileEntry,
703
718
  index,
704
719
  offset: localHeaderOffset,
705
- signature,
720
+ crc32,
706
721
  compressedSize,
707
722
  uncompressedSize,
708
723
  dataOffset,
@@ -793,6 +808,7 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
793
808
  const compressionMethod = getUint16(dataView, offset + 4);
794
809
  Object.assign(directory, {
795
810
  signature: getUint32(dataView, offset + HEADER_OFFSET_SIGNATURE),
811
+ crc32: getUint32(dataView, offset + HEADER_OFFSET_SIGNATURE),
796
812
  compressedSize: getUint32(dataView, offset + HEADER_OFFSET_COMPRESSED_SIZE),
797
813
  uncompressedSize: getUint32(dataView, offset + HEADER_OFFSET_UNCOMPRESSED_SIZE)
798
814
  });
@@ -818,6 +834,16 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
818
834
  } else {
819
835
  directory.compressionMethod = compressionMethod;
820
836
  }
837
+ const extraFieldPkwareUnix = extraField.get(EXTRAFIELD_TYPE_PKWARE_UNIX);
838
+ if (extraFieldPkwareUnix) {
839
+ readExtraFieldUnixDates(extraFieldPkwareUnix, directory);
840
+ directory.extraFieldPkwareUnix = extraFieldPkwareUnix;
841
+ }
842
+ const extraFieldUnixType1 = extraField.get(EXTRAFIELD_TYPE_UNIX_TYPE1);
843
+ if (extraFieldUnixType1) {
844
+ readExtraFieldUnixDates(extraFieldUnixType1, directory);
845
+ directory.extraFieldUnixType1 = extraFieldUnixType1;
846
+ }
821
847
  const extraFieldNTFS = extraField.get(EXTRAFIELD_TYPE_NTFS);
822
848
  if (extraFieldNTFS) {
823
849
  readExtraFieldNTFS(extraFieldNTFS, directory);
@@ -867,15 +893,15 @@ function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName,
867
893
  return;
868
894
  }
869
895
  const extraFieldView = getDataView(extraFieldUnicode.data);
870
- const crc32 = new Crc32();
871
- crc32.append(fileEntry[rawPropertyName]);
872
- const dataViewSignature = getDataView(new Uint8Array(4));
873
- dataViewSignature.setUint32(0, crc32.get(), true);
874
- const signature = getUint32(extraFieldView, 1);
896
+ const computedCrc32 = new Crc32();
897
+ computedCrc32.append(fileEntry[rawPropertyName]);
898
+ const computedCrc32View = getDataView(new Uint8Array(4));
899
+ computedCrc32View.setUint32(0, computedCrc32.get(), true);
900
+ const nameCrc32 = getUint32(extraFieldView, 1);
875
901
  Object.assign(extraFieldUnicode, {
876
902
  version: getUint8(extraFieldView, 0),
877
903
  [propertyName]: decodeText(extraFieldUnicode.data.subarray(5)),
878
- valid: !fileEntry.bitFlag.languageEncodingFlag && signature == getUint32(dataViewSignature, 0)
904
+ valid: !fileEntry.bitFlag.languageEncodingFlag && nameCrc32 == getUint32(computedCrc32View, 0)
879
905
  });
880
906
  if (extraFieldUnicode.valid) {
881
907
  directory[propertyName] = extraFieldUnicode[propertyName];
@@ -894,6 +920,9 @@ function readExtraFieldAES(extraFieldAES, directory, compressionMethod) {
894
920
  compressionMethod: getUint16(extraFieldView, 5)
895
921
  });
896
922
  directory.compressionMethod = extraFieldAES.compressionMethod;
923
+ if (extraFieldAES.vendorVersion != VENDOR_VERSION_AE_1) {
924
+ directory.crc32 = UNDEFINED_VALUE;
925
+ }
897
926
  }
898
927
 
899
928
  function readExtraFieldNTFS(extraFieldNTFS, directory) {
@@ -912,29 +941,41 @@ function readExtraFieldNTFS(extraFieldNTFS, directory) {
912
941
  } catch {
913
942
  // ignored
914
943
  }
915
- try {
916
- if (tag1Data && tag1Data.length == 24) {
917
- const tag1View = getDataView(tag1Data);
918
- const rawLastModDate = tag1View.getBigUint64(0, true);
919
- const rawLastAccessDate = tag1View.getBigUint64(8, true);
920
- const rawCreationDate = tag1View.getBigUint64(16, true);
921
- Object.assign(extraFieldNTFS, {
922
- rawLastModDate,
923
- rawLastAccessDate,
924
- rawCreationDate
925
- });
926
- const lastModDate = getDateNTFS(rawLastModDate);
927
- const lastAccessDate = getDateNTFS(rawLastAccessDate);
928
- const creationDate = getDateNTFS(rawCreationDate);
929
- const extraFieldData = { lastModDate, lastAccessDate, creationDate };
930
- Object.assign(extraFieldNTFS, extraFieldData);
931
- Object.assign(directory, extraFieldData);
932
- }
933
- } catch {
934
- // ignored
944
+ if (tag1Data && tag1Data.length == 24) {
945
+ const tag1View = getDataView(tag1Data);
946
+ const rawLastModDate = tag1View.getBigUint64(0, true);
947
+ const rawLastAccessDate = tag1View.getBigUint64(8, true);
948
+ const rawCreationDate = tag1View.getBigUint64(16, true);
949
+ Object.assign(extraFieldNTFS, {
950
+ rawLastModDate,
951
+ rawLastAccessDate,
952
+ rawCreationDate
953
+ });
954
+ const lastModDate = getDateNTFS(rawLastModDate);
955
+ const lastAccessDate = getDateNTFS(rawLastAccessDate);
956
+ const creationDate = getDateNTFS(rawCreationDate);
957
+ const extraFieldData = { lastModDate, lastAccessDate, creationDate };
958
+ Object.assign(extraFieldNTFS, extraFieldData);
959
+ Object.assign(directory, extraFieldData);
935
960
  }
936
961
  }
937
962
 
963
+ function readExtraFieldUnixDates(extraField, directory) {
964
+ if (extraField.data.length < 8) {
965
+ return;
966
+ }
967
+ const extraFieldView = getDataView(extraField.data);
968
+ const lastAccessDate = new Date(getUint32(extraFieldView, 0) * 1000);
969
+ const lastModDate = new Date(getUint32(extraFieldView, 4) * 1000);
970
+ const extraFieldData = { lastAccessDate, lastModDate };
971
+ if (extraField.data.length >= 12) {
972
+ extraFieldData.uid = getUint16(extraFieldView, 8);
973
+ extraFieldData.gid = getUint16(extraFieldView, 10);
974
+ }
975
+ Object.assign(extraField, extraFieldData);
976
+ Object.assign(directory, extraFieldData);
977
+ }
978
+
938
979
  function readExtraFieldUnix(extraField, directory, isInfoZip) {
939
980
  try {
940
981
  const view = getDataView(extraField.data);
@@ -1013,7 +1054,7 @@ async function detectOverlappingEntry({
1013
1054
  fileEntry,
1014
1055
  index,
1015
1056
  offset,
1016
- signature,
1057
+ crc32,
1017
1058
  compressedSize,
1018
1059
  uncompressedSize,
1019
1060
  dataOffset,
@@ -1034,7 +1075,7 @@ async function detectOverlappingEntry({
1034
1075
  const dataDescriptorSignature = dataDescriptorArray.length == dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH &&
1035
1076
  getUint32(getDataView(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
1036
1077
  if (dataDescriptorSignature) {
1037
- const readSignature = getUint32(getDataView(dataDescriptorArray), 4);
1078
+ const readCrc32 = getUint32(getDataView(dataDescriptorArray), 4);
1038
1079
  let readCompressedSize;
1039
1080
  let readUncompressedSize;
1040
1081
  if (extraFieldZip64) {
@@ -1044,8 +1085,8 @@ async function detectOverlappingEntry({
1044
1085
  readCompressedSize = getUint32(getDataView(dataDescriptorArray), 8);
1045
1086
  readUncompressedSize = getUint32(getDataView(dataDescriptorArray), 12);
1046
1087
  }
1047
- const matchSignature = (fileEntry.encrypted && !fileEntry.zipCrypto) || readSignature == signature;
1048
- if (matchSignature &&
1088
+ const matchCrc32 = (fileEntry.encrypted && !fileEntry.zipCrypto) || readCrc32 == crc32;
1089
+ if (matchCrc32 &&
1049
1090
  readCompressedSize == compressedSize &&
1050
1091
  readUncompressedSize == uncompressedSize) {
1051
1092
  dataDescriptorLength += DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH;
@@ -1214,11 +1255,11 @@ function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
1214
1255
  throwAmbiguousArchive("mismatched local file header (compression method)");
1215
1256
  }
1216
1257
  if (!localDirectory.bitFlag.dataDescriptor &&
1217
- (localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
1218
- (localDirectory.signature != zipEntry.signature ||
1258
+ (localDirectory.crc32 || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
1259
+ (localDirectory.crc32 != zipEntry.crc32 ||
1219
1260
  localDirectory.compressedSize != zipEntry.compressedSize ||
1220
1261
  localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
1221
- throwAmbiguousArchive("mismatched local file header (signature or sizes)");
1262
+ throwAmbiguousArchive("mismatched local file header (crc32 or sizes)");
1222
1263
  }
1223
1264
  }
1224
1265
 
@@ -1234,11 +1275,7 @@ function getOptionValue(zipReader, options, name) {
1234
1275
 
1235
1276
  function getDate(timeRaw) {
1236
1277
  const date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & MAX_16_BITS;
1237
- try {
1238
- return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
1239
- } catch {
1240
- // ignored
1241
- }
1278
+ return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
1242
1279
  }
1243
1280
 
1244
1281
  function getDateNTFS(timeRaw) {
@@ -208,7 +208,8 @@ import {
208
208
  OPTION_ENCODE_TEXT,
209
209
  OPTION_OFFSET,
210
210
  OPTION_USDZ,
211
- OPTION_UNIX_EXTRA_FIELD_TYPE
211
+ OPTION_UNIX_EXTRA_FIELD_TYPE,
212
+ OPTION_LOCAL_EXTRA_FIELD
212
213
  } from "./options.js";
213
214
 
214
215
  const ERR_DUPLICATED_NAME = "File already exists";
@@ -223,6 +224,8 @@ const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
223
224
  const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
224
225
  const MIN_UNIX_TIME = -2147483648;
225
226
  const MAX_UNIX_TIME = 2147483647;
227
+ const MIN_NTFS_TIME = BigInt(0);
228
+ const MAX_NTFS_TIME = BigInt("0x7fffffffffffffff");
226
229
  const ERR_UNSUPPORTED_FORMAT = "Zip64 is not supported (set the 'zip64' option to 'true')";
227
230
  const ERR_UNDEFINED_UNCOMPRESSED_SIZE = "Undefined uncompressed size";
228
231
  const ERR_UNDEFINED_READER = "Undefined reader";
@@ -236,6 +239,8 @@ const ERR_INVALID_MSDOS_ATTRIBUTES = "Invalid msdosAttributesRaw (must be intege
236
239
  const ERR_INVALID_MSDOS_DATA = "Invalid msdosAttributes (must be an object with boolean flags)";
237
240
 
238
241
  const EXTRAFIELD_DATA_AES = new Uint8Array([0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00, 0x00]);
242
+ const EXTRAFIELD_OFFSET_AES_VENDOR_VERSION = 4;
243
+ const VENDOR_VERSION_AE_1 = 1;
239
244
  const INFOZIP_EXTRA_FIELD_TYPE = "infozip";
240
245
  const UNIX_EXTRA_FIELD_TYPE = "unix";
241
246
 
@@ -330,9 +335,9 @@ class ZipWriter {
330
335
  zip64UncompressedSize,
331
336
  extraFieldLength
332
337
  });
333
- const { signature } = entry;
334
- if (signature !== UNDEFINED_VALUE) {
335
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, signature);
338
+ const { crc32 } = entry;
339
+ if (crc32 !== UNDEFINED_VALUE) {
340
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
336
341
  }
337
342
  Object.assign(entry, {
338
343
  zip64UncompressedSize,
@@ -535,6 +540,7 @@ async function addFile(zipWriter, name, reader, options) {
535
540
  internalFileAttribute: metadataInfo.resolvedOptions.internalFileAttributes,
536
541
  externalFileAttribute: attributesInfo.resolvedOptions.externalFileAttributes,
537
542
  signature: options[PROPERTY_NAME_SIGNATURE],
543
+ crc32: options.crc32 === UNDEFINED_VALUE ? options[PROPERTY_NAME_SIGNATURE] : options.crc32,
538
544
  offset: zipWriter.offset - diskOffset,
539
545
  diskNumberStart: diskNumber
540
546
  });
@@ -751,27 +757,8 @@ function resolveMetadata(zipWriter, name, options) {
751
757
  if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
752
758
  throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
753
759
  }
754
- let rawExtraField = EMPTY_UINT8_ARRAY;
755
- const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
756
- if (extraField) {
757
- let extraFieldSize = 0;
758
- let offset = 0;
759
- extraField.forEach(data => extraFieldSize += 4 + getLength(data));
760
- rawExtraField = new Uint8Array(extraFieldSize);
761
- const rawExtraFieldView = getDataView(rawExtraField);
762
- extraField.forEach((data, type) => {
763
- if (type > MAX_16_BITS) {
764
- throw new Error(ERR_INVALID_EXTRAFIELD_TYPE);
765
- }
766
- if (getLength(data) > MAX_16_BITS) {
767
- throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
768
- }
769
- setUint16(rawExtraFieldView, offset, type);
770
- setUint16(rawExtraFieldView, offset + 2, getLength(data));
771
- arraySet(rawExtraField, data, offset + 4);
772
- offset += 4 + getLength(data);
773
- });
774
- }
760
+ const rawExtraField = serializeExtraField(options[PROPERTY_NAME_EXTRA_FIELD]);
761
+ const rawLocalExtraField = serializeExtraField(options[OPTION_LOCAL_EXTRA_FIELD]);
775
762
  return {
776
763
  comment,
777
764
  resolvedOptions: {
@@ -805,11 +792,36 @@ function resolveMetadata(zipWriter, name, options) {
805
792
  useCompressionStream,
806
793
  dataDescriptor,
807
794
  zip64,
808
- rawExtraField
795
+ rawExtraField,
796
+ rawLocalExtraField
809
797
  }
810
798
  };
811
799
  }
812
800
 
801
+ function serializeExtraField(extraField) {
802
+ if (!extraField) {
803
+ return EMPTY_UINT8_ARRAY;
804
+ }
805
+ let extraFieldSize = 0;
806
+ let offset = 0;
807
+ extraField.forEach(data => extraFieldSize += 4 + getLength(data));
808
+ const rawExtraField = new Uint8Array(extraFieldSize);
809
+ const rawExtraFieldView = getDataView(rawExtraField);
810
+ extraField.forEach((data, type) => {
811
+ if (type > MAX_16_BITS) {
812
+ throw new Error(ERR_INVALID_EXTRAFIELD_TYPE);
813
+ }
814
+ if (getLength(data) > MAX_16_BITS) {
815
+ throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
816
+ }
817
+ setUint16(rawExtraFieldView, offset, type);
818
+ setUint16(rawExtraFieldView, offset + 2, getLength(data));
819
+ arraySet(rawExtraField, data, offset + 4);
820
+ offset += 4 + getLength(data);
821
+ });
822
+ return rawExtraField;
823
+ }
824
+
813
825
  async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, options) {
814
826
  const { passThrough, zipCrypto, password, rawPassword, encryptionStrength } = metadata;
815
827
  let { dataDescriptor, zip64, level, compressionMethod } = metadata;
@@ -1134,7 +1146,7 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1134
1146
  msdosAttributes
1135
1147
  };
1136
1148
  let {
1137
- signature,
1149
+ crc32,
1138
1150
  uncompressedSize
1139
1151
  } = options;
1140
1152
  let compressedSize = 0;
@@ -1154,7 +1166,7 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1154
1166
  encryptionStrength,
1155
1167
  zipCrypto: encrypted && zipCrypto,
1156
1168
  passwordVerification: encrypted && zipCrypto && (rawLastModDate >> 8) & MAX_8_BITS,
1157
- signed: !passThrough,
1169
+ computeCrc32: !passThrough,
1158
1170
  compressed: compressed && !passThrough,
1159
1171
  encrypted: encrypted && !passThrough,
1160
1172
  useWebWorkers,
@@ -1173,7 +1185,9 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1173
1185
  writer.size += compressedSize;
1174
1186
  if (!passThrough) {
1175
1187
  uncompressedSize = result.inputSize;
1176
- signature = result.signature;
1188
+ if (!encrypted || zipCrypto) {
1189
+ crc32 = result.crc32;
1190
+ }
1177
1191
  }
1178
1192
  if ((!zip64CompressedSize && compressedSize >= MAX_32_BITS) ||
1179
1193
  (!zip64UncompressedSize && uncompressedSize >= MAX_32_BITS)) {
@@ -1188,7 +1202,7 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1188
1202
 
1189
1203
  }
1190
1204
  setEntryInfo({
1191
- signature,
1205
+ crc32,
1192
1206
  compressedSize,
1193
1207
  uncompressedSize,
1194
1208
  headerInfo,
@@ -1211,7 +1225,8 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
1211
1225
  version,
1212
1226
  headerArray,
1213
1227
  headerView,
1214
- signature,
1228
+ signature: crc32,
1229
+ crc32: encrypted && !zipCrypto && !passThrough ? UNDEFINED_VALUE : crc32,
1215
1230
  extraFieldExtendedTimestampFlag,
1216
1231
  zip64UncompressedSize,
1217
1232
  zip64CompressedSize
@@ -1232,6 +1247,7 @@ function getHeaderInfo(options) {
1232
1247
  dataDescriptor,
1233
1248
  directory,
1234
1249
  rawExtraField,
1250
+ rawLocalExtraField,
1235
1251
  encryptionStrength,
1236
1252
  extendedTimestamp,
1237
1253
  ntfsTimestamp,
@@ -1239,7 +1255,8 @@ function getHeaderInfo(options) {
1239
1255
  encrypted,
1240
1256
  zip64UncompressedSize,
1241
1257
  zip64CompressedSize,
1242
- uncompressedSize
1258
+ uncompressedSize,
1259
+ crc32
1243
1260
  } = options;
1244
1261
  let { version, compressionMethod } = options;
1245
1262
  const compressed = !directory && (compressionMethod === UNDEFINED_VALUE
@@ -1312,8 +1329,8 @@ function getHeaderInfo(options) {
1312
1329
  extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS_TAG1);
1313
1330
  extraFieldNTFS.uint16(24);
1314
1331
  extraFieldNTFS.uint64(lastModTimeNTFS);
1315
- extraFieldNTFS.uint64(getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
1316
- extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
1332
+ extraFieldNTFS.uint64(lastAccessDate ? getTimeNTFS(lastAccessDate) : lastModTimeNTFS);
1333
+ extraFieldNTFS.uint64(creationDate ? getTimeNTFS(creationDate) : lastModTimeNTFS);
1317
1334
  rawExtraFieldNTFS = extraFieldNTFS.array;
1318
1335
  } catch {
1319
1336
  rawExtraFieldNTFS = EMPTY_UINT8_ARRAY;
@@ -1365,15 +1382,18 @@ function getHeaderInfo(options) {
1365
1382
  }
1366
1383
  if (encrypted && !zipCrypto) {
1367
1384
  version = version > VERSION_AES ? version : VERSION_AES;
1385
+ if (passThrough && crc32 !== UNDEFINED_VALUE) {
1386
+ rawExtraFieldAES[EXTRAFIELD_OFFSET_AES_VENDOR_VERSION] = VENDOR_VERSION_AE_1;
1387
+ }
1368
1388
  rawExtraFieldAES[9] = compressionMethod;
1369
1389
  compressionMethod = COMPRESSION_METHOD_AES;
1370
1390
  }
1371
1391
  const localExtraFieldZip64Length = writeLocalExtraFieldZip64 ? getLength(rawLocalExtraFieldZip64) : 0;
1372
- const extraFieldLength = localExtraFieldZip64Length + getLength(rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
1392
+ const extraFieldLength = localExtraFieldZip64Length + getLength(rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField, rawLocalExtraField);
1373
1393
  if (extraFieldLength > MAX_16_BITS) {
1374
1394
  throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
1375
1395
  }
1376
- const dosLastModDate = new Date(Math.ceil(lastModDate.getTime() / 2000) * 2000);
1396
+ const dosLastModDate = new Date(Math.ceil(Math.floor(lastModDate.getTime() / 1000) / 2) * 2000);
1377
1397
  const {
1378
1398
  headerArray,
1379
1399
  headerView,
@@ -1403,6 +1423,7 @@ function getHeaderInfo(options) {
1403
1423
  localHeader.bytes(rawExtraFieldNTFS);
1404
1424
  localHeader.bytes(rawExtraFieldUnix);
1405
1425
  localHeader.bytes(rawExtraField);
1426
+ localHeader.bytes(rawLocalExtraField);
1406
1427
  if (dataDescriptor) {
1407
1428
  if (!zip64CompressedSize) {
1408
1429
  setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
@@ -1521,7 +1542,7 @@ function getDataDescriptorInfo({
1521
1542
  }
1522
1543
 
1523
1544
  function setEntryInfo({
1524
- signature,
1545
+ crc32,
1525
1546
  compressedSize,
1526
1547
  uncompressedSize,
1527
1548
  headerInfo,
@@ -1529,6 +1550,7 @@ function setEntryInfo({
1529
1550
  }, {
1530
1551
  zip64,
1531
1552
  zipCrypto,
1553
+ passThrough,
1532
1554
  dataDescriptor
1533
1555
  }) {
1534
1556
  const {
@@ -1539,10 +1561,10 @@ function setEntryInfo({
1539
1561
  dataDescriptorView,
1540
1562
  dataDescriptorOffset
1541
1563
  } = dataDescriptorInfo;
1542
- if ((!encrypted || zipCrypto) && signature !== UNDEFINED_VALUE) {
1543
- setUint32(headerView, HEADER_OFFSET_SIGNATURE, signature);
1564
+ if ((!encrypted || zipCrypto || passThrough) && crc32 !== UNDEFINED_VALUE) {
1565
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, crc32);
1544
1566
  if (dataDescriptor) {
1545
- setUint32(dataDescriptorView, dataDescriptorOffset, signature);
1567
+ setUint32(dataDescriptorView, dataDescriptorOffset, crc32);
1546
1568
  }
1547
1569
  }
1548
1570
  if (zip64) {
@@ -1565,15 +1587,15 @@ function updateLocalHeader({
1565
1587
  encrypted,
1566
1588
  zip64,
1567
1589
  localExtraFieldZip64Length,
1568
- signature,
1590
+ crc32,
1569
1591
  compressedSize,
1570
1592
  uncompressedSize,
1571
1593
  zip64UncompressedSize,
1572
1594
  zip64CompressedSize
1573
- }, localHeaderView, { dataDescriptor }) {
1595
+ }, localHeaderView, { dataDescriptor, passThrough }) {
1574
1596
  if (!dataDescriptor) {
1575
- if (!encrypted) {
1576
- setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + LOCAL_HEADER_COMMON_OFFSET, signature);
1597
+ if (!encrypted || (passThrough && crc32 !== UNDEFINED_VALUE)) {
1598
+ setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + LOCAL_HEADER_COMMON_OFFSET, crc32);
1577
1599
  }
1578
1600
  if (!zip64CompressedSize) {
1579
1601
  setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, compressedSize);
@@ -1882,7 +1904,8 @@ async function flushBufferedData(readable, writer, signal, onChunkWritten) {
1882
1904
 
1883
1905
  function getTimeNTFS(date) {
1884
1906
  if (date) {
1885
- return ((BigInt(date.getTime()) + BigInt(11644473600000)) * BigInt(10000));
1907
+ const timeNTFS = ((BigInt(date.getTime()) + BigInt(11644473600000)) * BigInt(10000));
1908
+ return timeNTFS < MIN_NTFS_TIME ? MIN_NTFS_TIME : timeNTFS > MAX_NTFS_TIME ? MAX_NTFS_TIME : timeNTFS;
1886
1909
  }
1887
1910
  }
1888
1911
 
@@ -1,3 +1,5 @@
1
+ /// <reference types="../index.d.ts" />
2
+
1
3
  /*
2
4
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
3
5
 
@@ -39,6 +39,8 @@ export {
39
39
  ERR_UNSUPPORTED_ENCRYPTION,
40
40
  ERR_UNSUPPORTED_COMPRESSION,
41
41
  ERR_INVALID_SIGNATURE,
42
+ ERR_INVALID_CRC32,
43
+ ERR_INVALID_AUTHENTICATION_CODE,
42
44
  ERR_INVALID_UNCOMPRESSED_SIZE,
43
45
  ERR_INVALID_PASSWORD,
44
46
  ERR_INVALID_COMPRESSED_DATA,
@@ -1,3 +1,5 @@
1
+ /// <reference types="../index.d.ts" />
2
+
1
3
  /*
2
4
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
3
5
 
@@ -1,3 +1,5 @@
1
+ /// <reference types="../index.d.ts" />
2
+
1
3
  /*
2
4
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
3
5
 
@@ -1,3 +1,5 @@
1
+ /// <reference types="../index.d.ts" />
2
+
1
3
  /*
2
4
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.
3
5
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A JavaScript library to zip and unzip files in the browser, Deno and Node.js",
4
4
  "author": "Gildas Lormeau",
5
5
  "license": "BSD-3-Clause",
6
- "version": "2.8.43",
6
+ "version": "2.8.45",
7
7
  "type": "module",
8
8
  "keywords": [
9
9
  "zip",
@@ -34,6 +34,10 @@
34
34
  "bun": ">=0.7.0"
35
35
  },
36
36
  "scripts": {
37
+ "lint": "npx eslint . && deno lint",
38
+ "test": "npm run test-node && npm run test-deno && npm run test-bun && npm run test-node-dist && npm run test-deno-dist && npm run test-bun-dist && npm run test-web-streams-polyfill && npm run test-types",
39
+ "test-browsers": "npm run test-chrome && npm run test-firefox && npm run test-webkit",
40
+ "test-all": "npm run lint && npm run test && npm run test-browsers",
37
41
  "build": "npx rollup -c",
38
42
  "build-dev": "npx rollup -c rollup.config.dev.js",
39
43
  "build-doc-api": "npx typedoc",
@@ -46,8 +50,9 @@
46
50
  "test-node-dist": "./tests/dist-runner.sh node",
47
51
  "test-bun-dist": "./tests/dist-runner.sh bun",
48
52
  "test-types": "tsc -p ./tests/types/tsconfig.json",
49
- "test-firefox": "firefox http://localhost:8081/tests/ & npx http-server -p=8081 & wait $!",
50
- "test-chrome": "google-chrome http://localhost:8081/tests/ & npx http-server -p=8081 & wait $!"
53
+ "test-firefox": "node ./tests/browser-runner.js firefox",
54
+ "test-chrome": "node ./tests/browser-runner.js chrome",
55
+ "test-webkit": "node ./tests/browser-runner.js webkit"
51
56
  },
52
57
  "main": "./index.cjs",
53
58
  "module": "./index.js",
@@ -236,6 +241,7 @@
236
241
  "@rollup/plugin-terser": "^1.0.0",
237
242
  "eslint": "^10.8.1",
238
243
  "http-server": "^14.1.1",
244
+ "playwright": "^1.62.1",
239
245
  "rollup": "^4.62.4",
240
246
  "typedoc": "^0.28.20",
241
247
  "typedoc-plugin-markdown": "^4.12.0",
@@ -1,3 +1,3 @@
1
- const WORKER_MESSAGE_PROPERTY_NAMES = ["baseURI", "cause", "checkPasswordOnly", "chunkSize", "code", "codecType", "codecURI", "compressed", "compressionMethod", "config", "deflate64", "done", "encrypted", "encryptionStrength", "error", "format", "inputSize", "level", "message", "messageId", "name", "options", "outputSize", "password", "passwordVerification", "rawBitFlag", "rawPassword", "readable", "result", "signature", "signed", "stack", "transferStreams", "type", "useCompressionStream", "useWebWorkers", "value", "wasmURI", "workerStartupTimeout", "writable", "zipCrypto"];
1
+ const WORKER_MESSAGE_PROPERTY_NAMES = ["baseURI", "cause", "checkAuthenticationCode", "checkCrc32", "checkPasswordOnly", "chunkSize", "code", "codecType", "codecURI", "compressed", "compressionMethod", "computeCrc32", "config", "crc32", "deflate64", "done", "encrypted", "encryptionStrength", "error", "format", "inputSize", "level", "message", "messageId", "name", "options", "outputSize", "password", "passwordVerification", "rawBitFlag", "rawPassword", "readable", "result", "stack", "transferStreams", "type", "useCompressionStream", "useWebWorkers", "value", "wasmURI", "workerStartupTimeout", "writable", "zipCrypto"];
2
2
 
3
3
  export { WORKER_MESSAGE_PROPERTY_NAMES };