@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/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,14 +26,16 @@
|
|
|
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 {
|
|
38
|
+
CODEC_DEFLATE,
|
|
37
39
|
CodecStream,
|
|
38
40
|
ChunkStream,
|
|
39
41
|
MESSAGE_EVENT_TYPE,
|
|
@@ -45,8 +47,10 @@ import {
|
|
|
45
47
|
} from "./streams/codec-stream.js";
|
|
46
48
|
|
|
47
49
|
const MODULE_WORKER_OPTIONS = { type: "module" };
|
|
50
|
+
const ERROR_EVENT_TYPE = "error";
|
|
51
|
+
const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
|
|
48
52
|
|
|
49
|
-
let webWorkerSupported, webWorkerURI, webWorkerOptions;
|
|
53
|
+
let webWorkerSupported, webWorkerSource, webWorkerURI, webWorkerOptions;
|
|
50
54
|
let transferStreamsSupported = true;
|
|
51
55
|
try {
|
|
52
56
|
transferStreamsSupported = typeof structuredClone == FUNCTION_TYPE && structuredClone(new DOMException("", "AbortError")).code !== UNDEFINED_VALUE;
|
|
@@ -70,8 +74,9 @@ class CodecWorker {
|
|
|
70
74
|
const { signal } = streamOptions;
|
|
71
75
|
Object.assign(workerData, {
|
|
72
76
|
busy: true,
|
|
77
|
+
generation: (workerData.generation || 0) + 1,
|
|
73
78
|
readable: readable
|
|
74
|
-
.pipeThrough(new ChunkStream(config
|
|
79
|
+
.pipeThrough(new ChunkStream(getChunkSize(config)))
|
|
75
80
|
.pipeThrough(new ProgressWatcherStream(streamOptions), { signal }),
|
|
76
81
|
writable,
|
|
77
82
|
options: Object.assign({}, options),
|
|
@@ -94,15 +99,17 @@ class CodecWorker {
|
|
|
94
99
|
});
|
|
95
100
|
},
|
|
96
101
|
onTaskFinished() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
if (workerData.busy) {
|
|
103
|
+
const { resolveTerminated } = workerData;
|
|
104
|
+
if (resolveTerminated) {
|
|
105
|
+
workerData.resolveTerminated = null;
|
|
106
|
+
workerData.terminated = true;
|
|
107
|
+
workerData.worker.terminate();
|
|
108
|
+
resolveTerminated();
|
|
109
|
+
}
|
|
110
|
+
workerData.busy = false;
|
|
111
|
+
onTaskFinished(workerData);
|
|
103
112
|
}
|
|
104
|
-
workerData.busy = false;
|
|
105
|
-
onTaskFinished(workerData);
|
|
106
113
|
}
|
|
107
114
|
});
|
|
108
115
|
if (webWorkerSupported === UNDEFINED_VALUE) {
|
|
@@ -171,6 +178,7 @@ function createWebWorkerInterface(workerData, config) {
|
|
|
171
178
|
}
|
|
172
179
|
Object.assign(workerData, {
|
|
173
180
|
worker,
|
|
181
|
+
terminated: false,
|
|
174
182
|
interface: {
|
|
175
183
|
run: () => runWebWorker(workerData, { chunkSize, wasmURI, baseURI })
|
|
176
184
|
}
|
|
@@ -186,7 +194,15 @@ async function runWorker({ options, readable, writable, onTaskFinished }, config
|
|
|
186
194
|
try {
|
|
187
195
|
await initModule(config);
|
|
188
196
|
} catch {
|
|
189
|
-
|
|
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
|
+
const ZlibStream = options.codecType.startsWith(CODEC_DEFLATE) ?
|
|
201
|
+
config.CompressionStreamZlib :
|
|
202
|
+
config.DecompressionStreamZlib;
|
|
203
|
+
if (!ZlibStream || ZlibStream.requiresModule) {
|
|
204
|
+
options.useCompressionStream = true;
|
|
205
|
+
}
|
|
190
206
|
}
|
|
191
207
|
}
|
|
192
208
|
codecStream = new CodecStream(options, config);
|
|
@@ -225,37 +241,85 @@ async function runWebWorker(workerData, config) {
|
|
|
225
241
|
result
|
|
226
242
|
});
|
|
227
243
|
const { readable, options } = workerData;
|
|
228
|
-
const { writable, closed } = watchClosedStream(workerData.writable);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
244
|
+
const { writable, closed, abortPipe } = watchClosedStream(workerData.writable);
|
|
245
|
+
let streamsTransferred;
|
|
246
|
+
try {
|
|
247
|
+
streamsTransferred = sendMessage({
|
|
248
|
+
type: MESSAGE_START,
|
|
249
|
+
options,
|
|
250
|
+
config,
|
|
251
|
+
readable,
|
|
252
|
+
writable
|
|
253
|
+
}, workerData);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
abortPipe();
|
|
256
|
+
try {
|
|
257
|
+
await closed;
|
|
258
|
+
} catch {
|
|
259
|
+
// ignored
|
|
260
|
+
}
|
|
261
|
+
workerData.onTaskFinished();
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
236
264
|
if (!streamsTransferred) {
|
|
237
265
|
Object.assign(workerData, {
|
|
238
266
|
reader: readable.getReader(),
|
|
239
267
|
writer: writable.getWriter()
|
|
240
268
|
});
|
|
241
269
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
await
|
|
270
|
+
try {
|
|
271
|
+
const resultValue = await result;
|
|
272
|
+
await closeWritable();
|
|
273
|
+
await closed;
|
|
274
|
+
return resultValue;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
await closeWritable();
|
|
277
|
+
// unblock the pipe watcher if the worker died before closing its end of the stream
|
|
278
|
+
abortPipe();
|
|
279
|
+
try {
|
|
280
|
+
await closed;
|
|
281
|
+
} catch {
|
|
282
|
+
// ignored
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function closeWritable() {
|
|
288
|
+
if (!streamsTransferred && !writable.locked) {
|
|
289
|
+
try {
|
|
290
|
+
await writable.getWriter().close();
|
|
291
|
+
} catch {
|
|
292
|
+
// ignored
|
|
293
|
+
}
|
|
294
|
+
}
|
|
245
295
|
}
|
|
246
|
-
await closed;
|
|
247
|
-
return resultValue;
|
|
248
296
|
}
|
|
249
297
|
|
|
250
298
|
function watchClosedStream(writableSource) {
|
|
299
|
+
const abortController = new AbortController();
|
|
251
300
|
const { writable, readable } = new TransformStream();
|
|
252
|
-
const closed = readable.pipeTo(writableSource, { preventClose: true });
|
|
253
|
-
|
|
301
|
+
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
|
+
closed.catch(() => { });
|
|
305
|
+
return { writable, closed, abortPipe: () => abortController.abort() };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function terminateWorker(workerData) {
|
|
309
|
+
const { worker } = workerData;
|
|
310
|
+
if (worker) {
|
|
311
|
+
try {
|
|
312
|
+
worker.terminate();
|
|
313
|
+
} catch {
|
|
314
|
+
// ignored
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
workerData.interface = null;
|
|
254
318
|
}
|
|
255
319
|
|
|
256
320
|
function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true) {
|
|
257
321
|
let worker, resolvedURI, resolvedOptions;
|
|
258
|
-
if (webWorkerURI === UNDEFINED_VALUE) {
|
|
322
|
+
if (webWorkerURI === UNDEFINED_VALUE || webWorkerSource !== url) {
|
|
259
323
|
// deno-lint-ignore valid-typeof
|
|
260
324
|
const isFunctionURI = typeof url == FUNCTION_TYPE;
|
|
261
325
|
if (isFunctionURI) {
|
|
@@ -312,16 +376,36 @@ function getWebWorker(url, baseURI, workerData, isModuleType, useBlobURI = true)
|
|
|
312
376
|
}
|
|
313
377
|
}
|
|
314
378
|
}
|
|
379
|
+
webWorkerSource = url;
|
|
315
380
|
webWorkerURI = resolvedURI;
|
|
316
381
|
webWorkerOptions = resolvedOptions;
|
|
317
382
|
} else {
|
|
318
383
|
worker = new Worker(webWorkerURI, webWorkerOptions);
|
|
319
384
|
}
|
|
320
385
|
worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData));
|
|
386
|
+
worker.addEventListener(ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
387
|
+
worker.addEventListener(MESSAGE_ERROR_EVENT_TYPE, event => onWorkerError(event, workerData));
|
|
321
388
|
return worker;
|
|
322
389
|
}
|
|
323
390
|
|
|
324
|
-
function
|
|
391
|
+
function onWorkerError(event, workerData) {
|
|
392
|
+
if (event.preventDefault) {
|
|
393
|
+
event.preventDefault();
|
|
394
|
+
}
|
|
395
|
+
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
|
+
terminateWorker(workerData);
|
|
399
|
+
if (rejectResult) {
|
|
400
|
+
rejectResult(event.error || new Error(event.message || ERROR_EVENT_TYPE));
|
|
401
|
+
if (writer) {
|
|
402
|
+
writer.releaseLock();
|
|
403
|
+
}
|
|
404
|
+
onTaskFinished();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function sendMessage(message, { worker, writer, transferStreams }) {
|
|
325
409
|
try {
|
|
326
410
|
const { value, readable, writable } = message;
|
|
327
411
|
const transferables = [];
|
|
@@ -355,14 +439,16 @@ function sendMessage(message, { worker, writer, onTaskFinished, transferStreams
|
|
|
355
439
|
if (writer) {
|
|
356
440
|
writer.releaseLock();
|
|
357
441
|
}
|
|
358
|
-
onTaskFinished();
|
|
359
442
|
throw error;
|
|
360
443
|
}
|
|
361
444
|
}
|
|
362
445
|
|
|
363
446
|
async function onMessage({ data }, workerData) {
|
|
364
447
|
const { type, value, messageId, result, error } = data;
|
|
365
|
-
const { reader, writer, resolveResult, rejectResult, onTaskFinished } = workerData;
|
|
448
|
+
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
|
+
const stale = () => workerData.generation != generation;
|
|
366
452
|
try {
|
|
367
453
|
if (error) {
|
|
368
454
|
const { message, stack, code, name, outputSize } = error;
|
|
@@ -372,23 +458,32 @@ async function onMessage({ data }, workerData) {
|
|
|
372
458
|
} else {
|
|
373
459
|
if (type == MESSAGE_PULL) {
|
|
374
460
|
const { value, done } = await reader.read();
|
|
375
|
-
|
|
461
|
+
if (!stale()) {
|
|
462
|
+
sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData);
|
|
463
|
+
}
|
|
376
464
|
}
|
|
377
465
|
if (type == MESSAGE_DATA) {
|
|
378
466
|
await writer.ready;
|
|
379
467
|
await writer.write(new Uint8Array(value));
|
|
380
|
-
|
|
468
|
+
if (!stale()) {
|
|
469
|
+
sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData);
|
|
470
|
+
}
|
|
381
471
|
}
|
|
382
472
|
if (type == MESSAGE_CLOSE) {
|
|
383
473
|
close(null, result);
|
|
384
474
|
}
|
|
385
475
|
}
|
|
386
476
|
} catch (error) {
|
|
387
|
-
|
|
388
|
-
|
|
477
|
+
if (!stale()) {
|
|
478
|
+
terminateWorker(workerData);
|
|
479
|
+
close(error);
|
|
480
|
+
}
|
|
389
481
|
}
|
|
390
482
|
|
|
391
483
|
function close(error, result) {
|
|
484
|
+
if (stale()) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
392
487
|
if (error) {
|
|
393
488
|
rejectResult(error);
|
|
394
489
|
} 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,
|