@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.
- package/BENCHMARKS.md +168 -0
- package/README.md +2 -2
- package/deno.json +1 -1
- package/dist/zip-core.js +1113 -574
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +1209 -622
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +1270 -632
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +1347 -686
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +1114 -575
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +1116 -577
- 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 +1193 -631
- package/dist/zip.min.js +1 -1
- package/eslint.config.mjs +6 -1
- package/index-native.cjs +1270 -632
- package/index-native.min.js +1 -1
- package/index.cjs +1347 -686
- 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 +130 -35
- 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 +89 -5
- package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
- package/lib/core/streams/zlib-wasm/zlib-streams.js +79 -56
- 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/dist/zip-fs-core.js
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
|
|
50
50
|
const ZIP64_END_OF_CENTRAL_DIR_SIGNATURE = 0x06064b50;
|
|
51
51
|
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = 0x07064b50;
|
|
52
|
+
const CENTRAL_FILE_HEADER_LENGTH = 46;
|
|
52
53
|
const END_OF_CENTRAL_DIR_LENGTH = 22;
|
|
53
54
|
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH = 20;
|
|
54
55
|
const ZIP64_END_OF_CENTRAL_DIR_LENGTH = 56;
|
|
@@ -96,9 +97,11 @@
|
|
|
96
97
|
const DIRECTORY_SIGNATURE = "/";
|
|
97
98
|
|
|
98
99
|
const HEADER_SIZE = 30;
|
|
100
|
+
const HEADER_OFFSET_VERSION = 0;
|
|
99
101
|
const HEADER_OFFSET_SIGNATURE = 10;
|
|
100
102
|
const HEADER_OFFSET_COMPRESSED_SIZE = 14;
|
|
101
103
|
const HEADER_OFFSET_UNCOMPRESSED_SIZE = 18;
|
|
104
|
+
const LOCAL_HEADER_COMMON_OFFSET = 4;
|
|
102
105
|
|
|
103
106
|
const MAX_DATE = new Date(2107, 11, 31);
|
|
104
107
|
const MIN_DATE = new Date(1980, 0, 1);
|
|
@@ -153,6 +156,7 @@
|
|
|
153
156
|
chunkSize: 64 * 1024,
|
|
154
157
|
maxWorkers,
|
|
155
158
|
terminateWorkerTimeout: 5000,
|
|
159
|
+
workerStarvationTimeout: 5000,
|
|
156
160
|
useWebWorkers: true,
|
|
157
161
|
useCompressionStream: true,
|
|
158
162
|
CompressionStream: typeof CompressionStream != UNDEFINED_TYPE && CompressionStream,
|
|
@@ -205,6 +209,7 @@
|
|
|
205
209
|
|
|
206
210
|
const CONTENT_TYPE_TEXT_PLAIN = "text/plain";
|
|
207
211
|
const HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
|
|
212
|
+
const HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
|
|
208
213
|
const HTTP_HEADER_CONTENT_RANGE = "Content-Range";
|
|
209
214
|
const HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
|
|
210
215
|
const HTTP_HEADER_RANGE = "Range";
|
|
@@ -212,7 +217,7 @@
|
|
|
212
217
|
const HTTP_METHOD_HEAD = "HEAD";
|
|
213
218
|
const HTTP_METHOD_GET = "GET";
|
|
214
219
|
const HTTP_RANGE_UNIT = "bytes";
|
|
215
|
-
const DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
220
|
+
const DEFAULT_CHUNK_SIZE$1 = 64 * 1024;
|
|
216
221
|
const DEFAULT_BUFFER_SIZE = 256 * 1024;
|
|
217
222
|
|
|
218
223
|
const PROPERTY_NAME_WRITABLE = "writable";
|
|
@@ -232,7 +237,7 @@
|
|
|
232
237
|
|
|
233
238
|
get readable() {
|
|
234
239
|
const reader = this;
|
|
235
|
-
const { chunkSize = DEFAULT_CHUNK_SIZE } = reader;
|
|
240
|
+
const { chunkSize = DEFAULT_CHUNK_SIZE$1 } = reader;
|
|
236
241
|
const readable = new ReadableStream({
|
|
237
242
|
start() {
|
|
238
243
|
this.chunkOffset = 0;
|
|
@@ -329,7 +334,7 @@
|
|
|
329
334
|
|
|
330
335
|
writeUint8Array(array) {
|
|
331
336
|
const writer = this;
|
|
332
|
-
let indexArray
|
|
337
|
+
let indexArray;
|
|
333
338
|
let dataString = writer.pending;
|
|
334
339
|
const delta = writer.pending.length;
|
|
335
340
|
writer.pending = "";
|
|
@@ -339,12 +344,10 @@
|
|
|
339
344
|
for (; indexArray < array.length; indexArray++) {
|
|
340
345
|
writer.pending += String.fromCharCode(array[indexArray]);
|
|
341
346
|
}
|
|
342
|
-
if (dataString.length) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
writer.pending += dataString;
|
|
347
|
-
}
|
|
347
|
+
if (dataString.length > 2) {
|
|
348
|
+
writer.data += btoa(dataString);
|
|
349
|
+
} else {
|
|
350
|
+
writer.pending = dataString + writer.pending;
|
|
348
351
|
}
|
|
349
352
|
}
|
|
350
353
|
|
|
@@ -391,6 +394,9 @@
|
|
|
391
394
|
}
|
|
392
395
|
});
|
|
393
396
|
writer.blob = new Response(transformStream.readable, { headers }).blob();
|
|
397
|
+
// mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
|
|
398
|
+
// surfaces them
|
|
399
|
+
writer.blob.catch(() => { });
|
|
394
400
|
}
|
|
395
401
|
|
|
396
402
|
getData() {
|
|
@@ -475,7 +481,8 @@
|
|
|
475
481
|
preventHeadRequest,
|
|
476
482
|
useRangeHeader,
|
|
477
483
|
forceRangeRequests,
|
|
478
|
-
combineSizeEocd
|
|
484
|
+
combineSizeEocd,
|
|
485
|
+
fetch
|
|
479
486
|
} = options;
|
|
480
487
|
options = Object.assign({}, options);
|
|
481
488
|
delete options.preventHeadRequest;
|
|
@@ -483,13 +490,15 @@
|
|
|
483
490
|
delete options.forceRangeRequests;
|
|
484
491
|
delete options.combineSizeEocd;
|
|
485
492
|
delete options.useXHR;
|
|
493
|
+
delete options.fetch;
|
|
486
494
|
Object.assign(httpReader, {
|
|
487
495
|
url,
|
|
488
496
|
options,
|
|
489
497
|
preventHeadRequest,
|
|
490
498
|
useRangeHeader,
|
|
491
499
|
forceRangeRequests,
|
|
492
|
-
combineSizeEocd
|
|
500
|
+
combineSizeEocd,
|
|
501
|
+
fetch
|
|
493
502
|
});
|
|
494
503
|
}
|
|
495
504
|
|
|
@@ -508,7 +517,10 @@
|
|
|
508
517
|
throw new Error(ERR_HTTP_RANGE);
|
|
509
518
|
} else {
|
|
510
519
|
if (combineSizeEocd) {
|
|
511
|
-
|
|
520
|
+
const eocdCache = new Uint8Array(await response.arrayBuffer());
|
|
521
|
+
if (response.status == 206 && eocdCache.length == END_OF_CENTRAL_DIR_LENGTH) {
|
|
522
|
+
httpReader.eocdCache = eocdCache;
|
|
523
|
+
}
|
|
512
524
|
}
|
|
513
525
|
let contentSize;
|
|
514
526
|
const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
|
|
@@ -554,7 +566,18 @@
|
|
|
554
566
|
if (response.status != 206) {
|
|
555
567
|
throw new Error(ERR_HTTP_RANGE);
|
|
556
568
|
}
|
|
557
|
-
|
|
569
|
+
const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
|
|
570
|
+
if (contentRangeHeader) {
|
|
571
|
+
const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
|
|
572
|
+
if (!Number.isNaN(rangeStart) && rangeStart != index) {
|
|
573
|
+
throw new Error(ERR_HTTP_RANGE);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
577
|
+
if (data.length != length) {
|
|
578
|
+
throw new Error(ERR_HTTP_RANGE);
|
|
579
|
+
}
|
|
580
|
+
return data;
|
|
558
581
|
}
|
|
559
582
|
} else {
|
|
560
583
|
const { data } = httpReader;
|
|
@@ -591,9 +614,9 @@
|
|
|
591
614
|
async function getRequestData(httpReader, sendRequest) {
|
|
592
615
|
const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
|
|
593
616
|
httpReader.data = new Uint8Array(await response.arrayBuffer());
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
617
|
+
// the downloaded content is authoritative, e.g. the Content-Length response header
|
|
618
|
+
// of the HEAD request can be the encoded size when the content is compressed
|
|
619
|
+
httpReader.size = httpReader.data.length;
|
|
597
620
|
}
|
|
598
621
|
|
|
599
622
|
async function getContentLength(httpReader, sendRequest, getRequestData) {
|
|
@@ -602,7 +625,7 @@
|
|
|
602
625
|
} else {
|
|
603
626
|
const response = await sendRequest(HTTP_METHOD_HEAD, httpReader, getHeaders(httpReader));
|
|
604
627
|
const contentLength = response.headers.get(HTTP_HEADER_CONTENT_LENGTH);
|
|
605
|
-
if (contentLength) {
|
|
628
|
+
if (contentLength && !response.headers.get(HTTP_HEADER_CONTENT_ENCODING)) {
|
|
606
629
|
httpReader.size = Number(contentLength);
|
|
607
630
|
} else {
|
|
608
631
|
await getRequestData(httpReader, httpReader.options);
|
|
@@ -610,8 +633,8 @@
|
|
|
610
633
|
}
|
|
611
634
|
}
|
|
612
635
|
|
|
613
|
-
async function sendFetchRequest(method, { options, url }, headers) {
|
|
614
|
-
const response = await
|
|
636
|
+
async function sendFetchRequest(method, { fetch: fetchFunction = fetch, options, url }, headers) {
|
|
637
|
+
const response = await fetchFunction(url, Object.assign({}, options, { method, headers }));
|
|
615
638
|
if (response.status < 400) {
|
|
616
639
|
return response;
|
|
617
640
|
} else {
|
|
@@ -657,7 +680,8 @@
|
|
|
657
680
|
super();
|
|
658
681
|
Object.assign(this, {
|
|
659
682
|
url,
|
|
660
|
-
|
|
683
|
+
// an explicit fetch implementation takes precedence over useXHR
|
|
684
|
+
reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
|
|
661
685
|
});
|
|
662
686
|
}
|
|
663
687
|
|
|
@@ -749,7 +773,7 @@
|
|
|
749
773
|
reader.lastDiskNumber = 0;
|
|
750
774
|
reader.lastDiskOffset = 0;
|
|
751
775
|
await Promise.all(readers.map(async (diskReader, indexDiskReader) => {
|
|
752
|
-
await diskReader
|
|
776
|
+
await initStream(diskReader);
|
|
753
777
|
if (indexDiskReader != readers.length - 1) {
|
|
754
778
|
reader.lastDiskOffset += diskReader.size;
|
|
755
779
|
}
|
|
@@ -833,14 +857,26 @@
|
|
|
833
857
|
writer.diskOffset += diskSourceWriter.size;
|
|
834
858
|
writer.diskNumber++;
|
|
835
859
|
diskWriter = null;
|
|
836
|
-
|
|
860
|
+
// the next disk is not opened yet but its space is available; leaving a
|
|
861
|
+
// stale value here would force a new disk for every subsequent write
|
|
862
|
+
writer.availableSize = writer.maxSize;
|
|
863
|
+
if (chunk.length > availableSize) {
|
|
864
|
+
await this.write(chunk.subarray(availableSize));
|
|
865
|
+
}
|
|
837
866
|
} else {
|
|
838
867
|
await writeChunk(chunk);
|
|
839
868
|
}
|
|
840
869
|
},
|
|
841
870
|
async close() {
|
|
842
|
-
|
|
843
|
-
|
|
871
|
+
if (diskWriter) {
|
|
872
|
+
await diskWriter.ready;
|
|
873
|
+
await closeDisk();
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
async abort(reason) {
|
|
877
|
+
if (diskWriter) {
|
|
878
|
+
await diskWriter.abort(reason);
|
|
879
|
+
}
|
|
844
880
|
}
|
|
845
881
|
});
|
|
846
882
|
Object.defineProperty(writer, PROPERTY_NAME_WRITABLE, {
|
|
@@ -855,7 +891,6 @@
|
|
|
855
891
|
await diskWriter.ready;
|
|
856
892
|
await diskWriter.write(chunk);
|
|
857
893
|
diskSourceWriter.size += chunkLength;
|
|
858
|
-
writer.size += chunkLength;
|
|
859
894
|
writer.availableSize -= chunkLength;
|
|
860
895
|
}
|
|
861
896
|
}
|
|
@@ -1672,37 +1707,6 @@
|
|
|
1672
1707
|
}
|
|
1673
1708
|
};
|
|
1674
1709
|
|
|
1675
|
-
/**
|
|
1676
|
-
* Random values
|
|
1677
|
-
* @namespace
|
|
1678
|
-
*/
|
|
1679
|
-
const random = {
|
|
1680
|
-
/**
|
|
1681
|
-
* Generate random words with pure js, cryptographically not as strong & safe as native implementation.
|
|
1682
|
-
* @param {TypedArray} typedArray The array to fill.
|
|
1683
|
-
* @return {TypedArray} The random values.
|
|
1684
|
-
*/
|
|
1685
|
-
getRandomValues(typedArray) {
|
|
1686
|
-
const words = new Uint32Array(typedArray.buffer);
|
|
1687
|
-
const r = (m_w) => {
|
|
1688
|
-
let m_z = 0x3ade68b1;
|
|
1689
|
-
const mask = 0xffffffff;
|
|
1690
|
-
return function () {
|
|
1691
|
-
m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
|
|
1692
|
-
m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
|
|
1693
|
-
const result = ((((m_z << 0x10) + m_w) & mask) / 0x100000000) + .5;
|
|
1694
|
-
return result * (Math.random() > .5 ? 1 : -1);
|
|
1695
|
-
};
|
|
1696
|
-
};
|
|
1697
|
-
for (let i = 0, rcache; i < typedArray.length; i += 4) {
|
|
1698
|
-
const _r = r((rcache || Math.random()) * 0x100000000);
|
|
1699
|
-
rcache = _r() * 0x3ade67b7;
|
|
1700
|
-
words[i / 4] = (_r() * 0x100000000) | 0;
|
|
1701
|
-
}
|
|
1702
|
-
return typedArray;
|
|
1703
|
-
}
|
|
1704
|
-
};
|
|
1705
|
-
|
|
1706
1710
|
/** @fileOverview CTR mode implementation.
|
|
1707
1711
|
*
|
|
1708
1712
|
* Special thanks to Roy Nicholson for pointing out a bug in our
|
|
@@ -1933,12 +1937,15 @@
|
|
|
1933
1937
|
const ERR_INVALID_PASSWORD = "Invalid password";
|
|
1934
1938
|
const ERR_INVALID_SIGNATURE = "Invalid signature";
|
|
1935
1939
|
const ERR_ABORT_CHECK_PASSWORD = "zipjs-abort-check-password";
|
|
1940
|
+
const ERR_UNSUPPORTED_CRYPTO_API = "Crypto API not supported";
|
|
1936
1941
|
|
|
1937
1942
|
function getRandomValues(array) {
|
|
1938
1943
|
if (GET_RANDOM_VALUES_SUPPORTED) {
|
|
1939
1944
|
return crypto.getRandomValues(array);
|
|
1940
1945
|
} else {
|
|
1941
|
-
|
|
1946
|
+
// all supported runtimes expose crypto.getRandomValues; failing hard beats
|
|
1947
|
+
// silently generating predictable encryption salts
|
|
1948
|
+
throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
|
|
1942
1949
|
}
|
|
1943
1950
|
}
|
|
1944
1951
|
|
|
@@ -1997,13 +2004,12 @@
|
|
|
1997
2004
|
|
|
1998
2005
|
class AESDecryptionStream extends TransformStream {
|
|
1999
2006
|
|
|
2000
|
-
constructor({ password, rawPassword,
|
|
2007
|
+
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
2001
2008
|
super({
|
|
2002
2009
|
start() {
|
|
2003
2010
|
Object.assign(this, {
|
|
2004
2011
|
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
2005
2012
|
password: encodePassword(password, rawPassword),
|
|
2006
|
-
signed,
|
|
2007
2013
|
strength: encryptionStrength - 1,
|
|
2008
2014
|
pending: new Uint8Array()
|
|
2009
2015
|
});
|
|
@@ -2032,7 +2038,6 @@
|
|
|
2032
2038
|
},
|
|
2033
2039
|
async flush(controller) {
|
|
2034
2040
|
const {
|
|
2035
|
-
signed,
|
|
2036
2041
|
ctr,
|
|
2037
2042
|
hmac,
|
|
2038
2043
|
pending,
|
|
@@ -2049,13 +2054,15 @@
|
|
|
2049
2054
|
const decryptedChunk = ctr.update(encryptedChunk);
|
|
2050
2055
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
2051
2056
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2057
|
+
const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
|
|
2058
|
+
// branch-free comparison: accumulate the differences instead of
|
|
2059
|
+
// short-circuiting on the first mismatching byte
|
|
2060
|
+
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
2061
|
+
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
2062
|
+
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
2063
|
+
}
|
|
2064
|
+
if (invalidSignature) {
|
|
2065
|
+
throw new Error(ERR_INVALID_SIGNATURE);
|
|
2059
2066
|
}
|
|
2060
2067
|
controller.enqueue(decryptedChunkArray);
|
|
2061
2068
|
}
|
|
@@ -2127,11 +2134,11 @@
|
|
|
2127
2134
|
hmac,
|
|
2128
2135
|
pending
|
|
2129
2136
|
} = aesCrypto;
|
|
2130
|
-
const inputLength = input.length - paddingEnd;
|
|
2131
2137
|
if (pending.length) {
|
|
2132
2138
|
input = concat(pending, input);
|
|
2133
|
-
output = expand(output, inputLength - (inputLength % BLOCK_LENGTH));
|
|
2134
2139
|
}
|
|
2140
|
+
const inputLength = input.length - paddingEnd;
|
|
2141
|
+
output = expand(output, paddingStart + (inputLength - (inputLength % BLOCK_LENGTH)));
|
|
2135
2142
|
let offset;
|
|
2136
2143
|
for (offset = 0; offset <= inputLength - BLOCK_LENGTH; offset += BLOCK_LENGTH) {
|
|
2137
2144
|
const inputChunk = toBits(codecBytes, subarray(input, offset, offset + BLOCK_LENGTH));
|
|
@@ -2279,21 +2286,24 @@
|
|
|
2279
2286
|
|
|
2280
2287
|
class ZipCryptoDecryptionStream extends TransformStream {
|
|
2281
2288
|
|
|
2282
|
-
constructor({ password, passwordVerification, checkPasswordOnly }) {
|
|
2289
|
+
constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
|
|
2283
2290
|
super({
|
|
2284
2291
|
start() {
|
|
2285
2292
|
Object.assign(this, {
|
|
2286
2293
|
password,
|
|
2294
|
+
rawPassword,
|
|
2287
2295
|
passwordVerification
|
|
2288
2296
|
});
|
|
2289
|
-
createKeys(this, password);
|
|
2297
|
+
createKeys(this, password, rawPassword);
|
|
2290
2298
|
},
|
|
2291
2299
|
transform(chunk, controller) {
|
|
2292
2300
|
const zipCrypto = this;
|
|
2293
|
-
if (zipCrypto.password) {
|
|
2301
|
+
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
2294
2302
|
const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
|
|
2295
|
-
zipCrypto.password = null;
|
|
2296
|
-
|
|
2303
|
+
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
2304
|
+
// verify the password with a branch-free comparison of the verification byte, mirroring
|
|
2305
|
+
// the constant-time AES HMAC signature check
|
|
2306
|
+
if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
|
|
2297
2307
|
throw new Error(ERR_INVALID_PASSWORD);
|
|
2298
2308
|
}
|
|
2299
2309
|
chunk = chunk.subarray(HEADER_LENGTH);
|
|
@@ -2310,21 +2320,22 @@
|
|
|
2310
2320
|
|
|
2311
2321
|
class ZipCryptoEncryptionStream extends TransformStream {
|
|
2312
2322
|
|
|
2313
|
-
constructor({ password, passwordVerification }) {
|
|
2323
|
+
constructor({ password, rawPassword, passwordVerification }) {
|
|
2314
2324
|
super({
|
|
2315
2325
|
start() {
|
|
2316
2326
|
Object.assign(this, {
|
|
2317
2327
|
password,
|
|
2328
|
+
rawPassword,
|
|
2318
2329
|
passwordVerification
|
|
2319
2330
|
});
|
|
2320
|
-
createKeys(this, password);
|
|
2331
|
+
createKeys(this, password, rawPassword);
|
|
2321
2332
|
},
|
|
2322
2333
|
transform(chunk, controller) {
|
|
2323
2334
|
const zipCrypto = this;
|
|
2324
2335
|
let output;
|
|
2325
2336
|
let offset;
|
|
2326
|
-
if (zipCrypto.password) {
|
|
2327
|
-
zipCrypto.password = null;
|
|
2337
|
+
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
2338
|
+
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
2328
2339
|
const header = getRandomValues(new Uint8Array(HEADER_LENGTH));
|
|
2329
2340
|
header[HEADER_LENGTH - 1] = zipCrypto.passwordVerification;
|
|
2330
2341
|
output = new Uint8Array(chunk.length + header.length);
|
|
@@ -2359,25 +2370,31 @@
|
|
|
2359
2370
|
return output;
|
|
2360
2371
|
}
|
|
2361
2372
|
|
|
2362
|
-
function createKeys(target, password) {
|
|
2373
|
+
function createKeys(target, password, rawPassword) {
|
|
2363
2374
|
const keys = [0x12345678, 0x23456789, 0x34567890];
|
|
2364
2375
|
Object.assign(target, {
|
|
2365
2376
|
keys,
|
|
2366
2377
|
crcKey0: new Crc32(keys[0]),
|
|
2367
2378
|
crcKey2: new Crc32(keys[2])
|
|
2368
2379
|
});
|
|
2369
|
-
|
|
2370
|
-
|
|
2380
|
+
if (rawPassword) {
|
|
2381
|
+
for (let index = 0; index < rawPassword.length; index++) {
|
|
2382
|
+
updateKeys(target, rawPassword[index]);
|
|
2383
|
+
}
|
|
2384
|
+
} else {
|
|
2385
|
+
for (let index = 0; index < password.length; index++) {
|
|
2386
|
+
updateKeys(target, password.charCodeAt(index));
|
|
2387
|
+
}
|
|
2371
2388
|
}
|
|
2372
2389
|
}
|
|
2373
2390
|
|
|
2374
2391
|
function updateKeys(target, byte) {
|
|
2375
|
-
let [
|
|
2392
|
+
let [, key1] = target.keys;
|
|
2376
2393
|
target.crcKey0.append([byte]);
|
|
2377
|
-
key0 = ~target.crcKey0.get();
|
|
2394
|
+
const key0 = ~target.crcKey0.get();
|
|
2378
2395
|
key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1);
|
|
2379
2396
|
target.crcKey2.append([key1 >>> 24]);
|
|
2380
|
-
key2 = ~target.crcKey2.get();
|
|
2397
|
+
const key2 = ~target.crcKey2.get();
|
|
2381
2398
|
target.keys = [key0, key1, key2];
|
|
2382
2399
|
}
|
|
2383
2400
|
|
|
@@ -2424,6 +2441,7 @@
|
|
|
2424
2441
|
|
|
2425
2442
|
|
|
2426
2443
|
const ERR_INVALID_UNCOMPRESSED_SIZE = "Invalid uncompressed size";
|
|
2444
|
+
const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
|
|
2427
2445
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
2428
2446
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
2429
2447
|
|
|
@@ -2480,6 +2498,7 @@
|
|
|
2480
2498
|
}
|
|
2481
2499
|
if (compressed) {
|
|
2482
2500
|
readable = pipeThroughCommpressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamZlib, DecompressionStream);
|
|
2501
|
+
readable = mapInflateStreamError(readable);
|
|
2483
2502
|
}
|
|
2484
2503
|
if ((!encrypted || zipCrypto) && signed) {
|
|
2485
2504
|
crc32Stream = new Crc32Stream();
|
|
@@ -2508,14 +2527,15 @@
|
|
|
2508
2527
|
function pipeThroughCommpressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamZlib, CompressionStream) {
|
|
2509
2528
|
const Stream = useCompressionStream && CompressionStreamNative ? CompressionStreamNative : CompressionStreamZlib || CompressionStream;
|
|
2510
2529
|
const format = options.deflate64 ? FORMAT_DEFLATE64_RAW : FORMAT_DEFLATE_RAW;
|
|
2530
|
+
let codecStream;
|
|
2511
2531
|
try {
|
|
2512
|
-
|
|
2532
|
+
codecStream = new Stream(format, options);
|
|
2513
2533
|
} catch (error) {
|
|
2514
2534
|
if (useCompressionStream) {
|
|
2515
2535
|
if (CompressionStreamZlib) {
|
|
2516
|
-
|
|
2536
|
+
codecStream = new CompressionStreamZlib(format, options);
|
|
2517
2537
|
} else if (CompressionStream) {
|
|
2518
|
-
|
|
2538
|
+
codecStream = new CompressionStream(format, options);
|
|
2519
2539
|
} else {
|
|
2520
2540
|
throw error;
|
|
2521
2541
|
}
|
|
@@ -2523,13 +2543,93 @@
|
|
|
2523
2543
|
throw error;
|
|
2524
2544
|
}
|
|
2525
2545
|
}
|
|
2526
|
-
|
|
2546
|
+
// The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
|
|
2547
|
+
// backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
|
|
2548
|
+
// the whole source into them and peak memory grows with the entry size. Awaiting each write()
|
|
2549
|
+
// paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
|
|
2550
|
+
// codec already backpressures, so this is a no-op cost for it.
|
|
2551
|
+
return pipeThroughBackpressured(readable, codecStream);
|
|
2527
2552
|
}
|
|
2528
2553
|
|
|
2529
2554
|
function pipeThrough(readable, transformStream) {
|
|
2530
2555
|
return readable.pipeThrough(transformStream);
|
|
2531
2556
|
}
|
|
2532
2557
|
|
|
2558
|
+
// Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
|
|
2559
|
+
// write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
|
|
2560
|
+
// Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
|
|
2561
|
+
// (surfaced through the codec's writable rejecting) cancels the source.
|
|
2562
|
+
function pipeThroughBackpressured(readable, transformStream) {
|
|
2563
|
+
const writer = transformStream.writable.getWriter();
|
|
2564
|
+
const reader = readable.getReader();
|
|
2565
|
+
pump();
|
|
2566
|
+
return transformStream.readable;
|
|
2567
|
+
|
|
2568
|
+
async function pump() {
|
|
2569
|
+
try {
|
|
2570
|
+
for (; ;) {
|
|
2571
|
+
await writer.ready;
|
|
2572
|
+
const result = await reader.read();
|
|
2573
|
+
if (result.done) {
|
|
2574
|
+
await writer.close();
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
await writer.write(result.value);
|
|
2578
|
+
}
|
|
2579
|
+
} catch (error) {
|
|
2580
|
+
await abort(writer, error);
|
|
2581
|
+
await cancel(reader, error);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
async function abort(writer, error) {
|
|
2587
|
+
try {
|
|
2588
|
+
await writer.abort(error);
|
|
2589
|
+
} catch {
|
|
2590
|
+
// the writable may already be errored/closed
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
async function cancel(reader, error) {
|
|
2595
|
+
try {
|
|
2596
|
+
await reader.cancel(error);
|
|
2597
|
+
} catch {
|
|
2598
|
+
// the readable may already be errored/closed
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// DecompressionStream implementations can fail with a message-less TypeError on malformed
|
|
2603
|
+
// input; give these errors an identifiable message (errors raised by the other stages of
|
|
2604
|
+
// the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
|
|
2605
|
+
function mapInflateStreamError(readable) {
|
|
2606
|
+
const reader = readable.getReader();
|
|
2607
|
+
return new ReadableStream({
|
|
2608
|
+
async pull(controller) {
|
|
2609
|
+
let result;
|
|
2610
|
+
try {
|
|
2611
|
+
result = await reader.read();
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
if (error && error.message) {
|
|
2614
|
+
throw error;
|
|
2615
|
+
}
|
|
2616
|
+
const mappedError = new Error(ERR_INVALID_COMPRESSED_DATA);
|
|
2617
|
+
mappedError.cause = error;
|
|
2618
|
+
throw mappedError;
|
|
2619
|
+
}
|
|
2620
|
+
const { value, done } = result;
|
|
2621
|
+
if (done) {
|
|
2622
|
+
controller.close();
|
|
2623
|
+
} else {
|
|
2624
|
+
controller.enqueue(value);
|
|
2625
|
+
}
|
|
2626
|
+
},
|
|
2627
|
+
cancel(reason) {
|
|
2628
|
+
return reader.cancel(reason);
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2533
2633
|
/*
|
|
2534
2634
|
Copyright (c) 2022 Gildas Lormeau. All rights reserved.
|
|
2535
2635
|
|
|
@@ -2559,6 +2659,7 @@
|
|
|
2559
2659
|
*/
|
|
2560
2660
|
|
|
2561
2661
|
|
|
2662
|
+
const DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
2562
2663
|
const MESSAGE_EVENT_TYPE = "message";
|
|
2563
2664
|
const MESSAGE_START = "start";
|
|
2564
2665
|
const MESSAGE_PULL = "pull";
|
|
@@ -2627,6 +2728,9 @@
|
|
|
2627
2728
|
|
|
2628
2729
|
constructor(chunkSize) {
|
|
2629
2730
|
let pendingChunk;
|
|
2731
|
+
if (!(chunkSize >= 1)) {
|
|
2732
|
+
chunkSize = DEFAULT_CHUNK_SIZE;
|
|
2733
|
+
}
|
|
2630
2734
|
super({
|
|
2631
2735
|
transform,
|
|
2632
2736
|
flush(controller) {
|
|
@@ -2644,12 +2748,12 @@
|
|
|
2644
2748
|
chunk = newChunk;
|
|
2645
2749
|
pendingChunk = null;
|
|
2646
2750
|
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
pendingChunk = chunk;
|
|
2751
|
+
let offset = 0;
|
|
2752
|
+
while (chunk.length - offset > chunkSize) {
|
|
2753
|
+
controller.enqueue(chunk.slice(offset, offset + chunkSize));
|
|
2754
|
+
offset += chunkSize;
|
|
2652
2755
|
}
|
|
2756
|
+
pendingChunk = offset ? chunk.slice(offset) : chunk;
|
|
2653
2757
|
}
|
|
2654
2758
|
}
|
|
2655
2759
|
}
|
|
@@ -2684,8 +2788,10 @@
|
|
|
2684
2788
|
|
|
2685
2789
|
|
|
2686
2790
|
const MODULE_WORKER_OPTIONS = { type: "module" };
|
|
2791
|
+
const ERROR_EVENT_TYPE = "error";
|
|
2792
|
+
const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
|
|
2687
2793
|
|
|
2688
|
-
let webWorkerSupported, webWorkerURI, webWorkerOptions;
|
|
2794
|
+
let webWorkerSupported, webWorkerSource, webWorkerURI, webWorkerOptions;
|
|
2689
2795
|
let transferStreamsSupported = true;
|
|
2690
2796
|
try {
|
|
2691
2797
|
transferStreamsSupported = typeof structuredClone == FUNCTION_TYPE && structuredClone(new DOMException("", "AbortError")).code !== UNDEFINED_VALUE;
|
|
@@ -2700,8 +2806,9 @@
|
|
|
2700
2806
|
const { signal } = streamOptions;
|
|
2701
2807
|
Object.assign(workerData, {
|
|
2702
2808
|
busy: true,
|
|
2809
|
+
generation: (workerData.generation || 0) + 1,
|
|
2703
2810
|
readable: readable
|
|
2704
|
-
.pipeThrough(new ChunkStream(config
|
|
2811
|
+
.pipeThrough(new ChunkStream(getChunkSize(config)))
|
|
2705
2812
|
.pipeThrough(new ProgressWatcherStream(streamOptions), { signal }),
|
|
2706
2813
|
writable,
|
|
2707
2814
|
options: Object.assign({}, options),
|
|
@@ -2724,15 +2831,17 @@
|
|
|
2724
2831
|
});
|
|
2725
2832
|
},
|
|
2726
2833
|
onTaskFinished() {
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2834
|
+
if (workerData.busy) {
|
|
2835
|
+
const { resolveTerminated } = workerData;
|
|
2836
|
+
if (resolveTerminated) {
|
|
2837
|
+
workerData.resolveTerminated = null;
|
|
2838
|
+
workerData.terminated = true;
|
|
2839
|
+
workerData.worker.terminate();
|
|
2840
|
+
resolveTerminated();
|
|
2841
|
+
}
|
|
2842
|
+
workerData.busy = false;
|
|
2843
|
+
onTaskFinished(workerData);
|
|
2733
2844
|
}
|
|
2734
|
-
workerData.busy = false;
|
|
2735
|
-
onTaskFinished(workerData);
|
|
2736
2845
|
}
|
|
2737
2846
|
});
|
|
2738
2847
|
if (webWorkerSupported === UNDEFINED_VALUE) {
|
|
@@ -2801,6 +2910,7 @@
|
|
|
2801
2910
|
}
|
|
2802
2911
|
Object.assign(workerData, {
|
|
2803
2912
|
worker,
|
|
2913
|
+
terminated: false,
|
|
2804
2914
|
interface: {
|
|
2805
2915
|
run: () => runWebWorker(workerData, { chunkSize, wasmURI, baseURI })
|
|
2806
2916
|
}
|
|
@@ -2816,7 +2926,15 @@
|
|
|
2816
2926
|
try {
|
|
2817
2927
|
await initModule(config);
|
|
2818
2928
|
} catch {
|
|
2819
|
-
|
|
2929
|
+
// The WASM module failed to load. Fall back to the native CompressionStream only if the
|
|
2930
|
+
// zlib codec that would otherwise run depends on that module; a self-contained codec
|
|
2931
|
+
// supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
|
|
2932
|
+
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
2933
|
+
config.CompressionStreamZlib :
|
|
2934
|
+
config.DecompressionStreamZlib;
|
|
2935
|
+
if (!ZlibStream || ZlibStream.requiresModule) {
|
|
2936
|
+
options.useCompressionStream = true;
|
|
2937
|
+
}
|
|
2820
2938
|
}
|
|
2821
2939
|
}
|
|
2822
2940
|
codecStream = new CodecStream(options, config);
|
|
@@ -2855,37 +2973,85 @@
|
|
|
2855
2973
|
result
|
|
2856
2974
|
});
|
|
2857
2975
|
const { readable, options } = workerData;
|
|
2858
|
-
const { writable, closed } = watchClosedStream(workerData.writable);
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2976
|
+
const { writable, closed, abortPipe } = watchClosedStream(workerData.writable);
|
|
2977
|
+
let streamsTransferred;
|
|
2978
|
+
try {
|
|
2979
|
+
streamsTransferred = sendMessage({
|
|
2980
|
+
type: MESSAGE_START,
|
|
2981
|
+
options,
|
|
2982
|
+
config,
|
|
2983
|
+
readable,
|
|
2984
|
+
writable
|
|
2985
|
+
}, workerData);
|
|
2986
|
+
} catch (error) {
|
|
2987
|
+
abortPipe();
|
|
2988
|
+
try {
|
|
2989
|
+
await closed;
|
|
2990
|
+
} catch {
|
|
2991
|
+
// ignored
|
|
2992
|
+
}
|
|
2993
|
+
workerData.onTaskFinished();
|
|
2994
|
+
throw error;
|
|
2995
|
+
}
|
|
2866
2996
|
if (!streamsTransferred) {
|
|
2867
2997
|
Object.assign(workerData, {
|
|
2868
2998
|
reader: readable.getReader(),
|
|
2869
2999
|
writer: writable.getWriter()
|
|
2870
3000
|
});
|
|
2871
3001
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
await
|
|
3002
|
+
try {
|
|
3003
|
+
const resultValue = await result;
|
|
3004
|
+
await closeWritable();
|
|
3005
|
+
await closed;
|
|
3006
|
+
return resultValue;
|
|
3007
|
+
} catch (error) {
|
|
3008
|
+
await closeWritable();
|
|
3009
|
+
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
3010
|
+
abortPipe();
|
|
3011
|
+
try {
|
|
3012
|
+
await closed;
|
|
3013
|
+
} catch {
|
|
3014
|
+
// ignored
|
|
3015
|
+
}
|
|
3016
|
+
throw error;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
async function closeWritable() {
|
|
3020
|
+
if (!streamsTransferred && !writable.locked) {
|
|
3021
|
+
try {
|
|
3022
|
+
await writable.getWriter().close();
|
|
3023
|
+
} catch {
|
|
3024
|
+
// ignored
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
2875
3027
|
}
|
|
2876
|
-
await closed;
|
|
2877
|
-
return resultValue;
|
|
2878
3028
|
}
|
|
2879
3029
|
|
|
2880
3030
|
function watchClosedStream(writableSource) {
|
|
3031
|
+
const abortController = new AbortController();
|
|
2881
3032
|
const { writable, readable } = new TransformStream();
|
|
2882
|
-
const closed = readable.pipeTo(writableSource, { preventClose: true });
|
|
2883
|
-
|
|
3033
|
+
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
3034
|
+
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
3035
|
+
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
3036
|
+
closed.catch(() => { });
|
|
3037
|
+
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
function terminateWorker$1(workerData) {
|
|
3041
|
+
const { worker } = workerData;
|
|
3042
|
+
if (worker) {
|
|
3043
|
+
try {
|
|
3044
|
+
worker.terminate();
|
|
3045
|
+
} catch {
|
|
3046
|
+
// ignored
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
workerData.interface = null;
|
|
2884
3050
|
}
|
|
2885
3051
|
|
|
2886
3052
|
function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true) {
|
|
2887
3053
|
let worker, resolvedURI, resolvedOptions;
|
|
2888
|
-
if (webWorkerURI === UNDEFINED_VALUE) {
|
|
3054
|
+
if (webWorkerURI === UNDEFINED_VALUE || webWorkerSource !== url) {
|
|
2889
3055
|
// deno-lint-ignore valid-typeof
|
|
2890
3056
|
const isFunctionURI = typeof url == FUNCTION_TYPE;
|
|
2891
3057
|
if (isFunctionURI) {
|
|
@@ -2942,16 +3108,36 @@
|
|
|
2942
3108
|
}
|
|
2943
3109
|
}
|
|
2944
3110
|
}
|
|
3111
|
+
webWorkerSource = url;
|
|
2945
3112
|
webWorkerURI = resolvedURI;
|
|
2946
3113
|
webWorkerOptions = resolvedOptions;
|
|
2947
3114
|
} else {
|
|
2948
3115
|
worker = new Worker(webWorkerURI, webWorkerOptions);
|
|
2949
3116
|
}
|
|
2950
3117
|
worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData));
|
|
3118
|
+
worker.addEventListener(ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
3119
|
+
worker.addEventListener(MESSAGE_ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
2951
3120
|
return worker;
|
|
2952
3121
|
}
|
|
2953
3122
|
|
|
2954
|
-
function
|
|
3123
|
+
function onWorkerError(event, workerData) {
|
|
3124
|
+
if (event.preventDefault) {
|
|
3125
|
+
event.preventDefault();
|
|
3126
|
+
}
|
|
3127
|
+
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
3128
|
+
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
3129
|
+
// fail the current task instead of letting it hang forever
|
|
3130
|
+
terminateWorker$1(workerData);
|
|
3131
|
+
if (rejectResult) {
|
|
3132
|
+
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
3133
|
+
if (writer) {
|
|
3134
|
+
writer.releaseLock();
|
|
3135
|
+
}
|
|
3136
|
+
onTaskFinished();
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
function sendMessage(message, { worker, writer, transferStreams }) {
|
|
2955
3141
|
try {
|
|
2956
3142
|
const { value, readable, writable } = message;
|
|
2957
3143
|
const transferables = [];
|
|
@@ -2985,14 +3171,16 @@
|
|
|
2985
3171
|
if (writer) {
|
|
2986
3172
|
writer.releaseLock();
|
|
2987
3173
|
}
|
|
2988
|
-
onTaskFinished();
|
|
2989
3174
|
throw error;
|
|
2990
3175
|
}
|
|
2991
3176
|
}
|
|
2992
3177
|
|
|
2993
3178
|
async function onMessage({ data }, workerData) {
|
|
2994
3179
|
const { type, value, messageId, result, error } = data;
|
|
2995
|
-
const { reader, writer, resolveResult, rejectResult, onTaskFinished } = workerData;
|
|
3180
|
+
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
3181
|
+
// continuations below can settle after the worker slot has been reassigned to a new
|
|
3182
|
+
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
3183
|
+
const stale = () => workerData.generation != generation;
|
|
2996
3184
|
try {
|
|
2997
3185
|
if (error) {
|
|
2998
3186
|
const { message, stack, code, name, outputSize } = error;
|
|
@@ -3002,23 +3190,32 @@
|
|
|
3002
3190
|
} else {
|
|
3003
3191
|
if (type == MESSAGE_PULL) {
|
|
3004
3192
|
const { value, done } = await reader.read();
|
|
3005
|
-
|
|
3193
|
+
if (!stale()) {
|
|
3194
|
+
sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData);
|
|
3195
|
+
}
|
|
3006
3196
|
}
|
|
3007
3197
|
if (type == MESSAGE_DATA) {
|
|
3008
3198
|
await writer.ready;
|
|
3009
3199
|
await writer.write(new Uint8Array(value));
|
|
3010
|
-
|
|
3200
|
+
if (!stale()) {
|
|
3201
|
+
sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData);
|
|
3202
|
+
}
|
|
3011
3203
|
}
|
|
3012
3204
|
if (type == MESSAGE_CLOSE) {
|
|
3013
3205
|
close(null, result);
|
|
3014
3206
|
}
|
|
3015
3207
|
}
|
|
3016
3208
|
} catch (error) {
|
|
3017
|
-
|
|
3018
|
-
|
|
3209
|
+
if (!stale()) {
|
|
3210
|
+
terminateWorker$1(workerData);
|
|
3211
|
+
close(error);
|
|
3212
|
+
}
|
|
3019
3213
|
}
|
|
3020
3214
|
|
|
3021
3215
|
function close(error, result) {
|
|
3216
|
+
if (stale()) {
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3022
3219
|
if (error) {
|
|
3023
3220
|
rejectResult(error);
|
|
3024
3221
|
} else {
|
|
@@ -3062,6 +3259,8 @@
|
|
|
3062
3259
|
|
|
3063
3260
|
let pool = [];
|
|
3064
3261
|
const pendingRequests = [];
|
|
3262
|
+
let starvationTimeout;
|
|
3263
|
+
let starvationDelay;
|
|
3065
3264
|
|
|
3066
3265
|
let indexWorker = 0;
|
|
3067
3266
|
|
|
@@ -3088,14 +3287,20 @@
|
|
|
3088
3287
|
pool.push(workerData);
|
|
3089
3288
|
return new CodecWorker(workerData, stream, workerOptions, onTaskFinished);
|
|
3090
3289
|
} else {
|
|
3091
|
-
return new Promise(resolve =>
|
|
3290
|
+
return new Promise(resolve => {
|
|
3291
|
+
pendingRequests.push({ resolve, stream, workerOptions });
|
|
3292
|
+
starvationDelay = config.workerStarvationTimeout;
|
|
3293
|
+
armStarvationTimeout();
|
|
3294
|
+
});
|
|
3092
3295
|
}
|
|
3093
3296
|
}
|
|
3094
3297
|
|
|
3095
3298
|
function onTaskFinished(workerData) {
|
|
3299
|
+
clearStarvationTimeout();
|
|
3096
3300
|
if (pendingRequests.length) {
|
|
3097
3301
|
const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
|
|
3098
3302
|
resolve(new CodecWorker(workerData, stream, workerOptions, onTaskFinished));
|
|
3303
|
+
armStarvationTimeout();
|
|
3099
3304
|
} else if (workerData.worker) {
|
|
3100
3305
|
clearTerminateTimeout(workerData);
|
|
3101
3306
|
terminateWorker(workerData, workerOptions);
|
|
@@ -3105,6 +3310,34 @@
|
|
|
3105
3310
|
}
|
|
3106
3311
|
}
|
|
3107
3312
|
|
|
3313
|
+
function armStarvationTimeout() {
|
|
3314
|
+
if (!starvationTimeout && pendingRequests.length && Number.isFinite(starvationDelay) && starvationDelay >= 0) {
|
|
3315
|
+
starvationTimeout = setTimeout(onWorkerStarvation, starvationDelay);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
function clearStarvationTimeout() {
|
|
3320
|
+
if (starvationTimeout) {
|
|
3321
|
+
clearTimeout(starvationTimeout);
|
|
3322
|
+
starvationTimeout = null;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
function onWorkerStarvation() {
|
|
3327
|
+
starvationTimeout = null;
|
|
3328
|
+
if (pendingRequests.length) {
|
|
3329
|
+
const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
|
|
3330
|
+
const inlineWorkerOptions = Object.assign({}, workerOptions, { useWebWorkers: false, workerURI: UNDEFINED_VALUE });
|
|
3331
|
+
resolve(new CodecWorker({}, stream, inlineWorkerOptions, onInlineTaskFinished));
|
|
3332
|
+
armStarvationTimeout();
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
function onInlineTaskFinished() {
|
|
3337
|
+
clearStarvationTimeout();
|
|
3338
|
+
armStarvationTimeout();
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3108
3341
|
function terminateWorker(workerData, workerOptions) {
|
|
3109
3342
|
const { config } = workerOptions;
|
|
3110
3343
|
const { terminateWorkerTimeout } = config;
|
|
@@ -3162,7 +3395,7 @@
|
|
|
3162
3395
|
|
|
3163
3396
|
/* global TextDecoder */
|
|
3164
3397
|
|
|
3165
|
-
const CP437 = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
|
|
3398
|
+
const CP437 = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");
|
|
3166
3399
|
const VALID_CP437 = CP437.length == 256;
|
|
3167
3400
|
|
|
3168
3401
|
function decodeCP437(stringValue) {
|
|
@@ -3210,7 +3443,8 @@
|
|
|
3210
3443
|
if (encoding && encoding.trim().toLowerCase() == "cp437") {
|
|
3211
3444
|
return decodeCP437(value);
|
|
3212
3445
|
} else {
|
|
3213
|
-
|
|
3446
|
+
// ignoreBOM keeps a leading U+FEFF in filenames/comments instead of dropping it
|
|
3447
|
+
return new TextDecoder(encoding, { ignoreBOM: true }).decode(value);
|
|
3214
3448
|
}
|
|
3215
3449
|
}
|
|
3216
3450
|
|
|
@@ -3387,6 +3621,7 @@
|
|
|
3387
3621
|
const OPTION_CHECK_PASSWORD_ONLY = "checkPasswordOnly";
|
|
3388
3622
|
const OPTION_CHECK_OVERLAPPING_ENTRY_ONLY = "checkOverlappingEntryOnly";
|
|
3389
3623
|
const OPTION_CHECK_OVERLAPPING_ENTRY = "checkOverlappingEntry";
|
|
3624
|
+
const OPTION_CHECK_AMBIGUITY = "checkAmbiguity";
|
|
3390
3625
|
const OPTION_CHECK_SIGNATURE = "checkSignature";
|
|
3391
3626
|
const OPTION_USE_WEB_WORKERS = "useWebWorkers";
|
|
3392
3627
|
const OPTION_USE_COMPRESSION_STREAM = "useCompressionStream";
|
|
@@ -3444,12 +3679,16 @@
|
|
|
3444
3679
|
const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
|
|
3445
3680
|
const ERR_ENCRYPTED = "File contains encrypted entry";
|
|
3446
3681
|
const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
|
|
3447
|
-
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
3682
|
+
const ERR_UNSUPPORTED_COMPRESSION$1 = "Compression method not supported";
|
|
3448
3683
|
const ERR_SPLIT_ZIP_FILE = "Split zip file";
|
|
3449
3684
|
const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
|
|
3685
|
+
const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
|
|
3450
3686
|
const CHARSET_UTF8 = "utf-8";
|
|
3451
3687
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
3452
3688
|
const CHARSET_CP437 = "cp437";
|
|
3689
|
+
// bits of the general purpose flag that change how an entry is interpreted; the level bits (1-2) are excluded
|
|
3690
|
+
// because they are informative only
|
|
3691
|
+
const BITFLAG_AMBIGUITY_MASK = BITFLAG_ENCRYPTED | BITFLAG_DATA_DESCRIPTOR | BITFLAG_LANG_ENCODING_FLAG;
|
|
3453
3692
|
const ZIP64_PROPERTIES = [
|
|
3454
3693
|
[PROPERTY_NAME_UNCOMPRESSED_SIZE, MAX_32_BITS],
|
|
3455
3694
|
[PROPERTY_NAME_COMPRESSED_SIZE, MAX_32_BITS],
|
|
@@ -3474,7 +3713,7 @@
|
|
|
3474
3713
|
reader: new GenericReader(reader),
|
|
3475
3714
|
options,
|
|
3476
3715
|
config: getConfiguration(),
|
|
3477
|
-
readRanges:
|
|
3716
|
+
readRanges: new Map()
|
|
3478
3717
|
});
|
|
3479
3718
|
}
|
|
3480
3719
|
|
|
@@ -3507,21 +3746,30 @@
|
|
|
3507
3746
|
const commentOffset = endOfDirectoryInfo.offset;
|
|
3508
3747
|
const commentLength = getUint16(endOfDirectoryView, 20);
|
|
3509
3748
|
const appendedDataOffset = commentOffset + END_OF_CENTRAL_DIR_LENGTH + commentLength;
|
|
3749
|
+
const checkAmbiguity = getOptionValue$1(zipReader, options, OPTION_CHECK_AMBIGUITY);
|
|
3750
|
+
if (checkAmbiguity && appendedDataOffset != reader.size) {
|
|
3751
|
+
throwAmbiguousArchive("appended data");
|
|
3752
|
+
}
|
|
3510
3753
|
let lastDiskNumber = getUint16(endOfDirectoryView, 4);
|
|
3511
3754
|
const expectedLastDiskNumber = reader.lastDiskNumber || 0;
|
|
3512
3755
|
let diskNumber = getUint16(endOfDirectoryView, 6);
|
|
3513
|
-
let filesLength = getUint16(endOfDirectoryView,
|
|
3756
|
+
let filesLength = getUint16(endOfDirectoryView, 10);
|
|
3514
3757
|
let prependedDataLength = 0;
|
|
3515
|
-
let startOffset
|
|
3758
|
+
let startOffset;
|
|
3759
|
+
let zip64EndOfDirectory;
|
|
3516
3760
|
if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS || diskNumber == MAX_16_BITS) {
|
|
3517
|
-
const endOfDirectoryLocatorArray =
|
|
3761
|
+
const endOfDirectoryLocatorArray = endOfDirectoryInfo.offset >= ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH ?
|
|
3762
|
+
await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH) :
|
|
3763
|
+
new Uint8Array();
|
|
3518
3764
|
const endOfDirectoryLocatorView = getDataView$1(endOfDirectoryLocatorArray);
|
|
3519
|
-
if (
|
|
3765
|
+
if (endOfDirectoryLocatorArray.length == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH &&
|
|
3766
|
+
getUint32(endOfDirectoryLocatorView, 0) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
3520
3767
|
directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
|
|
3521
3768
|
let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
|
|
3522
3769
|
let endOfDirectoryView = getDataView$1(endOfDirectoryArray);
|
|
3523
|
-
const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
|
|
3524
|
-
if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE &&
|
|
3770
|
+
const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH - (reader.lastDiskOffset || 0);
|
|
3771
|
+
if ((endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) &&
|
|
3772
|
+
directoryDataOffset != expectedDirectoryDataOffset && expectedDirectoryDataOffset >= 0) {
|
|
3525
3773
|
const originalDirectoryDataOffset = directoryDataOffset;
|
|
3526
3774
|
directoryDataOffset = expectedDirectoryDataOffset;
|
|
3527
3775
|
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
@@ -3530,24 +3778,36 @@
|
|
|
3530
3778
|
endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH, -1);
|
|
3531
3779
|
endOfDirectoryView = getDataView$1(endOfDirectoryArray);
|
|
3532
3780
|
}
|
|
3533
|
-
if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
3781
|
+
if (endOfDirectoryArray.length < ZIP64_END_OF_CENTRAL_DIR_LENGTH || getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
3534
3782
|
throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
|
|
3535
3783
|
}
|
|
3784
|
+
zip64EndOfDirectory = true;
|
|
3536
3785
|
if (lastDiskNumber == MAX_16_BITS) {
|
|
3537
3786
|
lastDiskNumber = getUint32(endOfDirectoryView, 16);
|
|
3787
|
+
} else if (checkAmbiguity && lastDiskNumber != getUint32(endOfDirectoryView, 16)) {
|
|
3788
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
3538
3789
|
}
|
|
3539
3790
|
if (diskNumber == MAX_16_BITS) {
|
|
3540
3791
|
diskNumber = getUint32(endOfDirectoryView, 20);
|
|
3792
|
+
} else if (checkAmbiguity && diskNumber != getUint32(endOfDirectoryView, 20)) {
|
|
3793
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
3541
3794
|
}
|
|
3542
3795
|
if (filesLength == MAX_16_BITS) {
|
|
3543
3796
|
filesLength = getBigUint64(endOfDirectoryView, 32);
|
|
3797
|
+
} else if (checkAmbiguity && filesLength != getBigUint64(endOfDirectoryView, 32)) {
|
|
3798
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
3544
3799
|
}
|
|
3545
3800
|
if (directoryDataLength == MAX_32_BITS) {
|
|
3546
3801
|
directoryDataLength = getBigUint64(endOfDirectoryView, 40);
|
|
3802
|
+
} else if (checkAmbiguity && directoryDataLength != getBigUint64(endOfDirectoryView, 40)) {
|
|
3803
|
+
throwAmbiguousArchive("mismatched zip64 end of central directory record");
|
|
3547
3804
|
}
|
|
3548
|
-
directoryDataOffset
|
|
3805
|
+
directoryDataOffset = getBigUint64(endOfDirectoryView, 48) + prependedDataLength;
|
|
3549
3806
|
}
|
|
3550
3807
|
}
|
|
3808
|
+
const declaredDirectoryDataLength = directoryDataLength;
|
|
3809
|
+
const centralDirectoryEndOffset = endOfDirectoryInfo.offset -
|
|
3810
|
+
(zip64EndOfDirectory ? ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH : 0);
|
|
3551
3811
|
if (directoryDataOffset >= reader.size) {
|
|
3552
3812
|
prependedDataLength = reader.size - directoryDataOffset - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
|
|
3553
3813
|
directoryDataOffset = reader.size - directoryDataLength - END_OF_CENTRAL_DIR_LENGTH;
|
|
@@ -3562,8 +3822,11 @@
|
|
|
3562
3822
|
let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3563
3823
|
let directoryView = getDataView$1(directoryArray);
|
|
3564
3824
|
if (directoryDataLength) {
|
|
3565
|
-
|
|
3566
|
-
|
|
3825
|
+
if (directoryArray.length < 4) {
|
|
3826
|
+
throw new Error(ERR_BAD_FORMAT);
|
|
3827
|
+
}
|
|
3828
|
+
const expectedDirectoryDataOffset = centralDirectoryEndOffset - directoryDataLength - (reader.lastDiskOffset || 0);
|
|
3829
|
+
if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset && diskNumber == lastDiskNumber) {
|
|
3567
3830
|
const originalDirectoryDataOffset = directoryDataOffset;
|
|
3568
3831
|
directoryDataOffset = expectedDirectoryDataOffset;
|
|
3569
3832
|
if (directoryDataOffset > originalDirectoryDataOffset) {
|
|
@@ -3573,8 +3836,8 @@
|
|
|
3573
3836
|
directoryView = getDataView$1(directoryArray);
|
|
3574
3837
|
}
|
|
3575
3838
|
}
|
|
3576
|
-
const expectedDirectoryDataLength =
|
|
3577
|
-
if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0) {
|
|
3839
|
+
const expectedDirectoryDataLength = centralDirectoryEndOffset - directoryDataOffset - (reader.lastDiskOffset || 0);
|
|
3840
|
+
if (directoryDataLength != expectedDirectoryDataLength && expectedDirectoryDataLength >= 0 && diskNumber == lastDiskNumber) {
|
|
3578
3841
|
directoryDataLength = expectedDirectoryDataLength;
|
|
3579
3842
|
directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength, diskNumber);
|
|
3580
3843
|
directoryView = getDataView$1(directoryArray);
|
|
@@ -3582,16 +3845,19 @@
|
|
|
3582
3845
|
if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
|
|
3583
3846
|
throw new Error(ERR_BAD_FORMAT);
|
|
3584
3847
|
}
|
|
3848
|
+
startOffset = directoryDataOffset;
|
|
3585
3849
|
const filenameEncoding = getOptionValue$1(zipReader, options, OPTION_FILENAME_ENCODING);
|
|
3586
3850
|
const commentEncoding = getOptionValue$1(zipReader, options, OPTION_COMMENT_ENCODING);
|
|
3851
|
+
const filenames = checkAmbiguity ? new Set() : UNDEFINED_VALUE;
|
|
3852
|
+
let duplicateFilename;
|
|
3587
3853
|
for (let indexFile = 0; indexFile < filesLength; indexFile++) {
|
|
3588
3854
|
const fileEntry = new ZipEntry$1(reader, config, zipReader.options);
|
|
3589
|
-
if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
3855
|
+
if (offset + CENTRAL_FILE_HEADER_LENGTH > directoryArray.length || getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
3590
3856
|
throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
|
|
3591
3857
|
}
|
|
3592
3858
|
readCommonHeader(fileEntry, directoryView, offset + 6);
|
|
3593
3859
|
const languageEncodingFlag = Boolean(fileEntry.bitFlag.languageEncodingFlag);
|
|
3594
|
-
const filenameOffset = offset +
|
|
3860
|
+
const filenameOffset = offset + CENTRAL_FILE_HEADER_LENGTH;
|
|
3595
3861
|
const extraFieldOffset = filenameOffset + fileEntry.filenameLength;
|
|
3596
3862
|
const commentOffset = extraFieldOffset + fileEntry.extraFieldLength;
|
|
3597
3863
|
const versionMadeBy = getUint16(directoryView, offset + 4);
|
|
@@ -3612,7 +3878,7 @@
|
|
|
3612
3878
|
directory: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_DIR_MASK),
|
|
3613
3879
|
archive: Boolean(msdosAttributesRaw & FILE_ATTR_MSDOS_ARCHIVE_MASK)
|
|
3614
3880
|
};
|
|
3615
|
-
const offsetFileEntry = getUint32(directoryView, offset + 42)
|
|
3881
|
+
const offsetFileEntry = getUint32(directoryView, offset + 42);
|
|
3616
3882
|
const decode = getOptionValue$1(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
|
|
3617
3883
|
const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
|
|
3618
3884
|
const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
|
|
@@ -3625,6 +3891,7 @@
|
|
|
3625
3891
|
comment = decodeText(rawComment, rawCommentEncoding);
|
|
3626
3892
|
}
|
|
3627
3893
|
Object.assign(fileEntry, {
|
|
3894
|
+
index: indexFile,
|
|
3628
3895
|
versionMadeBy,
|
|
3629
3896
|
msDosCompatible,
|
|
3630
3897
|
compressedSize: 0,
|
|
@@ -3644,8 +3911,15 @@
|
|
|
3644
3911
|
filename,
|
|
3645
3912
|
comment
|
|
3646
3913
|
});
|
|
3647
|
-
startOffset = Math.max(offsetFileEntry, startOffset);
|
|
3648
3914
|
readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
|
|
3915
|
+
fileEntry.offset += prependedDataLength;
|
|
3916
|
+
startOffset = Math.min(fileEntry.offset, startOffset);
|
|
3917
|
+
if (checkAmbiguity) {
|
|
3918
|
+
if (filenames.has(fileEntry.filename)) {
|
|
3919
|
+
duplicateFilename = true;
|
|
3920
|
+
}
|
|
3921
|
+
filenames.add(fileEntry.filename);
|
|
3922
|
+
}
|
|
3649
3923
|
const unixExternalUpper = (fileEntry.externalFileAttributes >> 16) & MAX_16_BITS;
|
|
3650
3924
|
if (fileEntry.unixMode === UNDEFINED_VALUE && (unixExternalUpper & (FILE_ATTR_UNIX_DEFAULT_MASK | FILE_ATTR_UNIX_EXECUTABLE_MASK | FILE_ATTR_UNIX_TYPE_DIR)) != 0) {
|
|
3651
3925
|
fileEntry.unixMode = unixExternalUpper;
|
|
@@ -3666,7 +3940,7 @@
|
|
|
3666
3940
|
internalFileAttribute: fileEntry.internalFileAttributes,
|
|
3667
3941
|
externalFileAttribute: fileEntry.externalFileAttributes,
|
|
3668
3942
|
executable,
|
|
3669
|
-
directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
|
|
3943
|
+
directory: modeIsDir || upperIsDir || (msDosCompatible && msdosAttributes.directory) || (fileEntry.filename.endsWith(DIRECTORY_SIGNATURE) && !fileEntry.uncompressedSize),
|
|
3670
3944
|
zipCrypto: fileEntry.encrypted && !fileEntry.extraFieldAES
|
|
3671
3945
|
});
|
|
3672
3946
|
const entry = new Entry(fileEntry);
|
|
@@ -3689,6 +3963,15 @@
|
|
|
3689
3963
|
}
|
|
3690
3964
|
yield entry;
|
|
3691
3965
|
}
|
|
3966
|
+
if (checkAmbiguity && offset != declaredDirectoryDataLength) {
|
|
3967
|
+
throwAmbiguousArchive("trailing central directory data");
|
|
3968
|
+
}
|
|
3969
|
+
if (duplicateFilename) {
|
|
3970
|
+
throwAmbiguousArchive("duplicate filename");
|
|
3971
|
+
}
|
|
3972
|
+
if (checkAmbiguity && (prependedDataLength || (filesLength && startOffset > 0))) {
|
|
3973
|
+
throwAmbiguousArchive("prepended data");
|
|
3974
|
+
}
|
|
3692
3975
|
const extractPrependedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_PREPENDED_DATA);
|
|
3693
3976
|
const extractAppendedData = getOptionValue$1(zipReader, options, OPTION_EXTRACT_APPENDED_DATA);
|
|
3694
3977
|
if (extractPrependedData) {
|
|
@@ -3727,6 +4010,7 @@
|
|
|
3727
4010
|
const zipEntry = this;
|
|
3728
4011
|
const {
|
|
3729
4012
|
reader,
|
|
4013
|
+
index,
|
|
3730
4014
|
offset,
|
|
3731
4015
|
diskNumberStart,
|
|
3732
4016
|
extraFieldAES,
|
|
@@ -3752,27 +4036,42 @@
|
|
|
3752
4036
|
rawPassword = rawPassword && rawPassword.length && rawPassword;
|
|
3753
4037
|
if (extraFieldAES) {
|
|
3754
4038
|
if (extraFieldAES.originalCompressionMethod != COMPRESSION_METHOD_AES) {
|
|
3755
|
-
throw new Error(ERR_UNSUPPORTED_COMPRESSION);
|
|
4039
|
+
throw new Error(ERR_UNSUPPORTED_COMPRESSION$1);
|
|
3756
4040
|
}
|
|
3757
4041
|
}
|
|
3758
4042
|
if ((compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE && compressionMethod != COMPRESSION_METHOD_DEFLATE_64) && !passThrough) {
|
|
3759
|
-
throw new Error(ERR_UNSUPPORTED_COMPRESSION);
|
|
4043
|
+
throw new Error(ERR_UNSUPPORTED_COMPRESSION$1);
|
|
3760
4044
|
}
|
|
3761
|
-
if (getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
|
|
4045
|
+
if (dataArray.length < HEADER_SIZE || getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
|
|
3762
4046
|
throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
|
|
3763
4047
|
}
|
|
3764
4048
|
readCommonHeader(localDirectory, dataView, 4);
|
|
3765
4049
|
const {
|
|
3766
4050
|
extraFieldLength,
|
|
3767
|
-
filenameLength
|
|
3768
|
-
lastAccessDate,
|
|
3769
|
-
creationDate
|
|
4051
|
+
filenameLength
|
|
3770
4052
|
} = localDirectory;
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4053
|
+
const checkAmbiguity = getOptionValue$1(zipEntry, options, OPTION_CHECK_AMBIGUITY);
|
|
4054
|
+
let rawLocalFilename = new Uint8Array();
|
|
4055
|
+
if (checkAmbiguity && (filenameLength || extraFieldLength)) {
|
|
4056
|
+
const trailingDataArray = await readUint8Array(reader, offset + HEADER_SIZE, filenameLength + extraFieldLength, diskNumberStart);
|
|
4057
|
+
rawLocalFilename = trailingDataArray.subarray(0, filenameLength);
|
|
4058
|
+
localDirectory.rawExtraField = trailingDataArray.subarray(filenameLength);
|
|
4059
|
+
} else {
|
|
4060
|
+
localDirectory.rawExtraField = extraFieldLength ?
|
|
4061
|
+
await readUint8Array(reader, offset + HEADER_SIZE + filenameLength, extraFieldLength, diskNumberStart) :
|
|
4062
|
+
new Uint8Array();
|
|
4063
|
+
}
|
|
3774
4064
|
readCommonFooter(zipEntry, localDirectory, dataView, 4, true);
|
|
3775
|
-
|
|
4065
|
+
if (checkAmbiguity) {
|
|
4066
|
+
checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename);
|
|
4067
|
+
}
|
|
4068
|
+
const { lastAccessDate, creationDate } = localDirectory;
|
|
4069
|
+
if (lastAccessDate) {
|
|
4070
|
+
fileEntry.lastAccessDate = lastAccessDate;
|
|
4071
|
+
}
|
|
4072
|
+
if (creationDate) {
|
|
4073
|
+
fileEntry.creationDate = creationDate;
|
|
4074
|
+
}
|
|
3776
4075
|
const encrypted = zipEntry.encrypted && localDirectory.encrypted && !passThrough;
|
|
3777
4076
|
const zipCrypto = encrypted && !extraFieldAES;
|
|
3778
4077
|
if (!passThrough) {
|
|
@@ -3818,7 +4117,7 @@
|
|
|
3818
4117
|
outputSize: passThrough ? compressedSize : uncompressedSize,
|
|
3819
4118
|
signature,
|
|
3820
4119
|
compressed: compressionMethod != 0 && !passThrough,
|
|
3821
|
-
encrypted
|
|
4120
|
+
encrypted,
|
|
3822
4121
|
useWebWorkers: getOptionValue$1(zipEntry, options, OPTION_USE_WEB_WORKERS),
|
|
3823
4122
|
useCompressionStream,
|
|
3824
4123
|
transferStreams: getOptionValue$1(zipEntry, options, OPTION_TRANSFER_STREAMS),
|
|
@@ -3832,6 +4131,7 @@
|
|
|
3832
4131
|
await detectOverlappingEntry({
|
|
3833
4132
|
reader,
|
|
3834
4133
|
fileEntry,
|
|
4134
|
+
index,
|
|
3835
4135
|
offset,
|
|
3836
4136
|
diskNumberStart,
|
|
3837
4137
|
signature,
|
|
@@ -3934,7 +4234,7 @@
|
|
|
3934
4234
|
directory.extraFieldUnicodeComment = extraFieldUnicodeComment;
|
|
3935
4235
|
}
|
|
3936
4236
|
const extraFieldAES = extraField.get(EXTRAFIELD_TYPE_AES);
|
|
3937
|
-
if (extraFieldAES) {
|
|
4237
|
+
if (extraFieldAES && extraFieldAES.data.length >= 7) {
|
|
3938
4238
|
readExtraFieldAES(extraFieldAES, directory, compressionMethod);
|
|
3939
4239
|
directory.extraFieldAES = extraFieldAES;
|
|
3940
4240
|
} else {
|
|
@@ -3971,19 +4271,23 @@
|
|
|
3971
4271
|
directory.zip64 = true;
|
|
3972
4272
|
const extraFieldView = getDataView$1(extraFieldZip64.data);
|
|
3973
4273
|
const missingProperties = ZIP64_PROPERTIES.filter(([propertyName, max]) => directory[propertyName] == max);
|
|
4274
|
+
const requiredLength = missingProperties.reduce((length, [, max]) => length + ZIP64_EXTRACTION[max].bytes, 0);
|
|
4275
|
+
if (extraFieldZip64.data.length < requiredLength) {
|
|
4276
|
+
throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
|
|
4277
|
+
}
|
|
3974
4278
|
for (let indexMissingProperty = 0, offset = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
|
|
3975
4279
|
const [propertyName, max] = missingProperties[indexMissingProperty];
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
offset += extraction.bytes;
|
|
3980
|
-
} else if (extraFieldZip64[propertyName]) {
|
|
3981
|
-
throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
|
|
3982
|
-
}
|
|
4280
|
+
const extraction = ZIP64_EXTRACTION[max];
|
|
4281
|
+
directory[propertyName] = extraFieldZip64[propertyName] = extraction.getValue(extraFieldView, offset);
|
|
4282
|
+
offset += extraction.bytes;
|
|
3983
4283
|
}
|
|
3984
4284
|
}
|
|
3985
4285
|
|
|
3986
4286
|
function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
|
|
4287
|
+
if (extraFieldUnicode.data.length < 5) {
|
|
4288
|
+
extraFieldUnicode.valid = false;
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
3987
4291
|
const extraFieldView = getDataView$1(extraFieldUnicode.data);
|
|
3988
4292
|
const crc32 = new Crc32();
|
|
3989
4293
|
crc32.append(fileEntry[rawPropertyName]);
|
|
@@ -4056,32 +4360,30 @@
|
|
|
4056
4360
|
function readExtraFieldUnix(extraField, directory, isInfoZip) {
|
|
4057
4361
|
try {
|
|
4058
4362
|
const view = getDataView$1(new Uint8Array(extraField.data));
|
|
4059
|
-
let
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
if (
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4363
|
+
let uid, gid;
|
|
4364
|
+
if (isInfoZip) {
|
|
4365
|
+
// Info-ZIP New Unix "ux" field (0x7875): version + variable-length uid/gid
|
|
4366
|
+
let offset = 0;
|
|
4367
|
+
const version = getUint8(view, offset++);
|
|
4368
|
+
const uidSize = getUint8(view, offset++);
|
|
4369
|
+
uid = unpackUnixId(extraField.data.subarray(offset, offset + uidSize));
|
|
4370
|
+
offset += uidSize;
|
|
4371
|
+
const gidSize = getUint8(view, offset++);
|
|
4372
|
+
gid = unpackUnixId(extraField.data.subarray(offset, offset + gidSize));
|
|
4373
|
+
Object.assign(extraField, { version, uid, gid });
|
|
4374
|
+
} else if (extraField.data.length >= 4) {
|
|
4375
|
+
// Info-ZIP Unix "Ux" field (0x7855): fixed 2-byte uid + gid (the file mode, if any, is
|
|
4376
|
+
// carried in the external file attributes, not here)
|
|
4377
|
+
uid = getUint16(view, 0);
|
|
4378
|
+
gid = getUint16(view, 2);
|
|
4379
|
+
Object.assign(extraField, { uid, gid });
|
|
4380
|
+
}
|
|
4076
4381
|
if (uid !== UNDEFINED_VALUE) {
|
|
4077
4382
|
directory.uid = uid;
|
|
4078
4383
|
}
|
|
4079
4384
|
if (gid !== UNDEFINED_VALUE) {
|
|
4080
4385
|
directory.gid = gid;
|
|
4081
4386
|
}
|
|
4082
|
-
if (unixMode !== UNDEFINED_VALUE) {
|
|
4083
|
-
directory.unixMode = unixMode;
|
|
4084
|
-
}
|
|
4085
4387
|
} catch {
|
|
4086
4388
|
// ignored
|
|
4087
4389
|
}
|
|
@@ -4095,6 +4397,9 @@
|
|
|
4095
4397
|
}
|
|
4096
4398
|
|
|
4097
4399
|
function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory, localDirectory) {
|
|
4400
|
+
if (!extraFieldExtendedTimestamp.data.length) {
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4098
4403
|
const extraFieldView = getDataView$1(extraFieldExtendedTimestamp.data);
|
|
4099
4404
|
const flags = getUint8(extraFieldView, 0);
|
|
4100
4405
|
const timeProperties = [];
|
|
@@ -4120,7 +4425,8 @@
|
|
|
4120
4425
|
timeProperties.forEach((propertyName, indexProperty) => {
|
|
4121
4426
|
if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
|
|
4122
4427
|
const time = getUint32(extraFieldView, offset);
|
|
4123
|
-
|
|
4428
|
+
// the extended timestamp is a signed 32-bit time_t value
|
|
4429
|
+
directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date((time | 0) * 1000);
|
|
4124
4430
|
const rawPropertyName = timeRawProperties[indexProperty];
|
|
4125
4431
|
extraFieldExtendedTimestamp[rawPropertyName] = time;
|
|
4126
4432
|
}
|
|
@@ -4131,6 +4437,7 @@
|
|
|
4131
4437
|
async function detectOverlappingEntry({
|
|
4132
4438
|
reader,
|
|
4133
4439
|
fileEntry,
|
|
4440
|
+
index,
|
|
4134
4441
|
offset,
|
|
4135
4442
|
diskNumberStart,
|
|
4136
4443
|
signature,
|
|
@@ -4142,10 +4449,9 @@
|
|
|
4142
4449
|
readRanges
|
|
4143
4450
|
}) {
|
|
4144
4451
|
let diskOffset = 0;
|
|
4145
|
-
if (diskNumberStart) {
|
|
4146
|
-
for (let indexReader = 0; indexReader < diskNumberStart; indexReader++) {
|
|
4147
|
-
|
|
4148
|
-
diskOffset += diskReader.size;
|
|
4452
|
+
if (diskNumberStart && reader.readers) {
|
|
4453
|
+
for (let indexReader = 0; indexReader < Math.min(diskNumberStart, reader.readers.length); indexReader++) {
|
|
4454
|
+
diskOffset += reader.readers[indexReader].size;
|
|
4149
4455
|
}
|
|
4150
4456
|
}
|
|
4151
4457
|
let dataDescriptorLength = 0;
|
|
@@ -4158,7 +4464,8 @@
|
|
|
4158
4464
|
}
|
|
4159
4465
|
if (dataDescriptorLength) {
|
|
4160
4466
|
const dataDescriptorArray = await readUint8Array(reader, dataOffset + compressedSize, dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH, diskNumberStart);
|
|
4161
|
-
const dataDescriptorSignature =
|
|
4467
|
+
const dataDescriptorSignature = dataDescriptorArray.length == dataDescriptorLength + DATA_DESCRIPTOR_RECORD_SIGNATURE_LENGTH &&
|
|
4468
|
+
getUint32(getDataView$1(dataDescriptorArray), 0) == DATA_DESCRIPTOR_RECORD_SIGNATURE;
|
|
4162
4469
|
if (dataDescriptorSignature) {
|
|
4163
4470
|
const readSignature = getUint32(getDataView$1(dataDescriptorArray), 4);
|
|
4164
4471
|
let readCompressedSize;
|
|
@@ -4183,14 +4490,14 @@
|
|
|
4183
4490
|
end: diskOffset + dataOffset + compressedSize + dataDescriptorLength,
|
|
4184
4491
|
fileEntry
|
|
4185
4492
|
};
|
|
4186
|
-
for (const otherRange of readRanges) {
|
|
4187
|
-
if (
|
|
4493
|
+
for (const [otherIndex, otherRange] of readRanges) {
|
|
4494
|
+
if (otherIndex != index && range.start < otherRange.end && otherRange.start < range.end) {
|
|
4188
4495
|
const error = new Error(ERR_OVERLAPPING_ENTRY);
|
|
4189
4496
|
error.overlappingEntry = otherRange.fileEntry;
|
|
4190
4497
|
throw error;
|
|
4191
4498
|
}
|
|
4192
4499
|
}
|
|
4193
|
-
readRanges.
|
|
4500
|
+
readRanges.set(index, range);
|
|
4194
4501
|
}
|
|
4195
4502
|
|
|
4196
4503
|
async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
|
|
@@ -4215,6 +4522,35 @@
|
|
|
4215
4522
|
}
|
|
4216
4523
|
}
|
|
4217
4524
|
|
|
4525
|
+
function checkLocalDirectory(zipEntry, localDirectory, rawLocalFilename) {
|
|
4526
|
+
const { rawFilename } = zipEntry;
|
|
4527
|
+
if (rawLocalFilename.length != rawFilename.length ||
|
|
4528
|
+
rawLocalFilename.some((byteValue, indexByte) => byteValue != rawFilename[indexByte])) {
|
|
4529
|
+
throwAmbiguousArchive("mismatched local file header (filename)");
|
|
4530
|
+
}
|
|
4531
|
+
if ((localDirectory.rawBitFlag & BITFLAG_AMBIGUITY_MASK) != (zipEntry.rawBitFlag & BITFLAG_AMBIGUITY_MASK)) {
|
|
4532
|
+
throwAmbiguousArchive("mismatched local file header (general purpose bit flag)");
|
|
4533
|
+
}
|
|
4534
|
+
if (localDirectory.compressionMethod != zipEntry.compressionMethod) {
|
|
4535
|
+
throwAmbiguousArchive("mismatched local file header (compression method)");
|
|
4536
|
+
}
|
|
4537
|
+
// when the data descriptor flag is set, the local signature and sizes are zeroed out; all-zero values are
|
|
4538
|
+
// also tolerated when the flag is clear because some tools produce them (streaming writers predating bit 3)
|
|
4539
|
+
if (!localDirectory.bitFlag.dataDescriptor &&
|
|
4540
|
+
(localDirectory.signature || localDirectory.compressedSize || localDirectory.uncompressedSize) &&
|
|
4541
|
+
(localDirectory.signature != zipEntry.signature ||
|
|
4542
|
+
localDirectory.compressedSize != zipEntry.compressedSize ||
|
|
4543
|
+
localDirectory.uncompressedSize != zipEntry.uncompressedSize)) {
|
|
4544
|
+
throwAmbiguousArchive("mismatched local file header (signature or sizes)");
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
function throwAmbiguousArchive(reason) {
|
|
4549
|
+
const error = new Error(ERR_AMBIGUOUS_ARCHIVE);
|
|
4550
|
+
error.reason = reason;
|
|
4551
|
+
throw error;
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4218
4554
|
function getOptionValue$1(zipReader, options, name) {
|
|
4219
4555
|
return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
|
|
4220
4556
|
}
|
|
@@ -4253,7 +4589,7 @@
|
|
|
4253
4589
|
}
|
|
4254
4590
|
|
|
4255
4591
|
function getDataView$1(array) {
|
|
4256
|
-
return new DataView(array.buffer);
|
|
4592
|
+
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
4257
4593
|
}
|
|
4258
4594
|
|
|
4259
4595
|
/*
|
|
@@ -4293,13 +4629,19 @@
|
|
|
4293
4629
|
const ERR_INVALID_ENCRYPTION_STRENGTH = "The strength must equal 1, 2, or 3";
|
|
4294
4630
|
const ERR_INVALID_EXTRAFIELD_TYPE = "Extra field type exceeds 65535";
|
|
4295
4631
|
const ERR_INVALID_EXTRAFIELD_DATA = "Extra field data exceeds 64KB";
|
|
4632
|
+
// same message as the constant exported by zip-reader.js
|
|
4633
|
+
const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
|
|
4634
|
+
const MIN_UNIX_TIME = -2147483648;
|
|
4635
|
+
const MAX_UNIX_TIME = 2147483647;
|
|
4296
4636
|
const ERR_UNSUPPORTED_FORMAT = "Zip64 is not supported (set the 'zip64' option to 'true')";
|
|
4297
4637
|
const ERR_UNDEFINED_UNCOMPRESSED_SIZE = "Undefined uncompressed size";
|
|
4638
|
+
const ERR_UNDEFINED_READER = "Undefined reader";
|
|
4298
4639
|
const ERR_ZIP_NOT_EMPTY = "Zip file not empty";
|
|
4299
4640
|
const ERR_INVALID_UID = "Invalid uid (must be integer 0..2^32-1)";
|
|
4300
4641
|
const ERR_INVALID_GID = "Invalid gid (must be integer 0..2^32-1)";
|
|
4301
4642
|
const ERR_INVALID_UNIX_MODE = "Invalid UNIX mode (must be integer 0..65535)";
|
|
4302
4643
|
const ERR_INVALID_UNIX_EXTRA_FIELD_TYPE = "Invalid unixExtraFieldType (must be 'infozip' or 'unix')";
|
|
4644
|
+
const ERR_INVALID_UNIX_ID_SIZE = "uid/gid must be 0..65535 for unixExtraFieldType 'unix' (use 'infozip' for larger ids)";
|
|
4303
4645
|
const ERR_INVALID_MSDOS_ATTRIBUTES = "Invalid msdosAttributesRaw (must be integer 0..255)";
|
|
4304
4646
|
const ERR_INVALID_MSDOS_DATA = "Invalid msdosAttributes (must be an object with boolean flags)";
|
|
4305
4647
|
|
|
@@ -4325,9 +4667,10 @@
|
|
|
4325
4667
|
files: new Map(),
|
|
4326
4668
|
filenames: new Set(),
|
|
4327
4669
|
offset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? writer.size || writer.writable.size || 0 : options[OPTION_OFFSET],
|
|
4328
|
-
|
|
4670
|
+
initialOffset: options[OPTION_OFFSET] === UNDEFINED_VALUE ? 0 : options[OPTION_OFFSET] - (writer.size || writer.writable.size || 0),
|
|
4329
4671
|
pendingAddFileCalls: new Set(),
|
|
4330
|
-
bufferedWrites: 0
|
|
4672
|
+
bufferedWrites: 0,
|
|
4673
|
+
lastFileEntry: UNDEFINED_VALUE
|
|
4331
4674
|
});
|
|
4332
4675
|
}
|
|
4333
4676
|
|
|
@@ -4336,17 +4679,18 @@
|
|
|
4336
4679
|
throw new Error(ERR_ZIP_NOT_EMPTY);
|
|
4337
4680
|
}
|
|
4338
4681
|
reader = new GenericReader(reader);
|
|
4682
|
+
await initStream(reader);
|
|
4339
4683
|
const zipReader = new ZipReader(reader.readable);
|
|
4340
4684
|
const entries = await zipReader.getEntries();
|
|
4341
4685
|
await zipReader.close();
|
|
4686
|
+
await initStream(this.writer);
|
|
4342
4687
|
await reader.readable.pipeTo(this.writer.writable, { preventClose: true, preventAbort: true });
|
|
4343
4688
|
this.writer.size = this.offset = reader.size;
|
|
4344
4689
|
this.filenames = new Set(entries.map(entry => entry.filename));
|
|
4345
4690
|
this.files = new Map(entries.map(entry => {
|
|
4346
4691
|
const {
|
|
4347
4692
|
version,
|
|
4348
|
-
|
|
4349
|
-
lastModDate,
|
|
4693
|
+
rawLastModDate,
|
|
4350
4694
|
lastAccessDate,
|
|
4351
4695
|
creationDate,
|
|
4352
4696
|
rawFilename,
|
|
@@ -4359,6 +4703,7 @@
|
|
|
4359
4703
|
zip64
|
|
4360
4704
|
} = entry;
|
|
4361
4705
|
let {
|
|
4706
|
+
compressionMethod,
|
|
4362
4707
|
rawExtraFieldZip64,
|
|
4363
4708
|
rawExtraFieldAES,
|
|
4364
4709
|
rawExtraFieldExtendedTimestamp,
|
|
@@ -4371,31 +4716,39 @@
|
|
|
4371
4716
|
rawExtraFieldAES = rawExtraFieldAES || new Uint8Array();
|
|
4372
4717
|
rawExtraFieldExtendedTimestamp = rawExtraFieldExtendedTimestamp || new Uint8Array();
|
|
4373
4718
|
rawExtraFieldNTFS = rawExtraFieldNTFS || new Uint8Array();
|
|
4374
|
-
rawExtraFieldUnix =
|
|
4719
|
+
rawExtraFieldUnix = rawExtraFieldUnix || new Uint8Array();
|
|
4375
4720
|
rawExtraField = rawExtraField || new Uint8Array();
|
|
4721
|
+
if (entry.extraFieldAES) {
|
|
4722
|
+
compressionMethod = COMPRESSION_METHOD_AES;
|
|
4723
|
+
}
|
|
4376
4724
|
const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
|
|
4377
|
-
const zip64UncompressedSize = zip64 && uncompressedSize
|
|
4378
|
-
const zip64CompressedSize = zip64 && compressedSize
|
|
4725
|
+
const zip64UncompressedSize = zip64 && uncompressedSize >= MAX_32_BITS;
|
|
4726
|
+
const zip64CompressedSize = zip64 && compressedSize >= MAX_32_BITS;
|
|
4727
|
+
const bitFlagValue = (getBitFlag(level, languageEncodingFlag, dataDescriptor, encrypted, compressionMethod) & ~BITFLAG_LEVEL) | (level << 1);
|
|
4379
4728
|
const {
|
|
4380
4729
|
headerArray,
|
|
4381
4730
|
headerView
|
|
4382
4731
|
} = getHeaderArrayData({
|
|
4383
4732
|
version,
|
|
4384
|
-
bitFlag:
|
|
4733
|
+
bitFlag: bitFlagValue,
|
|
4385
4734
|
compressionMethod,
|
|
4386
4735
|
uncompressedSize,
|
|
4387
4736
|
compressedSize,
|
|
4388
|
-
|
|
4737
|
+
rawLastModDate,
|
|
4389
4738
|
rawFilename,
|
|
4390
4739
|
zip64CompressedSize,
|
|
4391
4740
|
zip64UncompressedSize,
|
|
4392
4741
|
extraFieldLength
|
|
4393
4742
|
});
|
|
4743
|
+
const { signature } = entry;
|
|
4744
|
+
if (signature !== UNDEFINED_VALUE) {
|
|
4745
|
+
setUint32(headerView, HEADER_OFFSET_SIGNATURE, signature);
|
|
4746
|
+
}
|
|
4394
4747
|
Object.assign(entry, {
|
|
4395
4748
|
zip64UncompressedSize,
|
|
4396
4749
|
zip64CompressedSize,
|
|
4397
|
-
zip64Offset: zip64 && this.offset - diskOffset
|
|
4398
|
-
zip64DiskNumberStart: zip64 && diskNumber
|
|
4750
|
+
zip64Offset: zip64 && this.offset - diskOffset >= MAX_32_BITS,
|
|
4751
|
+
zip64DiskNumberStart: zip64 && diskNumber >= MAX_16_BITS,
|
|
4399
4752
|
rawExtraFieldZip64,
|
|
4400
4753
|
rawExtraFieldAES,
|
|
4401
4754
|
rawExtraFieldExtendedTimestamp,
|
|
@@ -4413,6 +4766,7 @@
|
|
|
4413
4766
|
|
|
4414
4767
|
async add(name = "", reader, options = {}) {
|
|
4415
4768
|
const zipWriter = this;
|
|
4769
|
+
options = Object.assign({}, options);
|
|
4416
4770
|
const {
|
|
4417
4771
|
pendingAddFileCalls,
|
|
4418
4772
|
config
|
|
@@ -4423,17 +4777,24 @@
|
|
|
4423
4777
|
await new Promise(resolve => pendingEntries.push(resolve));
|
|
4424
4778
|
}
|
|
4425
4779
|
let promiseAddFile;
|
|
4780
|
+
let nameAdded;
|
|
4426
4781
|
try {
|
|
4427
4782
|
name = name.trim();
|
|
4783
|
+
if (getOptionValue(zipWriter, options, PROPERTY_NAME_DIRECTORY) && !name.endsWith(DIRECTORY_SIGNATURE)) {
|
|
4784
|
+
name += DIRECTORY_SIGNATURE;
|
|
4785
|
+
}
|
|
4428
4786
|
if (zipWriter.filenames.has(name)) {
|
|
4429
4787
|
throw new Error(ERR_DUPLICATED_NAME);
|
|
4430
4788
|
}
|
|
4431
4789
|
zipWriter.filenames.add(name);
|
|
4790
|
+
nameAdded = true;
|
|
4432
4791
|
promiseAddFile = addFile(zipWriter, name, reader, options);
|
|
4433
4792
|
pendingAddFileCalls.add(promiseAddFile);
|
|
4434
4793
|
return await promiseAddFile;
|
|
4435
4794
|
} catch (error) {
|
|
4436
|
-
|
|
4795
|
+
if (nameAdded) {
|
|
4796
|
+
zipWriter.filenames.delete(name);
|
|
4797
|
+
}
|
|
4437
4798
|
throw error;
|
|
4438
4799
|
} finally {
|
|
4439
4800
|
pendingAddFileCalls.delete(promiseAddFile);
|
|
@@ -4466,6 +4827,9 @@
|
|
|
4466
4827
|
const zipWriter = this;
|
|
4467
4828
|
const { pendingAddFileCalls, writer } = this;
|
|
4468
4829
|
const { writable } = writer;
|
|
4830
|
+
if (getLength(comment) > MAX_16_BITS) {
|
|
4831
|
+
throw new Error(ERR_INVALID_COMMENT);
|
|
4832
|
+
}
|
|
4469
4833
|
while (pendingAddFileCalls.size) {
|
|
4470
4834
|
await Promise.allSettled(Array.from(pendingAddFileCalls));
|
|
4471
4835
|
}
|
|
@@ -4479,6 +4843,37 @@
|
|
|
4479
4843
|
}
|
|
4480
4844
|
|
|
4481
4845
|
async function addFile(zipWriter, name, reader, options) {
|
|
4846
|
+
const attributesInfo = resolveAttributes(zipWriter, name, options);
|
|
4847
|
+
({ name } = attributesInfo);
|
|
4848
|
+
const metadataInfo = resolveMetadata(zipWriter, name, options);
|
|
4849
|
+
const { comment } = metadataInfo;
|
|
4850
|
+
const extraField = options[PROPERTY_NAME_EXTRA_FIELD];
|
|
4851
|
+
zipWriter.files.set(name, UNDEFINED_VALUE);
|
|
4852
|
+
let fileEntry;
|
|
4853
|
+
try {
|
|
4854
|
+
const sizesInfo = await resolveSizes(zipWriter, reader, metadataInfo, options);
|
|
4855
|
+
({ reader } = sizesInfo);
|
|
4856
|
+
const { diskOffset, diskNumber } = zipWriter.writer;
|
|
4857
|
+
options = Object.assign({}, options, attributesInfo.resolvedOptions, metadataInfo.resolvedOptions, sizesInfo.resolvedOptions, {
|
|
4858
|
+
internalFileAttribute: metadataInfo.resolvedOptions.internalFileAttributes,
|
|
4859
|
+
externalFileAttribute: attributesInfo.resolvedOptions.externalFileAttributes,
|
|
4860
|
+
signature: options[PROPERTY_NAME_SIGNATURE],
|
|
4861
|
+
offset: zipWriter.offset - diskOffset,
|
|
4862
|
+
diskNumberStart: diskNumber
|
|
4863
|
+
});
|
|
4864
|
+
const headerInfo = getHeaderInfo(options);
|
|
4865
|
+
const dataDescriptorInfo = getDataDescriptorInfo(options);
|
|
4866
|
+
const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
|
|
4867
|
+
fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
|
|
4868
|
+
} catch (error) {
|
|
4869
|
+
zipWriter.files.delete(name);
|
|
4870
|
+
throw error;
|
|
4871
|
+
}
|
|
4872
|
+
Object.assign(fileEntry, { name, comment, extraField });
|
|
4873
|
+
return new Entry(fileEntry);
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
function resolveAttributes(zipWriter, name, options) {
|
|
4482
4877
|
name = name.trim();
|
|
4483
4878
|
let msDosCompatible = getOptionValue(zipWriter, options, PROPERTY_NAME_MS_DOS_COMPATIBLE);
|
|
4484
4879
|
let versionMadeBy = getOptionValue(zipWriter, options, PROPERTY_NAME_VERSION_MADE_BY, msDosCompatible ? 20 : 768);
|
|
@@ -4486,7 +4881,7 @@
|
|
|
4486
4881
|
const uid = getOptionValue(zipWriter, options, PROPERTY_NAME_UID);
|
|
4487
4882
|
const gid = getOptionValue(zipWriter, options, PROPERTY_NAME_GID);
|
|
4488
4883
|
let unixMode = getOptionValue(zipWriter, options, PROPERTY_NAME_UNIX_MODE);
|
|
4489
|
-
|
|
4884
|
+
let unixExtraFieldType = getOptionValue(zipWriter, options, OPTION_UNIX_EXTRA_FIELD_TYPE);
|
|
4490
4885
|
let setuid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETUID);
|
|
4491
4886
|
let setgid = getOptionValue(zipWriter, options, PROPERTY_NAME_SETGID);
|
|
4492
4887
|
let sticky = getOptionValue(zipWriter, options, PROPERTY_NAME_STICKY);
|
|
@@ -4502,6 +4897,13 @@
|
|
|
4502
4897
|
if (unixExtraFieldType !== UNDEFINED_VALUE && unixExtraFieldType !== INFOZIP_EXTRA_FIELD_TYPE && unixExtraFieldType !== UNIX_EXTRA_FIELD_TYPE) {
|
|
4503
4898
|
throw new Error(ERR_INVALID_UNIX_EXTRA_FIELD_TYPE);
|
|
4504
4899
|
}
|
|
4900
|
+
if (unixExtraFieldType === UNIX_EXTRA_FIELD_TYPE &&
|
|
4901
|
+
((uid !== UNDEFINED_VALUE && uid > MAX_16_BITS) || (gid !== UNDEFINED_VALUE && gid > MAX_16_BITS))) {
|
|
4902
|
+
throw new Error(ERR_INVALID_UNIX_ID_SIZE);
|
|
4903
|
+
}
|
|
4904
|
+
if (unixExtraFieldType === UNDEFINED_VALUE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
|
|
4905
|
+
unixExtraFieldType = INFOZIP_EXTRA_FIELD_TYPE;
|
|
4906
|
+
}
|
|
4505
4907
|
let msdosAttributesRaw = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES_RAW);
|
|
4506
4908
|
let msdosAttributes = getOptionValue(zipWriter, options, PROPERTY_NAME_MSDOS_ATTRIBUTES);
|
|
4507
4909
|
const hasUnixMetadata = uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE || unixMode !== UNDEFINED_VALUE || unixExtraFieldType;
|
|
@@ -4572,6 +4974,27 @@
|
|
|
4572
4974
|
if (hasMsDosProvided) {
|
|
4573
4975
|
externalFileAttributes = (externalFileAttributes & MAX_32_BITS) | (msdosAttributesRaw & MAX_8_BITS);
|
|
4574
4976
|
}
|
|
4977
|
+
return {
|
|
4978
|
+
name,
|
|
4979
|
+
resolvedOptions: {
|
|
4980
|
+
versionMadeBy,
|
|
4981
|
+
msDosCompatible,
|
|
4982
|
+
externalFileAttributes,
|
|
4983
|
+
unixExternalUpper,
|
|
4984
|
+
uid,
|
|
4985
|
+
gid,
|
|
4986
|
+
unixMode,
|
|
4987
|
+
unixExtraFieldType,
|
|
4988
|
+
setuid,
|
|
4989
|
+
setgid,
|
|
4990
|
+
sticky,
|
|
4991
|
+
msdosAttributesRaw,
|
|
4992
|
+
msdosAttributes
|
|
4993
|
+
}
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
function resolveMetadata(zipWriter, name, options) {
|
|
4575
4998
|
const encode = getOptionValue(zipWriter, options, OPTION_ENCODE_TEXT, encodeText);
|
|
4576
4999
|
let rawFilename = encode(name);
|
|
4577
5000
|
if (rawFilename === UNDEFINED_VALUE) {
|
|
@@ -4610,10 +5033,14 @@
|
|
|
4610
5033
|
const transferStreams = getOptionValue(zipWriter, options, OPTION_TRANSFER_STREAMS, true);
|
|
4611
5034
|
const bufferedWrite = getOptionValue(zipWriter, options, OPTION_BUFFERED_WRITE);
|
|
4612
5035
|
const createTempStream = getOptionValue(zipWriter, options, OPTION_CREATE_TEMP_STREAM);
|
|
4613
|
-
const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE,
|
|
5036
|
+
const dataDescriptorSignature = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR_SIGNATURE, true);
|
|
4614
5037
|
const signal = getOptionValue(zipWriter, options, OPTION_SIGNAL);
|
|
4615
5038
|
const useUnicodeFileNames = getOptionValue(zipWriter, options, OPTION_USE_UNICODE_FILE_NAMES, true);
|
|
4616
5039
|
const compressionMethod = getOptionValue(zipWriter, options, PROPERTY_NAME_COMPRESSION_METHOD);
|
|
5040
|
+
if (!passThrough && compressionMethod !== UNDEFINED_VALUE &&
|
|
5041
|
+
compressionMethod !== COMPRESSION_METHOD_STORE && compressionMethod !== COMPRESSION_METHOD_DEFLATE) {
|
|
5042
|
+
throw new Error(ERR_UNSUPPORTED_COMPRESSION);
|
|
5043
|
+
}
|
|
4617
5044
|
let level = getOptionValue(zipWriter, options, OPTION_LEVEL);
|
|
4618
5045
|
let useCompressionStream = getOptionValue(zipWriter, options, OPTION_USE_COMPRESSION_STREAM);
|
|
4619
5046
|
let dataDescriptor = getOptionValue(zipWriter, options, OPTION_DATA_DESCRIPTOR);
|
|
@@ -4629,7 +5056,7 @@
|
|
|
4629
5056
|
if (!useCompressionStream && (zipWriter.config.CompressionStream === UNDEFINED_VALUE && zipWriter.config.CompressionStreamZlib === UNDEFINED_VALUE)) {
|
|
4630
5057
|
level = 0;
|
|
4631
5058
|
}
|
|
4632
|
-
|
|
5059
|
+
const zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
|
|
4633
5060
|
if (!zipCrypto && (password !== UNDEFINED_VALUE || rawPassword !== UNDEFINED_VALUE) && !(encryptionStrength >= 1 && encryptionStrength <= 3)) {
|
|
4634
5061
|
throw new Error(ERR_INVALID_ENCRYPTION_STRENGTH);
|
|
4635
5062
|
}
|
|
@@ -4640,6 +5067,7 @@
|
|
|
4640
5067
|
let offset = 0;
|
|
4641
5068
|
extraField.forEach(data => extraFieldSize += 4 + getLength(data));
|
|
4642
5069
|
rawExtraField = new Uint8Array(extraFieldSize);
|
|
5070
|
+
const rawExtraFieldView = getDataView(rawExtraField);
|
|
4643
5071
|
extraField.forEach((data, type) => {
|
|
4644
5072
|
if (type > MAX_16_BITS) {
|
|
4645
5073
|
throw new Error(ERR_INVALID_EXTRAFIELD_TYPE);
|
|
@@ -4647,22 +5075,72 @@
|
|
|
4647
5075
|
if (getLength(data) > MAX_16_BITS) {
|
|
4648
5076
|
throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
|
|
4649
5077
|
}
|
|
4650
|
-
|
|
4651
|
-
|
|
5078
|
+
setUint16(rawExtraFieldView, offset, type);
|
|
5079
|
+
setUint16(rawExtraFieldView, offset + 2, getLength(data));
|
|
4652
5080
|
arraySet(rawExtraField, data, offset + 4);
|
|
4653
5081
|
offset += 4 + getLength(data);
|
|
4654
5082
|
});
|
|
4655
5083
|
}
|
|
5084
|
+
return {
|
|
5085
|
+
comment,
|
|
5086
|
+
resolvedOptions: {
|
|
5087
|
+
rawFilename,
|
|
5088
|
+
rawComment,
|
|
5089
|
+
version,
|
|
5090
|
+
lastModDate,
|
|
5091
|
+
lastAccessDate,
|
|
5092
|
+
creationDate,
|
|
5093
|
+
internalFileAttributes,
|
|
5094
|
+
passThrough,
|
|
5095
|
+
password,
|
|
5096
|
+
rawPassword,
|
|
5097
|
+
encryptionStrength,
|
|
5098
|
+
zipCrypto,
|
|
5099
|
+
extendedTimestamp,
|
|
5100
|
+
keepOrder,
|
|
5101
|
+
useWebWorkers,
|
|
5102
|
+
transferStreams,
|
|
5103
|
+
bufferedWrite,
|
|
5104
|
+
createTempStream,
|
|
5105
|
+
dataDescriptorSignature,
|
|
5106
|
+
signal,
|
|
5107
|
+
useUnicodeFileNames,
|
|
5108
|
+
compressionMethod,
|
|
5109
|
+
level,
|
|
5110
|
+
useCompressionStream,
|
|
5111
|
+
dataDescriptor,
|
|
5112
|
+
zip64,
|
|
5113
|
+
rawExtraField
|
|
5114
|
+
}
|
|
5115
|
+
};
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
async function resolveSizes(zipWriter, reader, { resolvedOptions: metadata }, options) {
|
|
5119
|
+
const { passThrough, zipCrypto, password, rawPassword, encryptionStrength } = metadata;
|
|
5120
|
+
let { dataDescriptor, zip64, level, compressionMethod } = metadata;
|
|
4656
5121
|
let maximumCompressedSize = 0;
|
|
4657
|
-
let maximumEntrySize = 0;
|
|
4658
5122
|
let uncompressedSize = 0;
|
|
4659
5123
|
if (passThrough) {
|
|
5124
|
+
// the headers of a passThrough entry describe the payload verbatim: without a
|
|
5125
|
+
// reader they would declare content that is not there
|
|
5126
|
+
if (!reader) {
|
|
5127
|
+
throw new Error(ERR_UNDEFINED_READER);
|
|
5128
|
+
}
|
|
4660
5129
|
uncompressedSize = options[PROPERTY_NAME_UNCOMPRESSED_SIZE];
|
|
4661
5130
|
if (uncompressedSize === UNDEFINED_VALUE) {
|
|
4662
5131
|
throw new Error(ERR_UNDEFINED_UNCOMPRESSED_SIZE);
|
|
4663
5132
|
}
|
|
4664
5133
|
}
|
|
4665
5134
|
const zip64Enabled = zip64 === true;
|
|
5135
|
+
const encrypted = getOptionValue(zipWriter, options, PROPERTY_NAME_ENCRYPTED);
|
|
5136
|
+
// entries without a reader have no payload: write them as empty stored files instead of
|
|
5137
|
+
// declaring a compressed or encrypted content that does not exist
|
|
5138
|
+
const encryptedEntry = Boolean(reader) && (Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted));
|
|
5139
|
+
if (!reader) {
|
|
5140
|
+
level = 0;
|
|
5141
|
+
compressionMethod = COMPRESSION_METHOD_STORE;
|
|
5142
|
+
}
|
|
5143
|
+
const encryptionOverhead = encryptedEntry ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
|
|
4666
5144
|
if (reader) {
|
|
4667
5145
|
reader = new GenericReader(reader);
|
|
4668
5146
|
await initStream(reader);
|
|
@@ -4675,16 +5153,15 @@
|
|
|
4675
5153
|
}
|
|
4676
5154
|
} else {
|
|
4677
5155
|
options.uncompressedSize = uncompressedSize = reader.size;
|
|
4678
|
-
maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
|
|
5156
|
+
maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
|
|
4679
5157
|
}
|
|
4680
5158
|
} else {
|
|
4681
5159
|
options.uncompressedSize = uncompressedSize;
|
|
4682
|
-
maximumCompressedSize = getMaximumCompressedSize(uncompressedSize);
|
|
5160
|
+
maximumCompressedSize = getMaximumCompressedSize(uncompressedSize) + encryptionOverhead;
|
|
4683
5161
|
}
|
|
4684
5162
|
}
|
|
4685
|
-
const
|
|
4686
|
-
const
|
|
4687
|
-
const zip64CompressedSize = zip64Enabled || maximumCompressedSize > MAX_32_BITS;
|
|
5163
|
+
const zip64UncompressedSize = zip64Enabled || uncompressedSize >= MAX_32_BITS;
|
|
5164
|
+
const zip64CompressedSize = zip64Enabled || maximumCompressedSize >= MAX_32_BITS;
|
|
4688
5165
|
if (zip64UncompressedSize || zip64CompressedSize) {
|
|
4689
5166
|
if (zip64 === false) {
|
|
4690
5167
|
throw new Error(ERR_UNSUPPORTED_FORMAT);
|
|
@@ -4693,73 +5170,19 @@
|
|
|
4693
5170
|
}
|
|
4694
5171
|
}
|
|
4695
5172
|
zip64 = zip64 || false;
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
password,
|
|
4710
|
-
rawPassword,
|
|
4711
|
-
level,
|
|
4712
|
-
useWebWorkers,
|
|
4713
|
-
transferStreams,
|
|
4714
|
-
encryptionStrength,
|
|
4715
|
-
extendedTimestamp,
|
|
4716
|
-
zipCrypto,
|
|
4717
|
-
bufferedWrite,
|
|
4718
|
-
createTempStream,
|
|
4719
|
-
keepOrder,
|
|
4720
|
-
useUnicodeFileNames,
|
|
4721
|
-
dataDescriptor,
|
|
4722
|
-
dataDescriptorSignature,
|
|
4723
|
-
signal,
|
|
4724
|
-
msDosCompatible,
|
|
4725
|
-
internalFileAttribute: internalFileAttributes,
|
|
4726
|
-
internalFileAttributes,
|
|
4727
|
-
externalFileAttribute: externalFileAttributes,
|
|
4728
|
-
externalFileAttributes,
|
|
4729
|
-
useCompressionStream,
|
|
4730
|
-
passThrough,
|
|
4731
|
-
encrypted: Boolean((password && getLength(password)) || (rawPassword && getLength(rawPassword))) || (passThrough && encrypted),
|
|
4732
|
-
signature: options[PROPERTY_NAME_SIGNATURE],
|
|
4733
|
-
compressionMethod,
|
|
4734
|
-
uncompressedSize,
|
|
4735
|
-
offset: zipWriter.offset - diskOffset,
|
|
4736
|
-
diskNumberStart: diskNumber,
|
|
4737
|
-
uid,
|
|
4738
|
-
gid,
|
|
4739
|
-
setuid,
|
|
4740
|
-
setgid,
|
|
4741
|
-
sticky,
|
|
4742
|
-
unixMode,
|
|
4743
|
-
msdosAttributesRaw,
|
|
4744
|
-
msdosAttributes,
|
|
4745
|
-
unixExternalUpper
|
|
4746
|
-
});
|
|
4747
|
-
const headerInfo = getHeaderInfo(options);
|
|
4748
|
-
const dataDescriptorInfo = getDataDescriptorInfo(options);
|
|
4749
|
-
const metadataSize = getLength(headerInfo.localHeaderArray, dataDescriptorInfo.dataDescriptorArray);
|
|
4750
|
-
maximumEntrySize = metadataSize + maximumCompressedSize;
|
|
4751
|
-
if (zipWriter.options[OPTION_USDZ]) {
|
|
4752
|
-
maximumEntrySize += maximumEntrySize + 64;
|
|
4753
|
-
}
|
|
4754
|
-
zipWriter.pendingEntriesSize += maximumEntrySize;
|
|
4755
|
-
let fileEntry;
|
|
4756
|
-
try {
|
|
4757
|
-
fileEntry = await getFileEntry(zipWriter, name, reader, { headerInfo, dataDescriptorInfo, metadataSize }, options);
|
|
4758
|
-
} finally {
|
|
4759
|
-
zipWriter.pendingEntriesSize -= maximumEntrySize;
|
|
4760
|
-
}
|
|
4761
|
-
Object.assign(fileEntry, { name, comment, extraField });
|
|
4762
|
-
return new Entry(fileEntry);
|
|
5173
|
+
return {
|
|
5174
|
+
reader,
|
|
5175
|
+
resolvedOptions: {
|
|
5176
|
+
dataDescriptor,
|
|
5177
|
+
zip64,
|
|
5178
|
+
zip64UncompressedSize,
|
|
5179
|
+
zip64CompressedSize,
|
|
5180
|
+
uncompressedSize,
|
|
5181
|
+
level,
|
|
5182
|
+
compressionMethod,
|
|
5183
|
+
encrypted: encryptedEntry
|
|
5184
|
+
}
|
|
5185
|
+
};
|
|
4763
5186
|
}
|
|
4764
5187
|
|
|
4765
5188
|
async function getFileEntry(zipWriter, name, reader, entryInfo, options) {
|
|
@@ -4776,37 +5199,40 @@
|
|
|
4776
5199
|
headerInfo
|
|
4777
5200
|
} = entryInfo;
|
|
4778
5201
|
const usdz = zipWriter.options[OPTION_USDZ];
|
|
4779
|
-
const previousFileEntry =
|
|
5202
|
+
const previousFileEntry = zipWriter.lastFileEntry;
|
|
4780
5203
|
let fileEntry = {};
|
|
4781
5204
|
let bufferedWrite;
|
|
4782
5205
|
let releaseLockWriter;
|
|
4783
5206
|
let releaseLockCurrentFileEntry;
|
|
4784
5207
|
let writingBufferedEntryData;
|
|
4785
5208
|
let writingEntryData;
|
|
5209
|
+
let writerSizeBeforeEntry;
|
|
5210
|
+
let flushedBufferedSize = 0;
|
|
4786
5211
|
let fileWriter;
|
|
4787
5212
|
files.set(name, fileEntry);
|
|
5213
|
+
zipWriter.lastFileEntry = fileEntry;
|
|
4788
5214
|
try {
|
|
4789
5215
|
let lockPreviousFileEntry;
|
|
4790
5216
|
if (keepOrder) {
|
|
4791
5217
|
lockPreviousFileEntry = previousFileEntry && previousFileEntry.lock;
|
|
4792
5218
|
requestLockCurrentFileEntry();
|
|
4793
5219
|
}
|
|
4794
|
-
if (
|
|
5220
|
+
if (options.bufferedWrite || !keepOrder || zipWriter.writerLocked || zipWriter.bufferedWrites || !dataDescriptor) {
|
|
5221
|
+
bufferedWrite = true;
|
|
5222
|
+
zipWriter.bufferedWrites++;
|
|
4795
5223
|
if (options.createTempStream) {
|
|
4796
5224
|
fileWriter = await options.createTempStream();
|
|
4797
5225
|
} else {
|
|
4798
5226
|
fileWriter = new TransformStream(UNDEFINED_VALUE, UNDEFINED_VALUE, { highWaterMark: INFINITY_VALUE });
|
|
4799
5227
|
}
|
|
4800
5228
|
fileWriter.size = 0;
|
|
4801
|
-
bufferedWrite = true;
|
|
4802
|
-
zipWriter.bufferedWrites++;
|
|
4803
5229
|
await initStream(writer);
|
|
4804
5230
|
} else {
|
|
4805
5231
|
fileWriter = writer;
|
|
4806
5232
|
await requestLockWriter();
|
|
4807
5233
|
}
|
|
4808
5234
|
await initStream(fileWriter);
|
|
4809
|
-
const {
|
|
5235
|
+
const { diskOffset } = writer;
|
|
4810
5236
|
if (zipWriter.addSplitZipSignature) {
|
|
4811
5237
|
delete zipWriter.addSplitZipSignature;
|
|
4812
5238
|
const signatureArray = new Uint8Array(4);
|
|
@@ -4815,21 +5241,20 @@
|
|
|
4815
5241
|
await writeData(writer, signatureArray);
|
|
4816
5242
|
zipWriter.offset += 4;
|
|
4817
5243
|
}
|
|
4818
|
-
if (usdz) {
|
|
5244
|
+
if (usdz && !bufferedWrite) {
|
|
4819
5245
|
appendExtraFieldUSDZ(entryInfo, zipWriter.offset - diskOffset);
|
|
4820
5246
|
}
|
|
4821
|
-
const {
|
|
4822
|
-
localHeaderView,
|
|
4823
|
-
localHeaderArray
|
|
4824
|
-
} = headerInfo;
|
|
5247
|
+
const { localHeaderArray } = headerInfo;
|
|
4825
5248
|
if (!bufferedWrite) {
|
|
4826
5249
|
await lockPreviousFileEntry;
|
|
4827
|
-
await skipDiskIfNeeded(
|
|
5250
|
+
await skipDiskIfNeeded();
|
|
4828
5251
|
}
|
|
4829
|
-
const
|
|
4830
|
-
|
|
5252
|
+
const diskNumberStart = writer.diskNumber;
|
|
5253
|
+
const entryOffset = getSegmentOffset(zipWriter, writer);
|
|
5254
|
+
fileEntry.diskNumberStart = diskNumberStart;
|
|
4831
5255
|
if (!bufferedWrite) {
|
|
4832
5256
|
writingEntryData = true;
|
|
5257
|
+
writerSizeBeforeEntry = writer.size;
|
|
4833
5258
|
await writeData(fileWriter, localHeaderArray);
|
|
4834
5259
|
}
|
|
4835
5260
|
fileEntry = await createFileEntry(reader, fileWriter, fileEntry, entryInfo, zipWriter.config, options);
|
|
@@ -4842,16 +5267,24 @@
|
|
|
4842
5267
|
await Promise.all([fileWriter.writable.getWriter().close(), lockPreviousFileEntry]);
|
|
4843
5268
|
await requestLockWriter();
|
|
4844
5269
|
writingBufferedEntryData = true;
|
|
5270
|
+
writerSizeBeforeEntry = writer.size;
|
|
5271
|
+
await skipDiskIfNeeded();
|
|
4845
5272
|
fileEntry.diskNumberStart = writer.diskNumber;
|
|
4846
|
-
fileEntry.offset = zipWriter
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
5273
|
+
fileEntry.offset = getSegmentOffset(zipWriter, writer);
|
|
5274
|
+
if (usdz) {
|
|
5275
|
+
// the padding depends on the entry offset, only known now that the writer is locked
|
|
5276
|
+
const previousMetadataSize = entryInfo.metadataSize;
|
|
5277
|
+
appendExtraFieldUSDZ(entryInfo, zipWriter.offset - writer.diskOffset);
|
|
5278
|
+
fileEntry.size += entryInfo.metadataSize - previousMetadataSize;
|
|
5279
|
+
}
|
|
5280
|
+
updateLocalHeader(fileEntry, headerInfo.localHeaderView, options);
|
|
5281
|
+
await writeData(writer, headerInfo.localHeaderArray);
|
|
5282
|
+
await flushBufferedData(fileWriter.readable, writer, signal, chunkLength => flushedBufferedSize += chunkLength);
|
|
4851
5283
|
writer.size += fileWriter.size;
|
|
4852
5284
|
writingBufferedEntryData = false;
|
|
4853
5285
|
} else {
|
|
4854
|
-
fileEntry.
|
|
5286
|
+
fileEntry.diskNumberStart = diskNumberStart;
|
|
5287
|
+
fileEntry.offset = entryOffset;
|
|
4855
5288
|
}
|
|
4856
5289
|
zipWriter.offset += fileEntry.size;
|
|
4857
5290
|
return fileEntry;
|
|
@@ -4865,10 +5298,9 @@
|
|
|
4865
5298
|
// ignored
|
|
4866
5299
|
}
|
|
4867
5300
|
}
|
|
5301
|
+
zipWriter.offset += writer.size - writerSizeBeforeEntry;
|
|
4868
5302
|
if (bufferedWrite) {
|
|
4869
|
-
zipWriter.offset +=
|
|
4870
|
-
} else {
|
|
4871
|
-
zipWriter.offset = fileWriter.size;
|
|
5303
|
+
zipWriter.offset += flushedBufferedSize;
|
|
4872
5304
|
}
|
|
4873
5305
|
}
|
|
4874
5306
|
files.delete(name);
|
|
@@ -4899,10 +5331,10 @@
|
|
|
4899
5331
|
await lockWriter;
|
|
4900
5332
|
}
|
|
4901
5333
|
|
|
4902
|
-
async function skipDiskIfNeeded(
|
|
5334
|
+
async function skipDiskIfNeeded() {
|
|
4903
5335
|
if (getLength(headerInfo.localHeaderArray) > writer.availableSize) {
|
|
4904
5336
|
writer.availableSize = 0;
|
|
4905
|
-
await writeData(
|
|
5337
|
+
await writeData(writer, new Uint8Array());
|
|
4906
5338
|
}
|
|
4907
5339
|
}
|
|
4908
5340
|
}
|
|
@@ -5043,6 +5475,10 @@
|
|
|
5043
5475
|
uncompressedSize = result.inputSize;
|
|
5044
5476
|
signature = result.signature;
|
|
5045
5477
|
}
|
|
5478
|
+
if ((!zip64CompressedSize && compressedSize >= MAX_32_BITS) ||
|
|
5479
|
+
(!zip64UncompressedSize && uncompressedSize >= MAX_32_BITS)) {
|
|
5480
|
+
throw new Error(ERR_UNSUPPORTED_FORMAT);
|
|
5481
|
+
}
|
|
5046
5482
|
} catch (error) {
|
|
5047
5483
|
if (error.outputSize !== UNDEFINED_VALUE) {
|
|
5048
5484
|
writer.size += error.outputSize;
|
|
@@ -5105,35 +5541,28 @@
|
|
|
5105
5541
|
uncompressedSize
|
|
5106
5542
|
} = options;
|
|
5107
5543
|
let { version, compressionMethod } = options;
|
|
5108
|
-
const compressed = !directory && (
|
|
5544
|
+
const compressed = !directory && (compressionMethod === UNDEFINED_VALUE
|
|
5545
|
+
? (level === UNDEFINED_VALUE || level > 0)
|
|
5546
|
+
: compressionMethod !== COMPRESSION_METHOD_STORE);
|
|
5109
5547
|
let rawLocalExtraFieldZip64;
|
|
5110
5548
|
const uncompressedFile = passThrough || !compressed;
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
setUint16(rawLocalExtraFieldZip64View, 2, getLength(rawLocalExtraFieldZip64) - 4);
|
|
5549
|
+
// entries without a data descriptor are always buffered (cf. getFileEntry), so the
|
|
5550
|
+
// zip64 extra field values can be patched into the local header before flushing it
|
|
5551
|
+
const zip64ExtraFieldComplete = zip64 && (options.bufferedWrite || !dataDescriptor || ((!zip64UncompressedSize && !zip64CompressedSize) || uncompressedFile));
|
|
5552
|
+
const writeLocalExtraFieldZip64 = zip64ExtraFieldComplete || (zip64 && dataDescriptor && (zip64UncompressedSize || zip64CompressedSize));
|
|
5553
|
+
if (zip64 && (zip64UncompressedSize || zip64CompressedSize)) {
|
|
5554
|
+
// APPNOTE 4.5.3: the zip64 extra field of a local header must include both the
|
|
5555
|
+
// uncompressed and compressed size fields
|
|
5556
|
+
const length = 4 + 16;
|
|
5557
|
+
const extraFieldZip64 = createRecordWriter(length);
|
|
5558
|
+
extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
|
|
5559
|
+
extraFieldZip64.uint16(length - 4);
|
|
5560
|
+
rawLocalExtraFieldZip64 = extraFieldZip64.array;
|
|
5124
5561
|
if (zip64ExtraFieldComplete) {
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
rawLocalExtraFieldZip64Offset += 8;
|
|
5130
|
-
}
|
|
5131
|
-
if (zip64CompressedSize && uncompressedFile) {
|
|
5132
|
-
setBigUint64(rawLocalExtraFieldZip64View, rawLocalExtraFieldZip64Offset, BigInt(uncompressedSize));
|
|
5133
|
-
rawLocalExtraFieldZip64Offset += 8;
|
|
5134
|
-
}
|
|
5135
|
-
if (rawLocalExtraFieldZip64Offset == 4) {
|
|
5136
|
-
rawLocalExtraFieldZip64 = new Uint8Array();
|
|
5562
|
+
extraFieldZip64.uint64(uncompressedSize);
|
|
5563
|
+
if (uncompressedFile) {
|
|
5564
|
+
const encryptionOverhead = encrypted ? (zipCrypto ? 12 : 16 + encryptionStrength * 4) : 0;
|
|
5565
|
+
extraFieldZip64.uint64(passThrough ? 0 : uncompressedSize + encryptionOverhead);
|
|
5137
5566
|
}
|
|
5138
5567
|
}
|
|
5139
5568
|
} else {
|
|
@@ -5141,11 +5570,11 @@
|
|
|
5141
5570
|
}
|
|
5142
5571
|
let rawExtraFieldAES;
|
|
5143
5572
|
if (encrypted && !zipCrypto) {
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5573
|
+
const extraFieldAES = createRecordWriter(getLength(EXTRAFIELD_DATA_AES) + 2);
|
|
5574
|
+
extraFieldAES.uint16(EXTRAFIELD_TYPE_AES);
|
|
5575
|
+
extraFieldAES.bytes(EXTRAFIELD_DATA_AES);
|
|
5576
|
+
rawExtraFieldAES = extraFieldAES.array;
|
|
5577
|
+
rawExtraFieldAES[8] = encryptionStrength;
|
|
5149
5578
|
} else {
|
|
5150
5579
|
rawExtraFieldAES = new Uint8Array();
|
|
5151
5580
|
}
|
|
@@ -5153,33 +5582,38 @@
|
|
|
5153
5582
|
let rawExtraFieldExtendedTimestamp;
|
|
5154
5583
|
let extraFieldExtendedTimestampFlag;
|
|
5155
5584
|
if (extendedTimestamp) {
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5585
|
+
const lastModTimeUnix = getTimeUnix(lastModDate);
|
|
5586
|
+
if (inUnixTimeRange(lastModTimeUnix)) {
|
|
5587
|
+
const extraFieldTimestampLength = 9 + (lastAccessDate ? 4 : 0) + (creationDate ? 4 : 0);
|
|
5588
|
+
const extraFieldTimestamp = createRecordWriter(extraFieldTimestampLength);
|
|
5589
|
+
extraFieldExtendedTimestampFlag = 0x1 + (lastAccessDate ? 0x2 : 0) + (creationDate ? 0x4 : 0);
|
|
5590
|
+
extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
|
|
5591
|
+
extraFieldTimestamp.uint16(extraFieldTimestampLength - 4);
|
|
5592
|
+
extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
|
|
5593
|
+
extraFieldTimestamp.uint32(lastModTimeUnix);
|
|
5594
|
+
if (lastAccessDate) {
|
|
5595
|
+
extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(lastAccessDate)));
|
|
5596
|
+
}
|
|
5597
|
+
if (creationDate) {
|
|
5598
|
+
extraFieldTimestamp.uint32(clampUnixTime(getTimeUnix(creationDate)));
|
|
5599
|
+
}
|
|
5600
|
+
rawExtraFieldExtendedTimestamp = extraFieldTimestamp.array;
|
|
5601
|
+
} else {
|
|
5602
|
+
// the date cannot be stored as a 32-bit time_t; the NTFS field replaces it
|
|
5603
|
+
rawExtraFieldExtendedTimestamp = new Uint8Array();
|
|
5171
5604
|
}
|
|
5172
5605
|
try {
|
|
5173
|
-
rawExtraFieldNTFS = new Uint8Array(36);
|
|
5174
|
-
const extraFieldNTFSView = getDataView(rawExtraFieldNTFS);
|
|
5175
5606
|
const lastModTimeNTFS = getTimeNTFS(lastModDate);
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5607
|
+
const extraFieldNTFS = createRecordWriter(36);
|
|
5608
|
+
extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS);
|
|
5609
|
+
extraFieldNTFS.uint16(32);
|
|
5610
|
+
extraFieldNTFS.skip(4);
|
|
5611
|
+
extraFieldNTFS.uint16(EXTRAFIELD_TYPE_NTFS_TAG1);
|
|
5612
|
+
extraFieldNTFS.uint16(24);
|
|
5613
|
+
extraFieldNTFS.uint64(lastModTimeNTFS);
|
|
5614
|
+
extraFieldNTFS.uint64(getTimeNTFS(lastAccessDate) || lastModTimeNTFS);
|
|
5615
|
+
extraFieldNTFS.uint64(getTimeNTFS(creationDate) || lastModTimeNTFS);
|
|
5616
|
+
rawExtraFieldNTFS = extraFieldNTFS.array;
|
|
5183
5617
|
} catch {
|
|
5184
5618
|
rawExtraFieldNTFS = new Uint8Array();
|
|
5185
5619
|
}
|
|
@@ -5188,41 +5622,27 @@
|
|
|
5188
5622
|
}
|
|
5189
5623
|
let rawExtraFieldUnix;
|
|
5190
5624
|
try {
|
|
5191
|
-
const { uid, gid,
|
|
5192
|
-
if (unixExtraFieldType && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE
|
|
5625
|
+
const { uid, gid, unixExtraFieldType } = options;
|
|
5626
|
+
if (unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
|
|
5193
5627
|
const uidBytes = packUnixId(uid);
|
|
5194
5628
|
const gidBytes = packUnixId(gid);
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
rawExtraFieldUnix = new Uint8Array(4 + payloadLength);
|
|
5213
|
-
const rawExtraFieldUnixView = getDataView(rawExtraFieldUnix);
|
|
5214
|
-
setUint16(rawExtraFieldUnixView, 0, unixExtraFieldType == INFOZIP_EXTRA_FIELD_TYPE ? EXTRAFIELD_TYPE_INFOZIP : EXTRAFIELD_TYPE_UNIX);
|
|
5215
|
-
setUint16(rawExtraFieldUnixView, 2, payloadLength);
|
|
5216
|
-
setUint8(rawExtraFieldUnixView, 4, 1);
|
|
5217
|
-
setUint8(rawExtraFieldUnixView, 5, uidBytes.length);
|
|
5218
|
-
let offset = 6;
|
|
5219
|
-
arraySet(rawExtraFieldUnix, uidBytes, offset);
|
|
5220
|
-
offset += uidBytes.length;
|
|
5221
|
-
setUint8(rawExtraFieldUnixView, offset, gidBytes.length);
|
|
5222
|
-
offset++;
|
|
5223
|
-
arraySet(rawExtraFieldUnix, gidBytes, offset);
|
|
5224
|
-
offset += gidBytes.length;
|
|
5225
|
-
arraySet(rawExtraFieldUnix, modeArray, offset);
|
|
5629
|
+
const payloadLength = 3 + uidBytes.length + gidBytes.length;
|
|
5630
|
+
const extraFieldUnix = createRecordWriter(4 + payloadLength);
|
|
5631
|
+
extraFieldUnix.uint16(EXTRAFIELD_TYPE_INFOZIP);
|
|
5632
|
+
extraFieldUnix.uint16(payloadLength);
|
|
5633
|
+
extraFieldUnix.uint8(1);
|
|
5634
|
+
extraFieldUnix.uint8(uidBytes.length);
|
|
5635
|
+
extraFieldUnix.bytes(uidBytes);
|
|
5636
|
+
extraFieldUnix.uint8(gidBytes.length);
|
|
5637
|
+
extraFieldUnix.bytes(gidBytes);
|
|
5638
|
+
rawExtraFieldUnix = extraFieldUnix.array;
|
|
5639
|
+
} else if (unixExtraFieldType == UNIX_EXTRA_FIELD_TYPE && (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE)) {
|
|
5640
|
+
const extraFieldUnix = createRecordWriter(8);
|
|
5641
|
+
extraFieldUnix.uint16(EXTRAFIELD_TYPE_UNIX);
|
|
5642
|
+
extraFieldUnix.uint16(4);
|
|
5643
|
+
extraFieldUnix.uint16((uid === UNDEFINED_VALUE ? 0 : uid) & MAX_16_BITS);
|
|
5644
|
+
extraFieldUnix.uint16((gid === UNDEFINED_VALUE ? 0 : gid) & MAX_16_BITS);
|
|
5645
|
+
rawExtraFieldUnix = extraFieldUnix.array;
|
|
5226
5646
|
} else {
|
|
5227
5647
|
rawExtraFieldUnix = new Uint8Array();
|
|
5228
5648
|
}
|
|
@@ -5240,8 +5660,11 @@
|
|
|
5240
5660
|
rawExtraFieldAES[9] = compressionMethod;
|
|
5241
5661
|
compressionMethod = COMPRESSION_METHOD_AES;
|
|
5242
5662
|
}
|
|
5243
|
-
const localExtraFieldZip64Length =
|
|
5663
|
+
const localExtraFieldZip64Length = writeLocalExtraFieldZip64 ? getLength(rawLocalExtraFieldZip64) : 0;
|
|
5244
5664
|
const extraFieldLength = localExtraFieldZip64Length + getLength(rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
|
|
5665
|
+
if (extraFieldLength > MAX_16_BITS) {
|
|
5666
|
+
throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
|
|
5667
|
+
}
|
|
5245
5668
|
const {
|
|
5246
5669
|
headerArray,
|
|
5247
5670
|
headerView,
|
|
@@ -5257,29 +5680,27 @@
|
|
|
5257
5680
|
zip64UncompressedSize,
|
|
5258
5681
|
extraFieldLength
|
|
5259
5682
|
});
|
|
5260
|
-
|
|
5261
|
-
const localHeaderArray =
|
|
5683
|
+
const localHeader = createRecordWriter(HEADER_SIZE + getLength(rawFilename) + extraFieldLength);
|
|
5684
|
+
const localHeaderArray = localHeader.array;
|
|
5262
5685
|
const localHeaderView = getDataView(localHeaderArray);
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
localHeaderOffset += getLength(rawExtraFieldExtendedTimestamp);
|
|
5275
|
-
arraySet(localHeaderArray, rawExtraFieldNTFS, localHeaderOffset);
|
|
5276
|
-
localHeaderOffset += getLength(rawExtraFieldNTFS);
|
|
5277
|
-
arraySet(localHeaderArray, rawExtraFieldUnix, localHeaderOffset);
|
|
5278
|
-
localHeaderOffset += getLength(rawExtraFieldUnix);
|
|
5279
|
-
arraySet(localHeaderArray, rawExtraField, localHeaderOffset);
|
|
5686
|
+
localHeader.uint32(LOCAL_FILE_HEADER_SIGNATURE);
|
|
5687
|
+
localHeader.bytes(headerArray);
|
|
5688
|
+
localHeader.bytes(rawFilename);
|
|
5689
|
+
if (writeLocalExtraFieldZip64) {
|
|
5690
|
+
localHeader.bytes(rawLocalExtraFieldZip64);
|
|
5691
|
+
}
|
|
5692
|
+
localHeader.bytes(rawExtraFieldAES);
|
|
5693
|
+
localHeader.bytes(rawExtraFieldExtendedTimestamp);
|
|
5694
|
+
localHeader.bytes(rawExtraFieldNTFS);
|
|
5695
|
+
localHeader.bytes(rawExtraFieldUnix);
|
|
5696
|
+
localHeader.bytes(rawExtraField);
|
|
5280
5697
|
if (dataDescriptor) {
|
|
5281
|
-
|
|
5282
|
-
|
|
5698
|
+
if (!zip64CompressedSize) {
|
|
5699
|
+
setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
|
|
5700
|
+
}
|
|
5701
|
+
if (!zip64UncompressedSize) {
|
|
5702
|
+
setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, 0);
|
|
5703
|
+
}
|
|
5283
5704
|
}
|
|
5284
5705
|
return {
|
|
5285
5706
|
localHeaderArray,
|
|
@@ -5306,7 +5727,7 @@
|
|
|
5306
5727
|
function appendExtraFieldUSDZ(entryInfo, zipWriterOffset) {
|
|
5307
5728
|
const { headerInfo } = entryInfo;
|
|
5308
5729
|
let { localHeaderArray, extraFieldLength } = headerInfo;
|
|
5309
|
-
let localHeaderArrayView
|
|
5730
|
+
let localHeaderArrayView;
|
|
5310
5731
|
let extraBytesLength = 64 - ((zipWriterOffset + getLength(localHeaderArray)) % 64);
|
|
5311
5732
|
if (extraBytesLength < 4) {
|
|
5312
5733
|
extraBytesLength += 64;
|
|
@@ -5314,13 +5735,14 @@
|
|
|
5314
5735
|
const rawExtraFieldUSDZ = new Uint8Array(extraBytesLength);
|
|
5315
5736
|
const extraFieldUSDZView = getDataView(rawExtraFieldUSDZ);
|
|
5316
5737
|
setUint16(extraFieldUSDZView, 0, EXTRAFIELD_TYPE_USDZ);
|
|
5317
|
-
setUint16(extraFieldUSDZView, 2, extraBytesLength -
|
|
5738
|
+
setUint16(extraFieldUSDZView, 2, extraBytesLength - 4);
|
|
5318
5739
|
const previousLocalHeaderArray = localHeaderArray;
|
|
5319
5740
|
headerInfo.localHeaderArray = localHeaderArray = new Uint8Array(getLength(previousLocalHeaderArray) + extraBytesLength);
|
|
5320
5741
|
arraySet(localHeaderArray, previousLocalHeaderArray);
|
|
5321
5742
|
arraySet(localHeaderArray, rawExtraFieldUSDZ, getLength(previousLocalHeaderArray));
|
|
5322
5743
|
localHeaderArrayView = getDataView(localHeaderArray);
|
|
5323
5744
|
setUint16(localHeaderArrayView, 28, extraFieldLength + extraBytesLength);
|
|
5745
|
+
headerInfo.localHeaderView = localHeaderArrayView;
|
|
5324
5746
|
entryInfo.metadataSize += extraBytesLength;
|
|
5325
5747
|
}
|
|
5326
5748
|
|
|
@@ -5443,37 +5865,32 @@
|
|
|
5443
5865
|
}, localHeaderView, { dataDescriptor }) {
|
|
5444
5866
|
if (!dataDescriptor) {
|
|
5445
5867
|
if (!encrypted) {
|
|
5446
|
-
setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE +
|
|
5868
|
+
setUint32(localHeaderView, HEADER_OFFSET_SIGNATURE + LOCAL_HEADER_COMMON_OFFSET, signature);
|
|
5447
5869
|
}
|
|
5448
5870
|
if (!zip64CompressedSize) {
|
|
5449
|
-
setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE +
|
|
5871
|
+
setUint32(localHeaderView, HEADER_OFFSET_COMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, compressedSize);
|
|
5450
5872
|
}
|
|
5451
5873
|
if (!zip64UncompressedSize) {
|
|
5452
|
-
setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE +
|
|
5874
|
+
setUint32(localHeaderView, HEADER_OFFSET_UNCOMPRESSED_SIZE + LOCAL_HEADER_COMMON_OFFSET, uncompressedSize);
|
|
5453
5875
|
}
|
|
5454
5876
|
}
|
|
5455
5877
|
if (zip64 && localExtraFieldZip64Length) {
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
}
|
|
5461
|
-
if (zip64CompressedSize) {
|
|
5462
|
-
setBigUint64(localHeaderView, localHeaderOffset, BigInt(compressedSize));
|
|
5463
|
-
localHeaderOffset += 8;
|
|
5464
|
-
}
|
|
5878
|
+
// the local zip64 extra field always contains both size slots
|
|
5879
|
+
const localHeaderOffset = HEADER_SIZE + getLength(rawFilename) + 4;
|
|
5880
|
+
setBigUint64(localHeaderView, localHeaderOffset, BigInt(uncompressedSize));
|
|
5881
|
+
setBigUint64(localHeaderView, localHeaderOffset + 8, BigInt(compressedSize));
|
|
5465
5882
|
}
|
|
5466
5883
|
}
|
|
5467
5884
|
|
|
5468
5885
|
|
|
5469
5886
|
async function closeFile(zipWriter, comment, options) {
|
|
5470
|
-
const
|
|
5471
|
-
const {
|
|
5472
|
-
|
|
5473
|
-
|
|
5887
|
+
const directoryDataLength = createDirectoryRecords(zipWriter.files);
|
|
5888
|
+
const { cdStartDiskNumber, cdStartDiskOffset } = await writeDirectoryRecords(zipWriter, directoryDataLength, options);
|
|
5889
|
+
await writeEndOfDirectoryRecord(zipWriter, comment, options, { cdStartDiskNumber, cdStartDiskOffset, directoryDataLength });
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5892
|
+
function createDirectoryRecords(files) {
|
|
5474
5893
|
let directoryDataLength = 0;
|
|
5475
|
-
let directoryOffset = zipWriter.offset - diskOffset;
|
|
5476
|
-
let filesLength = files.size;
|
|
5477
5894
|
for (const [, fileEntry] of files) {
|
|
5478
5895
|
const {
|
|
5479
5896
|
rawFilename,
|
|
@@ -5490,24 +5907,27 @@
|
|
|
5490
5907
|
uncompressedSize,
|
|
5491
5908
|
compressedSize
|
|
5492
5909
|
} = fileEntry;
|
|
5493
|
-
const zip64Offset = fileEntry.offset
|
|
5494
|
-
const zip64DiskNumberStart = fileEntry.diskNumberStart
|
|
5910
|
+
const zip64Offset = fileEntry.offset >= MAX_32_BITS;
|
|
5911
|
+
const zip64DiskNumberStart = fileEntry.diskNumberStart >= MAX_16_BITS;
|
|
5495
5912
|
let rawExtraFieldZip64;
|
|
5496
5913
|
if (zip64Offset || zip64DiskNumberStart || zip64UncompressedSize || zip64CompressedSize) {
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
if (
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
if (
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
if (zip64DiskNumberStart) {
|
|
5914
|
+
const length = 4 + (zip64UncompressedSize ? 8 : 0) + (zip64CompressedSize ? 8 : 0) + (zip64Offset ? 8 : 0) + (zip64DiskNumberStart ? 4 : 0);
|
|
5915
|
+
const extraFieldZip64 = createRecordWriter(length);
|
|
5916
|
+
extraFieldZip64.uint16(EXTRAFIELD_TYPE_ZIP64);
|
|
5917
|
+
extraFieldZip64.uint16(length - 4);
|
|
5918
|
+
if (zip64UncompressedSize) {
|
|
5919
|
+
extraFieldZip64.uint64(uncompressedSize);
|
|
5920
|
+
}
|
|
5921
|
+
if (zip64CompressedSize) {
|
|
5922
|
+
extraFieldZip64.uint64(compressedSize);
|
|
5923
|
+
}
|
|
5924
|
+
if (zip64Offset) {
|
|
5925
|
+
extraFieldZip64.uint64(fileEntry.offset);
|
|
5926
|
+
}
|
|
5927
|
+
if (zip64DiskNumberStart) {
|
|
5928
|
+
extraFieldZip64.uint32(fileEntry.diskNumberStart);
|
|
5929
|
+
}
|
|
5930
|
+
rawExtraFieldZip64 = extraFieldZip64.array;
|
|
5511
5931
|
} else {
|
|
5512
5932
|
rawExtraFieldZip64 = new Uint8Array();
|
|
5513
5933
|
}
|
|
@@ -5515,32 +5935,41 @@
|
|
|
5515
5935
|
fileEntry.zip64Offset = zip64Offset;
|
|
5516
5936
|
fileEntry.zip64DiskNumberStart = zip64DiskNumberStart;
|
|
5517
5937
|
let rawExtraFieldTimestamp;
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
const
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5938
|
+
const lastModTimeUnix = getTimeUnix(lastModDate);
|
|
5939
|
+
if (extendedTimestamp && inUnixTimeRange(lastModTimeUnix)) {
|
|
5940
|
+
const extraFieldTimestamp = createRecordWriter(9);
|
|
5941
|
+
extraFieldTimestamp.uint16(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
|
|
5942
|
+
extraFieldTimestamp.uint16(5);
|
|
5943
|
+
extraFieldTimestamp.uint8(extraFieldExtendedTimestampFlag);
|
|
5944
|
+
extraFieldTimestamp.uint32(lastModTimeUnix);
|
|
5945
|
+
rawExtraFieldTimestamp = extraFieldTimestamp.array;
|
|
5525
5946
|
} else {
|
|
5526
5947
|
rawExtraFieldTimestamp = new Uint8Array();
|
|
5527
5948
|
}
|
|
5528
5949
|
fileEntry.rawExtraFieldExtendedTimestamp = rawExtraFieldTimestamp;
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5950
|
+
const extraFieldLength = getLength(
|
|
5951
|
+
rawExtraFieldZip64,
|
|
5952
|
+
rawExtraFieldAES,
|
|
5953
|
+
rawExtraFieldNTFS,
|
|
5954
|
+
rawExtraFieldUnix,
|
|
5955
|
+
rawExtraFieldTimestamp,
|
|
5956
|
+
rawExtraField);
|
|
5957
|
+
if (extraFieldLength > MAX_16_BITS) {
|
|
5958
|
+
throw new Error(ERR_INVALID_EXTRAFIELD_DATA);
|
|
5959
|
+
}
|
|
5960
|
+
directoryDataLength += CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
|
|
5539
5961
|
}
|
|
5962
|
+
return directoryDataLength;
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5965
|
+
async function writeDirectoryRecords(zipWriter, directoryDataLength, options) {
|
|
5966
|
+
const { files, writer } = zipWriter;
|
|
5540
5967
|
const directoryArray = new Uint8Array(directoryDataLength);
|
|
5541
|
-
const directoryView = getDataView(directoryArray);
|
|
5542
5968
|
await initStream(writer);
|
|
5969
|
+
let offset = 0;
|
|
5543
5970
|
let directoryDiskOffset = 0;
|
|
5971
|
+
let cdStartDiskNumber = writer.diskNumber;
|
|
5972
|
+
let cdStartDiskOffset = writer.diskOffset;
|
|
5544
5973
|
for (const [indexFileEntry, fileEntry] of Array.from(files.values()).entries()) {
|
|
5545
5974
|
const {
|
|
5546
5975
|
offset: fileEntryOffset,
|
|
@@ -5566,52 +5995,46 @@
|
|
|
5566
5995
|
compressedSize
|
|
5567
5996
|
} = fileEntry;
|
|
5568
5997
|
const extraFieldLength = getLength(rawExtraFieldZip64, rawExtraFieldAES, rawExtraFieldExtendedTimestamp, rawExtraFieldNTFS, rawExtraFieldUnix, rawExtraField);
|
|
5569
|
-
|
|
5570
|
-
|
|
5998
|
+
const directoryRecordLength = CENTRAL_FILE_HEADER_LENGTH + getLength(rawFilename, rawComment) + extraFieldLength;
|
|
5999
|
+
if (offset + directoryRecordLength - directoryDiskOffset > writer.availableSize) {
|
|
6000
|
+
await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
|
|
6001
|
+
directoryDiskOffset = offset;
|
|
6002
|
+
writer.availableSize = 0;
|
|
6003
|
+
await writeData(writer, new Uint8Array());
|
|
6004
|
+
}
|
|
6005
|
+
if (indexFileEntry == 0) {
|
|
6006
|
+
cdStartDiskNumber = writer.diskNumber;
|
|
6007
|
+
cdStartDiskOffset = writer.diskOffset;
|
|
6008
|
+
}
|
|
5571
6009
|
if (!zip64UncompressedSize) {
|
|
5572
6010
|
setUint32(headerView, HEADER_OFFSET_UNCOMPRESSED_SIZE, uncompressedSize);
|
|
5573
6011
|
}
|
|
5574
6012
|
if (!zip64CompressedSize) {
|
|
5575
6013
|
setUint32(headerView, HEADER_OFFSET_COMPRESSED_SIZE, compressedSize);
|
|
5576
6014
|
}
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
directoryOffset += getLength(rawExtraFieldExtendedTimestamp);
|
|
5601
|
-
arraySet(directoryArray, rawExtraFieldNTFS, directoryOffset);
|
|
5602
|
-
directoryOffset += getLength(rawExtraFieldNTFS);
|
|
5603
|
-
arraySet(directoryArray, rawExtraFieldUnix, directoryOffset);
|
|
5604
|
-
directoryOffset += getLength(rawExtraFieldUnix);
|
|
5605
|
-
arraySet(directoryArray, rawExtraField, directoryOffset);
|
|
5606
|
-
directoryOffset += getLength(rawExtraField);
|
|
5607
|
-
arraySet(directoryArray, rawComment, directoryOffset);
|
|
5608
|
-
directoryOffset += getLength(rawComment);
|
|
5609
|
-
if (offset - directoryDiskOffset > writer.availableSize) {
|
|
5610
|
-
writer.availableSize = 0;
|
|
5611
|
-
await writeData(writer, directoryArray.slice(directoryDiskOffset, offset));
|
|
5612
|
-
directoryDiskOffset = offset;
|
|
5613
|
-
}
|
|
5614
|
-
offset = directoryOffset;
|
|
6015
|
+
if ((zip64Offset || zip64DiskNumberStart) && fileEntry.version < VERSION_ZIP64) {
|
|
6016
|
+
setUint16(headerView, HEADER_OFFSET_VERSION, VERSION_ZIP64);
|
|
6017
|
+
}
|
|
6018
|
+
const directoryRecord = createRecordWriter(directoryRecordLength);
|
|
6019
|
+
directoryRecord.uint32(CENTRAL_FILE_HEADER_SIGNATURE);
|
|
6020
|
+
directoryRecord.uint16(versionMadeBy);
|
|
6021
|
+
directoryRecord.bytes(headerArray.subarray(0, HEADER_SIZE - 4 - 2));
|
|
6022
|
+
directoryRecord.uint16(extraFieldLength);
|
|
6023
|
+
directoryRecord.uint16(getLength(rawComment));
|
|
6024
|
+
directoryRecord.uint16(zip64DiskNumberStart ? MAX_16_BITS : diskNumberStart);
|
|
6025
|
+
directoryRecord.uint16(internalFileAttributes);
|
|
6026
|
+
directoryRecord.uint32(externalFileAttributes);
|
|
6027
|
+
directoryRecord.uint32(zip64Offset ? MAX_32_BITS : fileEntryOffset);
|
|
6028
|
+
directoryRecord.bytes(rawFilename);
|
|
6029
|
+
directoryRecord.bytes(rawExtraFieldZip64);
|
|
6030
|
+
directoryRecord.bytes(rawExtraFieldAES);
|
|
6031
|
+
directoryRecord.bytes(rawExtraFieldExtendedTimestamp);
|
|
6032
|
+
directoryRecord.bytes(rawExtraFieldNTFS);
|
|
6033
|
+
directoryRecord.bytes(rawExtraFieldUnix);
|
|
6034
|
+
directoryRecord.bytes(rawExtraField);
|
|
6035
|
+
directoryRecord.bytes(rawComment);
|
|
6036
|
+
arraySet(directoryArray, directoryRecord.array, offset);
|
|
6037
|
+
offset += directoryRecordLength;
|
|
5615
6038
|
if (options.onprogress) {
|
|
5616
6039
|
try {
|
|
5617
6040
|
await options.onprogress(indexFileEntry + 1, files.size, new Entry(fileEntry));
|
|
@@ -5621,36 +6044,53 @@
|
|
|
5621
6044
|
}
|
|
5622
6045
|
}
|
|
5623
6046
|
await writeData(writer, directoryDiskOffset ? directoryArray.slice(directoryDiskOffset) : directoryArray);
|
|
6047
|
+
return { cdStartDiskNumber, cdStartDiskOffset };
|
|
6048
|
+
}
|
|
6049
|
+
|
|
6050
|
+
async function writeEndOfDirectoryRecord(zipWriter, comment, options, cdInfo) {
|
|
6051
|
+
const { writer } = zipWriter;
|
|
6052
|
+
const { cdStartDiskNumber, cdStartDiskOffset } = cdInfo;
|
|
6053
|
+
let { directoryDataLength } = cdInfo;
|
|
6054
|
+
let filesLength = zipWriter.files.size;
|
|
6055
|
+
let diskNumber = cdStartDiskNumber;
|
|
6056
|
+
let directoryOffset = zipWriter.offset - cdStartDiskOffset - (cdStartDiskNumber ? zipWriter.initialOffset : 0);
|
|
5624
6057
|
let lastDiskNumber = writer.diskNumber;
|
|
5625
|
-
|
|
5626
|
-
if (availableSize < END_OF_CENTRAL_DIR_LENGTH) {
|
|
6058
|
+
if (writer.availableSize < END_OF_CENTRAL_DIR_LENGTH) {
|
|
5627
6059
|
lastDiskNumber++;
|
|
5628
6060
|
}
|
|
5629
6061
|
let zip64 = getOptionValue(zipWriter, options, PROPERTY_NAME_ZIP64);
|
|
5630
|
-
if (directoryOffset
|
|
6062
|
+
if (directoryOffset >= MAX_32_BITS || directoryDataLength >= MAX_32_BITS || filesLength >= MAX_16_BITS || lastDiskNumber >= MAX_16_BITS) {
|
|
5631
6063
|
if (zip64 === false) {
|
|
5632
6064
|
throw new Error(ERR_UNSUPPORTED_FORMAT);
|
|
5633
6065
|
} else {
|
|
5634
6066
|
zip64 = true;
|
|
5635
6067
|
}
|
|
5636
6068
|
}
|
|
5637
|
-
const
|
|
5638
|
-
|
|
5639
|
-
|
|
6069
|
+
const commentLength = getLength(comment);
|
|
6070
|
+
if (commentLength > MAX_16_BITS) {
|
|
6071
|
+
throw new Error(ERR_INVALID_COMMENT);
|
|
6072
|
+
}
|
|
6073
|
+
const endOfdirectoryRecord = createRecordWriter(zip64 ? ZIP64_END_OF_CENTRAL_DIR_TOTAL_LENGTH : END_OF_CENTRAL_DIR_LENGTH);
|
|
6074
|
+
if (getLength(endOfdirectoryRecord.array) + commentLength > writer.availableSize) {
|
|
6075
|
+
writer.availableSize = 0;
|
|
6076
|
+
await writeData(writer, new Uint8Array());
|
|
6077
|
+
}
|
|
6078
|
+
lastDiskNumber = writer.diskNumber;
|
|
5640
6079
|
if (zip64) {
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
6080
|
+
endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_SIGNATURE);
|
|
6081
|
+
endOfdirectoryRecord.uint64(44);
|
|
6082
|
+
endOfdirectoryRecord.uint16(45);
|
|
6083
|
+
endOfdirectoryRecord.uint16(45);
|
|
6084
|
+
endOfdirectoryRecord.uint32(lastDiskNumber);
|
|
6085
|
+
endOfdirectoryRecord.uint32(diskNumber);
|
|
6086
|
+
endOfdirectoryRecord.uint64(filesLength);
|
|
6087
|
+
endOfdirectoryRecord.uint64(filesLength);
|
|
6088
|
+
endOfdirectoryRecord.uint64(directoryDataLength);
|
|
6089
|
+
endOfdirectoryRecord.uint64(directoryOffset);
|
|
6090
|
+
endOfdirectoryRecord.uint32(ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE);
|
|
6091
|
+
endOfdirectoryRecord.uint32(lastDiskNumber);
|
|
6092
|
+
endOfdirectoryRecord.uint64(BigInt(zipWriter.offset) + BigInt(directoryDataLength) - BigInt(writer.diskOffset) - BigInt(writer.diskNumber ? zipWriter.initialOffset : 0));
|
|
6093
|
+
endOfdirectoryRecord.uint32(lastDiskNumber + 1);
|
|
5654
6094
|
const supportZip64SplitFile = getOptionValue(zipWriter, options, OPTION_SUPPORT_ZIP64_SPLIT_FILE, true);
|
|
5655
6095
|
if (supportZip64SplitFile) {
|
|
5656
6096
|
lastDiskNumber = MAX_16_BITS;
|
|
@@ -5659,29 +6099,40 @@
|
|
|
5659
6099
|
filesLength = MAX_16_BITS;
|
|
5660
6100
|
directoryOffset = MAX_32_BITS;
|
|
5661
6101
|
directoryDataLength = MAX_32_BITS;
|
|
5662
|
-
offset += ZIP64_END_OF_CENTRAL_DIR_LENGTH + ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH;
|
|
5663
|
-
}
|
|
5664
|
-
setUint32(endOfdirectoryView, offset, END_OF_CENTRAL_DIR_SIGNATURE);
|
|
5665
|
-
setUint16(endOfdirectoryView, offset + 4, lastDiskNumber);
|
|
5666
|
-
setUint16(endOfdirectoryView, offset + 6, diskNumber);
|
|
5667
|
-
setUint16(endOfdirectoryView, offset + 8, filesLength);
|
|
5668
|
-
setUint16(endOfdirectoryView, offset + 10, filesLength);
|
|
5669
|
-
setUint32(endOfdirectoryView, offset + 12, directoryDataLength);
|
|
5670
|
-
setUint32(endOfdirectoryView, offset + 16, directoryOffset);
|
|
5671
|
-
const commentLength = getLength(comment);
|
|
5672
|
-
if (commentLength) {
|
|
5673
|
-
if (commentLength <= MAX_16_BITS) {
|
|
5674
|
-
setUint16(endOfdirectoryView, offset + 20, commentLength);
|
|
5675
|
-
} else {
|
|
5676
|
-
throw new Error(ERR_INVALID_COMMENT);
|
|
5677
|
-
}
|
|
5678
6102
|
}
|
|
5679
|
-
|
|
6103
|
+
endOfdirectoryRecord.uint32(END_OF_CENTRAL_DIR_SIGNATURE);
|
|
6104
|
+
endOfdirectoryRecord.uint16(lastDiskNumber);
|
|
6105
|
+
endOfdirectoryRecord.uint16(diskNumber);
|
|
6106
|
+
endOfdirectoryRecord.uint16(filesLength);
|
|
6107
|
+
endOfdirectoryRecord.uint16(filesLength);
|
|
6108
|
+
endOfdirectoryRecord.uint32(directoryDataLength);
|
|
6109
|
+
endOfdirectoryRecord.uint32(directoryOffset);
|
|
6110
|
+
endOfdirectoryRecord.uint16(commentLength);
|
|
6111
|
+
await writeData(writer, endOfdirectoryRecord.array);
|
|
5680
6112
|
if (commentLength) {
|
|
5681
6113
|
await writeData(writer, comment);
|
|
5682
6114
|
}
|
|
5683
6115
|
}
|
|
5684
6116
|
|
|
6117
|
+
function createRecordWriter(length) {
|
|
6118
|
+
const array = new Uint8Array(length);
|
|
6119
|
+
const view = getDataView(array);
|
|
6120
|
+
let offset = 0;
|
|
6121
|
+
return {
|
|
6122
|
+
array,
|
|
6123
|
+
uint8: value => { setUint8(view, offset, value); offset += 1; },
|
|
6124
|
+
uint16: value => { setUint16(view, offset, value); offset += 2; },
|
|
6125
|
+
uint32: value => { setUint32(view, offset, value); offset += 4; },
|
|
6126
|
+
uint64: value => { setBigUint64(view, offset, BigInt(value)); offset += 8; },
|
|
6127
|
+
bytes: value => { arraySet(array, value, offset); offset += getLength(value); },
|
|
6128
|
+
skip: count => offset += count
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
|
|
6132
|
+
function getSegmentOffset(zipWriter, writer) {
|
|
6133
|
+
return zipWriter.offset - writer.diskOffset - (writer.diskNumber ? zipWriter.initialOffset : 0);
|
|
6134
|
+
}
|
|
6135
|
+
|
|
5685
6136
|
async function writeData(writer, array) {
|
|
5686
6137
|
const { writable } = writer;
|
|
5687
6138
|
const streamWriter = writable.getWriter();
|
|
@@ -5694,12 +6145,40 @@
|
|
|
5694
6145
|
}
|
|
5695
6146
|
}
|
|
5696
6147
|
|
|
6148
|
+
async function flushBufferedData(readable, writer, signal, onChunkWritten) {
|
|
6149
|
+
const streamWriter = writer.writable.getWriter();
|
|
6150
|
+
try {
|
|
6151
|
+
await readable.pipeTo(new WritableStream({
|
|
6152
|
+
async write(chunk) {
|
|
6153
|
+
await streamWriter.ready;
|
|
6154
|
+
await streamWriter.write(chunk);
|
|
6155
|
+
onChunkWritten(getLength(chunk));
|
|
6156
|
+
}
|
|
6157
|
+
}), { preventClose: true, preventAbort: true, signal });
|
|
6158
|
+
} finally {
|
|
6159
|
+
streamWriter.releaseLock();
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
|
|
5697
6163
|
function getTimeNTFS(date) {
|
|
5698
6164
|
if (date) {
|
|
5699
6165
|
return ((BigInt(date.getTime()) + BigInt(11644473600000)) * BigInt(10000));
|
|
5700
6166
|
}
|
|
5701
6167
|
}
|
|
5702
6168
|
|
|
6169
|
+
function getTimeUnix(date) {
|
|
6170
|
+
return Math.floor(date.getTime() / 1000);
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6173
|
+
// the extended timestamp extra field stores dates as signed 32-bit time_t values
|
|
6174
|
+
function inUnixTimeRange(timeUnix) {
|
|
6175
|
+
return timeUnix >= MIN_UNIX_TIME && timeUnix <= MAX_UNIX_TIME;
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6178
|
+
function clampUnixTime(timeUnix) {
|
|
6179
|
+
return Math.min(MAX_UNIX_TIME, Math.max(MIN_UNIX_TIME, timeUnix));
|
|
6180
|
+
}
|
|
6181
|
+
|
|
5703
6182
|
function getOptionValue(zipWriter, options, name, defaultValue) {
|
|
5704
6183
|
const result = options[name] === UNDEFINED_VALUE ? zipWriter.options[name] : options[name];
|
|
5705
6184
|
return result === UNDEFINED_VALUE ? defaultValue : result;
|
|
@@ -5730,7 +6209,7 @@
|
|
|
5730
6209
|
}
|
|
5731
6210
|
|
|
5732
6211
|
function getDataView(array) {
|
|
5733
|
-
return new DataView(array.buffer);
|
|
6212
|
+
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
5734
6213
|
}
|
|
5735
6214
|
|
|
5736
6215
|
function getLength(...arrayLikes) {
|
|
@@ -5746,30 +6225,39 @@
|
|
|
5746
6225
|
uncompressedSize,
|
|
5747
6226
|
compressedSize,
|
|
5748
6227
|
lastModDate,
|
|
6228
|
+
rawLastModDate,
|
|
5749
6229
|
rawFilename,
|
|
5750
6230
|
zip64CompressedSize,
|
|
5751
6231
|
zip64UncompressedSize,
|
|
5752
6232
|
extraFieldLength
|
|
5753
6233
|
}) {
|
|
5754
|
-
const
|
|
6234
|
+
const headerRecord = createRecordWriter(HEADER_SIZE - 4);
|
|
6235
|
+
const headerArray = headerRecord.array;
|
|
5755
6236
|
const headerView = getDataView(headerArray);
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
6237
|
+
headerRecord.uint16(version);
|
|
6238
|
+
headerRecord.uint16(bitFlag);
|
|
6239
|
+
headerRecord.uint16(compressionMethod);
|
|
6240
|
+
if (rawLastModDate === UNDEFINED_VALUE) {
|
|
6241
|
+
const dateArray = new Uint32Array(1);
|
|
6242
|
+
const dateView = getDataView(dateArray);
|
|
6243
|
+
setUint16(dateView, 0, (((lastModDate.getHours() << 6) | lastModDate.getMinutes()) << 5) | lastModDate.getSeconds() / 2);
|
|
6244
|
+
setUint16(dateView, 2, ((((lastModDate.getFullYear() - 1980) << 4) | (lastModDate.getMonth() + 1)) << 5) | lastModDate.getDate());
|
|
6245
|
+
rawLastModDate = dateArray[0];
|
|
6246
|
+
}
|
|
6247
|
+
headerRecord.uint32(rawLastModDate);
|
|
6248
|
+
headerRecord.skip(4);
|
|
5765
6249
|
if (zip64CompressedSize || compressedSize !== UNDEFINED_VALUE) {
|
|
5766
|
-
|
|
6250
|
+
headerRecord.uint32(zip64CompressedSize ? MAX_32_BITS : compressedSize);
|
|
6251
|
+
} else {
|
|
6252
|
+
headerRecord.skip(4);
|
|
5767
6253
|
}
|
|
5768
6254
|
if (zip64UncompressedSize || uncompressedSize !== UNDEFINED_VALUE) {
|
|
5769
|
-
|
|
6255
|
+
headerRecord.uint32(zip64UncompressedSize ? MAX_32_BITS : uncompressedSize);
|
|
6256
|
+
} else {
|
|
6257
|
+
headerRecord.skip(4);
|
|
5770
6258
|
}
|
|
5771
|
-
|
|
5772
|
-
|
|
6259
|
+
headerRecord.uint16(getLength(rawFilename));
|
|
6260
|
+
headerRecord.uint16(extraFieldLength);
|
|
5773
6261
|
return {
|
|
5774
6262
|
headerArray,
|
|
5775
6263
|
headerView,
|
|
@@ -5831,12 +6319,15 @@
|
|
|
5831
6319
|
*/
|
|
5832
6320
|
|
|
5833
6321
|
|
|
6322
|
+
const ERR_ENTRY_EXISTS = "Entry filename already exists";
|
|
6323
|
+
const ERR_READABLE_CONSUMED = "Readable stream already consumed";
|
|
6324
|
+
|
|
5834
6325
|
class ZipEntry {
|
|
5835
6326
|
|
|
5836
6327
|
constructor(fs, name, params, parent) {
|
|
5837
6328
|
const zipEntry = this;
|
|
5838
6329
|
if (fs.root && parent && parent.getChildByName(name)) {
|
|
5839
|
-
throw new Error(
|
|
6330
|
+
throw new Error(ERR_ENTRY_EXISTS);
|
|
5840
6331
|
}
|
|
5841
6332
|
if (!params) {
|
|
5842
6333
|
params = {};
|
|
@@ -5846,13 +6337,17 @@
|
|
|
5846
6337
|
name,
|
|
5847
6338
|
data: params.data,
|
|
5848
6339
|
options: params.options,
|
|
5849
|
-
id: fs.
|
|
6340
|
+
id: fs.entryIdCounter++,
|
|
5850
6341
|
parent,
|
|
5851
6342
|
children: [],
|
|
5852
6343
|
uncompressedSize: params.uncompressedSize || 0,
|
|
5853
6344
|
passThrough: params.passThrough
|
|
5854
6345
|
});
|
|
5855
|
-
|
|
6346
|
+
// detached entries (e.g. clones) are not registered until they are attached,
|
|
6347
|
+
// otherwise they could never be garbage collected
|
|
6348
|
+
if (parent || !fs.root) {
|
|
6349
|
+
fs.entries[zipEntry.id] = zipEntry;
|
|
6350
|
+
}
|
|
5856
6351
|
if (parent) {
|
|
5857
6352
|
zipEntry.parent.children.push(zipEntry);
|
|
5858
6353
|
}
|
|
@@ -5890,7 +6385,7 @@
|
|
|
5890
6385
|
rename(name) {
|
|
5891
6386
|
const parent = this.parent;
|
|
5892
6387
|
if (parent && parent.getChildByName(name)) {
|
|
5893
|
-
throw new Error(
|
|
6388
|
+
throw new Error(ERR_ENTRY_EXISTS);
|
|
5894
6389
|
} else {
|
|
5895
6390
|
this.name = name;
|
|
5896
6391
|
}
|
|
@@ -5929,16 +6424,17 @@
|
|
|
5929
6424
|
}
|
|
5930
6425
|
|
|
5931
6426
|
isPasswordProtected() {
|
|
5932
|
-
return this.data.encrypted;
|
|
6427
|
+
return Boolean(this.data && this.data.encrypted);
|
|
5933
6428
|
}
|
|
5934
6429
|
|
|
5935
6430
|
async checkPassword(password, options = {}) {
|
|
5936
6431
|
const zipEntry = this;
|
|
5937
6432
|
if (zipEntry.isPasswordProtected()) {
|
|
5938
|
-
options.password = password;
|
|
5939
|
-
options.checkPasswordOnly = true;
|
|
5940
6433
|
try {
|
|
5941
|
-
await zipEntry.data.getData(null, options
|
|
6434
|
+
await zipEntry.data.getData(null, Object.assign({}, options, {
|
|
6435
|
+
password,
|
|
6436
|
+
checkPasswordOnly: true
|
|
6437
|
+
}));
|
|
5942
6438
|
return true;
|
|
5943
6439
|
} catch (error) {
|
|
5944
6440
|
if (error.message == ERR_INVALID_PASSWORD) {
|
|
@@ -5972,8 +6468,15 @@
|
|
|
5972
6468
|
return this.getData({ writable }, options);
|
|
5973
6469
|
}
|
|
5974
6470
|
|
|
5975
|
-
getArrayBuffer(options) {
|
|
5976
|
-
|
|
6471
|
+
async getArrayBuffer(options) {
|
|
6472
|
+
// this.data may be a string, a Uint8Array, a data URI or a Blob depending on how the entry
|
|
6473
|
+
// was created, so retrieve the bytes through getUint8Array rather than assuming a Blob; this
|
|
6474
|
+
// also applies the options (password, signal, onprogress...) like the other getData() getters
|
|
6475
|
+
const array = await this.getUint8Array(options);
|
|
6476
|
+
// return the backing buffer directly when the array spans it, slicing the exact bytes otherwise
|
|
6477
|
+
return array.byteOffset || array.byteLength != array.buffer.byteLength
|
|
6478
|
+
? array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength)
|
|
6479
|
+
: array.buffer;
|
|
5977
6480
|
}
|
|
5978
6481
|
|
|
5979
6482
|
replaceBlob(blob) {
|
|
@@ -6015,7 +6518,7 @@
|
|
|
6015
6518
|
replaceReadable(readable) {
|
|
6016
6519
|
Object.assign(this, {
|
|
6017
6520
|
data: null,
|
|
6018
|
-
Reader:
|
|
6521
|
+
Reader: getReadableReader(readable),
|
|
6019
6522
|
Writer: null,
|
|
6020
6523
|
reader: null
|
|
6021
6524
|
});
|
|
@@ -6031,7 +6534,7 @@
|
|
|
6031
6534
|
|
|
6032
6535
|
clone(deepClone) {
|
|
6033
6536
|
const zipEntry = this;
|
|
6034
|
-
const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name);
|
|
6537
|
+
const clonedEntry = new ZipDirectoryEntry(zipEntry.fs, zipEntry.name, zipEntry);
|
|
6035
6538
|
if (deepClone) {
|
|
6036
6539
|
clonedEntry.children = zipEntry.children.map(child => {
|
|
6037
6540
|
const childClone = child.clone(deepClone);
|
|
@@ -6105,7 +6608,7 @@
|
|
|
6105
6608
|
|
|
6106
6609
|
addReadable(name, readable, options = {}) {
|
|
6107
6610
|
return addChild(this, name, {
|
|
6108
|
-
Reader:
|
|
6611
|
+
Reader: getReadableReader(readable),
|
|
6109
6612
|
options
|
|
6110
6613
|
});
|
|
6111
6614
|
}
|
|
@@ -6119,6 +6622,7 @@
|
|
|
6119
6622
|
}
|
|
6120
6623
|
|
|
6121
6624
|
addFile(file, options = {}) {
|
|
6625
|
+
options = Object.assign({}, options);
|
|
6122
6626
|
if (!options.lastModDate) {
|
|
6123
6627
|
options.lastModDate = new Date(file.lastModified);
|
|
6124
6628
|
}
|
|
@@ -6185,11 +6689,15 @@
|
|
|
6185
6689
|
try {
|
|
6186
6690
|
const path = entry.filename.split("/");
|
|
6187
6691
|
const name = path.pop();
|
|
6188
|
-
path.forEach(
|
|
6692
|
+
path.forEach(pathPart => {
|
|
6189
6693
|
const previousParent = parent;
|
|
6190
6694
|
parent = parent.getChildByName(pathPart);
|
|
6191
|
-
if (
|
|
6192
|
-
|
|
6695
|
+
if (parent) {
|
|
6696
|
+
if (!parent.directory) {
|
|
6697
|
+
throw new Error(ERR_ENTRY_EXISTS);
|
|
6698
|
+
}
|
|
6699
|
+
} else {
|
|
6700
|
+
parent = new ZipDirectoryEntry(this.fs, pathPart, { data: null }, previousParent);
|
|
6193
6701
|
importedEntries.push(parent);
|
|
6194
6702
|
}
|
|
6195
6703
|
});
|
|
@@ -6200,6 +6708,22 @@
|
|
|
6200
6708
|
uncompressedSize: entry.uncompressedSize,
|
|
6201
6709
|
passThrough: options.passThrough
|
|
6202
6710
|
}));
|
|
6711
|
+
} else {
|
|
6712
|
+
let directoryEntry = parent;
|
|
6713
|
+
if (name) {
|
|
6714
|
+
directoryEntry = parent.getChildByName(name);
|
|
6715
|
+
if (directoryEntry) {
|
|
6716
|
+
if (!directoryEntry.directory) {
|
|
6717
|
+
throw new Error(ERR_ENTRY_EXISTS);
|
|
6718
|
+
}
|
|
6719
|
+
} else {
|
|
6720
|
+
directoryEntry = new ZipDirectoryEntry(this.fs, name, { data: null }, parent);
|
|
6721
|
+
importedEntries.push(directoryEntry);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
if (directoryEntry != this && !directoryEntry.data) {
|
|
6725
|
+
directoryEntry.data = entry;
|
|
6726
|
+
}
|
|
6203
6727
|
}
|
|
6204
6728
|
} catch (error) {
|
|
6205
6729
|
try {
|
|
@@ -6215,14 +6739,15 @@
|
|
|
6215
6739
|
return importedEntries;
|
|
6216
6740
|
}
|
|
6217
6741
|
|
|
6218
|
-
async exportZip(writer, options) {
|
|
6742
|
+
async exportZip(writer, options = {}) {
|
|
6219
6743
|
const zipEntry = this;
|
|
6744
|
+
options = Object.assign({}, options);
|
|
6220
6745
|
if (options.bufferedWrite === UNDEFINED_VALUE) {
|
|
6221
6746
|
options.bufferedWrite = true;
|
|
6222
6747
|
}
|
|
6223
|
-
await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
|
|
6748
|
+
const [readers] = await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
|
|
6224
6749
|
const zipWriter = new ZipWriter(writer, options);
|
|
6225
|
-
await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options);
|
|
6750
|
+
await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options, readers);
|
|
6226
6751
|
await zipWriter.close();
|
|
6227
6752
|
return writer.getData ? writer.getData() : writer.writable;
|
|
6228
6753
|
}
|
|
@@ -6268,7 +6793,15 @@
|
|
|
6268
6793
|
|
|
6269
6794
|
remove(entry) {
|
|
6270
6795
|
detach(entry);
|
|
6271
|
-
|
|
6796
|
+
const removedEntries = [entry];
|
|
6797
|
+
while (removedEntries.length) {
|
|
6798
|
+
const removedEntry = removedEntries.pop();
|
|
6799
|
+
this.entries[removedEntry.id] = null;
|
|
6800
|
+
for (const child of removedEntry.children) {
|
|
6801
|
+
removedEntries.push(child);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
entry.parent = UNDEFINED_VALUE;
|
|
6272
6805
|
}
|
|
6273
6806
|
|
|
6274
6807
|
move(entry, destination) {
|
|
@@ -6278,12 +6811,17 @@
|
|
|
6278
6811
|
if (destination.directory) {
|
|
6279
6812
|
if (!destination.isDescendantOf(entry)) {
|
|
6280
6813
|
if (entry != destination) {
|
|
6281
|
-
|
|
6282
|
-
|
|
6814
|
+
const existingChild = destination.getChildByName(entry.name);
|
|
6815
|
+
if (existingChild) {
|
|
6816
|
+
if (existingChild != entry) {
|
|
6817
|
+
throw new Error(ERR_ENTRY_EXISTS);
|
|
6818
|
+
}
|
|
6819
|
+
} else {
|
|
6820
|
+
detach(entry);
|
|
6821
|
+
entry.parent = destination;
|
|
6822
|
+
destination.children.push(entry);
|
|
6823
|
+
registerEntries(this, entry);
|
|
6283
6824
|
}
|
|
6284
|
-
detach(entry);
|
|
6285
|
-
entry.parent = destination;
|
|
6286
|
-
destination.children.push(entry);
|
|
6287
6825
|
}
|
|
6288
6826
|
} else {
|
|
6289
6827
|
throw new Error("Entry is a ancestor of target entry");
|
|
@@ -6300,6 +6838,11 @@
|
|
|
6300
6838
|
for (let index = 0; node && index < path.length; index++) {
|
|
6301
6839
|
node = node.getChildByName(path[index]);
|
|
6302
6840
|
}
|
|
6841
|
+
if (!node) {
|
|
6842
|
+
// exclude detached entries, e.g. clones registered in `entries` but not attached
|
|
6843
|
+
node = this.entries.find(entry => entry && (entry == this.root || entry.isDescendantOf(this.root)) &&
|
|
6844
|
+
entry.getRelativeName() == fullname);
|
|
6845
|
+
}
|
|
6303
6846
|
return node;
|
|
6304
6847
|
}
|
|
6305
6848
|
|
|
@@ -6381,6 +6924,7 @@
|
|
|
6381
6924
|
}
|
|
6382
6925
|
|
|
6383
6926
|
importZip(reader, options) {
|
|
6927
|
+
resetFS(this);
|
|
6384
6928
|
return this.root.importZip(reader, options);
|
|
6385
6929
|
}
|
|
6386
6930
|
|
|
@@ -6400,6 +6944,10 @@
|
|
|
6400
6944
|
return this.root.exportWritable(writable, options);
|
|
6401
6945
|
}
|
|
6402
6946
|
|
|
6947
|
+
exportZip(writer, options) {
|
|
6948
|
+
return this.root.exportZip(writer, options);
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6403
6951
|
isPasswordProtected() {
|
|
6404
6952
|
return this.root.isPasswordProtected();
|
|
6405
6953
|
}
|
|
@@ -6413,15 +6961,26 @@
|
|
|
6413
6961
|
|
|
6414
6962
|
function getTotalSize(entries, propertyName) {
|
|
6415
6963
|
let size = 0;
|
|
6416
|
-
|
|
6964
|
+
const pendingEntries = Array.from(entries);
|
|
6965
|
+
while (pendingEntries.length) {
|
|
6966
|
+
const entry = pendingEntries.pop();
|
|
6967
|
+
size += entry[propertyName] || 0;
|
|
6968
|
+
for (const child of entry.children) {
|
|
6969
|
+
pendingEntries.push(child);
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6417
6972
|
return size;
|
|
6973
|
+
}
|
|
6418
6974
|
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6975
|
+
function getReadableReader(readable) {
|
|
6976
|
+
let consumed;
|
|
6977
|
+
return function () {
|
|
6978
|
+
if (consumed) {
|
|
6979
|
+
throw new Error(ERR_READABLE_CONSUMED);
|
|
6423
6980
|
}
|
|
6424
|
-
|
|
6981
|
+
consumed = true;
|
|
6982
|
+
return { readable };
|
|
6983
|
+
};
|
|
6425
6984
|
}
|
|
6426
6985
|
|
|
6427
6986
|
function getZipBlobReader(options) {
|
|
@@ -6436,7 +6995,7 @@
|
|
|
6436
6995
|
async init() {
|
|
6437
6996
|
const zipBlobReader = this;
|
|
6438
6997
|
zipBlobReader.size = zipBlobReader.entry.uncompressedSize;
|
|
6439
|
-
const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({},
|
|
6998
|
+
const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, options, zipBlobReader.options));
|
|
6440
6999
|
zipBlobReader.data = data;
|
|
6441
7000
|
zipBlobReader.blobReader = new BlobReader(data);
|
|
6442
7001
|
super.init();
|
|
@@ -6449,29 +7008,41 @@
|
|
|
6449
7008
|
}
|
|
6450
7009
|
|
|
6451
7010
|
async function initReaders(entry, options) {
|
|
6452
|
-
|
|
6453
|
-
|
|
7011
|
+
const fileEntries = [];
|
|
7012
|
+
const pendingEntries = [entry];
|
|
7013
|
+
// per-export map so concurrent exports do not share (and lock) the same readers
|
|
7014
|
+
const readers = new Map();
|
|
7015
|
+
while (pendingEntries.length) {
|
|
7016
|
+
const pendingEntry = pendingEntries.pop();
|
|
7017
|
+
for (const child of pendingEntry.children) {
|
|
6454
7018
|
if (child.directory) {
|
|
6455
|
-
|
|
7019
|
+
pendingEntries.push(child);
|
|
6456
7020
|
} else {
|
|
6457
|
-
|
|
6458
|
-
try {
|
|
6459
|
-
await initStream(reader);
|
|
6460
|
-
} catch (error) {
|
|
6461
|
-
try {
|
|
6462
|
-
error.entryId = child.id;
|
|
6463
|
-
error.cause = {
|
|
6464
|
-
entry: child
|
|
6465
|
-
};
|
|
6466
|
-
} catch {
|
|
6467
|
-
// ignored
|
|
6468
|
-
}
|
|
6469
|
-
throw error;
|
|
6470
|
-
}
|
|
6471
|
-
child.uncompressedSize = reader.size;
|
|
7021
|
+
fileEntries.push(child);
|
|
6472
7022
|
}
|
|
6473
|
-
}
|
|
7023
|
+
}
|
|
6474
7024
|
}
|
|
7025
|
+
await Promise.all(fileEntries.map(async child => {
|
|
7026
|
+
const reader = child.reader = new child.Reader(child.data, options);
|
|
7027
|
+
readers.set(child, reader);
|
|
7028
|
+
try {
|
|
7029
|
+
await initStream(reader);
|
|
7030
|
+
} catch (error) {
|
|
7031
|
+
try {
|
|
7032
|
+
error.entryId = child.id;
|
|
7033
|
+
error.cause = {
|
|
7034
|
+
entry: child
|
|
7035
|
+
};
|
|
7036
|
+
} catch {
|
|
7037
|
+
// ignored
|
|
7038
|
+
}
|
|
7039
|
+
throw error;
|
|
7040
|
+
}
|
|
7041
|
+
if (reader.size !== UNDEFINED_VALUE) {
|
|
7042
|
+
child.uncompressedSize = reader.size;
|
|
7043
|
+
}
|
|
7044
|
+
}));
|
|
7045
|
+
return readers;
|
|
6475
7046
|
}
|
|
6476
7047
|
|
|
6477
7048
|
function detach(entry) {
|
|
@@ -6485,7 +7056,7 @@
|
|
|
6485
7056
|
}
|
|
6486
7057
|
}
|
|
6487
7058
|
|
|
6488
|
-
async function exportZip(zipWriter, entry, totalSize, options) {
|
|
7059
|
+
async function exportZip(zipWriter, entry, totalSize, options, readers) {
|
|
6489
7060
|
const selectedEntry = entry;
|
|
6490
7061
|
const entryOffsets = new Map();
|
|
6491
7062
|
await process(zipWriter, entry);
|
|
@@ -6495,7 +7066,11 @@
|
|
|
6495
7066
|
|
|
6496
7067
|
async function exportChild() {
|
|
6497
7068
|
if (options.bufferedWrite) {
|
|
6498
|
-
await Promise.allSettled(entry.children.map(processChild));
|
|
7069
|
+
const results = await Promise.allSettled(entry.children.map(processChild));
|
|
7070
|
+
const errorResult = results.find(result => result.status == "rejected");
|
|
7071
|
+
if (errorResult) {
|
|
7072
|
+
throw errorResult.reason;
|
|
7073
|
+
}
|
|
6499
7074
|
} else {
|
|
6500
7075
|
for (const child of entry.children) {
|
|
6501
7076
|
await processChild(child);
|
|
@@ -6550,7 +7125,7 @@
|
|
|
6550
7125
|
});
|
|
6551
7126
|
}
|
|
6552
7127
|
}
|
|
6553
|
-
await zipWriter.add(name, child
|
|
7128
|
+
await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
|
|
6554
7129
|
directory: child.directory,
|
|
6555
7130
|
onprogress: async indexProgress => {
|
|
6556
7131
|
if (options.onprogress) {
|
|
@@ -6599,7 +7174,7 @@
|
|
|
6599
7174
|
}
|
|
6600
7175
|
} catch (error) {
|
|
6601
7176
|
const message = error.message + (handle ? " (" + handle.name + ")" : "");
|
|
6602
|
-
throw new Error(message);
|
|
7177
|
+
throw new Error(message, { cause: error });
|
|
6603
7178
|
}
|
|
6604
7179
|
}
|
|
6605
7180
|
return addedEntries;
|
|
@@ -6659,9 +7234,21 @@
|
|
|
6659
7234
|
|
|
6660
7235
|
function resetFS(fs) {
|
|
6661
7236
|
fs.entries = [];
|
|
7237
|
+
fs.entryIdCounter = 0;
|
|
6662
7238
|
fs.root = new ZipDirectoryEntry(fs);
|
|
6663
7239
|
}
|
|
6664
7240
|
|
|
7241
|
+
function registerEntries(fs, entry) {
|
|
7242
|
+
const pendingEntries = [entry];
|
|
7243
|
+
while (pendingEntries.length) {
|
|
7244
|
+
const pendingEntry = pendingEntries.pop();
|
|
7245
|
+
fs.entries[pendingEntry.id] = pendingEntry;
|
|
7246
|
+
for (const child of pendingEntry.children) {
|
|
7247
|
+
pendingEntries.push(child);
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
|
|
6665
7252
|
function addChild(parent, name, params, directory) {
|
|
6666
7253
|
if (parent.directory) {
|
|
6667
7254
|
return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent);
|