@zip.js/zip.js 2.16.0 → 2.17.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 (49) hide show
  1. package/deno.json +1 -1
  2. package/dist/zip-core-external.js +429 -274
  3. package/dist/zip-core-external.min.js +1 -1
  4. package/dist/zip-core.js +230 -151
  5. package/dist/zip-core.min.js +1 -1
  6. package/dist/zip-fs-core-external.js +580 -283
  7. package/dist/zip-fs-core-external.min.js +1 -1
  8. package/dist/zip-fs-core.js +383 -160
  9. package/dist/zip-fs-core.min.js +1 -1
  10. package/dist/zip-fs-external.js +580 -283
  11. package/dist/zip-fs-external.min.js +1 -1
  12. package/dist/zip-fs-native.js +386 -163
  13. package/dist/zip-fs-native.min.js +1 -1
  14. package/dist/zip-fs.js +583 -283
  15. package/dist/zip-fs.min.js +1 -1
  16. package/dist/zip-legacy.js +232 -153
  17. package/dist/zip-legacy.min.js +1 -1
  18. package/dist/zip-native.js +233 -154
  19. package/dist/zip-native.min.js +1 -1
  20. package/dist/zip-web-worker-native.js +1 -1
  21. package/dist/zip-web-worker.js +1 -1
  22. package/dist/zip.js +430 -274
  23. package/dist/zip.min.js +1 -1
  24. package/eslint.config.mjs +1 -1
  25. package/index-native.cjs +386 -163
  26. package/index-native.min.js +1 -1
  27. package/index.cjs +583 -283
  28. package/index.d.cts +168 -29
  29. package/index.d.ts +168 -29
  30. package/index.min.js +1 -1
  31. package/lib/core/codec-pool.js +2 -0
  32. package/lib/core/codec-worker-web.js +11 -3
  33. package/lib/core/codec-worker.js +2 -1
  34. package/lib/core/streams/aes-crypto-stream.js +60 -17
  35. package/lib/core/streams/codec-stream.js +2 -0
  36. package/lib/core/streams/zip-crypto-stream.js +7 -7
  37. package/lib/core/streams/zip-entry-stream.js +130 -113
  38. package/lib/core/streams/zlib-js/zlib-streams.min.js +1 -1
  39. package/lib/core/streams/zlib-wasm/zlib-streams.js +199 -122
  40. package/lib/core/util/compatible-streams.js +10 -5
  41. package/lib/core/version.js +1 -1
  42. package/lib/core/web-worker-base.js +1 -1
  43. package/lib/core/web-worker-inline-native.js +1 -1
  44. package/lib/core/web-worker-inline-wasm.js +1 -1
  45. package/lib/core/zip-fs.js +157 -10
  46. package/lib/core/zip-reader.js +8 -5
  47. package/lib/core/zip-writer.js +5 -2
  48. package/lib/zip-core-reader.js +1 -0
  49. package/package.json +3 -2
@@ -37,6 +37,7 @@ import {
37
37
  ERR_INVALID_UNCOMPRESSED_SIZE,
38
38
  ERR_INVALID_PASSWORD,
39
39
  ERR_INVALID_COMPRESSED_DATA,
40
+ ERR_CODEC_OUT_OF_MEMORY,
40
41
  ERR_ABORT_CHECK_PASSWORD,
41
42
  ERR_UNSUPPORTED_CRYPTO_API
42
43
  } from "./streams/codec-stream.js";
