@zip.js/zip.js 2.15.0 → 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.
package/BENCHMARKS.md CHANGED
@@ -63,8 +63,10 @@ The page answers four questions, each from one or two scripts of the harness:
63
63
  ship with the library, like the codecs of jszip and fflate, so those rows compare the
64
64
  libraries without the host's zlib in the picture.
65
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 adds one pass
67
- over the output, about 15 ms per 20 MB.
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.
68
70
  - **Units.** MB in the tables is 10^6 bytes. The workload sizes (20 MB, 8 MB, 256 MB) and the
69
71
  MB/s throughputs use 2^20 bytes.
70
72
 
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zip-js/zip-js",
3
- "version": "2.15.0",
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,7 +1899,7 @@ 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 GzipCompressionStream = getGzipCompressionStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
1902
+ const GzipCompressionStream = getGzipCodecStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
1904
1903
  const useGzipCrc32 = computeCrc32 && compressed && !deflate64 && !codecStreams && (!encrypted || zipCrypto) && Boolean(GzipCompressionStream);
1905
1904
  if ((!encrypted || zipCrypto) && computeCrc32 && !useGzipCrc32) {
1906
1905
  crc32Stream = new Crc32Stream();
@@ -1917,6 +1916,9 @@ class DeflateStream extends TransformStream {
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);
@@ -1986,74 +1988,137 @@ class GzipToRawDeflateStream extends TransformStream {
1986
1988
  }
1987
1989
 
1988
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;
1989
1994
  let outputLength = 0;
1990
1995
  let inputDone = false;
1991
- let watchdogTimeout;
1996
+ let trailerWritten = false;
1997
+ let idleCheckArmed = false;
1998
+ let readCount = 0;
1999
+ let readPending = false;
1992
2000
  let resolveTrailerReady, rejectTrailerReady;
1993
2001
  const trailerReady = new Promise((resolve, reject) => {
1994
2002
  resolveTrailerReady = resolve;
1995
2003
  rejectTrailerReady = reject;
1996
2004
  });
1997
2005
  trailerReady.catch(() => { });
1998
- if (!outputSize) {
1999
- resolveTrailerReady();
2000
- }
2001
- const gzipWrapStream = new TransformStream({
2002
- 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 {
2003
2041
  const header = new Uint8Array(GZIP_HEADER_LENGTH);
2004
2042
  header.set(GZIP_HEADER_BYTES);
2005
- controller.enqueue(header);
2006
- },
2007
- transform(chunk, controller) {
2008
- controller.enqueue(chunk);
2009
- },
2010
- 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
+ }
2011
2052
  inputDone = true;
2012
- startWatchdog();
2013
- try {
2053
+ if (outputCrc32) {
2054
+ if (readPending) {
2055
+ armIdleCheck();
2056
+ }
2014
2057
  await trailerReady;
2015
- } finally {
2016
- stopWatchdog();
2017
2058
  }
2018
2059
  const trailer = new Uint8Array(GZIP_TRAILER_LENGTH);
2019
2060
  const dataView = getDataView(trailer);
2020
- dataView.setUint32(0, crc32.get(), true);
2061
+ dataView.setUint32(0, outputCrc32 ? outputCrc32.get() : crc32, true);
2021
2062
  dataView.setUint32(4, outputSize, true);
2022
- controller.enqueue(trailer);
2023
- },
2024
- cancel(reason) {
2025
- 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);
2026
2069
  }
2027
- });
2028
- const outputStream = new TransformStream({
2029
- transform(chunk, controller) {
2030
- crc32.append(chunk);
2031
- outputLength += chunk.length;
2032
- if (outputLength >= outputSize) {
2033
- resolveTrailerReady();
2034
- } else if (inputDone) {
2035
- startWatchdog();
2036
- }
2037
- controller.enqueue(chunk);
2038
- },
2039
- cancel(reason) {
2040
- 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();
2041
2079
  }
2042
- });
2043
- readable = pipeThrough(readable, gzipWrapStream);
2044
- readable = pipeThroughBackpressured(readable, gzipStream);
2045
- return pipeThrough(readable, outputStream);
2080
+ return result;
2081
+ }
2082
+
2083
+ function onReadSettled() {
2084
+ readPending = false;
2085
+ }
2046
2086
 
2047
- function startWatchdog() {
2048
- stopWatchdog();
2049
- watchdogTimeout = setTimeout(() => rejectTrailerReady(new Error(ERR_INVALID_UNCOMPRESSED_SIZE)), GZIP_OUTPUT_STALL_TIMEOUT);
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
+ }
2050
2101
  }
2051
2102
 
