@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/xz.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Reader } from './io';
|
|
2
|
+
import type { Note } from './model';
|
|
3
|
+
import type { Limits, StreamCounter as Meter } from './stream-meter';
|
|
4
|
+
/** What reading an xz file found out, beyond its content. */
|
|
5
|
+
export interface XzInfo {
|
|
6
|
+
streams: number;
|
|
7
|
+
/** The check ID of each stream. */
|
|
8
|
+
checks: number[];
|
|
9
|
+
notes: Note[];
|
|
10
|
+
/** Offset of the first byte that is not part of a stream or its padding, when the file goes on past the last stream. */
|
|
11
|
+
trailingAt?: number;
|
|
12
|
+
}
|
|
13
|
+
/** What reading an xz file needs from the run. */
|
|
14
|
+
export interface XzEnv {
|
|
15
|
+
reader: Reader;
|
|
16
|
+
size: number;
|
|
17
|
+
limits: Limits;
|
|
18
|
+
meter: Meter;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decodes an xz file and yields its content: every block of every stream, in order. Stream padding, the index and the
|
|
22
|
+
* footers are checked against what the blocks held. What can be fixed by writing the file again goes to `info.notes`;
|
|
23
|
+
* what cannot throws.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readXz(env: XzEnv, info: XzInfo): AsyncGenerator<Uint8Array>;
|
package/dist/xz.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readXz = readXz;
|
|
4
|
+
const checks_1 = require("./checks");
|
|
5
|
+
const coders_1 = require("./coders");
|
|
6
|
+
const crc32_1 = require("./crc32");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const filters_1 = require("./filters");
|
|
9
|
+
const lzma_sniff_1 = require("./lzma-sniff");
|
|
10
|
+
const lzma1_stream_1 = require("./lzma1-stream");
|
|
11
|
+
const types_1 = require("./types");
|
|
12
|
+
const BRANCH_IDS = { 4: 'x86', 5: 'powerpc', 6: 'ia64', 7: 'arm', 8: 'armthumb', 9: 'sparc', 10: 'arm64', 11: 'riscv' };
|
|
13
|
+
const FILTER_LZMA2 = 0x21;
|
|
14
|
+
const FILTER_DELTA = 0x03;
|
|
15
|
+
/** A variable-length integer of xz: 7 bits per byte, low first, at most 9 bytes and no unneeded zero byte at the end. */
|
|
16
|
+
function vli(b, at) {
|
|
17
|
+
let value = 0;
|
|
18
|
+
for (let i = 0; i < 9; i++) {
|
|
19
|
+
if (at + i >= b.length)
|
|
20
|
+
throw new errors_1.ParseError('a number runs past its field');
|
|
21
|
+
const byte = b[at + i];
|
|
22
|
+
value += (byte & 0x7f) * 2 ** (7 * i);
|
|
23
|
+
if ((byte & 0x80) === 0) {
|
|
24
|
+
if (byte === 0 && i > 0)
|
|
25
|
+
throw new errors_1.ParseError('a number is written with an unneeded zero byte');
|
|
26
|
+
return { value, next: at + i + 1 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw new errors_1.ParseError('a number is longer than nine bytes');
|
|
30
|
+
}
|
|
31
|
+
/** Reads bytes one at a time from a reader. */
|
|
32
|
+
class ByteStream {
|
|
33
|
+
reader;
|
|
34
|
+
pos;
|
|
35
|
+
buf = new Uint8Array(0);
|
|
36
|
+
bufAt = 0;
|
|
37
|
+
constructor(reader, pos) {
|
|
38
|
+
this.reader = reader;
|
|
39
|
+
this.pos = pos;
|
|
40
|
+
}
|
|
41
|
+
async byte() {
|
|
42
|
+
if (this.pos >= this.bufAt + this.buf.length) {
|
|
43
|
+
this.buf = await this.reader.read(this.pos, 65536);
|
|
44
|
+
this.bufAt = this.pos;
|
|
45
|
+
if (this.buf.length === 0)
|
|
46
|
+
throw new errors_1.DataError('the index ends past the end of the file', true);
|
|
47
|
+
}
|
|
48
|
+
const b = this.buf[this.pos - this.bufAt];
|
|
49
|
+
this.pos++;
|
|
50
|
+
return b;
|
|
51
|
+
}
|
|
52
|
+
async vli() {
|
|
53
|
+
let value = 0;
|
|
54
|
+
for (let i = 0; i < 9; i++) {
|
|
55
|
+
const byte = await this.byte();
|
|
56
|
+
value += (byte & 0x7f) * 2 ** (7 * i);
|
|
57
|
+
if ((byte & 0x80) === 0) {
|
|
58
|
+
if (byte === 0 && i > 0)
|
|
59
|
+
throw new errors_1.ParseError('a number is written with an unneeded zero byte');
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new errors_1.ParseError('a number is longer than nine bytes');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function exact(reader, pos, n, what) {
|
|
67
|
+
const b = await reader.read(pos, n);
|
|
68
|
+
if (b.length < n)
|
|
69
|
+
throw new errors_1.DataError(`the ${what} ends past the end of the file`, true);
|
|
70
|
+
return b;
|
|
71
|
+
}
|
|
72
|
+
const u32 = (b, at) => (b[at] | (b[at + 1] << 8) | (b[at + 2] << 16) | (b[at + 3] << 24)) >>> 0;
|
|
73
|
+
const isMagic = (b) => b.length >= 6 && lzma_sniff_1.XZ_MAGIC.every((x, i) => b[i] === x);
|
|
74
|
+
function parseBlockHeader(h) {
|
|
75
|
+
const size = h.length;
|
|
76
|
+
if ((0, crc32_1.crc32)(h.subarray(0, size - 4)) !== u32(h, size - 4))
|
|
77
|
+
throw new errors_1.CheckError('the block header CRC32 does not match', 'block header CRC32');
|
|
78
|
+
const flags = h[1];
|
|
79
|
+
if (flags & 0x3c)
|
|
80
|
+
throw new errors_1.UnsupportedError('reserved block flags', false, { reason: 'reserved block header flags' });
|
|
81
|
+
const end = size - 4;
|
|
82
|
+
let q = 2;
|
|
83
|
+
let compressed;
|
|
84
|
+
let uncompressed;
|
|
85
|
+
const field = (what) => {
|
|
86
|
+
if (q >= end)
|
|
87
|
+
throw new errors_1.ParseError(`the block header ends in its ${what}`);
|
|
88
|
+
const r = vli(h.subarray(0, end), q);
|
|
89
|
+
q = r.next;
|
|
90
|
+
return r.value;
|
|
91
|
+
};
|
|
92
|
+
if (flags & 0x40) {
|
|
93
|
+
compressed = field('compressed size');
|
|
94
|
+
if (compressed === 0)
|
|
95
|
+
throw new errors_1.ParseError('a block declares a compressed size of zero');
|
|
96
|
+
}
|
|
97
|
+
if (flags & 0x80)
|
|
98
|
+
uncompressed = field('uncompressed size');
|
|
99
|
+
const raw = [];
|
|
100
|
+
for (let k = 0; k < (flags & 3) + 1; k++) {
|
|
101
|
+
const id = field('filter flags');
|
|
102
|
+
const propsSize = field('filter flags');
|
|
103
|
+
if (q + propsSize > end)
|
|
104
|
+
throw new errors_1.ParseError('the block header ends in its filter flags');
|
|
105
|
+
raw.push({ id, props: h.slice(q, q + propsSize) });
|
|
106
|
+
q += propsSize;
|
|
107
|
+
}
|
|
108
|
+
for (let i = q; i < end; i++) {
|
|
109
|
+
if (h[i] !== 0)
|
|
110
|
+
throw new errors_1.UnsupportedError('block header padding that is not zero', false, { reason: 'a block header field this package does not know' });
|
|
111
|
+
}
|
|
112
|
+
const last = raw[raw.length - 1];
|
|
113
|
+
const known = (id) => id === FILTER_LZMA2 || id === FILTER_DELTA || BRANCH_IDS[id] !== undefined;
|
|
114
|
+
for (const f of raw) {
|
|
115
|
+
if (!known(f.id))
|
|
116
|
+
throw new errors_1.UnsupportedError(`xz filter 0x${f.id.toString(16)}`, true, { method: `filter 0x${f.id.toString(16)}` });
|
|
117
|
+
}
|
|
118
|
+
if (last.id !== FILTER_LZMA2)
|
|
119
|
+
throw new errors_1.UnsupportedError('a filter chain that does not end in LZMA2', false, { reason: 'a filter chain that does not end in LZMA2' });
|
|
120
|
+
if (raw.slice(0, -1).some(f => f.id === FILTER_LZMA2))
|
|
121
|
+
throw new errors_1.UnsupportedError('LZMA2 before the last filter', false, { reason: 'LZMA2 before the last filter' });
|
|
122
|
+
const dict = last.props.length === 1 ? (0, coders_1.lzma2DictSize)(last.props[0]) : undefined;
|
|
123
|
+
if (dict === undefined)
|
|
124
|
+
throw new errors_1.ParseError('invalid LZMA2 properties');
|
|
125
|
+
const filters = raw.slice(0, -1).map(f => {
|
|
126
|
+
if (f.id === FILTER_DELTA) {
|
|
127
|
+
if (f.props.length !== 1)
|
|
128
|
+
throw new errors_1.ParseError('invalid delta filter properties');
|
|
129
|
+
return { kind: 'delta', distance: f.props[0] + 1 };
|
|
130
|
+
}
|
|
131
|
+
if (f.props.length !== 0 && f.props.length !== 4)
|
|
132
|
+
throw new errors_1.ParseError('invalid branch filter properties');
|
|
133
|
+
return { kind: BRANCH_IDS[f.id], start: f.props.length === 4 ? u32(f.props, 0) : 0 };
|
|
134
|
+
});
|
|
135
|
+
return { size, compressed, uncompressed, dict, filters };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Decodes an xz file and yields its content: every block of every stream, in order. Stream padding, the index and the
|
|
139
|
+
* footers are checked against what the blocks held. What can be fixed by writing the file again goes to `info.notes`;
|
|
140
|
+
* what cannot throws.
|
|
141
|
+
*/
|
|
142
|
+
async function* readXz(env, info) {
|
|
143
|
+
const { reader, size, meter } = env;
|
|
144
|
+
let pos = 0;
|
|
145
|
+
for (;;) {
|
|
146
|
+
// Stream header. Bytes after a stream that do not start the magic are trailing data, however few; bytes that do
|
|
147
|
+
// start it are a stream cut short.
|
|
148
|
+
const header = await reader.read(pos, 12);
|
|
149
|
+
const magic = header.length >= 6 ? isMagic(header) : header.every((b, i) => b === lzma_sniff_1.XZ_MAGIC[i]);
|
|
150
|
+
if (!magic) {
|
|
151
|
+
if (info.streams === 0)
|
|
152
|
+
throw new errors_1.ParseError('the file does not start with an xz stream');
|
|
153
|
+
info.trailingAt = pos;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (header.length < 12)
|
|
157
|
+
throw new errors_1.DataError('the stream header ends past the end of the file', true);
|
|
158
|
+
const location = info.streams === 0 ? undefined : `stream ${info.streams + 1}`;
|
|
159
|
+
if ((0, crc32_1.crc32)(header.subarray(6, 8)) !== u32(header, 8))
|
|
160
|
+
throw new errors_1.CheckError('the stream header CRC32 does not match', 'stream header CRC32');
|
|
161
|
+
if (header[6] !== 0 || (header[7] & 0xf0) !== 0)
|
|
162
|
+
throw new errors_1.UnsupportedError('reserved stream flags', false, { reason: 'reserved stream flags' });
|
|
163
|
+
const checkId = header[7] & 0x0f;
|
|
164
|
+
const cSize = (0, checks_1.checkSize)(checkId);
|
|
165
|
+
if (checkId === checks_1.CHECK_NONE)
|
|
166
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.NoCheck, location });
|
|
167
|
+
else if (checkId !== checks_1.CHECK_CRC32 && checkId !== checks_1.CHECK_CRC64 && checkId !== checks_1.CHECK_SHA256) {
|
|
168
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.UnknownCheck, location, data: { check: (0, checks_1.checkName)(checkId) } });
|
|
169
|
+
}
|
|
170
|
+
info.checks.push(checkId);
|
|
171
|
+
info.streams++;
|
|
172
|
+
meter.tick();
|
|
173
|
+
pos += 12;
|
|
174
|
+
// Blocks
|
|
175
|
+
const blocks = [];
|
|
176
|
+
for (;;) {
|
|
177
|
+
const first = await exact(reader, pos, 1, 'block header');
|
|
178
|
+
if (first[0] === 0x00)
|
|
179
|
+
break;
|
|
180
|
+
const blockLoc = `${location ? `${location}, ` : ''}block ${blocks.length + 1}`;
|
|
181
|
+
const headerSize = (first[0] + 1) * 4;
|
|
182
|
+
const h = await exact(reader, pos, headerSize, 'block header');
|
|
183
|
+
const bh = parseBlockHeader(h);
|
|
184
|
+
if (bh.uncompressed !== undefined && bh.uncompressed > env.limits.decompressedBytes) {
|
|
185
|
+
throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.DecompressedSize, 'Decompressed size limit exceeded', { limit: env.limits.decompressedBytes, declared: bh.uncompressed }, blockLoc);
|
|
186
|
+
}
|
|
187
|
+
const dictNeed = (0, lzma1_stream_1.effectiveDictSize)(bh.dict, bh.uncompressed);
|
|
188
|
+
if (dictNeed > env.limits.dictionaryBytes) {
|
|
189
|
+
throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.DictionarySize, 'Dictionary size limit exceeded', { limit: env.limits.dictionaryBytes, dictionary: dictNeed }, blockLoc);
|
|
190
|
+
}
|
|
191
|
+
const dataStart = pos + headerSize;
|
|
192
|
+
const dataEnd = bh.compressed !== undefined ? dataStart + bh.compressed : size;
|
|
193
|
+
if (dataEnd > size)
|
|
194
|
+
throw new errors_1.DataError('a block declares more compressed data than the file holds', true);
|
|
195
|
+
// Decoding runs LZMA2 first, then the other filters from the last listed to the first.
|
|
196
|
+
const transforms = bh.filters.map(f => (f.kind === 'delta' ? new filters_1.DeltaStream(f.distance) : new filters_1.SimpleFilterStream(f.kind, f.start))).reverse();
|
|
197
|
+
const check = (0, checks_1.makeCheck)(checkId);
|
|
198
|
+
const feeder = new coders_1.Lzma2Feeder(bh.dict);
|
|
199
|
+
let uncompressed = 0;
|
|
200
|
+
const emit = (chunk, from) => {
|
|
201
|
+
let d = chunk;
|
|
202
|
+
for (let k = from; k < transforms.length && d.length; k++)
|
|
203
|
+
d = transforms[k].push(d);
|
|
204
|
+
return d;
|
|
205
|
+
};
|
|
206
|
+
let p = dataStart;
|
|
207
|
+
while (!feeder.ended) {
|
|
208
|
+
if (p >= dataEnd)
|
|
209
|
+
throw new errors_1.DataError(bh.compressed !== undefined ? 'the LZMA2 data does not end within the compressed size its block declares' : 'the LZMA2 data ends past the end of the file', true);
|
|
210
|
+
const chunk = await reader.read(p, Math.min(65536, dataEnd - p));
|
|
211
|
+
let used = 0;
|
|
212
|
+
while (used < chunk.length && !feeder.ended) {
|
|
213
|
+
const n = feeder.push(chunk.subarray(used, used + 4096));
|
|
214
|
+
used += n;
|
|
215
|
+
meter.packed(n);
|
|
216
|
+
for (const out of feeder.take()) {
|
|
217
|
+
const d = emit(out, 0);
|
|
218
|
+
if (!d.length)
|
|
219
|
+
continue;
|
|
220
|
+
check?.update(d);
|
|
221
|
+
uncompressed += d.length;
|
|
222
|
+
meter.add(d.length);
|
|
223
|
+
yield d;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
p += used;
|
|
227
|
+
}
|
|
228
|
+
for (let k = 0; k < transforms.length; k++) {
|
|
229
|
+
const d = emit(transforms[k].end(), k + 1);
|
|
230
|
+
if (!d.length)
|
|
231
|
+
continue;
|
|
232
|
+
check?.update(d);
|
|
233
|
+
uncompressed += d.length;
|
|
234
|
+
meter.add(d.length);
|
|
235
|
+
yield d;
|
|
236
|
+
}
|
|
237
|
+
const compressed = p - dataStart;
|
|
238
|
+
if (bh.compressed !== undefined && bh.compressed !== compressed) {
|
|
239
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.SizeMismatch, location: blockLoc, data: { reason: 'compressed size of a block', declared: bh.compressed, actual: compressed } });
|
|
240
|
+
}
|
|
241
|
+
if (bh.uncompressed !== undefined && bh.uncompressed !== uncompressed) {
|
|
242
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.SizeMismatch, location: blockLoc, data: { reason: 'uncompressed size of a block', declared: bh.uncompressed, actual: uncompressed } });
|
|
243
|
+
}
|
|
244
|
+
const padding = (4 - ((headerSize + compressed) % 4)) % 4;
|
|
245
|
+
const tail = await exact(reader, p, padding + cSize, 'block');
|
|
246
|
+
if (tail.subarray(0, padding).some(b => b !== 0))
|
|
247
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.BadPadding, location: blockLoc, data: { reason: 'block padding that is not zero' } });
|
|
248
|
+
if (check) {
|
|
249
|
+
const want = check.digest();
|
|
250
|
+
const got = tail.subarray(padding);
|
|
251
|
+
if (want.some((b, i) => b !== got[i]))
|
|
252
|
+
throw new errors_1.CheckError(`the ${(0, checks_1.checkName)(checkId)} of a block does not match`, (0, checks_1.checkName)(checkId));
|
|
253
|
+
}
|
|
254
|
+
blocks.push({ unpadded: headerSize + compressed + cSize, uncompressed });
|
|
255
|
+
pos = p + padding + cSize;
|
|
256
|
+
}
|
|
257
|
+
// Index
|
|
258
|
+
const index = new ByteStream(reader, pos);
|
|
259
|
+
await index.byte();
|
|
260
|
+
const count = await index.vli();
|
|
261
|
+
if (count * 2 > size - index.pos)
|
|
262
|
+
throw new errors_1.DataError('the index lists more blocks than the file can hold', true);
|
|
263
|
+
let mismatch;
|
|
264
|
+
if (count !== blocks.length)
|
|
265
|
+
mismatch = 'the number of blocks';
|
|
266
|
+
for (let i = 0; i < count; i++) {
|
|
267
|
+
const unpadded = await index.vli();
|
|
268
|
+
const uncompressed = await index.vli();
|
|
269
|
+
if (unpadded === 0)
|
|
270
|
+
mismatch ??= 'an unpadded size of zero';
|
|
271
|
+
const b = blocks[i];
|
|
272
|
+
if (b && b.unpadded !== unpadded)
|
|
273
|
+
mismatch ??= 'the unpadded size of a block';
|
|
274
|
+
if (b && b.uncompressed !== uncompressed)
|
|
275
|
+
mismatch ??= 'the uncompressed size of a block';
|
|
276
|
+
}
|
|
277
|
+
let padBad = false;
|
|
278
|
+
while ((index.pos - pos) % 4 !== 0)
|
|
279
|
+
if ((await index.byte()) !== 0)
|
|
280
|
+
padBad = true;
|
|
281
|
+
let indexCrc = 0;
|
|
282
|
+
for (let at = pos; at < index.pos; at += 65536)
|
|
283
|
+
indexCrc = (0, crc32_1.crc32)(await exact(reader, at, Math.min(65536, index.pos - at), 'index'), indexCrc);
|
|
284
|
+
const stored = await exact(reader, index.pos, 4, 'index');
|
|
285
|
+
if (u32(stored, 0) !== indexCrc)
|
|
286
|
+
throw new errors_1.CheckError('the index CRC32 does not match', 'index CRC32');
|
|
287
|
+
if (padBad)
|
|
288
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.BadPadding, location, data: { reason: 'index padding that is not zero' } });
|
|
289
|
+
if (mismatch)
|
|
290
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.IndexMismatch, location, data: { reason: mismatch } });
|
|
291
|
+
const indexSize = index.pos + 4 - pos;
|
|
292
|
+
pos += indexSize;
|
|
293
|
+
// Stream footer
|
|
294
|
+
const footer = await exact(reader, pos, 12, 'stream footer');
|
|
295
|
+
if (footer[10] !== 0x59 || footer[11] !== 0x5a)
|
|
296
|
+
throw new errors_1.ParseError('the stream footer has no magic bytes');
|
|
297
|
+
if ((0, crc32_1.crc32)(footer.subarray(4, 10)) !== u32(footer, 0))
|
|
298
|
+
throw new errors_1.CheckError('the stream footer CRC32 does not match', 'stream footer CRC32');
|
|
299
|
+
if ((u32(footer, 4) + 1) * 4 !== indexSize)
|
|
300
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.IndexMismatch, location, data: { reason: 'the index size in the stream footer' } });
|
|
301
|
+
if (footer[8] !== header[6] || footer[9] !== header[7])
|
|
302
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.HeaderMismatch, location });
|
|
303
|
+
pos += 12;
|
|
304
|
+
// Stream padding, then the next stream or the end.
|
|
305
|
+
let zeros = 0;
|
|
306
|
+
for (;;) {
|
|
307
|
+
const b = await reader.read(pos + zeros, 4096);
|
|
308
|
+
let k = 0;
|
|
309
|
+
while (k < b.length && b[k] === 0)
|
|
310
|
+
k++;
|
|
311
|
+
zeros += k;
|
|
312
|
+
if (k < b.length || b.length === 0)
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
if (zeros > 0) {
|
|
316
|
+
const next = await reader.read(pos + zeros, 6);
|
|
317
|
+
const atEnd = pos + zeros === size;
|
|
318
|
+
if (atEnd || isMagic(next)) {
|
|
319
|
+
if (zeros % 4 === 0)
|
|
320
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.StreamPadding, data: { bytes: zeros } });
|
|
321
|
+
else
|
|
322
|
+
info.notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.BadPadding, data: { reason: 'stream padding that is not a multiple of four bytes', bytes: zeros } });
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
info.trailingAt = pos;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
pos += zeros;
|
|
330
|
+
if (pos >= size)
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Consumed } from './codecs';
|
|
2
|
+
import { type TempDir } from './io';
|
|
3
|
+
import type { Meter } from './model';
|
|
4
|
+
import type { Limits } from './stream-meter';
|
|
5
|
+
/**
|
|
6
|
+
* Decodes a method 14 entry: the LZMA header APPNOTE 5.8.8 gives, then raw LZMA data of the entry's size, which may
|
|
7
|
+
* end with an end marker. `consumed.bytes` follows the input the data took, so the caller can see bytes after it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function zipLzma(chunks: AsyncIterable<Uint8Array>, size: number, consumed: Consumed, dictionaryBytes: number): AsyncGenerator<Uint8Array>;
|
|
10
|
+
/**
|
|
11
|
+
* Decodes a method 95 entry, which holds a whole xz file. The xz reader reads by offset, so the entry's compressed
|
|
12
|
+
* bytes go to memory or a temporary file first. A file that goes on past its streams, or that has notes the xz reader
|
|
13
|
+
* makes, such as bad padding, does not decode: an entry is one xz file and nothing else.
|
|
14
|
+
*/
|
|
15
|
+
export declare function zipXz(chunks: AsyncIterable<Uint8Array>, consumed: Consumed, meter: Meter, temp: TempDir, threshold: number, limits: Limits): AsyncGenerator<Uint8Array>;
|
package/dist/zip-lzma.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.zipLzma = zipLzma;
|
|
4
|
+
exports.zipXz = zipXz;
|
|
5
|
+
const coders_1 = require("./coders");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const io_1 = require("./io");
|
|
8
|
+
const lzma1_stream_1 = require("./lzma1-stream");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const xz_1 = require("./xz");
|
|
11
|
+
/** Input handed to the LZMA decoder per step, so one step decodes a bounded amount. */
|
|
12
|
+
const SLICE = 1024;
|
|
13
|
+
/** APPNOTE 5.8.8: two version bytes, a two-byte properties size, then the five bytes of LZMA properties. */
|
|
14
|
+
const HEADER = 9;
|
|
15
|
+
const concat = (a, b) => {
|
|
16
|
+
const out = new Uint8Array(a.length + b.length);
|
|
17
|
+
out.set(a, 0);
|
|
18
|
+
out.set(b, a.length);
|
|
19
|
+
return out;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decodes a method 14 entry: the LZMA header APPNOTE 5.8.8 gives, then raw LZMA data of the entry's size, which may
|
|
23
|
+
* end with an end marker. `consumed.bytes` follows the input the data took, so the caller can see bytes after it.
|
|
24
|
+
*/
|
|
25
|
+
async function* zipLzma(chunks, size, consumed, dictionaryBytes) {
|
|
26
|
+
const it = chunks[Symbol.asyncIterator]();
|
|
27
|
+
let head = new Uint8Array(0);
|
|
28
|
+
try {
|
|
29
|
+
while (head.length < HEADER) {
|
|
30
|
+
const r = await it.next();
|
|
31
|
+
if (r.done)
|
|
32
|
+
throw new errors_1.DataError('the LZMA header is cut short', true);
|
|
33
|
+
head = concat(head, r.value);
|
|
34
|
+
}
|
|
35
|
+
const propsSize = head[2] | (head[3] << 8);
|
|
36
|
+
if (propsSize !== 5)
|
|
37
|
+
throw new errors_1.DataError(`LZMA properties of ${propsSize} bytes, not 5`);
|
|
38
|
+
const dict = (head[5] | (head[6] << 8) | (head[7] << 16) | (head[8] << 24)) >>> 0;
|
|
39
|
+
const need = (0, lzma1_stream_1.effectiveDictSize)(dict, size);
|
|
40
|
+
if (need > dictionaryBytes)
|
|
41
|
+
throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.DictionarySize, 'Dictionary size limit exceeded', { limit: dictionaryBytes, dictionary: need });
|
|
42
|
+
const out = [];
|
|
43
|
+
let stream;
|
|
44
|
+
try {
|
|
45
|
+
stream = new lzma1_stream_1.Lzma1Stream((0, lzma1_stream_1.decodeProps)(head[4], dict), size, c => out.push(c.slice()), true);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
throw (0, coders_1.lzmaError)(e);
|
|
49
|
+
}
|
|
50
|
+
let fed = HEADER;
|
|
51
|
+
consumed.bytes = HEADER;
|
|
52
|
+
let rest = head.subarray(HEADER);
|
|
53
|
+
for (;;) {
|
|
54
|
+
for (let p = 0; p < rest.length; p += SLICE) {
|
|
55
|
+
const piece = rest.subarray(p, p + SLICE);
|
|
56
|
+
try {
|
|
57
|
+
stream.write(piece);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
throw (0, coders_1.lzmaError)(e);
|
|
61
|
+
}
|
|
62
|
+
fed += piece.length;
|
|
63
|
+
if (!stream.done)
|
|
64
|
+
consumed.bytes = fed;
|
|
65
|
+
yield* out.splice(0);
|
|
66
|
+
}
|
|
67
|
+
const r = await it.next();
|
|
68
|
+
if (r.done)
|
|
69
|
+
break;
|
|
70
|
+
rest = r.value;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
stream.end();
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
throw (0, coders_1.lzmaError)(e);
|
|
77
|
+
}
|
|
78
|
+
yield* out.splice(0);
|
|
79
|
+
consumed.bytes = HEADER + stream.consumed;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await it.return?.();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The engine's meter in the shape the xz reader counts through. */
|
|
86
|
+
function counter(meter) {
|
|
87
|
+
let decoded = 0;
|
|
88
|
+
let compressed = 0;
|
|
89
|
+
return {
|
|
90
|
+
get decoded() {
|
|
91
|
+
return decoded;
|
|
92
|
+
},
|
|
93
|
+
get compressed() {
|
|
94
|
+
return compressed;
|
|
95
|
+
},
|
|
96
|
+
tick: () => meter.time(),
|
|
97
|
+
packed: n => {
|
|
98
|
+
compressed += n;
|
|
99
|
+
},
|
|
100
|
+
add: n => {
|
|
101
|
+
decoded += n;
|
|
102
|
+
meter.add(decoded, compressed);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Decodes a method 95 entry, which holds a whole xz file. The xz reader reads by offset, so the entry's compressed
|
|
108
|
+
* bytes go to memory or a temporary file first. A file that goes on past its streams, or that has notes the xz reader
|
|
109
|
+
* makes, such as bad padding, does not decode: an entry is one xz file and nothing else.
|
|
110
|
+
*/
|
|
111
|
+
async function* zipXz(chunks, consumed, meter, temp, threshold, limits) {
|
|
112
|
+
const spill = new io_1.SpillSink(temp, threshold);
|
|
113
|
+
try {
|
|
114
|
+
for await (const c of chunks) {
|
|
115
|
+
meter.time();
|
|
116
|
+
await spill.write(c);
|
|
117
|
+
}
|
|
118
|
+
await spill.close();
|
|
119
|
+
const source = spill.source();
|
|
120
|
+
try {
|
|
121
|
+
const info = { streams: 0, checks: [], notes: [] };
|
|
122
|
+
yield* (0, xz_1.readXz)({ reader: new io_1.Reader(source, spill.length), size: spill.length, limits, meter: counter(meter) }, info);
|
|
123
|
+
const end = info.trailingAt ?? spill.length;
|
|
124
|
+
consumed.bytes = end;
|
|
125
|
+
// Padding between streams and a stream with no check are well formed; the entry's CRC covers the data.
|
|
126
|
+
const faults = info.notes.filter(n => n.detail !== types_1.ArchiveDetail.StreamPadding && n.detail !== types_1.ArchiveDetail.NoCheck);
|
|
127
|
+
if (faults.length)
|
|
128
|
+
throw new errors_1.DataError(`the xz data is not well formed: ${faults.map(n => `${n.category}/${n.detail}`).join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await source.close?.();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
await spill.dispose();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type WriteContext, type WritePlan } from './model';
|
|
2
|
+
import type { ByteSink } from './types';
|
|
3
|
+
import type { ZipArchive, ZipEntry } from './zip';
|
|
4
|
+
/** Extra field records that hold only times and owners. The rewrite keeps them unless metadata is stripped. */
|
|
5
|
+
export declare const METADATA_EXTRAS: Set<number>;
|
|
6
|
+
/** What the headers of one written entry say. */
|
|
7
|
+
export interface Written {
|
|
8
|
+
name: Uint8Array;
|
|
9
|
+
flags: number;
|
|
10
|
+
method: number;
|
|
11
|
+
versionNeeded: number;
|
|
12
|
+
versionMadeBy: number;
|
|
13
|
+
externalAttr: number;
|
|
14
|
+
internalAttr: number;
|
|
15
|
+
time: number;
|
|
16
|
+
date: number;
|
|
17
|
+
crc: number;
|
|
18
|
+
compressedSize: number;
|
|
19
|
+
size: number;
|
|
20
|
+
extras: Uint8Array;
|
|
21
|
+
comment: Uint8Array;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Writes the entries of a ZIP archive one after another, then its central directory. Local headers carry the CRC and
|
|
25
|
+
* sizes, so no entry needs a data descriptor. ZIP64 records appear only where a size, an offset or the entry count
|
|
26
|
+
* needs them.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ZipOutput {
|
|
29
|
+
private readonly sink;
|
|
30
|
+
private offset;
|
|
31
|
+
private readonly central;
|
|
32
|
+
constructor(sink: ByteSink);
|
|
33
|
+
/** Writes bytes as they are, such as data kept before the archive. */
|
|
34
|
+
put(b: Uint8Array): Promise<void>;
|
|
35
|
+
/** Writes one entry: its local header, then `chunks`, which must hold exactly `info.compressedSize` bytes. */
|
|
36
|
+
entry(info: Written, chunks: () => AsyncIterable<Uint8Array>, label: string): Promise<void>;
|
|
37
|
+
/** Writes the central directory and the end records, with the archive's comment. */
|
|
38
|
+
end(comment: Uint8Array): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/** Writes `zip` again with the entries `plan` keeps. */
|
|
41
|
+
export declare function writeZip(zip: ZipArchive, plan: WritePlan<ZipEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
|