@zip.js/zip.js 2.15.0 → 2.16.1

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 (44) hide show
  1. package/BENCHMARKS.md +4 -2
  2. package/deno.json +1 -1
  3. package/dist/zip-core-external.js +410 -227
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +234 -109
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +552 -237
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +377 -118
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +552 -237
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +379 -120
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +554 -237
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +236 -111
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-native.js +236 -111
  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 +411 -228
  24. package/dist/zip.min.js +1 -1
  25. package/eslint.config.mjs +1 -1
  26. package/index-native.cjs +379 -120
  27. package/index-native.min.js +1 -1
  28. package/index.cjs +554 -237
  29. package/index.d.cts +143 -16
  30. package/index.d.ts +143 -16
  31. package/index.min.js +1 -1
  32. package/lib/core/io.js +3 -2
  33. package/lib/core/streams/aes-crypto-stream.js +60 -17
  34. package/lib/core/streams/zip-crypto-stream.js +7 -7
  35. package/lib/core/streams/zip-entry-stream.js +146 -73
  36. package/lib/core/streams/zlib-wasm/zlib-streams.js +176 -118
  37. package/lib/core/util/compatible-streams.js +10 -5
  38. package/lib/core/version.js +1 -1
  39. package/lib/core/web-worker-inline-native.js +1 -1
  40. package/lib/core/web-worker-inline-wasm.js +1 -1
  41. package/lib/core/zip-fs.js +144 -10
  42. package/lib/core/zip-reader.js +5 -5
  43. package/lib/core/zip-writer.js +4 -2
  44. package/package.json +3 -2
