@gyng/remote-zip 0.2.5 → 1.0.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.
- package/LICENSE-APACHE +201 -0
- package/{LICENSE → LICENSE-MIT} +1 -1
- package/README.md +89 -41
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/index.js.map +7 -0
- package/lib/esm/index.mjs +2 -0
- package/lib/esm/index.mjs.map +7 -0
- package/lib/types/crypto.d.mts +39 -0
- package/lib/types/crypto.d.ts +39 -0
- package/lib/types/index.d.mts +1 -0
- package/lib/types/zip.d.mts +459 -0
- package/lib/types/zip.d.ts +142 -13
- package/package.json +71 -45
- package/lib/types/zip.test.d.ts +0 -1
package/lib/types/zip.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
export { crc32 } from "./crypto";
|
|
2
|
+
/** Machine-readable discriminant for {@link RemoteZipError}. */
|
|
3
|
+
export type RemoteZipErrorCode = "UNKNOWN" | "CONTENT_LENGTH_MISSING" | "INVALID_CONTENT_LENGTH" | "HTTP_ERROR" | "RANGE_NOT_SUPPORTED" | "RANGE_RESPONSE_MISMATCH" | "EOCD_NOT_FOUND" | "UNSUPPORTED_ZIP64" | "UNSUPPORTED_ENCRYPTION" | "FILE_NOT_FOUND" | "LOCAL_HEADER_PARSE_FAILED" | "CENTRAL_DIRECTORY_OUT_OF_BOUNDS" | "INVALID_ARCHIVE" | "TRUNCATED_ENTRY" | "UNSUPPORTED_COMPRESSION" | "UNSUPPORTED_MULTI_DISK" | "DECOMPRESSION_LIMIT_EXCEEDED" | "CRC_MISMATCH" | "WRONG_PASSWORD" | "DECRYPTION_FAILED";
|
|
1
4
|
export declare class RemoteZipError extends Error {
|
|
2
|
-
|
|
5
|
+
/** Machine-readable error code, for programmatic handling without matching on `message`. */
|
|
6
|
+
code: RemoteZipErrorCode;
|
|
7
|
+
constructor(message: string, code?: RemoteZipErrorCode);
|
|
8
|
+
}
|
|
9
|
+
/** Per-call options for {@link RemoteZip.fetch} / {@link RemoteZip.fetchStream}. */
|
|
10
|
+
export interface EntryDecodeOptions {
|
|
11
|
+
/** If set, decompression aborts and throws once output would exceed this many bytes. */
|
|
12
|
+
maxUncompressedSize?: number;
|
|
13
|
+
/** Aborts this request (in addition to any instance-level signal). */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Per-request timeout in milliseconds. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
/** If set, verify the decompressed output against the entry's CRC-32. */
|
|
18
|
+
verifyCrc?: boolean;
|
|
19
|
+
/** Password for an encrypted entry (traditional ZipCrypto or WinZip AES). */
|
|
20
|
+
password?: string;
|
|
3
21
|
}
|
|
4
22
|
export interface EndOfCentralDirectory {
|
|
5
23
|
meta: Record<string, unknown>;
|
|
@@ -18,9 +36,9 @@ export interface EndOfCentralDirectory {
|
|
|
18
36
|
centralDirectoryByteSize: number;
|
|
19
37
|
/** Offset of start of central directory, relative to start of archive (or 0xffffffff for ZIP64) */
|
|
20
38
|
centralDirectoryByteOffset: number;
|
|
21
|
-
/** Comment length (n) */
|
|
22
|
-
comment: string;
|
|
23
39
|
/** Comment */
|
|
40
|
+
comment: string;
|
|
41
|
+
/** Comment length (n) */
|
|
24
42
|
commentLength: number;
|
|
25
43
|
};
|
|
26
44
|
}
|
|
@@ -237,7 +255,15 @@ export declare class RemoteZip {
|
|
|
237
255
|
method: string;
|
|
238
256
|
/** Credentials passed to `fetch` when retrieving files. Defaults to `same-origin`. */
|
|
239
257
|
credentials: "include" | "omit" | "same-origin";
|
|
240
|
-
|
|
258
|
+
/** Redirect mode passed to `fetch`. Defaults to `"follow"`. */
|
|
259
|
+
redirect?: RequestRedirect;
|
|
260
|
+
/** Signal that aborts in-flight requests. */
|
|
261
|
+
signal?: AbortSignal;
|
|
262
|
+
/** Per-request timeout in milliseconds. */
|
|
263
|
+
timeoutMs?: number;
|
|
264
|
+
/** Extra RequestInit merged into every `fetch`. */
|
|
265
|
+
requestInit?: RequestInit;
|
|
266
|
+
constructor({ contentLength, url, centralDirectoryRecords, endOfCentralDirectory, method, credentials, redirect, signal, timeoutMs, requestInit, }: {
|
|
241
267
|
/** Length of the remote ZIP archive in bytes */
|
|
242
268
|
contentLength: number;
|
|
243
269
|
/** Passed to fetch when performing a HTTP GET request for the file */
|
|
@@ -247,8 +273,10 @@ export declare class RemoteZip {
|
|
|
247
273
|
/** Passed to fetch when performing a HTTP GET request for the file */
|
|
248
274
|
method: string;
|
|
249
275
|
/** Passed to fetch when performing a HTTP GET request for the file. */
|
|
250
|
-
credentials
|
|
251
|
-
});
|
|
276
|
+
credentials?: "include" | "omit" | "same-origin";
|
|
277
|
+
} & Pick<RemoteZipRequestOptions, "redirect" | "signal" | "timeoutMs" | "requestInit">);
|
|
278
|
+
/** Build a fetch RequestInit from this instance's network options. */
|
|
279
|
+
private requestInitFor;
|
|
252
280
|
/**
|
|
253
281
|
* Get a formatted file listing of the remote ZIP archive.
|
|
254
282
|
*
|
|
@@ -269,10 +297,64 @@ export declare class RemoteZip {
|
|
|
269
297
|
*
|
|
270
298
|
* @param path Path of the file in the remote ZIP archive
|
|
271
299
|
* @param additionalHeaders Additional headers, if any, to be passed to the `fetch` request
|
|
300
|
+
* @param options.maxUncompressedSize If set, inflation aborts and throws once the
|
|
301
|
+
* decompressed output would exceed this many bytes. Set this when handling
|
|
302
|
+
* untrusted archives to guard against decompression bombs.
|
|
303
|
+
* @param options.signal Aborts this request (in addition to any instance-level signal).
|
|
304
|
+
* @param options.timeoutMs Per-request timeout for this fetch.
|
|
305
|
+
* @param options.verifyCrc If set, verify the decompressed output against the
|
|
306
|
+
* entry's CRC-32 and throw a {@link RemoteZipError} (`CRC_MISMATCH`) on mismatch.
|
|
272
307
|
* @returns Inflated (uncompressed) bytes of the requested file
|
|
273
|
-
* @throws
|
|
308
|
+
* @throws {@link RemoteZipError} if it fails to parse, fetch, or exceeds limits
|
|
309
|
+
*/
|
|
310
|
+
fetch(path: string, additionalHeaders?: Headers, options?: EntryDecodeOptions): Promise<Uint8Array>;
|
|
311
|
+
/**
|
|
312
|
+
* Decrypt (if needed), decompress, and CRC-verify one entry's bytes. Shared by
|
|
313
|
+
* {@link fetch} and the encrypted path of {@link fetchStream}.
|
|
314
|
+
*/
|
|
315
|
+
private decodeEntry;
|
|
316
|
+
/**
|
|
317
|
+
* Like {@link fetch}, but returns a `ReadableStream` of the uncompressed bytes
|
|
318
|
+
* so large entries can be processed incrementally without buffering the whole
|
|
319
|
+
* file. `maxUncompressedSize` is enforced mid-stream (the stream errors with a
|
|
320
|
+
* {@link RemoteZipError} once exceeded).
|
|
321
|
+
*
|
|
322
|
+
* ```ts
|
|
323
|
+
* const stream = await remoteZip.fetchStream("big.bin");
|
|
324
|
+
* for await (const chunk of stream) {
|
|
325
|
+
* // process each chunk
|
|
326
|
+
* }
|
|
327
|
+
* ```
|
|
274
328
|
*/
|
|
275
|
-
|
|
329
|
+
fetchStream(path: string, additionalHeaders?: Headers, options?: EntryDecodeOptions): Promise<ReadableStream<Uint8Array>>;
|
|
330
|
+
/**
|
|
331
|
+
* Find an entry, reject encrypted ones, and issue the Range request that covers
|
|
332
|
+
* its local file header + compressed data. Shared by {@link fetch} and
|
|
333
|
+
* {@link fetchStream}.
|
|
334
|
+
*/
|
|
335
|
+
private fetchEntryResponse;
|
|
336
|
+
private fetchRange;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Network options shared by every request a {@link RemoteZip} / {@link RemoteZipPointer}
|
|
340
|
+
* makes. All are optional with sensible defaults.
|
|
341
|
+
*/
|
|
342
|
+
export interface RemoteZipRequestOptions {
|
|
343
|
+
/** HTTP method for the Range GET requests. The metadata probe is always a HEAD. Defaults to `"GET"`. */
|
|
344
|
+
method?: string;
|
|
345
|
+
/** Passed to `fetch`. Defaults to `"same-origin"`. */
|
|
346
|
+
credentials?: "include" | "omit" | "same-origin";
|
|
347
|
+
/**
|
|
348
|
+
* Passed to `fetch`. Defaults to `"follow"`. Use `"manual"` or `"error"` to avoid
|
|
349
|
+
* leaking `Authorization`/cookies cross-origin when a server responds with a redirect.
|
|
350
|
+
*/
|
|
351
|
+
redirect?: RequestRedirect;
|
|
352
|
+
/** Aborts in-flight requests when this signal fires. */
|
|
353
|
+
signal?: AbortSignal;
|
|
354
|
+
/** Per-request timeout in milliseconds; combined with `signal` via `AbortSignal.any`. */
|
|
355
|
+
timeoutMs?: number;
|
|
356
|
+
/** Escape hatch merged into every `fetch` RequestInit (lowest precedence — the options above win). */
|
|
357
|
+
requestInit?: RequestInit;
|
|
276
358
|
}
|
|
277
359
|
/**
|
|
278
360
|
* An uninitialised pointer to a remote ZIP file.
|
|
@@ -283,7 +365,7 @@ export declare class RemoteZip {
|
|
|
283
365
|
* const url = new URL("http://www.example.com/test.zip");
|
|
284
366
|
* const remoteZip = await new RemoteZipPointer({ url }).populate();
|
|
285
367
|
* const fileListing = remoteZip.files(); // RemoteZipFile[]
|
|
286
|
-
* const uncompressedBytes = await remoteZip.fetch("test.txt"); //
|
|
368
|
+
* const uncompressedBytes = await remoteZip.fetch("test.txt"); // Uint8Array
|
|
287
369
|
* ```
|
|
288
370
|
*/
|
|
289
371
|
export declare class RemoteZipPointer {
|
|
@@ -297,7 +379,15 @@ export declare class RemoteZipPointer {
|
|
|
297
379
|
method: string;
|
|
298
380
|
/** Credentials passed to `fetch` when retrieving files. Defaults to `same-origin`. */
|
|
299
381
|
credentials: "include" | "omit" | "same-origin";
|
|
300
|
-
|
|
382
|
+
/** Redirect mode passed to `fetch`. Defaults to `"follow"`. */
|
|
383
|
+
redirect?: RequestRedirect;
|
|
384
|
+
/** Signal that aborts in-flight requests. */
|
|
385
|
+
signal?: AbortSignal;
|
|
386
|
+
/** Per-request timeout in milliseconds. */
|
|
387
|
+
timeoutMs?: number;
|
|
388
|
+
/** Extra RequestInit merged into every `fetch`. */
|
|
389
|
+
requestInit?: RequestInit;
|
|
390
|
+
constructor({ url, headUrl, additionalHeaders, method, credentials, redirect, signal, timeoutMs, requestInit, }: {
|
|
301
391
|
/** URL for GET requests */
|
|
302
392
|
url: URL;
|
|
303
393
|
/** Passed to fetch when performing a HTTP GET request for the file */
|
|
@@ -308,15 +398,18 @@ export declare class RemoteZipPointer {
|
|
|
308
398
|
credentials?: "include" | "omit" | "same-origin";
|
|
309
399
|
/** URL for HEAD request. Defaults to `url`. This can, for example, differ from `url` if you are using a signed URL for S3. */
|
|
310
400
|
headUrl?: URL;
|
|
311
|
-
});
|
|
401
|
+
} & Pick<RemoteZipRequestOptions, "redirect" | "signal" | "timeoutMs" | "requestInit">);
|
|
402
|
+
/** Build a fetch RequestInit from this pointer's network options. */
|
|
403
|
+
private requestInitFor;
|
|
312
404
|
/**
|
|
313
405
|
* Gets metadata about the ZIP file and constructs an initialised `RemoteZip`.
|
|
314
406
|
*
|
|
315
|
-
* @returns An initialised
|
|
316
|
-
* @throws
|
|
407
|
+
* @returns An initialised {@link RemoteZip}
|
|
408
|
+
* @throws {@link RemoteZipError} if it fails to parse or fetch
|
|
317
409
|
*/
|
|
318
410
|
populate(): Promise<RemoteZip>;
|
|
319
411
|
private fetchEndOfCentralDirectory;
|
|
412
|
+
private fetchZip64EOCD;
|
|
320
413
|
private fetchCentralDirectoryRecords;
|
|
321
414
|
}
|
|
322
415
|
/**
|
|
@@ -328,3 +421,39 @@ export declare class RemoteZipPointer {
|
|
|
328
421
|
* @see https://github.com/Stuk/jszip/blob/112fcdb9953c6b9a2744afee451d73029f7cd2f8/lib/reader/DataReader.js#L105
|
|
329
422
|
*/
|
|
330
423
|
export declare const parseZipDatetime: (zipDate: number, zipTime: number) => string;
|
|
424
|
+
/**
|
|
425
|
+
* Detects whether an EOCD record points at ZIP64 structures this library does
|
|
426
|
+
* not parse yet. Any ZIP64 sentinel value (`0xffff` for 16-bit fields,
|
|
427
|
+
* `0xffffffff` for 32-bit fields) means the real value lives in a ZIP64 EOCD.
|
|
428
|
+
*/
|
|
429
|
+
export declare const isZip64: (eocd: EndOfCentralDirectory) => boolean;
|
|
430
|
+
/** Effective central-directory location from a ZIP64 End Of Central Directory record. */
|
|
431
|
+
export interface Zip64EndOfCentralDirectory {
|
|
432
|
+
diskNumber: number;
|
|
433
|
+
cdDisk: number;
|
|
434
|
+
centralDirectoryDiskRecordCount: number;
|
|
435
|
+
centralDirectoryRecordCount: number;
|
|
436
|
+
centralDirectoryByteSize: number;
|
|
437
|
+
centralDirectoryByteOffset: number;
|
|
438
|
+
}
|
|
439
|
+
/** Locate the ZIP64 EOCD locator in a tail buffer and return the ZIP64 EOCD offset. */
|
|
440
|
+
export declare const parseZip64EOCDLocator: (buffer: ArrayBuffer) => {
|
|
441
|
+
diskWithZip64EOCD: number;
|
|
442
|
+
zip64EOCDOffset: number;
|
|
443
|
+
totalDisks: number;
|
|
444
|
+
} | null;
|
|
445
|
+
/** Parse a ZIP64 End Of Central Directory record (the real CD offset/size/count). */
|
|
446
|
+
export declare const parseZip64EOCD: (buffer: ArrayBuffer) => Zip64EndOfCentralDirectory | null;
|
|
447
|
+
/**
|
|
448
|
+
* Decode a ZIP filename/comment. ZIP entries use CP437 unless general-purpose
|
|
449
|
+
* bit 11 marks the field as UTF-8; ASCII is identical in both.
|
|
450
|
+
*/
|
|
451
|
+
export declare const decodeZipString: (bytes: ArrayBuffer, utf8: boolean) => string;
|
|
452
|
+
export declare const parseAllCDs: (buffer: ArrayBuffer) => CentralDirectoryRecord[];
|
|
453
|
+
export declare const parseOneCD: (buffer: ArrayBuffer) => CentralDirectoryRecord | null;
|
|
454
|
+
export declare const parseOneEOCD: (buffer: ArrayBuffer) => EndOfCentralDirectory | null;
|
|
455
|
+
export declare const parseOneLocalFile: (buffer: ArrayBuffer,
|
|
456
|
+
/** Sometimes, the local header does not have the compressed size and a data descriptor is used after the compressed data.
|
|
457
|
+
* If provided, will be used if the local header indicates a data descriptor block.
|
|
458
|
+
* It is used to find the correct offset for the data descriptor. */
|
|
459
|
+
compressedSizeOverride?: number) => LocalFileHeader | null;
|
package/package.json
CHANGED
|
@@ -1,57 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gyng/remote-zip",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Fetch file listings and individual files from a remote ZIP archive over HTTP Range requests, without downloading the whole archive.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"browser",
|
|
7
|
+
"central-directory",
|
|
8
|
+
"fetch",
|
|
9
|
+
"http",
|
|
10
|
+
"range",
|
|
11
|
+
"remote",
|
|
12
|
+
"stream",
|
|
13
|
+
"zip"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/gyng/remote-zip#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/gyng/remote-zip/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "(MIT OR Apache-2.0)",
|
|
20
|
+
"author": "Ng Guoyou <gyng@users.noreply.github.com>",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/gyng/remote-zip.git"
|
|
24
|
+
},
|
|
9
25
|
"files": [
|
|
10
|
-
"LICENSE",
|
|
26
|
+
"LICENSE-MIT",
|
|
27
|
+
"LICENSE-APACHE",
|
|
28
|
+
"README.md",
|
|
11
29
|
"package.json",
|
|
12
30
|
"lib/**"
|
|
13
31
|
],
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
"type": "commonjs",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"main": "lib/cjs/index.js",
|
|
35
|
+
"module": "lib/esm/index.mjs",
|
|
36
|
+
"types": "lib/types/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./lib/types/index.d.mts",
|
|
41
|
+
"default": "./lib/esm/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"require": {
|
|
44
|
+
"types": "./lib/types/index.d.ts",
|
|
45
|
+
"default": "./lib/cjs/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
17
49
|
},
|
|
18
50
|
"scripts": {
|
|
19
|
-
"build": "rm -rf lib/* &&
|
|
20
|
-
"d
|
|
21
|
-
"
|
|
22
|
-
"doc:
|
|
23
|
-
"lint:
|
|
24
|
-
"lint
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
51
|
+
"build": "rm -rf lib/* && npm run ts-types && node ./esbuild.mjs",
|
|
52
|
+
"d": "WATCH=1 npm run build",
|
|
53
|
+
"doc:gen": "npm --prefix tools/typedoc run generate",
|
|
54
|
+
"doc:site": "npm run build && npm run doc:gen && node ./scripts/build-site.mjs",
|
|
55
|
+
"lint:fix": "oxfmt . && oxlint --type-aware --import-plugin --vitest-plugin --fix .",
|
|
56
|
+
"lint": "oxfmt --check . && oxlint --type-aware --import-plugin --vitest-plugin .",
|
|
57
|
+
"t:watch": "vitest",
|
|
58
|
+
"t": "npm test",
|
|
59
|
+
"test:coverage": "vitest run --coverage",
|
|
60
|
+
"test:package": "npm run build && node ./scripts/test-package.mjs",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"ts-types": "tsc -p tsconfig.build.json",
|
|
63
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
64
|
+
"prepublishOnly": "npm run build"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"pako": "^3.0.1"
|
|
34
68
|
},
|
|
35
69
|
"devDependencies": {
|
|
36
|
-
"@types/
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"eslint": "^8.43.0",
|
|
45
|
-
"eslint-config-prettier": "^8.8.0",
|
|
46
|
-
"eslint-plugin-jest": "^27.2.2",
|
|
47
|
-
"http-server": "^14.1.1",
|
|
48
|
-
"jest": "^29.5.0",
|
|
49
|
-
"prettier": "^2.8.8",
|
|
50
|
-
"typedoc": "^0.24.8",
|
|
51
|
-
"typescript": "^5.1.3"
|
|
70
|
+
"@types/node": "^26.1.1",
|
|
71
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
72
|
+
"esbuild": "^0.28.1",
|
|
73
|
+
"oxfmt": "^0.58.0",
|
|
74
|
+
"oxlint": "^1.73.0",
|
|
75
|
+
"oxlint-tsgolint": "^0.24.0",
|
|
76
|
+
"typescript": "~7.0.2",
|
|
77
|
+
"vitest": "^4.1.10"
|
|
52
78
|
},
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=22"
|
|
81
|
+
},
|
|
82
|
+
"packageManager": "npm@11.11.0"
|
|
57
83
|
}
|
package/lib/types/zip.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|