@@ -58,6 +59,7 @@ export {
58
59
  ERR_INVALID_UNCOMPRESSED_SIZE,
59
60
  ERR_INVALID_PASSWORD,
60
61
  ERR_INVALID_COMPRESSED_DATA,
62
+ ERR_CODEC_OUT_OF_MEMORY,
61
63
  ERR_ABORT_CHECK_PASSWORD,
62
64
  ERR_UNSUPPORTED_CRYPTO_API,
63
65
  ERR_WORKER_STARTUP_TIMEOUT
@@ -53,6 +53,7 @@ import {
53
53
  const MODULE_WORKER_OPTIONS = { type: "module" };
54
54
  const ERROR_EVENT_TYPE = "error";
55
55
  const MESSAGE_ERROR_EVENT_TYPE = "messageerror";
56
+ const ABORT_EVENT_TYPE = "abort";
56
57
 
57
58
  let webWorkerSource, webWorkerURI, webWorkerOptions;
58
59
  let transferStreamsSupported = true;
@@ -229,6 +230,13 @@ function watchClosedStream(writableSource, workerData) {
229
230
  Object.assign(workerData, { destinationFailed: true, destinationError: error });
230
231
  }
231
232
  });
233
+ const { signal } = workerData.workerOptions.streamOptions;
234
+ if (signal) {
235
+ const onAbort = () => abortController.abort(signal.reason);
236
+ const removeAbortListener = () => signal.removeEventListener(ABORT_EVENT_TYPE, onAbort);
237
+ signal.addEventListener(ABORT_EVENT_TYPE, onAbort);
238
+ closed.then(removeAbortListener, removeAbortListener);
239
+ }
232
240
  return {
233
241
  writable, closed, abortPipe: () => {
234
242
  aborting = true;
@@ -491,9 +499,6 @@ function getResponseError(errorData, errorValue) {
491
499
  responseError = errorValue.value;
492
500
  } else {
493
501
  responseError = Object.assign(new Error(message), { stack, code, name });
494
- if (cause) {
495
- responseError.cause = Object.assign(new Error(cause.message), { name: cause.name });
496
- }
497
502
  }
498
503
  if (isErrorObject(responseError)) {
499
504
  try {
@@ -503,6 +508,9 @@ function getResponseError(errorData, errorValue) {
503
508
  if (codecImportFailed) {
504
509
  responseError.codecImportFailed = true;
505
510
  }
511
+ if (cause && !isErrorObject(responseError.cause)) {
512
+ responseError.cause = Object.assign(new Error(cause.message), { name: cause.name });
513
+ }
506
514
  if (errorValue) {
507
515
  if (responseError.name !== name) {
508
516
  responseError.name = name;
@@ -231,10 +231,11 @@ async function runWorker({ options, readable, writable, onTaskFinished, workerOp
231
231
  }
232
232
  codecStream = new CodecStream(options, config);
233
233
  chunkStream = new ChunkStream(getChunkSize(config));
234
+ const { signal } = workerOptions.streamOptions;
234
235
  await readable
235
236
  .pipeThrough(codecStream)
236
237
  .pipeThrough(chunkStream)
237
- .pipeTo(writable, { preventClose: true, preventAbort: true });
238
+ .pipeTo(writable, { preventClose: true, preventAbort: true, signal });
238
239
  const {
239
240
  crc32,
240
241
  inputSize,
@@ -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
 
@@ -43,6 +43,7 @@ import {
43
43
  ERR_INVALID_AUTHENTICATION_CODE,
44
44
  ERR_INVALID_UNCOMPRESSED_SIZE,
45
45
  ERR_INVALID_COMPRESSED_DATA,
46
+ ERR_CODEC_OUT_OF_MEMORY,
46
47
  ERR_ABORT_CHECK_PASSWORD,
47
48
  ERR_UNSUPPORTED_CRYPTO_API,
48
49
  InflateStream,
@@ -77,6 +78,7 @@ export {
77
78
  ERR_INVALID_AUTHENTICATION_CODE,
78
79
  ERR_INVALID_UNCOMPRESSED_SIZE,
79
80
  ERR_INVALID_COMPRESSED_DATA,
81
+ ERR_CODEC_OUT_OF_MEMORY,
80
82
  ERR_ABORT_CHECK_PASSWORD,
81
83
  ERR_UNSUPPORTED_CRYPTO_API,
82
84
  CodecStream,
@@ -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, MessageChannel */
29
+ /* global TransformStream, ReadableStream */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
32
  import { Crc32 } from "./codecs/crc32.js";
@@ -48,11 +48,14 @@ import {
48
48
  import { UNDEFINED_VALUE } from "../constants.js";
49
49
  import { concat, getDataView } from "../util/array.js";
50
50
  import { toCompatibleReadable } from "../util/compatible-streams.js";
51
+ import { isErrorObject } from "../util/error.js";
51
52
  import { getCodecStreams, ERR_UNSUPPORTED_COMPRESSION } from "../codec-registry.js";
52
53
 
53
54
  const ERR_INVALID_UNCOMPRESSED_SIZE = "Invalid uncompressed size";
54
55
  const ERR_INVALID_COMPRESSED_DATA = "Invalid compressed data";
56
+ const ERR_CODEC_OUT_OF_MEMORY = "Codec out of memory";
55
57
  const ERR_INVALID_CRC32 = "Invalid CRC32";
58
+ const Z_MEM_ERROR_CODE = "Z_MEM_ERROR";
56
59
  const FORMAT_DEFLATE_RAW = "deflate-raw";
57
60
  const FORMAT_DEFLATE64_RAW = "deflate64-raw";
58
61
  const FORMAT_GZIP = "gzip";
@@ -87,13 +90,13 @@ class DeflateStream extends TransformStream {
87
90
  readable = pipeThroughCompressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStream, CompressionStreamFallback);
88
91
  } catch (error) {
89
92
  if (!useCompressionStream && CompressionStreamFallback) {
90
- throw error;
93
+ throw mapMemoryError(error);
91
94
  }
92
95
  let gzipStream;
93
96
  try {
94
97
  gzipStream = new CompressionStream(FORMAT_GZIP);
95
98
  } catch {
96
- throw error;
99
+ throw mapMemoryError(error);
97
100
  }
98
101
  readable = pipeThroughBackpressured(readable, gzipStream);
99
102
  readable = pipeThrough(readable, new GzipToRawDeflateStream());
@@ -157,53 +160,30 @@ class GzipToRawDeflateStream extends TransformStream {
157
160
  }
158
161
  }
159
162
 
160
- function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32) {
163
+ function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32, sourceErrors) {
161
164
  const writer = gzipStream.writable.getWriter();
162
165
  const reader = gzipStream.readable.getReader();
163
166
  const outputCrc32 = crc32 === UNDEFINED_VALUE ? new Crc32() : UNDEFINED_VALUE;
164
167
  let outputLength = 0;
165
- let inputDone = false;
166
168
  let trailerWritten = false;
167
- let idleCheckArmed = false;
168
- let readCount = 0;
169
- let readPending = false;
170
- let resolveTrailerReady, rejectTrailerReady;
171
- const trailerReady = new Promise((resolve, reject) => {
172
- resolveTrailerReady = resolve;
173
- rejectTrailerReady = reject;
174
- });
175
- trailerReady.catch(() => { });
176
- pump();
177
- return new ReadableStream({
178
- async pull(controller) {
179
- let result;
180
- try {
181
- result = await read();
182
- } catch (error) {
183
- throw trailerWritten ? getTrailerError(error) : error;
184
- }
185
- const { value, done } = result;
186
- if (done) {
187
- controller.close();
188
- } else {
189
- outputLength += value.length;
190
- if (outputLength > outputSize) {
191
- const error = new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
192
- rejectTrailerReady(error);
193
- await cancel(reader, error);
194
- throw error;
195
- }
196
- if (outputCrc32) {
197
- outputCrc32.append(value);
198
- }
199
- controller.enqueue(value);
200
- }
169
+ let settled = false;
170
+ let resolvePull, controller;
171
+ const output = new ReadableStream({
172
+ start(streamController) {
173
+ controller = streamController;
174
+ },
175
+ pull() {
176
+ resumePump();
201
177
  },
202
178
  cancel(reason) {
203
- rejectTrailerReady(reason);
179
+ settled = true;
180
+ resumePump();
204
181
  return reader.cancel(reason);
205
182
  }
206
183
  });
184
+ pump();
185
+ drain();
186
+ return output;
207
187
 
208
188
  async function pump() {
209
189
  const inputReader = readable.getReader();
@@ -212,19 +192,16 @@ function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, cr
212
192
  header.set(GZIP_HEADER_BYTES);
213
193
  await writer.write(header);
214
194
  for (; ;) {
195
+ await outputCapacity();
215
196
  await writer.ready;
216
- const { value, done } = await inputReader.read();
197
+ const { value, done } = await readSource(inputReader, sourceErrors);
217
198
  if (done) {
218
199
  break;
219
200
  }
220
201
  await writer.write(value);
221
202
  }
222
- inputDone = true;
223
203
  if (outputCrc32) {
224
- if (readPending) {
225
- armIdleCheck();
226
- }
227
- await trailerReady;
204
+ await writer.write(new Uint8Array(0));
228
205
  }
229
206
  const trailer = new Uint8Array(GZIP_TRAILER_LENGTH);
230
207
  const dataView = getDataView(trailer);
@@ -239,54 +216,68 @@ function pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, cr
239
216
  }
240
217
  }
241
218
 
242
- function read() {
243
- readCount++;
244
- readPending = true;
245
- const result = reader.read();
246
- result.then(onReadSettled, onReadSettled);
247
- if (inputDone && outputCrc32) {
248
- armIdleCheck();
219
+ async function drain() {
220
+ try {
221
+ for (; ;) {
222
+ const { value, done } = await read();
223
+ if (done) {
224
+ break;
225
+ }
226
+ outputLength += value.length;
227
+ if (outputLength > outputSize) {
228
+ throw new Error(ERR_INVALID_UNCOMPRESSED_SIZE);
229
+ }
230
+ if (outputCrc32) {
231
+ outputCrc32.append(value);
232
+ }
233
+ controller.enqueue(value);
234
+ }
235
+ if (!settled) {
236
+ settled = true;
237
+ controller.close();
238
+ }
239
+ } catch (error) {
240
+ fail(error);
241
+ await cancel(reader, error);
249
242
  }
250
- return result;
251
243
  }
252
244
 
253
- function onReadSettled() {
254
- readPending = false;
255
- }
256
-
257
- async function armIdleCheck() {
258
- if (!idleCheckArmed) {
259
- idleCheckArmed = true;
260
- const count = readCount;
261
- await nextTask();
262
- idleCheckArmed = false;
263
- if (readPending) {
264
- if (readCount == count) {
265
- resolveTrailerReady();
266
- } else {
267
- armIdleCheck();
245
+ function read() {
246
+ return reader.read().catch(error => {
247
+ if (trailerWritten) {
248
+ if (!outputCrc32) {
249
+ throw mapError(error, ERR_INVALID_CRC32);
250
+ }
251
+ if (outputLength != outputSize) {
252
+ throw mapError(error, ERR_INVALID_UNCOMPRESSED_SIZE);
268
253
  }
254
+ return { done: true };
269
255
  }
270
- }
256
+ throw mapCodecError(error, sourceErrors);
257
+ });
271
258
  }
272
259
 
273
- function getTrailerError(error) {
274
- const trailerError = new Error(outputLength == outputSize ? ERR_INVALID_CRC32 : ERR_INVALID_UNCOMPRESSED_SIZE);
275
- trailerError.cause = error;
276
- return trailerError;
260
+ function outputCapacity() {
261
+ if (!settled && controller.desiredSize <= 0) {
262
+ return new Promise(resolve => resolvePull = resolve);
263
+ }
277
264
  }
278
- }
279
265
 
280
- function nextTask() {
281
- return new Promise(resolve => {
282
- const { port1, port2 } = new MessageChannel();
283
- port2.onmessage = () => {
284
- port1.close();
285
- port2.close();
266
+ function resumePump() {
267
+ if (resolvePull) {
268
+ const resolve = resolvePull;
269
+ resolvePull = UNDEFINED_VALUE;
286
270
  resolve();
287
- };
288
- port1.postMessage(UNDEFINED_VALUE);
289
- });
271
+ }
272
+ }
273
+
274
+ function fail(error) {
275
+ if (!settled) {
276
+ settled = true;
277
+ controller.error(error);
278
+ resumePump();
279
+ }
280
+ }
290
281
  }
291
282
 
292
283
  class InflateStream extends TransformStream {
@@ -305,11 +296,12 @@ class InflateStream extends TransformStream {
305
296
  }
306
297
  }
307
298
  if (compressed) {
299
+ const sourceErrors = new Set();
308
300
  const codecStreams = format && getCodecStreams(format);
301
+ let gzipStream;
309
302
  if (codecStreams) {
310
- readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }));
303
+ readable = pipeThroughBackpressured(readable, createCodecStream(codecStreams.DecompressionStream, format, { chunkSize, compressionMethod, rawBitFlag, uncompressedSize: outputSize }), sourceErrors);
311
304
  } else {
312
- let gzipStream;
313
305
  const GzipDecompressionStream = getGzipCodecStream(useCompressionStream, DecompressionStream, DecompressionStreamFallback);
314
306
  if (checkCrc32 && !deflate64 && crc32 !== UNDEFINED_VALUE && outputSize !== UNDEFINED_VALUE && GzipDecompressionStream) {
315
307
  try {
@@ -320,24 +312,25 @@ class InflateStream extends TransformStream {
320
312
  }
321
313
  if (!gzipStream) {
322
314
  try {
323
- readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback);
315
+ readable = pipeThroughCompressionStream(readable, useCompressionStream, { chunkSize, deflate64 }, DecompressionStream, DecompressionStreamFallback, sourceErrors);
324
316
  } catch (error) {
325
317
  if (deflate64 || outputSize === UNDEFINED_VALUE || (!useCompressionStream && DecompressionStreamFallback)) {
326
- throw error;
318
+ throw mapMemoryError(error);
327
319
  }
328
320
  try {
329
321
  gzipStream = new DecompressionStream(FORMAT_GZIP);
330
322
  } catch {
331
- throw error;
323
+ throw mapMemoryError(error);
332
324
  }
333
325
  }
334
326
  }
335
- if (gzipStream) {
336
- gzipCrc32 = true;
337
- readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32);
338
- }
339
327
  }
340
- readable = mapInflateStreamError(readable);
328
+ if (gzipStream) {
329
+ gzipCrc32 = true;
330
+ readable = pipeThroughGzipDecompressionStream(readable, gzipStream, outputSize, crc32, sourceErrors);
331
+ } else {
332
+ readable = mapInflateStreamError(readable, sourceErrors);
333
+ }
341
334
  }
342
335
  if (checkCrc32 && !gzipCrc32) {
343
336
  crc32Stream = new Crc32Stream();
@@ -400,6 +393,7 @@ export {
400
393
  ERR_INVALID_AUTHENTICATION_CODE,
401
394
  ERR_INVALID_UNCOMPRESSED_SIZE,
402
395
  ERR_INVALID_COMPRESSED_DATA,
396
+ ERR_CODEC_OUT_OF_MEMORY,
403
397
  ERR_ABORT_CHECK_PASSWORD,
404
398
  ERR_UNSUPPORTED_CRYPTO_API
405
399
  };
@@ -428,7 +422,7 @@ function getGzipCodecStream(useCompressionStream, CodecStreamNative, CodecStream
428
422
  }
429
423
  }
430
424
 
431
- function pipeThroughCompressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamFallback) {
425
+ function pipeThroughCompressionStream(readable, useCompressionStream, options, CompressionStreamNative, CompressionStreamFallback, sourceErrors) {
432
426
  const Stream = useCompressionStream && CompressionStreamNative ?
433
427
  CompressionStreamNative :
434
428
  CompressionStreamFallback || CompressionStreamNative;
@@ -443,14 +437,14 @@ function pipeThroughCompressionStream(readable, useCompressionStream, options, C
443
437
  throw error;
444
438
  }
445
439
  }
446
- return pipeThroughBackpressured(readable, codecStream);
440
+ return pipeThroughBackpressured(readable, codecStream, sourceErrors);
447
441
  }
448
442
 
449
443
  function pipeThrough(readable, transformStream) {
450
444
  return toCompatibleReadable(readable).pipeThrough(transformStream);
451
445
  }
452
446
 
453
- function pipeThroughBackpressured(readable, transformStream) {
447
+ function pipeThroughBackpressured(readable, transformStream, sourceErrors) {
454
448
  const writer = transformStream.writable.getWriter();
455
449
  const reader = readable.getReader();
456
450
  pump();
@@ -460,7 +454,7 @@ function pipeThroughBackpressured(readable, transformStream) {
460
454
  try {
461
455
  for (; ;) {
462
456
  await writer.ready;
463
- const result = await reader.read();
457
+ const result = await readSource(reader, sourceErrors);
464
458
  if (result.done) {
465
459
  await writer.close();
466
460
  break;
@@ -490,26 +484,49 @@ async function cancel(reader, error) {
490
484
  }
491
485
  }
492
486
 
493
- function mapInflateStreamError(readable) {
487
+ function readSource(reader, sourceErrors) {
488
+ const result = reader.read();
489
+ return sourceErrors ? result.catch(error => {
490
+ sourceErrors.add(error);
491
+ throw error;
492
+ }) : result;
493
+ }
494
+
495
+ function mapCodecError(error, sourceErrors) {
496
+ if (sourceErrors.has(error)) {
497
+ return error;
498
+ }
499
+ return mapError(error, isMemoryError(error) ? ERR_CODEC_OUT_OF_MEMORY : ERR_INVALID_COMPRESSED_DATA);
500
+ }
501
+
502
+ function mapMemoryError(error) {
503
+ return isMemoryError(error) ? mapError(error, ERR_CODEC_OUT_OF_MEMORY) : error;
504
+ }
505
+
506
+ function isMemoryError(error) {
507
+ return isErrorObject(error) && error.code == Z_MEM_ERROR_CODE;
508
+ }
509
+
510
+ function mapError(error, message) {
511
+ const mappedError = new Error(message);
512
+ mappedError.cause = error;
513
+ return mappedError;
514
+ }
515
+
516
+ function mapInflateStreamError(readable, sourceErrors) {
494
517
  const reader = readable.getReader();
495
518
  return new ReadableStream({
496
519
  async pull(controller) {
497
- let result;
498
520
  try {
499
- result = await reader.read();
500
- } catch (error) {
501
- if (error && error.message) {
502
- throw error;
521
+ const { value, done } = await reader.read();
522
+ if (done) {
523
+ controller.close();
524
+ } else {
525
+ controller.enqueue(value);
503
526
  }
504
- const mappedError = new Error(ERR_INVALID_COMPRESSED_DATA);
505
- mappedError.cause = error;
506
- throw mappedError;
507
- }
508
- const { value, done } = result;
509
- if (done) {
510
- controller.close();
511
- } else {
512
- controller.enqueue(value);
527
+ } catch (error) {
528
+ await cancel(reader, error);
529
+ throw mapCodecError(error, sourceErrors);
513
530
  }
514
531
  },
515
532
  cancel(reason) {