@patrickdk77/archive-defuse 0.0.0-stage → 0.1.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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +378 -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/detect.d.ts +10 -0
  24. package/dist/detect.js +95 -0
  25. package/dist/encoder.d.ts +44 -0
  26. package/dist/encoder.js +114 -0
  27. package/dist/engine.d.ts +44 -0
  28. package/dist/engine.js +753 -0
  29. package/dist/errors.d.ts +64 -0
  30. package/dist/errors.js +98 -0
  31. package/dist/filters.d.ts +34 -0
  32. package/dist/filters.js +385 -0
  33. package/dist/findings.d.ts +26 -0
  34. package/dist/findings.js +176 -0
  35. package/dist/formats.d.ts +24 -0
  36. package/dist/formats.js +106 -0
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.js +53 -0
  39. package/dist/index.mjs +3 -0
  40. package/dist/inflate64.d.ts +6 -0
  41. package/dist/inflate64.js +388 -0
  42. package/dist/io.d.ts +78 -0
  43. package/dist/io.js +379 -0
  44. package/dist/lzma-formats.d.ts +75 -0
  45. package/dist/lzma-formats.js +373 -0
  46. package/dist/lzma-sniff.d.ts +20 -0
  47. package/dist/lzma-sniff.js +73 -0
  48. package/dist/lzma-writers.d.ts +35 -0
  49. package/dist/lzma-writers.js +276 -0
  50. package/dist/lzma1/LICENSE +201 -0
  51. package/dist/lzma1/NOTICE +36 -0
  52. package/dist/lzma1/crc32.d.ts +4 -0
  53. package/dist/lzma1/crc32.js +19 -0
  54. package/dist/lzma1/hash234.d.ts +22 -0
  55. package/dist/lzma1/hash234.js +75 -0
  56. package/dist/lzma1/hc4.d.ts +30 -0
  57. package/dist/lzma1/hc4.js +142 -0
  58. package/dist/lzma1/header.d.ts +32 -0
  59. package/dist/lzma1/header.js +73 -0
  60. package/dist/lzma1/lz-decoder.d.ts +41 -0
  61. package/dist/lzma1/lz-decoder.js +128 -0
  62. package/dist/lzma1/lz-encoder.d.ts +87 -0
  63. package/dist/lzma1/lz-encoder.js +166 -0
  64. package/dist/lzma1/lzma-coder.d.ts +107 -0
  65. package/dist/lzma1/lzma-coder.js +162 -0
  66. package/dist/lzma1/lzma-decoder.d.ts +36 -0
  67. package/dist/lzma1/lzma-decoder.js +484 -0
  68. package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
  69. package/dist/lzma1/lzma-encoder-fast.js +114 -0
  70. package/dist/lzma1/lzma-encoder.d.ts +105 -0
  71. package/dist/lzma1/lzma-encoder.js +497 -0
  72. package/dist/lzma1/lzma2-decoder.d.ts +85 -0
  73. package/dist/lzma1/lzma2-decoder.js +249 -0
  74. package/dist/lzma1/range-coder.d.ts +18 -0
  75. package/dist/lzma1/range-coder.js +23 -0
  76. package/dist/lzma1/range-decoder.d.ts +41 -0
  77. package/dist/lzma1/range-decoder.js +78 -0
  78. package/dist/lzma1/range-encoder.d.ts +53 -0
  79. package/dist/lzma1/range-encoder.js +269 -0
  80. package/dist/lzma1-stream.d.ts +60 -0
  81. package/dist/lzma1-stream.js +202 -0
  82. package/dist/model.d.ts +141 -0
  83. package/dist/model.js +16 -0
  84. package/dist/names.d.ts +13 -0
  85. package/dist/names.js +66 -0
  86. package/dist/pack.d.ts +79 -0
  87. package/dist/pack.js +590 -0
  88. package/dist/plugins.d.ts +13 -0
  89. package/dist/plugins.js +56 -0
  90. package/dist/rules.d.ts +25 -0
  91. package/dist/rules.js +403 -0
  92. package/dist/score.d.ts +17 -0
  93. package/dist/score.js +58 -0
  94. package/dist/seven.d.ts +75 -0
  95. package/dist/seven.js +492 -0
  96. package/dist/sevenzip.d.ts +133 -0
  97. package/dist/sevenzip.js +529 -0
  98. package/dist/single.d.ts +103 -0
  99. package/dist/single.js +453 -0
  100. package/dist/sniff.d.ts +20 -0
  101. package/dist/sniff.js +224 -0
  102. package/dist/stream-meter.d.ts +39 -0
  103. package/dist/stream-meter.js +54 -0
  104. package/dist/tar.d.ts +102 -0
  105. package/dist/tar.js +765 -0
  106. package/dist/types.d.ts +311 -0
  107. package/dist/types.js +133 -0
  108. package/dist/unpack.d.ts +67 -0
  109. package/dist/unpack.js +194 -0
  110. package/dist/xz.d.ts +25 -0
  111. package/dist/xz.js +333 -0
  112. package/dist/zip-lzma.d.ts +15 -0
  113. package/dist/zip-lzma.js +137 -0
  114. package/dist/zip-writer.d.ts +51 -0
  115. package/dist/zip-writer.js +305 -0
  116. package/dist/zip.d.ts +99 -0
  117. package/dist/zip.js +901 -0
  118. package/dist/zipcrypt.d.ts +47 -0
  119. package/dist/zipcrypt.js +233 -0
  120. package/dist/zstd.d.ts +25 -0
  121. package/dist/zstd.js +160 -0
  122. package/package.json +94 -4
