@zip.js/zip.js 2.8.26 → 2.8.29

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 (53) hide show
  1. package/BENCHMARKS.md +168 -0
  2. package/README.md +2 -2
  3. package/deno.json +1 -1
  4. package/dist/zip-core.js +1113 -574
  5. package/dist/zip-core.min.js +1 -1
  6. package/dist/zip-fs-core.js +1209 -622
  7. package/dist/zip-fs-core.min.js +1 -1
  8. package/dist/zip-fs-native.js +1270 -632
  9. package/dist/zip-fs-native.min.js +1 -1
  10. package/dist/zip-fs.js +1347 -686
  11. package/dist/zip-fs.min.js +1 -1
  12. package/dist/zip-legacy.js +1114 -575
  13. package/dist/zip-legacy.min.js +1 -1
  14. package/dist/zip-module.wasm +0 -0
  15. package/dist/zip-native.js +1116 -577
  16. package/dist/zip-native.min.js +1 -1
  17. package/dist/zip-web-worker-native.js +1 -1
  18. package/dist/zip-web-worker.js +1 -1
  19. package/dist/zip.js +1193 -631
  20. package/dist/zip.min.js +1 -1
  21. package/eslint.config.mjs +6 -1
  22. package/index-native.cjs +1270 -632
  23. package/index-native.min.js +1 -1
  24. package/index.cjs +1347 -686
  25. package/index.d.ts +2451 -2355
  26. package/index.min.js +1 -1
  27. package/lib/core/codec-pool.js +39 -1
  28. package/lib/core/codec-worker.js +130 -35
  29. package/lib/core/configuration.js +3 -0
  30. package/lib/core/constants.js +6 -0
  31. package/lib/core/io.js +55 -25
  32. package/lib/core/options.js +2 -0
  33. package/lib/core/streams/aes-crypto-stream.js +12 -12
  34. package/lib/core/streams/codec-stream.js +11 -5
  35. package/lib/core/streams/codecs/sjcl.js +1 -33
  36. package/lib/core/streams/common-crypto.js +4 -4
  37. package/lib/core/streams/zip-crypto-stream.js +25 -15
  38. package/lib/core/streams/zip-entry-stream.js +89 -5
  39. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  40. package/lib/core/streams/zlib-wasm/zlib-streams.js +79 -56
  41. package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
  42. package/lib/core/util/decode-cp437.js +1 -1
  43. package/lib/core/util/decode-text.js +2 -1
  44. package/lib/core/web-worker-base.js +12 -4
  45. package/lib/core/web-worker-inline-native.js +1 -1
  46. package/lib/core/web-worker-inline-wasm.js +1 -1
  47. package/lib/core/zip-fs.js +154 -55
  48. package/lib/core/zip-reader.js +185 -67
  49. package/lib/core/zip-writer.js +613 -376
  50. package/lib/core/zlib-streams-inline.js +1 -1
  51. package/lib/zip-core-reader.js +3 -1
  52. package/lib/zip-core-writer.js +1 -0
  53. package/package.json +20 -6
