@oh-my-pi/pi-utils 17.4.0 → 17.4.2
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/CHANGELOG.md +12 -0
- package/LICENSE +22 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/ar/arj.d.ts +5 -0
- package/dist/types/ar/asar.d.ts +7 -0
- package/dist/types/ar/bytes.d.ts +20 -0
- package/dist/types/ar/cab.d.ts +5 -0
- package/dist/types/ar/checksums.d.ts +10 -0
- package/dist/types/ar/codecs/bzip2.d.ts +4 -0
- package/dist/types/ar/codecs/gzip.d.ts +6 -0
- package/dist/types/ar/codecs/lzma.d.ts +6 -0
- package/dist/types/ar/codecs/lzw.d.ts +4 -0
- package/dist/types/ar/codecs/lzx.d.ts +7 -0
- package/dist/types/ar/codecs/xz.d.ts +4 -0
- package/dist/types/ar/codecs/zstd.d.ts +6 -0
- package/dist/types/ar/cpio.d.ts +7 -0
- package/dist/types/ar/deb.d.ts +5 -0
- package/dist/types/ar/entries.d.ts +22 -0
- package/dist/types/ar/error.d.ts +8 -0
- package/dist/types/ar/index.d.ts +11 -0
- package/dist/types/ar/iso.d.ts +5 -0
- package/dist/types/ar/limits.d.ts +33 -0
- package/dist/types/ar/lzh.d.ts +7 -0
- package/dist/types/ar/open.d.ts +45 -0
- package/dist/types/ar/paths.d.ts +18 -0
- package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
- package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
- package/dist/types/ar/rar.d.ts +5 -0
- package/dist/types/ar/reader.d.ts +28 -0
- package/dist/types/ar/registry.d.ts +17 -0
- package/dist/types/ar/rpm.d.ts +5 -0
- package/dist/types/ar/sevenzip/decode.d.ts +33 -0
- package/dist/types/ar/sevenzip.d.ts +5 -0
- package/dist/types/ar/source.d.ts +54 -0
- package/dist/types/ar/tar.d.ts +9 -0
- package/dist/types/ar/types.d.ts +99 -0
- package/dist/types/ar/unix-ar.d.ts +7 -0
- package/dist/types/ar/write.d.ts +10 -0
- package/dist/types/ar/zip.d.ts +10 -0
- package/dist/types/postmortem.d.ts +33 -0
- package/package.json +9 -3
- package/src/ar/arj.ts +314 -0
- package/src/ar/asar.ts +480 -0
- package/src/ar/bytes.ts +78 -0
- package/src/ar/cab.ts +377 -0
- package/src/ar/checksums.ts +62 -0
- package/src/ar/codecs/bzip2.ts +489 -0
- package/src/ar/codecs/gzip.ts +25 -0
- package/src/ar/codecs/lzma.ts +437 -0
- package/src/ar/codecs/lzw.ts +191 -0
- package/src/ar/codecs/lzx.ts +366 -0
- package/src/ar/codecs/xz.ts +522 -0
- package/src/ar/codecs/zstd.ts +25 -0
- package/src/ar/cpio.ts +389 -0
- package/src/ar/deb.ts +160 -0
- package/src/ar/entries.ts +97 -0
- package/src/ar/error.ts +11 -0
- package/src/ar/index.ts +16 -0
- package/src/ar/iso.ts +712 -0
- package/src/ar/limits.ts +80 -0
- package/src/ar/lzh.ts +659 -0
- package/src/ar/open.ts +225 -0
- package/src/ar/paths.ts +70 -0
- package/src/ar/rar/rar4-decoder.ts +459 -0
- package/src/ar/rar/rar5-decoder.ts +400 -0
- package/src/ar/rar.ts +735 -0
- package/src/ar/reader.ts +162 -0
- package/src/ar/registry.ts +208 -0
- package/src/ar/rpm.ts +320 -0
- package/src/ar/sevenzip/decode.ts +239 -0
- package/src/ar/sevenzip.ts +634 -0
- package/src/ar/source.ts +190 -0
- package/src/ar/tar.ts +771 -0
- package/src/ar/types.ts +131 -0
- package/src/ar/unix-ar.ts +312 -0
- package/src/ar/write.ts +55 -0
- package/src/ar/zip.ts +719 -0
- package/src/browsers.ts +2 -149
- package/src/docx/converter.ts +9 -9
- package/src/postmortem.ts +74 -0
- package/dist/types/docx/zip.d.ts +0 -6
- package/src/docx/zip.ts +0 -87
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { crc32, crc64 } from "../checksums";
|
|
2
|
+
import { ArchiveError } from "../error";
|
|
3
|
+
import { lzma2Decompress } from "./lzma";
|
|
4
|
+
|
|
5
|
+
const XZ_MAGIC = Uint8Array.of(0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00);
|
|
6
|
+
|
|
7
|
+
function read32LE(bytes: Uint8Array, offset: number): number {
|
|
8
|
+
if (offset < 0 || offset + 4 > bytes.byteLength) throw new ArchiveError("Invalid XZ stream: truncated integer");
|
|
9
|
+
return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function write32LE(bytes: Uint8Array, offset: number, value: number): void {
|
|
13
|
+
bytes[offset] = value & 0xff;
|
|
14
|
+
bytes[offset + 1] = (value >>> 8) & 0xff;
|
|
15
|
+
bytes[offset + 2] = (value >>> 16) & 0xff;
|
|
16
|
+
bytes[offset + 3] = value >>> 24;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function read32BE(bytes: Uint8Array, offset: number): number {
|
|
20
|
+
return ((bytes[offset]! << 24) | (bytes[offset + 1]! << 16) | (bytes[offset + 2]! << 8) | bytes[offset + 3]!) >>> 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function write32BE(bytes: Uint8Array, offset: number, value: number): void {
|
|
24
|
+
bytes[offset] = value >>> 24;
|
|
25
|
+
bytes[offset + 1] = (value >>> 16) & 0xff;
|
|
26
|
+
bytes[offset + 2] = (value >>> 8) & 0xff;
|
|
27
|
+
bytes[offset + 3] = value & 0xff;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function equalBytes(left: Uint8Array, right: Uint8Array): boolean {
|
|
31
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
32
|
+
for (let index = 0; index < left.byteLength; index++) if (left[index] !== right[index]) return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Cursor {
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
pos: number;
|
|
39
|
+
limit: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readVarInt(cursor: Cursor): number {
|
|
43
|
+
let value = 0;
|
|
44
|
+
for (let index = 0; index < 9; index++) {
|
|
45
|
+
if (cursor.pos >= cursor.limit) throw new ArchiveError("Invalid XZ stream: truncated variable-length integer");
|
|
46
|
+
const byte = cursor.bytes[cursor.pos++]!;
|
|
47
|
+
if (index > 0 && byte === 0) throw new ArchiveError("Invalid XZ stream: non-canonical variable-length integer");
|
|
48
|
+
value += (byte & 0x7f) * 2 ** (index * 7);
|
|
49
|
+
if (!Number.isSafeInteger(value)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
|
|
50
|
+
if ((byte & 0x80) === 0) return value;
|
|
51
|
+
}
|
|
52
|
+
throw new ArchiveError("Invalid XZ stream: variable-length integer is too long");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface XzRecord {
|
|
56
|
+
unpaddedSize: number;
|
|
57
|
+
uncompressedSize: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface XzStream {
|
|
61
|
+
start: number;
|
|
62
|
+
indexStart: number;
|
|
63
|
+
footerStart: number;
|
|
64
|
+
checkId: number;
|
|
65
|
+
records: XzRecord[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function checkSize(checkId: number): number {
|
|
69
|
+
switch (checkId) {
|
|
70
|
+
case 0:
|
|
71
|
+
return 0;
|
|
72
|
+
case 1:
|
|
73
|
+
return 4;
|
|
74
|
+
case 4:
|
|
75
|
+
return 8;
|
|
76
|
+
case 10:
|
|
77
|
+
return 32;
|
|
78
|
+
default:
|
|
79
|
+
throw new ArchiveError(`Unsupported XZ integrity check ID 0x${checkId.toString(16)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseIndex(bytes: Uint8Array, start: number, size: number): XzRecord[] {
|
|
84
|
+
if (size < 8 || start < 0 || start + size > bytes.byteLength)
|
|
85
|
+
throw new ArchiveError("Invalid XZ stream: index range is invalid");
|
|
86
|
+
const end = start + size;
|
|
87
|
+
if (bytes[start] !== 0) throw new ArchiveError("Invalid XZ stream: missing index indicator");
|
|
88
|
+
if (crc32(bytes.subarray(start, end - 4)) !== read32LE(bytes, end - 4))
|
|
89
|
+
throw new ArchiveError("Invalid XZ stream: index CRC32 mismatch");
|
|
90
|
+
const cursor: Cursor = { bytes, pos: start + 1, limit: end - 4 };
|
|
91
|
+
const count = readVarInt(cursor);
|
|
92
|
+
if (count > Math.floor(size / 2)) throw new ArchiveError("Invalid XZ stream: impossible index record count");
|
|
93
|
+
const records: XzRecord[] = [];
|
|
94
|
+
for (let index = 0; index < count; index++) {
|
|
95
|
+
const unpaddedSize = readVarInt(cursor);
|
|
96
|
+
const uncompressedSize = readVarInt(cursor);
|
|
97
|
+
if (unpaddedSize === 0) throw new ArchiveError("Invalid XZ stream: zero unpadded block size");
|
|
98
|
+
records.push({ unpaddedSize, uncompressedSize });
|
|
99
|
+
}
|
|
100
|
+
while (cursor.pos < cursor.limit)
|
|
101
|
+
if (bytes[cursor.pos++] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero index padding");
|
|
102
|
+
return records;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function discoverStreams(bytes: Uint8Array): XzStream[] {
|
|
106
|
+
if (bytes.byteLength === 0 || (bytes.byteLength & 3) !== 0)
|
|
107
|
+
throw new ArchiveError("Invalid XZ stream: size is not a multiple of four bytes");
|
|
108
|
+
const streams: XzStream[] = [];
|
|
109
|
+
let end = bytes.byteLength;
|
|
110
|
+
while (end > 0) {
|
|
111
|
+
let padding = 0;
|
|
112
|
+
while (end >= 4 && bytes[end - 1] === 0 && bytes[end - 2] === 0 && bytes[end - 3] === 0 && bytes[end - 4] === 0) {
|
|
113
|
+
end -= 4;
|
|
114
|
+
padding += 4;
|
|
115
|
+
}
|
|
116
|
+
if (end === 0) throw new ArchiveError("Invalid XZ stream: padding without a stream");
|
|
117
|
+
if (end < 24) throw new ArchiveError("Invalid XZ stream: truncated stream framing");
|
|
118
|
+
const footerStart = end - 12;
|
|
119
|
+
if (bytes[footerStart + 10] !== 0x59 || bytes[footerStart + 11] !== 0x5a)
|
|
120
|
+
throw new ArchiveError("Invalid XZ stream: footer magic mismatch");
|
|
121
|
+
if (crc32(bytes.subarray(footerStart + 4, footerStart + 10)) !== read32LE(bytes, footerStart))
|
|
122
|
+
throw new ArchiveError("Invalid XZ stream: footer CRC32 mismatch");
|
|
123
|
+
const flag0 = bytes[footerStart + 8]!;
|
|
124
|
+
const flag1 = bytes[footerStart + 9]!;
|
|
125
|
+
if (flag0 !== 0 || (flag1 & 0xf0) !== 0) throw new ArchiveError("Unsupported XZ stream flags");
|
|
126
|
+
const checkId = flag1 & 0x0f;
|
|
127
|
+
checkSize(checkId);
|
|
128
|
+
const indexSize = (read32LE(bytes, footerStart + 4) + 1) * 4;
|
|
129
|
+
if (!Number.isSafeInteger(indexSize) || indexSize > footerStart)
|
|
130
|
+
throw new ArchiveError("Invalid XZ stream: backward index size is invalid");
|
|
131
|
+
const indexStart = footerStart - indexSize;
|
|
132
|
+
const records = parseIndex(bytes, indexStart, indexSize);
|
|
133
|
+
let blocksSize = 0;
|
|
134
|
+
for (const record of records) {
|
|
135
|
+
blocksSize += Math.ceil(record.unpaddedSize / 4) * 4;
|
|
136
|
+
if (!Number.isSafeInteger(blocksSize)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
|
|
137
|
+
}
|
|
138
|
+
const start = indexStart - blocksSize - 12;
|
|
139
|
+
if (start < 0 || start + 12 > bytes.byteLength || !equalBytes(bytes.subarray(start, start + 6), XZ_MAGIC)) {
|
|
140
|
+
throw new ArchiveError("Invalid XZ stream: header position or magic is invalid");
|
|
141
|
+
}
|
|
142
|
+
if (bytes[start + 6] !== flag0 || bytes[start + 7] !== flag1)
|
|
143
|
+
throw new ArchiveError("Invalid XZ stream: header and footer flags differ");
|
|
144
|
+
if (crc32(bytes.subarray(start + 6, start + 8)) !== read32LE(bytes, start + 8))
|
|
145
|
+
throw new ArchiveError("Invalid XZ stream: header CRC32 mismatch");
|
|
146
|
+
streams.unshift({ start, indexStart, footerStart, checkId, records });
|
|
147
|
+
end = start;
|
|
148
|
+
void padding;
|
|
149
|
+
}
|
|
150
|
+
return streams;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface XzFilter {
|
|
154
|
+
id: number;
|
|
155
|
+
properties: Uint8Array;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deltaDecode(bytes: Uint8Array, distance: number): void {
|
|
159
|
+
const history = new Uint8Array(256);
|
|
160
|
+
let position = 0;
|
|
161
|
+
for (let index = 0; index < bytes.byteLength; index++) {
|
|
162
|
+
const value = (bytes[index]! + history[(distance + position) & 0xff]!) & 0xff;
|
|
163
|
+
history[position] = value;
|
|
164
|
+
bytes[index] = value;
|
|
165
|
+
position = (position - 1) & 0xff;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function x86Decode(bytes: Uint8Array, startOffset: number): void {
|
|
170
|
+
const maskToBitNumber = [0, 1, 2, 2, 3] as const;
|
|
171
|
+
let previousMask = 0;
|
|
172
|
+
let previousPosition = 0xfffffffb;
|
|
173
|
+
if (bytes.byteLength < 5) return;
|
|
174
|
+
let position = 0;
|
|
175
|
+
const limit = bytes.byteLength - 5;
|
|
176
|
+
while (position <= limit) {
|
|
177
|
+
let byte = bytes[position]!;
|
|
178
|
+
if (byte !== 0xe8 && byte !== 0xe9) {
|
|
179
|
+
position++;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const absolutePosition = (startOffset + position) >>> 0;
|
|
183
|
+
const offset = (absolutePosition - previousPosition) >>> 0;
|
|
184
|
+
previousPosition = absolutePosition;
|
|
185
|
+
if (offset > 5) previousMask = 0;
|
|
186
|
+
else for (let index = 0; index < offset; index++) previousMask = ((previousMask & 0x77) << 1) >>> 0;
|
|
187
|
+
byte = bytes[position + 4]!;
|
|
188
|
+
if ((byte === 0 || byte === 0xff) && previousMask >>> 1 <= 4 && previousMask >>> 1 !== 3) {
|
|
189
|
+
let source = read32LE(bytes, position + 1);
|
|
190
|
+
let destination = 0;
|
|
191
|
+
for (;;) {
|
|
192
|
+
destination = (source - absolutePosition - 5) >>> 0;
|
|
193
|
+
if (previousMask === 0) break;
|
|
194
|
+
const bitIndex = maskToBitNumber[previousMask >>> 1]! * 8;
|
|
195
|
+
const testByte = (destination >>> (24 - bitIndex)) & 0xff;
|
|
196
|
+
if (testByte !== 0 && testByte !== 0xff) break;
|
|
197
|
+
const lowMask = bitIndex === 0 ? 0xffffffff : 2 ** (32 - bitIndex) - 1;
|
|
198
|
+
source = (destination ^ lowMask) >>> 0;
|
|
199
|
+
}
|
|
200
|
+
bytes[position + 4] = ~(((destination >>> 24) & 1) - 1) & 0xff;
|
|
201
|
+
bytes[position + 3] = destination >>> 16;
|
|
202
|
+
bytes[position + 2] = destination >>> 8;
|
|
203
|
+
bytes[position + 1] = destination;
|
|
204
|
+
position += 5;
|
|
205
|
+
previousMask = 0;
|
|
206
|
+
} else {
|
|
207
|
+
position++;
|
|
208
|
+
previousMask |= 1;
|
|
209
|
+
if (byte === 0 || byte === 0xff) previousMask |= 0x10;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function powerPcDecode(bytes: Uint8Array, startOffset: number): void {
|
|
215
|
+
for (let index = 0; index + 4 <= bytes.byteLength; index += 4) {
|
|
216
|
+
if (bytes[index]! >>> 2 !== 0x12 || (bytes[index + 3]! & 3) !== 1) continue;
|
|
217
|
+
const source =
|
|
218
|
+
(((bytes[index]! & 3) << 24) |
|
|
219
|
+
(bytes[index + 1]! << 16) |
|
|
220
|
+
(bytes[index + 2]! << 8) |
|
|
221
|
+
(bytes[index + 3]! & 0xfc)) >>>
|
|
222
|
+
0;
|
|
223
|
+
const destination = (source - startOffset - index) >>> 0;
|
|
224
|
+
bytes[index] = 0x48 | ((destination >>> 24) & 3);
|
|
225
|
+
bytes[index + 1] = destination >>> 16;
|
|
226
|
+
bytes[index + 2] = destination >>> 8;
|
|
227
|
+
bytes[index + 3] = (bytes[index + 3]! & 3) | (destination & 0xfc);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function armDecode(bytes: Uint8Array, startOffset: number): void {
|
|
232
|
+
for (let index = 0; index + 4 <= bytes.byteLength; index += 4) {
|
|
233
|
+
if (bytes[index + 3] !== 0xeb) continue;
|
|
234
|
+
const source = ((bytes[index + 2]! << 18) | (bytes[index + 1]! << 10) | (bytes[index]! << 2)) >>> 0;
|
|
235
|
+
const destination = (source - startOffset - index - 8) >>> 2;
|
|
236
|
+
bytes[index] = destination;
|
|
237
|
+
bytes[index + 1] = destination >>> 8;
|
|
238
|
+
bytes[index + 2] = destination >>> 16;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function armThumbDecode(bytes: Uint8Array, startOffset: number): void {
|
|
243
|
+
for (let index = 0; index + 4 <= bytes.byteLength; index += 2) {
|
|
244
|
+
if ((bytes[index + 1]! & 0xf8) !== 0xf0 || (bytes[index + 3]! & 0xf8) !== 0xf8) continue;
|
|
245
|
+
const source =
|
|
246
|
+
((((bytes[index + 1]! & 7) << 19) |
|
|
247
|
+
(bytes[index]! << 11) |
|
|
248
|
+
((bytes[index + 3]! & 7) << 8) |
|
|
249
|
+
bytes[index + 2]!) <<
|
|
250
|
+
1) >>>
|
|
251
|
+
0;
|
|
252
|
+
const destination = (source - startOffset - index - 4) >>> 1;
|
|
253
|
+
bytes[index + 1] = 0xf0 | ((destination >>> 19) & 7);
|
|
254
|
+
bytes[index] = destination >>> 11;
|
|
255
|
+
bytes[index + 3] = 0xf8 | ((destination >>> 8) & 7);
|
|
256
|
+
bytes[index + 2] = destination;
|
|
257
|
+
index += 2;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function sparcDecode(bytes: Uint8Array, startOffset: number): void {
|
|
262
|
+
for (let index = 0; index + 4 <= bytes.byteLength; index += 4) {
|
|
263
|
+
if (
|
|
264
|
+
!(
|
|
265
|
+
(bytes[index] === 0x40 && (bytes[index + 1]! & 0xc0) === 0) ||
|
|
266
|
+
(bytes[index] === 0x7f && (bytes[index + 1]! & 0xc0) === 0xc0)
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
continue;
|
|
270
|
+
const source = (read32BE(bytes, index) << 2) >>> 0;
|
|
271
|
+
let destination = (source - startOffset - index) >>> 2;
|
|
272
|
+
destination = (((0 - ((destination >>> 22) & 1)) << 22) & 0x3fffffff) | (destination & 0x3fffff) | 0x40000000;
|
|
273
|
+
write32BE(bytes, index, destination >>> 0);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function ia64Decode(bytes: Uint8Array, startOffset: number): void {
|
|
278
|
+
const branchTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0];
|
|
279
|
+
for (let index = 0; index + 16 <= bytes.byteLength; index += 16) {
|
|
280
|
+
const mask = branchTable[bytes[index]! & 0x1f]!;
|
|
281
|
+
let bitPosition = 5;
|
|
282
|
+
for (let slot = 0; slot < 3; slot++, bitPosition += 41) {
|
|
283
|
+
if (((mask >>> slot) & 1) === 0) continue;
|
|
284
|
+
const bytePosition = bitPosition >>> 3;
|
|
285
|
+
const bitOffset = bitPosition & 7;
|
|
286
|
+
let instruction = 0n;
|
|
287
|
+
for (let byte = 0; byte < 6; byte++)
|
|
288
|
+
instruction |= BigInt(bytes[index + bytePosition + byte]!) << BigInt(byte * 8);
|
|
289
|
+
let normalized = instruction >> BigInt(bitOffset);
|
|
290
|
+
if (((normalized >> 37n) & 15n) !== 5n || ((normalized >> 9n) & 7n) !== 0n) continue;
|
|
291
|
+
let source = Number((normalized >> 13n) & 0xfffffn) | (Number((normalized >> 36n) & 1n) << 20);
|
|
292
|
+
source = (source << 4) >>> 0;
|
|
293
|
+
const destination = ((source - startOffset - index) >>> 4) >>> 0;
|
|
294
|
+
normalized &= ~(0x8fffffn << 13n);
|
|
295
|
+
normalized |= BigInt(destination & 0xfffff) << 13n;
|
|
296
|
+
normalized |= BigInt(destination & 0x100000) << 16n;
|
|
297
|
+
instruction &= (1n << BigInt(bitOffset)) - 1n;
|
|
298
|
+
instruction |= normalized << BigInt(bitOffset);
|
|
299
|
+
for (let byte = 0; byte < 6; byte++)
|
|
300
|
+
bytes[index + bytePosition + byte] = Number(instruction >> BigInt(byte * 8)) & 0xff;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function arm64Decode(bytes: Uint8Array, startOffset: number): void {
|
|
306
|
+
for (let index = 0; index + 4 <= bytes.byteLength; index += 4) {
|
|
307
|
+
const pc = (startOffset + index) >>> 0;
|
|
308
|
+
let instruction = read32LE(bytes, index);
|
|
309
|
+
if (instruction >>> 26 === 0x25) {
|
|
310
|
+
instruction = (0x94000000 | ((instruction - (pc >>> 2)) & 0x03ffffff)) >>> 0;
|
|
311
|
+
write32LE(bytes, index, instruction);
|
|
312
|
+
} else if ((instruction & 0x9f000000) === 0x90000000) {
|
|
313
|
+
const source = ((instruction >>> 29) & 3) | ((instruction >>> 3) & 0x001ffffc);
|
|
314
|
+
if (((source + 0x20000) & 0x1c0000) !== 0) continue;
|
|
315
|
+
const destination = (source - (pc >>> 12)) >>> 0;
|
|
316
|
+
instruction &= 0x9000001f;
|
|
317
|
+
instruction |= (destination & 3) << 29;
|
|
318
|
+
instruction |= (destination & 0x3fffc) << 3;
|
|
319
|
+
instruction |= (0 - (destination & 0x20000)) & 0xe00000;
|
|
320
|
+
write32LE(bytes, index, instruction >>> 0);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function riscvDecode(bytes: Uint8Array, startOffset: number): void {
|
|
326
|
+
if (bytes.byteLength < 8) return;
|
|
327
|
+
for (let index = 0; index <= bytes.byteLength - 8; index += 2) {
|
|
328
|
+
let instruction = bytes[index]!;
|
|
329
|
+
if (instruction === 0xef) {
|
|
330
|
+
const byte1 = bytes[index + 1]!;
|
|
331
|
+
if ((byte1 & 0x0d) !== 0) continue;
|
|
332
|
+
const address =
|
|
333
|
+
((((byte1 & 0xf0) << 13) | (bytes[index + 2]! << 9) | (bytes[index + 3]! << 1)) - startOffset - index) >>>
|
|
334
|
+
0;
|
|
335
|
+
bytes[index + 1] = (byte1 & 0x0f) | ((address >>> 8) & 0xf0);
|
|
336
|
+
bytes[index + 2] = ((address >>> 16) & 0x0f) | ((address >>> 7) & 0x10) | ((address << 4) & 0xe0);
|
|
337
|
+
bytes[index + 3] = ((address >>> 4) & 0x7f) | ((address >>> 13) & 0x80);
|
|
338
|
+
index += 2;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if ((instruction & 0x7f) !== 0x17) continue;
|
|
342
|
+
instruction = read32LE(bytes, index);
|
|
343
|
+
let instruction2: number;
|
|
344
|
+
if ((instruction & 0xe80) !== 0) {
|
|
345
|
+
instruction2 = read32LE(bytes, index + 4);
|
|
346
|
+
if (((instruction << 8) ^ ((instruction2 - 3) & 0xf8003)) !== 0) {
|
|
347
|
+
index += 4;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const address = (instruction & 0xfffff000) + (instruction2 >>> 20);
|
|
351
|
+
instruction = (0x17 | (2 << 7) | (instruction2 << 12)) >>> 0;
|
|
352
|
+
instruction2 = address >>> 0;
|
|
353
|
+
} else {
|
|
354
|
+
const register = instruction >>> 27;
|
|
355
|
+
if (((instruction - 0x3117) << 18) >>> 0 >= (register & 0x1d)) {
|
|
356
|
+
index += 2;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const address = (read32BE(bytes, index + 4) - startOffset - index) >>> 0;
|
|
360
|
+
instruction2 = ((instruction >>> 12) | (address << 20)) >>> 0;
|
|
361
|
+
instruction = (0x17 | (register << 7) | ((address + 0x800) & 0xfffff000)) >>> 0;
|
|
362
|
+
}
|
|
363
|
+
write32LE(bytes, index, instruction);
|
|
364
|
+
write32LE(bytes, index + 4, instruction2);
|
|
365
|
+
index += 6;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function applyFilter(bytes: Uint8Array, filter: XzFilter): void {
|
|
370
|
+
if (filter.id === 3) {
|
|
371
|
+
if (filter.properties.byteLength !== 1) throw new ArchiveError("Invalid XZ Delta filter properties");
|
|
372
|
+
deltaDecode(bytes, filter.properties[0]! + 1);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (filter.properties.byteLength !== 0 && filter.properties.byteLength !== 4)
|
|
376
|
+
throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} properties`);
|
|
377
|
+
const startOffset = filter.properties.byteLength === 4 ? read32LE(filter.properties, 0) : 0;
|
|
378
|
+
const alignment = filter.id === 6 ? 16 : filter.id === 8 || filter.id === 11 ? 2 : filter.id === 4 ? 1 : 4;
|
|
379
|
+
if ((startOffset & (alignment - 1)) !== 0)
|
|
380
|
+
throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} start offset`);
|
|
381
|
+
switch (filter.id) {
|
|
382
|
+
case 4:
|
|
383
|
+
x86Decode(bytes, startOffset);
|
|
384
|
+
break;
|
|
385
|
+
case 5:
|
|
386
|
+
powerPcDecode(bytes, startOffset);
|
|
387
|
+
break;
|
|
388
|
+
case 6:
|
|
389
|
+
ia64Decode(bytes, startOffset);
|
|
390
|
+
break;
|
|
391
|
+
case 7:
|
|
392
|
+
armDecode(bytes, startOffset);
|
|
393
|
+
break;
|
|
394
|
+
case 8:
|
|
395
|
+
armThumbDecode(bytes, startOffset);
|
|
396
|
+
break;
|
|
397
|
+
case 9:
|
|
398
|
+
sparcDecode(bytes, startOffset);
|
|
399
|
+
break;
|
|
400
|
+
case 10:
|
|
401
|
+
arm64Decode(bytes, startOffset);
|
|
402
|
+
break;
|
|
403
|
+
case 11:
|
|
404
|
+
riscvDecode(bytes, startOffset);
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
throw new ArchiveError(`Unsupported XZ filter ID 0x${filter.id.toString(16)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function verifyCheck(checkId: number, output: Uint8Array, expected: Uint8Array): void {
|
|
412
|
+
if (checkId === 0) return;
|
|
413
|
+
if (checkId === 1) {
|
|
414
|
+
if (read32LE(expected, 0) !== crc32(output)) throw new ArchiveError("Invalid XZ stream: block CRC32 mismatch");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (checkId === 4) {
|
|
418
|
+
const actual = crc64(output);
|
|
419
|
+
let stored = 0n;
|
|
420
|
+
for (let index = 0; index < 8; index++) stored |= BigInt(expected[index]!) << BigInt(index * 8);
|
|
421
|
+
if (actual !== stored) throw new ArchiveError("Invalid XZ stream: block CRC64 mismatch");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const actual = new Uint8Array(new Bun.CryptoHasher("sha256").update(output).digest());
|
|
425
|
+
if (!equalBytes(actual, expected)) throw new ArchiveError("Invalid XZ stream: block SHA-256 mismatch");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function decodeBlock(bytes: Uint8Array, offset: number, record: XzRecord, checkId: number): Promise<Uint8Array> {
|
|
429
|
+
if (offset >= bytes.byteLength || bytes[offset] === 0)
|
|
430
|
+
throw new ArchiveError("Invalid XZ stream: missing block header");
|
|
431
|
+
const headerSize = (bytes[offset]! + 1) * 4;
|
|
432
|
+
if (offset + headerSize > bytes.byteLength || headerSize < 8)
|
|
433
|
+
throw new ArchiveError("Invalid XZ stream: truncated block header");
|
|
434
|
+
if (crc32(bytes.subarray(offset, offset + headerSize - 4)) !== read32LE(bytes, offset + headerSize - 4))
|
|
435
|
+
throw new ArchiveError("Invalid XZ stream: block header CRC32 mismatch");
|
|
436
|
+
const cursor: Cursor = { bytes, pos: offset + 1, limit: offset + headerSize - 4 };
|
|
437
|
+
const flags = bytes[cursor.pos++]!;
|
|
438
|
+
if ((flags & 0x3c) !== 0) throw new ArchiveError("Unsupported XZ block flags");
|
|
439
|
+
const filterCount = (flags & 3) + 1;
|
|
440
|
+
const declaredCompressed = (flags & 0x40) !== 0 ? readVarInt(cursor) : undefined;
|
|
441
|
+
const declaredUncompressed = (flags & 0x80) !== 0 ? readVarInt(cursor) : undefined;
|
|
442
|
+
const filters: XzFilter[] = [];
|
|
443
|
+
for (let index = 0; index < filterCount; index++) {
|
|
444
|
+
const id = readVarInt(cursor);
|
|
445
|
+
const propertySize = readVarInt(cursor);
|
|
446
|
+
if (propertySize > cursor.limit - cursor.pos)
|
|
447
|
+
throw new ArchiveError("Invalid XZ stream: truncated filter properties");
|
|
448
|
+
filters.push({ id, properties: bytes.slice(cursor.pos, cursor.pos + propertySize) });
|
|
449
|
+
cursor.pos += propertySize;
|
|
450
|
+
}
|
|
451
|
+
while (cursor.pos < cursor.limit)
|
|
452
|
+
if (bytes[cursor.pos++] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero block header padding");
|
|
453
|
+
const integritySize = checkSize(checkId);
|
|
454
|
+
const compressedSize = record.unpaddedSize - headerSize - integritySize;
|
|
455
|
+
if (!Number.isSafeInteger(compressedSize) || compressedSize <= 0)
|
|
456
|
+
throw new ArchiveError("Invalid XZ stream: compressed block size is invalid");
|
|
457
|
+
if (declaredCompressed !== undefined && declaredCompressed !== compressedSize)
|
|
458
|
+
throw new ArchiveError("Invalid XZ stream: block compressed size mismatch");
|
|
459
|
+
if (declaredUncompressed !== undefined && declaredUncompressed !== record.uncompressedSize)
|
|
460
|
+
throw new ArchiveError("Invalid XZ stream: block uncompressed size mismatch");
|
|
461
|
+
const compressedStart = offset + headerSize;
|
|
462
|
+
const compressedEnd = compressedStart + compressedSize;
|
|
463
|
+
const paddingSize = (4 - ((headerSize + compressedSize) & 3)) & 3;
|
|
464
|
+
const checkStart = compressedEnd + paddingSize;
|
|
465
|
+
if (checkStart + integritySize > bytes.byteLength) throw new ArchiveError("Invalid XZ stream: truncated block data");
|
|
466
|
+
const last = filters[filters.length - 1]!;
|
|
467
|
+
if (last.id !== 0x21 || last.properties.byteLength !== 1)
|
|
468
|
+
throw new ArchiveError(`Unsupported XZ terminal filter ID 0x${last.id.toString(16)} (LZMA2 required)`);
|
|
469
|
+
let output = await lzma2Decompress(
|
|
470
|
+
last.properties[0]!,
|
|
471
|
+
bytes.subarray(compressedStart, compressedEnd),
|
|
472
|
+
record.uncompressedSize,
|
|
473
|
+
);
|
|
474
|
+
if (output.byteLength !== record.uncompressedSize)
|
|
475
|
+
throw new ArchiveError("Invalid XZ stream: decoded block size mismatch");
|
|
476
|
+
output = output.slice();
|
|
477
|
+
for (let index = filters.length - 2; index >= 0; index--) applyFilter(output, filters[index]!);
|
|
478
|
+
for (let index = compressedEnd; index < checkStart; index++)
|
|
479
|
+
if (bytes[index] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero block padding");
|
|
480
|
+
verifyCheck(checkId, output, bytes.subarray(checkStart, checkStart + integritySize));
|
|
481
|
+
const paddedEnd = offset + Math.ceil(record.unpaddedSize / 4) * 4;
|
|
482
|
+
if (checkStart + integritySize !== paddedEnd)
|
|
483
|
+
throw new ArchiveError("Invalid XZ stream: block size does not match its index record");
|
|
484
|
+
return output;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** Whether bytes begin with the XZ stream-header magic. */
|
|
488
|
+
export function isXz(bytes: Uint8Array): boolean {
|
|
489
|
+
return bytes.byteLength >= XZ_MAGIC.byteLength && equalBytes(bytes.subarray(0, XZ_MAGIC.byteLength), XZ_MAGIC);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Decompress all concatenated streams in an XZ container within `maxOutput`. */
|
|
493
|
+
export async function xzDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
|
|
494
|
+
if (!Number.isSafeInteger(maxOutput) || maxOutput < 0) throw new ArchiveError("Invalid XZ output limit");
|
|
495
|
+
try {
|
|
496
|
+
const streams = discoverStreams(bytes);
|
|
497
|
+
let totalSize = 0;
|
|
498
|
+
for (const stream of streams)
|
|
499
|
+
for (const record of stream.records) {
|
|
500
|
+
totalSize += record.uncompressedSize;
|
|
501
|
+
if (!Number.isSafeInteger(totalSize) || totalSize > maxOutput)
|
|
502
|
+
throw new ArchiveError("XZ output exceeds its size limit");
|
|
503
|
+
}
|
|
504
|
+
const output = new Uint8Array(totalSize);
|
|
505
|
+
let outputPosition = 0;
|
|
506
|
+
for (const stream of streams) {
|
|
507
|
+
let blockPosition = stream.start + 12;
|
|
508
|
+
for (const record of stream.records) {
|
|
509
|
+
const block = await decodeBlock(bytes, blockPosition, record, stream.checkId);
|
|
510
|
+
output.set(block, outputPosition);
|
|
511
|
+
outputPosition += block.byteLength;
|
|
512
|
+
blockPosition += Math.ceil(record.unpaddedSize / 4) * 4;
|
|
513
|
+
}
|
|
514
|
+
if (blockPosition !== stream.indexStart)
|
|
515
|
+
throw new ArchiveError("Invalid XZ stream: blocks do not align with index");
|
|
516
|
+
}
|
|
517
|
+
return output;
|
|
518
|
+
} catch (error) {
|
|
519
|
+
if (error instanceof ArchiveError) throw error;
|
|
520
|
+
throw new ArchiveError(`Invalid XZ stream: ${error instanceof Error ? error.message : String(error)}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { promisify } from "node:util";
|
|
2
|
+
import * as zlib from "node:zlib";
|
|
3
|
+
import { ArchiveError } from "../error";
|
|
4
|
+
|
|
5
|
+
const zstdCompressAsync = promisify(zlib.zstdCompress);
|
|
6
|
+
const zstdDecompressAsync = promisify(zlib.zstdDecompress);
|
|
7
|
+
|
|
8
|
+
/** zstd frame magic: 28 b5 2f fd. */
|
|
9
|
+
export function isZstd(bytes: Uint8Array): boolean {
|
|
10
|
+
return bytes.byteLength >= 4 && bytes[0] === 0x28 && bytes[1] === 0xb5 && bytes[2] === 0x2f && bytes[3] === 0xfd;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Decompress one zstd frame, bounded to `maxOutput` bytes. */
|
|
14
|
+
export async function zstdDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
|
|
15
|
+
try {
|
|
16
|
+
return await zstdDecompressAsync(bytes, { maxOutputLength: Math.max(maxOutput, 1) });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new ArchiveError(error instanceof Error ? error.message : String(error));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Compress bytes as one zstd frame (tar.zst writing). */
|
|
23
|
+
export function zstdCompress(bytes: Uint8Array): Promise<Uint8Array> {
|
|
24
|
+
return zstdCompressAsync(bytes);
|
|
25
|
+
}
|