@zip.js/zip.js 2.8.37 → 2.8.39

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.
package/README.md CHANGED
@@ -14,18 +14,6 @@ See https://gildas-lormeau.github.io/zip-manager
14
14
 
15
15
  See here for more info: https://gildas-lormeau.github.io/zip.js/
16
16
 
17
- # Bundle size
18
-
19
- zip.js is tree-shakeable: import named identifiers and your bundler removes what you do
20
- not use, e.g. the whole write path if you only read zip files. `import * as zip from
21
- "@zip.js/zip.js"` keeps everything.
22
-
23
- Smaller entry points are also available, e.g. `@zip.js/zip.js/lib/zip-core.js` embeds
24
- neither the Web Worker code nor the WASM module, pass `workerURI` and `wasmURI` to
25
- `configure()` instead. The prebuilt bundles in
26
- [`/dist`](https://github.com/gildas-lormeau/zip.js/tree/master/dist) are standalone files
27
- for script tags and AMD loaders, they are not tree-shaken.
28
-
29
17
  # Examples
30
18
 
31
19
  ## Hello world
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zip-js/zip-js",
3
- "version": "2.8.37",
3
+ "version": "2.8.39",
4
4
  "exports": {
5
5
  ".": "./index.js"
6
6
  },
@@ -24,6 +24,7 @@
24
24
  "benchmarks",
25
25
  "docs",
26
26
  ".github",
27
- "tests/corpus"
27
+ "tests/corpus",
28
+ "tests/fidelity/corpus"
28
29
  ]
29
30
  }
package/dist/zip-core.js CHANGED
@@ -46,6 +46,7 @@
46
46
  const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
47
47
  const SPLIT_ZIP_FILE_SIGNATURE = 0x08074b50;
48
48
  const DATA_DESCRIPTOR_RECORD_SIGNATURE = SPLIT_ZIP_FILE_SIGNATURE;
49
+ const ARCHIVE_EXTRA_DATA_SIGNATURE = 0x08064b50;
49
50
  const CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
50
51
  const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
51
52
  const ZIP64_END_OF_CENTRAL_DIR_SIGNATURE = 0x06064b50;
@@ -77,6 +78,7 @@
77
78
  const BITFLAG_LEVEL_FAST_MASK = 0b100;
78
79
  const BITFLAG_LEVEL_SUPER_FAST_MASK = 0b110;
79
80
  const BITFLAG_DATA_DESCRIPTOR = 0b1000;
81
+ const BITFLAG_STRONG_ENCRYPTION = 0b1000000;
80
82
  const BITFLAG_LANG_ENCODING_FLAG = 0b100000000000;
81
83
  const FILE_ATTR_MSDOS_DIR_MASK = 0b10000;
82
84
  const FILE_ATTR_MSDOS_READONLY_MASK = 0x01;
@@ -160,6 +162,7 @@
160
162
  maxWorkers,
161
163
  terminateWorkerTimeout: 5000,
162
164
  workerStarvationTimeout: 5000,
165
+ workerStartupTimeout: 5000,
163
166
  useWebWorkers: true,
164
167
  useCompressionStream: true,
165
168
  transferStreams: true,
@@ -175,6 +178,7 @@
175
178
  "maxWorkers",
176
179
  "terminateWorkerTimeout",
177
180
  "workerStarvationTimeout",
181
+ "workerStartupTimeout",
178
182
  "useCompressionStream",
179
183
  "useWebWorkers",
180
184
  "transferStreams",
@@ -1710,7 +1714,7 @@
1710
1714
  if (zipCrypto.password || zipCrypto.rawPassword) {
1711
1715
  const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
1712
1716
  zipCrypto.password = zipCrypto.rawPassword = null;
1713
- if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
1717
+ if ((decryptedHeader[HEADER_LENGTH - 1] ^ zipCrypto.passwordVerification) != 0) {
1714
1718
  throw new Error(ERR_INVALID_PASSWORD);
1715
1719
  }
1716
1720
  chunk = chunk.subarray(HEADER_LENGTH);
@@ -1883,7 +1887,7 @@
1883
1887
  readable = pipeThroughBackpressured(readable, new CompressionStream(FORMAT_GZIP));
1884
1888
  readable = pipeThrough(readable, gzipCrc32Stream);
1885
1889
  } else {
1886
- readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib, CompressionStream);
1890
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib);
1887
1891
  }
