@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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +345 -2
  3. package/dist/aes7z.d.ts +16 -0
  4. package/dist/aes7z.js +131 -0
  5. package/dist/alone.d.ts +21 -0
  6. package/dist/alone.js +65 -0
  7. package/dist/bcj2.d.ts +21 -0
  8. package/dist/bcj2.js +140 -0
  9. package/dist/bzip2.d.ts +21 -0
  10. package/dist/bzip2.js +688 -0
  11. package/dist/cab.d.ts +63 -0
  12. package/dist/cab.js +493 -0
  13. package/dist/checks.d.ts +23 -0
  14. package/dist/checks.js +123 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +364 -0
  17. package/dist/codecs.d.ts +39 -0
  18. package/dist/codecs.js +124 -0
  19. package/dist/coders.d.ts +58 -0
  20. package/dist/coders.js +392 -0
  21. package/dist/crc32.d.ts +8 -0
  22. package/dist/crc32.js +59 -0
  23. package/dist/encoder.d.ts +44 -0
  24. package/dist/encoder.js +114 -0
  25. package/dist/engine.d.ts +44 -0
  26. package/dist/engine.js +753 -0
  27. package/dist/errors.d.ts +64 -0
  28. package/dist/errors.js +98 -0
  29. package/dist/filters.d.ts +34 -0
  30. package/dist/filters.js +385 -0
  31. package/dist/findings.d.ts +26 -0
  32. package/dist/findings.js +176 -0
  33. package/dist/formats.d.ts +14 -0
  34. package/dist/formats.js +97 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.js +50 -0
  37. package/dist/index.mjs +3 -0
  38. package/dist/inflate64.d.ts +6 -0
  39. package/dist/inflate64.js +388 -0
  40. package/dist/io.d.ts +78 -0
  41. package/dist/io.js +379 -0
  42. package/dist/lzma-formats.d.ts +75 -0
  43. package/dist/lzma-formats.js +373 -0
  44. package/dist/lzma-sniff.d.ts +20 -0
  45. package/dist/lzma-sniff.js +73 -0
  46. package/dist/lzma-writers.d.ts +35 -0
  47. package/dist/lzma-writers.js +276 -0
  48. package/dist/lzma1/LICENSE +201 -0
  49. package/dist/lzma1/NOTICE +36 -0
  50. package/dist/lzma1/crc32.d.ts +4 -0
  51. package/dist/lzma1/crc32.js +19 -0
  52. package/dist/lzma1/hash234.d.ts +22 -0
  53. package/dist/lzma1/hash234.js +75 -0
  54. package/dist/lzma1/hc4.d.ts +30 -0
  55. package/dist/lzma1/hc4.js +142 -0
  56. package/dist/lzma1/header.d.ts +32 -0
  57. package/dist/lzma1/header.js +73 -0
  58. package/dist/lzma1/lz-decoder.d.ts +41 -0
  59. package/dist/lzma1/lz-decoder.js +128 -0
  60. package/dist/lzma1/lz-encoder.d.ts +87 -0
  61. package/dist/lzma1/lz-encoder.js +166 -0
  62. package/dist/lzma1/lzma-coder.d.ts +107 -0
  63. package/dist/lzma1/lzma-coder.js +162 -0
  64. package/dist/lzma1/lzma-decoder.d.ts +36 -0
  65. package/dist/lzma1/lzma-decoder.js +484 -0
  66. package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
  67. package/dist/lzma1/lzma-encoder-fast.js +114 -0
  68. package/dist/lzma1/lzma-encoder.d.ts +105 -0
  69. package/dist/lzma1/lzma-encoder.js +497 -0
  70. package/dist/lzma1/lzma2-decoder.d.ts +85 -0
  71. package/dist/lzma1/lzma2-decoder.js +249 -0
  72. package/dist/lzma1/range-coder.d.ts +18 -0
  73. package/dist/lzma1/range-coder.js +23 -0
  74. package/dist/lzma1/range-decoder.d.ts +41 -0
  75. package/dist/lzma1/range-decoder.js +78 -0
  76. package/dist/lzma1/range-encoder.d.ts +53 -0
  77. package/dist/lzma1/range-encoder.js +269 -0
  78. package/dist/lzma1-stream.d.ts +60 -0
  79. package/dist/lzma1-stream.js +202 -0
  80. package/dist/model.d.ts +141 -0
  81. package/dist/model.js +16 -0
  82. package/dist/names.d.ts +13 -0
  83. package/dist/names.js +66 -0
  84. package/dist/pack.d.ts +72 -0
  85. package/dist/pack.js +514 -0
  86. package/dist/plugins.d.ts +13 -0
  87. package/dist/plugins.js +55 -0
  88. package/dist/rules.d.ts +25 -0
  89. package/dist/rules.js +403 -0
  90. package/dist/score.d.ts +17 -0
  91. package/dist/score.js +58 -0
  92. package/dist/seven.d.ts +75 -0
  93. package/dist/seven.js +492 -0
  94. package/dist/sevenzip.d.ts +133 -0
  95. package/dist/sevenzip.js +529 -0
  96. package/dist/single.d.ts +103 -0
  97. package/dist/single.js +453 -0
  98. package/dist/sniff.d.ts +20 -0
  99. package/dist/sniff.js +224 -0
  100. package/dist/stream-meter.d.ts +39 -0
  101. package/dist/stream-meter.js +54 -0
  102. package/dist/tar.d.ts +102 -0
  103. package/dist/tar.js +765 -0
  104. package/dist/types.d.ts +284 -0
  105. package/dist/types.js +133 -0
  106. package/dist/unpack.d.ts +67 -0
  107. package/dist/unpack.js +194 -0
  108. package/dist/xz.d.ts +25 -0
  109. package/dist/xz.js +333 -0
  110. package/dist/zip-lzma.d.ts +15 -0
  111. package/dist/zip-lzma.js +137 -0
  112. package/dist/zip-writer.d.ts +41 -0
  113. package/dist/zip-writer.js +263 -0
  114. package/dist/zip.d.ts +99 -0
  115. package/dist/zip.js +901 -0
  116. package/dist/zipcrypt.d.ts +47 -0
  117. package/dist/zipcrypt.js +233 -0
  118. package/dist/zstd.d.ts +25 -0
  119. package/dist/zstd.js +160 -0
  120. package/package.json +94 -4