@@ -0,0 +1,529 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeaderReader = exports.Nid = void 0;
4
+ exports.parseStartHeader = parseStartHeader;
5
+ exports.coderOf = coderOf;
6
+ exports.firstStream = firstStream;
7
+ exports.readStreamsInfo = readStreamsInfo;
8
+ exports.parseHeader = parseHeader;
9
+ exports.parseEncodedHeader = parseEncodedHeader;
10
+ const errors_1 = require("./errors");
11
+ /** Property IDs of the 7z header. */
12
+ var Nid;
13
+ (function (Nid) {
14
+ Nid[Nid["End"] = 0] = "End";
15
+ Nid[Nid["Header"] = 1] = "Header";
16
+ Nid[Nid["ArchiveProperties"] = 2] = "ArchiveProperties";
17
+ Nid[Nid["AdditionalStreamsInfo"] = 3] = "AdditionalStreamsInfo";
18
+ Nid[Nid["MainStreamsInfo"] = 4] = "MainStreamsInfo";
19
+ Nid[Nid["FilesInfo"] = 5] = "FilesInfo";
20
+ Nid[Nid["PackInfo"] = 6] = "PackInfo";
21
+ Nid[Nid["UnpackInfo"] = 7] = "UnpackInfo";
22
+ Nid[Nid["SubStreamsInfo"] = 8] = "SubStreamsInfo";
23
+ Nid[Nid["Size"] = 9] = "Size";
24
+ Nid[Nid["Crc"] = 10] = "Crc";
25
+ Nid[Nid["Folder"] = 11] = "Folder";
26
+ Nid[Nid["CodersUnpackSize"] = 12] = "CodersUnpackSize";
27
+ Nid[Nid["NumUnpackStream"] = 13] = "NumUnpackStream";
28
+ Nid[Nid["EmptyStream"] = 14] = "EmptyStream";
29
+ Nid[Nid["EmptyFile"] = 15] = "EmptyFile";
30
+ Nid[Nid["Anti"] = 16] = "Anti";
31
+ Nid[Nid["Name"] = 17] = "Name";
32
+ Nid[Nid["CTime"] = 18] = "CTime";
33
+ Nid[Nid["ATime"] = 19] = "ATime";
34
+ Nid[Nid["MTime"] = 20] = "MTime";
35
+ Nid[Nid["WinAttributes"] = 21] = "WinAttributes";
36
+ Nid[Nid["Comment"] = 22] = "Comment";
37
+ Nid[Nid["EncodedHeader"] = 23] = "EncodedHeader";
38
+ Nid[Nid["StartPos"] = 24] = "StartPos";
39
+ Nid[Nid["Dummy"] = 25] = "Dummy";
40
+ })(Nid || (exports.Nid = Nid = {}));
41
+ const MAX_SAFE = Number.MAX_SAFE_INTEGER;
42
+ /** Reads the numbers, bit vectors and digests of a 7z header. */
43
+ class HeaderReader {
44
+ buf;
45
+ pos = 0;
46
+ constructor(buf) {
47
+ this.buf = buf;
48
+ }
49
+ get remaining() {
50
+ return this.buf.length - this.pos;
51
+ }
52
+ byte() {
53
+ if (this.pos >= this.buf.length)
54
+ throw new errors_1.ParseError('the header ends early');
55
+ return this.buf[this.pos++];
56
+ }
57
+ /** A property ID, read as a 7z number, so an ID can take more than one byte. Any large one is unknown. */
58
+ id() {
59
+ return Math.min(this.raw(), MAX_SAFE);
60
+ }
61
+ bytes(n) {
62
+ if (n > this.remaining)
63
+ throw new errors_1.ParseError('the header ends early');
64
+ const b = this.buf.subarray(this.pos, this.pos + n);
65
+ this.pos += n;
66
+ return b;
67
+ }
68
+ /** A 7z number: the first byte's leading one bits count the bytes that follow, which hold the low bits. */
69
+ raw() {
70
+ const first = this.byte();
71
+ let mask = 0x80;
72
+ let value = 0;
73
+ for (let i = 0; i < 8; i++) {
74
+ if ((first & mask) === 0)
75
+ return value + (first & (mask - 1)) * 2 ** (8 * i);
76
+ value += this.byte() * 2 ** (8 * i);
77
+ mask >>>= 1;
78
+ }
79
+ return value;
80
+ }
81
+ /** A 7z number used as a size, a count or an index. */
82
+ number() {
83
+ const value = this.raw();
84
+ if (value > MAX_SAFE)
85
+ throw new errors_1.ParseError('a header number is too large');
86
+ return value;
87
+ }
88
+ /** A number used as a count of things that each take at least `unit` bytes of what is left. */
89
+ count(unit, max = MAX_SAFE) {
90
+ const n = this.number();
91
+ if (n > max || n * unit > this.remaining)
92
+ throw new errors_1.ParseError('a header count is larger than the header');
93
+ return n;
94
+ }
95
+ uint32() {
96
+ const b = this.bytes(4);
97
+ return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0;
98
+ }
99
+ bits(n) {
100
+ const out = [];
101
+ let mask = 0;
102
+ let cur = 0;
103
+ for (let i = 0; i < n; i++) {
104
+ if (mask === 0) {
105
+ cur = this.byte();
106
+ mask = 0x80;
107
+ }
108
+ out.push((cur & mask) !== 0);
109
+ mask >>>= 1;
110
+ }
111
+ return out;
112
+ }
113
+ /** A byte that says all are defined, or a bit vector that says which. */
114
+ defined(n) {
115
+ const all = this.byte();
116
+ return all ? new Array(n).fill(true) : this.bits(n);
117
+ }
118
+ digests(n) {
119
+ const defined = this.defined(n);
120
+ return defined.map(d => (d ? this.uint32() : undefined));
121
+ }
122
+ }
123
+ exports.HeaderReader = HeaderReader;
124
+ const hex = (b) => Buffer.from(b).toString('hex');
125
+ /** Reads the 20 bytes after the signature, version and CRC. */
126
+ function parseStartHeader(b) {
127
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
128
+ const u64 = (at) => {
129
+ const v = view.getUint32(at, true) + view.getUint32(at + 4, true) * 2 ** 32;
130
+ if (v > MAX_SAFE)
131
+ throw new errors_1.ParseError('the start header holds an offset beyond any file');
132
+ return v;
133
+ };
134
+ return { minorVersion: b[7], nextHeaderOffset: u64(12), nextHeaderSize: u64(20), nextHeaderCrc: view.getUint32(28, true) };
135
+ }
136
+ /** At most 64 coders, and 64 streams, in a folder: more than any chain of 7z methods needs. */
137
+ const MAX_FOLDER_STREAMS = 64;
138
+ function readFolder(r) {
139
+ const numCoders = r.count(2, MAX_FOLDER_STREAMS);
140
+ if (numCoders === 0)
141
+ throw new errors_1.ParseError('a folder has no coders');
142
+ const coders = [];
143
+ let totalIn = 0;
144
+ let totalOut = 0;
145
+ for (let i = 0; i < numCoders; i++) {
146
+ const flags = r.byte();
147
+ if (flags & 0x80)
148
+ throw new errors_1.UnsupportedError('alternative coder methods', false, { reason: 'alternative coder methods' });
149
+ const idSize = flags & 0x0f;
150
+ // A method ID is a big-endian number, so leading zero bytes add nothing, and an ID of no bytes is 0, which is Copy.
151
+ const method = hex(r.bytes(idSize)).replace(/^(00)+/, '') || '00';
152
+ let numIn = 1;
153
+ let numOut = 1;
154
+ if (flags & 0x10) {
155
+ numIn = r.count(0, MAX_FOLDER_STREAMS);
156
+ numOut = r.count(0, MAX_FOLDER_STREAMS);
157
+ }
158
+ const props = flags & 0x20 ? r.bytes(r.count(1)).slice() : new Uint8Array(0);
159
+ coders.push({ method, numIn, numOut, props });
160
+ totalIn += numIn;
161
+ totalOut += numOut;
162
+ }
163
+ if (totalOut === 0 || totalIn > MAX_FOLDER_STREAMS || totalOut > MAX_FOLDER_STREAMS)
164
+ throw new errors_1.ParseError('a folder has an invalid number of streams');
165
+ const bindPairs = [];
166
+ for (let i = 0; i < totalOut - 1; i++) {
167
+ const inIndex = r.number();
168
+ const outIndex = r.number();
169
+ if (inIndex >= totalIn || outIndex >= totalOut)
170
+ throw new errors_1.ParseError('a bind pair names a stream the folder does not have');
171
+ bindPairs.push({ inIndex, outIndex });
172
+ }
173
+ const numPacked = totalIn - bindPairs.length;
174
+ if (numPacked < 1)
175
+ throw new errors_1.ParseError('a folder reads no packed stream');
176
+ const packedStreams = [];
177
+ if (numPacked === 1) {
178
+ for (let i = 0; i < totalIn; i++) {
179
+ if (!bindPairs.some(bp => bp.inIndex === i)) {
180
+ packedStreams.push(i);
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ else {
186
+ for (let i = 0; i < numPacked; i++) {
187
+ const index = r.number();
188
+ if (index >= totalIn)
189
+ throw new errors_1.ParseError('a packed stream index names a stream the folder does not have');
190
+ packedStreams.push(index);
191
+ }
192
+ }
193
+ if (packedStreams.length !== numPacked)
194
+ throw new errors_1.ParseError('the folder binds every input');
195
+ // Every in-stream gets exactly one source, and no out-stream feeds two in-streams.
196
+ const inUsed = new Set([...bindPairs.map(bp => bp.inIndex), ...packedStreams]);
197
+ const outUsed = new Set(bindPairs.map(bp => bp.outIndex));
198
+ if (inUsed.size !== totalIn || outUsed.size !== bindPairs.length)
199
+ throw new errors_1.ParseError('the folder joins its streams inconsistently');
200
+ return { coders, bindPairs, packedStreams };
201
+ }
202
+ /** The coder that owns a global in- or out-stream index, and the stream's index within that coder. */
203
+ function coderOf(folder, index, side) {
204
+ let base = 0;
205
+ for (let c = 0; c < folder.coders.length; c++) {
206
+ const n = side === 'in' ? folder.coders[c].numIn : folder.coders[c].numOut;
207
+ if (index < base + n)
208
+ return { coder: c, local: index - base };
209
+ base += n;
210
+ }
211
+ throw new errors_1.ParseError('a stream index is out of range');
212
+ }
213
+ /** The first global in- or out-stream index of coder `c`. */
214
+ function firstStream(folder, c, side) {
215
+ let base = 0;
216
+ for (let i = 0; i < c; i++)
217
+ base += side === 'in' ? folder.coders[i].numIn : folder.coders[i].numOut;
218
+ return base;
219
+ }
220
+ /**
221
+ * Throws when following the bind pairs from the main output loops back on itself, or leaves a coder unreached. The
222
+ * walk visits each coder once at most, so a crafted folder cannot make it run long.
223
+ */
224
+ function checkAcyclic(folder) {
225
+ const visiting = new Set();
226
+ const reached = new Set();
227
+ const visit = (c, depth) => {
228
+ if (depth > MAX_FOLDER_STREAMS || visiting.has(c) || reached.has(c))
229
+ throw new errors_1.ParseError('the folder binds its coders in a loop');
230
+ visiting.add(c);
231
+ reached.add(c);
232
+ const first = firstStream(folder, c, 'in');
233
+ for (let i = 0; i < folder.coders[c].numIn; i++) {
234
+ const bp = folder.bindPairs.find(p => p.inIndex === first + i);
235
+ if (bp)
236
+ visit(coderOf(folder, bp.outIndex, 'out').coder, depth + 1);
237
+ }
238
+ visiting.delete(c);
239
+ };
240
+ visit(coderOf(folder, folder.mainOut, 'out').coder, 0);
241
+ if (reached.size !== folder.coders.length)
242
+ throw new errors_1.ParseError('a coder of the folder feeds nothing the folder outputs');
243
+ }
244
+ /**
245
+ * Reads a streams info. `maxEntries` caps the substreams, each of which is a file, before anything is allocated for
246
+ * them; `tooMany` reports a count over it.
247
+ */
248
+ function readStreamsInfo(r, maxEntries = MAX_SAFE, tooMany) {
249
+ const info = { packPos: 0, packSizes: [], folders: [], numUnpackStreams: [], subSizes: [], subCrcs: [] };
250
+ let id = r.id();
251
+ if (id === Nid.PackInfo) {
252
+ info.packPos = r.number();
253
+ const numPack = r.count(1);
254
+ for (;;) {
255
+ id = r.id();
256
+ if (id === Nid.End)
257
+ break;
258
+ if (id === Nid.Size)
259
+ for (let i = 0; i < numPack; i++)
260
+ info.packSizes.push(r.number());
261
+ else if (id === Nid.Crc)
262
+ r.digests(numPack);
263
+ else
264
+ throw new errors_1.ParseError(`unexpected property ${id} in the pack info`);
265
+ }
266
+ if (info.packSizes.length !== numPack)
267
+ throw new errors_1.ParseError('the pack info has no sizes');
268
+ id = r.id();
269
+ }
270
+ if (id === Nid.UnpackInfo) {
271
+ if (r.id() !== Nid.Folder)
272
+ throw new errors_1.ParseError('the unpack info does not start with its folders');
273
+ const numFolders = r.count(3);
274
+ if (r.byte() !== 0)
275
+ throw new errors_1.UnsupportedError('folders stored outside the header', false, { reason: 'folders stored outside the header' });
276
+ const raw = [];
277
+ for (let i = 0; i < numFolders; i++)
278
+ raw.push(readFolder(r));
279
+ if (r.id() !== Nid.CodersUnpackSize)
280
+ throw new errors_1.ParseError('the unpack info has no sizes');
281
+ for (const f of raw) {
282
+ const totalOut = f.coders.reduce((s, c) => s + c.numOut, 0);
283
+ const unpackSizes = [];
284
+ for (let i = 0; i < totalOut; i++)
285
+ unpackSizes.push(r.number());
286
+ let mainOut = -1;
287
+ for (let i = 0; i < totalOut; i++)
288
+ if (!f.bindPairs.some(bp => bp.outIndex === i))
289
+ mainOut = i;
290
+ const folder = { ...f, unpackSizes, mainOut };
291
+ checkAcyclic(folder);
292
+ info.folders.push(folder);
293
+ }
294
+ for (;;) {
295
+ id = r.id();
296
+ if (id === Nid.End)
297
+ break;
298
+ if (id === Nid.Crc) {
299
+ const crcs = r.digests(numFolders);
300
+ crcs.forEach((c, i) => {
301
+ if (c !== undefined)
302
+ info.folders[i].crc = c;
303
+ });
304
+ }
305
+ else
306
+ throw new errors_1.ParseError(`unexpected property ${id} in the unpack info`);
307
+ }
308
+ id = r.id();
309
+ }
310
+ // Without substream info, each folder holds one file.
311
+ info.numUnpackStreams = info.folders.map(() => 1);
312
+ if (id === Nid.SubStreamsInfo) {
313
+ id = r.id();
314
+ if (id === Nid.NumUnpackStream) {
315
+ info.numUnpackStreams = info.folders.map(() => r.number());
316
+ id = r.id();
317
+ }
318
+ const total = info.numUnpackStreams.reduce((s, n) => s + n, 0);
319
+ if (total > maxEntries)
320
+ tooMany?.(total);
321
+ if (total > maxEntries || total > r.remaining * 8 + info.folders.length)
322
+ throw new errors_1.ParseError('the substream count is larger than the header');
323
+ const sizes = [];
324
+ const hasSizes = id === Nid.Size;
325
+ for (let f = 0; f < info.folders.length; f++) {
326
+ const n = info.numUnpackStreams[f];
327
+ if (n === 0)
328
+ continue;
329
+ const folder = info.folders[f];
330
+ const folderSize = folder.unpackSizes[folder.mainOut];
331
+ let sum = 0;
332
+ for (let i = 0; i < n - 1; i++) {
333
+ const s = hasSizes ? r.number() : 0;
334
+ sizes.push(s);
335
+ sum += s;
336
+ }
337
+ if (sum > folderSize)
338
+ throw new errors_1.ParseError('substream sizes add up to more than their folder');
339
+ sizes.push(folderSize - sum);
340
+ }
341
+ if (hasSizes)
342
+ id = r.id();
343
+ info.subSizes = sizes;
344
+ // A folder with one substream and a CRC of its own already gives that substream's CRC. The digests list the rest.
345
+ const crcs = [];
346
+ const open = [];
347
+ for (let f = 0; f < info.folders.length; f++) {
348
+ const n = info.numUnpackStreams[f];
349
+ if (n === 1 && info.folders[f].crc !== undefined)
350
+ crcs.push(info.folders[f].crc);
351
+ else
352
+ for (let i = 0; i < n; i++) {
353
+ open.push(crcs.length);
354
+ crcs.push(undefined);
355
+ }
356
+ }
357
+ while (id !== Nid.End) {
358
+ if (id !== Nid.Crc)
359
+ throw new errors_1.ParseError(`unexpected property ${id} in the substream info`);
360
+ const read = r.digests(open.length);
361
+ open.forEach((at, k) => {
362
+ crcs[at] = read[k];
363
+ });
364
+ id = r.id();
365
+ }
366
+ info.subCrcs = crcs;
367
+ id = r.id();
368
+ }
369
+ else {
370
+ info.subSizes = info.folders.map(f => f.unpackSizes[f.mainOut]);
371
+ info.subCrcs = info.folders.map(f => f.crc);
372
+ }
373
+ if (id !== Nid.End)
374
+ throw new errors_1.ParseError(`unexpected property ${id} in the streams info`);
375
+ return info;
376
+ }
377
+ /** Names of the properties this package drops, for the finding that reports them. */
378
+ const PROPERTY_NAMES = {
379
+ [Nid.Comment]: 'comment',
380
+ [Nid.StartPos]: 'start positions',
381
+ [Nid.ArchiveProperties]: 'archive properties',
382
+ };
383
+ function readFiles(r, header, maxEntries, tooMany) {
384
+ const numFiles = r.number();
385
+ if (numFiles > maxEntries)
386
+ tooMany(numFiles);
387
+ if (numFiles > r.remaining * 8 + 1)
388
+ throw new errors_1.ParseError('the file count is larger than the header');
389
+ const files = [];
390
+ for (let i = 0; i < numFiles; i++)
391
+ files.push({ hasStream: true, emptyFile: false, anti: false });
392
+ let emptyStreams = [];
393
+ const seenTypes = new Set();
394
+ for (;;) {
395
+ const type = r.id();
396
+ if (type === Nid.End)
397
+ break;
398
+ // A property given twice would leave the entries described two ways, so the header is refused.
399
+ if (type !== Nid.Dummy && seenTypes.has(type))
400
+ throw new errors_1.ParseError(`file property ${type} appears twice`);
401
+ seenTypes.add(type);
402
+ const size = r.number();
403
+ if (size > r.remaining)
404
+ throw new errors_1.ParseError('a file property is larger than the header');
405
+ const sub = new HeaderReader(r.bytes(size));
406
+ switch (type) {
407
+ case Nid.EmptyStream: {
408
+ const bits = sub.bits(numFiles);
409
+ emptyStreams = [];
410
+ bits.forEach((b, i) => {
411
+ files[i].hasStream = !b;
412
+ if (b)
413
+ emptyStreams.push(i);
414
+ });
415
+ break;
416
+ }
417
+ case Nid.EmptyFile:
418
+ sub.bits(emptyStreams.length).forEach((b, k) => {
419
+ files[emptyStreams[k]].emptyFile = b;
420
+ });
421
+ break;
422
+ case Nid.Anti:
423
+ sub.bits(emptyStreams.length).forEach((b, k) => {
424
+ files[emptyStreams[k]].anti = b;
425
+ });
426
+ break;
427
+ case Nid.Name: {
428
+ if (sub.byte() !== 0)
429
+ throw new errors_1.UnsupportedError('names stored outside the header', false, { reason: 'names stored outside the header' });
430
+ const body = sub.bytes(sub.remaining);
431
+ let at = 0;
432
+ for (const f of files) {
433
+ let end = at;
434
+ while (end + 1 < body.length && (body[end] | body[end + 1]) !== 0)
435
+ end += 2;
436
+ if (end + 1 >= body.length)
437
+ throw new errors_1.ParseError('a file name is not terminated');
438
+ const units = new Uint16Array(body.buffer.slice(body.byteOffset + at, body.byteOffset + end));
439
+ // As stored, lone surrogates included, so the name rules see what an extractor would.
440
+ let name = '';
441
+ for (let k = 0; k < units.length; k += 4096)
442
+ name += String.fromCharCode(...units.subarray(k, k + 4096));
443
+ f.name = name;
444
+ at = end + 2;
445
+ }
446
+ // The names fill their property exactly, so a NUL inside one cannot shift the rest.
447
+ if (at !== body.length)
448
+ throw new errors_1.ParseError('the names do not fill their property');
449
+ break;
450
+ }
451
+ case Nid.CTime:
452
+ case Nid.ATime:
453
+ case Nid.MTime: {
454
+ const defined = sub.defined(numFiles);
455
+ if (sub.byte() !== 0)
456
+ throw new errors_1.UnsupportedError('times stored outside the header', false, { reason: 'times stored outside the header' });
457
+ defined.forEach((d, i) => {
458
+ if (!d)
459
+ return;
460
+ const t = sub.bytes(8).slice();
461
+ if (type === Nid.CTime)
462
+ files[i].ctime = t;
463
+ else if (type === Nid.ATime)
464
+ files[i].atime = t;
465
+ else
466
+ files[i].mtime = t;
467
+ });
468
+ break;
469
+ }
470
+ case Nid.WinAttributes: {
471
+ const defined = sub.defined(numFiles);
472
+ if (sub.byte() !== 0)
473
+ throw new errors_1.UnsupportedError('attributes stored outside the header', false, { reason: 'attributes stored outside the header' });
474
+ defined.forEach((d, i) => {
475
+ if (d)
476
+ files[i].attrib = sub.uint32();
477
+ });
478
+ break;
479
+ }
480
+ case Nid.Dummy:
481
+ // Padding 7-Zip writes to align what follows.
482
+ break;
483
+ default:
484
+ header.unknown.push(PROPERTY_NAMES[type] ?? `property ${type}`);
485
+ }
486
+ }
487
+ header.files = files;
488
+ }
489
+ /**
490
+ * Reads a plain header, starting after its kHeader byte. `maxEntries` stops a file count over the limit before
491
+ * anything is allocated for it.
492
+ */
493
+ function parseHeader(buf, maxEntries, tooMany) {
494
+ const r = new HeaderReader(buf);
495
+ if (r.id() !== Nid.Header)
496
+ throw new errors_1.ParseError('the header does not start with kHeader');
497
+ const header = { files: [], unknown: [] };
498
+ let id = r.id();
499
+ if (id === Nid.ArchiveProperties) {
500
+ for (;;) {
501
+ const type = r.id();
502
+ if (type === Nid.End)
503
+ break;
504
+ r.bytes(r.number());
505
+ }
506
+ header.unknown.push(PROPERTY_NAMES[Nid.ArchiveProperties]);
507
+ id = r.id();
508
+ }
509
+ if (id === Nid.AdditionalStreamsInfo)
510
+ throw new errors_1.UnsupportedError('additional streams', false, { reason: 'additional streams' });
511
+ if (id === Nid.MainStreamsInfo) {
512
+ header.streams = readStreamsInfo(r, maxEntries, tooMany);
513
+ id = r.id();
514
+ }
515
+ if (id === Nid.FilesInfo) {
516
+ readFiles(r, header, maxEntries, tooMany);
517
+ id = r.id();
518
+ }
519
+ if (id !== Nid.End)
520
+ throw new errors_1.ParseError(`unexpected property ${id} in the header`);
521
+ return header;
522
+ }
523
+ /** Reads an encoded header's streams info, starting after its kEncodedHeader byte. */
524
+ function parseEncodedHeader(buf) {
525
+ const r = new HeaderReader(buf);
526
+ if (r.id() !== Nid.EncodedHeader)
527
+ throw new errors_1.ParseError('the header is not encoded');
528
+ return readStreamsInfo(r);
529
+ }
@@ -0,0 +1,103 @@
1
+ import { Reader } from './io';
2
+ import { type Archive, type Entry, type Kept, type Meter, type Note, type WriteContext, type WritePlan } from './model';
3
+ import { type ArchiveFormat, type ByteSink, type ByteSource } from './types';
4
+ import { type ZstdLayout } from './zstd';
5
+ /** The one entry of a compressed file. Its size is known once it has been read. */
6
+ export interface SingleEntry extends Entry {
7
+ /** The decoded size, set when the engine has read the entry. */
8
+ decoded?: number;
9
+ }
10
+ /** What the writer copies when the content stays as it was: the one compressed stream, header included. */
11
+ interface RawStream {
12
+ start: number;
13
+ length: number;
14
+ }
15
+ declare abstract class Single implements Archive<SingleEntry> {
16
+ readonly reader: Reader;
17
+ readonly notes: Note[];
18
+ abstract readonly format: ArchiveFormat;
19
+ readonly single = true;
20
+ readonly lateNotes: Note[];
21
+ readonly entries: SingleEntry[];
22
+ comment?: string;
23
+ /** Set once the entry has been read and the stream turned out to be one stream with nothing after it. */
24
+ protected raw?: RawStream;
25
+ constructor(reader: Reader, notes: Note[], name?: string);
26
+ unlock(): Promise<boolean>;
27
+ hasMetadata(): boolean;
28
+ abstract read(e: SingleEntry, meter: Meter): AsyncGenerator<Uint8Array>;
29
+ /** Compresses the content as one stream of the format. With `store`, a format that can store data uncompressed does. */
30
+ protected abstract encode(chunks: AsyncIterable<Uint8Array>, plan: WritePlan<SingleEntry>, store: boolean): AsyncIterable<Uint8Array>;
31
+ /** True when encode() can store data uncompressed. */
32
+ protected readonly canStore: boolean;
33
+ /** Notes what follows the last stream, and what it reads as. */
34
+ protected trailing(at: number): Promise<void>;
35
+ /**
36
+ * Writes the stream: the compressed bytes as they were when the content stayed as it was and was one stream, or the
37
+ * content compressed again. Compressed data that would pass the run's ratio limit is stored instead where the
38
+ * format can store, so the output reads under the same limit.
39
+ */
40
+ protected writeStream(plan: WritePlan<SingleEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
41
+ /** How much of what encode() wrote to keep. */
42
+ protected encodedLength(_source: ByteSource, length: number): Promise<number>;
43
+ protected sizeOf(k: Kept<SingleEntry>): number;
44
+ write(plan: WritePlan<SingleEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
45
+ /** Where the data after the last stream starts, once the entry has been read. */
46
+ protected trailingStart?: number;
47
+ }
48
+ interface GzipHeader {
49
+ flags: number;
50
+ mtime: number;
51
+ os: number;
52
+ name?: string;
53
+ comment?: string;
54
+ /** Offset of the deflate data. */
55
+ dataStart: number;
56
+ }
57
+ export declare class GzipArchive extends Single {
58
+ readonly header: GzipHeader;
59
+ readonly format = "gzip";
60
+ private constructor();
61
+ hasMetadata(): boolean;
62
+ static open(source: ByteSource): Promise<GzipArchive>;
63
+ read(e: SingleEntry, meter: Meter): AsyncGenerator<Uint8Array>;
64
+ /** A member header with the name a rule fixed, and the metadata unless it is stripped. */
65
+ private headerBytes;
66
+ protected readonly canStore = true;
67
+ protected encode(chunks: AsyncIterable<Uint8Array>, plan: WritePlan<SingleEntry>, store: boolean): AsyncGenerator<Uint8Array>;
68
+ /** The header is written again, so the name a rule fixed and the metadata stripped apply also when the data is copied. */
69
+ protected writeStream(plan: WritePlan<SingleEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
70
+ }
71
+ export declare class Bzip2Archive extends Single {
72
+ readonly format = "bzip2";
73
+ private readonly level;
74
+ private constructor();
75
+ static open(source: ByteSource): Promise<Bzip2Archive>;
76
+ read(e: SingleEntry, meter: Meter): AsyncGenerator<Uint8Array>;
77
+ protected encode(chunks: AsyncIterable<Uint8Array>): AsyncIterable<Uint8Array>;
78
+ }
79
+ export declare class ZstdArchive extends Single {
80
+ readonly layout: ZstdLayout;
81
+ readonly format = "zstd";
82
+ private constructor();
83
+ static open(source: ByteSource, deadline?: number): Promise<ZstdArchive>;
84
+ read(e: SingleEntry, meter: Meter): AsyncGenerator<Uint8Array>;
85
+ protected encode(chunks: AsyncIterable<Uint8Array>): AsyncIterable<Uint8Array>;
86
+ /** Node's zstd stream can end with a second, empty frame after the one that holds the data, which is left out. */
87
+ protected encodedLength(source: ByteSource, length: number): Promise<number>;
88
+ }
89
+ export declare class BrotliArchive extends Single {
90
+ readonly format = "brotli";
91
+ static open(source: ByteSource): Promise<BrotliArchive>;
92
+ read(e: SingleEntry, meter: Meter): AsyncGenerator<Uint8Array>;
93
+ protected encode(chunks: AsyncIterable<Uint8Array>): AsyncIterable<Uint8Array>;
94
+ }
95
+ /**
96
+ * True when the content decodes as one Brotli stream, which has no signature: the stream must end where the file
97
+ * does, or decode to more than 32 MiB without an error. A prefix that merely decodes is not enough, since an
98
+ * uncompressed meta-block can take megabytes of any bytes before the next header shows whether they are Brotli.
99
+ */
100
+ export declare function looksLikeBrotli(source: ByteSource): Promise<boolean>;
101
+ export declare const isGzip: (b: Uint8Array) => boolean;
102
+ export declare const isBzip2: (b: Uint8Array) => boolean;
103
+ export {};