2052
- function stopWatchdog() {
2053
- clearTimeout(watchdogTimeout);
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;
2054
2107
  }
2055
2108
  }
2056
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
+
2057
2122
  class InflateStream extends TransformStream {
2058
2123
 
2059
2124
  constructor(options, { chunkSize, DecompressionStreamFallback, DecompressionStream }) {
@@ -2074,20 +2139,32 @@ class InflateStream extends TransformStream {
2074
2139
  if (codecStreams) {
2075
2140
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }));
2076
2141
  } else {
2077
- try {
2078
- readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
2079
- } catch (error) {
2080
- if (deflate64 || outputSize === UNDEFINED_VALUE) {
2081
- throw error;
2082
- }
2083
- let gzipStream;
2142
+ let gzipStream;
2143
+ const GzipDecompressionStream = getGzipCodecStream(useCompressionStream, DecompressionStream, DecompressionStreamFallback);
2144
+ if (checkCrc32 && !deflate64 && crc32 !== UNDEFINED_VALUE && outputSize !== UNDEFINED_VALUE && GzipDecompressionStream) {
2084
2145
  try {
2085
- gzipStream = new DecompressionStream(FORMAT_GZIP$1);
2146
+ gzipStream = new GzipDecompressionStream(FORMAT_GZIP$1, { chunkSize });
2086
2147
  } catch {
2087
- 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
+ }
2088
2163
  }
2089
- gzipCrc32 = new Crc32();
2090
- readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, gzipCrc32);
2164
+ }
2165
+ if (gzipStream) {
2166
+ gzipCrc32 = true;
2167
+ readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32);
2091
2168
  }
2092
2169
  }
2093
2170
  readable = mapInflateStreamError(readable);
@@ -2097,8 +2174,8 @@ class InflateStream extends TransformStream {
2097
2174
  readable = pipeThrough(readable, crc32Stream);
2098
2175
  }
2099
2176
  setReadable(this, readable, () => {
2100
- if (checkCrc32) {
2101
- const computedCrc32 = gzipCrc32 ? gzipCrc32.get() >>> 0 : new DataView(crc32Stream.value.buffer).getUint32(0, false);
2177
+ if (crc32Stream) {
2178
+ const computedCrc32 = new DataView(crc32Stream.value.buffer).getUint32(0, false);
2102
2179
  if (crc32 != computedCrc32) {
2103
2180
  throw new Error(ERR_INVALID_CRC32);
2104
2181
  }
@@ -2155,11 +2232,11 @@ function createCodecStream(CodecStreamClass, format, options) {
2155
2232
  return new CodecStreamClass(format, options);
2156
2233
  }
2157
2234
 
2158
- function getGzipCompressionStream(useCompressionStream, CompressionStreamNative, CompressionStreamFallback) {
2159
- if (useCompressionStream && CompressionStreamNative) {
2160
- return CompressionStreamNative;
2161
- } else if (CompressionStreamFallback && CompressionStreamFallback.requiresModule) {
2162
- return CompressionStreamFallback;
2235
+ function getGzipCodecStream(useCompressionStream, CodecStreamNative, CodecStreamFallback) {
2236
+ if (useCompressionStream && CodecStreamNative) {
2237
+ return CodecStreamNative;
2238
+ } else if (CodecStreamFallback && CodecStreamFallback.requiresModule) {
2239
+ return CodecStreamFallback;
2163
2240
  }
2164
2241
  }
2165
2242
 
@@ -3741,10 +3818,11 @@ class FetchReader extends Reader {
3741
3818
 
3742
3819
  createReadable(options) {
3743
3820
  const reader = this;
3744
- const { useRangeHeader, forceRangeRequests, size } = reader;
3821
+ const { useRangeHeader, forceRangeRequests, size, eocdCache } = reader;
3745
3822
  if ((useRangeHeader || forceRangeRequests) && size !== UNDEFINED_VALUE) {
3746
3823
  const { offset = 0, size: readSize = size - offset } = options || {};
3747
- if (readSize > 0 && offset < size) {
3824
+ const cached = eocdCache && offset >= size - eocdCache.length;
3825
+ if (readSize > 0 && offset < size && !cached) {
3748
3826
  return createRangeReadable(reader, offset, Math.min(readSize, size - offset));
3749
3827
  }
3750
3828
  }
@@ -8775,7 +8853,7 @@ function formatSupported(StreamClass, format) {
8775
8853
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8776
8854
  */
8777
8855
 
8778
- const VERSION = "2.15.0";
8856
+ const VERSION = "2.16.0";
8779
8857
 
8780
8858
  /*
8781
8859
  Copyright (c) 2025 Gildas Lormeau. All rights reserved.