package/lib/core/io.js CHANGED
@@ -354,10 +354,11 @@ class FetchReader extends Reader {
354
354
 
355
355
  createReadable(options) {
356
356
  const reader = this;
357
- const { useRangeHeader, forceRangeRequests, size } = reader;
357
+ const { useRangeHeader, forceRangeRequests, size, eocdCache } = reader;
358
358
  if ((useRangeHeader || forceRangeRequests) && size !== UNDEFINED_VALUE) {
359
359
  const { offset = 0, size: readSize = size - offset } = options || {};
360
- if (readSize > 0 && offset < size) {
360
+ const cached = eocdCache && offset >= size - eocdCache.length;
361
+ if (readSize > 0 && offset < size && !cached) {
361
362
  return createRangeReadable(reader, offset, Math.min(readSize, size - offset));
362
363
  }
363
364
  }
@@ -26,8 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global crypto, TransformStream */
30
- // deno-lint-ignore-file no-this-alias
29
+ /* global crypto, TransformStream, ReadableStream */
31
30
 
32
31
  import { UNDEFINED_VALUE, UNDEFINED_TYPE, FUNCTION_TYPE, EMPTY_UINT8_ARRAY } from "../constants.js";
33
32
  import { encodeText } from "./../util/encode-text.js";
@@ -64,12 +63,12 @@ let createEngine = createDefaultEngine;
64
63
  class AESDecryptionStream extends TransformStream {
65
64
 
66
65
  constructor({ password, rawPassword, encryptionStrength, checkPasswordOnly, checkAuthenticationCode = true }) {
66
+ const aesCrypto = {};
67
67
  super({
68
68
  start() {
69
- initAesCrypto(this, password, rawPassword, encryptionStrength);
69
+ initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength);
70
70
  },
71
71
  async transform(chunk, controller) {
72
- const aesCrypto = this;
73
72
  const {
74
73
  password,
75
74
  strength,
@@ -88,6 +87,9 @@ class AESDecryptionStream extends TransformStream {
88
87
  } else {
89
88
  await ready;
90
89
  }
90
+ if (aesCrypto.discarded) {
91
+ return;
92
+ }
91
93
  const output = new Uint8Array(chunk.length - AUTHENTICATION_CODE_LENGTH - ((chunk.length - AUTHENTICATION_CODE_LENGTH) % BLOCK_LENGTH));
92
94
  controller.enqueue(append(aesCrypto, chunk, output, 0, AUTHENTICATION_CODE_LENGTH, true));
93
95
  },
@@ -96,9 +98,12 @@ class AESDecryptionStream extends TransformStream {
96
98
  engine,
97
99
  pendingInput,
98
100
  ready
99
- } = this;
101
+ } = aesCrypto;
100
102
  if (engine) {
101
103
  await ready;
104
+ if (aesCrypto.discarded) {
105
+ return;
106
+ }
102
107
  const originalAuthenticationCode = subarray(pendingInput, pendingInput.length - AUTHENTICATION_CODE_LENGTH);
103
108
  const decryptedChunkArray = new Uint8Array(subarray(pendingInput, 0, pendingInput.length - AUTHENTICATION_CODE_LENGTH));
104
109
  engine.process(decryptedChunkArray, true);
@@ -108,27 +113,26 @@ class AESDecryptionStream extends TransformStream {
108
113
  invalidAuthenticationCode |= authenticationCode[indexByte] ^ originalAuthenticationCode[indexByte];
109
114
  }
110
115
  if (invalidAuthenticationCode && checkAuthenticationCode) {
111
- throw new Error(ERR_INVALID_AUTHENTICATION_CODE);
116
+ controller.error(new Error(ERR_INVALID_AUTHENTICATION_CODE));
117
+ return;
112
118
  }
113
119
  controller.enqueue(decryptedChunkArray);
114
120
  }
115
- },
116
- cancel() {
117
- disposeEngine(this);
118
121
  }
119
122
  });
123
+ setDisposingReadable(this, aesCrypto);
120
124
  }
121
125
  }
122
126
 
123
127
  class AESEncryptionStream extends TransformStream {
124
128
 
125
129
  constructor({ password, rawPassword, encryptionStrength }) {
130
+ const aesCrypto = {};
126
131
  super({
127
132
  start() {
128
- initAesCrypto(this, password, rawPassword, encryptionStrength);
133
+ initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength);
129
134
  },
130
135
  async transform(chunk, controller) {
131
- const aesCrypto = this;
132
136
  const {
133
137
  password,
134
138
  strength,
@@ -142,6 +146,9 @@ class AESEncryptionStream extends TransformStream {
142
146
  } else {
143
147
  await ready;
144
148
  }
149
+ if (aesCrypto.discarded) {
150
+ return;
151
+ }
145
152
  const output = new Uint8Array(preamble.length + chunk.length - (chunk.length % BLOCK_LENGTH));
146
153
  output.set(preamble, 0);
147
154
  controller.enqueue(append(aesCrypto, chunk, output, preamble.length, 0, false));
@@ -151,19 +158,20 @@ class AESEncryptionStream extends TransformStream {
151
158
  engine,
152
159
  pendingInput,
153
160
  ready
154
- } = this;
161
+ } = aesCrypto;
155
162
  if (engine) {
156
163
  await ready;
164
+ if (aesCrypto.discarded) {
165
+ return;
166
+ }
157
167
  const encryptedChunkArray = new Uint8Array(pendingInput);
158
168
  engine.process(encryptedChunkArray, false);
159
169
  const authenticationCode = subarray(engine.digest(), 0, AUTHENTICATION_CODE_LENGTH);
160
170
  controller.enqueue(concat(encryptedChunkArray, authenticationCode));
161
171
  }
162
- },
163
- cancel() {
164
- disposeEngine(this);
165
172
  }
166
173
  });
174
+ setDisposingReadable(this, aesCrypto);
167
175
  }
168
176
  }
169
177
 
