@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,373 @@
1
+ "use strict";
2
+ // The formats that came from lzma-defuse when it merged into this package: 7z, xz and .lzma. The readers and writers
3
+ // are lzma-defuse's; these classes put them behind the Archive interface the engine drives, so 7z entries go through
4
+ // the same rules and plugins as ZIP and tar entries.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LzmaArchive = exports.XzArchive = exports.SevenZipArchive = void 0;
7
+ const alone_1 = require("./alone");
8
+ const checks_1 = require("./checks");
9
+ const errors_1 = require("./errors");
10
+ const io_1 = require("./io");
11
+ const lzma_sniff_1 = require("./lzma-sniff");
12
+ const lzma_writers_1 = require("./lzma-writers");
13
+ const model_1 = require("./model");
14
+ const seven_1 = require("./seven");
15
+ const sniff_1 = require("./sniff");
16
+ const types_1 = require("./types");
17
+ const xz_1 = require("./xz");
18
+ /** Notes of one kind as one note with a count, so a file of a million xz streams does not make a million findings. */
19
+ function grouped(notes) {
20
+ const groups = new Map();
21
+ for (const n of notes) {
22
+ const k = `${n.category}/${n.detail}`;
23
+ const g = groups.get(k) ?? [];
24
+ g.push(n);
25
+ groups.set(k, g);
26
+ }
27
+ return [...groups.values()].map(g => (g.length > 1 ? { ...g[0], data: { ...g[0].data, count: g.length } } : g[0]));
28
+ }
29
+ /**
30
+ * Notes a ZIP end record that a ZIP reader would find from the end of the file, and a PDF header in the first 1024
31
+ * bytes, where a PDF reader takes one. `trailingAt` is where data after the format's own structures starts.
32
+ */
33
+ async function otherReaders(reader, start, trailingAt, notes) {
34
+ const size = reader.size;
35
+ const last = await reader.read(Math.max(0, size - 65_557), Math.min(size, 65_557));
36
+ for (let i = last.length - 22; i >= 0; i--) {
37
+ if (last[i] === 0x50 && last[i + 1] === 0x4b && last[i + 2] === 0x05 && last[i + 3] === 0x06 && i + 22 + (last[i + 20] | (last[i + 21] << 8)) === last.length) {
38
+ const at = size - last.length + i;
39
+ notes.push({
40
+ category: types_1.ArchiveCategory.Structure,
41
+ detail: types_1.ArchiveDetail.Polyglot,
42
+ data: { type: 'application/zip', where: trailingAt !== undefined && at >= trailingAt ? 'after the archive' : 'at the end of the file' },
43
+ });
44
+ break;
45
+ }
46
+ }
47
+ const first = await reader.read(start, 1024);
48
+ if (Buffer.from(first.buffer, first.byteOffset, first.byteLength).toString('latin1').includes('%PDF-')) {
49
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Polyglot, data: { type: 'application/pdf', where: 'in the first 1024 bytes' } });
50
+ }
51
+ }
52
+ /** Notes data after the format's own structures, and what it reads as. */
53
+ async function trailing(reader, at, notes) {
54
+ if (at === undefined || at >= reader.size)
55
+ return;
56
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.TrailingBytes, data: { bytes: reader.size - at } });
57
+ const type = (0, sniff_1.sniffType)(await reader.read(at, Math.min(1024, reader.size - at)));
58
+ if (type !== undefined)
59
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Polyglot, data: { type, where: 'after the archive' } });
60
+ }
61
+ /** Copies a range of the source to the sink. */
62
+ async function copyRange(reader, sink, from, to, time) {
63
+ for await (const c of reader.chunks(from, to - from)) {
64
+ time();
65
+ await sink.write(c);
66
+ }
67
+ }
68
+ const KINDS = { file: 'file', dir: 'directory', symlink: 'symlink', device: 'device', fifo: 'fifo', socket: 'socket' };
69
+ /** No system takes a link target longer than PATH_MAX, 4096 bytes on Linux. */
70
+ const MAX_LINK = 4096;
71
+ /** `s` with each lone surrogate, which no file system name holds, read as U+FFFD, as a UTF-16 decoder reads it. */
72
+ function wellFormed(s) {
73
+ let out = '';
74
+ let from = 0;
75
+ for (let i = 0; i < s.length; i++) {
76
+ const c = s.charCodeAt(i);
77
+ if (c < 0xd800 || c > 0xdfff)
78
+ continue;
79
+ const next = s.charCodeAt(i + 1);
80
+ if (c <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) {
81
+ i++;
82
+ continue;
83
+ }
84
+ out += `${s.slice(from, i)}\ufffd`;
85
+ from = i + 1;
86
+ }
87
+ return from === 0 ? s : out + s.slice(from);
88
+ }
89
+ class SevenZipArchive {
90
+ reader;
91
+ seven;
92
+ notes;
93
+ passwords;
94
+ format = '7z';
95
+ single = false;
96
+ nameless;
97
+ lateNotes = [];
98
+ entries;
99
+ contents;
100
+ constructor(reader, seven, notes, passwords, env) {
101
+ this.reader = reader;
102
+ this.seven = seven;
103
+ this.notes = notes;
104
+ this.passwords = passwords;
105
+ this.contents = new seven_1.SevenReader(env, seven);
106
+ this.nameless = seven.entries.length > 0 && seven.entries.every(s => s.info.name === undefined);
107
+ this.entries = seven.entries.map(s => {
108
+ const kind = KINDS[s.kind];
109
+ const raw = s.info.name ?? '';
110
+ const name = wellFormed(raw);
111
+ const entryNotes = name === raw ? [] : [{ category: types_1.ArchiveCategory.Name, detail: types_1.ArchiveDetail.Encoding, data: { name } }];
112
+ const folder = s.folder === undefined ? undefined : seven.streams?.folders[s.folder];
113
+ const encrypted = folder?.coders.some(c => c.method === '06f10701') === true;
114
+ const full = (0, seven_1.unixMode)(s.info.attrib);
115
+ return {
116
+ index: s.index,
117
+ name: kind === 'directory' && name !== '' && !name.endsWith('/') ? `${name}/` : name,
118
+ kind,
119
+ mode: full === undefined ? undefined : full & 0o7777,
120
+ size: s.size,
121
+ compressedSize: 0,
122
+ encryption: encrypted ? { kind: 'aes', bits: 256 } : undefined,
123
+ notes: entryNotes,
124
+ anti: s.info.anti === true,
125
+ seven: s,
126
+ };
127
+ });
128
+ }
129
+ static async open(source, limits, deadline, passwords) {
130
+ const size = await source.size();
131
+ const reader = new io_1.Reader(source, size);
132
+ const found = (0, lzma_sniff_1.detectLzmaFormat)(await reader.read(0, Math.min(size, lzma_sniff_1.SEVENZIP_SEARCH)));
133
+ if (found?.format !== '7z')
134
+ throw new errors_1.DataError('no 7z signature header');
135
+ const env = { source, reader, size, limits, totals: { decoded: 0, compressed: 0 }, deadline, passwords };
136
+ const seven = await (0, seven_1.openSevenZip)(env, found.offset);
137
+ const notes = grouped(seven.notes);
138
+ if (seven.headerEncrypted)
139
+ notes.push({ category: types_1.ArchiveCategory.Encrypted, detail: types_1.ArchiveDetail.Header });
140
+ if (found.offset > 0) {
141
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.LeadingBytes, data: { bytes: found.offset } });
142
+ const type = (0, sniff_1.sniffType)(await reader.read(0, Math.min(found.offset, 1024)));
143
+ if (type !== undefined)
144
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Polyglot, data: { type, where: 'before the archive' } });
145
+ }
146
+ await trailing(reader, seven.trailingAt, notes);
147
+ await otherReaders(reader, found.offset, seven.trailingAt, notes);
148
+ return new SevenZipArchive(reader, seven, notes, passwords, env);
149
+ }
150
+ hasMetadata() {
151
+ return this.seven.entries.some(e => e.info.ctime || e.info.atime || e.info.mtime);
152
+ }
153
+ async unlock(e, password) {
154
+ return this.contents.unlock(e.seven, password);
155
+ }
156
+ async *read(e, meter, password) {
157
+ let total = 0;
158
+ for await (const c of this.contents.content(e.seven, password)) {
159
+ total += c.length;
160
+ meter.add(total);
161
+ yield c;
162
+ }
163
+ }
164
+ /** A symbolic link's target, which a 7z archive keeps as the link's content. */
165
+ async linkTarget(e, meter) {
166
+ // A Windows reparse point holds a reparse buffer, not a path, so where it leads cannot be read as a path.
167
+ if (e.size > MAX_LINK || (0, seven_1.unixMode)(e.seven.info.attrib) === undefined)
168
+ return undefined;
169
+ let password;
170
+ if (e.encryption) {
171
+ for (const p of this.passwords) {
172
+ if (await this.unlock(e, p)) {
173
+ password = p;
174
+ break;
175
+ }
176
+ }
177
+ if (password === undefined)
178
+ return undefined;
179
+ }
180
+ const parts = [];
181
+ for await (const c of this.read(e, meter, password))
182
+ parts.push(c);
183
+ return Buffer.concat(parts).toString('utf8');
184
+ }
185
+ async write(plan, sink, context) {
186
+ const time = () => context.meter().time();
187
+ if (plan.keepLeading && this.seven.offset > 0)
188
+ await copyRange(this.reader, sink, 0, this.seven.offset, time);
189
+ const out = plan.kept.map(k => this.out(k, plan, context));
190
+ await (0, lzma_writers_1.writeSevenZip)(out, sink, context.temp, context.threshold, time, plan.ratioLimit);
191
+ await this.contents.close();
192
+ const end = this.seven.trailingAt;
193
+ if (plan.keepTrailing && end !== undefined)
194
+ await copyRange(this.reader, sink, end, this.reader.size, time);
195
+ }
196
+ /** One kept entry as the writer takes it, with its name and permissions as the rules left them. */
197
+ out(k, plan, context) {
198
+ const info = k.entry.seven.info;
199
+ const dir = k.entry.kind === 'directory';
200
+ let attrib = info.attrib;
201
+ const full = (0, seven_1.unixMode)(attrib);
202
+ if (full !== undefined && attrib !== undefined) {
203
+ const mode = ((full & ~0o7777) | ((k.mode ?? full) & 0o7777)) & 0xffff;
204
+ attrib = ((attrib & 0xffff) | (mode << 16)) >>> 0;
205
+ }
206
+ const size = (0, model_1.sizeOf)(k);
207
+ const content = dir || size === 0 ? undefined : { size, chunks: () => (0, model_1.contentOf)(this, k, context) };
208
+ const name = dir && k.name.endsWith('/') ? k.name.slice(0, -1) : k.name;
209
+ const o = { name: info.name === undefined ? undefined : name, dir, attrib, content };
210
+ if (!plan.stripMetadata) {
211
+ o.ctime = info.ctime;
212
+ o.atime = info.atime;
213
+ o.mtime = info.mtime;
214
+ }
215
+ return o;
216
+ }
217
+ }
218
+ exports.SevenZipArchive = SevenZipArchive;
219
+ // ---------------------------------------------------------------------------------------------------------------
220
+ // xz and .lzma
221
+ /** The engine's meter in the shape the xz and .lzma readers count through: decoded bytes against compressed ones. */
222
+ function counter(meter) {
223
+ let decoded = 0;
224
+ let compressed = 0;
225
+ return {
226
+ get decoded() {
227
+ return decoded;
228
+ },
229
+ get compressed() {
230
+ return compressed;
231
+ },
232
+ tick: () => meter.time(),
233
+ packed: n => {
234
+ compressed += n;
235
+ },
236
+ add: n => {
237
+ decoded += n;
238
+ meter.add(decoded, compressed);
239
+ },
240
+ };
241
+ }
242
+ /** A file that compresses one file: its one entry has no name, and its content goes to the plugins like any other. */
243
+ class LzmaSingle {
244
+ reader;
245
+ limits;
246
+ single = true;
247
+ notes = [];
248
+ lateNotes = [];
249
+ entries;
250
+ /** Set once the entry has been read: where data after the stream starts, when the file goes on. */
251
+ trailingAt;
252
+ /** Set once the entry has been read and nothing about the stream needs writing again: it can be copied as it is. */
253
+ copyable = false;
254
+ constructor(reader, limits) {
255
+ this.reader = reader;
256
+ this.limits = limits;
257
+ this.entries = [{ index: 0, name: '', kind: 'file', size: 0, compressedSize: reader.size, notes: [] }];
258
+ }
259
+ async unlock() {
260
+ return false;
261
+ }
262
+ hasMetadata() {
263
+ return false;
264
+ }
265
+ /** True when encode() can store data uncompressed. */
266
+ canStore = false;
267
+ /** Notes what the read found besides the content, and whether the stream can be copied as it is. */
268
+ async finishRead(e, total, notes, oneStream, trailingAt) {
269
+ e.decoded = total;
270
+ e.size = total;
271
+ this.trailingAt = trailingAt;
272
+ this.lateNotes.length = 0;
273
+ this.lateNotes.push(...grouped(notes));
274
+ await trailing(this.reader, trailingAt, this.lateNotes);
275
+ await otherReaders(this.reader, 0, trailingAt, this.lateNotes);
276
+ this.copyable = oneStream && this.lateNotes.length === 0;
277
+ }
278
+ async write(plan, sink, context) {
279
+ const time = () => context.meter().time();
280
+ const k = plan.kept[0];
281
+ const end = this.trailingAt ?? this.reader.size;
282
+ if (k && !k.replacement && !k.recompress && this.copyable)
283
+ await copyRange(this.reader, sink, 0, end, time);
284
+ else
285
+ await this.writeContent(k, sink, context, time, plan.ratioLimit);
286
+ if (plan.keepTrailing && this.trailingAt !== undefined)
287
+ await copyRange(this.reader, sink, this.trailingAt, this.reader.size, time);
288
+ }
289
+ async writeContent(k, sink, context, time, ratioLimit) {
290
+ if (!k)
291
+ return this.encode(undefined, sink, time, false);
292
+ // The writer reads the content by offset, so content that is not a plugin's copy is decoded again into memory or
293
+ // a temporary file first.
294
+ const decoded = k.replacement ? undefined : new io_1.SpillSink(context.temp, context.threshold);
295
+ const encoded = new io_1.SpillSink(context.temp, context.threshold);
296
+ try {
297
+ if (decoded) {
298
+ for await (const c of (0, model_1.contentOf)(this, k, context))
299
+ await decoded.write(c);
300
+ await decoded.close();
301
+ }
302
+ const content = (decoded ?? k.replacement)?.source();
303
+ if (!content)
304
+ throw new Error('No content to write');
305
+ try {
306
+ await this.encode(content, encoded, time, false);
307
+ await encoded.close();
308
+ const size = await content.size();
309
+ // Content compressed past the ratio limit is stored where the format can store, so the output reads under the
310
+ // same limit.
311
+ if (this.canStore && (0, lzma_writers_1.passesRatio)(size, encoded.length, ratioLimit))
312
+ await this.encode(content, sink, time, true);
313
+ else
314
+ for await (const c of encoded.read())
315
+ await sink.write(c);
316
+ }
317
+ finally {
318
+ await content.close?.();
319
+ }
320
+ }
321
+ finally {
322
+ await encoded.dispose();
323
+ await decoded?.dispose();
324
+ }
325
+ }
326
+ }
327
+ class XzArchive extends LzmaSingle {
328
+ format = 'xz';
329
+ /** The check the rewrite writes: the one every stream used when this package can compute it, else CRC64. */
330
+ checkId = checks_1.CHECK_CRC64;
331
+ static async open(source, limits) {
332
+ return new XzArchive(new io_1.Reader(source, await source.size()), limits);
333
+ }
334
+ async *read(e, meter) {
335
+ const info = { streams: 0, checks: [], notes: [] };
336
+ let total = 0;
337
+ for await (const c of (0, xz_1.readXz)({ reader: this.reader, size: this.reader.size, limits: this.limits, meter: counter(meter) }, info)) {
338
+ total += c.length;
339
+ yield c;
340
+ }
341
+ const notes = [...info.notes];
342
+ if (info.streams > 1)
343
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MultipleStreams, data: { count: info.streams } });
344
+ const checks = new Set(info.checks);
345
+ const only = checks.size === 1 ? [...checks][0] : undefined;
346
+ this.checkId = only !== undefined && lzma_writers_1.WRITABLE_CHECKS.has(only) ? only : checks_1.CHECK_CRC64;
347
+ await this.finishRead(e, total, notes, info.streams === 1, info.trailingAt);
348
+ }
349
+ canStore = true;
350
+ encode(content, sink, time, store) {
351
+ return (0, lzma_writers_1.writeXz)(content, this.checkId, sink, time, store);
352
+ }
353
+ }
354
+ exports.XzArchive = XzArchive;
355
+ class LzmaArchive extends LzmaSingle {
356
+ format = 'lzma';
357
+ static async open(source, limits) {
358
+ return new LzmaArchive(new io_1.Reader(source, await source.size()), limits);
359
+ }
360
+ async *read(e, meter) {
361
+ const info = { notes: [] };
362
+ let total = 0;
363
+ for await (const c of (0, alone_1.readAlone)({ reader: this.reader, size: this.reader.size, limits: this.limits, meter: counter(meter) }, info)) {
364
+ total += c.length;
365
+ yield c;
366
+ }
367
+ await this.finishRead(e, total, info.notes, true, info.trailingAt);
368
+ }
369
+ encode(content, sink, time) {
370
+ return (0, lzma_writers_1.writeAlone)(content, sink, time);
371
+ }
372
+ }
373
+ exports.LzmaArchive = LzmaArchive;
@@ -0,0 +1,20 @@
1
+ export declare const XZ_MAGIC: number[];
2
+ export declare const SEVENZIP_MAGIC: number[];
3
+ /**
4
+ * True when the 13 bytes at `at` are a header liblzma's .lzma decoder takes when it has to guess the format: valid
5
+ * properties with lc + lp at most 4, a dictionary of 2^n or 2^n + 2^(n-1) bytes, a size that is unknown or below 256
6
+ * GiB, and a first byte of 0 in the range coder data that follows.
7
+ */
8
+ export declare function looksLikeLzma(b: Uint8Array, at?: number): boolean;
9
+ /** True when a 7z signature header sits at `at`: the signature, version 0 and a start header whose CRC matches. */
10
+ export declare function sevenZipHeaderAt(b: Uint8Array, at: number): boolean;
11
+ /** How far into a file a 7z signature header is looked for, as self-extracting archives put a program first. */
12
+ export declare const SEVENZIP_SEARCH: number;
13
+ /**
14
+ * The format of a file from its first bytes: xz and 7z by their signatures at the start, then .lzma by its header.
15
+ * `head` should hold the first SEVENZIP_SEARCH bytes when a 7z archive may follow other data.
16
+ */
17
+ export declare function detectLzmaFormat(head: Uint8Array): {
18
+ format: 'xz' | '7z' | 'lzma';
19
+ offset: number;
20
+ } | undefined;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEVENZIP_SEARCH = exports.SEVENZIP_MAGIC = exports.XZ_MAGIC = void 0;
4
+ exports.looksLikeLzma = looksLikeLzma;
5
+ exports.sevenZipHeaderAt = sevenZipHeaderAt;
6
+ exports.detectLzmaFormat = detectLzmaFormat;
7
+ // The signatures of xz, 7z and .lzma files. Copied from lzma-defuse src/sniff.ts when it merged into this package.
8
+ const crc32_1 = require("./crc32");
9
+ exports.XZ_MAGIC = [0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00];
10
+ exports.SEVENZIP_MAGIC = [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c];
11
+ const startsWith = (b, at, bytes) => b.length >= at + bytes.length && bytes.every((x, i) => b[at + i] === x);
12
+ /**
13
+ * True when the 13 bytes at `at` are a header liblzma's .lzma decoder takes when it has to guess the format: valid
14
+ * properties with lc + lp at most 4, a dictionary of 2^n or 2^n + 2^(n-1) bytes, a size that is unknown or below 256
15
+ * GiB, and a first byte of 0 in the range coder data that follows.
16
+ */
17
+ function looksLikeLzma(b, at = 0) {
18
+ if (b.length < at + 14)
19
+ return false;
20
+ const props = b[at];
21
+ if (props >= 9 * 5 * 5)
22
+ return false;
23
+ const lc = props % 9;
24
+ const lp = Math.floor(props / 9) % 5;
25
+ if (lc + lp > 4)
26
+ return false;
27
+ const view = new DataView(b.buffer, b.byteOffset + at, 13);
28
+ const dict = view.getUint32(1, true);
29
+ if (dict !== 0xffffffff) {
30
+ // 2^n or 2^n + 2^(n-1): the highest set bit, plus at most the one below it.
31
+ let d = dict - 1;
32
+ d |= d >>> 2;
33
+ d |= d >>> 3;
34
+ d |= d >>> 4;
35
+ d |= d >>> 8;
36
+ d |= d >>> 16;
37
+ if ((d >>> 0) + 1 !== dict && dict !== 0)
38
+ return false;
39
+ }
40
+ const low = view.getUint32(5, true);
41
+ const high = view.getUint32(9, true);
42
+ if (!(low === 0xffffffff && high === 0xffffffff) && high >= 1 << 6)
43
+ return false;
44
+ return b[at + 13] === 0x00;
45
+ }
46
+ /** True when a 7z signature header sits at `at`: the signature, version 0 and a start header whose CRC matches. */
47
+ function sevenZipHeaderAt(b, at) {
48
+ if (!startsWith(b, at, exports.SEVENZIP_MAGIC) || b.length < at + 32 || b[at + 6] !== 0)
49
+ return false;
50
+ const stored = new DataView(b.buffer, b.byteOffset + at + 8, 4).getUint32(0, true);
51
+ return (0, crc32_1.crc32)(b.subarray(at + 12, at + 32)) === stored;
52
+ }
53
+ /** How far into a file a 7z signature header is looked for, as self-extracting archives put a program first. */
54
+ exports.SEVENZIP_SEARCH = 1024 * 1024;
55
+ /**
56
+ * The format of a file from its first bytes: xz and 7z by their signatures at the start, then .lzma by its header.
57
+ * `head` should hold the first SEVENZIP_SEARCH bytes when a 7z archive may follow other data.
58
+ */
59
+ function detectLzmaFormat(head) {
60
+ if (startsWith(head, 0, exports.XZ_MAGIC))
61
+ return { format: 'xz', offset: 0 };
62
+ if (startsWith(head, 0, exports.SEVENZIP_MAGIC))
63
+ return { format: '7z', offset: 0 };
64
+ if (looksLikeLzma(head))
65
+ return { format: 'lzma', offset: 0 };
66
+ // A 7z archive after other data, such as the program of a self-extracting archive.
67
+ const limit = Math.min(head.length, exports.SEVENZIP_SEARCH) - 32;
68
+ for (let at = head.indexOf(0x37, 1); at >= 0 && at <= limit; at = head.indexOf(0x37, at + 1)) {
69
+ if (sevenZipHeaderAt(head, at))
70
+ return { format: '7z', offset: at };
71
+ }
72
+ return undefined;
73
+ }
@@ -0,0 +1,35 @@
1
+ import { type TempDir } from './io';
2
+ import type { ByteSink, ByteSource } from './types';
3
+ /** True when data compressed this far would pass the ratio limit, so the writer stores it instead. */
4
+ export declare const passesRatio: (size: number, packed: number, ratioLimit: number) => boolean;
5
+ /** Content to write: its size, known before it is read, and its bytes, read once. */
6
+ export interface Content {
7
+ size: number;
8
+ chunks(): AsyncIterable<Uint8Array>;
9
+ }
10
+ /** The check IDs this package writes, the ones it can compute. */
11
+ export declare const WRITABLE_CHECKS: Set<number>;
12
+ /**
13
+ * Writes `content` as a single-stream xz file with one LZMA2 block, or no block when it is empty. The block's chunks
14
+ * come from Lzma2ChunkWriter. Chunks that would start within the first 1024 bytes of the file stay compressed.
15
+ */
16
+ export declare function writeXz(content: ByteSource | undefined, checkId: number, sink: ByteSink, time: () => void, store?: boolean): Promise<void>;
17
+ /** Writes `content` as an .lzma file with its size in the header and no end marker. */
18
+ export declare function writeAlone(content: ByteSource | undefined, sink: ByteSink, time: () => void): Promise<void>;
19
+ /** One entry of a 7z archive to write. */
20
+ export interface SevenOut {
21
+ /** Absent for an entry of an archive that stores no names. */
22
+ name?: string;
23
+ dir: boolean;
24
+ attrib?: number;
25
+ ctime?: Uint8Array;
26
+ atime?: Uint8Array;
27
+ mtime?: Uint8Array;
28
+ /** The content of a file. Empty or absent for an empty file. */
29
+ content?: Content;
30
+ }
31
+ /**
32
+ * Writes a 7z archive: one LZMA folder per file with content, compressed by lzma1's encoder at its preset 1, a plain
33
+ * header with the names, empty files and directories, attributes and the times given, and CRCs of every file.
34
+ */
35
+ export declare function writeSevenZip(entries: SevenOut[], sink: ByteSink, temp: TempDir, memoryThreshold: number, time: () => void, ratioLimit?: number): Promise<void>;