@zip.js/zip.js 2.8.29 → 2.8.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BENCHMARKS.md +35 -0
- package/deno.json +5 -4
- package/dist/zip-core.js +496 -218
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +398 -195
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +588 -254
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +587 -253
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +501 -223
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +504 -226
- 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 +503 -225
- package/dist/zip.min.js +1 -1
- package/index-native.cjs +588 -254
- package/index-native.min.js +1 -1
- package/index.cjs +587 -253
- package/index.d.cts +2814 -0
- package/index.d.ts +364 -1
- package/index.min.js +1 -1
- package/lib/core/codec-worker.js +0 -10
- package/lib/core/configuration.js +21 -33
- package/lib/core/constants.js +3 -0
- package/lib/core/io.js +11 -19
- package/lib/core/options.js +11 -1
- package/lib/core/streams/aes-crypto-stream.js +16 -20
- package/lib/core/streams/codecs/crc32.js +39 -11
- package/lib/core/streams/common-crypto.js +0 -2
- package/lib/core/streams/zip-crypto-stream.js +12 -16
- package/lib/core/streams/zip-entry-stream.js +69 -19
- package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
- package/lib/core/streams/zlib-wasm/zlib-streams-loader.js +1 -3
- package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
- package/lib/core/util/base64.js +68 -0
- package/lib/core/util/decode-text.js +0 -1
- package/lib/core/util/default-mime-type.js +0 -3
- package/lib/core/util/inflate.js +194 -0
- package/lib/core/util/mime-type.js +26 -22
- package/lib/core/util/opfs-temp-stream.js +139 -0
- package/lib/core/web-worker-inline-native.js +1 -1
- package/lib/core/web-worker-inline-template-native.js +5 -5
- package/lib/core/web-worker-inline-wasm.js +1 -1
- package/lib/core/zip-fs.js +61 -10
- package/lib/core/zip-reader.js +162 -53
- package/lib/core/zip-writer.js +34 -41
- package/lib/core/zlib-streams-inline-template.js +3 -2
- package/lib/core/zlib-streams-inline.js +1 -1
- package/lib/zip-core-base.js +7 -2
- package/package.json +9 -6
- package/reserved-property-names.js +50 -0
- package/lib/core/util/mini-lz.js +0 -198
package/lib/core/codec-worker.js
CHANGED
|
@@ -194,9 +194,6 @@ async function runWorker({ options, readable, writable, onTaskFinished }, config
|
|
|
194
194
|
try {
|
|
195
195
|
await initModule(config);
|
|
196
196
|
} catch {
|
|
197
|
-
// The WASM module failed to load. Fall back to the native CompressionStream only if the
|
|
198
|
-
// zlib codec that would otherwise run depends on that module; a self-contained codec
|
|
199
|
-
// supplied through config (e.g. the pure-JS port) stays usable and is kept as requested.
|
|
200
197
|
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
201
198
|
config.CompressionStreamZlib :
|
|
202
199
|
config.DecompressionStreamZlib;
|
|
@@ -274,7 +271,6 @@ async function runWebWorker(workerData, config) {
|
|
|
274
271
|
return resultValue;
|
|
275
272
|
} catch (error) {
|
|
276
273
|
await closeWritable();
|
|
277
|
-
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
278
274
|
abortPipe();
|
|
279
275
|
try {
|
|
280
276
|
await closed;
|
|
@@ -299,8 +295,6 @@ function watchClosedStream(writableSource) {
|
|
|
299
295
|
const abortController = new AbortController();
|
|
300
296
|
const { writable, readable } = new TransformStream();
|
|
301
297
|
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
302
|
-
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
303
|
-
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
304
298
|
closed.catch(() => { });
|
|
305
299
|
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
306
300
|
}
|
|
@@ -393,8 +387,6 @@ function onWorkerError(event, workerData) {
|
|
|
393
387
|
event.preventDefault();
|
|
394
388
|
}
|
|
395
389
|
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
396
|
-
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
397
|
-
// fail the current task instead of letting it hang forever
|
|
398
390
|
terminateWorker(workerData);
|
|
399
391
|
if (rejectResult) {
|
|
400
392
|
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
@@ -446,8 +438,6 @@ function sendMessage(message, { worker, writer, transferStreams }) {
|
|
|
446
438
|
async function onMessage({ data }, workerData) {
|
|
447
439
|
const { type, value, messageId, result, error } = data;
|
|
448
440
|
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
449
|
-
// continuations below can settle after the worker slot has been reassigned to a new
|
|
450
|
-
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
451
441
|
const stale = () => workerData.generation != generation;
|
|
452
442
|
try {
|
|
453
443
|
if (error) {
|
|
@@ -55,6 +55,22 @@ const DEFAULT_CONFIGURATION = {
|
|
|
55
55
|
DecompressionStream: typeof DecompressionStream != UNDEFINED_TYPE && DecompressionStream
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
const CONFIGURABLE_PROPERTY_NAMES = [
|
|
59
|
+
"baseURI",
|
|
60
|
+
"wasmURI",
|
|
61
|
+
"workerURI",
|
|
62
|
+
"chunkSize",
|
|
63
|
+
"maxWorkers",
|
|
64
|
+
"terminateWorkerTimeout",
|
|
65
|
+
"workerStarvationTimeout",
|
|
66
|
+
"useCompressionStream",
|
|
67
|
+
"useWebWorkers",
|
|
68
|
+
"CompressionStream",
|
|
69
|
+
"DecompressionStream",
|
|
70
|
+
"CompressionStreamZlib",
|
|
71
|
+
"DecompressionStreamZlib"
|
|
72
|
+
];
|
|
73
|
+
|
|
58
74
|
const config = Object.assign({}, DEFAULT_CONFIGURATION);
|
|
59
75
|
|
|
60
76
|
export {
|
|
@@ -72,38 +88,10 @@ function getChunkSize(config) {
|
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
function configure(configuration) {
|
|
75
|
-
const {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
workerStarvationTimeout,
|
|
81
|
-
useCompressionStream,
|
|
82
|
-
useWebWorkers,
|
|
83
|
-
CompressionStream,
|
|
84
|
-
DecompressionStream,
|
|
85
|
-
CompressionStreamZlib,
|
|
86
|
-
DecompressionStreamZlib,
|
|
87
|
-
workerURI,
|
|
88
|
-
wasmURI
|
|
89
|
-
} = configuration;
|
|
90
|
-
setIfDefined("baseURI", baseURI);
|
|
91
|
-
setIfDefined("wasmURI", wasmURI);
|
|
92
|
-
setIfDefined("workerURI", workerURI);
|
|
93
|
-
setIfDefined("chunkSize", chunkSize);
|
|
94
|
-
setIfDefined("maxWorkers", maxWorkers);
|
|
95
|
-
setIfDefined("terminateWorkerTimeout", terminateWorkerTimeout);
|
|
96
|
-
setIfDefined("workerStarvationTimeout", workerStarvationTimeout);
|
|
97
|
-
setIfDefined("useCompressionStream", useCompressionStream);
|
|
98
|
-
setIfDefined("useWebWorkers", useWebWorkers);
|
|
99
|
-
setIfDefined("CompressionStream", CompressionStream);
|
|
100
|
-
setIfDefined("DecompressionStream", DecompressionStream);
|
|
101
|
-
setIfDefined("CompressionStreamZlib", CompressionStreamZlib);
|
|
102
|
-
setIfDefined("DecompressionStreamZlib", DecompressionStreamZlib);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function setIfDefined(propertyName, propertyValue) {
|
|
106
|
-
if (propertyValue !== UNDEFINED_VALUE) {
|
|
107
|
-
config[propertyName] = propertyValue;
|
|
91
|
+
for (const propertyName of CONFIGURABLE_PROPERTY_NAMES) {
|
|
92
|
+
const propertyValue = configuration[propertyName];
|
|
93
|
+
if (propertyValue !== UNDEFINED_VALUE) {
|
|
94
|
+
config[propertyName] = propertyValue;
|
|
95
|
+
}
|
|
108
96
|
}
|
|
109
97
|
}
|
package/lib/core/constants.js
CHANGED
|
@@ -104,7 +104,10 @@ const UNDEFINED_TYPE = "undefined";
|
|
|
104
104
|
const FUNCTION_TYPE = "function";
|
|
105
105
|
const OBJECT_TYPE = "object";
|
|
106
106
|
|
|
107
|
+
const EMPTY_UINT8_ARRAY = new Uint8Array();
|
|
108
|
+
|
|
107
109
|
export {
|
|
110
|
+
EMPTY_UINT8_ARRAY,
|
|
108
111
|
MAX_32_BITS,
|
|
109
112
|
MAX_16_BITS,
|
|
110
113
|
MAX_8_BITS,
|
package/lib/core/io.js
CHANGED
|
@@ -33,7 +33,8 @@ import {
|
|
|
33
33
|
UNDEFINED_VALUE,
|
|
34
34
|
INFINITY_VALUE,
|
|
35
35
|
FUNCTION_TYPE,
|
|
36
|
-
END_OF_CENTRAL_DIR_LENGTH
|
|
36
|
+
END_OF_CENTRAL_DIR_LENGTH,
|
|
37
|
+
EMPTY_UINT8_ARRAY
|
|
37
38
|
} from "./constants.js";
|
|
38
39
|
import { getConfiguration } from "./configuration.js";
|
|
39
40
|
|
|
@@ -71,26 +72,24 @@ class Stream {
|
|
|
71
72
|
class Reader extends Stream {
|
|
72
73
|
|
|
73
74
|
get readable() {
|
|
75
|
+
return this.createReadable();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
createReadable({ offset = 0, size, diskNumberStart, chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
|
|
74
79
|
const reader = this;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
start() {
|
|
78
|
-
this.chunkOffset = 0;
|
|
79
|
-
},
|
|
80
|
+
let chunkOffset = 0;
|
|
81
|
+
return new ReadableStream({
|
|
80
82
|
async pull(controller) {
|
|
81
|
-
const { offset = 0, size, diskNumberStart } = readable;
|
|
82
|
-
const { chunkOffset } = this;
|
|
83
83
|
const dataSize = size === UNDEFINED_VALUE ? chunkSize : Math.min(chunkSize, size - chunkOffset);
|
|
84
84
|
const data = await readUint8Array(reader, offset + chunkOffset, dataSize, diskNumberStart);
|
|
85
85
|
controller.enqueue(data);
|
|
86
86
|
if ((chunkOffset + chunkSize > size) || (size === UNDEFINED_VALUE && !data.length && dataSize)) {
|
|
87
87
|
controller.close();
|
|
88
88
|
} else {
|
|
89
|
-
|
|
89
|
+
chunkOffset += chunkSize;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
|
-
return readable;
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -229,8 +228,6 @@ class BlobWriter extends Stream {
|
|
|
229
228
|
}
|
|
230
229
|
});
|
|
231
230
|
writer.blob = new Response(transformStream.readable, { headers }).blob();
|
|
232
|
-
// mark early rejections (e.g. aborted pipe) as handled; awaiting getData() still
|
|
233
|
-
// surfaces them
|
|
234
231
|
writer.blob.catch(() => { });
|
|
235
232
|
}
|
|
236
233
|
|
|
@@ -392,7 +389,7 @@ async function readUint8ArrayHttpReader(httpReader, index, length, sendRequest,
|
|
|
392
389
|
return eocdCache;
|
|
393
390
|
}
|
|
394
391
|
if (index >= size || length === 0) {
|
|
395
|
-
return
|
|
392
|
+
return EMPTY_UINT8_ARRAY;
|
|
396
393
|
} else {
|
|
397
394
|
if (index + length > size) {
|
|
398
395
|
length = size - index;
|
|
@@ -449,8 +446,6 @@ async function getXMLHttpRequestData(httpReader) {
|
|
|
449
446
|
async function getRequestData(httpReader, sendRequest) {
|
|
450
447
|
const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
|
|
451
448
|
httpReader.data = new Uint8Array(await response.arrayBuffer());
|
|
452
|
-
// the downloaded content is authoritative, e.g. the Content-Length response header
|
|
453
|
-
// of the HEAD request can be the encoded size when the content is compressed
|
|
454
449
|
httpReader.size = httpReader.data.length;
|
|
455
450
|
}
|
|
456
451
|
|
|
@@ -515,7 +510,6 @@ class HttpReader extends Reader {
|
|
|
515
510
|
super();
|
|
516
511
|
Object.assign(this, {
|
|
517
512
|
url,
|
|
518
|
-
// an explicit fetch implementation takes precedence over useXHR
|
|
519
513
|
reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
|
|
520
514
|
});
|
|
521
515
|
}
|
|
@@ -654,7 +648,7 @@ class SplitDataReader extends Reader {
|
|
|
654
648
|
}
|
|
655
649
|
}
|
|
656
650
|
} else {
|
|
657
|
-
result =
|
|
651
|
+
result = EMPTY_UINT8_ARRAY;
|
|
658
652
|
}
|
|
659
653
|
reader.lastDiskNumber = Math.max(currentDiskNumber, reader.lastDiskNumber);
|
|
660
654
|
return result;
|
|
@@ -699,8 +693,6 @@ class SplitDataWriter extends Stream {
|
|
|
699
693
|
writer.diskOffset += diskSourceWriter.size;
|
|
700
694
|
writer.diskNumber++;
|
|
701
695
|
diskWriter = null;
|
|
702
|
-
// the next disk is not opened yet but its space is available; leaving a
|
|
703
|
-
// stale value here would force a new disk for every subsequent write
|
|
704
696
|
writer.availableSize = writer.maxSize;
|
|
705
697
|
if (chunk.length > availableSize) {
|
|
706
698
|
await this.write(chunk.subarray(availableSize));
|
package/lib/core/options.js
CHANGED
|
@@ -58,6 +58,11 @@ const OPTION_ENCODE_TEXT = "encodeText";
|
|
|
58
58
|
const OPTION_OFFSET = "offset";
|
|
59
59
|
const OPTION_USDZ = "usdz";
|
|
60
60
|
const OPTION_UNIX_EXTRA_FIELD_TYPE = "unixExtraFieldType";
|
|
61
|
+
const OPTION_STRICTNESS = "strictness";
|
|
62
|
+
const OPTION_MAX_APPENDED_DATA_SIZE = "maxAppendedDataSize";
|
|
63
|
+
const STRICTNESS_STRICT = "strict";
|
|
64
|
+
const STRICTNESS_BALANCED = "balanced";
|
|
65
|
+
const STRICTNESS_TOLERANT = "tolerant";
|
|
61
66
|
|
|
62
67
|
export {
|
|
63
68
|
OPTION_FILENAME_ENCODING,
|
|
@@ -91,5 +96,10 @@ export {
|
|
|
91
96
|
OPTION_ENCODE_TEXT,
|
|
92
97
|
OPTION_OFFSET,
|
|
93
98
|
OPTION_USDZ,
|
|
94
|
-
OPTION_UNIX_EXTRA_FIELD_TYPE
|
|
99
|
+
OPTION_UNIX_EXTRA_FIELD_TYPE,
|
|
100
|
+
OPTION_STRICTNESS,
|
|
101
|
+
OPTION_MAX_APPENDED_DATA_SIZE,
|
|
102
|
+
STRICTNESS_STRICT,
|
|
103
|
+
STRICTNESS_BALANCED,
|
|
104
|
+
STRICTNESS_TOLERANT
|
|
95
105
|
};
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
/* global crypto, TransformStream */
|
|
30
30
|
// deno-lint-ignore-file no-this-alias
|
|
31
31
|
|
|
32
|
-
import { UNDEFINED_VALUE, UNDEFINED_TYPE, FUNCTION_TYPE } from "../constants.js";
|
|
32
|
+
import { UNDEFINED_VALUE, UNDEFINED_TYPE, FUNCTION_TYPE, EMPTY_UINT8_ARRAY } from "../constants.js";
|
|
33
33
|
import { encodeText } from "./../util/encode-text.js";
|
|
34
34
|
import {
|
|
35
35
|
cipher,
|
|
@@ -73,12 +73,7 @@ class AESDecryptionStream extends TransformStream {
|
|
|
73
73
|
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
74
74
|
super({
|
|
75
75
|
start() {
|
|
76
|
-
|
|
77
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
78
|
-
password: encodePassword(password, rawPassword),
|
|
79
|
-
strength: encryptionStrength - 1,
|
|
80
|
-
pending: new Uint8Array()
|
|
81
|
-
});
|
|
76
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
82
77
|
},
|
|
83
78
|
async transform(chunk, controller) {
|
|
84
79
|
const aesCrypto = this;
|
|
@@ -113,7 +108,7 @@ class AESDecryptionStream extends TransformStream {
|
|
|
113
108
|
await ready;
|
|
114
109
|
const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH);
|
|
115
110
|
const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH);
|
|
116
|
-
let decryptedChunkArray =
|
|
111
|
+
let decryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
117
112
|
if (chunkToDecrypt.length) {
|
|
118
113
|
const encryptedChunk = toBits(codecBytes, chunkToDecrypt);
|
|
119
114
|
hmac.update(encryptedChunk);
|
|
@@ -121,8 +116,6 @@ class AESDecryptionStream extends TransformStream {
|
|
|
121
116
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
122
117
|
}
|
|
123
118
|
const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH);
|
|
124
|
-
// branch-free comparison: accumulate the differences instead of
|
|
125
|
-
// short-circuiting on the first mismatching byte
|
|
126
119
|
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
127
120
|
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
128
121
|
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
@@ -144,12 +137,7 @@ class AESEncryptionStream extends TransformStream {
|
|
|
144
137
|
let stream;
|
|
145
138
|
super({
|
|
146
139
|
start() {
|
|
147
|
-
|
|
148
|
-
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
149
|
-
password: encodePassword(password, rawPassword),
|
|
150
|
-
strength: encryptionStrength - 1,
|
|
151
|
-
pending: new Uint8Array()
|
|
152
|
-
});
|
|
140
|
+
initAesCrypto(this, password, rawPassword, encryptionStrength);
|
|
153
141
|
},
|
|
154
142
|
async transform(chunk, controller) {
|
|
155
143
|
const aesCrypto = this;
|
|
@@ -159,7 +147,7 @@ class AESEncryptionStream extends TransformStream {
|
|
|
159
147
|
resolveReady,
|
|
160
148
|
ready
|
|
161
149
|
} = aesCrypto;
|
|
162
|
-
let preamble =
|
|
150
|
+
let preamble = EMPTY_UINT8_ARRAY;
|
|
163
151
|
if (password) {
|
|
164
152
|
preamble = await createEncryptionKeys(aesCrypto, strength, password);
|
|
165
153
|
resolveReady();
|
|
@@ -179,7 +167,7 @@ class AESEncryptionStream extends TransformStream {
|
|
|
179
167
|
} = this;
|
|
180
168
|
if (hmac && ctr) {
|
|
181
169
|
await ready;
|
|
182
|
-
let encryptedChunkArray =
|
|
170
|
+
let encryptedChunkArray = EMPTY_UINT8_ARRAY;
|
|
183
171
|
if (pending.length) {
|
|
184
172
|
const encryptedChunk = ctr.update(toBits(codecBytes, pending));
|
|
185
173
|
hmac.update(encryptedChunk);
|
|
@@ -196,10 +184,18 @@ class AESEncryptionStream extends TransformStream {
|
|
|
196
184
|
|
|
197
185
|
export {
|
|
198
186
|
AESDecryptionStream,
|
|
199
|
-
AESEncryptionStream
|
|
200
|
-
ERR_INVALID_PASSWORD
|
|
187
|
+
AESEncryptionStream
|
|
201
188
|
};
|
|
202
189
|
|
|
190
|
+
function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
|
|
191
|
+
Object.assign(aesCrypto, {
|
|
192
|
+
ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
|
|
193
|
+
password: encodePassword(password, rawPassword),
|
|
194
|
+
strength: encryptionStrength - 1,
|
|
195
|
+
pending: EMPTY_UINT8_ARRAY
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
203
199
|
function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) {
|
|
204
200
|
const {
|
|
205
201
|
ctr,
|
|
@@ -26,18 +26,30 @@
|
|
|
26
26
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Slicing-by-8 CRC-32 (Intel / zlib). The eight 256-entry tables let the inner loop
|
|
30
|
+
// consume 8 bytes per iteration with a shorter dependency chain, ~4x the byte-at-a-time
|
|
31
|
+
// rate (measured ~320 -> ~1400 MB/s on 64KB chunks).
|
|
32
|
+
//
|
|
33
|
+
// Every table MUST stay a PACKED_SMI array: build with array literals (not `new Array(n)`,
|
|
34
|
+
// which is HOLEY) and store the signed int32 XOR result (no `>>> 0`). An unsigned or holey
|
|
35
|
+
// table becomes a V8 FixedDoubleArray whose every hot-loop lookup unboxes a double (~1.6x
|
|
36
|
+
// slower). Signedness is irrelevant to the result — the reads mask/shift it and the final
|
|
37
|
+
// `~crc` normalizes it. Do NOT reintroduce `>>> 0` here or switch to `new Array(256)`.
|
|
38
|
+
const T = [[], [], [], [], [], [], [], []];
|
|
39
|
+
for (let n = 0; n < 256; n++) {
|
|
40
|
+
let t = n;
|
|
32
41
|
for (let j = 0; j < 8; j++) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
t = (t & 1) ? (t >>> 1) ^ 0xEDB88320 : t >>> 1;
|
|
43
|
+
}
|
|
44
|
+
T[0][n] = t;
|
|
45
|
+
}
|
|
46
|
+
for (let n = 0; n < 256; n++) {
|
|
47
|
+
for (let k = 1; k < 8; k++) {
|
|
48
|
+
const previous = T[k - 1][n];
|
|
49
|
+
T[k][n] = (previous >>> 8) ^ T[0][previous & 0xFF];
|
|
38
50
|
}
|
|
39
|
-
table[i] = t;
|
|
40
51
|
}
|
|
52
|
+
const [T0, T1, T2, T3, T4, T5, T6, T7] = T;
|
|
41
53
|
|
|
42
54
|
class Crc32 {
|
|
43
55
|
|
|
@@ -47,8 +59,24 @@ class Crc32 {
|
|
|
47
59
|
|
|
48
60
|
append(data) {
|
|
49
61
|
let crc = this.crc | 0;
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
const length = data.length | 0;
|
|
63
|
+
let offset = 0;
|
|
64
|
+
// Process 8 bytes per iteration over the typed-array body. DataView.getInt32(le)
|
|
65
|
+
// reads an unaligned little-endian word as a signed int32 (no double boxing), so no
|
|
66
|
+
// alignment or endianness handling is needed; data.buffer guards non-typed inputs.
|
|
67
|
+
if (length >= 8 && data.buffer) {
|
|
68
|
+
const view = new DataView(data.buffer, data.byteOffset, length);
|
|
69
|
+
const end = length - 8;
|
|
70
|
+
for (; offset <= end; offset += 8) {
|
|
71
|
+
const a = crc ^ view.getInt32(offset, true);
|
|
72
|
+
const b = view.getInt32(offset + 4, true);
|
|
73
|
+
crc = T7[a & 0xFF] ^ T6[(a >>> 8) & 0xFF] ^ T5[(a >>> 16) & 0xFF] ^ T4[(a >>> 24) & 0xFF] ^
|
|
74
|
+
T3[b & 0xFF] ^ T2[(b >>> 8) & 0xFF] ^ T1[(b >>> 16) & 0xFF] ^ T0[(b >>> 24) & 0xFF];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Remaining tail (and non-typed inputs) byte-at-a-time with the base table.
|
|
78
|
+
for (; offset < length; offset++) {
|
|
79
|
+
crc = (crc >>> 8) ^ T0[(crc ^ data[offset]) & 0xFF];
|
|
52
80
|
}
|
|
53
81
|
this.crc = crc;
|
|
54
82
|
}
|
|
@@ -48,8 +48,6 @@ function getRandomValues(array) {
|
|
|
48
48
|
if (GET_RANDOM_VALUES_SUPPORTED) {
|
|
49
49
|
return crypto.getRandomValues(array);
|
|
50
50
|
} else {
|
|
51
|
-
// all supported runtimes expose crypto.getRandomValues; failing hard beats
|
|
52
|
-
// silently generating predictable encryption salts
|
|
53
51
|
throw new Error(ERR_UNSUPPORTED_CRYPTO_API);
|
|
54
52
|
}
|
|
55
53
|
}
|
|
@@ -43,20 +43,13 @@ class ZipCryptoDecryptionStream extends TransformStream {
|
|
|
43
43
|
constructor({ password, rawPassword, passwordVerification, checkPasswordOnly }) {
|
|
44
44
|
super({
|
|
45
45
|
start() {
|
|
46
|
-
|
|
47
|
-
password,
|
|
48
|
-
rawPassword,
|
|
49
|
-
passwordVerification
|
|
50
|
-
});
|
|
51
|
-
createKeys(this, password, rawPassword);
|
|
46
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
52
47
|
},
|
|
53
48
|
transform(chunk, controller) {
|
|
54
49
|
const zipCrypto = this;
|
|
55
50
|
if (zipCrypto.password || zipCrypto.rawPassword) {
|
|
56
51
|
const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH));
|
|
57
52
|
zipCrypto.password = zipCrypto.rawPassword = null;
|
|
58
|
-
// verify the password with a branch-free comparison of the verification byte, mirroring
|
|
59
|
-
// the constant-time AES HMAC signature check
|
|
60
53
|
if ((decryptedHeader.at(-1) ^ zipCrypto.passwordVerification) != 0) {
|
|
61
54
|
throw new Error(ERR_INVALID_PASSWORD);
|
|
62
55
|
}
|
|
@@ -77,12 +70,7 @@ class ZipCryptoEncryptionStream extends TransformStream {
|
|
|
77
70
|
constructor({ password, rawPassword, passwordVerification }) {
|
|
78
71
|
super({
|
|
79
72
|
start() {
|
|
80
|
-
|
|
81
|
-
password,
|
|
82
|
-
rawPassword,
|
|
83
|
-
passwordVerification
|
|
84
|
-
});
|
|
85
|
-
createKeys(this, password, rawPassword);
|
|
73
|
+
initZipCrypto(this, password, rawPassword, passwordVerification);
|
|
86
74
|
},
|
|
87
75
|
transform(chunk, controller) {
|
|
88
76
|
const zipCrypto = this;
|
|
@@ -108,10 +96,18 @@ class ZipCryptoEncryptionStream extends TransformStream {
|
|
|
108
96
|
|
|
109
97
|
export {
|
|
110
98
|
ZipCryptoDecryptionStream,
|
|
111
|
-
ZipCryptoEncryptionStream
|
|
112
|
-
ERR_INVALID_PASSWORD
|
|
99
|
+
ZipCryptoEncryptionStream
|
|
113
100
|
};
|
|
114
101
|
|
|
102
|
+
function initZipCrypto(zipCrypto, password, rawPassword, passwordVerification) {
|
|
103
|
+
Object.assign(zipCrypto, {
|
|
104
|
+
password,
|
|
105
|
+
rawPassword,
|
|
106
|
+
passwordVerification
|
|
107
|
+
});
|
|
108
|
+
createKeys(zipCrypto, password, rawPassword);
|
|
109
|
+
}
|
|
110
|
+
|
|
115
111
|
function decrypt(target, input) {
|
|
116
112
|
const output = new Uint8Array(input.length);
|
|
117
113
|
for (let index = 0; index < input.length; index++) {
|
|
@@ -48,21 +48,32 @@ const ERR_INVALID_UNCOMPRESSED_SIZE = "Invalid uncompressed size";
|
|
|
48
48
|
const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
|
|
49
49
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
50
50
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
51
|
+
const FORMAT_GZIP = "gzip";
|
|
52
|
+
const GZIP_HEADER_LENGTH = 10;
|
|
53
|
+
const GZIP_TRAILER_LENGTH = 8;
|
|
51
54
|
|
|
52
55
|
class DeflateStream extends TransformStream {
|
|
53
56
|
|
|
54
57
|
constructor(options, { chunkSize, CompressionStreamZlib, CompressionStream }) {
|
|
55
58
|
super({});
|
|
56
|
-
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level } = options;
|
|
59
|
+
const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level, deflate64 } = options;
|
|
57
60
|
const stream = this;
|
|
58
|
-
let crc32Stream, encryptionStream;
|
|
61
|
+
let crc32Stream, encryptionStream, gzipCrc32Stream;
|
|
59
62
|
let readable = super.readable;
|
|
60
|
-
|
|
63
|
+
const useGzipCrc32 = signed && compressed && !deflate64 && (!encrypted || zipCrypto) &&
|
|
64
|
+
Boolean(useCompressionStream && CompressionStream);
|
|
65
|
+
if ((!encrypted || zipCrypto) && signed && !useGzipCrc32) {
|
|
61
66
|
crc32Stream = new Crc32Stream();
|
|
62
67
|
readable = pipeThrough(readable, crc32Stream);
|
|
63
68
|
}
|
|
64
69
|
if (compressed) {
|
|
65
|
-
|
|
70
|
+
if (useGzipCrc32) {
|
|
71
|
+
gzipCrc32Stream = new GzipToRawDeflateStream();
|
|
72
|
+
readable = pipeThroughBackpressured(readable, new CompressionStream(FORMAT_GZIP));
|
|
73
|
+
readable = pipeThrough(readable, gzipCrc32Stream);
|
|
74
|
+
} else {
|
|
75
|
+
readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamZlib, CompressionStream);
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
78
|
if (encrypted) {
|
|
68
79
|
if (zipCrypto) {
|
|
@@ -78,13 +89,64 @@ class DeflateStream extends TransformStream {
|
|
|
78
89
|
signature = encryptionStream.signature;
|
|
79
90
|
}
|
|
80
91
|
if ((!encrypted || zipCrypto) && signed) {
|
|
81
|
-
signature = new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
92
|
+
signature = useGzipCrc32 ? gzipCrc32Stream.signature : new DataView(crc32Stream.value.buffer).getUint32(0);
|
|
82
93
|
}
|
|
83
94
|
stream.signature = signature;
|
|
84
95
|
});
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
|
|
99
|
+
class GzipToRawDeflateStream extends TransformStream {
|
|
100
|
+
|
|
101
|
+
constructor() {
|
|
102
|
+
// deno-lint-ignore prefer-const
|
|
103
|
+
let stream;
|
|
104
|
+
let headerLeft = GZIP_HEADER_LENGTH;
|
|
105
|
+
let tail = new Uint8Array(0);
|
|
106
|
+
super({
|
|
107
|
+
transform(chunk, controller) {
|
|
108
|
+
if (headerLeft) {
|
|
109
|
+
const dropped = Math.min(headerLeft, chunk.length);
|
|
110
|
+
headerLeft -= dropped;
|
|
111
|
+
chunk = chunk.subarray(dropped);
|
|
112
|
+
if (!chunk.length) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const available = tail.length + chunk.length;
|
|
117
|
+
if (available <= GZIP_TRAILER_LENGTH) {
|
|
118
|
+
const pending = new Uint8Array(available);
|
|
119
|
+
pending.set(tail);
|
|
120
|
+
pending.set(chunk, tail.length);
|
|
121
|
+
tail = pending;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const emitLength = available - GZIP_TRAILER_LENGTH;
|
|
125
|
+
const output = new Uint8Array(emitLength);
|
|
126
|
+
const fromTail = Math.min(emitLength, tail.length);
|
|
127
|
+
output.set(tail.subarray(0, fromTail), 0);
|
|
128
|
+
if (emitLength > fromTail) {
|
|
129
|
+
output.set(chunk.subarray(0, emitLength - fromTail), fromTail);
|
|
130
|
+
}
|
|
131
|
+
controller.enqueue(output);
|
|
132
|
+
const nextTail = new Uint8Array(GZIP_TRAILER_LENGTH);
|
|
133
|
+
const tailRemaining = tail.length - fromTail;
|
|
134
|
+
if (tailRemaining) {
|
|
135
|
+
nextTail.set(tail.subarray(fromTail), 0);
|
|
136
|
+
}
|
|
137
|
+
nextTail.set(chunk.subarray(emitLength - fromTail), tailRemaining);
|
|
138
|
+
tail = nextTail;
|
|
139
|
+
},
|
|
140
|
+
flush() {
|
|
141
|
+
const dataView = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);
|
|
142
|
+
stream.signature = dataView.getUint32(0, true);
|
|
143
|
+
stream.uncompressedSize = dataView.getUint32(4, true);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
stream = this;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
88
150
|
class InflateStream extends TransformStream {
|
|
89
151
|
|
|
90
152
|
constructor(options, { chunkSize, DecompressionStreamZlib, DecompressionStream }) {
|
|
@@ -157,11 +219,6 @@ function pipeThroughCommpressionStream(readable, useCompressionStream, options,
|
|
|
157
219
|
throw error;
|
|
158
220
|
}
|
|
159
221
|
}
|
|
160
|
-
// The native CompressionStream/DecompressionStream and the pure-JS zlib port do not signal
|
|
161
|
-
// backpressure on their writable side (their `ready` never pends), so a plain pipeThrough drains
|
|
162
|
-
// the whole source into them and peak memory grows with the entry size. Awaiting each write()
|
|
163
|
-
// paces the source to the codec's consumption rate, keeping streaming memory bounded. The WASM
|
|
164
|
-
// codec already backpressures, so this is a no-op cost for it.
|
|
165
222
|
return pipeThroughBackpressured(readable, codecStream);
|
|
166
223
|
}
|
|
167
224
|
|
|
@@ -169,10 +226,6 @@ function pipeThrough(readable, transformStream) {
|
|
|
169
226
|
return readable.pipeThrough(transformStream);
|
|
170
227
|
}
|
|
171
228
|
|
|
172
|
-
// Like readable.pipeThrough(transformStream), but drives the writable side manually and awaits each
|
|
173
|
-
// write() so that a codec which under-reports backpressure cannot pull the whole source into memory.
|
|
174
|
-
// Errors propagate in both directions: a source error aborts the codec; a downstream cancel/error
|
|
175
|
-
// (surfaced through the codec's writable rejecting) cancels the source.
|
|
176
229
|
function pipeThroughBackpressured(readable, transformStream) {
|
|
177
230
|
const writer = transformStream.writable.getWriter();
|
|
178
231
|
const reader = readable.getReader();
|
|
@@ -201,7 +254,7 @@ async function abort(writer, error) {
|
|
|
201
254
|
try {
|
|
202
255
|
await writer.abort(error);
|
|
203
256
|
} catch {
|
|
204
|
-
// the writable may already be errored/closed
|
|
257
|
+
// ignored: the writable may already be errored/closed
|
|
205
258
|
}
|
|
206
259
|
}
|
|
207
260
|
|
|
@@ -209,13 +262,10 @@ async function cancel(reader, error) {
|
|
|
209
262
|
try {
|
|
210
263
|
await reader.cancel(error);
|
|
211
264
|
} catch {
|
|
212
|
-
// the readable may already be errored/closed
|
|
265
|
+
// ignored: the readable may already be errored/closed
|
|
213
266
|
}
|
|
214
267
|
}
|
|
215
268
|
|
|
216
|
-
// DecompressionStream implementations can fail with a message-less TypeError on malformed
|
|
217
|
-
// input; give these errors an identifiable message (errors raised by the other stages of
|
|
218
|
-
// the pipeline, e.g. ERR_INVALID_PASSWORD, always carry a message and pass through as-is)
|
|
219
269
|
function mapInflateStreamError(readable) {
|
|
220
270
|
const reader = readable.getReader();
|
|
221
271
|
return new ReadableStream({
|