@@ -182,7 +190,37 @@ function initAesCrypto(aesCrypto, password, rawPassword, encryptionStrength) {
182
190
  ready: new Promise(resolve => aesCrypto.resolveReady = resolve),
183
191
  password: encodePassword(password, rawPassword),
184
192
  strength: encryptionStrength - 1,
185
- pendingInput: EMPTY_UINT8_ARRAY
193
+ pendingInput: EMPTY_UINT8_ARRAY,
194
+ discarded: false
195
+ });
196
+ }
197
+
198
+ function setDisposingReadable(stream, aesCrypto) {
199
+ const reader = stream.readable.getReader();
200
+ const readable = new ReadableStream({
201
+ async pull(controller) {
202
+ try {
203
+ const { value, done } = await reader.read();
204
+ if (done) {
205
+ controller.close();
206
+ } else {
207
+ controller.enqueue(value);
208
+ }
209
+ } catch (error) {
210
+ disposeEngine(aesCrypto);
211
+ reader.cancel(error).catch(() => { });
212
+ throw error;
213
+ }
214
+ },
215
+ cancel(reason) {
216
+ disposeEngine(aesCrypto);
217
+ return reader.cancel(reason);
218
+ }
219
+ });
220
+ Object.defineProperty(stream, "readable", {
221
+ get() {
222
+ return readable;
223
+ }
186
224
  });
187
225
  }
188
226
 
@@ -215,7 +253,9 @@ async function createDecryptionKeys(decrypt, strength, password, preamble) {
215
253
  }
216
254
  }
217
255
 
