@zip.js/zip.js 2.8.25 → 2.8.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/deno.json +1 -1
- package/dist/zip-core.js +1050 -569
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core.js +1146 -617
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-native.js +1207 -627
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +1279 -681
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +1051 -570
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-module.wasm +0 -0
- package/dist/zip-native.js +1053 -572
- package/dist/zip-native.min.js +1 -1
- package/dist/zip-web-worker-native.js +1 -1
- package/dist/zip-web-worker.js +1 -1
- package/dist/zip.js +1125 -626
- package/dist/zip.min.js +1 -1
- package/eslint.config.mjs +6 -1
- package/index-native.cjs +1207 -627
- package/index-native.min.js +1 -1
- package/index.cjs +1279 -681
- package/index.d.ts +2451 -2355
- package/index.min.js +1 -1
- package/lib/core/codec-pool.js +39 -1
- package/lib/core/codec-worker.js +120 -34
- package/lib/core/configuration.js +3 -0
- package/lib/core/constants.js +6 -0
- package/lib/core/io.js +55 -25
- package/lib/core/options.js +2 -0
- package/lib/core/streams/aes-crypto-stream.js +12 -12
- package/lib/core/streams/codec-stream.js +11 -5
- package/lib/core/streams/codecs/sjcl.js +1 -33
- package/lib/core/streams/common-crypto.js +4 -4
- package/lib/core/streams/zip-crypto-stream.js +25 -15
- package/lib/core/streams/zip-entry-stream.js +35 -1
- package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
- package/lib/core/streams/zlib-wasm/zlib-streams.js +73 -55
- package/lib/core/streams/zlib-wasm/zlib-streams.wasm +0 -0
- package/lib/core/util/decode-cp437.js +1 -1
- package/lib/core/util/decode-text.js +2 -1
- package/lib/core/web-worker-base.js +12 -4
- package/lib/core/web-worker-inline-native.js +1 -1
- package/lib/core/web-worker-inline-wasm.js +1 -1
- package/lib/core/zip-fs.js +154 -55
- package/lib/core/zip-reader.js +185 -67
- package/lib/core/zip-writer.js +613 -376
- package/lib/core/zlib-streams-inline.js +1 -1
- package/lib/zip-core-reader.js +3 -1
- package/lib/zip-core-writer.js +1 -0
- package/package.json +20 -6
package/lib/core/codec-pool.js
CHANGED
|
@@ -35,12 +35,15 @@ import {
|
|
|
35
35
|
ERR_INVALID_SIGNATURE,
|
|
36
36
|
ERR_INVALID_UNCOMPRESSED_SIZE,
|
|
37
37
|
ERR_INVALID_PASSWORD,
|
|
38
|
+
ERR_INVALID_COMPRESSED_DATA,
|
|
38
39
|
ERR_ABORT_CHECK_PASSWORD
|
|
39
40
|
} from "./streams/codec-stream.js";
|
|
40
41
|
import { CodecWorker } from "./codec-worker.js";
|
|
41
42
|
|
|
42
43
|
let pool = [];
|
|
43
44
|
const pendingRequests = [];
|
|
45
|
+
let starvationTimeout;
|
|
46
|
+
let starvationDelay;
|
|
44
47
|
|
|
45
48
|
export {
|
|
46
49
|
runWorker,
|
|
@@ -50,6 +53,7 @@ export {
|
|
|
50
53
|
ERR_INVALID_SIGNATURE,
|
|
51
54
|
ERR_INVALID_UNCOMPRESSED_SIZE,
|
|
52
55
|
ERR_INVALID_PASSWORD,
|
|
56
|
+
ERR_INVALID_COMPRESSED_DATA,
|
|
53
57
|
ERR_ABORT_CHECK_PASSWORD
|
|
54
58
|
};
|
|
55
59
|
|
|
@@ -78,14 +82,20 @@ async function runWorker(stream, workerOptions) {
|
|
|
78
82
|
pool.push(workerData);
|
|
79
83
|
return new CodecWorker(workerData, stream, workerOptions, onTaskFinished);
|
|
80
84
|
} else {
|
|
81
|
-
return new Promise(resolve =>
|
|
85
|
+
return new Promise(resolve => {
|
|
86
|
+
pendingRequests.push({ resolve, stream, workerOptions });
|
|
87
|
+
starvationDelay = config.workerStarvationTimeout;
|
|
88
|
+
armStarvationTimeout();
|
|
89
|
+
});
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
function onTaskFinished(workerData) {
|
|
94
|
+
clearStarvationTimeout();
|
|
86
95
|
if (pendingRequests.length) {
|
|
87
96
|
const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
|
|
88
97
|
resolve(new CodecWorker(workerData, stream, workerOptions, onTaskFinished));
|
|
98
|
+
armStarvationTimeout();
|
|
89
99
|
} else if (workerData.worker) {
|
|
90
100
|
clearTerminateTimeout(workerData);
|
|
91
101
|
terminateWorker(workerData, workerOptions);
|
|
@@ -95,6 +105,34 @@ async function runWorker(stream, workerOptions) {
|
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
107
|
|
|
108
|
+
function armStarvationTimeout() {
|
|
109
|
+
if (!starvationTimeout && pendingRequests.length && Number.isFinite(starvationDelay) && starvationDelay >= 0) {
|
|
110
|
+
starvationTimeout = setTimeout(onWorkerStarvation, starvationDelay);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function clearStarvationTimeout() {
|
|
115
|
+
if (starvationTimeout) {
|
|
116
|
+
clearTimeout(starvationTimeout);
|
|
117
|
+
starvationTimeout = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onWorkerStarvation() {
|
|
122
|
+
starvationTimeout = null;
|
|
123
|
+
if (pendingRequests.length) {
|
|
124
|
+
const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
|
|
125
|
+
const inlineWorkerOptions = Object.assign({}, workerOptions, { useWebWorkers: false, workerURI: UNDEFINED_VALUE });
|
|
126
|
+
resolve(new CodecWorker({}, stream, inlineWorkerOptions, onInlineTaskFinished));
|
|
127
|
+
armStarvationTimeout();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onInlineTaskFinished() {
|
|
132
|
+
clearStarvationTimeout();
|
|
133
|
+
armStarvationTimeout();
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
function terminateWorker(workerData, workerOptions) {
|
|
99
137
|
const { config } = workerOptions;
|
|
100
138
|
const { terminateWorkerTimeout } = config;
|
package/lib/core/codec-worker.js
CHANGED
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
/* global Worker, URL, TransformStream,
|
|
29
|
+
/* global Worker, URL, TransformStream, AbortController, structuredClone, DOMException */
|
|
30
30
|
|
|
31
31
|
import {
|
|
32
32
|
UNDEFINED_VALUE,
|
|
33
33
|
UNDEFINED_TYPE,
|
|
34
34
|
FUNCTION_TYPE
|
|
35
35
|
} from "./constants.js";
|
|
36
|
+
import { getChunkSize } from "./configuration.js";
|
|
36
37
|
import {
|
|
37
38
|
CodecStream,
|
|
38
39
|
ChunkStream,
|
|
@@ -45,8 +46,10 @@ import {
|
|
|
45
46
|
} from "./streams/codec-stream.js";
|
|
46
47
|
|
|
47
48
|
const MODULE_WORKER_OPTIONS = { type: "module" };
|
|
49
|
+
const ERROR_EVENT_TYPE = "error";
|
|
50
|
+
const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
|
|
48
51
|
|
|
49
|
-
let webWorkerSupported, webWorkerURI, webWorkerOptions;
|
|
52
|
+
let webWorkerSupported, webWorkerSource, webWorkerURI, webWorkerOptions;
|
|
50
53
|
let transferStreamsSupported = true;
|
|
51
54
|
try {
|
|
52
55
|
transferStreamsSupported = typeof structuredClone == FUNCTION_TYPE && structuredClone(new DOMException("", "AbortError")).code !== UNDEFINED_VALUE;
|
|
@@ -70,8 +73,9 @@ class CodecWorker {
|
|
|
70
73
|
const { signal } = streamOptions;
|
|
71
74
|
Object.assign(workerData, {
|
|
72
75
|
busy: true,
|
|
76
|
+
generation: (workerData.generation || 0) + 1,
|
|
73
77
|
readable: readable
|
|
74
|
-
.pipeThrough(new ChunkStream(config
|
|
78
|
+
.pipeThrough(new ChunkStream(getChunkSize(config)))
|
|
75
79
|
.pipeThrough(new ProgressWatcherStream(streamOptions), { signal }),
|
|
76
80
|
writable,
|
|
77
81
|
options: Object.assign({}, options),
|
|
@@ -94,15 +98,17 @@ class CodecWorker {
|
|
|
94
98
|
});
|
|
95
99
|
},
|
|
96
100
|
onTaskFinished() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if (workerData.busy) {
|
|
102
|
+
const { resolveTerminated } = workerData;
|
|
103
|
+
if (resolveTerminated) {
|
|
104
|
+
workerData.resolveTerminated = null;
|
|
105
|
+
workerData.terminated = true;
|
|
106
|
+
workerData.worker.terminate();
|
|
107
|
+
resolveTerminated();
|
|
108
|
+
}
|
|
109
|
+
workerData.busy = false;
|
|
110
|
+
onTaskFinished(workerData);
|
|
103
111
|
}
|
|
104
|
-
workerData.busy = false;
|
|
105
|
-
onTaskFinished(workerData);
|
|
106
112
|
}
|
|
107
113
|
});
|
|
108
114
|
if (webWorkerSupported === UNDEFINED_VALUE) {
|
|
@@ -171,6 +177,7 @@ function createWebWorkerInterface(workerData, config) {
|
|
|
171
177
|
}
|
|
172
178
|
Object.assign(workerData, {
|
|
173
179
|
worker,
|
|
180
|
+
terminated: false,
|
|
174
181
|
interface: {
|
|
175
182
|
run: () => runWebWorker(workerData, { chunkSize, wasmURI, baseURI })
|
|
176
183
|
}
|
|
@@ -225,37 +232,85 @@ async function runWebWorker(workerData, config) {
|
|
|
225
232
|
result
|
|
226
233
|
});
|
|
227
234
|
const { readable, options } = workerData;
|
|
228
|
-
const { writable, closed } = watchClosedStream(workerData.writable);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
const { writable, closed, abortPipe } = watchClosedStream(workerData.writable);
|
|
236
|
+
let streamsTransferred;
|
|
237
|
+
try {
|
|
238
|
+
streamsTransferred = sendMessage({
|
|
239
|
+
type: MESSAGE_START,
|
|
240
|
+
options,
|
|
241
|
+
config,
|
|
242
|
+
readable,
|
|
243
|
+
writable
|
|
244
|
+
}, workerData);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
abortPipe();
|
|
247
|
+
try {
|
|
248
|
+
await closed;
|
|
249
|
+
} catch {
|
|
250
|
+
// ignored
|
|
251
|
+
}
|
|
252
|
+
workerData.onTaskFinished();
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
236
255
|
if (!streamsTransferred) {
|
|
237
256
|
Object.assign(workerData, {
|
|
238
257
|
reader: readable.getReader(),
|
|
239
258
|
writer: writable.getWriter()
|
|
240
259
|
});
|
|
241
260
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
await
|
|
261
|
+
try {
|
|
262
|
+
const resultValue = await result;
|
|
263
|
+
await closeWritable();
|
|
264
|
+
await closed;
|
|
265
|
+
return resultValue;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
await closeWritable();
|
|
268
|
+
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
269
|
+
abortPipe();
|
|
270
|
+
try {
|
|
271
|
+
await closed;
|
|
272
|
+
} catch {
|
|
273
|
+
// ignored
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function closeWritable() {
|
|
279
|
+
if (!streamsTransferred && !writable.locked) {
|
|
280
|
+
try {
|
|
281
|
+
await writable.getWriter().close();
|
|
282
|
+
} catch {
|
|
283
|
+
// ignored
|
|
284
|
+
}
|
|
285
|
+
}
|
|
245
286
|
}
|
|
246
|
-
await closed;
|
|
247
|
-
return resultValue;
|
|
248
287
|
}
|
|
249
288
|
|
|
250
289
|
function watchClosedStream(writableSource) {
|
|
290
|
+
const abortController = new AbortController();
|
|
251
291
|
const { writable, readable } = new TransformStream();
|
|
252
|
-
const closed = readable.pipeTo(writableSource, { preventClose: true });
|
|
253
|
-
|
|
292
|
+
const closed = readable.pipeTo(writableSource, { preventClose: true, preventAbort: true, signal: abortController.signal });
|
|
293
|
+
// the pipe can fail before `closed` is awaited (e.g. the destination write fails);
|
|
294
|
+
// mark the rejection as handled so it cannot kill the process in the meantime
|
|
295
|
+
closed.catch(() => { });
|
|
296
|
+
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function terminateWorker(workerData) {
|
|
300
|
+
const { worker } = workerData;
|
|
301
|
+
if (worker) {
|
|
302
|
+
try {
|
|
303
|
+
worker.terminate();
|
|
304
|
+
} catch {
|
|
305
|
+
// ignored
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
workerData.interface = null;
|
|
254
309
|
}
|
|
255
310
|
|
|
256
311
|
function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true) {
|
|
257
312
|
let worker, resolvedURI, resolvedOptions;
|
|
258
|
-
if (webWorkerURI === UNDEFINED_VALUE) {
|
|
313
|
+
if (webWorkerURI === UNDEFINED_VALUE || webWorkerSource !== url) {
|
|
259
314
|
// deno-lint-ignore valid-typeof
|
|
260
315
|
const isFunctionURI = typeof url == FUNCTION_TYPE;
|
|
261
316
|
if (isFunctionURI) {
|
|
@@ -312,16 +367,36 @@ function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true)
|
|
|
312
367
|
}
|
|
313
368
|
}
|
|
314
369
|
}
|
|
370
|
+
webWorkerSource = url;
|
|
315
371
|
webWorkerURI = resolvedURI;
|
|
316
372
|
webWorkerOptions = resolvedOptions;
|
|
317
373
|
} else {
|
|
318
374
|
worker = new Worker(webWorkerURI, webWorkerOptions);
|
|
319
375
|
}
|
|
320
376
|
worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData));
|
|
377
|
+
worker.addEventListener(ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
378
|
+
worker.addEventListener(MESSAGE_ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
321
379
|
return worker;
|
|
322
380
|
}
|
|
323
381
|
|
|
324
|
-
function
|
|
382
|
+
function onWorkerError(event, workerData) {
|
|
383
|
+
if (event.preventDefault) {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
}
|
|
386
|
+
const { rejectResult, writer, onTaskFinished } = workerData;
|
|
387
|
+
// the worker is unusable (e.g. its script failed to load); terminate it and
|
|
388
|
+
// fail the current task instead of letting it hang forever
|
|
389
|
+
terminateWorker(workerData);
|
|
390
|
+
if (rejectResult) {
|
|
391
|
+
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
392
|
+
if (writer) {
|
|
393
|
+
writer.releaseLock();
|
|
394
|
+
}
|
|
395
|
+
onTaskFinished();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function sendMessage(message, { worker, writer, transferStreams }) {
|
|
325
400
|
try {
|
|
326
401
|
const { value, readable, writable } = message;
|
|
327
402
|
const transferables = [];
|
|
@@ -355,14 +430,16 @@ function sendMessage(message, { worker, writer, onTaskFinished, transferStreams
|
|
|
355
430
|
if (writer) {
|
|
356
431
|
writer.releaseLock();
|
|
357
432
|
}
|
|
358
|
-
onTaskFinished();
|
|
359
433
|
throw error;
|
|
360
434
|
}
|
|
361
435
|
}
|
|
362
436
|
|
|
363
437
|
async function onMessage({ data }, workerData) {
|
|
364
438
|
const { type, value, messageId, result, error } = data;
|
|
365
|
-
const { reader, writer, resolveResult, rejectResult, onTaskFinished } = workerData;
|
|
439
|
+
const { reader, writer, resolveResult, rejectResult, onTaskFinished, generation } = workerData;
|
|
440
|
+
// continuations below can settle after the worker slot has been reassigned to a new
|
|
441
|
+
// task (e.g. a pending pull read failing late); they must not touch the new task
|
|
442
|
+
const stale = () => workerData.generation != generation;
|
|
366
443
|
try {
|
|
367
444
|
if (error) {
|
|
368
445
|
const { message, stack, code, name, outputSize } = error;
|
|
@@ -372,23 +449,32 @@ async function onMessage({ data }, workerData) {
|
|
|
372
449
|
} else {
|
|
373
450
|
if (type == MESSAGE_PULL) {
|
|
374
451
|
const { value, done } = await reader.read();
|
|
375
|
-
|
|
452
|
+
if (!stale()) {
|
|
453
|
+
sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData);
|
|
454
|
+
}
|
|
376
455
|
}
|
|
377
456
|
if (type == MESSAGE_DATA) {
|
|
378
457
|
await writer.ready;
|
|
379
458
|
await writer.write(new Uint8Array(value));
|
|
380
|
-
|
|
459
|
+
if (!stale()) {
|
|
460
|
+
sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData);
|
|
461
|
+
}
|
|
381
462
|
}
|
|
382
463
|
if (type == MESSAGE_CLOSE) {
|
|
383
464
|
close(null, result);
|
|
384
465
|
}
|
|
385
466
|
}
|
|
386
467
|
} catch (error) {
|
|
387
|
-
|
|
388
|
-
|
|
468
|
+
if (!stale()) {
|
|
469
|
+
terminateWorker(workerData);
|
|
470
|
+
close(error);
|
|
471
|
+
}
|
|
389
472
|
}
|
|
390
473
|
|
|
391
474
|
function close(error, result) {
|
|
475
|
+
if (stale()) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
392
478
|
if (error) {
|
|
393
479
|
rejectResult(error);
|
|
394
480
|
} else {
|
|
@@ -48,6 +48,7 @@ const DEFAULT_CONFIGURATION = {
|
|
|
48
48
|
chunkSize: 64 * 1024,
|
|
49
49
|
maxWorkers,
|
|
50
50
|
terminateWorkerTimeout: 5000,
|
|
51
|
+
workerStarvationTimeout: 5000,
|
|
51
52
|
useWebWorkers: true,
|
|
52
53
|
useCompressionStream: true,
|
|
53
54
|
CompressionStream: typeof CompressionStream != UNDEFINED_TYPE && CompressionStream,
|
|
@@ -76,6 +77,7 @@ function configure(configuration) {
|
|
|
76
77
|
chunkSize,
|
|
77
78
|
maxWorkers,
|
|
78
79
|
terminateWorkerTimeout,
|
|
80
|
+
workerStarvationTimeout,
|
|
79
81
|
useCompressionStream,
|
|
80
82
|
useWebWorkers,
|
|
81
83
|
CompressionStream,
|
|
@@ -91,6 +93,7 @@ function configure(configuration) {
|
|
|
91
93
|
setIfDefined("chunkSize", chunkSize);
|
|
92
94
|
setIfDefined("maxWorkers", maxWorkers);
|
|
93
95
|
setIfDefined("terminateWorkerTimeout", terminateWorkerTimeout);
|
|
96
|
+
setIfDefined("workerStarvationTimeout", workerStarvationTimeout);
|
|
94
97
|
setIfDefined("useCompressionStream", useCompressionStream);
|
|
95
98
|
setIfDefined("useWebWorkers", useWebWorkers);
|
|
96
99
|
setIfDefined("CompressionStream", CompressionStream);
|
package/lib/core/constants.js
CHANGED
|
@@ -41,6 +41,7 @@ const CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
|
|
|
41
41
|
const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
|
|
42
42
|
const ZIP64_END_OF_CENTRAL_DIR_SIGNATURE = 0x06064b50;
|
|
43
43
|
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = 0x07064b50;
|
|
44
|
+
const CENTRAL_FILE_HEADER_LENGTH = 46;
|
|
44
45
|
const END_OF_CENTRAL_DIR_LENGTH = 22;
|
|
45
46
|
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH = 20;
|
|
46
47
|
const ZIP64_END_OF_CENTRAL_DIR_LENGTH = 56;
|
|
@@ -88,9 +89,11 @@ const VERSION_AES = 0x33;
|
|
|
88
89
|
const DIRECTORY_SIGNATURE = "/";
|
|
89
90
|
|
|
90
91
|
const HEADER_SIZE = 30;
|
|
92
|
+
const HEADER_OFFSET_VERSION = 0;
|
|
91
93
|
const HEADER_OFFSET_SIGNATURE = 10;
|
|
92
94
|
const HEADER_OFFSET_COMPRESSED_SIZE = 14;
|
|
93
95
|
const HEADER_OFFSET_UNCOMPRESSED_SIZE = 18;
|
|
96
|
+
const LOCAL_HEADER_COMMON_OFFSET = 4;
|
|
94
97
|
|
|
95
98
|
const MAX_DATE = new Date(2107, 11, 31);
|
|
96
99
|
const MIN_DATE = new Date(1980, 0, 1);
|
|
@@ -113,6 +116,7 @@ export {
|
|
|
113
116
|
LOCAL_FILE_HEADER_SIGNATURE,
|
|
114
117
|
DATA_DESCRIPTOR_RECORD_SIGNATURE,
|
|
115
118
|
CENTRAL_FILE_HEADER_SIGNATURE,
|
|
119
|
+
CENTRAL_FILE_HEADER_LENGTH,
|
|
116
120
|
END_OF_CENTRAL_DIR_SIGNATURE,
|
|
117
121
|
ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE,
|
|
118
122
|
ZIP64_END_OF_CENTRAL_DIR_SIGNATURE,
|
|
@@ -156,9 +160,11 @@ export {
|
|
|
156
160
|
VERSION_ZIP64,
|
|
157
161
|
VERSION_AES,
|
|
158
162
|
HEADER_SIZE,
|
|
163
|
+
HEADER_OFFSET_VERSION,
|
|
159
164
|
HEADER_OFFSET_SIGNATURE,
|
|
160
165
|
HEADER_OFFSET_COMPRESSED_SIZE,
|
|
161
166
|
HEADER_OFFSET_UNCOMPRESSED_SIZE,
|
|
167
|
+
LOCAL_HEADER_COMMON_OFFSET,
|
|
162
168
|
DIRECTORY_SIGNATURE,
|
|
163
169
|
MIN_DATE,
|
|
164
170
|
MAX_DATE,
|
package/lib/core/io.js
CHANGED
|
@@ -44,6 +44,7 @@ const ERR_WRITER_NOT_INITIALIZED = "Writer not initialized";
|
|
|
44
44
|
|
|
45
45
|
const CONTENT_TYPE_TEXT_PLAIN = "text/plain";
|
|
46
46
|
const HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
|
|
47
|
+
const HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
|
|
47
48
|
const HTTP_HEADER_CONTENT_RANGE = "Content-Range";
|
|
48
49
|
const HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
|
|
49
50
|
const HTTP_HEADER_RANGE = "Range";
|
|
@@ -168,7 +169,7 @@ class Data64URIWriter extends Writer {
|
|
|
168
169
|
|
|
169
170
|
writeUint8Array(array) {
|
|
170
171
|
const writer = this;
|
|
171
|
-
let indexArray
|
|
172
|
+
let indexArray;
|
|
172
173
|
let dataString = writer.pending;
|
|
173
174
|
const delta = writer.pending.length;
|
|
174
175
|
writer.pending = "";
|
|
@@ -178,12 +179,10 @@ class Data64URIWriter extends Writer {
|
|
|
178
179
|
for (; indexArray < array.length; indexArray++) {
|
|
179
180
|
writer.pending += String.fromCharCode(array[indexArray]);
|
|
180
181
|
}
|
|
181
|
-
if (dataString.length) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
writer.pending += dataString;
|
|
186
|
-
}
|
|
182
|
+
if (dataString.length > 2) {
|
|
183
|
+
writer.data += btoa(dataString);
|
|
184
|
+
} else {
|
|
185
|
+
writer.pending = dataString + writer.pending;
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
188
|
|
|
@@ -230,6 +229,9 @@ class BlobWriter extends Stream {
|
|
|
230
229
|
}
|
|
231
230
|
});
|
|
232
231
|
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
|
+
writer.blob.catch(() => { });
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
getData() {
|
|
@@ -314,7 +316,8 @@ function createHttpReader(httpReader, url, options) {
|
|
|
314
316
|
preventHeadRequest,
|
|
315
317
|
useRangeHeader,
|
|
316
318
|
forceRangeRequests,
|
|
317
|
-
combineSizeEocd
|
|
319
|
+
combineSizeEocd,
|
|
320
|
+
fetch
|
|
318
321
|
} = options;
|
|
319
322
|
options = Object.assign({}, options);
|
|
320
323
|
delete options.preventHeadRequest;
|
|
@@ -322,13 +325,15 @@ function createHttpReader(httpReader, url, options) {
|
|
|
322
325
|
delete options.forceRangeRequests;
|
|
323
326
|
delete options.combineSizeEocd;
|
|
324
327
|
delete options.useXHR;
|
|
328
|
+
delete options.fetch;
|
|
325
329
|
Object.assign(httpReader, {
|
|
326
330
|
url,
|
|
327
331
|
options,
|
|
328
332
|
preventHeadRequest,
|
|
329
333
|
useRangeHeader,
|
|
330
334
|
forceRangeRequests,
|
|
331
|
-
combineSizeEocd
|
|
335
|
+
combineSizeEocd,
|
|
336
|
+
fetch
|
|
332
337
|
});
|
|
333
338
|
}
|
|
334
339
|
|
|
@@ -347,7 +352,10 @@ async function initHttpReader(httpReader, sendRequest, getRequestData) {
|
|
|
347
352
|
throw new Error(ERR_HTTP_RANGE);
|
|
348
353
|
} else {
|
|
349
354
|
if (combineSizeEocd) {
|
|
350
|
-
|
|
355
|
+
const eocdCache = new Uint8Array(await response.arrayBuffer());
|
|
356
|
+
if (response.status == 206 && eocdCache.length == END_OF_CENTRAL_DIR_LENGTH) {
|
|
357
|
+
httpReader.eocdCache = eocdCache;
|
|
358
|
+
}
|
|
351
359
|
}
|
|
352
360
|
let contentSize;
|
|
353
361
|
const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
|
|
@@ -393,7 +401,18 @@ async function readUint8ArrayHttpReader(httpReader, index, length, sendRequest,
|
|
|
393
401
|
if (response.status != 206) {
|
|
394
402
|
throw new Error(ERR_HTTP_RANGE);
|
|
395
403
|
}
|
|
396
|
-
|
|
404
|
+
const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
|
|
405
|
+
if (contentRangeHeader) {
|
|
406
|
+
const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
|
|
407
|
+
if (!Number.isNaN(rangeStart) && rangeStart != index) {
|
|
408
|
+
throw new Error(ERR_HTTP_RANGE);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
412
|
+
if (data.length != length) {
|
|
413
|
+
throw new Error(ERR_HTTP_RANGE);
|
|
414
|
+
}
|
|
415
|
+
return data;
|
|
397
416
|
}
|
|
398
417
|
} else {
|
|
399
418
|
const { data } = httpReader;
|
|
@@ -430,9 +449,9 @@ async function getXMLHttpRequestData(httpReader) {
|
|
|
430
449
|
async function getRequestData(httpReader, sendRequest) {
|
|
431
450
|
const response = await sendRequest(HTTP_METHOD_GET, httpReader, getHeaders(httpReader));
|
|
432
451
|
httpReader.data = new Uint8Array(await response.arrayBuffer());
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
+
httpReader.size = httpReader.data.length;
|
|
436
455
|
}
|
|
437
456
|
|
|
438
457
|
async function getContentLength(httpReader, sendRequest, getRequestData) {
|
|
@@ -441,7 +460,7 @@ async function getContentLength(httpReader, sendRequest, getRequestData) {
|
|
|
441
460
|
} else {
|
|
442
461
|
const response = await sendRequest(HTTP_METHOD_HEAD, httpReader, getHeaders(httpReader));
|
|
443
462
|
const contentLength = response.headers.get(HTTP_HEADER_CONTENT_LENGTH);
|
|
444
|
-
if (contentLength) {
|
|
463
|
+
if (contentLength && !response.headers.get(HTTP_HEADER_CONTENT_ENCODING)) {
|
|
445
464
|
httpReader.size = Number(contentLength);
|
|
446
465
|
} else {
|
|
447
466
|
await getRequestData(httpReader, httpReader.options);
|
|
@@ -449,8 +468,8 @@ async function getContentLength(httpReader, sendRequest, getRequestData) {
|
|
|
449
468
|
}
|
|
450
469
|
}
|
|
451
470
|
|
|
452
|
-
async function sendFetchRequest(method, { options, url }, headers) {
|
|
453
|
-
const response = await
|
|
471
|
+
async function sendFetchRequest(method, { fetch: fetchFunction = fetch, options, url }, headers) {
|
|
472
|
+
const response = await fetchFunction(url, Object.assign({}, options, { method, headers }));
|
|
454
473
|
if (response.status < 400) {
|
|
455
474
|
return response;
|
|
456
475
|
} else {
|
|
@@ -496,7 +515,8 @@ class HttpReader extends Reader {
|
|
|
496
515
|
super();
|
|
497
516
|
Object.assign(this, {
|
|
498
517
|
url,
|
|
499
|
-
|
|
518
|
+
// an explicit fetch implementation takes precedence over useXHR
|
|
519
|
+
reader: options.useXHR && !options.fetch ? new XHRReader(url, options) : new FetchReader(url, options)
|
|
500
520
|
});
|
|
501
521
|
}
|
|
502
522
|
|
|
@@ -521,8 +541,7 @@ class HttpReader extends Reader {
|
|
|
521
541
|
class HttpRangeReader extends HttpReader {
|
|
522
542
|
|
|
523
543
|
constructor(url, options = {}) {
|
|
524
|
-
options
|
|
525
|
-
super(url, options);
|
|
544
|
+
super(url, Object.assign({}, options, { useRangeHeader: true }));
|
|
526
545
|
}
|
|
527
546
|
}
|
|
528
547
|
|
|
@@ -596,7 +615,7 @@ class SplitDataReader extends Reader {
|
|
|
596
615
|
reader.lastDiskNumber = 0;
|
|
597
616
|
reader.lastDiskOffset = 0;
|
|
598
617
|
await Promise.all(readers.map(async (diskReader, indexDiskReader) => {
|
|
599
|
-
await diskReader
|
|
618
|
+
await initStream(diskReader);
|
|
600
619
|
if (indexDiskReader != readers.length - 1) {
|
|
601
620
|
reader.lastDiskOffset += diskReader.size;
|
|
602
621
|
}
|
|
@@ -680,14 +699,26 @@ class SplitDataWriter extends Stream {
|
|
|
680
699
|
writer.diskOffset += diskSourceWriter.size;
|
|
681
700
|
writer.diskNumber++;
|
|
682
701
|
diskWriter = null;
|
|
683
|
-
|
|
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
|
+
writer.availableSize = writer.maxSize;
|
|
705
|
+
if (chunk.length > availableSize) {
|
|
706
|
+
await this.write(chunk.subarray(availableSize));
|
|
707
|
+
}
|
|
684
708
|
} else {
|
|
685
709
|
await writeChunk(chunk);
|
|
686
710
|
}
|
|
687
711
|
},
|
|
688
712
|
async close() {
|
|
689
|
-
|
|
690
|
-
|
|
713
|
+
if (diskWriter) {
|
|
714
|
+
await diskWriter.ready;
|
|
715
|
+
await closeDisk();
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
async abort(reason) {
|
|
719
|
+
if (diskWriter) {
|
|
720
|
+
await diskWriter.abort(reason);
|
|
721
|
+
}
|
|
691
722
|
}
|
|
692
723
|
});
|
|
693
724
|
Object.defineProperty(writer, PROPERTY_NAME_WRITABLE, {
|
|
@@ -702,7 +733,6 @@ class SplitDataWriter extends Stream {
|
|
|
702
733
|
await diskWriter.ready;
|
|
703
734
|
await diskWriter.write(chunk);
|
|
704
735
|
diskSourceWriter.size += chunkLength;
|
|
705
|
-
writer.size += chunkLength;
|
|
706
736
|
writer.availableSize -= chunkLength;
|
|
707
737
|
}
|
|
708
738
|
}
|
package/lib/core/options.js
CHANGED
|
@@ -38,6 +38,7 @@ const OPTION_SIGNAL = "signal";
|
|
|
38
38
|
const OPTION_CHECK_PASSWORD_ONLY = "checkPasswordOnly";
|
|
39
39
|
const OPTION_CHECK_OVERLAPPING_ENTRY_ONLY = "checkOverlappingEntryOnly";
|
|
40
40
|
const OPTION_CHECK_OVERLAPPING_ENTRY = "checkOverlappingEntry";
|
|
41
|
+
const OPTION_CHECK_AMBIGUITY = "checkAmbiguity";
|
|
41
42
|
const OPTION_CHECK_SIGNATURE = "checkSignature";
|
|
42
43
|
const OPTION_USE_WEB_WORKERS = "useWebWorkers";
|
|
43
44
|
const OPTION_USE_COMPRESSION_STREAM = "useCompressionStream";
|
|
@@ -71,6 +72,7 @@ export {
|
|
|
71
72
|
OPTION_CHECK_PASSWORD_ONLY,
|
|
72
73
|
OPTION_CHECK_OVERLAPPING_ENTRY_ONLY,
|
|
73
74
|
OPTION_CHECK_OVERLAPPING_ENTRY,
|
|
75
|
+
OPTION_CHECK_AMBIGUITY,
|
|
74
76
|
OPTION_CHECK_SIGNATURE,
|
|
75
77
|
OPTION_USE_WEB_WORKERS,
|
|
76
78
|
OPTION_USE_COMPRESSION_STREAM,
|
|
@@ -70,13 +70,12 @@ let DERIVE_BITS_SUPPORTED = CRYPTO_API_SUPPORTED && SUBTLE_API_SUPPORTED && type
|
|
|
70
70
|
|
|
71
71
|
class AESDecryptionStream extends TransformStream {
|
|
72
72
|
|
|
73
|
-
constructor({ password, rawPassword,
|
|
73
|
+
constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly }) {
|
|
74
74
|
super({
|
|
75
75
|
start() {
|
|
76
76
|
Object.assign(this, {
|
|
77
77
|
ready: new Promise(resolve => this.resolveReady = resolve),
|
|
78
78
|
password: encodePassword(password, rawPassword),
|
|
79
|
-
signed,
|
|
80
79
|
strength: encryptionStrength - 1,
|
|
81
80
|
pending: new Uint8Array()
|
|
82
81
|
});
|
|
@@ -105,7 +104,6 @@ class AESDecryptionStream extends TransformStream {
|
|
|
105
104
|
},
|
|
106
105
|
async flush(controller) {
|
|
107
106
|
const {
|
|
108
|
-
signed,
|
|
109
107
|
ctr,
|
|
110
108
|
hmac,
|
|
111
109
|
pending,
|
|
@@ -122,13 +120,15 @@ class AESDecryptionStream extends TransformStream {
|
|
|
122
120
|
const decryptedChunk = ctr.update(encryptedChunk);
|
|
123
121
|
decryptedChunkArray = fromBits(codecBytes, decryptedChunk);
|
|
124
122
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
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
|
+
let invalidSignature = pending.length < SIGNATURE_LENGTH ? 1 : 0;
|
|
127
|
+
for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) {
|
|
128
|
+
invalidSignature |= signature[indexSignature] ^ originalSignature[indexSignature];
|
|
129
|
+
}
|
|
130
|
+
if (invalidSignature) {
|
|
131
|
+
throw new Error(ERR_INVALID_SIGNATURE);
|
|
132
132
|
}
|
|
133
133
|
controller.enqueue(decryptedChunkArray);
|
|
134
134
|
}
|
|
@@ -206,11 +206,11 @@ function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignat
|
|
|
206
206
|
hmac,
|
|
207
207
|
pending
|
|
208
208
|
} = aesCrypto;
|
|
209
|
-
const inputLength = input.length - paddingEnd;
|
|
210
209
|
if (pending.length) {
|
|
211
210
|
input = concat(pending, input);
|
|
212
|
-
output = expand(output, inputLength - (inputLength % BLOCK_LENGTH));
|
|
213
211
|
}
|
|
212
|
+
const inputLength = input.length - paddingEnd;
|
|
213
|
+
output = expand(output, paddingStart + (inputLength - (inputLength % BLOCK_LENGTH)));
|
|
214
214
|
let offset;
|
|
215
215
|
for (offset = 0; offset <= inputLength - BLOCK_LENGTH; offset += BLOCK_LENGTH) {
|
|
216
216
|
const inputChunk = toBits(codecBytes, subarray(input, offset, offset + BLOCK_LENGTH));
|