package/dist/single.js ADDED
@@ -0,0 +1,453 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isBzip2 = exports.isGzip = exports.BrotliArchive = exports.ZstdArchive = exports.Bzip2Archive = exports.GzipArchive = void 0;
4
+ exports.looksLikeBrotli = looksLikeBrotli;
5
+ // The formats that compress one file: gzip, bzip2, zstd and Brotli. Each is an archive with one entry, whose content
6
+ // the engine hands to the file plugins like any other entry's. A gzip file can name its content, the others cannot.
7
+ const bzip2_1 = require("./bzip2");
8
+ const codecs_1 = require("./codecs");
9
+ const crc32_1 = require("./crc32");
10
+ const errors_1 = require("./errors");
11
+ const io_1 = require("./io");
12
+ const model_1 = require("./model");
13
+ const sniff_1 = require("./sniff");
14
+ const types_1 = require("./types");
15
+ const zstd_1 = require("./zstd");
16
+ class Single {
17
+ reader;
18
+ notes;
19
+ single = true;
20
+ lateNotes = [];
21
+ entries;
22
+ comment;
23
+ /** Set once the entry has been read and the stream turned out to be one stream with nothing after it. */
24
+ raw;
25
+ constructor(reader, notes, name = '') {
26
+ this.reader = reader;
27
+ this.notes = notes;
28
+ this.entries = [{ index: 0, name, kind: 'file', size: 0, compressedSize: reader.size, notes: [] }];
29
+ }
30
+ async unlock() {
31
+ return false;
32
+ }
33
+ hasMetadata() {
34
+ return false;
35
+ }
36
+ /** True when encode() can store data uncompressed. */
37
+ canStore = false;
38
+ /** Notes what follows the last stream, and what it reads as. */
39
+ async trailing(at) {
40
+ const size = this.reader.size;
41
+ if (at >= size)
42
+ return;
43
+ this.lateNotes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.TrailingBytes, data: { bytes: size - at } });
44
+ const type = (0, sniff_1.sniffType)(await this.reader.read(at, Math.min(1024, size - at)));
45
+ if (type !== undefined)
46
+ this.lateNotes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Polyglot, data: { type, where: 'after the compressed data' } });
47
+ }
48
+ /**
49
+ * Writes the stream: the compressed bytes as they were when the content stayed as it was and was one stream, or the
50
+ * content compressed again. Compressed data that would pass the run's ratio limit is stored instead where the
51
+ * format can store, so the output reads under the same limit.
52
+ */
53
+ async writeStream(plan, sink, context) {
54
+ const k = plan.kept[0];
55
+ if (k && !k.replacement && !k.recompress && this.raw) {
56
+ for await (const c of this.reader.chunks(this.raw.start, this.raw.length)) {
57
+ context.meter().time();
58
+ await sink.write(c);
59
+ }
60
+ return;
61
+ }
62
+ const size = k ? this.sizeOf(k) : 0;
63
+ const content = () => (k ? (0, model_1.contentOf)(this, k, context) : (0, model_1.nothing)());
64
+ const spill = new io_1.SpillSink(context.temp, context.threshold);
65
+ try {
66
+ for await (const c of this.encode(content(), plan, false))
67
+ await spill.write(c);
68
+ await spill.close();
69
+ if (this.canStore && size > 1024 * 1024 && size > spill.length * plan.ratioLimit) {
70
+ await spill.dispose();
71
+ for await (const c of this.encode(content(), plan, true))
72
+ await sink.write(c);
73
+ return;
74
+ }
75
+ const source = spill.source();
76
+ try {
77
+ const length = await this.encodedLength(source, spill.length);
78
+ for (let at = 0; at < length; at += 262144) {
79
+ context.meter().time();
80
+ await sink.write(await source.read(at, Math.min(262144, length - at)));
81
+ }
82
+ }
83
+ finally {
84
+ await source.close?.();
85
+ }
86
+ }
87
+ finally {
88
+ await spill.dispose();
89
+ }
90
+ }
91
+ /** How much of what encode() wrote to keep. */
92
+ async encodedLength(_source, length) {
93
+ return length;
94
+ }
95
+ sizeOf(k) {
96
+ return k.replacement ? k.replacement.length : (k.entry.decoded ?? (0, model_1.sizeOf)(k));
97
+ }
98
+ async write(plan, sink, context) {
99
+ await this.writeStream(plan, sink, context);
100
+ if (plan.keepTrailing && this.trailingStart !== undefined)
101
+ for await (const c of this.reader.chunks(this.trailingStart, this.reader.size - this.trailingStart))
102
+ await sink.write(c);
103
+ }
104
+ /** Where the data after the last stream starts, once the entry has been read. */
105
+ trailingStart;
106
+ }
107
+ /** Reads a gzip member header at `at`. */
108
+ async function gzipHeader(reader, at, notes) {
109
+ const h = await reader.read(at, 10);
110
+ if (h.length < 10)
111
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a gzip header', { what: 'gzip header' });
112
+ if (h[0] !== 0x1f || h[1] !== 0x8b)
113
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'Not a gzip member', { reason: 'no gzip signature' });
114
+ if (h[2] !== 8)
115
+ throw new errors_1.StopError(types_1.ArchiveCategory.Compression, types_1.ArchiveDetail.UnsupportedMethod, 'A gzip method other than deflate', { method: `gzip method ${h[2]}` });
116
+ const flags = h[3];
117
+ const view = new DataView(h.buffer, h.byteOffset, h.byteLength);
118
+ const header = { flags, mtime: view.getUint32(4, true), os: h[9], dataStart: 0 };
119
+ if (flags & 0xe0)
120
+ notes?.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MalformedHeader, data: { reason: 'the gzip header sets reserved flags, which gzip refuses' } });
121
+ let p = at + 10;
122
+ const field = async () => {
123
+ // A NUL ends the name and the comment. Neither may run past 64 KiB.
124
+ const parts = [];
125
+ for (let len = 0;;) {
126
+ const b = await reader.read(p, 4096);
127
+ if (b.length === 0)
128
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a gzip header', { what: 'gzip header' });
129
+ const z = b.indexOf(0);
130
+ const piece = z < 0 ? b : b.subarray(0, z);
131
+ parts.push(piece);
132
+ len += piece.length;
133
+ p += piece.length;
134
+ if (z >= 0) {
135
+ p++;
136
+ return Buffer.concat(parts);
137
+ }
138
+ if (len > 65536)
139
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'A gzip header field without an end', { reason: 'a gzip name or comment without an end' });
140
+ }
141
+ };
142
+ if (flags & 0x04) {
143
+ const x = await reader.read(p, 2);
144
+ if (x.length < 2)
145
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a gzip header', { what: 'gzip header' });
146
+ const xlen = x[0] | (x[1] << 8);
147
+ notes?.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.ExtraField, data: { field: 'a gzip extra field' } });
148
+ p += 2 + xlen;
149
+ }
150
+ if (flags & 0x08)
151
+ header.name = Buffer.from(await field()).toString('latin1');
152
+ if (flags & 0x10)
153
+ header.comment = Buffer.from(await field()).toString('latin1');
154
+ if (flags & 0x02) {
155
+ const stored = await reader.read(p, 2);
156
+ if (stored.length < 2)
157
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a gzip header', { what: 'gzip header' });
158
+ const crc = (0, crc32_1.crc32)(await reader.read(at, p - at)) & 0xffff;
159
+ if ((stored[0] | (stored[1] << 8)) !== crc)
160
+ notes?.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MalformedHeader, data: { reason: 'the gzip header CRC is wrong, which gzip refuses' } });
161
+ p += 2;
162
+ }
163
+ if (p > reader.size)
164
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a gzip header', { what: 'gzip header' });
165
+ header.dataStart = p;
166
+ return header;
167
+ }
168
+ class GzipArchive extends Single {
169
+ header;
170
+ format = 'gzip';
171
+ constructor(reader, notes, header) {
172
+ super(reader, notes, header.name ?? '');
173
+ this.header = header;
174
+ if (header.comment !== undefined)
175
+ this.comment = header.comment;
176
+ }
177
+ hasMetadata() {
178
+ return this.header.mtime !== 0 || this.header.os !== 255 || this.comment !== undefined;
179
+ }
180
+ static async open(source) {
181
+ const reader = new io_1.Reader(source, await source.size());
182
+ const notes = [];
183
+ const header = await gzipHeader(reader, 0, notes);
184
+ return new GzipArchive(reader, notes, header);
185
+ }
186
+ async *read(e, meter) {
187
+ const size = this.reader.size;
188
+ let at = 0;
189
+ let members = 0;
190
+ let total = 0;
191
+ let used = 0;
192
+ this.lateNotes.length = 0;
193
+ for (;;) {
194
+ members++;
195
+ const h = members === 1 ? this.header : await gzipHeader(this.reader, at, undefined);
196
+ const consumed = { bytes: 0 };
197
+ let crc = 0;
198
+ let length = 0;
199
+ for await (const c of (0, codecs_1.inflateRaw)(this.reader.chunks(h.dataStart, size - h.dataStart), consumed)) {
200
+ length += c.length;
201
+ total += c.length;
202
+ meter.add(total, used + consumed.bytes);
203
+ crc = (0, crc32_1.crc32)(c, crc);
204
+ yield c;
205
+ }
206
+ used += consumed.bytes;
207
+ const trailerAt = h.dataStart + consumed.bytes;
208
+ const t = await this.reader.read(trailerAt, 8);
209
+ if (t.length < 8)
210
+ throw new errors_1.DataError('the file ends inside a gzip trailer', true);
211
+ const view = new DataView(t.buffer, t.byteOffset, 8);
212
+ if (view.getUint32(0, true) !== crc)
213
+ throw new errors_1.DataError('a gzip member does not match its CRC');
214
+ if (view.getUint32(4, true) !== length % 2 ** 32)
215
+ throw new errors_1.DataError('a gzip member does not match its size');
216
+ at = trailerAt + 8;
217
+ if (members === 1)
218
+ this.raw = { start: 0, length: at };
219
+ const next = await this.reader.read(at, 2);
220
+ if (next.length < 2 || next[0] !== 0x1f || next[1] !== 0x8b)
221
+ break;
222
+ }
223
+ if (members > 1) {
224
+ this.raw = undefined;
225
+ this.lateNotes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MultipleStreams, data: { count: members } });
226
+ }
227
+ this.trailingStart = at;
228
+ await this.trailing(at);
229
+ e.decoded = total;
230
+ e.size = total;
231
+ }
232
+ /** A member header with the name a rule fixed, and the metadata unless it is stripped. */
233
+ headerBytes(plan) {
234
+ const k = plan.kept[0];
235
+ const keepMeta = !plan.stripMetadata;
236
+ const name = k?.name || undefined;
237
+ const comment = keepMeta ? this.comment : undefined;
238
+ const out = Buffer.alloc(10);
239
+ out.set([0x1f, 0x8b, 8, (name !== undefined ? 0x08 : 0) | (comment !== undefined ? 0x10 : 0)]);
240
+ out.writeUInt32LE(keepMeta ? this.header.mtime : 0, 4);
241
+ out[8] = 0;
242
+ out[9] = keepMeta ? this.header.os : 255;
243
+ const field = (v) => Buffer.concat([Buffer.from(v.replace(/\0/g, '_'), 'latin1'), Buffer.alloc(1)]);
244
+ return Buffer.concat([out, ...(name !== undefined ? [field(name)] : []), ...(comment !== undefined ? [field(comment)] : [])]);
245
+ }
246
+ canStore = true;
247
+ async *encode(chunks, plan, store) {
248
+ yield this.headerBytes(plan);
249
+ let crc = 0;
250
+ let length = 0;
251
+ async function* tally() {
252
+ for await (const c of chunks) {
253
+ crc = (0, crc32_1.crc32)(c, crc);
254
+ length += c.length;
255
+ yield c;
256
+ }
257
+ }
258
+ yield* (0, codecs_1.deflateRaw)(tally(), store ? 0 : 6);
259
+ const trailer = Buffer.alloc(8);
260
+ trailer.writeUInt32LE(crc >>> 0, 0);
261
+ trailer.writeUInt32LE(length % 2 ** 32, 4);
262
+ yield trailer;
263
+ }
264
+ /** The header is written again, so the name a rule fixed and the metadata stripped apply also when the data is copied. */
265
+ async writeStream(plan, sink, context) {
266
+ const k = plan.kept[0];
267
+ const raw = this.raw;
268
+ if (!k || k.replacement || k.recompress || !raw)
269
+ return super.writeStream(plan, sink, context);
270
+ await sink.write(this.headerBytes(plan));
271
+ for await (const c of this.reader.chunks(this.header.dataStart, raw.length - this.header.dataStart)) {
272
+ context.meter().time();
273
+ await sink.write(c);
274
+ }
275
+ }
276
+ }
277
+ exports.GzipArchive = GzipArchive;
278
+ // ---------------------------------------------------------------------------------------------------------------
279
+ // bzip2
280
+ class Bzip2Archive extends Single {
281
+ format = 'bzip2';
282
+ level;
283
+ constructor(reader, level) {
284
+ super(reader, []);
285
+ this.level = level;
286
+ }
287
+ static async open(source) {
288
+ const reader = new io_1.Reader(source, await source.size());
289
+ const h = await reader.read(0, 4);
290
+ if (h.length < 4 || h[0] !== 0x42 || h[1] !== 0x5a || h[2] !== 0x68 || h[3] < 0x31 || h[3] > 0x39)
291
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'Not a bzip2 stream', { reason: 'no bzip2 signature' });
292
+ return new Bzip2Archive(reader, h[3] - 0x30);
293
+ }
294
+ async *read(e, meter) {
295
+ const info = { streams: [], trailing: 0, trailingZeros: true };
296
+ const consumed = { bytes: 0 };
297
+ let total = 0;
298
+ this.lateNotes.length = 0;
299
+ for await (const c of (0, bzip2_1.bunzip2)(this.reader.chunks(0, this.reader.size), info, true, consumed)) {
300
+ total += c.length;
301
+ meter.add(total, consumed.bytes);
302
+ yield c;
303
+ }
304
+ const end = info.streams[info.streams.length - 1]?.end ?? 0;
305
+ this.raw = info.streams.length === 1 ? { start: 0, length: end } : undefined;
306
+ if (info.streams.length > 1)
307
+ this.lateNotes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MultipleStreams, data: { count: info.streams.length } });
308
+ this.trailingStart = end;
309
+ await this.trailing(end);
310
+ e.decoded = total;
311
+ e.size = total;
312
+ }
313
+ encode(chunks) {
314
+ return (0, bzip2_1.bzip2)(chunks, this.level);
315
+ }
316
+ }
317
+ exports.Bzip2Archive = Bzip2Archive;
318
+ // ---------------------------------------------------------------------------------------------------------------
319
+ // zstd
320
+ class ZstdArchive extends Single {
321
+ layout;
322
+ format = 'zstd';
323
+ constructor(reader, notes, layout) {
324
+ super(reader, notes);
325
+ this.layout = layout;
326
+ }
327
+ static async open(source, deadline) {
328
+ const reader = new io_1.Reader(source, await source.size());
329
+ let layout;
330
+ try {
331
+ layout = await (0, zstd_1.zstdLayout)((o, n) => reader.read(o, n), 0, reader.size, false, () => (0, errors_1.checkTime)(deadline));
332
+ }
333
+ catch (err) {
334
+ if (err instanceof errors_1.UnsupportedError)
335
+ throw new errors_1.StopError(types_1.ArchiveCategory.Compression, types_1.ArchiveDetail.UnsupportedMethod, err.message, { method: 'zstd', reason: err.message });
336
+ if (err instanceof errors_1.DataError)
337
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, err.truncated ? types_1.ArchiveDetail.Truncated : types_1.ArchiveDetail.Unparseable, err.message, err.truncated ? { what: 'zstd frame' } : { reason: err.message });
338
+ throw err;
339
+ }
340
+ if (!codecs_1.zstdDecompressor)
341
+ throw new errors_1.StopError(types_1.ArchiveCategory.Compression, types_1.ArchiveDetail.Unavailable, 'This Node has no zstd', { method: 'zstd' });
342
+ const notes = [];
343
+ const frames = layout.frames.filter(f => !f.skippable);
344
+ const skipped = layout.frames.filter(f => f.skippable);
345
+ if (skipped.length)
346
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.HiddenData, data: { bytes: skipped.reduce((s, f) => s + f.end - f.start, 0), reason: 'skippable zstd frames' } });
347
+ if (frames.length > 1)
348
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MultipleStreams, data: { count: frames.length } });
349
+ const a = new ZstdArchive(reader, notes, layout);
350
+ const last = layout.frames[layout.frames.length - 1];
351
+ a.trailingStart = last?.end ?? 0;
352
+ if (frames.length === 1 && !skipped.length)
353
+ a.raw = { start: frames[0].start, length: frames[0].end - frames[0].start };
354
+ const declared = frames.every(f => f.contentSize !== undefined) ? frames.reduce((s, f) => s + (f.contentSize ?? 0), 0) : undefined;
355
+ if (declared !== undefined)
356
+ a.entries[0].size = declared;
357
+ return a;
358
+ }
359
+ async *read(e, meter) {
360
+ const make = codecs_1.zstdDecompressor;
361
+ if (!make)
362
+ throw new errors_1.StopError(types_1.ArchiveCategory.Compression, types_1.ArchiveDetail.Unavailable, 'This Node has no zstd', { method: 'zstd' });
363
+ this.lateNotes.length = 0;
364
+ let total = 0;
365
+ let base = 0;
366
+ for (const f of this.layout.frames) {
367
+ if (f.skippable)
368
+ continue;
369
+ const consumed = { bytes: 0 };
370
+ let out = 0;
371
+ for await (const c of (0, codecs_1.through)(make(), this.reader.chunks(f.start, f.end - f.start), consumed)) {
372
+ out += c.length;
373
+ total += c.length;
374
+ if (f.contentSize !== undefined && out > f.contentSize)
375
+ throw new errors_1.DataError('a zstd frame decodes past its declared size');
376
+ meter.add(total, base + consumed.bytes);
377
+ yield c;
378
+ }
379
+ if (consumed.bytes !== f.end - f.start)
380
+ throw new errors_1.DataError('a zstd frame ends before its blocks do', true);
381
+ if (f.contentSize !== undefined && out !== f.contentSize)
382
+ throw new errors_1.DataError('a zstd frame decodes to less than its declared size', true);
383
+ base += f.end - f.start;
384
+ }
385
+ await this.trailing(this.trailingStart ?? this.reader.size);
386
+ e.decoded = total;
387
+ e.size = total;
388
+ }
389
+ encode(chunks) {
390
+ return (0, zstd_1.zstdEncode)(chunks);
391
+ }
392
+ /** Node's zstd stream can end with a second, empty frame after the one that holds the data, which is left out. */
393
+ async encodedLength(source, length) {
394
+ const layout = await (0, zstd_1.zstdLayout)((o, n) => source.read(o, n), 0, length, false, () => undefined);
395
+ const last = layout.frames[layout.frames.length - 1];
396
+ return layout.frames.length > 1 && last.contentSize === 0 ? last.start : length;
397
+ }
398
+ }
399
+ exports.ZstdArchive = ZstdArchive;
400
+ // ---------------------------------------------------------------------------------------------------------------
401
+ // Brotli
402
+ class BrotliArchive extends Single {
403
+ format = 'brotli';
404
+ static async open(source) {
405
+ return new BrotliArchive(new io_1.Reader(source, await source.size()), []);
406
+ }
407
+ async *read(e, meter) {
408
+ this.lateNotes.length = 0;
409
+ const consumed = { bytes: 0 };
410
+ let total = 0;
411
+ for await (const c of (0, codecs_1.brotliDecode)(this.reader.chunks(0, this.reader.size), consumed)) {
412
+ total += c.length;
413
+ meter.add(total, consumed.bytes);
414
+ yield c;
415
+ }
416
+ this.raw = { start: 0, length: consumed.bytes };
417
+ this.trailingStart = consumed.bytes;
418
+ await this.trailing(consumed.bytes);
419
+ e.decoded = total;
420
+ e.size = total;
421
+ }
422
+ encode(chunks) {
423
+ return (0, codecs_1.brotliEncode)(chunks);
424
+ }
425
+ }
426
+ exports.BrotliArchive = BrotliArchive;
427
+ /**
428
+ * True when the content decodes as one Brotli stream, which has no signature: the stream must end where the file
429
+ * does, or decode to more than 32 MiB without an error. A prefix that merely decodes is not enough, since an
430
+ * uncompressed meta-block can take megabytes of any bytes before the next header shows whether they are Brotli.
431
+ */
432
+ async function looksLikeBrotli(source) {
433
+ const size = await source.size();
434
+ if (size === 0)
435
+ return false;
436
+ const consumed = { bytes: 0 };
437
+ let out = 0;
438
+ try {
439
+ for await (const c of (0, codecs_1.brotliDecode)(new io_1.Reader(source, size).chunks(0, size), consumed)) {
440
+ out += c.length;
441
+ if (out > 32 * 1024 * 1024)
442
+ return true;
443
+ }
444
+ }
445
+ catch {
446
+ return false;
447
+ }
448
+ return consumed.bytes === size;
449
+ }
450
+ const isGzip = (b) => b.length >= 3 && b[0] === 0x1f && b[1] === 0x8b && b[2] === 8;
451
+ exports.isGzip = isGzip;
452
+ const isBzip2 = (b) => b.length >= 4 && b[0] === 0x42 && b[1] === 0x5a && b[2] === 0x68 && b[3] >= 0x31 && b[3] <= 0x39;
453
+ exports.isBzip2 = isBzip2;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * True when `block` is a tar header: its checksum field matches the sum of its bytes, counted unsigned or, as some old
3
+ * writers did, signed, with the field itself counted as spaces. An all-zero block is the end of an archive, not a header.
4
+ */
5
+ export declare function isTarHeader(block: Uint8Array): boolean;
6
+ /** Identifies a file type from its first bytes. Returns a MIME type or undefined. */
7
+ export declare function sniffType(head: Uint8Array): string | undefined;
8
+ /** Maps MIME types that share a container format, and common aliases of one type, onto one family. */
9
+ export declare function family(mime: string): string;
10
+ /**
11
+ * A file name's extension, lowercased. Windows drops trailing dots and spaces when it saves a file, so the extension
12
+ * of "page.html." is html.
13
+ */
14
+ export declare function extensionOf(name?: string): string | undefined;
15
+ export declare function typeFromName(name?: string): string | undefined;
16
+ /**
17
+ * True when any two of the declared type, the name's extension and the sniffed content contradict each other, or
18
+ * when the declared type or the name claims a type sniffType recognizes and the content matched no signature.
19
+ */
20
+ export declare function typesDisagree(declared: string | undefined, name: string | undefined, sniffed: string | undefined): boolean;