218
- function disposeEngine({ engine }) {
256
+ function disposeEngine(aesCrypto) {
257
+ const { engine } = aesCrypto;
258
+ aesCrypto.discarded = true;
219
259
  if (engine && engine.dispose) {
220
260
  engine.dispose();
221
261
  }
@@ -232,6 +272,9 @@ async function createKeys(aesCrypto, strength, password, salt) {
232
272
  const keyLength = KEY_LENGTH[strength];
233
273
  const compositeKey = await deriveKey(password, salt, keyLength * 2 + PASSWORD_VERIFICATION_LENGTH);
234
274
  aesCrypto.engine = createEngine(subarray(compositeKey, 0, keyLength), subarray(compositeKey, keyLength, keyLength * 2));
275
+ if (aesCrypto.discarded) {
276
+ disposeEngine(aesCrypto);
277
+ }
235
278
  return subarray(compositeKey, keyLength * 2);
236
279
  }
237
280
 
@@ -127,11 +127,11 @@ function encrypt(target, input) {
127
127
  }
128
128
 
129
129
  function createKeys(target, password, rawPassword) {
130
- const keys = [0x12345678, 0x23456789, 0x34567890];
130
+ const cryptoKeys = [0x12345678, 0x23456789, 0x34567890];
131
131
  Object.assign(target, {
132
- keys,
133
- crcKey0: new Crc32(keys[0]),
134
- crcKey2: new Crc32(keys[2])
132
+ cryptoKeys,
133
+ crcKey0: new Crc32(cryptoKeys[0]),
134
+ crcKey2: new Crc32(cryptoKeys[2])
135
135
  });
136
136
  if (rawPassword) {
137
137
  for (let index = 0; index < rawPassword.length; index++) {
@@ -145,17 +145,17 @@ function createKeys(target, password, rawPassword) {
145
145
  }
146
146
 
147
147
  function updateKeys(target, byte) {
148
- let [, key1] = target.keys;
148
+ let [, key1] = target.cryptoKeys;
149
149
  target.crcKey0.append([byte]);
150
150
  const key0 = ~target.crcKey0.get();
151
151
  key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1);
152
152
  target.crcKey2.append([key1 >>> 24]);
153
153
  const key2 = ~target.crcKey2.get();
154
- target.keys = [key0, key1, key2];
154
+ target.cryptoKeys = [key0, key1, key2];
155
155
  }
156
156
 
157
157
  function getByte(target) {
158
- const temp = target.keys[2] | 2;
158
+ const temp = target.cryptoKeys[2] | 2;
159
159
  return getInt8(Math.imul(temp, (temp ^ 1)) >>> 8);
160
160
  }
161
161
 
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global TransformStream, ReadableStream, setTimeout, clearTimeout */
29
+ /* global TransformStream, ReadableStream, MessageChannel */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
32
  import { Crc32 } from "./codecs/crc32.js";
@@ -59,7 +59,6 @@ const FORMAT_GZIP = "gzip";
59
59
  const GZIP_HEADER_LENGTH = 10;
60
60
  const GZIP_TRAILER_LENGTH = 8;
61
61
  const GZIP_HEADER_BYTES = [0x1f, 0x8b, 0x08];
62
- const GZIP_OUTPUT_STALL_TIMEOUT = 5000;
63
62
 
64
63
  class DeflateStream extends TransformStream {
65
64
 
@@ -70,7 +69,7 @@ class DeflateStream extends TransformStream {
70
69
  let crc32Stream, encryptionStream, gzipCrc32Stream;
71
70
  let readable = super.readable;
72
71
  const codecStreams = format && getCodecStreams(format);
73
- const GzipCompressionStream = getGzipCompressionStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
72
+ const GzipCompressionStream = getGzipCodecStream(useCompressionStream, CompressionStream, CompressionStreamFallback);
74
73
  const useGzipCrc32 = computeCrc32 && compressed && !deflate64 && !codecStreams && (!encrypted || zipCrypto) && Boolean(GzipCompressionStream);
75
74
  if ((!encrypted || zipCrypto) && computeCrc32 && !useGzipCrc32) {
76
75
  crc32Stream = new Crc32Stream();
@@ -87,6 +86,9 @@ class DeflateStream extends TransformStream {
87
86
  try {
88
87
  readable = pipeThroughCompressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamFallback);
89
88
  } catch (error) {
89
+ if (!useCompressionStream && CompressionStreamFallback) {
90
+ throw error;
91
+ }
90
92
  let gzipStream;
91
93
  try {
92
94
  gzipStream = new CompressionStream(FORMAT_GZIP);
@@ -156,74 +158,134 @@ class GzipToRawDeflateStream extends TransformStream {
156
158
  }
157
159
 
158
160
  function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32) {
161
+ const writer = gzipStream.writable.getWriter();
162
+ const reader = gzipStream.readable.getReader();
163
+ const outputCrc32 = crc32 === UNDEFINED_VALUE ? new Crc32() : UNDEFINED_VALUE;
159
164
  let outputLength = 0;
160
165
  let inputDone = false;
161
- let watchdogTimeout;
166
+ let trailerWritten = false;
167
+ let idleCheckArmed = false;
168
+ let readCount = 0;
169
+ let readPending = false;
162
170
  let resolveTrailerReady, rejectTrailerReady;
163
171
  const trailerReady = new Promise((resolve, reject) => {
164
172
  resolveTrailerReady = resolve;
165
173
  rejectTrailerReady = reject;
166
174
  });
167
175
  trailerReady.catch(() => { });
168
- if (!outputSize) {
169
- resolveTrailerReady();
170
- }
171
- const gzipWrapStream = new TransformStream({
172
- start(controller) {
176
+ pump();
177
+ return new ReadableStream({
178
+ async pull(controller) {
179
+ try {
180
+ const { value, done } = await read();
181
+ if (done) {
182
+ controller.close();
183
+ } else {
184
+ outputLength += value.length;
185
+ if (outputLength > outputSize) {
186
+ throw new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
187
+ }
188
+ if (outputCrc32) {
189
+ outputCrc32.append(value);
190
+ }
191
+ controller.enqueue(value);
192
+ }
193
+ } catch (error) {
194
+ rejectTrailerReady(error);
195
+ await cancel(reader, error);
196
+ throw trailerWritten ? getTrailerError(error) : error;
197
+ }
198
+ },
199
+ cancel(reason) {
200
+ rejectTrailerReady(reason);
201
+ return reader.cancel(reason);
202
+ }
203
+ });
204
+
205
+ async function pump() {
206
+ const inputReader = readable.getReader();
207
+ try {
173
208
  const header = new Uint8Array(GZIP_HEADER_LENGTH);
174
209
  header.set(GZIP_HEADER_BYTES);
175
- controller.enqueue(header);
176
- },
177
- transform(chunk, controller) {
178
- controller.enqueue(chunk);
179
- },
180
- async flush(controller) {
210
+ await writer.write(header);
211
+ for (; ;) {
212
+ await writer.ready;
213
+ const { value, done } = await inputReader.read();
214
+ if (done) {
215
+ break;
216
+ }
217
+ await writer.write(value);
218
+ }
181
219
  inputDone = true;
182
- startWatchdog();
183
- try {
220
+ if (outputCrc32) {
221
+ if (readPending) {
222
+ armIdleCheck();
223
+ }
184
224
  await trailerReady;
185
- } finally {
186
- stopWatchdog();
187
225
  }
188
226
  const trailer = new Uint8Array(GZIP_TRAILER_LENGTH);
189
227
  const dataView = getDataView(trailer);
190
- dataView.setUint32(0, crc32.get(), true);
228
+ dataView.setUint32(0, outputCrc32 ? outputCrc32.get() : crc32, true);
191
229
  dataView.setUint32(4, outputSize, true);
192
- controller.enqueue(trailer);
193
- },
194
- cancel(reason) {
195
- rejectTrailerReady(reason);
230
+ trailerWritten = true;
231
+ await writer.write(trailer);
232
+ await writer.close();
233
+ } catch (error) {
234
+ await abort(writer, error);
235
+ await cancel(inputReader, error);
196
236
  }
197
- });
198
- const outputStream = new TransformStream({
199
- transform(chunk, controller) {
200
- crc32.append(chunk);
201
- outputLength += chunk.length;
202
- if (outputLength >= outputSize) {
203
- resolveTrailerReady();
204
- } else if (inputDone) {
205
- startWatchdog();
206
- }
207
- controller.enqueue(chunk);
208
- },
209
- cancel(reason) {
210
- rejectTrailerReady(reason);
237
+ }
238
+
239
+ function read() {
240
+ readCount++;
241
+ readPending = true;
242
+ const result = reader.read();
243
+ result.then(onReadSettled, onReadSettled);
244
+ if (inputDone && outputCrc32) {
245
+ armIdleCheck();
211
246
  }
212
- });
213
- readable = pipeThrough(readable, gzipWrapStream);
214
- readable = pipeThroughBackpressured(readable, gzipStream);
215
- return pipeThrough(readable, outputStream);
247
+ return result;
248
+ }
249
+
250
+ function onReadSettled() {
251
+ readPending = false;
252
+ }
216
253
 
217
- function startWatchdog() {
218
- stopWatchdog();
219
- watchdogTimeout = setTimeout(() => rejectTrailerReady(new Error(ERR_INVALID_UNCOMPRESSED_SIZE)), GZIP_OUTPUT_STALL_TIMEOUT);
254
+ async function armIdleCheck() {
255
+ if (!idleCheckArmed) {
256
+ idleCheckArmed = true;
257
+ const count = readCount;
258
+ await nextTask();
259
+ idleCheckArmed = false;
260
+ if (readPending) {
261
+ if (readCount == count) {
262
+ resolveTrailerReady();
263
+ } else {
264
+ armIdleCheck();
265
+ }
266
+ }
267
+ }
220
268
  }
221
269
 
222
- function stopWatchdog() {
223
- clearTimeout(watchdogTimeout);
270
+ function getTrailerError(error) {
271
+ const trailerError = new Error(outputLength == outputSize ? ERR_INVALID_CRC32 : ERR_INVALID_UNCOMPRESSED_SIZE);
272
+ trailerError.cause = error;
273
+ return trailerError;
224
274
  }
225
275
  }
226
276
 
277
+ function nextTask() {
278
+ return new Promise(resolve => {
279
+ const { port1, port2 } = new MessageChannel();
280
+ port2.onmessage = () => {
281
+ port1.close();
282
+ port2.close();
283
+ resolve();
284
+ };
285
+ port1.postMessage(UNDEFINED_VALUE);
286
+ });
287
+ }
288
+
227
289
  class InflateStream extends TransformStream {
228
290
 
229
291
  constructor(options, { chunkSize, DecompressionStreamFallback, DecompressionStream }) {
@@ -244,20 +306,32 @@ class InflateStream extends TransformStream {
244
306
  if (codecStreams) {
245
307
  readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }));
246
308
  } else {
247
- try {
248
- readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
249
- } catch (error) {
250
- if (deflate64 || outputSize === UNDEFINED_VALUE) {
251
- throw error;
252
- }
253
- let gzipStream;
309
+ let gzipStream;
310
+ const GzipDecompressionStream = getGzipCodecStream(useCompressionStream, DecompressionStream, DecompressionStreamFallback);
311
+ if (checkCrc32 && !deflate64 && crc32 !== UNDEFINED_VALUE && outputSize !== UNDEFINED_VALUE && GzipDecompressionStream) {
254
312
  try {
255
- gzipStream = new DecompressionStream(FORMAT_GZIP);
313
+ gzipStream = new GzipDecompressionStream(FORMAT_GZIP, { chunkSize });
256
314
  } catch {
257
- throw error;
315
+ gzipStream = UNDEFINED_VALUE;
316
+ }
317
+ }
318
+ if (!gzipStream) {
319
+ try {
320
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
321
+ } catch (error) {
322
+ if (deflate64 || outputSize === UNDEFINED_VALUE || (!useCompressionStream && DecompressionStreamFallback)) {
323
+ throw error;
324
+ }
325
+ try {
326
+ gzipStream = new DecompressionStream(FORMAT_GZIP);
327
+ } catch {
328
+ throw error;
329
+ }
258
330
  }
259
- gzipCrc32 = new Crc32();
260
- readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, gzipCrc32);
331
+ }
332
+ if (gzipStream) {
333
+ gzipCrc32 = true;
334
+ readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32);
261
335
  }
262
336
  }
263
337
  readable = mapInflateStreamError(readable);
@@ -267,8 +341,8 @@ class InflateStream extends TransformStream {
267
341
  readable = pipeThrough(readable, crc32Stream);
268
342
  }
269
343
  setReadable(this, readable, () => {
270
- if (checkCrc32) {
271
- const computedCrc32 = gzipCrc32 ? gzipCrc32.get() >>> 0 : new DataView(crc32Stream.value.buffer).getUint32(0, false);
344
+ if (crc32Stream) {
345
+ const computedCrc32 = new DataView(crc32Stream.value.buffer).getUint32(0, false);
272
346
  if (crc32 != computedCrc32) {
273
347
  throw new Error(ERR_INVALID_CRC32);
274
348
  }
@@ -343,11 +417,11 @@ function createCodecStream(CodecStreamClass, format, options) {
343
417
  return new CodecStreamClass(format, options);
344
418
  }
345
419
 
346
- function getGzipCompressionStream(useCompressionStream, CompressionStreamNative, CompressionStreamFallback) {
347
- if (useCompressionStream && CompressionStreamNative) {
348
- return CompressionStreamNative;
349
- } else if (CompressionStreamFallback && CompressionStreamFallback.requiresModule) {
350
- return CompressionStreamFallback;
420
+ function getGzipCodecStream(useCompressionStream, CodecStreamNative, CodecStreamFallback) {
421
+ if (useCompressionStream && CodecStreamNative) {
422
+ return CodecStreamNative;
423
+ } else if (CodecStreamFallback && CodecStreamFallback.requiresModule) {
424
+ return CodecStreamFallback;
351
425
  }
352
426
  }
353
427
 
@@ -417,10 +491,15 @@ function mapInflateStreamError(readable) {
417
491
  const reader = readable.getReader();
418
492
  return new ReadableStream({
419
493
  async pull(controller) {
420
- let result;
421
494
  try {
422
- result = await reader.read();
495
+ const { value, done } = await reader.read();
496
+ if (done) {
497
+ controller.close();
498
+ } else {
499
+ controller.enqueue(value);
500
+ }
423
501
  } catch (error) {
502
+ await cancel(reader, error);
424
503
  if (error && error.message) {
425
504
  throw error;
426
505
  }
@@ -428,12 +507,6 @@ function mapInflateStreamError(readable) {
428
507
  mappedError.cause = error;
429
508
  throw mappedError;
430
509
  }
431
- const { value, done } = result;
432
- if (done) {
433
- controller.close();
434
- } else {
435
- controller.enqueue(value);
436
- }
437
510
  },
438
511
  cancel(reason) {
439
512
  return reader.cancel(reason);