unzipit 2.0.0 → 2.0.3

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # unzipit.js
1
+ # unzipit
2
2
 
3
3
  <img src="./unzipit-no-anim.png" style="max-width: 640px">
4
-
4
+ <!-- cut here -->
5
5
  Random access unzip library for browser and node based JavaScript
6
6
 
7
- ![Build Status](https://github.com/greggman/unzipit/actions/workflows/test.yml/badge.svg)
7
+ [![Build and Deploy](https://github.com/greggman/unzipit/actions/workflows/build_and_deploy.yml/badge.svg)](https://github.com/greggman/unzipit/actions/workflows/build_and_deploy.yml)
8
8
  [[Live Tests](https://greggman.github.io/unzipit/test/)]
9
9
 
10
10
  * Less than 5k gzipped without workers, Less than 6k with.
@@ -90,7 +90,7 @@ You can also pass a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Bl
90
90
  or your own `Reader`
91
91
 
92
92
  For using without a builder/bundler grab `unzipit.min.js` or `unzipit.module.js` from
93
- the [`dist`](https://github.com/greggman/unzipit/tree/master/dist) folder and
93
+ the [`dist`](https://github.com/greggman/unzipit/tree/gh-pages/dist) folder and
94
94
  include with
95
95
 
96
96
  ```js
@@ -106,13 +106,13 @@ or
106
106
  or vs CDN
107
107
 
108
108
  ```js
109
- import * as unzipit from 'https://unpkg.com/unzipit@1.4.0/dist/unzipit.module.js';
109
+ import * as unzipit from 'https://unpkg.com/unzipit@2.0.1/dist/unzipit.module.js';
110
110
  ```
111
111
 
112
112
  or
113
113
 
114
114
  ```html
115
- <script src="https://unpkg.com/unzipit@1.4.0/dist/unzipit.js"></script>
115
+ <script src="https://unpkg.com/unzipit@2.0.1/dist/unzipit.js"></script>
116
116
  ```
117
117
  ## Node
118
118
 
@@ -279,6 +279,7 @@ entries.forEach(entry => {
279
279
  Some libraries both zip and unzip.
280
280
  IMO those should be separate libraries as there is little if any code to share between
281
281
  both. Plenty of projects only need to do one or the other.
282
+ [Here's the sister library for zip](https://greggman.github.io/zipup/).
282
283
 
283
284
  Similarly inflate and deflate libraries should be separate from zip, unzip libraries.
284
285
  You need one or the other not both. See zlib as an example.
@@ -336,6 +337,9 @@ manages them. They don't count as part of the JavaScript heap.
336
337
  In node, the examples with the file readers will only read the header and whatever entries' contents
337
338
  you ask for so similarly you can avoid having everything in memory except the things you read.
338
339
 
340
+ # Zip Creation
341
+
342
+ see: [zipup](https://greggman.github.io/zipup/)
339
343
 
340
344
  # API
341
345
 
@@ -0,0 +1,15 @@
1
+ export interface InflateRequestData {
2
+ id: number;
3
+ type?: string;
4
+ src: Uint8Array<ArrayBuffer> | Blob;
5
+ uncompressedSize: number;
6
+ }
7
+ export interface InflateRequestMessage {
8
+ type: 'inflate';
9
+ data: InflateRequestData;
10
+ }
11
+ export interface InflateResultMessage {
12
+ id: number;
13
+ data?: ArrayBuffer | Blob;
14
+ error?: string;
15
+ }
@@ -1,4 +1,4 @@
1
- /* unzipit@2.0.0, license MIT */
1
+ /* unzipit@2.0.3, license MIT */
2
2
  (function (factory) {
3
3
  typeof define === 'function' && define.amd ? define(factory) :
4
4
  factory();
@@ -47,18 +47,23 @@
47
47
  // then 50 blobs will be asked to be read at once.
48
48
  // If feels like that should happen at a higher level (user code)
49
49
  // or a lower level (the browser)?
50
- async function decompressRaw(src) {
50
+ async function decompressRaw(src, maxLimit) {
51
51
  const ds = new DecompressionStream('deflate-raw');
52
52
  const writer = ds.writable.getWriter();
53
53
  writer.write(src).then(() => writer.close()).catch(() => { });
54
54
  const chunks = [];
55
55
  const reader = ds.readable.getReader();
56
+ let seen = 0;
56
57
  for (;;) {
57
58
  const { done, value } = await reader.read();
58
59
  if (done) {
59
60
  break;
60
61
  }
61
62
  chunks.push(value);
63
+ seen += value.byteLength;
64
+ if (typeof maxLimit === 'number' && seen > maxLimit) {
65
+ throw new Error(`decompressed size exceeds limit: ${seen} > ${maxLimit}`);
66
+ }
62
67
  }
63
68
  const size = chunks.reduce((s, c) => s + c.byteLength, 0);
64
69
  const result = new Uint8Array(size);
@@ -75,7 +80,9 @@
75
80
  const srcData = isBlob(src)
76
81
  ? await readBlobAsUint8Array(src)
77
82
  : new Uint8Array(src);
78
- const dstData = await decompressRaw(srcData);
83
+ // Enforce declared uncompressedSize as the streaming limit
84
+ const limit = typeof req.uncompressedSize === 'number' ? req.uncompressedSize : undefined;
85
+ const dstData = await decompressRaw(srcData, limit);
79
86
  const transferables = [];
80
87
  let data;
81
88
  if (type) {
@@ -104,15 +111,14 @@
104
111
  if (isNode) {
105
112
  // Use dynamic import so this works in both CJS and ESM contexts.
106
113
  // The import of a built-in resolves before any messages can arrive.
107
- import('worker_threads').then(({ parentPort }) => {
114
+ const moduleId = 'node:worker_threads';
115
+ import(moduleId).then(({ parentPort }) => {
108
116
  parentPort.on('message', (msg) => {
109
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
117
  handleMessage(msg, (m, t) => parentPort.postMessage(m, t));
111
118
  });
112
119
  });
113
120
  }
114
121
  else {
115
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
122
  const workerSelf = self;
117
123
  workerSelf.addEventListener('message', (e) => {
118
124
  handleMessage(e.data, (m, t) => workerSelf.postMessage(m, t));
@@ -1 +1 @@
1
- !function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";var e,r;async function n(e){const r=await function(e){return e.arrayBuffer?e.arrayBuffer():new Promise((r,n)=>{const t=new FileReader;t.addEventListener("loadend",()=>{r(t.result)}),t.addEventListener("error",n),t.readAsArrayBuffer(e)})}(e);return new Uint8Array(r)}async function t(e,r){const{id:t,src:o,type:s}=e;try{const e=(a=o,"undefined"!=typeof Blob&&a instanceof Blob?await n(o):new Uint8Array(o)),i=await async function(e){const r=new DecompressionStream("deflate-raw"),n=r.writable.getWriter();n.write(e).then(()=>n.close()).catch(()=>{});const t=[],o=r.readable.getReader();for(;;){const{done:e,value:r}=await o.read();if(e)break;t.push(r)}const s=t.reduce((e,r)=>e+r.byteLength,0),a=new Uint8Array(s);let i=0;for(const e of t)a.set(e,i),i+=e.byteLength;return a}(e),d=[];let c;s?c=new Blob([i],{type:s}):(c=i.buffer,d.push(c)),r({id:t,data:c},d)}catch(e){console.error(e),r({id:t,error:`${e}`})}var a}function o(e,r){const{type:n,data:o}=e;if("inflate"!==n)throw new Error("no handler for type: "+n);t(o,r)}if("undefined"!=typeof process&&!!(null===process||void 0===process?void 0:process.versions)&&void 0!==(null===(e=null===process||void 0===process?void 0:process.versions)||void 0===e?void 0:e.node)&&void 0===(null===(r=null===process||void 0===process?void 0:process.versions)||void 0===r?void 0:r.electron))import("worker_threads").then(({parentPort:e})=>{e.on("message",r=>{o(r,(r,n)=>e.postMessage(r,n))})});else{const e=self;e.addEventListener("message",r=>{o(r.data,(r,n)=>e.postMessage(r,n))}),e.postMessage("start")}});
1
+ !function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";var e,r;async function n(e){const r=await function(e){return e.arrayBuffer?e.arrayBuffer():new Promise((r,n)=>{const o=new FileReader;o.addEventListener("loadend",()=>{r(o.result)}),o.addEventListener("error",n),o.readAsArrayBuffer(e)})}(e);return new Uint8Array(r)}async function o(e,r){const{id:o,src:t,type:s}=e;try{const a=(i=t,"undefined"!=typeof Blob&&i instanceof Blob?await n(t):new Uint8Array(t)),d="number"==typeof e.uncompressedSize?e.uncompressedSize:void 0,c=await async function(e,r){const n=new DecompressionStream("deflate-raw"),o=n.writable.getWriter();o.write(e).then(()=>o.close()).catch(()=>{});const t=[],s=n.readable.getReader();let i=0;for(;;){const{done:e,value:n}=await s.read();if(e)break;if(t.push(n),i+=n.byteLength,"number"==typeof r&&i>r)throw new Error(`decompressed size exceeds limit: ${i} > ${r}`)}const a=t.reduce((e,r)=>e+r.byteLength,0),d=new Uint8Array(a);let c=0;for(const e of t)d.set(e,c),c+=e.byteLength;return d}(a,d),f=[];let u;s?u=new Blob([c],{type:s}):(u=c.buffer,f.push(u)),r({id:o,data:u},f)}catch(e){console.error(e),r({id:o,error:`${e}`})}var i}function t(e,r){const{type:n,data:t}=e;if("inflate"!==n)throw new Error("no handler for type: "+n);o(t,r)}if("undefined"!=typeof process&&!!(null===process||void 0===process?void 0:process.versions)&&void 0!==(null===(e=null===process||void 0===process?void 0:process.versions)||void 0===e?void 0:e.node)&&void 0===(null===(r=null===process||void 0===process?void 0:process.versions)||void 0===r?void 0:r.electron)){import("node:worker_threads").then(({parentPort:e})=>{e.on("message",r=>{t(r,(r,n)=>e.postMessage(r,n))})})}else{const e=self;e.addEventListener("message",r=>{t(r.data,(r,n)=>e.postMessage(r,n))}),e.postMessage("start")}});
@@ -1,4 +1,4 @@
1
- /* unzipit@2.0.0, license MIT */
1
+ /* unzipit@2.0.3, license MIT */
2
2
  var _a, _b;
3
3
  function readBlobAsArrayBuffer(blob) {
4
4
  if (blob.arrayBuffer) {
@@ -42,18 +42,23 @@ const isNode = (typeof process !== 'undefined') &&
42
42
  // then 50 blobs will be asked to be read at once.
43
43
  // If feels like that should happen at a higher level (user code)
44
44
  // or a lower level (the browser)?
45
- async function decompressRaw(src) {
45
+ async function decompressRaw(src, maxLimit) {
46
46
  const ds = new DecompressionStream('deflate-raw');
47
47
  const writer = ds.writable.getWriter();
48
48
  writer.write(src).then(() => writer.close()).catch(() => { });
49
49
  const chunks = [];
50
50
  const reader = ds.readable.getReader();
51
+ let seen = 0;
51
52
  for (;;) {
52
53
  const { done, value } = await reader.read();
53
54
  if (done) {
54
55
  break;
55
56
  }
56
57
  chunks.push(value);
58
+ seen += value.byteLength;
59
+ if (typeof maxLimit === 'number' && seen > maxLimit) {
60
+ throw new Error(`decompressed size exceeds limit: ${seen} > ${maxLimit}`);
61
+ }
57
62
  }
58
63
  const size = chunks.reduce((s, c) => s + c.byteLength, 0);
59
64
  const result = new Uint8Array(size);
@@ -70,7 +75,9 @@ async function inflate(req, postMessage) {
70
75
  const srcData = isBlob(src)
71
76
  ? await readBlobAsUint8Array(src)
72
77
  : new Uint8Array(src);
73
- const dstData = await decompressRaw(srcData);
78
+ // Enforce declared uncompressedSize as the streaming limit
79
+ const limit = typeof req.uncompressedSize === 'number' ? req.uncompressedSize : undefined;
80
+ const dstData = await decompressRaw(srcData, limit);
74
81
  const transferables = [];
75
82
  let data;
76
83
  if (type) {
@@ -99,15 +106,14 @@ function handleMessage(msg, postMessage) {
99
106
  if (isNode) {
100
107
  // Use dynamic import so this works in both CJS and ESM contexts.
101
108
  // The import of a built-in resolves before any messages can arrive.
102
- import('worker_threads').then(({ parentPort }) => {
109
+ const moduleId = 'node:worker_threads';
110
+ import(moduleId).then(({ parentPort }) => {
103
111
  parentPort.on('message', (msg) => {
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
112
  handleMessage(msg, (m, t) => parentPort.postMessage(m, t));
106
113
  });
107
114
  });
108
115
  }
109
116
  else {
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
117
  const workerSelf = self;
112
118
  workerSelf.addEventListener('message', (e) => {
113
119
  handleMessage(e.data, (m, t) => workerSelf.postMessage(m, t));
package/dist/unzipit.js CHANGED
@@ -1,4 +1,4 @@
1
- /* unzipit@2.0.0, license MIT */
1
+ /* unzipit@2.0.3, license MIT */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -125,24 +125,30 @@
125
125
  // come in before a worker gets added to `workers`
126
126
  let numWorkers = 0;
127
127
  let canUseWorkers = true; // gets set to false if we can't start a worker
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
128
  const workers = [];
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
129
  const availableWorkers = [];
132
130
  const waitingForWorkerQueue = [];
133
131
  const currentlyProcessingIdToRequestMap = new Map();
134
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
132
  function handleResult(e) {
136
133
  makeWorkerAvailable(e.target);
137
134
  const { id, error, data } = e.data;
138
135
  const request = currentlyProcessingIdToRequestMap.get(id);
139
136
  currentlyProcessingIdToRequestMap.delete(id);
140
137
  if (error) {
141
- request.reject(error);
138
+ // The worker can only structured-clone the error as a string, so wrap it
139
+ // back into an Error to match the rejection type of the non-worker path.
140
+ request.reject(new Error(error));
141
+ return;
142
142
  }
143
- else {
144
- request.resolve(data);
143
+ // Verify that the decompressed size matches the declared uncompressedSize
144
+ // Treat declared size as authoritative metadata that must be verified.
145
+ const expected = request.uncompressedSize;
146
+ const actual = data instanceof ArrayBuffer ? data.byteLength : data === null || data === void 0 ? void 0 : data.size;
147
+ if (typeof expected === 'number' && typeof actual === 'number' && expected !== actual) {
148
+ request.reject(new Error(`decompressed size mismatch. declared: ${expected}, actual: ${actual}`));
149
+ return;
145
150
  }
151
+ request.resolve(data);
146
152
  }
147
153
  // Because Firefox uses non-standard onerror to signal an error.
148
154
  function startWorker(url) {
@@ -164,27 +170,25 @@
164
170
  const workerHelper = (function () {
165
171
  if (isNode) {
166
172
  return {
167
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
173
  async createWorker(url) {
169
- const { Worker } = await import('worker_threads');
174
+ const moduleId = 'node:worker_threads';
175
+ const { Worker } = await import(moduleId);
170
176
  return new Worker(url);
171
177
  },
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
178
  addEventListener(worker, fn) {
174
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
- worker.on('message', (data) => {
179
+ var _a;
180
+ (_a = worker.on) === null || _a === void 0 ? void 0 : _a.call(worker, 'message', (data) => {
176
181
  fn({ target: worker, data });
177
182
  });
178
183
  },
179
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
184
  async terminate(worker) {
181
- await worker.terminate();
185
+ var _a;
186
+ await ((_a = worker.terminate) === null || _a === void 0 ? void 0 : _a.call(worker));
182
187
  },
183
188
  };
184
189
  }
185
190
  else {
186
191
  return {
187
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
192
  async createWorker(url) {
189
193
  // I don't understand this security issue
190
194
  // Apparently there is some iframe setting or http header
@@ -227,23 +231,23 @@
227
231
  console.warn('workers will not be used');
228
232
  throw new Error('can not start workers');
229
233
  },
230
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
234
  addEventListener(worker, fn) {
232
- worker.addEventListener('message', fn);
235
+ var _a;
236
+ // The browser delivers a MessageEvent whose `target` is the worker
237
+ // and whose `data` is the InflateResultMessage, matching WorkerResultEvent.
238
+ (_a = worker.addEventListener) === null || _a === void 0 ? void 0 : _a.call(worker, 'message', fn);
233
239
  },
234
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
240
  async terminate(worker) {
236
- worker.terminate();
241
+ var _a;
242
+ await ((_a = worker.terminate) === null || _a === void 0 ? void 0 : _a.call(worker));
237
243
  },
238
244
  };
239
245
  }
240
246
  }());
241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
247
  function makeWorkerAvailable(worker) {
243
248
  availableWorkers.push(worker);
244
249
  processWaitingForWorkerQueue();
245
250
  }
246
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
247
251
  async function getAvailableWorker() {
248
252
  if (availableWorkers.length === 0 && numWorkers < config.numWorkers) {
249
253
  ++numWorkers; // see comment at numWorkers declaration
@@ -260,7 +264,7 @@
260
264
  }
261
265
  return availableWorkers.pop();
262
266
  }
263
- async function decompressRaw(src) {
267
+ async function decompressRaw(src, limit) {
264
268
  const ds = new DecompressionStream('deflate-raw');
265
269
  const writer = ds.writable.getWriter();
266
270
  // Do not await the write — doing so before reading causes a deadlock when
@@ -268,12 +272,19 @@
268
272
  writer.write(src).then(() => writer.close()).catch(() => { });
269
273
  const chunks = [];
270
274
  const reader = ds.readable.getReader();
275
+ let seen = 0;
271
276
  for (;;) {
272
277
  const { done, value } = await reader.read();
273
278
  if (done) {
274
279
  break;
275
280
  }
281
+ // If this chunk would push us past the configured/declared limit, abort
282
+ const newSeen = seen + value.byteLength;
283
+ if (typeof limit === 'number' && newSeen > limit) {
284
+ throw new Error(`decompressed size exceeds limit: ${newSeen} > ${limit}`);
285
+ }
276
286
  chunks.push(value);
287
+ seen = newSeen;
277
288
  }
278
289
  const size = chunks.reduce((s, c) => s + c.byteLength, 0);
279
290
  const result = new Uint8Array(size);
@@ -287,9 +298,15 @@
287
298
  // @param {Uint8Array} src
288
299
  // @param {string} [type] mime-type
289
300
  // @returns {ArrayBuffer|Blob} ArrayBuffer if type is falsy or Blob otherwise.
290
- async function inflateRawLocal(src, type, resolve, reject) {
301
+ async function inflateRawLocal(src, uncompressedSize, type, resolve, reject) {
291
302
  try {
292
- const dst = await decompressRaw(src);
303
+ const limit = uncompressedSize;
304
+ const dst = await decompressRaw(src, limit);
305
+ const actual = dst.byteLength;
306
+ if (typeof uncompressedSize === 'number' && actual !== uncompressedSize) {
307
+ reject(new Error(`decompressed size mismatch. declared: ${uncompressedSize}, actual: ${actual}`));
308
+ return;
309
+ }
293
310
  resolve(type ? new Blob([dst], { type }) : dst.buffer);
294
311
  }
295
312
  catch (e) {
@@ -297,6 +314,7 @@
297
314
  }
298
315
  }
299
316
  async function processWaitingForWorkerQueue() {
317
+ var _a;
300
318
  if (waitingForWorkerQueue.length === 0) {
301
319
  return;
302
320
  }
@@ -326,7 +344,7 @@
326
344
  //if (!isBlob(src) && !isSharedArrayBuffer(src)) {
327
345
  // transferables.push(src);
328
346
  //}
329
- worker.postMessage({
347
+ (_a = worker.postMessage) === null || _a === void 0 ? void 0 : _a.call(worker, {
330
348
  type: 'inflate',
331
349
  data: {
332
350
  id,
@@ -345,9 +363,14 @@
345
363
  // will then be on the queue. But if we fail to make workers then there
346
364
  // are pending requests.
347
365
  while (waitingForWorkerQueue.length) {
348
- const { src, type, resolve, reject } = waitingForWorkerQueue.shift();
366
+ const { src, uncompressedSize, type, resolve, reject } = waitingForWorkerQueue.shift();
349
367
  const data = isBlob(src) ? await readBlobAsUint8Array(src) : src;
350
- inflateRawLocal(data, type, resolve, reject);
368
+ try {
369
+ await inflateRawLocal(data, uncompressedSize, type, resolve, reject);
370
+ }
371
+ catch (e) {
372
+ reject(e);
373
+ }
351
374
  }
352
375
  }
353
376
  function setOptions$1(options) {
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).unzipit={})}(this,function(e){"use strict";var t,r;function n(e){return e.arrayBuffer?e.arrayBuffer():new Promise((t,r)=>{const n=new FileReader;n.addEventListener("loadend",()=>{t(n.result)}),n.addEventListener("error",r),n.readAsArrayBuffer(e)})}async function s(e){const t=await n(e);return new Uint8Array(t)}function o(e){return"undefined"!=typeof Blob&&e instanceof Blob}function i(e){return"undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer}const a="undefined"!=typeof process&&!!(null===process||void 0===process?void 0:process.versions)&&void 0!==(null===(t=null===process||void 0===process?void 0:process.versions)||void 0===t?void 0:t.node)&&void 0===(null===(r=null===process||void 0===process?void 0:process.versions)||void 0===r?void 0:r.electron);class c{constructor(e){this.typedArray=e instanceof ArrayBuffer||i(e)?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}async getLength(){return this.typedArray.byteLength}async read(e,t){return new Uint8Array(this.typedArray.buffer,this.typedArray.byteOffset+e,t)}}class f{constructor(e){this.blob=e}async getLength(){return this.blob.size}async read(e,t){const r=this.blob.slice(e,e+t),s=await n(r);return new Uint8Array(s)}async sliceAsBlob(e,t,r=""){return this.blob.slice(e,e+t,r)}}const d={numWorkers:1,workerURL:"",useWorkers:!1};let l=0,u=0,h=!0;const w=[],p=[],y=[],m=new Map;function g(e){z(e.target);const{id:t,error:r,data:n}=e.data,s=m.get(t);m.delete(t),r?s.reject(r):s.resolve(n)}function b(e){return new Promise((t,r)=>{const n=new Worker(e);n.onmessage=e=>{"start"===e.data?(n.onerror=null,n.onmessage=null,t(n)):r(new Error(`unexpected message: ${e.data}`))},n.onerror=r})}const v=a?{async createWorker(e){const{Worker:t}=await import("worker_threads");return new t(e)},addEventListener(e,t){e.on("message",r=>{t({target:e,data:r})})},async terminate(e){await e.terminate()}}:{async createWorker(e){try{return await b(e)}catch(t){console.warn("could not load worker:",e)}let t;try{const r=await fetch(e,{mode:"cors"});if(!r.ok)throw new Error(`could not load: ${e}`);t=await r.text(),e=URL.createObjectURL(new Blob([t],{type:"application/javascript"}));const n=await b(e);return d.workerURL=e,n}catch(t){console.warn("could not load worker via fetch:",e)}if(void 0!==t)try{e=`data:application/javascript;base64,${btoa(t)}`;const r=await b(e);return d.workerURL=e,r}catch(e){console.warn("could not load worker via dataURI")}throw console.warn("workers will not be used"),new Error("can not start workers")},addEventListener(e,t){e.addEventListener("message",t)},async terminate(e){e.terminate()}};function z(e){p.push(e),E()}async function L(e,t,r,n){try{const n=await async function(e){const t=new DecompressionStream("deflate-raw"),r=t.writable.getWriter();r.write(e).then(()=>r.close()).catch(()=>{});const n=[],s=t.readable.getReader();for(;;){const{done:e,value:t}=await s.read();if(e)break;n.push(t)}const o=n.reduce((e,t)=>e+t.byteLength,0),i=new Uint8Array(o);let a=0;for(const e of n)i.set(e,a),a+=e.byteLength;return i}(e);r(t?new Blob([n],{type:t}):n.buffer)}catch(e){n(e)}}async function E(){if(0!==y.length){if(d.useWorkers&&h){const e=await async function(){if(0===p.length&&u<d.numWorkers){++u;try{const e=await v.createWorker(d.workerURL);w.push(e),p.push(e),v.addEventListener(e,g)}catch(e){h=!1}}return p.pop()}();if(h){if(e){if(0===y.length)return void z(e);const{id:t,src:r,uncompressedSize:n,type:s,resolve:o,reject:i}=y.shift();m.set(t,{id:t,src:r,uncompressedSize:n,type:s,resolve:o,reject:i});const a=[];e.postMessage({type:"inflate",data:{id:t,type:s,src:r,uncompressedSize:n}},a)}return}}for(;y.length;){const{src:e,type:t,resolve:r,reject:n}=y.shift();L(o(e)?await s(e):e,t,r,n)}}}function B(e,t,r){return new Promise((n,s)=>{y.push({src:e,uncompressedSize:t,type:r,resolve:n,reject:s,id:l++}),E()})}function S(e){e.splice(0,e.length)}class x{constructor(e,t){var r,n;this._reader=e,this._rawEntry=t,this.name=t.name,this.nameBytes=t.nameBytes,this.size=t.uncompressedSize,this.compressedSize=t.compressedSize,this.comment=t.comment,this.commentBytes=t.commentBytes,this.compressionMethod=t.compressionMethod,this.lastModDate=(r=t.lastModFileDate,n=t.lastModFileTime,new Date(1980+(r>>9&127),(r>>5&15)-1,31&r,n>>11&31,n>>5&63,2*(31&n),0)),this.isDirectory=0===t.uncompressedSize&&t.name.endsWith("/"),this.encrypted=!!(1&t.generalPurposeBitFlag),this.externalFileAttributes=t.externalFileAttributes,this.versionMadeBy=t.versionMadeBy}async blob(e="application/octet-stream"){return await async function(e,t,r){const{decompress:n,fileDataStart:s}=await D(e,t);if(!n){const n=await A(e,s,t.compressedSize,r);return o(n)?n:new Blob([n],{type:r})}const i=await A(e,s,t.compressedSize),a=await B((Uint8Array,i),t.uncompressedSize,r);return a}(this._reader,this._rawEntry,e)}async arrayBuffer(){return await async function(e,t){const{decompress:r,fileDataStart:n}=await D(e,t);if(!r){const r=await k(e,n,t.compressedSize);return 0===(s=r).byteOffset&&s.byteLength===s.buffer.byteLength?r.buffer:r.slice().buffer}var s;const o=await A(e,n,t.compressedSize);return await B((Uint8Array,o),t.uncompressedSize)}(this._reader,this._rawEntry)}async text(){const e=await this.arrayBuffer();return M(new Uint8Array(e))}async json(){const e=await this.text();return JSON.parse(e)}}async function k(e,t,r){return await e.read(t,r)}async function A(e,t,r,n){return e.sliceAsBlob?await e.sliceAsBlob(t,r,n):await e.read(t,r)}const $={unsigned:()=>0};function U(e,t){return e[t]+256*e[t+1]}function F(e,t){return e[t]+256*e[t+1]+65536*e[t+2]+16777216*e[t+3]}function O(e,t){return F(e,t)+4294967296*F(e,t+4)}const R=new TextDecoder;function M(e,t){return i(e.buffer)&&(e=new Uint8Array(e)),R.decode(e)}const W=117853008;async function N(e,t,r,n){const s=t-20,o=await k(e,s,20);if(F(o,0)!==W)throw new Error("invalid zip64 end of central directory locator signature");const i=O(o,8),a=await k(e,i,56);if(101075792!==F(a,0))throw new Error("invalid zip64 end of central directory record signature");const c=O(a,32),f=O(a,40);return j(e,O(a,48),f,c,r,n)}const T=33639248;async function j(e,t,r,n,s,o){let i=0;const a=await k(e,t,r),c=[];for(let e=0;e<n;++e){const e=a.subarray(i,i+46),t=F(e,0);if(t!==T)throw new Error(`invalid central directory file header signature: 0x${t.toString(16)}`);const r={versionMadeBy:U(e,4),versionNeededToExtract:U(e,6),generalPurposeBitFlag:U(e,8),compressionMethod:U(e,10),lastModFileTime:U(e,12),lastModFileDate:U(e,14),crc32:F(e,16),compressedSize:F(e,20),uncompressedSize:F(e,24),fileNameLength:U(e,28),extraFieldLength:U(e,30),fileCommentLength:U(e,32),internalFileAttributes:U(e,36),externalFileAttributes:F(e,38),relativeOffsetOfLocalHeader:F(e,42)};if(64&r.generalPurposeBitFlag)throw new Error("strong encryption is not supported");i+=46;const n=a.subarray(i,i+r.fileNameLength+r.extraFieldLength+r.fileCommentLength);r.generalPurposeBitFlag,r.nameBytes=n.slice(0,r.fileNameLength),r.name=M(r.nameBytes);const s=r.fileNameLength+r.extraFieldLength,o=n.slice(r.fileNameLength,s);r.extraFields=[];let f=0;for(;f<o.length-3;){const e=U(o,f+0),t=f+4,n=t+U(o,f+2);if(n>o.length)throw new Error("extra field length exceeds extra field buffer size");r.extraFields.push({id:e,data:o.slice(t,n)}),f=n}if(r.commentBytes=n.slice(s,s+r.fileCommentLength),r.comment=M(r.commentBytes),i+=n.length,4294967295===r.uncompressedSize||4294967295===r.compressedSize||4294967295===r.relativeOffsetOfLocalHeader){const e=r.extraFields.find(e=>1===e.id);if(!e)throw new Error("expected zip64 extended information extra field");const t=e.data;let n=0;if(4294967295===r.uncompressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include uncompressed size");r.uncompressedSize=O(t,n),n+=8}if(4294967295===r.compressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include compressed size");r.compressedSize=O(t,n),n+=8}if(4294967295===r.relativeOffsetOfLocalHeader){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include relative header offset");r.relativeOffsetOfLocalHeader=O(t,n),n+=8}}const d=r.extraFields.find(e=>28789===e.id&&e.data.length>=6&&1===e.data[0]&&F(e.data,1),$.unsigned());if(d&&(r.fileName=M(d.data.slice(5))),0===r.compressionMethod){let e=r.uncompressedSize;if(1&r.generalPurposeBitFlag&&(e+=12),r.compressedSize!==e)throw new Error(`compressed size mismatch for stored file: ${r.compressedSize} != ${e}`)}c.push(r)}return{zip:{comment:s,commentBytes:o},entries:c.map(t=>new x(e,t))}}async function D(e,t){if(1&t.generalPurposeBitFlag)throw new Error("encrypted entries not supported");const r=await k(e,t.relativeOffsetOfLocalHeader,30),n=await e.getLength(),s=F(r,0);if(67324752!==s)throw new Error(`invalid local file header signature: 0x${s.toString(16)}`);const o=U(r,26),i=U(r,28),a=t.relativeOffsetOfLocalHeader+r.length+o+i;let c;if(0===t.compressionMethod)c=!1;else{if(8!==t.compressionMethod)throw new Error(`unsupported compression method: ${t.compressionMethod}`);c=!0}const f=a,d=f+t.compressedSize;if(0!==t.compressedSize&&d>n)throw new Error(`file data overflows file bounds: ${f} + ${t.compressedSize} > ${n}`);return{decompress:c,fileDataStart:f}}async function P(e){let t;if("undefined"!=typeof Blob&&e instanceof Blob)t=new f(e);else if(e instanceof ArrayBuffer||e&&e.buffer&&e.buffer instanceof ArrayBuffer)t=new c(e);else if(i(e)||i(e.buffer))t=new c(e);else if("string"==typeof e){const r=await fetch(e);if(!r.ok)throw new Error(`failed http request ${e}, status: ${r.status}: ${r.statusText}`);const n=await r.blob();t=new f(n)}else{if("function"!=typeof e.getLength||"function"!=typeof e.read)throw new Error("unsupported source type");t=e}const r=await t.getLength();if(r>Number.MAX_SAFE_INTEGER)throw new Error(`file too large. size: ${r}. Only file sizes up 4503599627370496 bytes are supported`);return await async function(e,t){const r=Math.min(65557,t),n=t-r,s=await k(e,n,r);for(let t=r-22;t>=0;--t){if(101010256!==F(s,t))continue;const r=new Uint8Array(s.buffer,s.byteOffset+t,s.byteLength-t),o=U(r,4);if(0!==o)throw new Error(`multi-volume zip files are not supported. This is volume: ${o}`);const i=U(r,10),a=F(r,12),c=F(r,16),f=U(r,20),d=r.length-22;if(f!==d)throw new Error(`invalid comment length. expected: ${d}, actual: ${f}`);const l=new Uint8Array(r.buffer,r.byteOffset+22,f),u=M(l);return 65535===i||4294967295===c?await N(e,n+t,u,l):await j(e,c,a,i,u,l)}throw new Error("could not find end of central directory. maybe not zip file")}(t,r)}e.ArrayBufferReader=c,e.BlobReader=f,e.HTTPRangeReader=class{constructor(e){this.url=e}async getLength(){if(void 0===this.length){const e=await fetch(this.url,{method:"HEAD"});if(!e.ok)throw new Error(`failed http request ${this.url}, status: ${e.status}: ${e.statusText}`);if(this.length=parseInt(e.headers.get("content-length")),Number.isNaN(this.length))throw Error("could not get length")}return this.length}async read(e,t){if(0===t)return new Uint8Array(0);const r=await fetch(this.url,{headers:{Range:`bytes=${e}-${e+t-1}`}});if(!r.ok)throw new Error(`failed http request ${this.url}, status: ${r.status} offset: ${e} size: ${t}: ${r.statusText}`);const n=await r.arrayBuffer();return new Uint8Array(n)}},e.ZipEntry=x,e.cleanup=function(){!async function(){for(const e of w)await v.terminate(e);S(w),S(p),S(y),m.clear(),u=0,h=!0}()},e.setOptions=function(e){!function(e){d.workerURL=e.workerURL||d.workerURL,e.workerURL&&(d.useWorkers=!0),d.useWorkers=void 0!==e.useWorkers?e.useWorkers:d.useWorkers,d.numWorkers=e.numWorkers||d.numWorkers}(e)},e.unzip=async function(e){const{zip:t,entries:r}=await P(e);return{zip:t,entries:Object.fromEntries(r.map(e=>[e.name,e]))}},e.unzipRaw=P});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).unzipit={})}(this,function(e){"use strict";var t,r;function n(e){return e.arrayBuffer?e.arrayBuffer():new Promise((t,r)=>{const n=new FileReader;n.addEventListener("loadend",()=>{t(n.result)}),n.addEventListener("error",r),n.readAsArrayBuffer(e)})}async function o(e){const t=await n(e);return new Uint8Array(t)}function s(e){return"undefined"!=typeof Blob&&e instanceof Blob}function i(e){return"undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer}const a="undefined"!=typeof process&&!!(null===process||void 0===process?void 0:process.versions)&&void 0!==(null===(t=null===process||void 0===process?void 0:process.versions)||void 0===t?void 0:t.node)&&void 0===(null===(r=null===process||void 0===process?void 0:process.versions)||void 0===r?void 0:r.electron);class c{constructor(e){this.typedArray=e instanceof ArrayBuffer||i(e)?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}async getLength(){return this.typedArray.byteLength}async read(e,t){return new Uint8Array(this.typedArray.buffer,this.typedArray.byteOffset+e,t)}}class d{constructor(e){this.blob=e}async getLength(){return this.blob.size}async read(e,t){const r=this.blob.slice(e,e+t),o=await n(r);return new Uint8Array(o)}async sliceAsBlob(e,t,r=""){return this.blob.slice(e,e+t,r)}}const f={numWorkers:1,workerURL:"",useWorkers:!1};let l=0,u=0,h=!0;const p=[],w=[],m=[],y=new Map;function g(e){z(e.target);const{id:t,error:r,data:n}=e.data,o=y.get(t);if(y.delete(t),r)return void o.reject(new Error(r));const s=o.uncompressedSize,i=n instanceof ArrayBuffer?n.byteLength:null==n?void 0:n.size;"number"!=typeof s||"number"!=typeof i||s===i?o.resolve(n):o.reject(new Error(`decompressed size mismatch. declared: ${s}, actual: ${i}`))}function b(e){return new Promise((t,r)=>{const n=new Worker(e);n.onmessage=e=>{"start"===e.data?(n.onerror=null,n.onmessage=null,t(n)):r(new Error(`unexpected message: ${e.data}`))},n.onerror=r})}const v=a?{async createWorker(e){const{Worker:t}=await import("node:worker_threads");return new t(e)},addEventListener(e,t){var r;null===(r=e.on)||void 0===r||r.call(e,"message",r=>{t({target:e,data:r})})},async terminate(e){var t;await(null===(t=e.terminate)||void 0===t?void 0:t.call(e))}}:{async createWorker(e){try{return await b(e)}catch(t){console.warn("could not load worker:",e)}let t;try{const r=await fetch(e,{mode:"cors"});if(!r.ok)throw new Error(`could not load: ${e}`);t=await r.text(),e=URL.createObjectURL(new Blob([t],{type:"application/javascript"}));const n=await b(e);return f.workerURL=e,n}catch(t){console.warn("could not load worker via fetch:",e)}if(void 0!==t)try{e=`data:application/javascript;base64,${btoa(t)}`;const r=await b(e);return f.workerURL=e,r}catch(e){console.warn("could not load worker via dataURI")}throw console.warn("workers will not be used"),new Error("can not start workers")},addEventListener(e,t){var r;null===(r=e.addEventListener)||void 0===r||r.call(e,"message",t)},async terminate(e){var t;await(null===(t=e.terminate)||void 0===t?void 0:t.call(e))}};function z(e){w.push(e),E()}async function L(e,t,r,n,o){try{const s=t,i=await async function(e,t){const r=new DecompressionStream("deflate-raw"),n=r.writable.getWriter();n.write(e).then(()=>n.close()).catch(()=>{});const o=[],s=r.readable.getReader();let i=0;for(;;){const{done:e,value:r}=await s.read();if(e)break;const n=i+r.byteLength;if("number"==typeof t&&n>t)throw new Error(`decompressed size exceeds limit: ${n} > ${t}`);o.push(r),i=n}const a=o.reduce((e,t)=>e+t.byteLength,0),c=new Uint8Array(a);let d=0;for(const e of o)c.set(e,d),d+=e.byteLength;return c}(e,s),a=i.byteLength;if("number"==typeof t&&a!==t)return void o(new Error(`decompressed size mismatch. declared: ${t}, actual: ${a}`));n(r?new Blob([i],{type:r}):i.buffer)}catch(e){o(e)}}async function E(){var e;if(0!==m.length){if(f.useWorkers&&h){const t=await async function(){if(0===w.length&&u<f.numWorkers){++u;try{const e=await v.createWorker(f.workerURL);p.push(e),w.push(e),v.addEventListener(e,g)}catch(e){h=!1}}return w.pop()}();if(h){if(t){if(0===m.length)return void z(t);const{id:r,src:n,uncompressedSize:o,type:s,resolve:i,reject:a}=m.shift();y.set(r,{id:r,src:n,uncompressedSize:o,type:s,resolve:i,reject:a});const c=[];null===(e=t.postMessage)||void 0===e||e.call(t,{type:"inflate",data:{id:r,type:s,src:n,uncompressedSize:o}},c)}return}}for(;m.length;){const{src:e,uncompressedSize:t,type:r,resolve:n,reject:i}=m.shift(),a=s(e)?await o(e):e;try{await L(a,t,r,n,i)}catch(e){i(e)}}}}function B(e,t,r){return new Promise((n,o)=>{m.push({src:e,uncompressedSize:t,type:r,resolve:n,reject:o,id:l++}),E()})}function S(e){e.splice(0,e.length)}class x{constructor(e,t){var r,n;this._reader=e,this._rawEntry=t,this.name=t.name,this.nameBytes=t.nameBytes,this.size=t.uncompressedSize,this.compressedSize=t.compressedSize,this.comment=t.comment,this.commentBytes=t.commentBytes,this.compressionMethod=t.compressionMethod,this.lastModDate=(r=t.lastModFileDate,n=t.lastModFileTime,new Date(1980+(r>>9&127),(r>>5&15)-1,31&r,n>>11&31,n>>5&63,2*(31&n),0)),this.isDirectory=0===t.uncompressedSize&&t.name.endsWith("/"),this.encrypted=!!(1&t.generalPurposeBitFlag),this.externalFileAttributes=t.externalFileAttributes,this.versionMadeBy=t.versionMadeBy}async blob(e="application/octet-stream"){return await async function(e,t,r){const{decompress:n,fileDataStart:o}=await D(e,t);if(!n){const n=await k(e,o,t.compressedSize,r);return s(n)?n:new Blob([n],{type:r})}const i=await k(e,o,t.compressedSize),a=await B((Uint8Array,i),t.uncompressedSize,r);return a}(this._reader,this._rawEntry,e)}async arrayBuffer(){return await async function(e,t){const{decompress:r,fileDataStart:n}=await D(e,t);if(!r){const r=await A(e,n,t.compressedSize);return 0===(o=r).byteOffset&&o.byteLength===o.buffer.byteLength?r.buffer:r.slice().buffer}var o;const s=await k(e,n,t.compressedSize);return await B((Uint8Array,s),t.uncompressedSize)}(this._reader,this._rawEntry)}async text(){const e=await this.arrayBuffer();return M(new Uint8Array(e))}async json(){const e=await this.text();return JSON.parse(e)}}async function A(e,t,r){return await e.read(t,r)}async function k(e,t,r,n){return e.sliceAsBlob?await e.sliceAsBlob(t,r,n):await e.read(t,r)}const $={unsigned:()=>0};function U(e,t){return e[t]+256*e[t+1]}function F(e,t){return e[t]+256*e[t+1]+65536*e[t+2]+16777216*e[t+3]}function O(e,t){return F(e,t)+4294967296*F(e,t+4)}const R=new TextDecoder;function M(e,t){return i(e.buffer)&&(e=new Uint8Array(e)),R.decode(e)}const W=117853008;async function N(e,t,r,n){const o=t-20,s=await A(e,o,20);if(F(s,0)!==W)throw new Error("invalid zip64 end of central directory locator signature");const i=O(s,8),a=await A(e,i,56);if(101075792!==F(a,0))throw new Error("invalid zip64 end of central directory record signature");const c=O(a,32),d=O(a,40);return j(e,O(a,48),d,c,r,n)}const T=33639248;async function j(e,t,r,n,o,s){let i=0;const a=await A(e,t,r),c=[];for(let e=0;e<n;++e){const e=a.subarray(i,i+46),t=F(e,0);if(t!==T)throw new Error(`invalid central directory file header signature: 0x${t.toString(16)}`);const r={versionMadeBy:U(e,4),versionNeededToExtract:U(e,6),generalPurposeBitFlag:U(e,8),compressionMethod:U(e,10),lastModFileTime:U(e,12),lastModFileDate:U(e,14),crc32:F(e,16),compressedSize:F(e,20),uncompressedSize:F(e,24),fileNameLength:U(e,28),extraFieldLength:U(e,30),fileCommentLength:U(e,32),internalFileAttributes:U(e,36),externalFileAttributes:F(e,38),relativeOffsetOfLocalHeader:F(e,42)};if(64&r.generalPurposeBitFlag)throw new Error("strong encryption is not supported");i+=46;const n=a.subarray(i,i+r.fileNameLength+r.extraFieldLength+r.fileCommentLength);r.generalPurposeBitFlag,r.nameBytes=n.slice(0,r.fileNameLength),r.name=M(r.nameBytes);const o=r.fileNameLength+r.extraFieldLength,s=n.slice(r.fileNameLength,o);r.extraFields=[];let d=0;for(;d<s.length-3;){const e=U(s,d+0),t=d+4,n=t+U(s,d+2);if(n>s.length)throw new Error("extra field length exceeds extra field buffer size");r.extraFields.push({id:e,data:s.slice(t,n)}),d=n}if(r.commentBytes=n.slice(o,o+r.fileCommentLength),r.comment=M(r.commentBytes),i+=n.length,4294967295===r.uncompressedSize||4294967295===r.compressedSize||4294967295===r.relativeOffsetOfLocalHeader){const e=r.extraFields.find(e=>1===e.id);if(!e)throw new Error("expected zip64 extended information extra field");const t=e.data;let n=0;if(4294967295===r.uncompressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include uncompressed size");r.uncompressedSize=O(t,n),n+=8}if(4294967295===r.compressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include compressed size");r.compressedSize=O(t,n),n+=8}if(4294967295===r.relativeOffsetOfLocalHeader){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include relative header offset");r.relativeOffsetOfLocalHeader=O(t,n),n+=8}}const f=r.extraFields.find(e=>28789===e.id&&e.data.length>=6&&1===e.data[0]&&F(e.data,1),$.unsigned());if(f&&(r.fileName=M(f.data.slice(5))),0===r.compressionMethod){let e=r.uncompressedSize;if(1&r.generalPurposeBitFlag&&(e+=12),r.compressedSize!==e)throw new Error(`compressed size mismatch for stored file: ${r.compressedSize} != ${e}`)}c.push(r)}return{zip:{comment:o,commentBytes:s},entries:c.map(t=>new x(e,t))}}async function D(e,t){if(1&t.generalPurposeBitFlag)throw new Error("encrypted entries not supported");const r=await A(e,t.relativeOffsetOfLocalHeader,30),n=await e.getLength(),o=F(r,0);if(67324752!==o)throw new Error(`invalid local file header signature: 0x${o.toString(16)}`);const s=U(r,26),i=U(r,28),a=t.relativeOffsetOfLocalHeader+r.length+s+i;let c;if(0===t.compressionMethod)c=!1;else{if(8!==t.compressionMethod)throw new Error(`unsupported compression method: ${t.compressionMethod}`);c=!0}const d=a,f=d+t.compressedSize;if(0!==t.compressedSize&&f>n)throw new Error(`file data overflows file bounds: ${d} + ${t.compressedSize} > ${n}`);return{decompress:c,fileDataStart:d}}async function P(e){let t;if("undefined"!=typeof Blob&&e instanceof Blob)t=new d(e);else if(e instanceof ArrayBuffer||e&&e.buffer&&e.buffer instanceof ArrayBuffer)t=new c(e);else if(i(e)||i(e.buffer))t=new c(e);else if("string"==typeof e){const r=await fetch(e);if(!r.ok)throw new Error(`failed http request ${e}, status: ${r.status}: ${r.statusText}`);const n=await r.blob();t=new d(n)}else{if("function"!=typeof e.getLength||"function"!=typeof e.read)throw new Error("unsupported source type");t=e}const r=await t.getLength();if(r>Number.MAX_SAFE_INTEGER)throw new Error(`file too large. size: ${r}. Only file sizes up 4503599627370496 bytes are supported`);return await async function(e,t){const r=Math.min(65557,t),n=t-r,o=await A(e,n,r);for(let t=r-22;t>=0;--t){if(101010256!==F(o,t))continue;const r=new Uint8Array(o.buffer,o.byteOffset+t,o.byteLength-t),s=U(r,4);if(0!==s)throw new Error(`multi-volume zip files are not supported. This is volume: ${s}`);const i=U(r,10),a=F(r,12),c=F(r,16),d=U(r,20),f=r.length-22;if(d!==f)throw new Error(`invalid comment length. expected: ${f}, actual: ${d}`);const l=new Uint8Array(r.buffer,r.byteOffset+22,d),u=M(l);return 65535===i||4294967295===c?await N(e,n+t,u,l):await j(e,c,a,i,u,l)}throw new Error("could not find end of central directory. maybe not zip file")}(t,r)}e.ArrayBufferReader=c,e.BlobReader=d,e.HTTPRangeReader=class{constructor(e){this.url=e}async getLength(){if(void 0===this.length){const e=await fetch(this.url,{method:"HEAD"});if(!e.ok)throw new Error(`failed http request ${this.url}, status: ${e.status}: ${e.statusText}`);if(this.length=parseInt(e.headers.get("content-length")),Number.isNaN(this.length))throw Error("could not get length")}return this.length}async read(e,t){if(0===t)return new Uint8Array(0);const r=await fetch(this.url,{headers:{Range:`bytes=${e}-${e+t-1}`}});if(!r.ok)throw new Error(`failed http request ${this.url}, status: ${r.status} offset: ${e} size: ${t}: ${r.statusText}`);const n=await r.arrayBuffer();return new Uint8Array(n)}},e.ZipEntry=x,e.cleanup=function(){!async function(){for(const e of p)await v.terminate(e);S(p),S(w),S(m),y.clear(),u=0,h=!0}()},e.setOptions=function(e){!function(e){f.workerURL=e.workerURL||f.workerURL,e.workerURL&&(f.useWorkers=!0),f.useWorkers=void 0!==e.useWorkers?e.useWorkers:f.useWorkers,f.numWorkers=e.numWorkers||f.numWorkers}(e)},e.unzip=async function(e){const{zip:t,entries:r}=await P(e);return{zip:t,entries:Object.fromEntries(r.map(e=>[e.name,e]))}},e.unzipRaw=P});
@@ -1,4 +1,4 @@
1
- /* unzipit@2.0.0, license MIT */
1
+ /* unzipit@2.0.3, license MIT */
2
2
  var _a, _b;
3
3
  function readBlobAsArrayBuffer(blob) {
4
4
  if (blob.arrayBuffer) {
@@ -119,24 +119,30 @@ let nextId = 0;
119
119
  // come in before a worker gets added to `workers`
120
120
  let numWorkers = 0;
121
121
  let canUseWorkers = true; // gets set to false if we can't start a worker
122
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
122
  const workers = [];
124
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
123
  const availableWorkers = [];
126
124
  const waitingForWorkerQueue = [];
127
125
  const currentlyProcessingIdToRequestMap = new Map();
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
126
  function handleResult(e) {
130
127
  makeWorkerAvailable(e.target);
131
128
  const { id, error, data } = e.data;
132
129
  const request = currentlyProcessingIdToRequestMap.get(id);
133
130
  currentlyProcessingIdToRequestMap.delete(id);
134
131
  if (error) {
135
- request.reject(error);
132
+ // The worker can only structured-clone the error as a string, so wrap it
133
+ // back into an Error to match the rejection type of the non-worker path.
134
+ request.reject(new Error(error));
135
+ return;
136
136
  }
137
- else {
138
- request.resolve(data);
137
+ // Verify that the decompressed size matches the declared uncompressedSize
138
+ // Treat declared size as authoritative metadata that must be verified.
139
+ const expected = request.uncompressedSize;
140
+ const actual = data instanceof ArrayBuffer ? data.byteLength : data === null || data === void 0 ? void 0 : data.size;
141
+ if (typeof expected === 'number' && typeof actual === 'number' && expected !== actual) {
142
+ request.reject(new Error(`decompressed size mismatch. declared: ${expected}, actual: ${actual}`));
143
+ return;
139
144
  }
145
+ request.resolve(data);
140
146
  }
141
147
  // Because Firefox uses non-standard onerror to signal an error.
142
148
  function startWorker(url) {
@@ -158,27 +164,25 @@ function startWorker(url) {
158
164
  const workerHelper = (function () {
159
165
  if (isNode) {
160
166
  return {
161
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
167
  async createWorker(url) {
163
- const { Worker } = await import('worker_threads');
168
+ const moduleId = 'node:worker_threads';
169
+ const { Worker } = await import(moduleId);
164
170
  return new Worker(url);
165
171
  },
166
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
172
  addEventListener(worker, fn) {
168
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
- worker.on('message', (data) => {
173
+ var _a;
174
+ (_a = worker.on) === null || _a === void 0 ? void 0 : _a.call(worker, 'message', (data) => {
170
175
  fn({ target: worker, data });
171
176
  });
172
177
  },
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
178
  async terminate(worker) {
175
- await worker.terminate();
179
+ var _a;
180
+ await ((_a = worker.terminate) === null || _a === void 0 ? void 0 : _a.call(worker));
176
181
  },
177
182
  };
178
183
  }
179
184
  else {
180
185
  return {
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
186
  async createWorker(url) {
183
187
  // I don't understand this security issue
184
188
  // Apparently there is some iframe setting or http header
@@ -221,23 +225,23 @@ const workerHelper = (function () {
221
225
  console.warn('workers will not be used');
222
226
  throw new Error('can not start workers');
223
227
  },
224
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
228
  addEventListener(worker, fn) {
226
- worker.addEventListener('message', fn);
229
+ var _a;
230
+ // The browser delivers a MessageEvent whose `target` is the worker
231
+ // and whose `data` is the InflateResultMessage, matching WorkerResultEvent.
232
+ (_a = worker.addEventListener) === null || _a === void 0 ? void 0 : _a.call(worker, 'message', fn);
227
233
  },
228
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
234
  async terminate(worker) {
230
- worker.terminate();
235
+ var _a;
236
+ await ((_a = worker.terminate) === null || _a === void 0 ? void 0 : _a.call(worker));
231
237
  },
232
238
  };
233
239
  }
234
240
  }());
235
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
241
  function makeWorkerAvailable(worker) {
237
242
  availableWorkers.push(worker);
238
243
  processWaitingForWorkerQueue();
239
244
  }
240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
245
  async function getAvailableWorker() {
242
246
  if (availableWorkers.length === 0 && numWorkers < config.numWorkers) {
243
247
  ++numWorkers; // see comment at numWorkers declaration
@@ -254,7 +258,7 @@ async function getAvailableWorker() {
254
258
  }
255
259
  return availableWorkers.pop();
256
260
  }
257
- async function decompressRaw(src) {
261
+ async function decompressRaw(src, limit) {
258
262
  const ds = new DecompressionStream('deflate-raw');
259
263
  const writer = ds.writable.getWriter();
260
264
  // Do not await the write — doing so before reading causes a deadlock when
@@ -262,12 +266,19 @@ async function decompressRaw(src) {
262
266
  writer.write(src).then(() => writer.close()).catch(() => { });
263
267
  const chunks = [];
264
268
  const reader = ds.readable.getReader();
269
+ let seen = 0;
265
270
  for (;;) {
266
271
  const { done, value } = await reader.read();
267
272
  if (done) {
268
273
  break;
269
274
  }
275
+ // If this chunk would push us past the configured/declared limit, abort
276
+ const newSeen = seen + value.byteLength;
277
+ if (typeof limit === 'number' && newSeen > limit) {
278
+ throw new Error(`decompressed size exceeds limit: ${newSeen} > ${limit}`);
279
+ }
270
280
  chunks.push(value);
281
+ seen = newSeen;
271
282
  }
272
283
  const size = chunks.reduce((s, c) => s + c.byteLength, 0);
273
284
  const result = new Uint8Array(size);
@@ -281,9 +292,15 @@ async function decompressRaw(src) {
281
292
  // @param {Uint8Array} src
282
293
  // @param {string} [type] mime-type
283
294
  // @returns {ArrayBuffer|Blob} ArrayBuffer if type is falsy or Blob otherwise.
284
- async function inflateRawLocal(src, type, resolve, reject) {
295
+ async function inflateRawLocal(src, uncompressedSize, type, resolve, reject) {
285
296
  try {
286
- const dst = await decompressRaw(src);
297
+ const limit = uncompressedSize;
298
+ const dst = await decompressRaw(src, limit);
299
+ const actual = dst.byteLength;
300
+ if (typeof uncompressedSize === 'number' && actual !== uncompressedSize) {
301
+ reject(new Error(`decompressed size mismatch. declared: ${uncompressedSize}, actual: ${actual}`));
302
+ return;
303
+ }
287
304
  resolve(type ? new Blob([dst], { type }) : dst.buffer);
288
305
  }
289
306
  catch (e) {
@@ -291,6 +308,7 @@ async function inflateRawLocal(src, type, resolve, reject) {
291
308
  }
292
309
  }
293
310
  async function processWaitingForWorkerQueue() {
311
+ var _a;
294
312
  if (waitingForWorkerQueue.length === 0) {
295
313
  return;
296
314
  }
@@ -320,7 +338,7 @@ async function processWaitingForWorkerQueue() {
320
338
  //if (!isBlob(src) && !isSharedArrayBuffer(src)) {
321
339
  // transferables.push(src);
322
340
  //}
323
- worker.postMessage({
341
+ (_a = worker.postMessage) === null || _a === void 0 ? void 0 : _a.call(worker, {
324
342
  type: 'inflate',
325
343
  data: {
326
344
  id,
@@ -339,9 +357,14 @@ async function processWaitingForWorkerQueue() {
339
357
  // will then be on the queue. But if we fail to make workers then there
340
358
  // are pending requests.
341
359
  while (waitingForWorkerQueue.length) {
342
- const { src, type, resolve, reject } = waitingForWorkerQueue.shift();
360
+ const { src, uncompressedSize, type, resolve, reject } = waitingForWorkerQueue.shift();
343
361
  const data = isBlob(src) ? await readBlobAsUint8Array(src) : src;
344
- inflateRawLocal(data, type, resolve, reject);
362
+ try {
363
+ await inflateRawLocal(data, uncompressedSize, type, resolve, reject);
364
+ }
365
+ catch (e) {
366
+ reject(e);
367
+ }
345
368
  }
346
369
  }
347
370
  function setOptions$1(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unzipit",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "random access unzip library for JavaScript",
5
5
  "type": "module",
6
6
  "main": "dist/unzipit.module.js",
@@ -10,11 +10,12 @@
10
10
  "node": ">=18"
11
11
  },
12
12
  "scripts": {
13
- "build": "npm run build-js && npm run build-types && node build/copy.js node_modules/chai/index.js test/chai.js && node build/copy.js node_modules/mocha/mocha.js test/mocha.js && node build/copy.js node_modules/mocha/mocha.css test/mocha.css",
13
+ "build": "npm run build-js && npm run build-types && npm run build-ts-test && node build/copy.js node_modules/chai/index.js test/chai.js && node build/copy.js node_modules/mocha/mocha.js test/mocha.js && node build/copy.js node_modules/mocha/mocha.css test/mocha.css",
14
14
  "build-ci": "npm run build && node build/prep-for-deploy.js",
15
15
  "build-normal": "rollup -c",
16
16
  "build-js": "rollup -c",
17
17
  "build-types": "tsc -p tsconfig.build.json",
18
+ "build-ts-test": "rollup -c test/ts/rollup.test.config.js",
18
19
  "eslint": "eslint src/**/*.ts test/index.js test/node-test.js test/puppeteer.js test/tests/**/*.js",
19
20
  "test": "npm run test-node && npm run test-browser",
20
21
  "test-node": "mocha test/node-test.js",