1888
1892
  }
1889
1893
  if (encrypted) {
@@ -1968,7 +1972,7 @@
1968
1972
  if (codecStreams) {
1969
1973
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize }));
1970
1974
  } else {
1971
- readable = pipeThroughCommpressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamZlib, DecompressionStream);
1975
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamZlib);
1972
1976
  }
1973
1977
  readable = mapInflateStreamError(readable);
1974
1978
  }
@@ -1987,6 +1991,25 @@
1987
1991
  }
1988
1992
  }
1989
1993
 
1994
+ const deflateRawSupportByStream = new Map();
1995
+
1996
+ function supportsDeflateRaw(StreamClass) {
1997
+ if (!StreamClass) {
1998
+ return false;
1999
+ }
2000
+ let supported = deflateRawSupportByStream.get(StreamClass);
2001
+ if (supported === UNDEFINED_VALUE) {
2002
+ try {
2003
+ new StreamClass(FORMAT_DEFLATE_RAW);
2004
+ supported = true;
2005
+ } catch {
2006
+ supported = false;
2007
+ }
2008
+ deflateRawSupportByStream.set(StreamClass, supported);
2009
+ }
2010
+ return supported;
2011
+ }
2012
+
1990
2013
  function setReadable(stream, readable, flush) {
1991
2014
  readable = pipeThrough(readable, new TransformStream({ flush }));
1992
2015
  Object.defineProperty(stream, "readable", {
@@ -2003,21 +2026,17 @@
2003
2026
  return new CodecStreamClass(format, options);
2004
2027
  }
2005
2028
 
2006
- function pipeThroughCommpressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamZlib, CompressionStream) {
2007
- const Stream = useCompressionStream && CompressionStreamNative ? CompressionStreamNative : CompressionStreamZlib || CompressionStream;
2029
+ function pipeThroughCompressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamZlib) {
2030
+ const Stream = useCompressionStream && CompressionStreamNative ?
2031
+ CompressionStreamNative :
2032
+ CompressionStreamZlib || CompressionStreamNative;
2008
2033
  const format = options.deflate64 ? FORMAT_DEFLATE64_RAW : FORMAT_DEFLATE_RAW;
2009
2034
  let codecStream;
2010
2035
  try {
2011
2036
  codecStream = new Stream(format, options);
2012
2037
  } catch (error) {
2013
- if (useCompressionStream) {
2014
- if (CompressionStreamZlib) {
2015
- codecStream = new CompressionStreamZlib(format, options);
2016
- } else if (CompressionStream) {
2017
- codecStream = new CompressionStream(format, options);
2018
- } else {
2019
- throw error;
2020
- }
2038
+ if (useCompressionStream && CompressionStreamZlib && Stream != CompressionStreamZlib) {
2039
+ codecStream = new CompressionStreamZlib(format, options);
2021
2040
  } else {
2022
2041
  throw error;
2023
2042
  }
@@ -2278,6 +2297,7 @@
2278
2297
  const MODULE_WORKER_OPTIONS = { type: "module" };
2279
2298
  const ERROR_EVENT_TYPE = "error";
2280
2299
  const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
2300
+ const ERR_WORKER_STARTUP_TIMEOUT = "Worker startup timeout";
2281
2301
 
2282
2302
  let webWorkerSupported, webWorkerSource, webWorkerURI, webWorkerOptions;
2283
2303
  let transferStreamsSupported = true;
@@ -2288,6 +2308,10 @@
2288
2308
  }
2289
2309
  let initModule = () => { };
2290
2310
 
2311
+ function resetWebWorkerSupport() {
2312
+ webWorkerSupported = UNDEFINED_VALUE;
2313
+ }
2314
+
2291
2315
  class CodecWorker {
2292
2316
 
2293
2317
  constructor(workerData, { readable, writable }, { options, config, streamOptions, useWebWorkers, transferStreams, workerURI }, onTaskFinished) {
@@ -2381,7 +2405,7 @@
2381
2405
  }
2382
2406
 
2383
2407
  function createWebWorkerInterface(workerData, config) {
2384
- const { baseURI, chunkSize } = config;
2408
+ const { baseURI, chunkSize, workerStartupTimeout } = config;
2385
2409
  let { wasmURI } = config;
2386
2410
 
2387
2411
  if (!workerData.interface) {
@@ -2398,9 +2422,26 @@
2398
2422
  }
2399
2423
  Object.assign(workerData, {
2400
2424
  worker,
2425
+ workerAlive: false,
2401
2426
  terminated: false,
2402
2427
  interface: {
2403
- run: () => runWebWorker(workerData, { chunkSize, wasmURI, baseURI })
2428
+ run: async () => {
2429
+ try {
2430
+ return await runWebWorker(workerData, { chunkSize, wasmURI, baseURI, workerStartupTimeout });
2431
+ } catch (error) {
2432
+ if (error && error.workerStartupFailed) {
2433
+ webWorkerSupported = false;
2434
+ const { reader } = workerData;
2435
+ if (reader) {
2436
+ reader.releaseLock();
2437
+ }
2438
+ workerData.reader = null;
2439
+ workerData.writer = null;
2440
+ return runWorker$1(workerData, config);
2441
+ }
2442
+ throw error;
2443
+ }
2444
+ }
2404
2445
  }
2405
2446
  });
2406
2447
  }
@@ -2410,15 +2451,23 @@
2410
2451
  async function runWorker$1({ options, readable, writable, onTaskFinished }, config) {
2411
2452
  let codecStream;
2412
2453
  try {
2413
- if (!options.useCompressionStream) {
2414
- try {
2415
- await initModule(config);
2416
- } catch {
2417
- const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
2418
- config.CompressionStreamZlib :
2419
- config.DecompressionStreamZlib;
2420
- if (!ZlibStream || ZlibStream.requiresModule) {
2421
- options.useCompressionStream = true;
2454
+ if (options.compressed && !options.format) {
2455
+ const deflate = options.codecType.startsWith(CODEC_DEFLATE);
2456
+ const ZlibStream = deflate ? config.CompressionStreamZlib : config.DecompressionStreamZlib;
2457
+ const NativeStream = deflate ? config.CompressionStream : config.DecompressionStream;
2458
+ if (!options.useCompressionStream) {
2459
+ try {
2460
+ await initModule(config);
2461
+ } catch {
2462
+ if (!ZlibStream || ZlibStream.requiresModule) {
2463
+ options.useCompressionStream = true;
2464
+ }
2465
+ }
2466
+ } else if (ZlibStream && ZlibStream.requiresModule && !supportsDeflateRaw(NativeStream)) {
2467
+ try {
2468
+ await initModule(config);
2469
+ } catch {
2470
+ // ignored
2422
2471
  }
2423
2472
  }
2424
2473
  }
@@ -2487,6 +2536,10 @@
2487
2536
  writer: writable.getWriter()
2488
2537
  });
2489
2538
  }
2539
+ const { workerStartupTimeout } = config;
2540
+ if (!workerData.workerAlive && Number.isFinite(workerStartupTimeout) && workerStartupTimeout >= 0) {
2541
+ workerData.startupTimeout = setTimeout(() => onStartupTimeout(workerData), workerStartupTimeout);
2542
+ }
2490
2543
  try {
2491
2544
  const resultValue = await result;
2492
2545
  await closeWritable();
@@ -2599,28 +2652,68 @@
2599
2652
  } else {
2600
2653
  worker = new Worker(webWorkerURI, webWorkerOptions);
2601
2654
  }
2602
- worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData));
2655
+ worker.addEventListener(MESSAGE_EVENT_TYPE, event => {
2656
+ workerData.workerAlive = true;
2657
+ clearStartupTimeout(workerData);
2658
+ onMessage(event, workerData);
2659
+ });
2603
2660
  worker.addEventListener(ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
2604
2661
  worker.addEventListener(MESSAGE_ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
2605
2662
  return worker;
2606
2663
  }
2607
2664
 
2665
+ function onStartupTimeout(workerData) {
2666
+ workerData.startupTimeout = null;
2667
+ if (workerData.workerAlive) {
2668
+ return;
2669
+ }
2670
+ const { rejectResult, writer } = workerData;
2671
+ terminateWorker$1(workerData);
2672
+ workerData.worker = null;
2673
+ if (rejectResult) {
2674
+ const error = new Error(ERR_WORKER_STARTUP_TIMEOUT);
2675
+ error.workerStartupFailed = true;
2676
+ rejectResult(error);
2677
+ if (writer) {
2678
+ writer.releaseLock();
2679
+ }
2680
+ }
2681
+ }
2682
+
2683
+ function clearStartupTimeout(workerData) {
2684
+ const { startupTimeout } = workerData;
2685
+ if (startupTimeout) {
2686
+ clearTimeout(startupTimeout);
2687
+ workerData.startupTimeout = null;
2688
+ }
2689
+ }
2690
+
2608
2691
  function onWorkerError(event, workerData) {
2609
2692
  if (event.preventDefault) {
2610
2693
  event.preventDefault();
2611
2694
  }
2612
- const { rejectResult, writer, onTaskFinished } = workerData;
2695
+ clearStartupTimeout(workerData);
2696
+ const { workerAlive, rejectResult, writer, onTaskFinished } = workerData;
2613
2697
  terminateWorker$1(workerData);
2698
+ if (!workerAlive) {
2699
+ workerData.worker = null;
2700
+ }
2614
2701
  if (rejectResult) {
2615
- rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
2702
+ let error = event.error || new Error(event.message || ERROR_EVENT_TYPE);
2703
+ if (!workerAlive) {
2704
+ error = Object.assign(new Error(error.message || ERROR_EVENT_TYPE), { workerStartupFailed: true });
2705
+ }
2706
+ rejectResult(error);
2616
2707
  if (writer) {
2617
2708
  writer.releaseLock();
2618
2709
  }
2619
- onTaskFinished();
2710
+ if (workerAlive) {
2711
+ onTaskFinished();
2712
+ }
2620
2713
  }
2621
2714
  }
2622
2715
 
2623
- function sendMessage(message, { worker, writer, transferStreams }) {
2716
+ function sendMessage(message, { worker, writer, transferStreams, workerAlive }) {
2624
2717
  try {
2625
2718
  const { value, readable, writable } = message;
2626
2719
  const transferables = [];
@@ -2628,7 +2721,7 @@
2628
2721
  message.value = toExactUint8Array(value);
2629
2722
  transferables.push(message.value.buffer);
2630
2723
  }
2631
- if (transferStreams && transferStreamsSupported) {
2724
+ if (transferStreams && transferStreamsSupported && workerAlive) {
2632
2725
  if (readable) {
2633
2726
  transferables.push(readable);
2634
2727
  }
@@ -2866,6 +2959,7 @@
2866
2959
  clearTerminateTimeout(workerData);
2867
2960
  return workerData.terminate();
2868
2961
  }));
2962
+ resetWebWorkerSupport();
2869
2963
  }
2870
2964
 
2871
2965
  /*
@@ -3642,7 +3736,7 @@
3642
3736
 
3643
3737
  getDiskOffset(diskNumber) {
3644
3738
  const { diskOffsets, size } = this;
3645
- const diskOffset = diskOffsets.at(diskNumber);
3739
+ const diskOffset = diskOffsets[diskNumber];
3646
3740
  return diskOffset === UNDEFINED_VALUE ? size : diskOffset;
3647
3741
  }
3648
3742
 
@@ -4149,6 +4243,7 @@
4149
4243
  const ERR_SPLIT_ZIP_FILE = "Split zip file";
4150
4244
  const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
4151
4245
  const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
4246
+ const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
4152
4247
  const CHARSET_UTF8 = "utf-8";
4153
4248
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
4154
4249
  const CHARSET_CP437 = "cp437";
@@ -4226,6 +4321,7 @@
4226
4321
  let prependedDataLength = 0;
4227
4322
  let startOffset;
4228
4323
  let zip64EndOfDirectory;
4324
+ let zip64EndOfDirectoryVersion2;
4229
4325
  const requiresZip64 = directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS;
4230
4326
  if (directoryDataOffset != MAX_32_BITS && diskNumber != MAX_16_BITS) {
4231
4327
  directoryDataOffset += getDiskOffset$1(reader, diskNumber);
@@ -4255,6 +4351,7 @@
4255
4351
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
4256
4352
  }
4257
4353
  zip64EndOfDirectory = true;
4354
+ zip64EndOfDirectoryVersion2 = getBigUint64(endOfDirectoryView, 4) > ZIP64_END_OF_CENTRAL_DIR_LENGTH - 12;
4258
4355
  if (lastDiskNumber == MAX_16_BITS) {
4259
4356
  lastDiskNumber = getUint32(endOfDirectoryView, 16);
4260
4357
  } else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
@@ -4334,6 +4431,9 @@
4334
4431
  for (let indexFile = 0; indexFile < filesLength; indexFile++) {
4335
4432
  const fileEntry = new ZipEntry(reader, config, zipReader.options);
4336
4433
  if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
4434
+ if (indexFile == 0 && (zip64EndOfDirectoryVersion2 || detectEncryptedCentralDirectory(directoryView))) {
4435
+ throw new Error(ERR_ENCRYPTED_CENTRAL_DIRECTORY);
4436
+ }
4337
4437
  throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
4338
4438
  }
4339
4439
  readCommonHeader(fileEntry, directoryView, offset + 6);
@@ -4601,6 +4701,9 @@
4601
4701
  fileEntry.zipCrypto = zipCrypto;
4602
4702
  }
4603
4703
  if (encrypted) {
4704
+ if ((localDirectory.rawBitFlag & BITFLAG_STRONG_ENCRYPTION) == BITFLAG_STRONG_ENCRYPTION) {
4705
+ throw new Error(ERR_UNSUPPORTED_ENCRYPTION);
4706
+ }
4604
4707
  if (!zipCrypto && (extraFieldAES.strength < 1 || extraFieldAES.strength > 3)) {
4605
4708
  throw new Error(ERR_UNSUPPORTED_ENCRYPTION);
4606
4709
  } else if (!password && !rawPassword) {
@@ -4694,6 +4797,16 @@
4694
4797
  }
4695
4798
  }
4696
4799
 
4800
+ function detectEncryptedCentralDirectory(directoryView) {
4801
+ const maxOffset = Math.min(directoryView.byteLength, 1024) - 3;
4802
+ for (let offset = 0; offset < maxOffset; offset++) {
4803
+ if (getUint32(directoryView, offset) == ARCHIVE_EXTRA_DATA_SIGNATURE) {
4804
+ return true;
4805
+ }
4806
+ }
4807
+ return false;
4808
+ }
4809
+
4697
4810
  function readCommonHeader(directory, dataView, offset) {
4698
4811
  const rawBitFlag = directory.rawBitFlag = getUint16(dataView, offset + 2);
4699
4812
  const encrypted = (rawBitFlag & BITFLAG_ENCRYPTED) == BITFLAG_ENCRYPTED;
@@ -5208,6 +5321,7 @@
5208
5321
  ERR_BAD_FORMAT: ERR_BAD_FORMAT,
5209
5322
  ERR_CENTRAL_DIRECTORY_NOT_FOUND: ERR_CENTRAL_DIRECTORY_NOT_FOUND,
5210
5323
  ERR_ENCRYPTED: ERR_ENCRYPTED,
5324
+ ERR_ENCRYPTED_CENTRAL_DIRECTORY: ERR_ENCRYPTED_CENTRAL_DIRECTORY,
5211
5325
  ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND: ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND,
5212
5326
  ERR_EOCDR_NOT_FOUND: ERR_EOCDR_NOT_FOUND,
5213
5327
  ERR_EXTRAFIELD_ZIP64_NOT_FOUND: ERR_EXTRAFIELD_ZIP64_NOT_FOUND,
@@ -5620,7 +5734,11 @@
5620
5734
  if (versionMadeBy > MAX_16_BITS) {
5621
5735
  throw new Error(ERR_INVALID_VERSION);
5622
5736
  }
5623
- let externalFileAttributes = getOptionValue(zipWriter, options, PROPERTY_NAME_EXTERNAL_FILE_ATTRIBUTES, 0);
5737
+ let externalFileAttributes = getOptionValue(zipWriter, options, PROPERTY_NAME_EXTERNAL_FILE_ATTRIBUTES);
5738
+ const externalFileAttributesProvided = externalFileAttributes !== UNDEFINED_VALUE;
5739
+ if (!externalFileAttributesProvided) {
5740
+ externalFileAttributes = 0;
5741
+ }
5624
5742
  if (!options[PROPERTY_NAME_DIRECTORY] && name.endsWith(DIRECTORY_SIGNATURE)) {
5625
5743
  options[PROPERTY_NAME_DIRECTORY] = true;
5626
5744
  }
@@ -5629,13 +5747,13 @@
5629
5747
  if (!name.endsWith(DIRECTORY_SIGNATURE)) {
5630
5748
  name += DIRECTORY_SIGNATURE;
5631
5749
  }
5632
- if (externalFileAttributes === 0) {
5750
+ if (!externalFileAttributesProvided) {
5633
5751
  externalFileAttributes = FILE_ATTR_MSDOS_DIR_MASK;
5634
5752
  if (!msDosCompatible) {
5635
5753
  externalFileAttributes |= (FILE_ATTR_UNIX_TYPE_DIR | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_DEFAULT_MASK) << 16;
5636
5754
  }
5637
5755
  }
5638
- } else if (!msDosCompatible && externalFileAttributes === 0) {
5756
+ } else if (!msDosCompatible && !externalFileAttributesProvided) {
5639
5757
  if (executable) {
5640
5758
  externalFileAttributes = (FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_DEFAULT_MASK) << 16;
5641
5759
  } else {
@@ -5644,6 +5762,7 @@
5644
5762
  }
5645
5763
  let unixExternalUpper;
5646
5764
  if (!msDosCompatible) {
5765
+ const unixModeProvided = unixMode !== UNDEFINED_VALUE || Boolean(setuid || setgid || sticky);
5647
5766
  unixExternalUpper = (externalFileAttributes >> 16) & MAX_16_BITS;
5648
5767
  unixMode = unixMode === UNDEFINED_VALUE ? unixExternalUpper : (unixMode & MAX_16_BITS);
5649
5768
  if (setuid) {
@@ -5661,10 +5780,12 @@
5661
5780
  } else {
5662
5781
  sticky = Boolean(unixMode & FILE_ATTR_UNIX_STICKY_MASK);
5663
5782
  }
5664
- if (directory) {
5665
- unixMode |= FILE_ATTR_UNIX_TYPE_DIR;
5783
+ if (!externalFileAttributesProvided || unixModeProvided) {
5784
+ if (directory) {
5785
+ unixMode |= FILE_ATTR_UNIX_TYPE_DIR;
5786
+ }
5787
+ externalFileAttributes = ((unixMode & MAX_16_BITS) << 16) | (externalFileAttributes & MAX_16_BITS);
5666
5788
  }
5667
- externalFileAttributes = ((unixMode & MAX_16_BITS) << 16) | (externalFileAttributes & MAX_8_BITS);
5668
5789
  }
5669
5790
  ({ msdosAttributesRaw, msdosAttributes } = normalizeMsdosAttributes(msdosAttributesRaw, msdosAttributes));
5670
5791
  if (hasMsDosProvided) {
@@ -6387,6 +6508,7 @@
6387
6508
  if (extraFieldLength > MAX_16_BITS) {
6388
6509
  throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
6389
6510
  }
6511
+ const dosLastModDate = new Date(Math.ceil(lastModDate.getTime() / 2000) * 2000);
6390
6512
  const {
6391
6513
  headerArray,
6392
6514
  headerView,
@@ -6396,7 +6518,7 @@
6396
6518
  bitFlag: getBitFlag(level, useUnicodeFileNames, dataDescriptor, encrypted, compressionMethod),
6397
6519
  compressionMethod,
6398
6520
  uncompressedSize,
6399
- lastModDate: lastModDate < MIN_DATE ? MIN_DATE : lastModDate > MAX_DATE ? MAX_DATE : lastModDate,
6521
+ lastModDate: dosLastModDate < MIN_DATE ? MIN_DATE : dosLastModDate > MAX_DATE ? MAX_DATE : dosLastModDate,
6400
6522
  rawFilename,
6401
6523
  zip64CompressedSize,
6402
6524
  zip64UncompressedSize,
@@ -7494,6 +7616,7 @@
7494
7616
  exports.ERR_CENTRAL_DIRECTORY_NOT_FOUND = ERR_CENTRAL_DIRECTORY_NOT_FOUND;
7495
7617
  exports.ERR_DUPLICATED_NAME = ERR_DUPLICATED_NAME;
7496
7618
  exports.ERR_ENCRYPTED = ERR_ENCRYPTED;
7619
+ exports.ERR_ENCRYPTED_CENTRAL_DIRECTORY = ERR_ENCRYPTED_CENTRAL_DIRECTORY;
7497
7620
  exports.ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND;
7498
7621
  exports.ERR_EOCDR_NOT_FOUND = ERR_EOCDR_NOT_FOUND;
7499
7622
  exports.ERR_EXTRAFIELD_ZIP64_NOT_FOUND = ERR_EXTRAFIELD_ZIP64_NOT_FOUND;