@zip.js/zip.js 2.8.26 → 2.8.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/deno.json +1 -1
- package/dist/zip-core.js +1050 -569
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +1146 -617
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +1207 -627
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +1279 -681
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +1051 -570
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +1053 -572
- package/dist/zip-native.min.js +1 -1
- package/dist/zip-web-worker-native.js +1 -1
- package/dist/zip-web-worker.js +1 -1
- package/dist/zip.js +1125 -626
- package/dist/zip.min.js +1 -1
- package/eslint.config.mjs +6 -1
- package/index-native.cjs +1207 -627
- package/index-native.min.js +1 -1
- package/index.cjs +1279 -681
- package/index.d.ts +2451 -2355
- package/index.min.js +1 -1
- package/lib/core/codec-pool.js +39 -1
- package/lib/core/codec-worker.js +120 -34
- package/lib/core/configuration.js +3 -0
- package/lib/core/constants.js +6 -0
- package/lib/core/io.js +55 -25
- package/lib/core/options.js +2 -0
- package/lib/core/streams/aes-crypto-stream.js +12 -12
- package/lib/core/streams/codec-stream.js +11 -5
- package/lib/core/streams/codecs/sjcl.js +1 -33
- package/lib/core/streams/common-crypto.js +4 -4
- package/lib/core/streams/zip-crypto-stream.js +25 -15
- package/lib/core/streams/zip-entry-stream.js +35 -1
- package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
- package/lib/core/streams/zlib-wasm/zlib-streams.js +73 -55
- package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
- package/lib/core/util/decode-cp437.js +1 -1
- package/lib/core/util/decode-text.js +2 -1
- package/lib/core/web-worker-base.js +12 -4
- package/lib/core/web-worker-inline-native.js +1 -1
- package/lib/core/web-worker-inline-wasm.js +1 -1
- package/lib/core/zip-fs.js +154 -55
- package/lib/core/zip-reader.js +185 -67
- package/lib/core/zip-writer.js +613 -376
- package/lib/core/zlib-streams-inline.js +1 -1
- package/lib/zip-core-reader.js +3 -1
- package/lib/zip-core-writer.js +1 -0
- package/package.json +20 -6
package/lib/core/zip-reader.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH,
|
|
44
44
|
LOCAL_FILE_HEADER_SIGNATURE,
|
|
45
45
|
CENTRAL_FILE_HEADER_SIGNATURE,
|
|
46
|
+
CENTRAL_FILE_HEADER_LENGTH,
|
|
46
47
|
END_OF_CENTRAL_DIR_SIGNATURE,
|
|
47
48
|
ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE,
|
|
48
49
|
ZIP64_END_OF_CENTRAL_DIR_SIGNATURE,
|
|
@@ -93,6 +94,7 @@ import {
|
|
|
93
94
|
ERR_INVALID_SIGNATURE,
|
|
94
95
|
ERR_INVALID_PASSWORD,
|
|
95
96
|
ERR_INVALID_UNCOMPRESSED_SIZE,
|
|
97
|
+
ERR_INVALID_COMPRESSED_DATA,
|
|
96
98
|
ERR_ABORT_CHECK_PASSWORD
|
|
97
99
|
} from "./codec-pool.js";
|
|
98
100
|
import {
|
|
@@ -134,6 +136,7 @@ import {
|
|
|
134
136
|
OPTION_CHECK_PASSWORD_ONLY,
|
|
135
137
|
OPTION_CHECK_OVERLAPPING_ENTRY_ONLY,
|
|
136
138
|
OPTION_CHECK_OVERLAPPING_ENTRY,
|
|
139
|
+
OPTION_CHECK_AMBIGUITY,
|
|
137
140
|
OPTION_CHECK_SIGNATURE,
|
|
138
141
|
OPTION_USE_WEB_WORKERS,
|
|
139
142
|
OPTION_USE_COMPRESSION_STREAM,
|
|
@@ -152,9 +155,13 @@ const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
|
|
|
152
155
|
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
153
156
|
const ERR_SPLIT_ZIP_FILE = "Split zip file";
|
|
154
157
|
const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
|
|
158
|
+
const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
|
|
155
159
|
const CHARSET_UTF8 = "utf-8";
|
|
156
160
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
157
161
|
const CHARSET_CP437 = "cp437";
|
|
162
|
+
// bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
|
|
163
|
+
// because they are informative only
|
|
164
|
+
const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
|
|
158
165
|
const ZIP64_PROPERTIES = [
|
|
159
166
|
[PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
|
|
160
167
|
[PROPERTY_NAME_COMPRESSED_SIZE, MAX_32_BITS],
|
|
@@ -179,7 +186,7 @@ class ZipReader {
|
|
|
179
186
|
reader: new GenericReader(reader),
|
|
180
187
|
options,
|
|
181
188
|
config: getConfiguration(),
|
|
182
|
-
readRanges:
|
|
189
|
+
readRanges: new Map()
|
|
183
190
|
});
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -212,21 +219,30 @@ class ZipReader {
|
|
|
212
219
|
const commentOffset = endOfDirectoryInfo.offset;
|
|
213
220
|
const commentLength = getUint16(endOfDirectoryView, 20);
|
|
214
221
|
const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
|
|
222
|
+
const checkAmbiguity = getOptionValue(zipReader, options, OPTION_CHECK_AMBIGUITY);
|
|
223
|
+
if (checkAmbiguity && appendedDataOffset != reader.size) {
|
|
224
|
+
throwAmbiguousArchive("appended data");
|
|
225
|
+
}
|
|
215
226
|
let lastDiskNumber = getUint16(endOfDirectoryView, 4);
|
|
216
227
|
const expectedLastDiskNumber = reader.lastDiskNumber || 0;
|
|
217
228
|
let diskNumber = getUint16(endOfDirectoryView, 6);
|
|
218
|
-
let filesLength = getUint16(endOfDirectoryView,
|
|
229
|
+
let filesLength = getUint16(endOfDirectoryView, 10);
|
|
219
230
|
let prependedDataLength = 0;
|
|
220
|
-
let startOffset
|
|
231
|
+
let startOffset;
|
|
232
|
+
let zip64EndOfDirectory;
|
|
221
233
|
if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
|
|
222
|
-
const endOfDirectoryLocatorArray =
|
|
234
|
+
const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
|
|
235
|
+
await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
|
|
236
|
+
new Uint8Array();
|
|
223
237
|
const endOfDirectoryLocatorView = getDataView(endOfDirectoryLocatorArray);
|
|
224
|
-
if (
|
|
238
|
+
if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
|
|
239
|
+
getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
225
240
|
directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
|
|
226
241
|
let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
|
|
227
242
|
let endOfDirectoryView = getDataView(endOfDirectoryArray);
|
|
228
|
-
const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
|
|
229
|
-
if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
|
|
243
|
+
const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH - (reader.lastDiskOffset || 0);
|
|
244
|
+
if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
|
|
245
|
+
directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
|
|
230
246
|
const originalDirectoryDataOffset = directoryDataOffset;
|
|
231
247
|
directoryDataOffset = expectedDirectoryDataOffset;
|
|
232
248
|
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
@@ -235,24 +251,36 @@ class ZipReader {
|
|
|
235
251
|
endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
|
|
236
252
|
endOfDirectoryView = getDataView(endOfDirectoryArray);
|
|
237
253
|
}
|
|
238
|
-
if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
254
|
+
if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
239
255
|
throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
|
|
240
256
|
}
|
|
257
|
+
zip64EndOfDirectory = true;
|
|
241
258
|
if (lastDiskNumber == MAX_16_BITS) {
|
|
242
259
|
lastDiskNumber = getUint32(endOfDirectoryView, 16);
|
|
260
|
+
} else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
|
|
261
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
243
262
|
}
|
|
244
263
|
if (diskNumber == MAX_16_BITS) {
|
|
245
264
|
diskNumber = getUint32(endOfDirectoryView, 20);
|
|
265
|
+
} else if (checkAmbiguity && diskNumber != getUint32(endOfDirectoryView, 20)) {
|
|
266
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
246
267
|
}
|
|
247
268
|
if (filesLength == MAX_16_BITS) {
|
|
248
269
|
filesLength = getBigUint64(endOfDirectoryView, 32);
|
|
270
|
+
} else if (checkAmbiguity && filesLength != getBigUint64(endOfDirectoryView, 32)) {
|
|
271
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
249
272
|
}
|
|
250
273
|
if (directoryDataLength == MAX_32_BITS) {
|
|
251
274
|
directoryDataLength = getBigUint64(endOfDirectoryView, 40);
|
|
275
|
+
} else if (checkAmbiguity && directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
|
|
276
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
252
277
|
}
|
|
253
|
-
directoryDataOffset
|
|
278
|
+
directoryDataOffset = getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
|
|
254
279
|
}
|
|
255
280
|
}
|
|
281
|
+
const declaredDirectoryDataLength = directoryDataLength;
|
|
282
|
+
const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
|
|
283
|
+
(zip64EndOfDirectory ? ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
|
|
256
284
|
if (directoryDataOffset >= reader.size) {
|
|
257
285
|
prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
|
|
258
286
|
directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
|
|
@@ -267,8 +295,11 @@ class ZipReader {
|
|
|
267
295
|
let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
268
296
|
let directoryView = getDataView(directoryArray);
|
|
269
297
|
if (directoryDataLength) {
|
|
270
|
-
|
|
271
|
-
|
|
298
|
+
if (directoryArray.length < 4) {
|
|
299
|
+
throw new Error(ERR_BAD_FORMAT);
|
|
300
|
+
}
|
|
301
|
+
const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
|
|
302
|
+
if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
|
|
272
303
|
const originalDirectoryDataOffset = directoryDataOffset;
|
|
273
304
|
directoryDataOffset = expectedDirectoryDataOffset;
|
|
274
305
|
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
@@ -278,8 +309,8 @@ class ZipReader {
|
|
|
278
309
|
directoryView = getDataView(directoryArray);
|
|
279
310
|
}
|
|
280
311
|
}
|
|
281
|
-
const expectedDirectoryDataLength =
|
|
282
|
-
if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0) {
|
|
312
|
+
const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
|
|
313
|
+
if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
|
|
283
314
|
directoryDataLength = expectedDirectoryDataLength;
|
|
284
315
|
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
285
316
|
directoryView = getDataView(directoryArray);
|
|
@@ -287,16 +318,19 @@ class ZipReader {
|
|
|
287
318
|
if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
|
|
288
319
|
throw new Error(ERR_BAD_FORMAT);
|
|
289
320
|
}
|
|
321
|
+
startOffset = directoryDataOffset;
|
|
290
322
|
const filenameEncoding = getOptionValue(zipReader, options, OPTION_FILENAME_ENCODING);
|
|
291
323
|
const commentEncoding = getOptionValue(zipReader, options, OPTION_COMMENT_ENCODING);
|
|
324
|
+
const filenames = checkAmbiguity ? new Set() : UNDEFINED_VALUE;
|
|
325
|
+
let duplicateFilename;
|
|
292
326
|
for (let indexFile = 0; indexFile < filesLength; indexFile++) {
|
|
293
327
|
const fileEntry = new ZipEntry(reader, config, zipReader.options);
|
|
294
|
-
if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
328
|
+
if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
295
329
|
throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
|
|
296
330
|
}
|
|
297
331
|
readCommonHeader(fileEntry, directoryView, offset + 6);
|
|
298
332
|
const languageEncodingFlag = Boolean(fileEntry.bitFlag.languageEncodingFlag);
|
|
299
|
-
const filenameOffset = offset +
|
|
333
|
+
const filenameOffset = offset + CENTRAL_FILE_HEADER_LENGTH;
|
|
300
334
|
const extraFieldOffset = filenameOffset + fileEntry.filenameLength;
|
|
301
335
|
const commentOffset = extraFieldOffset + fileEntry.extraFieldLength;
|
|
302
336
|
const versionMadeBy = getUint16(directoryView, offset + 4);
|
|
@@ -317,7 +351,7 @@ class ZipReader {
|
|
|
317
351
|
directory: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_DIR_MASK),
|
|
318
352
|
archive: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_ARCHIVE_MASK)
|
|
319
353
|
};
|
|
320
|
-
const offsetFileEntry = getUint32(directoryView, offset + 42)
|
|
354
|
+
const offsetFileEntry = getUint32(directoryView, offset + 42);
|
|
321
355
|
const decode = getOptionValue(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
|
|
322
356
|
const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
|
|
323
357
|
const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
|
|
@@ -330,6 +364,7 @@ class ZipReader {
|
|
|
330
364
|
comment = decodeText(rawComment, rawCommentEncoding);
|
|
331
365
|
}
|
|
332
366
|
Object.assign(fileEntry, {
|
|
367
|
+
index: indexFile,
|
|
333
368
|
versionMadeBy,
|
|
334
369
|
msDosCompatible,
|
|
335
370
|
compressedSize: 0,
|
|
@@ -349,8 +384,15 @@ class ZipReader {
|
|
|
349
384
|
filename,
|
|
350
385
|
comment
|
|
351
386
|
});
|
|
352
|
-
startOffset = Math.max(offsetFileEntry, startOffset);
|
|
353
387
|
readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
|
|
388
|
+
fileEntry.offset += prependedDataLength;
|
|
389
|
+
startOffset = Math.min(fileEntry.offset, startOffset);
|
|
390
|
+
if (checkAmbiguity) {
|
|
391
|
+
if (filenames.has(fileEntry.filename)) {
|
|
392
|
+
duplicateFilename = true;
|
|
393
|
+
}
|
|
394
|
+
filenames.add(fileEntry.filename);
|
|
395
|
+
}
|
|
354
396
|
const unixExternalUpper = (fileEntry.externalFileAttributes >> 16) & MAX_16_BITS;
|
|
355
397
|
if (fileEntry.unixMode === UNDEFINED_VALUE && (unixExternalUpper & (FILE_ATTR_UNIX_DEFAULT_MASK | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_TYPE_DIR)) != 0) {
|
|
356
398
|
fileEntry.unixMode = unixExternalUpper;
|
|
@@ -371,7 +413,7 @@ class ZipReader {
|
|
|
371
413
|
internalFileAttribute: fileEntry.internalFileAttributes,
|
|
372
414
|
externalFileAttribute: fileEntry.externalFileAttributes,
|
|
373
415
|
executable,
|
|
374
|
-
directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
|
|
416
|
+
directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (fileEntry.filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
|
|
375
417
|
zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
|
|
376
418
|
});
|
|
377
419
|
const entry = new Entry(fileEntry);
|
|
@@ -394,6 +436,15 @@ class ZipReader {
|
|
|
394
436
|
}
|
|
395
437
|
yield entry;
|
|
396
438
|
}
|
|
439
|
+
if (checkAmbiguity && offset != declaredDirectoryDataLength) {
|
|
440
|
+
throwAmbiguousArchive("trailing central directory data");
|
|
441
|
+
}
|
|
442
|
+
if (duplicateFilename) {
|
|
443
|
+
throwAmbiguousArchive("duplicate filename");
|
|
444
|
+
}
|
|
445
|
+
if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > 0))) {
|
|
446
|
+
throwAmbiguousArchive("prepended data");
|
|
447
|
+
}
|
|
397
448
|
const extractPrependedData = getOptionValue(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
|
|
398
449
|
const extractAppendedData = getOptionValue(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
|
|
399
450
|
if (extractPrependedData) {
|
|
@@ -433,9 +484,21 @@ class ZipReaderStream {
|
|
|
433
484
|
readable: (function () {
|
|
434
485
|
const { readable, writable } = new TransformStream();
|
|
435
486
|
if (value.getData) {
|
|
436
|
-
|
|
487
|
+
getData();
|
|
437
488
|
return readable;
|
|
438
489
|
}
|
|
490
|
+
|
|
491
|
+
async function getData() {
|
|
492
|
+
try {
|
|
493
|
+
await value.getData(writable);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
try {
|
|
496
|
+
await writable.abort(error);
|
|
497
|
+
} catch {
|
|
498
|
+
// ignored
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
439
502
|
})()
|
|
440
503
|
};
|
|
441
504
|
delete chunk.getData;
|
|
@@ -461,8 +524,10 @@ export {
|
|
|
461
524
|
ERR_INVALID_SIGNATURE,
|
|
462
525
|
ERR_INVALID_PASSWORD,
|
|
463
526
|
ERR_INVALID_UNCOMPRESSED_SIZE,
|
|
527
|
+
ERR_INVALID_COMPRESSED_DATA,
|
|
464
528
|
ERR_SPLIT_ZIP_FILE,
|
|
465
|
-
ERR_OVERLAPPING_ENTRY
|
|
529
|
+
ERR_OVERLAPPING_ENTRY,
|
|
530
|
+
ERR_AMBIGUOUS_ARCHIVE
|
|
466
531
|
};
|
|
467
532
|
|
|
468
533
|
class ZipEntry {
|
|
@@ -479,6 +544,7 @@ class ZipEntry {
|
|
|
479
544
|
const zipEntry = this;
|
|
480
545
|
const {
|
|
481
546
|
reader,
|
|
547
|
+
index,
|
|
482
548
|
offset,
|
|
483
549
|
diskNumberStart,
|
|
484
550
|
extraFieldAES,
|
|
@@ -510,21 +576,36 @@ class ZipEntry {
|
|
|
510
576
|
if ((compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64) && !passThrough) {
|
|
511
577
|
throw new Error(ERR_UNSUPPORTED_COMPRESSION);
|
|
512
578
|
}
|
|
513
|
-
if (getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
|
|
579
|
+
if (dataArray.length < HEADER_SIZE || getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
|
|
514
580
|
throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
|
|
515
581
|
}
|
|
516
582
|
readCommonHeader(localDirectory, dataView, 4);
|
|
517
583
|
const {
|
|
518
584
|
extraFieldLength,
|
|
519
|
-
filenameLength
|
|
520
|
-
lastAccessDate,
|
|
521
|
-
creationDate
|
|
585
|
+
filenameLength
|
|
522
586
|
} = localDirectory;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
587
|
+
const checkAmbiguity = getOptionValue(zipEntry, options, OPTION_CHECK_AMBIGUITY);
|
|
588
|
+
let rawLocalFilename = new Uint8Array();
|
|
589
|
+
if (checkAmbiguity && (filenameLength || extraFieldLength)) {
|
|
590
|
+
const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
|
|
591
|
+
rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
|
|
592
|
+
localDirectory.rawExtraField = trailingDataArray.subarray(filenameLength);
|
|
593
|
+
} else {
|
|
594
|
+
localDirectory.rawExtraField = extraFieldLength ?
|
|
595
|
+
await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
|
|
596
|
+
new Uint8Array();
|
|
597
|
+
}
|
|
526
598
|
readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
|
|
527
|
-
|
|
599
|
+
if (checkAmbiguity) {
|
|
600
|
+
checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename);
|
|
601
|
+
}
|
|
602
|
+
const { lastAccessDate, creationDate } = localDirectory;
|
|
603
|
+
if (lastAccessDate) {
|
|
604
|
+
fileEntry.lastAccessDate = lastAccessDate;
|
|
605
|
+
}
|
|
606
|
+
if (creationDate) {
|
|
607
|
+
fileEntry.creationDate = creationDate;
|
|
608
|
+
}
|
|
528
609
|
const encrypted = zipEntry.encrypted && localDirectory.encrypted && !passThrough;
|
|
529
610
|
const zipCrypto = encrypted && !extraFieldAES;
|
|
530
611
|
if (!passThrough) {
|
|
@@ -570,7 +651,7 @@ class ZipEntry {
|
|
|
570
651
|
outputSize: passThrough ? compressedSize : uncompressedSize,
|
|
571
652
|
signature,
|
|
572
653
|
compressed: compressionMethod != 0 && !passThrough,
|
|
573
|
-
encrypted
|
|
654
|
+
encrypted,
|
|
574
655
|
useWebWorkers: getOptionValue(zipEntry, options, OPTION_USE_WEB_WORKERS),
|
|
575
656
|
useCompressionStream,
|
|
576
657
|
transferStreams: getOptionValue(zipEntry, options, OPTION_TRANSFER_STREAMS),
|
|
@@ -584,6 +665,7 @@ class ZipEntry {
|
|
|
584
665
|
await detectOverlappingEntry({
|
|
585
666
|
reader,
|
|
586
667
|
fileEntry,
|
|
668
|
+
index,
|
|
587
669
|
offset,
|
|
588
670
|
diskNumberStart,
|
|
589
671
|
signature,
|
|
@@ -686,7 +768,7 @@ function readCommonFooter(fileEntry, directory, dataView, offset, localDirectory
|
|
|
686
768
|
directory.extraFieldUnicodeComment = extraFieldUnicodeComment;
|
|
687
769
|
}
|
|
688
770
|
const extraFieldAES = extraField.get(EXTRAFIELD_TYPE_AES);
|
|
689
|
-
if (extraFieldAES) {
|
|
771
|
+
if (extraFieldAES && extraFieldAES.data.length >= 7) {
|
|
690
772
|
readExtraFieldAES(extraFieldAES, directory, compressionMethod);
|
|
691
773
|
directory.extraFieldAES = extraFieldAES;
|
|
692
774
|
} else {
|
|
@@ -723,19 +805,23 @@ function readExtraFieldZip64(extraFieldZip64, directory) {
|
|
|
723
805
|
directory.zip64 = true;
|
|
724
806
|
const extraFieldView = getDataView(extraFieldZip64.data);
|
|
725
807
|
const missingProperties = ZIP64_PROPERTIES.filter(([propertyName, max]) => directory[propertyName] == max);
|
|
808
|
+
const requiredLength = missingProperties.reduce((length, [, max]) => length + ZIP64_EXTRACTION[max].bytes, 0);
|
|
809
|
+
if (extraFieldZip64.data.length < requiredLength) {
|
|
810
|
+
throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
|
|
811
|
+
}
|
|
726
812
|
for (let indexMissingProperty = 0, offset = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
|
|
727
813
|
const [propertyName, max] = missingProperties[indexMissingProperty];
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
offset += extraction.bytes;
|
|
732
|
-
} else if (extraFieldZip64[propertyName]) {
|
|
733
|
-
throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
|
|
734
|
-
}
|
|
814
|
+
const extraction = ZIP64_EXTRACTION[max];
|
|
815
|
+
directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
|
|
816
|
+
offset += extraction.bytes;
|
|
735
817
|
}
|
|
736
818
|
}
|
|
737
819
|
|
|
738
820
|
function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
|
|
821
|
+
if (extraFieldUnicode.data.length < 5) {
|
|
822
|
+
extraFieldUnicode.valid = false;
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
739
825
|
const extraFieldView = getDataView(extraFieldUnicode.data);
|
|
740
826
|
const crc32 = new Crc32();
|
|
741
827
|
crc32.append(fileEntry[rawPropertyName]);
|
|
@@ -808,32 +894,30 @@ function readExtraFieldNTFS(extraFieldNTFS, directory) {
|
|
|
808
894
|
function readExtraFieldUnix(extraField, directory, isInfoZip) {
|
|
809
895
|
try {
|
|
810
896
|
const view = getDataView(new Uint8Array(extraField.data));
|
|
811
|
-
let
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
897
|
+
let uid, gid;
|
|
898
|
+
if (isInfoZip) {
|
|
899
|
+
// Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
|
|
900
|
+
let offset = 0;
|
|
901
|
+
const version = getUint8(view, offset++);
|
|
902
|
+
const uidSize = getUint8(view, offset++);
|
|
903
|
+
uid = unpackUnixId(extraField.data.subarray(offset, offset + uidSize));
|
|
904
|
+
offset += uidSize;
|
|
905
|
+
const gidSize = getUint8(view, offset++);
|
|
906
|
+
gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
|
|
907
|
+
Object.assign(extraField, { version, uid, gid });
|
|
908
|
+
} else if (extraField.data.length >= 4) {
|
|
909
|
+
// Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
|
|
910
|
+
// carried in the external file attributes, not here)
|
|
911
|
+
uid = getUint16(view, 0);
|
|
912
|
+
gid = getUint16(view, 2);
|
|
913
|
+
Object.assign(extraField, { uid, gid });
|
|
914
|
+
}
|
|
828
915
|
if (uid !== UNDEFINED_VALUE) {
|
|
829
916
|
directory.uid = uid;
|
|
830
917
|
}
|
|
831
918
|
if (gid !== UNDEFINED_VALUE) {
|
|
832
919
|
directory.gid = gid;
|
|
833
920
|
}
|
|
834
|
-
if (unixMode !== UNDEFINED_VALUE) {
|
|
835
|
-
directory.unixMode = unixMode;
|
|
836
|
-
}
|
|
837
921
|
} catch {
|
|
838
922
|
// ignored
|
|
839
923
|
}
|
|
@@ -847,6 +931,9 @@ function unpackUnixId(bytes) {
|
|
|
847
931
|
}
|
|
848
932
|
|
|
849
933
|
function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory) {
|
|
934
|
+
if (!extraFieldExtendedTimestamp.data.length) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
850
937
|
const extraFieldView = getDataView(extraFieldExtendedTimestamp.data);
|
|
851
938
|
const flags = getUint8(extraFieldView, 0);
|
|
852
939
|
const timeProperties = [];
|
|
@@ -872,7 +959,8 @@ function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory,
|
|
|
872
959
|
timeProperties.forEach((propertyName, indexProperty) => {
|
|
873
960
|
if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
|
|
874
961
|
const time = getUint32(extraFieldView, offset);
|
|
875
|
-
|
|
962
|
+
// the extended timestamp is a signed 32-bit time_t value
|
|
963
|
+
directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
|
|
876
964
|
const rawPropertyName = timeRawProperties[indexProperty];
|
|
877
965
|
extraFieldExtendedTimestamp[rawPropertyName] = time;
|
|
878
966
|
}
|
|
@@ -883,6 +971,7 @@ function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory,
|
|
|
883
971
|
async function detectOverlappingEntry({
|
|
884
972
|
reader,
|
|
885
973
|
fileEntry,
|
|
974
|
+
index,
|
|
886
975
|
offset,
|
|
887
976
|
diskNumberStart,
|
|
888
977
|
signature,
|
|
@@ -894,10 +983,9 @@ async function detectOverlappingEntry({
|
|
|
894
983
|
readRanges
|
|
895
984
|
}) {
|
|
896
985
|
let diskOffset = 0;
|
|
897
|
-
if (diskNumberStart) {
|
|
898
|
-
for (let indexReader = 0; indexReader < diskNumberStart; indexReader++) {
|
|
899
|
-
|
|
900
|
-
diskOffset += diskReader.size;
|
|
986
|
+
if (diskNumberStart && reader.readers) {
|
|
987
|
+
for (let indexReader = 0; indexReader < Math.min(diskNumberStart, reader.readers.length); indexReader++) {
|
|
988
|
+
diskOffset += reader.readers[indexReader].size;
|
|
901
989
|
}
|
|
902
990
|
}
|
|
903
991
|
let dataDescriptorLength = 0;
|
|
@@ -910,7 +998,8 @@ async function detectOverlappingEntry({
|
|
|
910
998
|
}
|
|
911
999
|
if (dataDescriptorLength) {
|
|
912
1000
|
const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH, diskNumberStart);
|
|
913
|
-
const dataDescriptorSignature =
|
|
1001
|
+
const dataDescriptorSignature = dataDescriptorArray.length == dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH &&
|
|
1002
|
+
getUint32(getDataView(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
|
|
914
1003
|
if (dataDescriptorSignature) {
|
|
915
1004
|
const readSignature = getUint32(getDataView(dataDescriptorArray), 4);
|
|
916
1005
|
let readCompressedSize;
|
|
@@ -935,14 +1024,14 @@ async function detectOverlappingEntry({
|
|
|
935
1024
|
end: diskOffset + dataOffset + compressedSize + dataDescriptorLength,
|
|
936
1025
|
fileEntry
|
|
937
1026
|
};
|
|
938
|
-
for (const otherRange of readRanges) {
|
|
939
|
-
if (
|
|
1027
|
+
for (const [otherIndex, otherRange] of readRanges) {
|
|
1028
|
+
if (otherIndex != index && range.start < otherRange.end && otherRange.start < range.end) {
|
|
940
1029
|
const error = new Error(ERR_OVERLAPPING_ENTRY);
|
|
941
1030
|
error.overlappingEntry = otherRange.fileEntry;
|
|
942
1031
|
throw error;
|
|
943
1032
|
}
|
|
944
1033
|
}
|
|
945
|
-
readRanges.
|
|
1034
|
+
readRanges.set(index, range);
|
|
946
1035
|
}
|
|
947
1036
|
|
|
948
1037
|
async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
|
|
@@ -967,6 +1056,35 @@ async function seekSignature(reader, signature, startOffset, minimumBytes, maxim
|
|
|
967
1056
|
}
|
|
968
1057
|
}
|
|
969
1058
|
|
|
1059
|
+
function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
|
|
1060
|
+
const { rawFilename } = zipEntry;
|
|
1061
|
+
if (rawLocalFilename.length != rawFilename.length ||
|
|
1062
|
+
rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte])) {
|
|
1063
|
+
throwAmbiguousArchive("mismatched local file header (filename)");
|
|
1064
|
+
}
|
|
1065
|
+
if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
|
|
1066
|
+
throwAmbiguousArchive("mismatched local file header (general purpose bit flag)");
|
|
1067
|
+
}
|
|
1068
|
+
if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
|
|
1069
|
+
throwAmbiguousArchive("mismatched local file header (compression method)");
|
|
1070
|
+
}
|
|
1071
|
+
// when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
|
|
1072
|
+
// also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
|
|
1073
|
+
if (!localDirectory.bitFlag.dataDescriptor &&
|
|
1074
|
+
(localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
|
|
1075
|
+
(localDirectory.signature != zipEntry.signature ||
|
|
1076
|
+
localDirectory.compressedSize != zipEntry.compressedSize ||
|
|
1077
|
+
localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
|
|
1078
|
+
throwAmbiguousArchive("mismatched local file header (signature or sizes)");
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function throwAmbiguousArchive(reason) {
|
|
1083
|
+
const error = new Error(ERR_AMBIGUOUS_ARCHIVE);
|
|
1084
|
+
error.reason = reason;
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
970
1088
|
function getOptionValue(zipReader, options, name) {
|
|
971
1089
|
return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
|
|
972
1090
|
}
|
|
@@ -1005,5 +1123,5 @@ function setUint32(view, offset, value) {
|
|
|
1005
1123
|
}
|
|
1006
1124
|
|
|
1007
1125
|
function getDataView(array) {
|
|
1008
|
-
return new DataView(array.buffer);
|
|
1126
|
+
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
1009
1127
|
}
|