@@ -26,9 +26,56 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global TransformStream */
29
+ /* global TransformStream, WritableStream */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
+ /*
33
+ * Internal state invariants
34
+ *
35
+ * Positions:
36
+ * - `zipWriter.offset` is the logical write position: the offset where the next entry will start
37
+ * in the final archive. It includes `zipWriter.initialOffset` (see below) and is only updated
38
+ * between entries, or by the recovery code in getFileEntry() when a write fails mid-entry.
39
+ * - `writer.size` counts the bytes actually written into the writer. Between entries,
40
+ * `zipWriter.offset - writer.size` is constant and equals `initialOffset`.
41
+ * - `zipWriter.initialOffset` is the number of bytes assumed to be prepended to the final archive
42
+ * (i.e. to the first segment when the archive is split): the `offset` option minus the data
43
+ * already stored in the writer. Positions within the first segment include it; positions within
44
+ * the other segments do not, see getSegmentOffset().
45
+ * - For split archives, `writer.diskOffset` is the total size of the closed segments and
46
+ * `writer.diskNumber` the index of the current segment. `writer.availableSize` is the remaining
47
+ * capacity of the current segment (Infinity otherwise); setting it to 0 and writing an empty
48
+ * chunk starts a new segment. This is how header records are kept whole within a segment, as
49
+ * required by APPNOTE section 8.5.2.
50
+ * - The segment coordinates of an entry (`diskNumberStart` and `offset`) must be read after
51
+ * calling skipDiskIfNeeded() and while holding the writer lock; they are unstable otherwise.
52
+ *
53
+ * Scheduling:
54
+ * - The central directory order (i.e. the order in which entries are listed by getEntries()) is
55
+ * the insertion order of the `files` map. addFile() reserves each entry's slot synchronously in
56
+ * add() call order, before initializing the reader, so concurrent entries whose readers
57
+ * initialize at different speeds are still listed in call order.
58
+ * - `fileEntry.lock` chains every entry to the previous one to reach getFileEntry(); awaiting it
59
+ * serializes the physical writes when `keepOrder` is set. The physical layout therefore follows
60
+ * the order in which the readers finish initializing, not necessarily the central directory
61
+ * order; each entry records its own `offset`, so the archive stays consistent either way.
62
+ * - `zipWriter.lockWriter`/`writerLocked` is the mutex over the writer; only the holder may write
63
+ * into it or read the position variables above.
64
+ * - An entry writes directly into the writer (i.e. without being buffered) only when the writer
65
+ * is unlocked and no buffered entry is in flight (`bufferedWrites` == 0). This avoids a
66
+ * deadlock, it is not an optimization: an entry written directly acquires the writer lock
67
+ * before awaiting the lock of the previous entry, while a buffered predecessor needs the
68
+ * writer lock to be flushed.
69
+ * - `lastFileEntry` is the tail of the lock chain (the most recently added entry).
70
+ *
71
+ * Failures:
72
+ * - When a write fails mid-entry, `zipWriter.offset` advances by the number of bytes written for
73
+ * the entry so that the offsets of subsequent entries remain correct, and
74
+ * `hasCorruptedEntries` is set on the ZipWriter.
75
+ * - closeFile() does not update `zipWriter.offset`; the position of the central directory and of
76
+ * the end of central directory records are derived from it.
77
+ */
78
+
32
79
  import {
33
80
  MAX_32_BITS,
34
81
  MAX_16_BITS,
@@ -41,6 +88,7 @@ import {
41
88
  LOCAL_FILE_HEADER_SIGNATURE,
42
89
  DATA_DESCRIPTOR_RECORD_SIGNATURE,
43
90
  CENTRAL_FILE_HEADER_SIGNATURE,
91
+ CENTRAL_FILE_HEADER_LENGTH,
44
92
  END_OF_CENTRAL_DIR_SIGNATURE,
45
93
  ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE,
46
94
  ZIP64_END_OF_CENTRAL_DIR_SIGNATURE,
@@ -56,12 +104,11 @@ import {
56
104
  EXTRAFIELD_TYPE_INFOZIP,
57
105
  EXTRAFIELD_TYPE_UNIX,
58
106
  END_OF_CENTRAL_DIR_LENGTH,
59
- ZIP64_END_OF_CENTRAL_DIR_LENGTH,
60
- ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH,
61
107
  ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH,
62
108
  BITFLAG_ENCRYPTED,
63
109
  BITFLAG_DATA_DESCRIPTOR,
64
110
  BITFLAG_LANG_ENCODING_FLAG,
111
+ BITFLAG_LEVEL,
65
112
  BITFLAG_LEVEL_FAST_MASK,
66
113
  BITFLAG_LEVEL_SUPER_FAST_MASK,
67
114
  BITFLAG_LEVEL_MAX_MASK,
@@ -81,9 +128,11 @@ import {
81
128
  VERSION_AES,
82
129
  DIRECTORY_SIGNATURE,
83
130
  HEADER_SIZE,
131
+ HEADER_OFFSET_VERSION,
84
132
  HEADER_OFFSET_SIGNATURE,
85
133
  HEADER_OFFSET_COMPRESSED_SIZE,
86
134
  HEADER_OFFSET_UNCOMPRESSED_SIZE,
135
+ LOCAL_HEADER_COMMON_OFFSET,
87
136
  MIN_DATE,
88
137
  MAX_DATE,
89
138
  UNDEFINED_VALUE,
@@ -169,13 +218,19 @@ const ERR_INVALID_VERSION = "Version exceeds 65535";
169
218
  const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
170
219
  const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
171
220
  const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
221
+ // same message as the constant exported by zip-reader.js
222
+ const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
223
+ const MIN_UNIX_TIME = -2147483648;
224
+ const MAX_UNIX_TIME = 2147483647;
172
225
  const ERR_UNSUPPORTED_FORMAT = "Zip64 is not supported (set the 'zip64' option to 'true')";
173
226
  const ERR_UNDEFINED_UNCOMPRESSED_SIZE = "Undefined uncompressed size";
227
+ const ERR_UNDEFINED_READER = "Undefined reader";
174
228
  const ERR_ZIP_NOT_EMPTY = "Zip file not empty";
175
229
  const ERR_INVALID_UID = "Invalid uid (must be integer 0..2^32-1)";
176
230
  const ERR_INVALID_GID = "Invalid gid (must be integer 0..2^32-1)";
177
231
  const ERR_INVALID_UNIX_MODE = "Invalid UNIX mode (must be integer 0..65535)";
178
232
  const ERR_INVALID_UNIX_EXTRA_FIELD_TYPE = "Invalid unixExtraFieldType (must be 'infozip' or 'unix')";
233
+ const ERR_INVALID_UNIX_ID_SIZE = "uid/gid must be 0..65535 for unixExtraFieldType 'unix' (use 'infozip' for larger ids)";
179
234
  const ERR_INVALID_MSDOS_ATTRIBUTES = "Invalid msdosAttributesRaw (must be integer 0..255)";
180
235
  const ERR_INVALID_MSDOS_DATA = "Invalid msdosAttributes (must be an object with boolean flags)";
181
236
 
@@ -201,9 +256,10 @@ class ZipWriter {
201
256
  files: new Map(),
202
257
  filenames: new Set(),
203
258
  offset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? writer.size || writer.writable.size || 0 : options[OPTION_OFFSET],
204
- pendingEntriesSize: 0,
259
+ initialOffset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? 0 : options[OPTION_OFFSET] - (writer.size || writer.writable.size || 0),
205
260
  pendingAddFileCalls: new Set(),
206
- bufferedWrites: 0
261
+ bufferedWrites: 0,
262
+ lastFileEntry: UNDEFINED_VALUE
207
263
  });
208
264
  }
209
265
 
@@ -212,17 +268,18 @@ class ZipWriter {
212
268
  throw new Error(ERR_ZIP_NOT_EMPTY);
213
269
  }
214
270
  reader = new GenericReader(reader);
271
+ await initStream(reader);
215
272
  const zipReader = new ZipReader(reader.readable);
216
273
  const entries = await zipReader.getEntries();
217
274
  await zipReader.close();
275
+ await initStream(this.writer);
218
276
  await reader.readable.pipeTo(this.writer.writable, { preventClose: true, preventAbort: true });
219
277
  this.writer.size = this.offset = reader.size;
220
278
  this.filenames = new Set(entries.map(entry => entry.filename));
221
279
  this.files = new Map(entries.map(entry => {
222
280
  const {
223
281
  version,
224
- compressionMethod,
225
- lastModDate,
282
+ rawLastModDate,
226
283
  lastAccessDate,
227
284
  creationDate,
228
285
  rawFilename,
@@ -235,6 +292,7 @@ class ZipWriter {
235
292
  zip64
236
293
  } = entry;
237
294
  let {
295
+ compressionMethod,
238
296
  rawExtraFieldZip64,
239
297
  rawExtraFieldAES,
240
298
  rawExtraFieldExtendedTimestamp,
@@ -247,31 +305,39 @@ class ZipWriter {
247
305
  rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
248
306
  rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
249
307
  rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
250
- rawExtraFieldUnix = entry.rawExtraFieldUnix || new Uint8Array();
308
+ rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
251
309
  rawExtraField = rawExtraField || new Uint8Array();
310
+ if (entry.extraFieldAES) {
311
+ compressionMethod = COMPRESSION_METHOD_AES;
312
+ }
252
313
  const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
253
- const zip64UncompressedSize = zip64 && uncompressedSize > MAX_32_BITS;
254
- const zip64CompressedSize = zip64 && compressedSize > MAX_32_BITS;
314
+ const zip64UncompressedSize = zip64 && uncompressedSize >= MAX_32_BITS;
315
+ const zip64CompressedSize = zip64 && compressedSize >= MAX_32_BITS;
316
+ const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
255
317
  const {
256
318
  headerArray,
257
319
  headerView
258
320
  } = getHeaderArrayData({
259
321
  version,
260
- bitFlag: getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod),
322
+ bitFlag: bitFlagValue,
261
323
  compressionMethod,
262
324
  uncompressedSize,
263
325
  compressedSize,
264
- lastModDate,
326
+ rawLastModDate,
265
327
  rawFilename,
266
328
  zip64CompressedSize,
267
329
  zip64UncompressedSize,
268
330
  extraFieldLength
269
331
  });
332
+ const { signature } = entry;
333
+ if (signature !== UNDEFINED_VALUE) {
334
+ setUint32(headerView, HEADER_OFFSET_SIGNATURE, signature);
335
+ }
270
336
  Object.assign(entry, {
271
337
  zip64UncompressedSize,
272
338
  zip64CompressedSize,
273
- zip64Offset: zip64 && this.offset - diskOffset > MAX_32_BITS,
274
- zip64DiskNumberStart: zip64 && diskNumber > MAX_16_BITS,
339
+ zip64Offset: zip64 && this.offset - diskOffset >= MAX_32_BITS,
340
+ zip64DiskNumberStart: zip64 && diskNumber >= MAX_16_BITS,
275
341
  rawExtraFieldZip64,
276
342
  rawExtraFieldAES,
277
343
  rawExtraFieldExtendedTimestamp,
@@ -289,6 +355,7 @@ class ZipWriter {
289
355
 
290
356
  async add(name = "", reader, options = {}) {
291
357
  const zipWriter = this;
358
+ options = Object.assign({}, options);
292
359
  const {
293
360
  pendingAddFileCalls,
294
361
  config
@@ -299,17 +366,24 @@ class ZipWriter {
299
366
  await new Promise(resolve => pendingEntries.push(resolve));
300
367
  }
301
368
  let promiseAddFile;
369
+ let nameAdded;
302
370
  try {
303
371
  name = name.trim();
372
+ if (getOptionValue(zipWriter, options, PROPERTY_NAME_DIRECTORY) && !name.endsWith(DIRECTORY_SIGNATURE)) {
373
+ name += DIRECTORY_SIGNATURE;
374
+ }
304
375
  if (zipWriter.filenames.has(name)) {
305
376
  throw new Error(ERR_DUPLICATED_NAME);
306
377
  }
307
378
  zipWriter.filenames.add(name);
379
+ nameAdded = true;
308
380
  promiseAddFile = addFile(zipWriter, name, reader, options);
309
381
  pendingAddFileCalls.add(promiseAddFile);
310
382
  return await promiseAddFile;
311
383
  } catch (error) {
312
- zipWriter.filenames.delete(name);
384
+ if (nameAdded) {
385
+ zipWriter.filenames.delete(name);
386
+ }
313
387
  throw error;
314
388
  } finally {
315
389
  pendingAddFileCalls.delete(promiseAddFile);
@@ -342,6 +416,9 @@ class ZipWriter {
342
416
  const zipWriter = this;
343
417
  const { pendingAddFileCalls, writer } = this;
344
418
  const { writable } = writer;
419
+ if (getLength(comment) > MAX_16_BITS) {
420
+ throw new Error(ERR_INVALID_COMMENT);
421
+ }
345
422
  while (pendingAddFileCalls.size) {
346
423
  await Promise.allSettled(Array.from(pendingAddFileCalls));
347
424
  }
@@ -360,27 +437,62 @@ class ZipWriterStream {
360
437
  const { readable, writable } = new TransformStream();
361
438
  this.readable = readable;
362
439
  this.zipWriter = new ZipWriter(writable, options);
440
+ this.pendingAddFileCalls = new Set();
363
441
  }
364
442
 
365
443
  transform(path) {
444
+ const zipWriter = this.zipWriter;
445
+ let streamController;
366
446
  const { readable, writable } = new TransformStream({
367
- flush: () => { this.zipWriter.close(); }
447
+ start(controller) {
448
+ streamController = controller;
449
+ },
450
+ flush: () => void closeArchive()
368
451
  });
369
- this.zipWriter.add(path, readable);
452
+ watchAddFileCall(this, this.zipWriter.add(path, readable), error => streamController.error(error));
370
453
  return { readable: this.readable, writable };
454
+
455
+ async function closeArchive() {
456
+ try {
457
+ await zipWriter.close();
458
+ } catch (error) {
459
+ try {
460
+ await zipWriter.writer.writable.abort(error);
461
+ } catch {
462
+ // ignored
463
+ }
464
+ }
465
+ }
371
466
  }
372
467
 
373
468
  writable(path) {
374
- const { readable, writable } = new TransformStream();
375
- this.zipWriter.add(path, readable);
469
+ let streamController;
470
+ const { readable, writable } = new TransformStream({
471
+ start(controller) {
472
+ streamController = controller;
473
+ }
474
+ });
475
+ watchAddFileCall(this, this.zipWriter.add(path, readable), error => streamController.error(error));
376
476
  return writable;
377
477
  }
378
478
 
379
- close(comment = UNDEFINED_VALUE, options = {}) {
479
+ async close(comment = UNDEFINED_VALUE, options = {}) {
480
+ await Promise.all(Array.from(this.pendingAddFileCalls));
380
481
  return this.zipWriter.close(comment, options);
381
482
  }
382
483
  }
383
484
 
485
+ function watchAddFileCall(zipWriterStream, promiseAddFile, onerror) {
486
+ zipWriterStream.pendingAddFileCalls.add(promiseAddFile);
487
+ promiseAddFile.catch(error => {
488
+ try {
489
+ onerror(error);
490
+ } catch {
491
+ // ignored
492
+ }
493
+ });
494
+ }
495
+
384
496
  export {
385
497
  ZipWriter,
386
498
  ZipWriterStream,
@@ -394,10 +506,42 @@ export {
394
506
  ERR_INVALID_ENCRYPTION_STRENGTH,
395
507
  ERR_UNSUPPORTED_FORMAT,
396
508
  ERR_UNDEFINED_UNCOMPRESSED_SIZE,
509
+ ERR_UNDEFINED_READER,
397
510
  ERR_ZIP_NOT_EMPTY
398
511
  };
399
512
 
400
513
  async function addFile(zipWriter, name, reader, options) {
514
+ const attributesInfo = resolveAttributes(zipWriter, name, options);
515
+ ({ name } = attributesInfo);
516
+ const metadataInfo = resolveMetadata(zipWriter, name, options);
517
+ const { comment } = metadataInfo;
518
+ const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
519
+ zipWriter.files.set(name, UNDEFINED_VALUE);
520
+ let fileEntry;
521
+ try {
522
+ const sizesInfo = await resolveSizes(zipWriter, reader, metadataInfo, options);
523
+ ({ reader } = sizesInfo);
524
+ const { diskOffset, diskNumber } = zipWriter.writer;
525
+ options = Object.assign({}, options, attributesInfo.resolvedOptions, metadataInfo.resolvedOptions, sizesInfo.resolvedOptions, {
526
+ internalFileAttribute: metadataInfo.resolvedOptions.internalFileAttributes,
527
+ externalFileAttribute: attributesInfo.resolvedOptions.externalFileAttributes,
528
+ signature: options[PROPERTY_NAME_SIGNATURE],
529
+ offset: zipWriter.offset - diskOffset,
530
+ diskNumberStart: diskNumber
531
+ });
532
+ const headerInfo = getHeaderInfo(options);
533
+ const dataDescriptorInfo = getDataDescriptorInfo(options);
534
+ const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
535
+ fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
536
+ } catch (error) {
537
+ zipWriter.files.delete(name);
538
+ throw error;
539
+ }
540
+ Object.assign(fileEntry, { name, comment, extraField });
541
+ return new Entry(fileEntry);
542
+ }
543
+
544
+ function resolveAttributes(zipWriter, name, options) {
401
545
  name = name.trim();
402
546
  let msDosCompatible = getOptionValue(zipWriter, options, PROPERTY_NAME_MS_DOS_COMPATIBLE);
403
547
  let versionMadeBy = getOptionValue(zipWriter, options, PROPERTY_NAME_VERSION_MADE_BY, msDosCompatible ? 20 : 768);
@@ -405,7 +549,7 @@ async function addFile(zipWriter, name, reader, options) {
405
549
  const uid = getOptionValue(zipWriter, options, PROPERTY_NAME_UID);
406
550
  const gid = getOptionValue(zipWriter, options, PROPERTY_NAME_GID);
407
551
  let unixMode = getOptionValue(zipWriter, options, PROPERTY_NAME_UNIX_MODE);
408
- const unixExtraFieldType = getOptionValue(zipWriter, options, OPTION_UNIX_EXTRA_FIELD_TYPE);
552
+ let unixExtraFieldType = getOptionValue(zipWriter, options, OPTION_UNIX_EXTRA_FIELD_TYPE);
409
553
  let setuid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETUID);
410
554
  let setgid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETGID);
411
555
  let sticky = getOptionValue(zipWriter, options, PROPERTY_NAME_STICKY);
@@ -421,6 +565,13 @@ async function addFile(zipWriter, name, reader, options) {
421
565
  if (unixExtraFieldType !== UNDEFINED_VALUE && unixExtraFieldType !== INFOZIP_EXTRA_FIELD_TYPE && unixExtraFieldType !== UNIX_EXTRA_FIELD_TYPE) {
422
566
  throw new Error(ERR_INVALID_UNIX_EXTRA_FIELD_TYPE);
423
567
  }
568
+ if (unixExtraFieldType === UNIX_EXTRA_FIELD_TYPE &&
569
+ ((uid !== UNDEFINED_VALUE && uid > MAX_16_BITS) || (gid !== UNDEFINED_VALUE && gid > MAX_16_BITS))) {
570
+ throw new Error(ERR_INVALID_UNIX_ID_SIZE);
571
+ }
572
+ if (unixExtraFieldType === UNDEFINED_VALUE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
573
+ unixExtraFieldType = INFOZIP_EXTRA_FIELD_TYPE;
574
+ }
424
575
  let msdosAttributesRaw = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES_RAW);
425
576
  let msdosAttributes = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES);
426
577
  const hasUnixMetadata = uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE || unixMode !== UNDEFINED_VALUE || unixExtraFieldType;
@@ -491,6 +642,27 @@ async function addFile(zipWriter, name, reader, options) {
491
642
  if (hasMsDosProvided) {
492
643
  externalFileAttributes = (externalFileAttributes & MAX_32_BITS) | (msdosAttributesRaw & MAX_8_BITS);
493
644
  }
645
+ return {
646
+ name,
647
+ resolvedOptions: {
648
+ versionMadeBy,
649
+ msDosCompatible,
650
+ externalFileAttributes,
651
+ unixExternalUpper,
652
+ uid,
653
+ gid,
654
+ unixMode,
655
+ unixExtraFieldType,
656
+ setuid,
657
+ setgid,
658
+ sticky,
659
+ msdosAttributesRaw,
660
+ msdosAttributes
661
+ }
662
+ };
663
+ }
664
+
665
+ function resolveMetadata(zipWriter, name, options) {
494
666
  const encode = getOptionValue(zipWriter, options, OPTION_ENCODE_TEXT, encodeText);
495
667
  let rawFilename = encode(name);
496
668
  if (rawFilename === UNDEFINED_VALUE) {
@@ -529,10 +701,14 @@ async function addFile(zipWriter, name, reader, options) {
529
701
  const transferStreams = getOptionValue(zipWriter, options, OPTION_TRANSFER_STREAMS, true);
530
702
  const bufferedWrite = getOptionValue(zipWriter, options, OPTION_BUFFERED_WRITE);
531
703
  const createTempStream = getOptionValue(zipWriter, options, OPTION_CREATE_TEMP_STREAM);
532
- const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE, false);
704
+ const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE, true);
533
705
  const signal = getOptionValue(zipWriter, options, OPTION_SIGNAL);
534
706
  const useUnicodeFileNames = getOptionValue(zipWriter, options, OPTION_USE_UNICODE_FILE_NAMES, true);
535
707
  const compressionMethod = getOptionValue(zipWriter, options, PROPERTY_NAME_COMPRESSION_METHOD);
708
+ if (!passThrough && compressionMethod !== UNDEFINED_VALUE &&
709
+ compressionMethod !== COMPRESSION_METHOD_STORE && compressionMethod !== COMPRESSION_METHOD_DEFLATE) {
710
+ throw new Error(ERR_UNSUPPORTED_COMPRESSION);
711
+ }
536
712
  let level = getOptionValue(zipWriter, options, OPTION_LEVEL);
537
713
  let useCompressionStream = getOptionValue(zipWriter, options, OPTION_USE_COMPRESSION_STREAM);
538
714
  let dataDescriptor = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR);
@@ -548,7 +724,7 @@ async function addFile(zipWriter, name, reader, options) {
548
724
  if (!useCompressionStream && (zipWriter.config.CompressionStream === UNDEFINED_VALUE && zipWriter.config.CompressionStreamZlib === UNDEFINED_VALUE)) {
549
725
  level = 0;
550
726
  }
551
- let zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
727
+ const zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
552
728
  if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
553
729
  throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
554
730
  }
@@ -559,6 +735,7 @@ async function addFile(zipWriter, name, reader, options) {
559
735
  let offset = 0;
560
736
  extraField.forEach(data => extraFieldSize += 4 + getLength(data));
561
737
  rawExtraField = new Uint8Array(extraFieldSize);
738
+ const rawExtraFieldView = getDataView(rawExtraField);
562
739
  extraField.forEach((data, type) => {
563
740
  if (type > MAX_16_BITS) {
564
741
  throw new Error(ERR_INVALID_EXTRAFIELD_TYPE);
@@ -566,22 +743,72 @@ async function addFile(zipWriter, name, reader, options) {
566
743
  if (getLength(data) > MAX_16_BITS) {
567
744
  throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
568
745
  }
569
- arraySet(rawExtraField, new Uint16Array([type]), offset);
570
- arraySet(rawExtraField, new Uint16Array([getLength(data)]), offset + 2);
746
+ setUint16(rawExtraFieldView, offset, type);
747
+ setUint16(rawExtraFieldView, offset + 2, getLength(data));
571
748
  arraySet(rawExtraField, data, offset + 4);
572
749
  offset += 4 + getLength(data);
573
750
  });
574
751
  }
752
+ return {
753
+ comment,
754
+ resolvedOptions: {
755
+ rawFilename,
756
+ rawComment,
757
+ version,
758
+ lastModDate,
759
+ lastAccessDate,
760
+ creationDate,
761
+ internalFileAttributes,
762
+ passThrough,
763
+ password,
764
+ rawPassword,
765
+ encryptionStrength,
766
+ zipCrypto,
767
+ extendedTimestamp,
768
+ keepOrder,
769
+ useWebWorkers,
770
+ transferStreams,
771
+ bufferedWrite,
772
+ createTempStream,
773
+ dataDescriptorSignature,
774
+ signal,
775
+ useUnicodeFileNames,
776
+ compressionMethod,
777
+ level,
778
+ useCompressionStream,
779
+ dataDescriptor,
780
+ zip64,
781
+ rawExtraField
782
+ }
783
+ };
784
+ }
785
+
786
+ async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, options) {
787
+ const { passThrough, zipCrypto, password, rawPassword, encryptionStrength } = metadata;
788
+ let { dataDescriptor, zip64, level, compressionMethod } = metadata;
575
789
  let maximumCompressedSize = 0;
576
- let maximumEntrySize = 0;
577
790
  let uncompressedSize = 0;
578
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
+ if (!reader) {
795
+ throw new Error(ERR_UNDEFINED_READER);
796
+ }
579
797
  uncompressedSize = options[PROPERTY_NAME_UNCOMPRESSED_SIZE];
580
798
  if (uncompressedSize === UNDEFINED_VALUE) {
581
799
  throw new Error(ERR_UNDEFINED_UNCOMPRESSED_SIZE);
582
800
  }
583
801
  }
584
802
  const zip64Enabled = zip64 === true;
803
+ 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
+ const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
807
+ if (!reader) {
808
+ level = 0;
809
+ compressionMethod = COMPRESSION_METHOD_STORE;
810
+ }
811
+ const encryptionOverhead = encryptedEntry ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
585
812
  if (reader) {
586
813
  reader = new GenericReader(reader);
587
814
  await initStream(reader);
@@ -594,16 +821,15 @@ async function addFile(zipWriter, name, reader, options) {
594
821
  }
595
822
  } else {
596
823
  options.uncompressedSize = uncompressedSize = reader.size;
597
- maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
824
+ maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
598
825
  }
599
826
  } else {
600
827
  options.uncompressedSize = uncompressedSize;
601
- maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
828
+ maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
602
829
  }
603
830
  }
604
- const { diskOffset, diskNumber } = zipWriter.writer;
605
- const zip64UncompressedSize = zip64Enabled || uncompressedSize > MAX_32_BITS;
606
- const zip64CompressedSize = zip64Enabled || maximumCompressedSize > MAX_32_BITS;
831
+ const zip64UncompressedSize = zip64Enabled || uncompressedSize >= MAX_32_BITS;
832
+ const zip64CompressedSize = zip64Enabled || maximumCompressedSize >= MAX_32_BITS;
607
833
  if (zip64UncompressedSize || zip64CompressedSize) {
608
834
  if (zip64 === false) {
609
835
  throw new Error(ERR_UNSUPPORTED_FORMAT);
@@ -612,73 +838,19 @@ async function addFile(zipWriter, name, reader, options) {
612
838
  }
613
839
  }
614
840
  zip64 = zip64 || false;
615
- const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
616
- options = Object.assign({}, options, {
617
- rawFilename,
618
- rawComment,
619
- version,
620
- versionMadeBy,
621
- lastModDate,
622
- lastAccessDate,
623
- creationDate,
624
- rawExtraField,
625
- zip64,
626
- zip64UncompressedSize,
627
- zip64CompressedSize,
628
- password,
629
- rawPassword,
630
- level,
631
- useWebWorkers,
632
- transferStreams,
633
- encryptionStrength,
634
- extendedTimestamp,
635
- zipCrypto,
636
- bufferedWrite,
637
- createTempStream,
638
- keepOrder,
639
- useUnicodeFileNames,
640
- dataDescriptor,
641
- dataDescriptorSignature,
642
- signal,
643
- msDosCompatible,
644
- internalFileAttribute: internalFileAttributes,
645
- internalFileAttributes,
646
- externalFileAttribute: externalFileAttributes,
647
- externalFileAttributes,
648
- useCompressionStream,
649
- passThrough,
650
- encrypted: Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted),
651
- signature: options[PROPERTY_NAME_SIGNATURE],
652
- compressionMethod,
653
- uncompressedSize,
654
- offset: zipWriter.offset - diskOffset,
655
- diskNumberStart: diskNumber,
656
- uid,
657
- gid,
658
- setuid,
659
- setgid,
660
- sticky,
661
- unixMode,
662
- msdosAttributesRaw,
663
- msdosAttributes,
664
- unixExternalUpper
665
- });
666
- const headerInfo = getHeaderInfo(options);
667
- const dataDescriptorInfo = getDataDescriptorInfo(options);
668
- const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
669
- maximumEntrySize = metadataSize + maximumCompressedSize;
670
- if (zipWriter.options[OPTION_USDZ]) {
671
- maximumEntrySize += maximumEntrySize + 64;
672
- }
673
- zipWriter.pendingEntriesSize += maximumEntrySize;
674
- let fileEntry;
675
- try {
676
- fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
677
- } finally {
678
- zipWriter.pendingEntriesSize -= maximumEntrySize;
679
- }
680
- Object.assign(fileEntry, { name, comment, extraField });
681
- return new Entry(fileEntry);
841
+ return {
842
+ reader,
843
+ resolvedOptions: {
844
+ dataDescriptor,
845
+ zip64,
846
+ zip64UncompressedSize,
847
+ zip64CompressedSize,
848
+ uncompressedSize,
849
+ level,
850
+ compressionMethod,
851
+ encrypted: encryptedEntry
852
+ }
853
+ };
682
854
  }
683
855
 
684
856
  async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
@@ -695,37 +867,40 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
695
867
  headerInfo
696
868
  } = entryInfo;
697
869
  const usdz = zipWriter.options[OPTION_USDZ];
698
- const previousFileEntry = Array.from(files.values()).pop();
870
+ const previousFileEntry = zipWriter.lastFileEntry;
699
871
  let fileEntry = {};
700
872
  let bufferedWrite;
701
873
  let releaseLockWriter;
702
874
  let releaseLockCurrentFileEntry;
703
875
  let writingBufferedEntryData;
704
876
  let writingEntryData;
877
+ let writerSizeBeforeEntry;
878
+ let flushedBufferedSize = 0;
705
879
  let fileWriter;
706
880
  files.set(name, fileEntry);
881
+ zipWriter.lastFileEntry = fileEntry;
707
882
  try {
708
883
  let lockPreviousFileEntry;
709
884
  if (keepOrder) {
710
885
  lockPreviousFileEntry = previousFileEntry && previousFileEntry.lock;
711
886
  requestLockCurrentFileEntry();
712
887
  }
713
- if ((options.bufferedWrite || !keepOrder || zipWriter.writerLocked || zipWriter.bufferedWrites || !dataDescriptor) && !usdz) {
888
+ if (options.bufferedWrite || !keepOrder || zipWriter.writerLocked || zipWriter.bufferedWrites || !dataDescriptor) {
889
+ bufferedWrite = true;
890
+ zipWriter.bufferedWrites++;
714
891
  if (options.createTempStream) {
715
892
  fileWriter = await options.createTempStream();
716
893
  } else {
717
894
  fileWriter = new TransformStream(UNDEFINED_VALUE, UNDEFINED_VALUE, { highWaterMark: INFINITY_VALUE });
718
895
  }
719
896
  fileWriter.size = 0;
720
- bufferedWrite = true;
721
- zipWriter.bufferedWrites++;
722
897
  await initStream(writer);
723
898
  } else {
724
899
  fileWriter = writer;
725
900
  await requestLockWriter();
726
901
  }
727
902
  await initStream(fileWriter);
728
- const { writable, diskOffset } = writer;
903
+ const { diskOffset } = writer;
729
904
  if (zipWriter.addSplitZipSignature) {
730
905
  delete zipWriter.addSplitZipSignature;
731
906
  const signatureArray = new Uint8Array(4);
@@ -734,21 +909,20 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
734
909
  await writeData(writer, signatureArray);
735
910
  zipWriter.offset += 4;
736
911
  }
737
- if (usdz) {
912
+ if (usdz && !bufferedWrite) {
738
913
  appendExtraFieldUSDZ(entryInfo, zipWriter.offset - diskOffset);
739
914
  }
740
- const {
741
- localHeaderView,
742
- localHeaderArray
743
- } = headerInfo;
915
+ const { localHeaderArray } = headerInfo;
744
916
  if (!bufferedWrite) {
745
917
  await lockPreviousFileEntry;
746
- await skipDiskIfNeeded(writable);
918
+ await skipDiskIfNeeded();
747
919
  }
748
- const { diskNumber } = writer;
749
- fileEntry.diskNumberStart = diskNumber;
920
+ const diskNumberStart = writer.diskNumber;
921
+ const entryOffset = getSegmentOffset(zipWriter, writer);
922
+ fileEntry.diskNumberStart = diskNumberStart;
750
923
  if (!bufferedWrite) {
751
924
  writingEntryData = true;
925
+ writerSizeBeforeEntry = writer.size;
752
926
  await writeData(fileWriter, localHeaderArray);
753
927
  }
754
928
  fileEntry = await createFileEntry(reader, fileWriter, fileEntry, entryInfo, zipWriter.config, options);
@@ -761,16 +935,24 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
761
935
  await Promise.all([fileWriter.writable.getWriter().close(), lockPreviousFileEntry]);
762
936
  await requestLockWriter();
763
937
  writingBufferedEntryData = true;
938
+ writerSizeBeforeEntry = writer.size;
939
+ await skipDiskIfNeeded();
764
940
  fileEntry.diskNumberStart = writer.diskNumber;
765
- fileEntry.offset = zipWriter.offset - writer.diskOffset;
766
- updateLocalHeader(fileEntry, localHeaderView, options);
767
- await skipDiskIfNeeded(writable);
768
- await writeData(writer, localHeaderArray);
769
- await fileWriter.readable.pipeTo(writable, { preventClose: true, preventAbort: true, signal });
941
+ fileEntry.offset = getSegmentOffset(zipWriter, writer);
942
+ if (usdz) {
943
+ // the padding depends on the entry offset, only known now that the writer is locked
944
+ const previousMetadataSize = entryInfo.metadataSize;
945
+ appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
946
+ fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
947
+ }
948
+ updateLocalHeader(fileEntry, headerInfo.localHeaderView, options);
949
+ await writeData(writer, headerInfo.localHeaderArray);
950
+ await flushBufferedData(fileWriter.readable, writer, signal, chunkLength => flushedBufferedSize += chunkLength);
770
951
  writer.size += fileWriter.size;
771
952
  writingBufferedEntryData = false;
772
953
  } else {
773
- fileEntry.offset = zipWriter.offset - diskOffset;
954
+ fileEntry.diskNumberStart = diskNumberStart;
955
+ fileEntry.offset = entryOffset;
774
956
  }
775
957
  zipWriter.offset += fileEntry.size;
776
958
  return fileEntry;
@@ -784,10 +966,9 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
784
966
  // ignored
785
967
  }
786
968
  }
969
+ zipWriter.offset += writer.size - writerSizeBeforeEntry;
787
970
  if (bufferedWrite) {
788
- zipWriter.offset += fileWriter.size;
789
- } else {
790
- zipWriter.offset = fileWriter.size;
971
+ zipWriter.offset += flushedBufferedSize;
791
972
  }
792
973
  }
793
974
  files.delete(name);
@@ -818,10 +999,10 @@ async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
818
999
  await lockWriter;
819
1000
  }
820
1001
 
821
- async function skipDiskIfNeeded(writable) {
1002
+ async function skipDiskIfNeeded() {
822
1003
  if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
823
1004
  writer.availableSize = 0;
824
- await writeData(writable, new Uint8Array());
1005
+ await writeData(writer, new Uint8Array());
825
1006
  }
826
1007
  }
827
1008
  }
@@ -962,6 +1143,10 @@ async function createFileEntry(reader, writer, { diskNumberStart, lock }, entryI
962
1143
  uncompressedSize = result.inputSize;
963
1144
  signature = result.signature;
964
1145
  }
1146
+ if ((!zip64CompressedSize && compressedSize >= MAX_32_BITS) ||
1147
+ (!zip64UncompressedSize && uncompressedSize >= MAX_32_BITS)) {
1148
+ throw new Error(ERR_UNSUPPORTED_FORMAT);
1149
+ }
965
1150
  } catch (error) {
966
1151
  if (error.outputSize !== UNDEFINED_VALUE) {
967
1152
  writer.size += error.outputSize;
@@ -1024,35 +1209,28 @@ function getHeaderInfo(options) {
1024
1209
  uncompressedSize
1025
1210
  } = options;
1026
1211
  let { version, compressionMethod } = options;
1027
- const compressed = !directory && (level > 0 || (level === UNDEFINED_VALUE && compressionMethod !== 0));
1212
+ const compressed = !directory && (compressionMethod === UNDEFINED_VALUE
1213
+ ? (level === UNDEFINED_VALUE || level > 0)
1214
+ : compressionMethod !== COMPRESSION_METHOD_STORE);
1028
1215
  let rawLocalExtraFieldZip64;
1029
1216
  const uncompressedFile = passThrough || !compressed;
1030
- const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
1031
- if (zip64) {
1032
- let rawLocalExtraFieldZip64Length = 4;
1033
- if (zip64UncompressedSize) {
1034
- rawLocalExtraFieldZip64Length += 8;
1035
- }
1036
- if (zip64CompressedSize) {
1037
- rawLocalExtraFieldZip64Length += 8;
1038
- }
1039
- rawLocalExtraFieldZip64 = new Uint8Array(rawLocalExtraFieldZip64Length);
1040
- const rawLocalExtraFieldZip64View = getDataView(rawLocalExtraFieldZip64);
1041
- setUint16(rawLocalExtraFieldZip64View, 0, EXTRAFIELD_TYPE_ZIP64);
1042
- setUint16(rawLocalExtraFieldZip64View, 2, getLength(rawLocalExtraFieldZip64) - 4);
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
+ const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
1220
+ const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
1221
+ 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
+ const length = 4 + 16;
1225
+ const extraFieldZip64 = createRecordWriter(length);
1226
+ extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
1227
+ extraFieldZip64.uint16(length - 4);
1228
+ rawLocalExtraFieldZip64 = extraFieldZip64.array;
1043
1229
  if (zip64ExtraFieldComplete) {
1044
- const rawLocalExtraFieldZip64View = getDataView(rawLocalExtraFieldZip64);
1045
- let rawLocalExtraFieldZip64Offset = 4;
1046
- if (zip64UncompressedSize) {
1047
- setBigUint64(rawLocalExtraFieldZip64View, rawLocalExtraFieldZip64Offset, BigInt(uncompressedSize));
1048
- rawLocalExtraFieldZip64Offset += 8;
1049
- }
1050
- if (zip64CompressedSize && uncompressedFile) {
1051
- setBigUint64(rawLocalExtraFieldZip64View, rawLocalExtraFieldZip64Offset, BigInt(uncompressedSize));
1052
- rawLocalExtraFieldZip64Offset += 8;
1053
- }
1054
- if (rawLocalExtraFieldZip64Offset == 4) {
1055
- rawLocalExtraFieldZip64 = new Uint8Array();
1230
+ extraFieldZip64.uint64(uncompressedSize);
1231
+ if (uncompressedFile) {
1232
+ const encryptionOverhead = encrypted ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
1233
+ extraFieldZip64.uint64(passThrough ? 0 : uncompressedSize + encryptionOverhead);
1056
1234
  }
1057
1235
  }
1058
1236
  } else {
@@ -1060,11 +1238,11 @@ function getHeaderInfo(options) {
1060
1238
  }
1061
1239
  let rawExtraFieldAES;
1062
1240
  if (encrypted && !zipCrypto) {
1063
- rawExtraFieldAES = new Uint8Array(getLength(EXTRAFIELD_DATA_AES) + 2);
1064
- const extraFieldAESView = getDataView(rawExtraFieldAES);
1065
- setUint16(extraFieldAESView, 0, EXTRAFIELD_TYPE_AES);
1066
- arraySet(rawExtraFieldAES, EXTRAFIELD_DATA_AES, 2);
1067
- setUint8(extraFieldAESView, 8, encryptionStrength);
1241
+ const extraFieldAES = createRecordWriter(getLength(EXTRAFIELD_DATA_AES) + 2);
1242
+ extraFieldAES.uint16(EXTRAFIELD_TYPE_AES);
1243
+ extraFieldAES.bytes(EXTRAFIELD_DATA_AES);
1244
+ rawExtraFieldAES = extraFieldAES.array;
1245
+ rawExtraFieldAES[8] = encryptionStrength;
1068
1246
  } else {
1069
1247
  rawExtraFieldAES = new Uint8Array();
1070
1248
  }
@@ -1072,33 +1250,38 @@ function getHeaderInfo(options) {
1072
1250
  let rawExtraFieldExtendedTimestamp;
1073
1251
  let extraFieldExtendedTimestampFlag;
1074
1252
  if (extendedTimestamp) {
1075
- rawExtraFieldExtendedTimestamp = new Uint8Array(9 + (lastAccessDate ? 4 : 0) + (creationDate ? 4 : 0));
1076
- const extraFieldExtendedTimestampView = getDataView(rawExtraFieldExtendedTimestamp);
1077
- setUint16(extraFieldExtendedTimestampView, 0, EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1078
- setUint16(extraFieldExtendedTimestampView, 2, getLength(rawExtraFieldExtendedTimestamp) - 4);
1079
- extraFieldExtendedTimestampFlag = 0x1 + (lastAccessDate ? 0x2 : 0) + (creationDate ? 0x4 : 0);
1080
- setUint8(extraFieldExtendedTimestampView, 4, extraFieldExtendedTimestampFlag);
1081
- let offset = 5;
1082
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(lastModDate.getTime() / 1000));
1083
- offset += 4;
1084
- if (lastAccessDate) {
1085
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(lastAccessDate.getTime() / 1000));
1086
- offset += 4;
1087
- }
1088
- if (creationDate) {
1089
- setUint32(extraFieldExtendedTimestampView, offset, Math.floor(creationDate.getTime() / 1000));
1253
+ const lastModTimeUnix = getTimeUnix(lastModDate);
1254
+ if (inUnixTimeRange(lastModTimeUnix)) {
1255
+ const extraFieldTimestampLength = 9 + (lastAccessDate ? 4 : 0) + (creationDate ? 4 : 0);
1256
+ const extraFieldTimestamp = createRecordWriter(extraFieldTimestampLength);
1257
+ extraFieldExtendedTimestampFlag = 0x1 + (lastAccessDate ? 0x2 : 0) + (creationDate ? 0x4 : 0);
1258
+ extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1259
+ extraFieldTimestamp.uint16(extraFieldTimestampLength - 4);
1260
+ extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
1261
+ extraFieldTimestamp.uint32(lastModTimeUnix);
1262
+ if (lastAccessDate) {
1263
+ extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(lastAccessDate)));
1264
+ }
1265
+ if (creationDate) {
1266
+ extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(creationDate)));
1267
+ }
1268
+ rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
1269
+ } else {
1270
+ // the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
1271
+ rawExtraFieldExtendedTimestamp = new Uint8Array();
1090
1272
  }
1091
1273
  try {
1092
- rawExtraFieldNTFS = new Uint8Array(36);
1093
- const extraFieldNTFSView = getDataView(rawExtraFieldNTFS);
1094
1274
  const lastModTimeNTFS = getTimeNTFS(lastModDate);
1095
- setUint16(extraFieldNTFSView, 0, EXTRAFIELD_TYPE_NTFS);
1096
- setUint16(extraFieldNTFSView, 2, 32);
1097
- setUint16(extraFieldNTFSView, 8, EXTRAFIELD_TYPE_NTFS_TAG1);
1098
- setUint16(extraFieldNTFSView, 10, 24);
1099
- setBigUint64(extraFieldNTFSView, 12, lastModTimeNTFS);
1100
- setBigUint64(extraFieldNTFSView, 20, getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
1101
- setBigUint64(extraFieldNTFSView, 28, getTimeNTFS(creationDate) || lastModTimeNTFS);
1275
+ const extraFieldNTFS = createRecordWriter(36);
1276
+ extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS);
1277
+ extraFieldNTFS.uint16(32);
1278
+ extraFieldNTFS.skip(4);
1279
+ extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS_TAG1);
1280
+ extraFieldNTFS.uint16(24);
1281
+ extraFieldNTFS.uint64(lastModTimeNTFS);
1282
+ extraFieldNTFS.uint64(getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
1283
+ extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
1284
+ rawExtraFieldNTFS = extraFieldNTFS.array;
1102
1285
  } catch {
1103
1286
  rawExtraFieldNTFS = new Uint8Array();
1104
1287
  }
@@ -1107,41 +1290,27 @@ function getHeaderInfo(options) {
1107
1290
  }
1108
1291
  let rawExtraFieldUnix;
1109
1292
  try {
1110
- const { uid, gid, unixMode, setuid, setgid, sticky, unixExtraFieldType } = options;
1111
- if (unixExtraFieldType && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE || unixMode !== UNDEFINED_VALUE)) {
1293
+ const { uid, gid, unixExtraFieldType } = options;
1294
+ if (unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
1112
1295
  const uidBytes = packUnixId(uid);
1113
1296
  const gidBytes = packUnixId(gid);
1114
- let modeArray = new Uint8Array();
1115
- if (unixExtraFieldType == UNIX_EXTRA_FIELD_TYPE && unixMode !== UNDEFINED_VALUE) {
1116
- let modeToWrite = unixMode & MAX_16_BITS;
1117
- if (setuid) {
1118
- modeToWrite |= FILE_ATTR_UNIX_SETUID_MASK;
1119
- }
1120
- if (setgid) {
1121
- modeToWrite |= FILE_ATTR_UNIX_SETGID_MASK;
1122
- }
1123
- if (sticky) {
1124
- modeToWrite |= FILE_ATTR_UNIX_STICKY_MASK;
1125
- }
1126
- modeArray = new Uint8Array(2);
1127
- const modeDataView = new DataView(modeArray.buffer);
1128
- modeDataView.setUint16(0, modeToWrite, true);
1129
- }
1130
- const payloadLength = 3 + uidBytes.length + gidBytes.length + modeArray.length;
1131
- rawExtraFieldUnix = new Uint8Array(4 + payloadLength);
1132
- const rawExtraFieldUnixView = getDataView(rawExtraFieldUnix);
1133
- setUint16(rawExtraFieldUnixView, 0, unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE ? EXTRAFIELD_TYPE_INFOZIP : EXTRAFIELD_TYPE_UNIX);
1134
- setUint16(rawExtraFieldUnixView, 2, payloadLength);
1135
- setUint8(rawExtraFieldUnixView, 4, 1);
1136
- setUint8(rawExtraFieldUnixView, 5, uidBytes.length);
1137
- let offset = 6;
1138
- arraySet(rawExtraFieldUnix, uidBytes, offset);
1139
- offset += uidBytes.length;
1140
- setUint8(rawExtraFieldUnixView, offset, gidBytes.length);
1141
- offset++;
1142
- arraySet(rawExtraFieldUnix, gidBytes, offset);
1143
- offset += gidBytes.length;
1144
- arraySet(rawExtraFieldUnix, modeArray, offset);
1297
+ const payloadLength = 3 + uidBytes.length + gidBytes.length;
1298
+ const extraFieldUnix = createRecordWriter(4 + payloadLength);
1299
+ extraFieldUnix.uint16(EXTRAFIELD_TYPE_INFOZIP);
1300
+ extraFieldUnix.uint16(payloadLength);
1301
+ extraFieldUnix.uint8(1);
1302
+ extraFieldUnix.uint8(uidBytes.length);
1303
+ extraFieldUnix.bytes(uidBytes);
1304
+ extraFieldUnix.uint8(gidBytes.length);
1305
+ extraFieldUnix.bytes(gidBytes);
1306
+ rawExtraFieldUnix = extraFieldUnix.array;
1307
+ } else if (unixExtraFieldType == UNIX_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
1308
+ const extraFieldUnix = createRecordWriter(8);
1309
+ extraFieldUnix.uint16(EXTRAFIELD_TYPE_UNIX);
1310
+ extraFieldUnix.uint16(4);
1311
+ extraFieldUnix.uint16((uid === UNDEFINED_VALUE ? 0 : uid) & MAX_16_BITS);
1312
+ extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
1313
+ rawExtraFieldUnix = extraFieldUnix.array;
1145
1314
  } else {
1146
1315
  rawExtraFieldUnix = new Uint8Array();
1147
1316
  }
@@ -1159,8 +1328,11 @@ function getHeaderInfo(options) {
1159
1328
  rawExtraFieldAES[9] = compressionMethod;
1160
1329
  compressionMethod = COMPRESSION_METHOD_AES;
1161
1330
  }
1162
- const localExtraFieldZip64Length = zip64ExtraFieldComplete ? getLength(rawLocalExtraFieldZip64) : 0;
1331
+ const localExtraFieldZip64Length = writeLocalExtraFieldZip64 ? getLength(rawLocalExtraFieldZip64) : 0;
1163
1332
  const extraFieldLength = localExtraFieldZip64Length + getLength(rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
1333
+ if (extraFieldLength > MAX_16_BITS) {
1334
+ throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
1335
+ }
1164
1336
  const {
1165
1337
  headerArray,
1166
1338
  headerView,
@@ -1176,29 +1348,27 @@ function getHeaderInfo(options) {
1176
1348
  zip64UncompressedSize,
1177
1349
  extraFieldLength
1178
1350
  });
1179
- let localHeaderOffset = HEADER_SIZE;
1180
- const localHeaderArray = new Uint8Array(localHeaderOffset + getLength(rawFilename) + extraFieldLength);
1351
+ const localHeader = createRecordWriter(HEADER_SIZE + getLength(rawFilename) + extraFieldLength);
1352
+ const localHeaderArray = localHeader.array;
1181
1353
  const localHeaderView = getDataView(localHeaderArray);
1182
- setUint32(localHeaderView, 0, LOCAL_FILE_HEADER_SIGNATURE);
1183
- arraySet(localHeaderArray, headerArray, 4);
1184
- arraySet(localHeaderArray, rawFilename, localHeaderOffset);
1185
- localHeaderOffset += getLength(rawFilename);
1186
- if (zip64ExtraFieldComplete) {
1187
- arraySet(localHeaderArray, rawLocalExtraFieldZip64, localHeaderOffset);
1188
- }
1189
- localHeaderOffset += localExtraFieldZip64Length;
1190
- arraySet(localHeaderArray, rawExtraFieldAES, localHeaderOffset);
1191
- localHeaderOffset += getLength(rawExtraFieldAES);
1192
- arraySet(localHeaderArray, rawExtraFieldExtendedTimestamp, localHeaderOffset);
1193
- localHeaderOffset += getLength(rawExtraFieldExtendedTimestamp);
1194
- arraySet(localHeaderArray, rawExtraFieldNTFS, localHeaderOffset);
1195
- localHeaderOffset += getLength(rawExtraFieldNTFS);
1196
- arraySet(localHeaderArray, rawExtraFieldUnix, localHeaderOffset);
1197
- localHeaderOffset += getLength(rawExtraFieldUnix);
1198
- arraySet(localHeaderArray, rawExtraField, localHeaderOffset);
1354
+ localHeader.uint32(LOCAL_FILE_HEADER_SIGNATURE);
1355
+ localHeader.bytes(headerArray);
1356
+ localHeader.bytes(rawFilename);
1357
+ if (writeLocalExtraFieldZip64) {
1358
+ localHeader.bytes(rawLocalExtraFieldZip64);
1359
+ }
1360
+ localHeader.bytes(rawExtraFieldAES);
1361
+ localHeader.bytes(rawExtraFieldExtendedTimestamp);
1362
+ localHeader.bytes(rawExtraFieldNTFS);
1363
+ localHeader.bytes(rawExtraFieldUnix);
1364
+ localHeader.bytes(rawExtraField);
1199
1365
  if (dataDescriptor) {
1200
- setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + 4, 0);
1201
- setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + 4, 0);
1366
+ if (!zip64CompressedSize) {
1367
+ setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
1368
+ }
1369
+ if (!zip64UncompressedSize) {
1370
+ setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
1371
+ }
1202
1372
  }
1203
1373
  return {
1204
1374
  localHeaderArray,
@@ -1225,7 +1395,7 @@ function getHeaderInfo(options) {
1225
1395
  function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
1226
1396
  const { headerInfo } = entryInfo;
1227
1397
  let { localHeaderArray, extraFieldLength } = headerInfo;
1228
- let localHeaderArrayView = getDataView(localHeaderArray);
1398
+ let localHeaderArrayView;
1229
1399
  let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
1230
1400
  if (extraBytesLength < 4) {
1231
1401
  extraBytesLength += 64;
@@ -1233,13 +1403,14 @@ function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
1233
1403
  const rawExtraFieldUSDZ = new Uint8Array(extraBytesLength);
1234
1404
  const extraFieldUSDZView = getDataView(rawExtraFieldUSDZ);
1235
1405
  setUint16(extraFieldUSDZView, 0, EXTRAFIELD_TYPE_USDZ);
1236
- setUint16(extraFieldUSDZView, 2, extraBytesLength - 2);
1406
+ setUint16(extraFieldUSDZView, 2, extraBytesLength - 4);
1237
1407
  const previousLocalHeaderArray = localHeaderArray;
1238
1408
  headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
1239
1409
  arraySet(localHeaderArray, previousLocalHeaderArray);
1240
1410
  arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
1241
1411
  localHeaderArrayView = getDataView(localHeaderArray);
1242
1412
  setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
1413
+ headerInfo.localHeaderView = localHeaderArrayView;
1243
1414
  entryInfo.metadataSize += extraBytesLength;
1244
1415
  }
1245
1416
 
@@ -1362,37 +1533,32 @@ function updateLocalHeader({
1362
1533
  }, localHeaderView, { dataDescriptor }) {
1363
1534
  if (!dataDescriptor) {
1364
1535
  if (!encrypted) {
1365
- setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + 4, signature);
1536
+ setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + LOCAL_HEADER_COMMON_OFFSET, signature);
1366
1537
  }
1367
1538
  if (!zip64CompressedSize) {
1368
- setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + 4, compressedSize);
1539
+ setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, compressedSize);
1369
1540
  }
1370
1541
  if (!zip64UncompressedSize) {
1371
- setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + 4, uncompressedSize);
1542
+ setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, uncompressedSize);
1372
1543
  }
1373
1544
  }
1374
1545
  if (zip64 && localExtraFieldZip64Length) {
1375
- let localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
1376
- if (zip64UncompressedSize) {
1377
- setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
1378
- localHeaderOffset += 8;
1379
- }
1380
- if (zip64CompressedSize) {
1381
- setBigUint64(localHeaderView, localHeaderOffset, BigInt(compressedSize));
1382
- localHeaderOffset += 8;
1383
- }
1546
+ // the local zip64 extra field always contains both size slots
1547
+ const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
1548
+ setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
1549
+ setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
1384
1550
  }
1385
1551
  }
1386
1552
 
1387
1553
 
1388
1554
  async function closeFile(zipWriter, comment, options) {
1389
- const { files, writer } = zipWriter;
1390
- const { diskOffset } = writer;
1391
- let { diskNumber } = writer;
1392
- let offset = 0;
1555
+ const directoryDataLength = createDirectoryRecords(zipWriter.files);
1556
+ const { cdStartDiskNumber, cdStartDiskOffset } = await writeDirectoryRecords(zipWriter, directoryDataLength, options);
1557
+ await writeEndOfDirectoryRecord(zipWriter, comment, options, { cdStartDiskNumber, cdStartDiskOffset, directoryDataLength });
1558
+ }
1559
+
1560
+ function createDirectoryRecords(files) {
1393
1561
  let directoryDataLength = 0;
1394
- let directoryOffset = zipWriter.offset - diskOffset;
1395
- let filesLength = files.size;
1396
1562
  for (const [, fileEntry] of files) {
1397
1563
  const {
1398
1564
  rawFilename,
@@ -1409,24 +1575,27 @@ async function closeFile(zipWriter, comment, options) {
1409
1575
  uncompressedSize,
1410
1576
  compressedSize
1411
1577
  } = fileEntry;
1412
- const zip64Offset = fileEntry.offset > MAX_32_BITS;
1413
- const zip64DiskNumberStart = fileEntry.diskNumberStart > MAX_16_BITS;
1578
+ const zip64Offset = fileEntry.offset >= MAX_32_BITS;
1579
+ const zip64DiskNumberStart = fileEntry.diskNumberStart >= MAX_16_BITS;
1414
1580
  let rawExtraFieldZip64;
1415
1581
  if (zip64Offset || zip64DiskNumberStart || zip64UncompressedSize || zip64CompressedSize) {
1416
- let length = 4;
1417
- if (zip64UncompressedSize) length += 8;
1418
- if (zip64CompressedSize) length += 8;
1419
- if (zip64Offset) length += 8;
1420
- if (zip64DiskNumberStart) length += 4;
1421
- rawExtraFieldZip64 = new Uint8Array(length);
1422
- const zip64View = getDataView(rawExtraFieldZip64);
1423
- setUint16(zip64View, 0, EXTRAFIELD_TYPE_ZIP64);
1424
- setUint16(zip64View, 2, length - 4);
1425
- let zip64FieldOffset = 4;
1426
- if (zip64UncompressedSize) { setBigUint64(zip64View, zip64FieldOffset, BigInt(uncompressedSize)); zip64FieldOffset += 8; }
1427
- if (zip64CompressedSize) { setBigUint64(zip64View, zip64FieldOffset, BigInt(compressedSize)); zip64FieldOffset += 8; }
1428
- if (zip64Offset) { setBigUint64(zip64View, zip64FieldOffset, BigInt(fileEntry.offset)); zip64FieldOffset += 8; }
1429
- if (zip64DiskNumberStart) { setUint32(zip64View, zip64FieldOffset, fileEntry.diskNumberStart); }
1582
+ const length = 4 + (zip64UncompressedSize ? 8 : 0) + (zip64CompressedSize ? 8 : 0) + (zip64Offset ? 8 : 0) + (zip64DiskNumberStart ? 4 : 0);
1583
+ const extraFieldZip64 = createRecordWriter(length);
1584
+ extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
1585
+ extraFieldZip64.uint16(length - 4);
1586
+ if (zip64UncompressedSize) {
1587
+ extraFieldZip64.uint64(uncompressedSize);
1588
+ }
1589
+ if (zip64CompressedSize) {
1590
+ extraFieldZip64.uint64(compressedSize);
1591
+ }
1592
+ if (zip64Offset) {
1593
+ extraFieldZip64.uint64(fileEntry.offset);
1594
+ }
1595
+ if (zip64DiskNumberStart) {
1596
+ extraFieldZip64.uint32(fileEntry.diskNumberStart);
1597
+ }
1598
+ rawExtraFieldZip64 = extraFieldZip64.array;
1430
1599
  } else {
1431
1600
  rawExtraFieldZip64 = new Uint8Array();
1432
1601
  }
@@ -1434,32 +1603,41 @@ async function closeFile(zipWriter, comment, options) {
1434
1603
  fileEntry.zip64Offset = zip64Offset;
1435
1604
  fileEntry.zip64DiskNumberStart = zip64DiskNumberStart;
1436
1605
  let rawExtraFieldTimestamp;
1437
- if (extendedTimestamp) {
1438
- rawExtraFieldTimestamp = new Uint8Array(9);
1439
- const extraFieldExtendedTimestampView = getDataView(rawExtraFieldTimestamp);
1440
- setUint16(extraFieldExtendedTimestampView, 0, EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1441
- setUint16(extraFieldExtendedTimestampView, 2, 5);
1442
- setUint8(extraFieldExtendedTimestampView, 4, extraFieldExtendedTimestampFlag);
1443
- setUint32(extraFieldExtendedTimestampView, 5, Math.floor(lastModDate.getTime() / 1000));
1606
+ const lastModTimeUnix = getTimeUnix(lastModDate);
1607
+ if (extendedTimestamp && inUnixTimeRange(lastModTimeUnix)) {
1608
+ const extraFieldTimestamp = createRecordWriter(9);
1609
+ extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
1610
+ extraFieldTimestamp.uint16(5);
1611
+ extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
1612
+ extraFieldTimestamp.uint32(lastModTimeUnix);
1613
+ rawExtraFieldTimestamp = extraFieldTimestamp.array;
1444
1614
  } else {
1445
1615
  rawExtraFieldTimestamp = new Uint8Array();
1446
1616
  }
1447
1617
  fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
1448
- directoryDataLength += 46 +
1449
- getLength(
1450
- rawFilename,
1451
- rawComment,
1452
- rawExtraFieldZip64,
1453
- rawExtraFieldAES,
1454
- rawExtraFieldNTFS,
1455
- rawExtraFieldUnix,
1456
- rawExtraFieldTimestamp,
1457
- rawExtraField);
1618
+ const extraFieldLength = getLength(
1619
+ rawExtraFieldZip64,
1620
+ rawExtraFieldAES,
1621
+ rawExtraFieldNTFS,
1622
+ rawExtraFieldUnix,
1623
+ rawExtraFieldTimestamp,
1624
+ rawExtraField);
1625
+ if (extraFieldLength > MAX_16_BITS) {
1626
+ throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
1627
+ }
1628
+ directoryDataLength += CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
1458
1629
  }
1630
+ return directoryDataLength;
1631
+ }
1632
+
1633
+ async function writeDirectoryRecords(zipWriter, directoryDataLength, options) {
1634
+ const { files, writer } = zipWriter;
1459
1635
  const directoryArray = new Uint8Array(directoryDataLength);
1460
- const directoryView = getDataView(directoryArray);
1461
1636
  await initStream(writer);
1637
+ let offset = 0;
1462
1638
  let directoryDiskOffset = 0;
1639
+ let cdStartDiskNumber = writer.diskNumber;
1640
+ let cdStartDiskOffset = writer.diskOffset;
1463
1641
  for (const [indexFileEntry, fileEntry] of Array.from(files.values()).entries()) {
1464
1642
  const {
1465
1643
  offset: fileEntryOffset,
@@ -1485,52 +1663,46 @@ async function closeFile(zipWriter, comment, options) {
1485
1663
  compressedSize
1486
1664
  } = fileEntry;
1487
1665
  const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
1488
- setUint32(directoryView, offset, CENTRAL_FILE_HEADER_SIGNATURE);
1489
- setUint16(directoryView, offset + 4, versionMadeBy);
1666
+ const directoryRecordLength = CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
1667
+ if (offset + directoryRecordLength - directoryDiskOffset > writer.availableSize) {
1668
+ await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
1669
+ directoryDiskOffset = offset;
1670
+ writer.availableSize = 0;
1671
+ await writeData(writer, new Uint8Array());
1672
+ }
1673
+ if (indexFileEntry == 0) {
1674
+ cdStartDiskNumber = writer.diskNumber;
1675
+ cdStartDiskOffset = writer.diskOffset;
1676
+ }
1490
1677
  if (!zip64UncompressedSize) {
1491
1678
  setUint32(headerView, HEADER_OFFSET_UNCOMPRESSED_SIZE, uncompressedSize);
1492
1679
  }
1493
1680
  if (!zip64CompressedSize) {
1494
1681
  setUint32(headerView, HEADER_OFFSET_COMPRESSED_SIZE, compressedSize);
1495
1682
  }
1496
- arraySet(directoryArray, headerArray, offset + 6);
1497
- let directoryOffset = offset + HEADER_SIZE;
1498
- setUint16(directoryView, directoryOffset, extraFieldLength);
1499
- directoryOffset += 2;
1500
- setUint16(directoryView, directoryOffset, getLength(rawComment));
1501
- directoryOffset += 2;
1502
- setUint16(directoryView, directoryOffset, zip64DiskNumberStart ? MAX_16_BITS : diskNumberStart);
1503
- directoryOffset += 2;
1504
- setUint16(directoryView, directoryOffset, internalFileAttributes);
1505
- directoryOffset += 2;
1506
- if (externalFileAttributes) {
1507
- setUint32(directoryView, directoryOffset, externalFileAttributes);
1508
- }
1509
- directoryOffset += 4;
1510
- setUint32(directoryView, directoryOffset, zip64Offset ? MAX_32_BITS : fileEntryOffset);
1511
- directoryOffset += 4;
1512
- arraySet(directoryArray, rawFilename, directoryOffset);
1513
- directoryOffset += getLength(rawFilename);
1514
- arraySet(directoryArray, rawExtraFieldZip64, directoryOffset);
1515
- directoryOffset += getLength(rawExtraFieldZip64);
1516
- arraySet(directoryArray, rawExtraFieldAES, directoryOffset);
1517
- directoryOffset += getLength(rawExtraFieldAES);
1518
- arraySet(directoryArray, rawExtraFieldExtendedTimestamp, directoryOffset);
1519
- directoryOffset += getLength(rawExtraFieldExtendedTimestamp);
1520
- arraySet(directoryArray, rawExtraFieldNTFS, directoryOffset);
1521
- directoryOffset += getLength(rawExtraFieldNTFS);
1522
- arraySet(directoryArray, rawExtraFieldUnix, directoryOffset);
1523
- directoryOffset += getLength(rawExtraFieldUnix);
1524
- arraySet(directoryArray, rawExtraField, directoryOffset);
1525
- directoryOffset += getLength(rawExtraField);
1526
- arraySet(directoryArray, rawComment, directoryOffset);
1527
- directoryOffset += getLength(rawComment);
1528
- if (offset - directoryDiskOffset > writer.availableSize) {
1529
- writer.availableSize = 0;
1530
- await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
1531
- directoryDiskOffset = offset;
1683
+ if ((zip64Offset || zip64DiskNumberStart) && fileEntry.version < VERSION_ZIP64) {
1684
+ setUint16(headerView, HEADER_OFFSET_VERSION, VERSION_ZIP64);
1532
1685
  }
1533
- offset = directoryOffset;
1686
+ const directoryRecord = createRecordWriter(directoryRecordLength);
1687
+ directoryRecord.uint32(CENTRAL_FILE_HEADER_SIGNATURE);
1688
+ directoryRecord.uint16(versionMadeBy);
1689
+ directoryRecord.bytes(headerArray.subarray(0, HEADER_SIZE - 4 - 2));
1690
+ directoryRecord.uint16(extraFieldLength);
1691
+ directoryRecord.uint16(getLength(rawComment));
1692
+ directoryRecord.uint16(zip64DiskNumberStart ? MAX_16_BITS : diskNumberStart);
1693
+ directoryRecord.uint16(internalFileAttributes);
1694
+ directoryRecord.uint32(externalFileAttributes);
1695
+ directoryRecord.uint32(zip64Offset ? MAX_32_BITS : fileEntryOffset);
1696
+ directoryRecord.bytes(rawFilename);
1697
+ directoryRecord.bytes(rawExtraFieldZip64);
1698
+ directoryRecord.bytes(rawExtraFieldAES);
1699
+ directoryRecord.bytes(rawExtraFieldExtendedTimestamp);
1700
+ directoryRecord.bytes(rawExtraFieldNTFS);
1701
+ directoryRecord.bytes(rawExtraFieldUnix);
1702
+ directoryRecord.bytes(rawExtraField);
1703
+ directoryRecord.bytes(rawComment);
1704
+ arraySet(directoryArray, directoryRecord.array, offset);
1705
+ offset += directoryRecordLength;
1534
1706
  if (options.onprogress) {
1535
1707
  try {
1536
1708
  await options.onprogress(indexFileEntry + 1, files.size, new Entry(fileEntry));
@@ -1540,36 +1712,53 @@ async function closeFile(zipWriter, comment, options) {
1540
1712
  }
1541
1713
  }
1542
1714
  await writeData(writer, directoryDiskOffset ? directoryArray.slice(directoryDiskOffset) : directoryArray);
1715
+ return { cdStartDiskNumber, cdStartDiskOffset };
1716
+ }
1717
+
1718
+ async function writeEndOfDirectoryRecord(zipWriter, comment, options, cdInfo) {
1719
+ const { writer } = zipWriter;
1720
+ const { cdStartDiskNumber, cdStartDiskOffset } = cdInfo;
1721
+ let { directoryDataLength } = cdInfo;
1722
+ let filesLength = zipWriter.files.size;
1723
+ let diskNumber = cdStartDiskNumber;
1724
+ let directoryOffset = zipWriter.offset - cdStartDiskOffset - (cdStartDiskNumber ? zipWriter.initialOffset : 0);
1543
1725
  let lastDiskNumber = writer.diskNumber;
1544
- const { availableSize } = writer;
1545
- if (availableSize < END_OF_CENTRAL_DIR_LENGTH) {
1726
+ if (writer.availableSize < END_OF_CENTRAL_DIR_LENGTH) {
1546
1727
  lastDiskNumber++;
1547
1728
  }
1548
1729
  let zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
1549
- if (directoryOffset > MAX_32_BITS || directoryDataLength > MAX_32_BITS || filesLength > MAX_16_BITS || lastDiskNumber > MAX_16_BITS) {
1730
+ if (directoryOffset >= MAX_32_BITS || directoryDataLength >= MAX_32_BITS || filesLength >= MAX_16_BITS || lastDiskNumber >= MAX_16_BITS) {
1550
1731
  if (zip64 === false) {
1551
1732
  throw new Error(ERR_UNSUPPORTED_FORMAT);
1552
1733
  } else {
1553
1734
  zip64 = true;
1554
1735
  }
1555
1736
  }
1556
- const endOfdirectoryArray = new Uint8Array(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
1557
- const endOfdirectoryView = getDataView(endOfdirectoryArray);
1558
- offset = 0;
1737
+ const commentLength = getLength(comment);
1738
+ if (commentLength > MAX_16_BITS) {
1739
+ throw new Error(ERR_INVALID_COMMENT);
1740
+ }
1741
+ const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
1742
+ if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
1743
+ writer.availableSize = 0;
1744
+ await writeData(writer, new Uint8Array());
1745
+ }
1746
+ lastDiskNumber = writer.diskNumber;
1559
1747
  if (zip64) {
1560
- setUint32(endOfdirectoryView, 0, ZIP64_END_OF_CENTRAL_DIR_SIGNATURE);
1561
- setBigUint64(endOfdirectoryView, 4, BigInt(44));
1562
- setUint16(endOfdirectoryView, 12, 45);
1563
- setUint16(endOfdirectoryView, 14, 45);
1564
- setUint32(endOfdirectoryView, 16, lastDiskNumber);
1565
- setUint32(endOfdirectoryView, 20, diskNumber);
1566
- setBigUint64(endOfdirectoryView, 24, BigInt(filesLength));
1567
- setBigUint64(endOfdirectoryView, 32, BigInt(filesLength));
1568
- setBigUint64(endOfdirectoryView, 40, BigInt(directoryDataLength));
1569
- setBigUint64(endOfdirectoryView, 48, BigInt(directoryOffset));
1570
- setUint32(endOfdirectoryView, 56, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE);
1571
- setBigUint64(endOfdirectoryView, 64, BigInt(directoryOffset) + BigInt(directoryDataLength));
1572
- setUint32(endOfdirectoryView, 72, lastDiskNumber + 1);
1748
+ endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_SIGNATURE);
1749
+ endOfdirectoryRecord.uint64(44);
1750
+ endOfdirectoryRecord.uint16(45);
1751
+ endOfdirectoryRecord.uint16(45);
1752
+ endOfdirectoryRecord.uint32(lastDiskNumber);
1753
+ endOfdirectoryRecord.uint32(diskNumber);
1754
+ endOfdirectoryRecord.uint64(filesLength);
1755
+ endOfdirectoryRecord.uint64(filesLength);
1756
+ endOfdirectoryRecord.uint64(directoryDataLength);
1757
+ endOfdirectoryRecord.uint64(directoryOffset);
1758
+ endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE);
1759
+ endOfdirectoryRecord.uint32(lastDiskNumber);
1760
+ endOfdirectoryRecord.uint64(BigInt(zipWriter.offset) + BigInt(directoryDataLength) - BigInt(writer.diskOffset) - BigInt(writer.diskNumber ? zipWriter.initialOffset : 0));
1761
+ endOfdirectoryRecord.uint32(lastDiskNumber + 1);
1573
1762
  const supportZip64SplitFile = getOptionValue(zipWriter, options, OPTION_SUPPORT_ZIP64_SPLIT_FILE, true);
1574
1763
  if (supportZip64SplitFile) {
1575
1764
  lastDiskNumber = MAX_16_BITS;
@@ -1578,29 +1767,40 @@ async function closeFile(zipWriter, comment, options) {
1578
1767
  filesLength = MAX_16_BITS;
1579
1768
  directoryOffset = MAX_32_BITS;
1580
1769
  directoryDataLength = MAX_32_BITS;
1581
- offset += ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
1582
- }
1583
- setUint32(endOfdirectoryView, offset, END_OF_CENTRAL_DIR_SIGNATURE);
1584
- setUint16(endOfdirectoryView, offset + 4, lastDiskNumber);
1585
- setUint16(endOfdirectoryView, offset + 6, diskNumber);
1586
- setUint16(endOfdirectoryView, offset + 8, filesLength);
1587
- setUint16(endOfdirectoryView, offset + 10, filesLength);
1588
- setUint32(endOfdirectoryView, offset + 12, directoryDataLength);
1589
- setUint32(endOfdirectoryView, offset + 16, directoryOffset);
1590
- const commentLength = getLength(comment);
1591
- if (commentLength) {
1592
- if (commentLength <= MAX_16_BITS) {
1593
- setUint16(endOfdirectoryView, offset + 20, commentLength);
1594
- } else {
1595
- throw new Error(ERR_INVALID_COMMENT);
1596
- }
1597
1770
  }
1598
- await writeData(writer, endOfdirectoryArray);
1771
+ endOfdirectoryRecord.uint32(END_OF_CENTRAL_DIR_SIGNATURE);
1772
+ endOfdirectoryRecord.uint16(lastDiskNumber);
1773
+ endOfdirectoryRecord.uint16(diskNumber);
1774
+ endOfdirectoryRecord.uint16(filesLength);
1775
+ endOfdirectoryRecord.uint16(filesLength);
1776
+ endOfdirectoryRecord.uint32(directoryDataLength);
1777
+ endOfdirectoryRecord.uint32(directoryOffset);
1778
+ endOfdirectoryRecord.uint16(commentLength);
1779
+ await writeData(writer, endOfdirectoryRecord.array);
1599
1780
  if (commentLength) {
1600
1781
  await writeData(writer, comment);
1601
1782
  }
1602
1783
  }
1603
1784
 
1785
+ function createRecordWriter(length) {
1786
+ const array = new Uint8Array(length);
1787
+ const view = getDataView(array);
1788
+ let offset = 0;
1789
+ return {
1790
+ array,
1791
+ uint8: value => { setUint8(view, offset, value); offset += 1; },
1792
+ uint16: value => { setUint16(view, offset, value); offset += 2; },
1793
+ uint32: value => { setUint32(view, offset, value); offset += 4; },
1794
+ uint64: value => { setBigUint64(view, offset, BigInt(value)); offset += 8; },
1795
+ bytes: value => { arraySet(array, value, offset); offset += getLength(value); },
1796
+ skip: count => offset += count
1797
+ };
1798
+ }
1799
+
1800
+ function getSegmentOffset(zipWriter, writer) {
1801
+ return zipWriter.offset - writer.diskOffset - (writer.diskNumber ? zipWriter.initialOffset : 0);
1802
+ }
1803
+
1604
1804
  async function writeData(writer, array) {
1605
1805
  const { writable } = writer;
1606
1806
  const streamWriter = writable.getWriter();
@@ -1613,12 +1813,40 @@ async function writeData(writer, array) {
1613
1813
  }
1614
1814
  }
1615
1815
 
1816
+ async function flushBufferedData(readable, writer, signal, onChunkWritten) {
1817
+ const streamWriter = writer.writable.getWriter();
1818
+ try {
1819
+ await readable.pipeTo(new WritableStream({
1820
+ async write(chunk) {
1821
+ await streamWriter.ready;
1822
+ await streamWriter.write(chunk);
1823
+ onChunkWritten(getLength(chunk));
1824
+ }
1825
+ }), { preventClose: true, preventAbort: true, signal });
1826
+ } finally {
1827
+ streamWriter.releaseLock();
1828
+ }
1829
+ }
1830
+
1616
1831
  function getTimeNTFS(date) {
1617
1832
  if (date) {
1618
1833
  return ((BigInt(date.getTime()) + BigInt(11644473600000)) * BigInt(10000));
1619
1834
  }
1620
1835
  }
1621
1836
 
1837
+ function getTimeUnix(date) {
1838
+ return Math.floor(date.getTime() / 1000);
1839
+ }
1840
+
1841
+ // the extended timestamp extra field stores dates as signed 32-bit time_t values
1842
+ function inUnixTimeRange(timeUnix) {
1843
+ return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
1844
+ }
1845
+
1846
+ function clampUnixTime(timeUnix) {
1847
+ return Math.min(MAX_UNIX_TIME, Math.max(MIN_UNIX_TIME, timeUnix));
1848
+ }
1849
+
1622
1850
  function getOptionValue(zipWriter, options, name, defaultValue) {
1623
1851
  const result = options[name] === UNDEFINED_VALUE ? zipWriter.options[name] : options[name];
1624
1852
  return result === UNDEFINED_VALUE ? defaultValue : result;
@@ -1649,7 +1877,7 @@ function arraySet(array, typedArray, offset) {
1649
1877
  }
1650
1878
 
1651
1879
  function getDataView(array) {
1652
- return new DataView(array.buffer);
1880
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
1653
1881
  }
1654
1882
 
1655
1883
  function getLength(...arrayLikes) {
@@ -1665,30 +1893,39 @@ function getHeaderArrayData({
1665
1893
  uncompressedSize,
1666
1894
  compressedSize,
1667
1895
  lastModDate,
1896
+ rawLastModDate,
1668
1897
  rawFilename,
1669
1898
  zip64CompressedSize,
1670
1899
  zip64UncompressedSize,
1671
1900
  extraFieldLength
1672
1901
  }) {
1673
- const headerArray = new Uint8Array(HEADER_SIZE - 4);
1902
+ const headerRecord = createRecordWriter(HEADER_SIZE - 4);
1903
+ const headerArray = headerRecord.array;
1674
1904
  const headerView = getDataView(headerArray);
1675
- setUint16(headerView, 0, version);
1676
- setUint16(headerView, 2, bitFlag);
1677
- setUint16(headerView, 4, compressionMethod);
1678
- const dateArray = new Uint32Array(1);
1679
- const dateView = getDataView(dateArray);
1680
- setUint16(dateView, 0, (((lastModDate.getHours() << 6) | lastModDate.getMinutes()) << 5) | lastModDate.getSeconds() / 2);
1681
- setUint16(dateView, 2, ((((lastModDate.getFullYear() - 1980) << 4) | (lastModDate.getMonth() + 1)) << 5) | lastModDate.getDate());
1682
- const rawLastModDate = dateArray[0];
1683
- setUint32(headerView, 6, rawLastModDate);
1905
+ headerRecord.uint16(version);
1906
+ headerRecord.uint16(bitFlag);
1907
+ headerRecord.uint16(compressionMethod);
1908
+ if (rawLastModDate === UNDEFINED_VALUE) {
1909
+ const dateArray = new Uint32Array(1);
1910
+ const dateView = getDataView(dateArray);
1911
+ setUint16(dateView, 0, (((lastModDate.getHours() << 6) | lastModDate.getMinutes()) << 5) | lastModDate.getSeconds() / 2);
1912
+ setUint16(dateView, 2, ((((lastModDate.getFullYear() - 1980) << 4) | (lastModDate.getMonth() + 1)) << 5) | lastModDate.getDate());
1913
+ rawLastModDate = dateArray[0];
1914
+ }
1915
+ headerRecord.uint32(rawLastModDate);
1916
+ headerRecord.skip(4);
1684
1917
  if (zip64CompressedSize || compressedSize !== UNDEFINED_VALUE) {
1685
- setUint32(headerView, HEADER_OFFSET_COMPRESSED_SIZE, zip64CompressedSize ? MAX_32_BITS : compressedSize);
1918
+ headerRecord.uint32(zip64CompressedSize ? MAX_32_BITS : compressedSize);
1919
+ } else {
1920
+ headerRecord.skip(4);
1686
1921
  }
1687
1922
  if (zip64UncompressedSize || uncompressedSize !== UNDEFINED_VALUE) {
1688
- setUint32(headerView, HEADER_OFFSET_UNCOMPRESSED_SIZE, zip64UncompressedSize ? MAX_32_BITS : uncompressedSize);
1923
+ headerRecord.uint32(zip64UncompressedSize ? MAX_32_BITS : uncompressedSize);
1924
+ } else {
1925
+ headerRecord.skip(4);
1689
1926
  }
1690
- setUint16(headerView, 22, getLength(rawFilename));
1691
- setUint16(headerView, 24, extraFieldLength);
1927
+ headerRecord.uint16(getLength(rawFilename));
1928
+ headerRecord.uint16(extraFieldLength);
1692
1929
  return {
1693
1930
  headerArray,
1694
1931
  headerView,