@zip.js/zip.js 2.14.1 → 2.16.0

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.
Files changed (41) hide show
  1. package/BENCHMARKS.md +21 -11
  2. package/deno.json +1 -1
  3. package/dist/zip-core-external.js +217 -98
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +217 -98
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +217 -98
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +217 -98
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +217 -98
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +219 -100
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +219 -100
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +219 -100
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-native.js +219 -100
  20. package/dist/zip-native.min.js +1 -1
  21. package/dist/zip-web-worker-native.js +1 -1
  22. package/dist/zip-web-worker.js +1 -1
  23. package/dist/zip.js +219 -100
  24. package/dist/zip.min.js +1 -1
  25. package/index-native.cjs +219 -100
  26. package/index-native.min.js +1 -1
  27. package/index.cjs +219 -100
  28. package/index.d.cts +46 -13
  29. package/index.d.ts +46 -13
  30. package/index.min.js +1 -1
  31. package/lib/core/codec-pool.js +35 -19
  32. package/lib/core/codec-worker.js +4 -2
  33. package/lib/core/io.js +3 -2
  34. package/lib/core/streams/zip-entry-stream.js +151 -66
  35. package/lib/core/util/decode-text.js +15 -1
  36. package/lib/core/version.js +1 -1
  37. package/lib/core/web-worker-inline-native.js +1 -1
  38. package/lib/core/web-worker-inline-wasm.js +1 -1
  39. package/lib/core/zip-reader.js +12 -10
  40. package/lib/core/zlib-streams-inline.js +1 -1
  41. package/package.json +1 -1
package/BENCHMARKS.md CHANGED
@@ -39,8 +39,11 @@ The page answers four questions, each from one or two scripts of the harness:
39
39
  about 2 KB each, and the 8 × 8 MB and 256 MB workloads are text.
40
40
  - **Isolation.** In `bench.js` and `bench-backends.js`, each (library, operation, workload)
41
41
  combination runs in its own freshly spawned Node process under macOS's `/usr/bin/time -l`, so
42
- the libraries never share a heap and peak memory is per library. The codec, encryption and
43
- runtime scripts run in-process, with one warmup run before the timed ones.
42
+ the libraries never share a heap and peak memory is per library. Every row of those two
43
+ scripts includes what a fresh process pays on its first operation: the JIT warm-up of each
44
+ library and, for the zip.js WebAssembly rows, the module instantiation, about 20 ms. The
45
+ codec, encryption and runtime scripts run in-process, with one warmup run before the timed
46
+ ones.
44
47
  - **Timing.** `performance.now()` around the measured operation. Each combination runs 3 times
45
48
  (5 in the encryption script) and the tables report the median. Differences of a few percent
46
49
  are within run-to-run noise. A bold cell is the best number among the alternatives it is
@@ -59,6 +62,11 @@ The page answers four questions, each from one or two scripts of the harness:
59
62
  where they exist; the bundled WebAssembly zlib; and the pure-JavaScript zlib port. The last two
60
63
  ship with the library, like the codecs of jszip and fflate, so those rows compare the
61
64
  libraries without the host's zlib in the picture.
65
+ - **Checks.** No decompression row verifies the CRC-32 of the entries: zip.js and jszip leave
66
+ the check off by default, and fflate has none on read. Turning it on in zip.js makes the
67
+ inflater verify the CRC-32 through a gzip trailer: about 7 ms per 20 MB on the WebAssembly
68
+ codec, within the noise on Node's `DecompressionStream`; the pure-JavaScript port keeps a
69
+ separate pass over the output, about 15 ms.
62
70
  - **Units.** MB in the tables is 10^6 bytes. The workload sizes (20 MB, 8 MB, 256 MB) and the
63
71
  MB/s throughputs use 2^20 bytes.
64
72
 
@@ -126,15 +134,17 @@ Peak memory for the same runs (Δ over baseline):
126
134
  | fflate | fflate (JavaScript) | **92 MB** | **120 MB** |
