@zip.js/zip.js 2.6.29 → 2.6.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -54,6 +54,7 @@ export {
54
54
  HttpRangeReader,
55
55
  Uint8ArrayWriter,
56
56
  Uint8ArrayReader,
57
+ SplitZipReader,
57
58
  ERR_HTTP_RANGE,
58
59
  ERR_BAD_FORMAT,
59
60
  ERR_EOCDR_NOT_FOUND,
@@ -75,6 +76,7 @@ export {
75
76
  ERR_INVALID_EXTRAFIELD_TYPE,
76
77
  ERR_INVALID_EXTRAFIELD_DATA,
77
78
  ERR_INVALID_ENCRYPTION_STRENGTH,
78
- ERR_UNSUPPORTED_FORMAT
79
+ ERR_UNSUPPORTED_FORMAT,
80
+ ERR_SPLIT_ZIP_FILE
79
81
  } from "./lib/zip-fs.js";
80
82
  export { getMimeType, terminateWorkers };
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global Worker, URL, TransformStream */
29
+ /* global Worker, URL, TransformStream, WritableStream */
30
30
 
31
31
  import {
32
32
  Codec,
@@ -141,8 +141,24 @@ function createWebWorkerInterface(workerData, { baseURL, chunkSize }) {
141
141
  }
142
142
  workerData.worker.addEventListener(MESSAGE_EVENT_TYPE, onMessage, false);
143
143
  workerData.interface = {
144
- run() {
145
- const { readable, writable, options } = workerData;
144
+ async run() {
145
+ const { readable, options } = workerData;
146
+ const writer = workerData.writable.getWriter();
147
+ let resolveStreamClosed, streamClosed;
148
+ streamClosed = new Promise(resolve => resolveStreamClosed = resolve);
149
+ const writable = new WritableStream({
150
+ async write(chunk) {
151
+ await writer.ready;
152
+ await writer.write(new Uint8Array(chunk));
153
+ },
154
+ async close() {
155
+ writer.releaseLock();
156
+ resolveStreamClosed();
157
+ },
158
+ abort(reason) {
159
+ writer.abort(reason);
160
+ }
161
+ });
146
162
  const scripts = workerData.scripts.slice(1);
147
163
  const message = { type: MESSAGE_START, scripts, options, config: { chunkSize } };
148
164
  if (workerData.transferStreams && transferStreamsSupported) {
@@ -155,7 +171,14 @@ function createWebWorkerInterface(workerData, { baseURL, chunkSize }) {
155
171
  sendMessage(message);
156
172
  initStreams(readable, writable);
157
173
  }
158
- return workerData.result;
174
+ const result = await workerData.result;
175
+ try {
176
+ await writable.close();
177
+ } catch (_error) {
178
+ // ignored
179
+ }
180
+ await streamClosed;
181
+ return result;
159
182
  }
160
183
  };
161
184
  }
package/lib/core/io.js CHANGED
@@ -64,9 +64,9 @@ class Reader extends Stream {
64
64
  this.chunkOffset = 0;
65
65
  },
66
66
  async pull(controller) {
67
- const { offset = 0, size } = readable;
67
+ const { offset = 0, size, diskNumberStart } = readable;
68
68
  const { chunkOffset } = this;
69
- controller.enqueue(await reader.readUint8Array(offset + chunkOffset, Math.min(chunkSize, size - chunkOffset)));
69
+ controller.enqueue(await readUint8Array(reader, offset + chunkOffset, Math.min(chunkSize, size - chunkOffset), diskNumberStart));
70
70
  if (chunkOffset + chunkSize > size) {
71
71
  controller.close();
72
72
  } else {
@@ -483,6 +483,52 @@ class Uint8ArrayWriter extends Writer {
483
483
  }
484
484
  }
485
485
 
486
+ class SplitZipReader extends Reader {
487
+
488
+ constructor(readers) {
489
+ super();
490
+ this.readers = readers;
491
+ }
492
+
493
+ async init() {
494
+ super.init();
495
+ const reader = this;
496
+ const { readers } = reader;
497
+ reader.lastDiskNumber = 0;
498
+ await Promise.all(readers.map(async diskReader => {
499
+ await diskReader.init();
500
+ reader.size += diskReader.size;
501
+ }));
502
+ }
503
+
504
+ async readUint8Array(offset, length, diskNumber = 0) {
505
+ const reader = this;
506
+ const { readers } = this;
507
+ let result;
508
+ let currentDiskNumber = diskNumber;
509
+ if (currentDiskNumber == -1) {
510
+ currentDiskNumber = readers.length - 1;
511
+ }
512
+ let currentReaderOffset = offset;
513
+ while (currentReaderOffset >= readers[currentDiskNumber].size) {
514
+ currentReaderOffset -= readers[currentDiskNumber].size;
515
+ currentDiskNumber++;
516
+ }
517
+ const currentReader = readers[currentDiskNumber];
518
+ const currentReaderSize = currentReader.size;
519
+ if (currentReaderOffset + length <= currentReaderSize) {
520
+ result = await readUint8Array(currentReader, currentReaderOffset, length);
521
+ } else {
522
+ const chunkLength = currentReaderSize - currentReaderOffset;
523
+ result = new Uint8Array(length);
524
+ result.set(await readUint8Array(currentReader, currentReaderOffset, chunkLength));
525
+ result.set(await reader.readUint8Array(offset + chunkLength, length - chunkLength, diskNumber), chunkLength);
526
+ }
527
+ reader.lastDiskNumber = Math.max(currentDiskNumber, reader.lastDiskNumber);
528
+ return result;
529
+ }
530
+ }
531
+
486
532
  function isHttpFamily(url) {
487
533
  const { baseURL } = getConfiguration();
488
534
  const { protocol } = new URL(url, baseURL);
@@ -495,8 +541,13 @@ async function initStream(stream, initSize) {
495
541
  }
496
542
  }
497
543
 
544
+ function readUint8Array(reader, offset, size, diskNumber) {
545
+ return reader.readUint8Array(offset, size, diskNumber);
546
+ }
547
+
498
548
  export {
499
549
  initStream,
550
+ readUint8Array,
500
551
  Reader,
501
552
  Writer,
502
553
  TextReader,
@@ -509,5 +560,6 @@ export {
509
560
  Uint8ArrayWriter,
510
561
  HttpReader,
511
562
  HttpRangeReader,
563
+ SplitZipReader,
512
564
  ERR_HTTP_RANGE
513
565
  };
@@ -108,8 +108,10 @@ async function init(message) {
108
108
  }, strategy);
109
109
  codec = new Codec(readable, writable, options, config);
110
110
  const result = await codec.run();
111
- if (message.writable && !options.preventClose) {
112
- await message.writable.close();
111
+ try {
112
+ await writable.close();
113
+ } catch (_error) {
114
+ // ignored
113
115
  }
114
116
  sendMessage({ type: MESSAGE_CLOSE, result });
115
117
  } catch (error) {
@@ -68,12 +68,15 @@ import {
68
68
  } from "./codec-pool.js";
69
69
  import {
70
70
  initStream,
71
- BlobReader
71
+ readUint8Array,
72
+ BlobReader,
73
+ SplitZipReader
72
74
  } from "./io.js";
73
75
  import { decodeText } from "./util/decode-text.js";
74
76
  import { Crc32 } from "./streams/codecs/crc32.js";
75
77
  import { Entry } from "./zip-entry.js";
76
78
 
79
+ const SPLIT_ZIP_FILE_SIGNATURE = 0x08074b50;
77
80
  const ERR_BAD_FORMAT = "File format is not recognized";
78
81
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
79
82
  const ERR_EOCDR_ZIP64_NOT_FOUND = "End of Zip64 central directory not found";
@@ -84,9 +87,25 @@ const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
84
87
  const ERR_ENCRYPTED = "File contains encrypted entry";
85
88
  const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
86
89
  const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
90
+ const ERR_SPLIT_ZIP_FILE = "Split zip file";
87
91
  const CHARSET_UTF8 = "utf-8";
88
92
  const CHARSET_CP437 = "cp437";
89
- const ZIP64_PROPERTIES = ["uncompressedSize", "compressedSize", "offset"];
93
+ const ZIP64_PROPERTIES = [
94
+ ["uncompressedSize", MAX_32_BITS],
95
+ ["compressedSize", MAX_32_BITS],
96
+ ["offset", MAX_32_BITS],
97
+ ["diskNumberStart", MAX_16_BITS]
98
+ ];
99
+ const ZIP64_EXTRACTION = {
100
+ [MAX_16_BITS]: {
101
+ getValue: getUint32,
102
+ bytes: 4
103
+ },
104
+ [MAX_32_BITS]: {
105
+ getValue: getBigUint64,
106
+ bytes: 8
107
+ }
108
+ };
90
109
 
91
110
  class ZipReader {
92
111
 
@@ -109,8 +128,12 @@ class ZipReader {
109
128
  const { config } = zipReader;
110
129
  await initStream(reader);
111
130
  if (reader.size === undefined || !reader.readUint8Array) {
112
- const blob = await new Response(reader.readable).blob();
113
- reader = new BlobReader(blob);
131
+ if (Array.isArray(reader)) {
132
+ reader = new SplitZipReader(reader);
133
+ } else {
134
+ reader = new BlobReader(await new Response(reader.readable).blob());
135
+ }
136
+ await initStream(reader);
114
137
  }
115
138
  if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
116
139
  throw new Error(ERR_BAD_FORMAT);
@@ -118,46 +141,64 @@ class ZipReader {
118
141
  reader.chunkSize = getChunkSize(config);
119
142
  const endOfDirectoryInfo = await seekSignature(reader, END_OF_CENTRAL_DIR_SIGNATURE, reader.size, END_OF_CENTRAL_DIR_LENGTH, MAX_16_BITS * 16);
120
143
  if (!endOfDirectoryInfo) {
121
- throw new Error(ERR_EOCDR_NOT_FOUND);
144
+ const signatureArray = await readUint8Array(reader, 0, 4);
145
+ const signatureView = getDataView(signatureArray);
146
+ if (getUint32(signatureView) == SPLIT_ZIP_FILE_SIGNATURE) {
147
+ throw new Error(ERR_SPLIT_ZIP_FILE);
148
+ } else {
149
+ throw new Error(ERR_EOCDR_NOT_FOUND);
150
+ }
122
151
  }
152
+ const { lastDiskNumber } = reader;
123
153
  const endOfDirectoryView = getDataView(endOfDirectoryInfo);
124
154
  let directoryDataLength = getUint32(endOfDirectoryView, 12);
125
155
  let directoryDataOffset = getUint32(endOfDirectoryView, 16);
126
156
  const commentOffset = endOfDirectoryInfo.offset;
127
157
  const commentLength = getUint16(endOfDirectoryView, 20);
128
158
  const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
159
+ let diskNumber = getUint16(endOfDirectoryView, 4);
160
+ if (lastDiskNumber !== undefined && diskNumber != lastDiskNumber) {
161
+ throw new Error(ERR_SPLIT_ZIP_FILE);
162
+ }
129
163
  let filesLength = getUint16(endOfDirectoryView, 8);
130
164
  let prependedDataLength = 0;
131
165
  let startOffset = 0;
132
- if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS) {
166
+ if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
133
167
  const endOfDirectoryLocatorArray = await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH);
134
168
  const endOfDirectoryLocatorView = getDataView(endOfDirectoryLocatorArray);
135
169
  if (getUint32(endOfDirectoryLocatorView, 0) != ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
136
170
  throw new Error(ERR_EOCDR_ZIP64_NOT_FOUND);
137
171
  }
138
172
  directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
139
- let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
173
+ let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
140
174
  let endOfDirectoryView = getDataView(endOfDirectoryArray);
141
175
  const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
142
176
  if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
143
177
  const originalDirectoryDataOffset = directoryDataOffset;
144
178
  directoryDataOffset = expectedDirectoryDataOffset;
145
179
  prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
146
- endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
180
+ endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
147
181
  endOfDirectoryView = getDataView(endOfDirectoryArray);
148
182
  }
149
183
  if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
150
184
  throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
151
185
  }
152
- filesLength = getBigUint64(endOfDirectoryView, 32);
153
- directoryDataLength = getBigUint64(endOfDirectoryView, 40);
186
+ if (diskNumber == MAX_16_BITS) {
187
+ diskNumber = getUint32(endOfDirectoryView, 16);
188
+ }
189
+ if (filesLength == MAX_16_BITS) {
190
+ filesLength = getBigUint64(endOfDirectoryView, 32);
191
+ }
192
+ if (directoryDataLength == MAX_32_BITS) {
193
+ directoryDataLength = getBigUint64(endOfDirectoryView, 40);
194
+ }
154
195
  directoryDataOffset -= directoryDataLength;
155
196
  }
156
197
  if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
157
198
  throw new Error(ERR_BAD_FORMAT);
158
199
  }
159
200
  let offset = 0;
160
- let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
201
+ let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
161
202
  let directoryView = getDataView(directoryArray);
162
203
  if (directoryDataLength) {
163
204
  const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - directoryDataLength;
@@ -165,7 +206,7 @@ class ZipReader {
165
206
  const originalDirectoryDataOffset = directoryDataOffset;
166
207
  directoryDataOffset = expectedDirectoryDataOffset;
167
208
  prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
168
- directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
209
+ directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
169
210
  directoryView = getDataView(directoryArray);
170
211
  }
171
212
  }
@@ -202,6 +243,7 @@ class ZipReader {
202
243
  commentLength,
203
244
  directory,
204
245
  offset: offsetFileEntry,
246
+ diskNumberStart: getUint16(directoryView, offset + 34),
205
247
  internalFileAttribute: getUint16(directoryView, offset + 36),
206
248
  externalFileAttribute: getUint32(directoryView, offset + 38),
207
249
  rawFilename,
@@ -271,7 +313,8 @@ export {
271
313
  ERR_UNSUPPORTED_ENCRYPTION,
272
314
  ERR_UNSUPPORTED_COMPRESSION,
273
315
  ERR_INVALID_SIGNATURE,
274
- ERR_INVALID_PASSWORD
316
+ ERR_INVALID_PASSWORD,
317
+ ERR_SPLIT_ZIP_FILE
275
318
  };
276
319
 
277
320
  class ZipEntry {
@@ -289,6 +332,7 @@ class ZipEntry {
289
332
  const {
290
333
  reader,
291
334
  offset,
335
+ diskNumberStart,
292
336
  extraFieldAES,
293
337
  compressionMethod,
294
338
  config,
@@ -299,7 +343,7 @@ class ZipEntry {
299
343
  compressedSize
300
344
  } = zipEntry;
301
345
  const localDirectory = zipEntry.localDirectory = {};
302
- let dataArray = await readUint8Array(reader, offset, 30);
346
+ let dataArray = await readUint8Array(reader, offset, 30, diskNumberStart);
303
347
  const dataView = getDataView(dataArray);
304
348
  let password = getOptionValue(zipEntry, options, "password");
305
349
  password = password && password.length && password;
@@ -315,7 +359,7 @@ class ZipEntry {
315
359
  throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
316
360
  }
317
361
  readCommonHeader(localDirectory, dataView, 4);
318
- dataArray = await readUint8Array(reader, offset, 30 + localDirectory.filenameLength + localDirectory.extraFieldLength);
362
+ dataArray = await readUint8Array(reader, offset, 30 + localDirectory.filenameLength + localDirectory.extraFieldLength, diskNumberStart);
319
363
  localDirectory.rawExtraField = dataArray.subarray(30 + localDirectory.filenameLength);
320
364
  await readCommonFooter(zipEntry, localDirectory, dataView, 4);
321
365
  Object.assign(fileEntry, {
@@ -333,6 +377,7 @@ class ZipEntry {
333
377
  }
334
378
  const dataOffset = offset + 30 + localDirectory.filenameLength + localDirectory.extraFieldLength;
335
379
  const readable = reader.readable;
380
+ readable.diskNumberStart = diskNumberStart;
336
381
  readable.offset = dataOffset;
337
382
  const size = readable.size = compressedSize;
338
383
  if (writer instanceof WritableStream) {
@@ -345,7 +390,6 @@ class ZipEntry {
345
390
  writable.size = 0;
346
391
  }
347
392
  const signal = getOptionValue(zipEntry, options, "signal");
348
- const preventClose = getOptionValue(zipEntry, options, "preventClose");
349
393
  await initStream(writer, uncompressedSize);
350
394
  const { onstart, onprogress, onend } = options;
351
395
  const workerOptions = {
@@ -361,19 +405,15 @@ class ZipEntry {
361
405
  encrypted,
362
406
  useWebWorkers: getOptionValue(zipEntry, options, "useWebWorkers"),
363
407
  useCompressionStream: getOptionValue(zipEntry, options, "useCompressionStream"),
364
- transferStreams: getOptionValue(zipEntry, options, "transferStreams"),
365
- preventClose
408
+ transferStreams: getOptionValue(zipEntry, options, "transferStreams")
366
409
  },
367
410
  config,
368
411
  streamOptions: { signal, size, onstart, onprogress, onend }
369
412
  };
370
413
  writable.size += (await runWorker({ readable, writable }, workerOptions)).size;
414
+ const preventClose = getOptionValue(zipEntry, options, "preventClose");
371
415
  if (!preventClose) {
372
- try {
373
- await writable.close();
374
- } catch (_error) {
375
- // ignored
376
- }
416
+ await writable.close();
377
417
  }
378
418
  return writer.getData ? writer.getData() : writable;
379
419
  }
@@ -459,23 +499,17 @@ async function readCommonFooter(fileEntry, directory, dataView, offset) {
459
499
  function readExtraFieldZip64(extraFieldZip64, directory) {
460
500
  directory.zip64 = true;
461
501
  const extraFieldView = getDataView(extraFieldZip64.data);
462
- extraFieldZip64.values = [];
463
- for (let indexValue = 0; indexValue < Math.floor(extraFieldZip64.data.length / 8); indexValue++) {
464
- extraFieldZip64.values.push(getBigUint64(extraFieldView, 0 + indexValue * 8));
465
- }
466
- const missingProperties = ZIP64_PROPERTIES.filter(propertyName => directory[propertyName] == MAX_32_BITS);
467
- for (let indexMissingProperty = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
468
- extraFieldZip64[missingProperties[indexMissingProperty]] = extraFieldZip64.values[indexMissingProperty];
469
- }
470
- ZIP64_PROPERTIES.forEach(propertyName => {
471
- if (directory[propertyName] == MAX_32_BITS) {
472
- if (extraFieldZip64[propertyName] !== undefined) {
473
- directory[propertyName] = extraFieldZip64[propertyName];
474
- } else {
475
- throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
476
- }
502
+ const missingProperties = ZIP64_PROPERTIES.filter(([propertyName, max]) => directory[propertyName] == max);
503
+ for (let indexMissingProperty = 0, offset = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
504
+ const [propertyName, max] = missingProperties[indexMissingProperty];
505
+ if (directory[propertyName] == max) {
506
+ const extraction = ZIP64_EXTRACTION[max];
507
+ directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
508
+ offset += extraction.bytes;
509
+ } else if (extraFieldZip64[propertyName]) {
510
+ throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
477
511
  }
478
- });
512
+ }
479
513
  }
480
514
 
481
515
  async function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
@@ -638,8 +672,4 @@ function setUint32(view, offset, value) {
638
672
 
639
673
  function getDataView(array) {
640
674
  return new DataView(array.buffer);
641
- }
642
-
643
- function readUint8Array(reader, offset, size) {
644
- return reader.readUint8Array(offset, size);
645
675
  }