@patrickdk77/archive-defuse 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +345 -2
- package/dist/aes7z.d.ts +16 -0
- package/dist/aes7z.js +131 -0
- package/dist/alone.d.ts +21 -0
- package/dist/alone.js +65 -0
- package/dist/bcj2.d.ts +21 -0
- package/dist/bcj2.js +140 -0
- package/dist/bzip2.d.ts +21 -0
- package/dist/bzip2.js +688 -0
- package/dist/cab.d.ts +63 -0
- package/dist/cab.js +493 -0
- package/dist/checks.d.ts +23 -0
- package/dist/checks.js +123 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +364 -0
- package/dist/codecs.d.ts +39 -0
- package/dist/codecs.js +124 -0
- package/dist/coders.d.ts +58 -0
- package/dist/coders.js +392 -0
- package/dist/crc32.d.ts +8 -0
- package/dist/crc32.js +59 -0
- package/dist/encoder.d.ts +44 -0
- package/dist/encoder.js +114 -0
- package/dist/engine.d.ts +44 -0
- package/dist/engine.js +753 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +98 -0
- package/dist/filters.d.ts +34 -0
- package/dist/filters.js +385 -0
- package/dist/findings.d.ts +26 -0
- package/dist/findings.js +176 -0
- package/dist/formats.d.ts +14 -0
- package/dist/formats.js +97 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +50 -0
- package/dist/index.mjs +3 -0
- package/dist/inflate64.d.ts +6 -0
- package/dist/inflate64.js +388 -0
- package/dist/io.d.ts +78 -0
- package/dist/io.js +379 -0
- package/dist/lzma-formats.d.ts +75 -0
- package/dist/lzma-formats.js +373 -0
- package/dist/lzma-sniff.d.ts +20 -0
- package/dist/lzma-sniff.js +73 -0
- package/dist/lzma-writers.d.ts +35 -0
- package/dist/lzma-writers.js +276 -0
- package/dist/lzma1/LICENSE +201 -0
- package/dist/lzma1/NOTICE +36 -0
- package/dist/lzma1/crc32.d.ts +4 -0
- package/dist/lzma1/crc32.js +19 -0
- package/dist/lzma1/hash234.d.ts +22 -0
- package/dist/lzma1/hash234.js +75 -0
- package/dist/lzma1/hc4.d.ts +30 -0
- package/dist/lzma1/hc4.js +142 -0
- package/dist/lzma1/header.d.ts +32 -0
- package/dist/lzma1/header.js +73 -0
- package/dist/lzma1/lz-decoder.d.ts +41 -0
- package/dist/lzma1/lz-decoder.js +128 -0
- package/dist/lzma1/lz-encoder.d.ts +87 -0
- package/dist/lzma1/lz-encoder.js +166 -0
- package/dist/lzma1/lzma-coder.d.ts +107 -0
- package/dist/lzma1/lzma-coder.js +162 -0
- package/dist/lzma1/lzma-decoder.d.ts +36 -0
- package/dist/lzma1/lzma-decoder.js +484 -0
- package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
- package/dist/lzma1/lzma-encoder-fast.js +114 -0
- package/dist/lzma1/lzma-encoder.d.ts +105 -0
- package/dist/lzma1/lzma-encoder.js +497 -0
- package/dist/lzma1/lzma2-decoder.d.ts +85 -0
- package/dist/lzma1/lzma2-decoder.js +249 -0
- package/dist/lzma1/range-coder.d.ts +18 -0
- package/dist/lzma1/range-coder.js +23 -0
- package/dist/lzma1/range-decoder.d.ts +41 -0
- package/dist/lzma1/range-decoder.js +78 -0
- package/dist/lzma1/range-encoder.d.ts +53 -0
- package/dist/lzma1/range-encoder.js +269 -0
- package/dist/lzma1-stream.d.ts +60 -0
- package/dist/lzma1-stream.js +202 -0
- package/dist/model.d.ts +141 -0
- package/dist/model.js +16 -0
- package/dist/names.d.ts +13 -0
- package/dist/names.js +66 -0
- package/dist/pack.d.ts +72 -0
- package/dist/pack.js +514 -0
- package/dist/plugins.d.ts +13 -0
- package/dist/plugins.js +55 -0
- package/dist/rules.d.ts +25 -0
- package/dist/rules.js +403 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +58 -0
- package/dist/seven.d.ts +75 -0
- package/dist/seven.js +492 -0
- package/dist/sevenzip.d.ts +133 -0
- package/dist/sevenzip.js +529 -0
- package/dist/single.d.ts +103 -0
- package/dist/single.js +453 -0
- package/dist/sniff.d.ts +20 -0
- package/dist/sniff.js +224 -0
- package/dist/stream-meter.d.ts +39 -0
- package/dist/stream-meter.js +54 -0
- package/dist/tar.d.ts +102 -0
- package/dist/tar.js +765 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.js +133 -0
- package/dist/unpack.d.ts +67 -0
- package/dist/unpack.js +194 -0
- package/dist/xz.d.ts +25 -0
- package/dist/xz.js +333 -0
- package/dist/zip-lzma.d.ts +15 -0
- package/dist/zip-lzma.js +137 -0
- package/dist/zip-writer.d.ts +41 -0
- package/dist/zip-writer.js +263 -0
- package/dist/zip.d.ts +99 -0
- package/dist/zip.js +901 -0
- package/dist/zipcrypt.d.ts +47 -0
- package/dist/zipcrypt.js +233 -0
- package/dist/zstd.d.ts +25 -0
- package/dist/zstd.js +160 -0
- package/package.json +94 -4
package/dist/names.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Bytes read as code page 437. */
|
|
2
|
+
export declare function cp437(bytes: Uint8Array): string;
|
|
3
|
+
/** Bytes read as UTF-8, or undefined when they are not valid UTF-8. */
|
|
4
|
+
export declare function utf8OrUndefined(bytes: Uint8Array): string | undefined;
|
|
5
|
+
export declare const isAscii: (bytes: Uint8Array) => boolean;
|
|
6
|
+
/** Control characters, and the characters that reorder text, which can make "exe.txt" show as "txt.exe". */
|
|
7
|
+
export declare const CONTROL: RegExp;
|
|
8
|
+
/** `s` without the characters in `chars` at its end. A loop, since a pattern such as /x+$/ backtracks on long runs. */
|
|
9
|
+
export declare function trimEnd(s: string, chars: string): string;
|
|
10
|
+
/** True when a path segment is one Windows cannot create as written. "." and ".." are other rules' business. */
|
|
11
|
+
export declare function windowsReserved(segment: string): boolean;
|
|
12
|
+
/** The segment as Windows can create it: reserved characters and a trailing dot or space become "_", and a device name gets "_" after it. */
|
|
13
|
+
export declare function windowsSafe(segment: string): string;
|
package/dist/names.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONTROL = exports.isAscii = void 0;
|
|
4
|
+
exports.cp437 = cp437;
|
|
5
|
+
exports.utf8OrUndefined = utf8OrUndefined;
|
|
6
|
+
exports.trimEnd = trimEnd;
|
|
7
|
+
exports.windowsReserved = windowsReserved;
|
|
8
|
+
exports.windowsSafe = windowsSafe;
|
|
9
|
+
/** Code page 437, the ZIP format's default for names without the UTF-8 flag: the characters for bytes 0x80 to 0xFF. */
|
|
10
|
+
const CP437_HIGH = [
|
|
11
|
+
0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
|
|
12
|
+
0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
|
|
13
|
+
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
|
|
14
|
+
0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
|
|
15
|
+
0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
|
|
16
|
+
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0,
|
|
17
|
+
];
|
|
18
|
+
/** Bytes read as code page 437. */
|
|
19
|
+
function cp437(bytes) {
|
|
20
|
+
let s = '';
|
|
21
|
+
for (const b of bytes)
|
|
22
|
+
s += String.fromCharCode(b < 0x80 ? b : CP437_HIGH[b - 0x80]);
|
|
23
|
+
return s;
|
|
24
|
+
}
|
|
25
|
+
const utf8Strict = new TextDecoder('utf-8', { fatal: true });
|
|
26
|
+
/** Bytes read as UTF-8, or undefined when they are not valid UTF-8. */
|
|
27
|
+
function utf8OrUndefined(bytes) {
|
|
28
|
+
try {
|
|
29
|
+
return utf8Strict.decode(bytes);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const isAscii = (bytes) => bytes.every(b => b < 0x80);
|
|
36
|
+
exports.isAscii = isAscii;
|
|
37
|
+
/** Control characters, and the characters that reorder text, which can make "exe.txt" show as "txt.exe". */
|
|
38
|
+
exports.CONTROL = /[\p{Cc}\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
|
|
39
|
+
/** Names Windows gives to devices, in any case and with any extension. */
|
|
40
|
+
const RESERVED_DEVICE = /^(con|prn|aux|nul|com[0-9\u00b9\u00b2\u00b3]|lpt[0-9\u00b9\u00b2\u00b3]|conin\$|conout\$)(\..*)?$/i;
|
|
41
|
+
/** Characters Windows does not allow in a name. The colon also opens an alternate data stream on NTFS. */
|
|
42
|
+
const RESERVED_CHARS = /[<>:"|?*]/g;
|
|
43
|
+
/** `s` without the characters in `chars` at its end. A loop, since a pattern such as /x+$/ backtracks on long runs. */
|
|
44
|
+
function trimEnd(s, chars) {
|
|
45
|
+
let end = s.length;
|
|
46
|
+
while (end > 0 && chars.includes(s[end - 1]))
|
|
47
|
+
end--;
|
|
48
|
+
return s.slice(0, end);
|
|
49
|
+
}
|
|
50
|
+
/** True when a path segment is one Windows cannot create as written. "." and ".." are other rules' business. */
|
|
51
|
+
function windowsReserved(segment) {
|
|
52
|
+
if (segment === '.' || segment === '..' || segment === '')
|
|
53
|
+
return false;
|
|
54
|
+
return RESERVED_DEVICE.test(segment) || /[<>:"|?*]/.test(segment) || /[. ]$/.test(segment);
|
|
55
|
+
}
|
|
56
|
+
/** The segment as Windows can create it: reserved characters and a trailing dot or space become "_", and a device name gets "_" after it. */
|
|
57
|
+
function windowsSafe(segment) {
|
|
58
|
+
if (!windowsReserved(segment))
|
|
59
|
+
return segment;
|
|
60
|
+
const replaced = segment.replace(RESERVED_CHARS, '_');
|
|
61
|
+
const kept = trimEnd(replaced, '. ');
|
|
62
|
+
let s = kept + '_'.repeat(replaced.length - kept.length);
|
|
63
|
+
if (RESERVED_DEVICE.test(s))
|
|
64
|
+
s = s.replace(/^([^.]*)/, '$1_');
|
|
65
|
+
return s;
|
|
66
|
+
}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type Readable } from 'node:stream';
|
|
2
|
+
import { type ByteSink } from './types';
|
|
3
|
+
/** What an entry holds: bytes, text written as UTF-8, or a stream such as a Node Readable. */
|
|
4
|
+
export type PackData = Uint8Array | string | AsyncIterable<Uint8Array | string>;
|
|
5
|
+
/** One file, folder or symbolic link to put in an archive. */
|
|
6
|
+
export interface PackEntry {
|
|
7
|
+
/** The path inside the archive, with "/" between folders. A name that ends in "/" is a folder. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Defaults to 'directory' for a name that ends in "/", 'symlink' when `linkTarget` is set, and 'file' otherwise. */
|
|
10
|
+
kind?: 'file' | 'directory' | 'symlink';
|
|
11
|
+
/**
|
|
12
|
+
* The content of a file, or a function that returns it, which is called when the entry is written. A function lets
|
|
13
|
+
* a caller open a stream only when its turn comes.
|
|
14
|
+
*/
|
|
15
|
+
data?: PackData | (() => PackData | Promise<PackData>);
|
|
16
|
+
/** The target of a symbolic link. */
|
|
17
|
+
linkTarget?: string;
|
|
18
|
+
/** Permission bits. Defaults to 0644 for a file and 0755 for a folder. */
|
|
19
|
+
mode?: number;
|
|
20
|
+
/** Defaults to `PackOptions.mtime`. */
|
|
21
|
+
mtime?: Date;
|
|
22
|
+
/** ZIP only: store the file without compression, as the `mimetype` entry of an ODF document needs. */
|
|
23
|
+
store?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** The archive formats `createArchive` writes. */
|
|
26
|
+
export type PackFormat = 'zip' | 'tar' | 'tar.gz' | 'tar.bz2' | 'tar.zst' | 'tar.xz' | '7z';
|
|
27
|
+
/** The formats that compress one file, which `compress` writes. */
|
|
28
|
+
export type CompressFormat = 'gzip' | 'bzip2' | 'zstd' | 'brotli' | 'xz' | 'lzma';
|
|
29
|
+
export interface PackOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Compression level: 0 to 9 for ZIP deflate and gzip (default 6), 1 to 9 for bzip2 (default 9). The other formats
|
|
32
|
+
* have one level.
|
|
33
|
+
*/
|
|
34
|
+
level?: number;
|
|
35
|
+
/** The time of entries that give none. Defaults to the time the call starts. */
|
|
36
|
+
mtime?: Date;
|
|
37
|
+
/** ZIP: the archive's comment. */
|
|
38
|
+
comment?: string;
|
|
39
|
+
/**
|
|
40
|
+
* ZIP and 7z: a file that would compress past this many bytes per compressed byte is stored instead, so the archive
|
|
41
|
+
* reads under the same `limits.compressionRatio` when this package inspects it. Default 1,000, the reading default.
|
|
42
|
+
*/
|
|
43
|
+
ratioLimit?: number;
|
|
44
|
+
/** Folder for temporary files. Defaults to `os.tmpdir()`. */
|
|
45
|
+
tempDir?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Bytes of one entry held in memory before a temporary file takes over, default 8 MiB. ZIP, tar and 7z need an
|
|
48
|
+
* entry's size before its data, so an entry given as a stream is read into memory or a temporary file first.
|
|
49
|
+
*/
|
|
50
|
+
memoryThreshold?: number;
|
|
51
|
+
/** Stops the work when aborted: the sink is aborted and the call throws the signal's reason. */
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
/** Thrown for an entry this package will not write, before anything of the entry is written. */
|
|
55
|
+
export declare class PackError extends Error {
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Writes an archive of `entries`, in their order, to `sink`, then closes it. Entries can come as a list or as an async
|
|
59
|
+
* iterable, and each file's content as bytes, text or a stream. When anything fails, such as an entry's stream, the
|
|
60
|
+
* sink is aborted instead of closed and the error is thrown.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createArchive(format: PackFormat, entries: Iterable<PackEntry> | AsyncIterable<PackEntry>, sink: ByteSink, options?: PackOptions): Promise<void>;
|
|
63
|
+
/** `createArchive` as a Node Readable, to pipe to a file, an HTTP response or an upload. */
|
|
64
|
+
export declare function createArchiveStream(format: PackFormat, entries: Iterable<PackEntry> | AsyncIterable<PackEntry>, options?: PackOptions): Readable;
|
|
65
|
+
/** `createArchive` into memory. */
|
|
66
|
+
export declare function createArchiveBytes(format: PackFormat, entries: Iterable<PackEntry> | AsyncIterable<PackEntry>, options?: PackOptions): Promise<Uint8Array>;
|
|
67
|
+
/** Compresses `data` as one stream of `format` to `sink`, then closes it. */
|
|
68
|
+
export declare function compressTo(format: CompressFormat, data: PackData, sink: ByteSink, options?: PackOptions): Promise<void>;
|
|
69
|
+
/** `compressTo` as a Node Readable. */
|
|
70
|
+
export declare function compressStream(format: CompressFormat, data: PackData, options?: PackOptions): Readable;
|
|
71
|
+
/** `compressTo` into memory. */
|
|
72
|
+
export declare function compress(format: CompressFormat, data: PackData, options?: PackOptions): Promise<Uint8Array>;
|
package/dist/pack.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PackError = void 0;
|
|
4
|
+
exports.createArchive = createArchive;
|
|
5
|
+
exports.createArchiveStream = createArchiveStream;
|
|
6
|
+
exports.createArchiveBytes = createArchiveBytes;
|
|
7
|
+
exports.compressTo = compressTo;
|
|
8
|
+
exports.compressStream = compressStream;
|
|
9
|
+
exports.compress = compress;
|
|
10
|
+
// Making archives and compressed files from scratch, for callers who use this package as an archive library: ZIP,
|
|
11
|
+
// tar on its own or compressed, 7z, and the formats that compress one file. The writers are the ones the rewrite uses.
|
|
12
|
+
const node_stream_1 = require("node:stream");
|
|
13
|
+
const bzip2_1 = require("./bzip2");
|
|
14
|
+
const codecs_1 = require("./codecs");
|
|
15
|
+
const crc32_1 = require("./crc32");
|
|
16
|
+
const io_1 = require("./io");
|
|
17
|
+
const lzma_writers_1 = require("./lzma-writers");
|
|
18
|
+
const rules_1 = require("./rules");
|
|
19
|
+
const tar_1 = require("./tar");
|
|
20
|
+
const types_1 = require("./types");
|
|
21
|
+
const zip_writer_1 = require("./zip-writer");
|
|
22
|
+
const zstd_1 = require("./zstd");
|
|
23
|
+
/** Thrown for an entry this package will not write, before anything of the entry is written. */
|
|
24
|
+
class PackError extends Error {
|
|
25
|
+
}
|
|
26
|
+
exports.PackError = PackError;
|
|
27
|
+
const enc = new TextEncoder();
|
|
28
|
+
/** The reading default of `limits.compressionRatio`. */
|
|
29
|
+
const DEFAULT_RATIO = 1000;
|
|
30
|
+
const toBytes = (c) => (typeof c === 'string' ? enc.encode(c) : c);
|
|
31
|
+
/** `chunks`, with `check` before each one, so an aborted signal stops a long read. */
|
|
32
|
+
async function* checked(chunks, check) {
|
|
33
|
+
for await (const c of chunks) {
|
|
34
|
+
check();
|
|
35
|
+
yield c;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** The content of an entry as chunks of bytes. */
|
|
39
|
+
async function* chunksOf(data) {
|
|
40
|
+
if (data === undefined)
|
|
41
|
+
return;
|
|
42
|
+
if (typeof data === 'string' || data instanceof Uint8Array) {
|
|
43
|
+
const b = toBytes(data);
|
|
44
|
+
if (b.length)
|
|
45
|
+
yield b;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for await (const c of data) {
|
|
49
|
+
const b = toBytes(c);
|
|
50
|
+
if (b.length)
|
|
51
|
+
yield b;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Why `createArchive` refuses a name, for each name rule of the inspection that changes a name. */
|
|
55
|
+
const REFUSED = {
|
|
56
|
+
[types_1.ArchiveDetail.Empty]: 'is empty',
|
|
57
|
+
[types_1.ArchiveDetail.DriveLetter]: 'starts with a drive letter',
|
|
58
|
+
[types_1.ArchiveDetail.Absolute]: 'is an absolute path',
|
|
59
|
+
[types_1.ArchiveDetail.Traversal]: 'has a ".." part',
|
|
60
|
+
[types_1.ArchiveDetail.EmptySegment]: 'has an empty or "." part',
|
|
61
|
+
[types_1.ArchiveDetail.Backslash]: 'holds a backslash, which Windows reads as a folder separator',
|
|
62
|
+
[types_1.ArchiveDetail.ControlCharacter]: 'holds a control character or a character that reorders text',
|
|
63
|
+
[types_1.ArchiveDetail.WindowsReserved]: 'has a part Windows cannot create as written',
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Characters that Windows turns into a slash, a backslash, a colon or a dot when a program that uses the ANSI file
|
|
67
|
+
* calls converts a name with best fit. Taken from Microsoft's best-fit tables at unicode.org for code pages 874, 932,
|
|
68
|
+
* 936, 949, 950 and 1250 to 1258. The yen sign is a backslash in code page 932, the won sign in 949, and the acute
|
|
69
|
+
* accent a slash in 1253.
|
|
70
|
+
*/
|
|
71
|
+
const BEST_FIT = {
|
|
72
|
+
'\u00a5': '\\',
|
|
73
|
+
'\u20a9': '\\',
|
|
74
|
+
'\u2216': '\\',
|
|
75
|
+
'\uff3c': '\\',
|
|
76
|
+
'\u00b4': '/',
|
|
77
|
+
'\u2044': '/',
|
|
78
|
+
'\u2215': '/',
|
|
79
|
+
'\uff0f': '/',
|
|
80
|
+
'\u0589': ':',
|
|
81
|
+
'\u2236': ':',
|
|
82
|
+
'\uff1a': ':',
|
|
83
|
+
'\uff0e': '.',
|
|
84
|
+
};
|
|
85
|
+
const BEST_FIT_CHARS = /[\u00a5\u20a9\u2216\uff3c\u00b4\u2044\u2215\uff0f\u0589\u2236\uff1a\uff0e]/g;
|
|
86
|
+
/** The rules whose break lets an extractor write outside its folder. */
|
|
87
|
+
const ESCAPES = [types_1.ArchiveDetail.DriveLetter, types_1.ArchiveDetail.Absolute, types_1.ArchiveDetail.Traversal];
|
|
88
|
+
/**
|
|
89
|
+
* Settles an entry's kind and checks its name. The name must pass the inspection's name rules, so the archive passes
|
|
90
|
+
* this package's own inspection: no ".." part, no absolute path or drive letter, no empty or "." part, no backslash,
|
|
91
|
+
* control character or part Windows cannot create. It must also not leave its folder once NFKC normalization or
|
|
92
|
+
* Windows best fit has changed it, as "\uff0e\uff0e/x" becomes "../x".
|
|
93
|
+
*/
|
|
94
|
+
function settle(entry, options, now) {
|
|
95
|
+
const raw = entry.name;
|
|
96
|
+
if (typeof raw !== 'string' || raw === '' || raw === '/')
|
|
97
|
+
throw new PackError('An entry needs a name');
|
|
98
|
+
if (raw.includes('\0'))
|
|
99
|
+
throw new PackError(`Entry name ${JSON.stringify(raw)} holds a NUL`);
|
|
100
|
+
const kind = entry.kind ?? (raw.endsWith('/') ? 'directory' : entry.linkTarget !== undefined ? 'symlink' : 'file');
|
|
101
|
+
if (kind === 'symlink' && entry.linkTarget === undefined)
|
|
102
|
+
throw new PackError(`Symbolic link ${JSON.stringify(raw)} needs a linkTarget`);
|
|
103
|
+
const name = kind === 'directory' ? (raw.endsWith('/') ? raw : `${raw}/`) : raw.replace(/\/+$/, '');
|
|
104
|
+
for (const detail of (0, rules_1.nameDetails)(name, true)) {
|
|
105
|
+
const why = REFUSED[detail];
|
|
106
|
+
if (why)
|
|
107
|
+
throw new PackError(`Entry name ${JSON.stringify(raw)} ${why}`);
|
|
108
|
+
}
|
|
109
|
+
const folds = [
|
|
110
|
+
[name.normalize('NFKC'), 'NFKC normalization'],
|
|
111
|
+
[name.replace(BEST_FIT_CHARS, c => BEST_FIT[c]), 'Windows best fit'],
|
|
112
|
+
];
|
|
113
|
+
for (const [folded, how] of folds) {
|
|
114
|
+
if (folded !== name && (0, rules_1.nameDetails)(folded, true).some(d => ESCAPES.includes(d)))
|
|
115
|
+
throw new PackError(`Entry name ${JSON.stringify(raw)} leaves its folder after ${how}`);
|
|
116
|
+
}
|
|
117
|
+
return { name, kind, entry, mode: (entry.mode ?? (kind === 'directory' ? 0o755 : kind === 'symlink' ? 0o777 : 0o644)) & 0o7777, mtime: entry.mtime ?? options.mtime ?? now };
|
|
118
|
+
}
|
|
119
|
+
/** The content of a settled entry: a file's data, a link's target, or nothing for a folder. */
|
|
120
|
+
async function contentOf(s) {
|
|
121
|
+
if (s.kind === 'directory')
|
|
122
|
+
return chunksOf(undefined);
|
|
123
|
+
if (s.kind === 'symlink')
|
|
124
|
+
return chunksOf(s.entry.linkTarget);
|
|
125
|
+
const d = s.entry.data;
|
|
126
|
+
return chunksOf(typeof d === 'function' ? await d() : d);
|
|
127
|
+
}
|
|
128
|
+
/** Reads an entry's content into memory or a temporary file, counting its CRC-32. */
|
|
129
|
+
async function spillOf(s, temp, threshold, check) {
|
|
130
|
+
const spill = new io_1.SpillSink(temp, threshold);
|
|
131
|
+
let crc = 0;
|
|
132
|
+
try {
|
|
133
|
+
for await (const c of checked(await contentOf(s), check)) {
|
|
134
|
+
crc = (0, crc32_1.crc32)(c, crc);
|
|
135
|
+
await spill.write(c);
|
|
136
|
+
}
|
|
137
|
+
await spill.close();
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
await spill.dispose();
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
return { spill, crc };
|
|
144
|
+
}
|
|
145
|
+
const isAscii = (s) => /^[\x20-\x7e]*$/.test(s);
|
|
146
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
147
|
+
// ZIP
|
|
148
|
+
/** A date as the DOS time and date of a ZIP header, local time, clamped to the years DOS can hold. */
|
|
149
|
+
function dosTime(d) {
|
|
150
|
+
const year = Math.min(2107, Math.max(1980, d.getFullYear()));
|
|
151
|
+
return {
|
|
152
|
+
time: (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1),
|
|
153
|
+
date: ((year - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const ZIP_TYPE_BITS = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
|
|
157
|
+
async function writeZipEntries(entries, sink, options, temp, check) {
|
|
158
|
+
const out = new zip_writer_1.ZipOutput(sink);
|
|
159
|
+
const threshold = options.memoryThreshold ?? 8 * 1024 * 1024;
|
|
160
|
+
const level = options.level ?? 6;
|
|
161
|
+
for await (const s of entries) {
|
|
162
|
+
const { time, date } = dosTime(s.mtime);
|
|
163
|
+
const info = {
|
|
164
|
+
name: enc.encode(s.name),
|
|
165
|
+
flags: isAscii(s.name) ? 0 : 0x0800,
|
|
166
|
+
method: 0,
|
|
167
|
+
versionNeeded: s.kind === 'directory' ? 20 : 10,
|
|
168
|
+
versionMadeBy: (3 << 8) | 30,
|
|
169
|
+
externalAttr: (((ZIP_TYPE_BITS[s.kind] | s.mode) << 16) >>> 0) | (s.kind === 'directory' ? 0x10 : 0),
|
|
170
|
+
internalAttr: 0,
|
|
171
|
+
time,
|
|
172
|
+
date,
|
|
173
|
+
crc: 0,
|
|
174
|
+
compressedSize: 0,
|
|
175
|
+
size: 0,
|
|
176
|
+
extras: new Uint8Array(0),
|
|
177
|
+
comment: new Uint8Array(0),
|
|
178
|
+
};
|
|
179
|
+
if (s.kind === 'directory') {
|
|
180
|
+
await out.entry(info, () => chunksOf(undefined), s.name);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// The content is read once, into memory or a temporary file, and deflated from there, so it can be stored instead
|
|
184
|
+
// when deflate gains nothing or passes the ratio limit.
|
|
185
|
+
const { spill: raw, crc } = await spillOf(s, temp, threshold, check);
|
|
186
|
+
const deflated = new io_1.SpillSink(temp, threshold);
|
|
187
|
+
try {
|
|
188
|
+
let stored = s.kind === 'symlink' || s.entry.store === true || level === 0 || raw.length === 0;
|
|
189
|
+
if (!stored) {
|
|
190
|
+
for await (const c of (0, codecs_1.deflateRaw)(checked(raw.read(), check), level))
|
|
191
|
+
await deflated.write(c);
|
|
192
|
+
await deflated.close();
|
|
193
|
+
stored = deflated.length >= raw.length || (0, lzma_writers_1.passesRatio)(raw.length, deflated.length, options.ratioLimit ?? DEFAULT_RATIO);
|
|
194
|
+
}
|
|
195
|
+
info.method = stored ? 0 : 8;
|
|
196
|
+
info.versionNeeded = stored ? 10 : 20;
|
|
197
|
+
info.crc = crc;
|
|
198
|
+
info.size = raw.length;
|
|
199
|
+
info.compressedSize = stored ? raw.length : deflated.length;
|
|
200
|
+
await out.entry(info, () => checked(stored ? raw.read() : deflated.read(), check), s.name);
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await deflated.dispose();
|
|
204
|
+
await raw.dispose();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
await out.end(enc.encode(options.comment ?? ''));
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
210
|
+
// tar
|
|
211
|
+
const TAR_TYPES = { file: '0', directory: '5', symlink: '2' };
|
|
212
|
+
async function* tarChunks(entries, options, temp, check) {
|
|
213
|
+
const threshold = options.memoryThreshold ?? 8 * 1024 * 1024;
|
|
214
|
+
let written = 0;
|
|
215
|
+
const pad = (n) => Buffer.alloc((tar_1.BLOCK - (n % tar_1.BLOCK)) % tar_1.BLOCK);
|
|
216
|
+
for await (const s of entries) {
|
|
217
|
+
const { spill } = s.kind === 'file' ? await spillOf(s, temp, threshold, check) : { spill: undefined };
|
|
218
|
+
try {
|
|
219
|
+
const size = spill?.length ?? 0;
|
|
220
|
+
const link = s.kind === 'symlink' ? (s.entry.linkTarget ?? '') : '';
|
|
221
|
+
const mtime = Math.floor(s.mtime.getTime() / 1000);
|
|
222
|
+
// What the ustar header cannot hold goes in a pax header before it.
|
|
223
|
+
const records = [];
|
|
224
|
+
const parts = (0, tar_1.split)(s.name);
|
|
225
|
+
if (!parts)
|
|
226
|
+
records.push((0, tar_1.paxRecord)('path', Buffer.from(s.name, 'utf8')));
|
|
227
|
+
const linkFits = isAscii(link) && link.length <= 100;
|
|
228
|
+
if (!linkFits)
|
|
229
|
+
records.push((0, tar_1.paxRecord)('linkpath', Buffer.from(link, 'utf8')));
|
|
230
|
+
if (size > 0o77777777777)
|
|
231
|
+
records.push((0, tar_1.paxRecord)('size', Buffer.from(String(size))));
|
|
232
|
+
const timeFits = mtime >= 0 && mtime <= 0o77777777777;
|
|
233
|
+
if (!timeFits)
|
|
234
|
+
records.push((0, tar_1.paxRecord)('mtime', Buffer.from(String(mtime))));
|
|
235
|
+
if (records.length) {
|
|
236
|
+
const body = Buffer.concat(records);
|
|
237
|
+
const h = (0, tar_1.header)({
|
|
238
|
+
name: Buffer.from('././@PaxHeader'),
|
|
239
|
+
mode: 0o644,
|
|
240
|
+
uid: 0,
|
|
241
|
+
gid: 0,
|
|
242
|
+
size: body.length,
|
|
243
|
+
mtime: 0,
|
|
244
|
+
typeflag: 'x',
|
|
245
|
+
linkname: Buffer.alloc(0),
|
|
246
|
+
uname: '',
|
|
247
|
+
gname: '',
|
|
248
|
+
prefix: Buffer.alloc(0),
|
|
249
|
+
devmajor: 0,
|
|
250
|
+
devminor: 0,
|
|
251
|
+
});
|
|
252
|
+
yield h;
|
|
253
|
+
yield body;
|
|
254
|
+
yield pad(body.length);
|
|
255
|
+
written += h.length + body.length + pad(body.length).length;
|
|
256
|
+
}
|
|
257
|
+
const h = (0, tar_1.header)({
|
|
258
|
+
name: parts?.name ?? (0, tar_1.asciiFallback)(s.name),
|
|
259
|
+
prefix: parts?.prefix ?? Buffer.alloc(0),
|
|
260
|
+
mode: s.mode,
|
|
261
|
+
uid: 0,
|
|
262
|
+
gid: 0,
|
|
263
|
+
size: size > 0o77777777777 ? 0 : size,
|
|
264
|
+
mtime: timeFits ? mtime : 0,
|
|
265
|
+
typeflag: TAR_TYPES[s.kind],
|
|
266
|
+
linkname: linkFits ? Buffer.from(link, 'latin1') : (0, tar_1.asciiFallback)(link),
|
|
267
|
+
uname: '',
|
|
268
|
+
gname: '',
|
|
269
|
+
devmajor: 0,
|
|
270
|
+
devminor: 0,
|
|
271
|
+
});
|
|
272
|
+
yield h;
|
|
273
|
+
written += h.length;
|
|
274
|
+
if (spill) {
|
|
275
|
+
yield* checked(spill.read(), check);
|
|
276
|
+
const p = pad(size);
|
|
277
|
+
if (p.length)
|
|
278
|
+
yield p;
|
|
279
|
+
written += size + p.length;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
await spill?.dispose();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const end = Buffer.alloc(2 * tar_1.BLOCK);
|
|
287
|
+
yield end;
|
|
288
|
+
written += end.length;
|
|
289
|
+
const tail = (tar_1.RECORD - (written % tar_1.RECORD)) % tar_1.RECORD;
|
|
290
|
+
if (tail)
|
|
291
|
+
yield Buffer.alloc(tail);
|
|
292
|
+
}
|
|
293
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
294
|
+
// 7z
|
|
295
|
+
/** A date as a Windows FILETIME: 100-nanosecond intervals since 1601, little-endian. */
|
|
296
|
+
function fileTime(d) {
|
|
297
|
+
const t = BigInt(d.getTime() + 11_644_473_600_000) * 10000n;
|
|
298
|
+
const b = Buffer.alloc(8);
|
|
299
|
+
b.writeBigUInt64LE(t < 0n ? 0n : t);
|
|
300
|
+
return b;
|
|
301
|
+
}
|
|
302
|
+
async function writeSevenEntries(entries, sink, options, temp, check) {
|
|
303
|
+
const threshold = options.memoryThreshold ?? 8 * 1024 * 1024;
|
|
304
|
+
const spills = [];
|
|
305
|
+
const out = [];
|
|
306
|
+
try {
|
|
307
|
+
// 7z writes its header after the data, from every entry, so each entry is read before any is written.
|
|
308
|
+
let held = 0;
|
|
309
|
+
for await (const s of entries) {
|
|
310
|
+
const dir = s.kind === 'directory';
|
|
311
|
+
let content;
|
|
312
|
+
if (!dir) {
|
|
313
|
+
const { spill } = await spillOf(s, temp, Math.max(0, threshold - held), check);
|
|
314
|
+
spills.push(spill);
|
|
315
|
+
if (!spill.spilled)
|
|
316
|
+
held += spill.length;
|
|
317
|
+
content = { size: spill.length, chunks: () => spill.read() };
|
|
318
|
+
}
|
|
319
|
+
const typeBits = dir ? 0o040000 : s.kind === 'symlink' ? 0o120000 : 0o100000;
|
|
320
|
+
// The Unix mode sits in the high 16 bits, with 0x8000 to say it is there, as p7zip writes it.
|
|
321
|
+
const attrib = (((typeBits | s.mode) << 16) | 0x8000 | (dir ? 0x10 : 0x20)) >>> 0;
|
|
322
|
+
out.push({ name: dir ? s.name.slice(0, -1) : s.name, dir, attrib, mtime: fileTime(s.mtime), content });
|
|
323
|
+
}
|
|
324
|
+
await (0, lzma_writers_1.writeSevenZip)(out, sink, temp, threshold, check, options.ratioLimit ?? DEFAULT_RATIO);
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
for (const s of spills)
|
|
328
|
+
await s.dispose();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
332
|
+
// One compressed stream
|
|
333
|
+
/** A gzip member around raw deflate, with the CRC-32 and size after it. */
|
|
334
|
+
async function* gzipOf(chunks, level, mtime) {
|
|
335
|
+
const head = Buffer.from([0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 255]);
|
|
336
|
+
head.writeUInt32LE(Math.max(0, Math.min(0xffffffff, Math.floor(mtime.getTime() / 1000))), 4);
|
|
337
|
+
yield head;
|
|
338
|
+
let crc = 0;
|
|
339
|
+
let size = 0;
|
|
340
|
+
async function* tally() {
|
|
341
|
+
for await (const c of chunks) {
|
|
342
|
+
crc = (0, crc32_1.crc32)(c, crc);
|
|
343
|
+
size += c.length;
|
|
344
|
+
yield c;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
yield* (0, codecs_1.deflateRaw)(tally(), level);
|
|
348
|
+
const tail = Buffer.alloc(8);
|
|
349
|
+
tail.writeUInt32LE(crc >>> 0, 0);
|
|
350
|
+
tail.writeUInt32LE(size % 2 ** 32, 4);
|
|
351
|
+
yield tail;
|
|
352
|
+
}
|
|
353
|
+
/** xz and .lzma need the content by offset, or its size first, so it is read into memory or a temporary file. */
|
|
354
|
+
async function* lzmaOf(format, chunks, options, temp, check) {
|
|
355
|
+
const spill = new io_1.SpillSink(temp, options.memoryThreshold ?? 8 * 1024 * 1024);
|
|
356
|
+
try {
|
|
357
|
+
for await (const c of checked(chunks, check))
|
|
358
|
+
await spill.write(c);
|
|
359
|
+
await spill.close();
|
|
360
|
+
const source = spill.length ? spill.source() : undefined;
|
|
361
|
+
// The writers write to a sink, so their output goes to memory or a temporary file and then out.
|
|
362
|
+
const encoded = new io_1.SpillSink(temp, options.memoryThreshold ?? 8 * 1024 * 1024);
|
|
363
|
+
try {
|
|
364
|
+
try {
|
|
365
|
+
if (format === 'xz')
|
|
366
|
+
await (0, lzma_writers_1.writeXz)(source, 4, encoded, check);
|
|
367
|
+
else
|
|
368
|
+
await (0, lzma_writers_1.writeAlone)(source, encoded, check);
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
await source?.close?.();
|
|
372
|
+
}
|
|
373
|
+
await encoded.close();
|
|
374
|
+
yield* checked(encoded.read(), check);
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
await encoded.dispose();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
await spill.dispose();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function compressChunks(format, input, options, temp, now, check) {
|
|
385
|
+
const chunks = checked(input, check);
|
|
386
|
+
switch (format) {
|
|
387
|
+
case 'gzip':
|
|
388
|
+
return gzipOf(chunks, options.level ?? 6, options.mtime ?? now);
|
|
389
|
+
case 'bzip2':
|
|
390
|
+
return (0, bzip2_1.bzip2)(chunks, options.level ?? 9);
|
|
391
|
+
case 'zstd':
|
|
392
|
+
return (0, zstd_1.zstdEncode)(chunks);
|
|
393
|
+
case 'brotli':
|
|
394
|
+
return (0, codecs_1.brotliEncode)(chunks);
|
|
395
|
+
case 'xz':
|
|
396
|
+
case 'lzma':
|
|
397
|
+
return lzmaOf(format, chunks, options, temp, check);
|
|
398
|
+
default:
|
|
399
|
+
throw new PackError(`Unknown format ${String(format)}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
403
|
+
// The calls
|
|
404
|
+
const ARCHIVE_FORMATS = new Set(['zip', 'tar', 'tar.gz', 'tar.bz2', 'tar.zst', 'tar.xz', '7z']);
|
|
405
|
+
const TAR_COMPRESSION = { 'tar.gz': 'gzip', 'tar.bz2': 'bzip2', 'tar.zst': 'zstd', 'tar.xz': 'xz' };
|
|
406
|
+
/** Copies chunks to the sink, then closes it. On failure it aborts the sink, so a partial output never looks whole. */
|
|
407
|
+
async function deliver(produce, sink) {
|
|
408
|
+
try {
|
|
409
|
+
await produce();
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
try {
|
|
413
|
+
await sink.abort?.(e);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// The write's error is the one to report.
|
|
417
|
+
}
|
|
418
|
+
throw e;
|
|
419
|
+
}
|
|
420
|
+
await sink.close();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Writes an archive of `entries`, in their order, to `sink`, then closes it. Entries can come as a list or as an async
|
|
424
|
+
* iterable, and each file's content as bytes, text or a stream. When anything fails, such as an entry's stream, the
|
|
425
|
+
* sink is aborted instead of closed and the error is thrown.
|
|
426
|
+
*/
|
|
427
|
+
async function createArchive(format, entries, sink, options = {}) {
|
|
428
|
+
if (!ARCHIVE_FORMATS.has(format))
|
|
429
|
+
throw new PackError(`Unknown archive format ${String(format)}`);
|
|
430
|
+
const now = new Date();
|
|
431
|
+
const temp = new io_1.TempDir(options.tempDir);
|
|
432
|
+
const check = () => options.signal?.throwIfAborted();
|
|
433
|
+
async function* settled() {
|
|
434
|
+
for await (const e of entries) {
|
|
435
|
+
check();
|
|
436
|
+
yield settle(e, options, now);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
await deliver(async () => {
|
|
441
|
+
check();
|
|
442
|
+
if (format === 'zip')
|
|
443
|
+
return writeZipEntries(settled(), sink, options, temp, check);
|
|
444
|
+
if (format === '7z')
|
|
445
|
+
return writeSevenEntries(settled(), sink, options, temp, check);
|
|
446
|
+
const tar = tarChunks(settled(), options, temp, check);
|
|
447
|
+
const method = TAR_COMPRESSION[format];
|
|
448
|
+
for await (const c of method ? compressChunks(method, tar, options, temp, now, check) : tar)
|
|
449
|
+
await sink.write(c);
|
|
450
|
+
}, sink);
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
await temp.cleanup();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Runs `write` into a Node Readable. An error destroys the stream, so a consumer never takes a partial output as whole.
|
|
458
|
+
* A consumer that destroys the stream before its end, or the caller's signal, stops the work.
|
|
459
|
+
*/
|
|
460
|
+
function readableOf(write, signal) {
|
|
461
|
+
const out = new node_stream_1.PassThrough();
|
|
462
|
+
const controller = new AbortController();
|
|
463
|
+
const stop = () => controller.abort(signal?.reason);
|
|
464
|
+
if (signal?.aborted)
|
|
465
|
+
stop();
|
|
466
|
+
else
|
|
467
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
468
|
+
out.on('close', () => {
|
|
469
|
+
if (!controller.signal.aborted)
|
|
470
|
+
controller.abort(out.errored ?? new Error('The output stream closed before all data was written'));
|
|
471
|
+
});
|
|
472
|
+
write((0, io_1.writableSink)(out), controller.signal)
|
|
473
|
+
.catch(e => {
|
|
474
|
+
if (!out.destroyed)
|
|
475
|
+
out.destroy(e instanceof Error ? e : new Error(String(e)));
|
|
476
|
+
})
|
|
477
|
+
.finally(() => signal?.removeEventListener('abort', stop));
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
/** `createArchive` as a Node Readable, to pipe to a file, an HTTP response or an upload. */
|
|
481
|
+
function createArchiveStream(format, entries, options = {}) {
|
|
482
|
+
return readableOf((sink, signal) => createArchive(format, entries, sink, { ...options, signal }), options.signal);
|
|
483
|
+
}
|
|
484
|
+
/** `createArchive` into memory. */
|
|
485
|
+
async function createArchiveBytes(format, entries, options = {}) {
|
|
486
|
+
const sink = (0, io_1.bufferSink)();
|
|
487
|
+
await createArchive(format, entries, sink, options);
|
|
488
|
+
return sink.result();
|
|
489
|
+
}
|
|
490
|
+
/** Compresses `data` as one stream of `format` to `sink`, then closes it. */
|
|
491
|
+
async function compressTo(format, data, sink, options = {}) {
|
|
492
|
+
const temp = new io_1.TempDir(options.tempDir);
|
|
493
|
+
const check = () => options.signal?.throwIfAborted();
|
|
494
|
+
try {
|
|
495
|
+
await deliver(async () => {
|
|
496
|
+
check();
|
|
497
|
+
for await (const c of compressChunks(format, chunksOf(data), options, temp, new Date(), check))
|
|
498
|
+
await sink.write(c);
|
|
499
|
+
}, sink);
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
await temp.cleanup();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/** `compressTo` as a Node Readable. */
|
|
506
|
+
function compressStream(format, data, options = {}) {
|
|
507
|
+
return readableOf((sink, signal) => compressTo(format, data, sink, { ...options, signal }), options.signal);
|
|
508
|
+
}
|
|
509
|
+
/** `compressTo` into memory. */
|
|
510
|
+
async function compress(format, data, options = {}) {
|
|
511
|
+
const sink = (0, io_1.bufferSink)();
|
|
512
|
+
await compressTo(format, data, sink, options);
|
|
513
|
+
return sink.result();
|
|
514
|
+
}
|