127
135
 
128
136
  On the 20 MB stream `DecompressionStream` takes 66 ms and the WebAssembly inflate 78 ms, then
129
- fflate 116 ms, jszip 137 ms and the pure-JavaScript port 180 ms. On 5,000 small files the order
130
- reverses: fflate takes 102 ms, jszip 444 ms and the three zip.js rows 476 to 628 ms, with
131
- `DecompressionStream` the slowest of them, the same per-entry cost as in compression. fflate
132
- uses the least memory on both workloads, 34 % of the `DecompressionStream` row on the small
133
- files, and the WebAssembly and pure-JavaScript rows peak higher than `DecompressionStream`. The
134
- three zip.js rows allocate the same 800 MB of stream objects over the 5,000 entries, about
135
- 160 KB per entry. The WebAssembly and pure-JavaScript codecs run on the JavaScript thread, so
136
- V8's incremental marking keeps more of that garbage alive between collections, and that is the
137
- difference between the rows.
137
+ fflate 116 ms, jszip 137 ms and the pure-JavaScript port 180 ms. The WebAssembly row includes
138
+ the module instantiation a fresh process pays once, about 20 ms. Warm, the two inflates are
139
+ within 6 % of each other in the [Codecs](#codecs-at-equal-output-size) table. On 5,000 small
140
+ files the order reverses: fflate takes 102 ms, jszip 444 ms and the three zip.js rows 476 to
141
+ 628 ms, with `DecompressionStream` the slowest of them, the same per-entry cost as in
142
+ compression. fflate uses the least memory on both workloads, 34 % of the `DecompressionStream`
143
+ row on the small files, and the WebAssembly and pure-JavaScript rows peak higher than
144
+ `DecompressionStream`. The three zip.js rows allocate the same 800 MB of stream objects over the
145
+ 5,000 entries, about 160 KB per entry. The WebAssembly and pure-JavaScript codecs run on the
146
+ JavaScript thread, so V8's incremental marking keeps more of that garbage alive between
147
+ collections, and that is the difference between the rows.
138
148
 
139
149
  ### Streaming a 256 MB file, disk to disk
140
150
 
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zip-js/zip-js",
3
- "version": "2.14.1",
3
+ "version": "2.16.0",
4
4
  "exports": {
5
5
  ".": "./index.js",
6
6
  "./external": "./lib/zip-fs-external.js",
@@ -1889,7 +1889,6 @@ const FORMAT_GZIP$1 = "gzip";
1889
1889
  const GZIP_HEADER_LENGTH = 10;
1890
1890
  const GZIP_TRAILER_LENGTH = 8;
1891
1891
  const GZIP_HEADER_BYTES = [0x1f, 0x8b, 0x08];
1892
- const GZIP_OUTPUT_STALL_TIMEOUT = 5000;
1893
1892
 
1894
1893
  class DeflateStream extends TransformStream {
1895
1894
 
@@ -1900,8 +1899,8 @@ class DeflateStream extends TransformStream {
1900
1899
  let crc32Stream, encryptionStream, gzipCrc32Stream;
1901
1900
  let readable = super.readable;
1902
1901
  const codecStreams = format && getCodecStreams(format);
1903
- const useGzipCrc32 = computeCrc32 && compressed && !deflate64 && !codecStreams && (!encrypted || zipCrypto) &&
1904
- Boolean(useCompressionStream && CompressionStream);
1902
+ const GzipCompressionStream = getGzipCodecStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
1903
+ const useGzipCrc32 = computeCrc32 && compressed && !deflate64 && !codecStreams && (!encrypted || zipCrypto) && Boolean(GzipCompressionStream);
1905
1904
  if ((!encrypted || zipCrypto) && computeCrc32 && !useGzipCrc32) {
1906
1905
  crc32Stream = new Crc32Stream();
1907
1906
  readable = pipeThrough(readable, crc32Stream);
@@ -1911,12 +1910,15 @@ class DeflateStream extends TransformStream {
1911
1910
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.CompressionStream, format, { level, chunkSize, compressionMethod, uncompressedSize: inputSize }));
1912
1911
  } else if (useGzipCrc32) {
1913
1912
  gzipCrc32Stream = new GzipToRawDeflateStream();
1914
- readable = pipeThroughBackpressured(readable, new CompressionStream(FORMAT_GZIP$1));
1913
+ readable = pipeThroughBackpressured(readable, new GzipCompressionStream(FORMAT_GZIP$1, { level, chunkSize }));
1915
1914
  readable = pipeThrough(readable, gzipCrc32Stream);
1916
1915
  } else {
1917
1916
  try {
1918
1917
  readable = pipeThroughCompressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamFallback);
1919
1918
  } catch (error) {
1919
+ if (!useCompressionStream && CompressionStreamFallback) {
1920
+ throw error;
1921
+ }
1920
1922
  let gzipStream;
1921
1923
  try {
1922
1924
  gzipStream = new CompressionStream(FORMAT_GZIP$1);
@@ -1985,82 +1987,144 @@ class GzipToRawDeflateStream extends TransformStream {
1985
1987
  }
1986
1988
  }
1987
1989
 
1988
- function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize) {
1989
- const crc32 = new Crc32();
1990
+ function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32) {
1991
+ const writer = gzipStream.writable.getWriter();
1992
+ const reader = gzipStream.readable.getReader();
1993
+ const outputCrc32 = crc32 === UNDEFINED_VALUE ? new Crc32() : UNDEFINED_VALUE;
1990
1994
  let outputLength = 0;
1991
1995
  let inputDone = false;
1992
- let watchdogTimeout;
1996
+ let trailerWritten = false;
1997
+ let idleCheckArmed = false;
1998
+ let readCount = 0;
1999
+ let readPending = false;
1993
2000
  let resolveTrailerReady, rejectTrailerReady;
1994
2001
  const trailerReady = new Promise((resolve, reject) => {
1995
2002
  resolveTrailerReady = resolve;
1996
2003
  rejectTrailerReady = reject;
1997
2004
  });
1998
2005
  trailerReady.catch(() => { });
1999
- if (!outputSize) {
2000
- resolveTrailerReady();
2001
- }
2002
- const gzipWrapStream = new TransformStream({
2003
- start(controller) {
2006
+ pump();
2007
+ return new ReadableStream({
2008
+ async pull(controller) {
2009
+ let result;
2010
+ try {
2011
+ result = await read();
2012
+ } catch (error) {
2013
+ throw trailerWritten ? getTrailerError(error) : error;
2014
+ }
2015
+ const { value, done } = result;
2016
+ if (done) {
2017
+ controller.close();
2018
+ } else {
2019
+ outputLength += value.length;
2020
+ if (outputLength > outputSize) {
2021
+ const error = new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
2022
+ rejectTrailerReady(error);
2023
+ await cancel(reader, error);
2024
+ throw error;
2025
+ }
2026
+ if (outputCrc32) {
2027
+ outputCrc32.append(value);
2028
+ }
2029
+ controller.enqueue(value);
2030
+ }
2031
+ },
2032
+ cancel(reason) {
2033
+ rejectTrailerReady(reason);
2034
+ return reader.cancel(reason);
2035
+ }
2036
+ });
2037
+
2038
+ async function pump() {
2039
+ const inputReader = readable.getReader();
2040
+ try {
2004
2041
  const header = new Uint8Array(GZIP_HEADER_LENGTH);
2005
2042
  header.set(GZIP_HEADER_BYTES);
2006
- controller.enqueue(header);
2007
- },
2008
- transform(chunk, controller) {
2009
- controller.enqueue(chunk);
2010
- },
2011
- async flush(controller) {
2043
+ await writer.write(header);
2044
+ for (; ;) {
2045
+ await writer.ready;
2046
+ const { value, done } = await inputReader.read();
2047
+ if (done) {
2048
+ break;
2049
+ }
2050
+ await writer.write(value);
2051
+ }
2012
2052
  inputDone = true;
2013
- startWatchdog();
2014
- try {
2053
+ if (outputCrc32) {
2054
+ if (readPending) {
2055
+ armIdleCheck();
2056
+ }
2015
2057
  await trailerReady;
2016
- } finally {
2017
- stopWatchdog();
2018
2058
  }
2019
2059
  const trailer = new Uint8Array(GZIP_TRAILER_LENGTH);
2020
2060
  const dataView = getDataView(trailer);
2021
- dataView.setUint32(0, crc32.get(), true);
2061
+ dataView.setUint32(0, outputCrc32 ? outputCrc32.get() : crc32, true);
2022
2062
  dataView.setUint32(4, outputSize, true);
2023
- controller.enqueue(trailer);
2024
- },
2025
- cancel(reason) {
2026
- rejectTrailerReady(reason);
2063
+ trailerWritten = true;
2064
+ await writer.write(trailer);
2065
+ await writer.close();
2066
+ } catch (error) {
2067
+ await abort(writer, error);
2068
+ await cancel(inputReader, error);
2027
2069
  }
2028
- });
2029
- const outputStream = new TransformStream({
2030
- transform(chunk, controller) {
2031
- crc32.append(chunk);
2032
- outputLength += chunk.length;
2033
- if (outputLength >= outputSize) {
2034
- resolveTrailerReady();
2035
- } else if (inputDone) {
2036
- startWatchdog();
2037
- }
2038
- controller.enqueue(chunk);
2039
- },
2040
- cancel(reason) {
2041
- rejectTrailerReady(reason);
2070
+ }
2071
+
2072
+ function read() {
2073
+ readCount++;
2074
+ readPending = true;
2075
+ const result = reader.read();
2076
+ result.then(onReadSettled, onReadSettled);
2077
+ if (inputDone && outputCrc32) {
2078
+ armIdleCheck();
2042
2079
  }
2043
- });
2044
- readable = pipeThrough(readable, gzipWrapStream);
2045
- readable = pipeThroughBackpressured(readable, gzipStream);
2046
- return pipeThrough(readable, outputStream);
2080
+ return result;
2081
+ }
2047
2082
 
2048
- function startWatchdog() {
2049
- stopWatchdog();
2050
- watchdogTimeout = setTimeout(() => rejectTrailerReady(new Error(ERR_INVALID_UNCOMPRESSED_SIZE)), GZIP_OUTPUT_STALL_TIMEOUT);
2083
+ function onReadSettled() {
2084
+ readPending = false;
2051
2085
  }
2052
2086
 
2053
- function stopWatchdog() {
2054
- clearTimeout(watchdogTimeout);
2087
+ async function armIdleCheck() {
2088
+ if (!idleCheckArmed) {
2089
+ idleCheckArmed = true;
2090
+ const count = readCount;
2091
+ await nextTask();
2092
+ idleCheckArmed = false;
2093
+ if (readPending) {
2094
+ if (readCount == count) {
2095
+ resolveTrailerReady();
2096
+ } else {
2097
+ armIdleCheck();
2098
+ }
2099
+ }
2100
+ }
2101
+ }
2102
+
2103
+ function getTrailerError(error) {
2104
+ const trailerError = new Error(outputLength == outputSize ? ERR_INVALID_CRC32 : ERR_INVALID_UNCOMPRESSED_SIZE);
2105
+ trailerError.cause = error;
2106
+ return trailerError;
2055
2107
  }
2056
2108
  }
2057
2109
 
2110
+ function nextTask() {
2111
+ return new Promise(resolve => {
2112
+ const { port1, port2 } = new MessageChannel();
2113
+ port2.onmessage = () => {
2114
+ port1.close();
2115
+ port2.close();
2116
+ resolve();
2117
+ };
2118
+ port1.postMessage(UNDEFINED_VALUE);
2119
+ });
2120
+ }
2121
+
2058
2122
  class InflateStream extends TransformStream {
2059
2123
 
2060
2124
  constructor(options, { chunkSize, DecompressionStreamFallback, DecompressionStream }) {
2061
2125
  super({});
2062
2126
  const { zipCrypto, encrypted, checkCrc32, crc32, compressed, useCompressionStream, deflate64, format, compressionMethod, rawBitFlag, outputSize } = options;
2063
- let crc32Stream, decryptionStream;
2127
+ let crc32Stream, decryptionStream, gzipCrc32;
2064
2128
  let readable = super.readable;
2065
2129
  if (encrypted) {
2066
2130
  if (zipCrypto) {
@@ -2075,31 +2139,44 @@ class InflateStream extends TransformStream {
2075
2139
  if (codecStreams) {
2076
2140
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }));
2077
2141
  } else {
2078
- try {
2079
- readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
2080
- } catch (error) {
2081
- if (deflate64 || outputSize === UNDEFINED_VALUE) {
2082
- throw error;
2083
- }
2084
- let gzipStream;
2142
+ let gzipStream;
2143
+ const GzipDecompressionStream = getGzipCodecStream(useCompressionStream, DecompressionStream, DecompressionStreamFallback);
2144
+ if (checkCrc32 && !deflate64 && crc32 !== UNDEFINED_VALUE && outputSize !== UNDEFINED_VALUE && GzipDecompressionStream) {
2085
2145
  try {
2086
- gzipStream = new DecompressionStream(FORMAT_GZIP$1);
2146
+ gzipStream = new GzipDecompressionStream(FORMAT_GZIP$1, { chunkSize });
2087
2147
  } catch {
2088
- throw error;
2148
+ gzipStream = UNDEFINED_VALUE;
2149
+ }
2150
+ }
2151
+ if (!gzipStream) {
2152
+ try {
2153
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
2154
+ } catch (error) {
2155
+ if (deflate64 || outputSize === UNDEFINED_VALUE || (!useCompressionStream && DecompressionStreamFallback)) {
2156
+ throw error;
2157
+ }
2158
+ try {
2159
+ gzipStream = new DecompressionStream(FORMAT_GZIP$1);
2160
+ } catch {
2161
+ throw error;
2162
+ }
2089
2163
  }
2090
- readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize);
2164
+ }
2165
+ if (gzipStream) {
2166
+ gzipCrc32 = true;
2167
+ readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32);
2091
2168
  }
2092
2169
  }
2093
2170
  readable = mapInflateStreamError(readable);
2094
2171
  }
2095
- if (checkCrc32) {
2172
+ if (checkCrc32 && !gzipCrc32) {
2096
2173
  crc32Stream = new Crc32Stream();
2097
2174
  readable = pipeThrough(readable, crc32Stream);
2098
2175
  }
2099
2176
  setReadable(this, readable, () => {
2100
- if (checkCrc32) {
2101
- const computedCrc32View = new DataView(crc32Stream.value.buffer);
2102
- if (crc32 != computedCrc32View.getUint32(0, false)) {
2177
+ if (crc32Stream) {
2178
+ const computedCrc32 = new DataView(crc32Stream.value.buffer).getUint32(0, false);
2179
+ if (crc32 != computedCrc32) {
2103
2180
  throw new Error(ERR_INVALID_CRC32);
2104
2181
  }
2105
2182
  }
@@ -2155,6 +2232,14 @@ function createCodecStream(CodecStreamClass, format, options) {
2155
2232
  return new CodecStreamClass(format, options);
2156
2233
  }
2157
2234
 
2235
+ function getGzipCodecStream(useCompressionStream, CodecStreamNative, CodecStreamFallback) {
2236
+ if (useCompressionStream && CodecStreamNative) {
2237
+ return CodecStreamNative;
2238
+ } else if (CodecStreamFallback && CodecStreamFallback.requiresModule) {
2239
+ return CodecStreamFallback;
2240
+ }
2241
+ }
2242
+
2158
2243
  function pipeThroughCompressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamFallback) {
2159
2244
  const Stream = useCompressionStream && CompressionStreamNative ?
2160
2245
  CompressionStreamNative :
@@ -2527,10 +2612,12 @@ class CodecWorker {
2527
2612
  workerData.terminated = true;
2528
2613
  worker.terminate();
2529
2614
  }
2530
- terminateResolvers.forEach(resolve => resolve());
2531
2615
  }
2532
2616
  workerData.busy = false;
2533
- onTaskFinished(workerData);
2617
+ const pendingTasks = onTaskFinished(workerData);
2618
+ if (terminateResolvers) {
2619
+ terminateResolvers.forEach(resolve => resolve(pendingTasks));
2620
+ }
2534
2621
  }
2535
2622
  }
2536
2623
  });
@@ -3215,7 +3302,10 @@ async function runWorker(stream, workerOptions) {
3215
3302
 
3216
3303
  function onTaskFinished(workerData) {
3217
3304
  clearStarvationTimeout();
3218
- if (pendingRequests.length) {
3305
+ if (workerData.terminated) {
3306
+ workerData.terminated = false;
3307
+ return runPendingRequestsInline();
3308
+ } else if (pendingRequests.length) {
3219
3309
  const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
3220
3310
  resolve(new CodecWorker(workerData, stream, workerOptions, onTaskFinished));
3221
3311
  armStarvationTimeout();
@@ -3253,12 +3343,26 @@ function onWorkerStarvation() {
3253
3343
  starvationTimeout = null;
3254
3344
  if (pendingRequests.length) {
3255
3345
  const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1);
3256
- const inlineWorkerOptions = Object.assign({}, workerOptions, { useWebWorkers: false, workerURI: UNDEFINED_VALUE, createWorker: UNDEFINED_VALUE });
3257
- resolve(new CodecWorker({}, stream, inlineWorkerOptions, onInlineTaskFinished));
3346
+ resolve(new CodecWorker({}, stream, getInlineWorkerOptions(workerOptions), onInlineTaskFinished));
3258
3347
  armStarvationTimeout();
3259
3348
  }
3260
3349
  }
3261
3350
 
3351
+ function runPendingRequestsInline() {
3352
+ const tasks = pendingRequests.splice(0).map(({ resolve, stream, workerOptions }) => new Promise(resolveTask => {
3353
+ resolve(new CodecWorker({}, stream, getInlineWorkerOptions(workerOptions), () => {
3354
+ onInlineTaskFinished();
3355
+ resolveTask();
3356
+ }));
3357
+ }));
3358
+ clearStarvationTimeout();
3359
+ return Promise.all(tasks);
3360
+ }
3361
+
3362
+ function getInlineWorkerOptions(workerOptions) {
3363
+ return Object.assign({}, workerOptions, { useWebWorkers: false, workerURI: UNDEFINED_VALUE, createWorker: UNDEFINED_VALUE });
3364
+ }
3365
+
3262
3366
  function onInlineTaskFinished() {
3263
3367
  clearStarvationTimeout();
3264
3368
  armStarvationTimeout();
@@ -3268,18 +3372,14 @@ function terminateWorker(workerData, workerOptions) {
3268
3372
  const { config } = workerOptions;
3269
3373
  const { terminateWorkerTimeout } = config;
3270
3374
  if (Number.isFinite(terminateWorkerTimeout) && terminateWorkerTimeout >= 0) {
3271
- if (workerData.terminated) {
3272
- workerData.terminated = false;
3273
- } else {
3274
- workerData.terminateTimeout = setTimeout(async () => {
3275
- pool = pool.filter(data => data != workerData);
3276
- try {
3277
- await workerData.terminate();
3278
- } catch {
3279
- // ignored
3280
- }
3281
- }, terminateWorkerTimeout);
3282
- }
3375
+ workerData.terminateTimeout = setTimeout(async () => {
3376
+ pool = pool.filter(data => data != workerData);
3377
+ try {
3378
+ await workerData.terminate();
3379
+ } catch {
3380
+ // ignored
3381
+ }
3382
+ }, terminateWorkerTimeout);
3283
3383
  }
3284
3384
  }
3285
3385
 
@@ -3292,10 +3392,13 @@ function clearTerminateTimeout(workerData) {
3292
3392
  }
3293
3393
 
3294
3394
  async function terminateWorkers() {
3295
- await Promise.allSettled(pool.map(workerData => {
3296
- clearTerminateTimeout(workerData);
3297
- return workerData.terminate();
3298
- }));
3395
+ await Promise.allSettled([
3396
+ runPendingRequestsInline(),
3397
+ ...pool.map(workerData => {
3398
+ clearTerminateTimeout(workerData);
3399
+ return workerData.terminate();
3400
+ })
3401
+ ]);
3299
3402
  resetWebWorkerSupport();
3300
3403
  }
3301
3404
 
@@ -3370,6 +3473,19 @@ function decodeText(value, encoding) {
3370
3473
  return decode(value, encoding, true);
3371
3474
  }
3372
3475
 
3476
+ function isUTF8Text(value) {
3477
+ if (value.some(byte => byte > 0x7f)) {
3478
+ try {
3479
+ new TextDecoder("utf-8", { fatal: true }).decode(value);
3480
+ return true;
3481
+ } catch {
3482
+ return false;
3483
+ }
3484
+ } else {
3485
+ return false;
3486
+ }
3487
+ }
3488
+
3373
3489
  function decodeTextRemovingBOM(value, encoding) {
3374
3490
  return decode(value, encoding, false);
3375
3491
  }
@@ -3702,10 +3818,11 @@ class FetchReader extends Reader {
3702
3818
 
3703
3819
  createReadable(options) {
3704
3820
  const reader = this;
3705
- const { useRangeHeader, forceRangeRequests, size } = reader;
3821
+ const { useRangeHeader, forceRangeRequests, size, eocdCache } = reader;
3706
3822
  if ((useRangeHeader || forceRangeRequests) && size !== UNDEFINED_VALUE) {
3707
3823
  const { offset = 0, size: readSize = size - offset } = options || {};
3708
- if (readSize > 0 && offset < size) {
3824
+ const cached = eocdCache && offset >= size - eocdCache.length;
3825
+ if (readSize > 0 && offset < size && !cached) {
3709
3826
  return createRangeReadable(reader, offset, Math.min(readSize, size - offset));
3710
3827
  }
3711
3828
  }
@@ -4659,6 +4776,7 @@ const WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD = "mismatched loca
4659
4776
  const WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES = "mismatched local file header (crc32 or sizes)";
4660
4777
  const MAX_KNOWN_VERSION = 63;
4661
4778
  const DRIVE_LETTER_REGEXP = /^[a-zA-Z]:/;
4779
+ const PARENT_DIRECTORY_REGEXP = /(^|[\\/])\.\.([\\/]|$)/;
4662
4780
  const CHARSET_UTF8 = "utf-8";
4663
4781
  const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
4664
4782
  const CHARSET_CP437 = "cp437";
@@ -4911,12 +5029,13 @@ class ZipReader {
4911
5029
  const versionMadeBy = getUint16$1(directoryView, offset + 4);
4912
5030
  const msDosCompatible = versionMadeBy >> 8 == 0;
4913
5031
  const unixCompatible = versionMadeBy >> 8 == 3;
4914
- const rawFilename = directoryArray.subarray(filenameOffset, extraFieldOffset);
4915
5032
  const commentLength = getUint16$1(directoryView, offset + 32);
4916
5033
  const endOffset = commentOffset + commentLength;
4917
- const rawComment = directoryArray.subarray(commentOffset, endOffset);
4918
- const filenameUTF8 = languageEncodingFlag;
4919
- const commentUTF8 = languageEncodingFlag;
5034
+ const rawEntryData = new Uint8Array(directoryArray.subarray(filenameOffset, endOffset));
5035
+ const rawFilename = rawEntryData.subarray(0, fileEntry.filenameLength);
5036
+ const rawComment = rawEntryData.subarray(fileEntry.filenameLength + fileEntry.extraFieldLength);
5037
+ const filenameUTF8 = languageEncodingFlag || (!filenameEncoding && isUTF8Text(rawFilename));
5038
+ const commentUTF8 = languageEncodingFlag || (!commentEncoding && isUTF8Text(rawComment));
4920
5039
  const externalFileAttributes = getUint32$1(directoryView, offset + 38);
4921
5040
  const msdosAttributesRaw = externalFileAttributes & MAX_8_BITS;
4922
5041
  const msdosAttributes = {
@@ -4967,7 +5086,7 @@ class ZipReader {
4967
5086
  rawFilename,
4968
5087
  filenameUTF8,
4969
5088
  commentUTF8,
4970
- rawExtraField: directoryArray.subarray(extraFieldOffset, commentOffset),
5089
+ rawExtraField: rawEntryData.subarray(fileEntry.filenameLength, fileEntry.filenameLength + fileEntry.extraFieldLength),
4971
5090
  rawComment,
4972
5091
  filename,
4973
5092
  comment
@@ -5475,7 +5594,7 @@ function readDigitalSignature(signatureRecordArray) {
5475
5594
  if (getUint32$1(signatureRecordView, 0) == DIGITAL_SIGNATURE_RECORD_SIGNATURE) {
5476
5595
  const signatureDataLength = getUint16$1(signatureRecordView, 4);
5477
5596
  if (6 + signatureDataLength <= signatureRecordArray.length) {
5478
- return signatureRecordArray.subarray(6, 6 + signatureDataLength);
5597
+ return new Uint8Array(signatureRecordArray.subarray(6, 6 + signatureDataLength));
5479
5598
  }
5480
5599
  }
5481
5600
  }
@@ -5998,10 +6117,10 @@ function isUnsafeFilename(filename, filenameValidation) {
5998
6117
  if (pathParts.length > 1 && pathParts[pathParts.length - 1] === "") {
5999
6118
  pathParts.pop();
6000
6119
  }
6001
- if (pathParts.includes("..") || filename.startsWith("/") || filename.startsWith("\\\\") || DRIVE_LETTER_REGEXP.test(filename)) {
6120
+ if (PARENT_DIRECTORY_REGEXP.test(filename) || filename.startsWith("/") || filename.startsWith("\\") || DRIVE_LETTER_REGEXP.test(filename)) {
6002
6121
  return true;
6003
6122
  }
6004
- return filenameValidation == STRICTNESS_STRICT && (pathParts.includes(".") || pathParts.includes(""));
6123
+ return filenameValidation == STRICTNESS_STRICT && (pathParts.includes(".") || pathParts.includes("") || filename.includes("\0"));
6005
6124
  }
6006
6125
 
6007
6126
  function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
@@ -6087,7 +6206,7 @@ async function* scanEndOfCentralDirectory(reader, scanLength) {
6087
6206
  }
6088
6207
 
6089
6208
  function getEndOfCentralDirectoryInfo(scanArray, indexByte, offset) {
6090
- return { offset, buffer: scanArray.slice(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH).buffer };
6209
+ return { offset, buffer: new Uint8Array(scanArray.subarray(indexByte, indexByte + END_OF_CENTRAL_DIR_LENGTH)).buffer };
6091
6210
  }
6092
6211
 
6093
6212
  async function getCentralDirectoryReachability(reader, view, anchoredOffset, indexByte, offset, size, remoteProbeBudget) {
@@ -8734,7 +8853,7 @@ function formatSupported(StreamClass, format) {
8734
8853
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8735
8854
  */
8736
8855
 
8737
- const VERSION = "2.14.1";
8856
+ const VERSION = "2.16.0";
8738
8857
 
8739
8858
  /*
8740
8859
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.