@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +22 -0
  3. package/THIRD-PARTY-NOTICES.txt +22909 -0
  4. package/dist/types/ar/arj.d.ts +5 -0
  5. package/dist/types/ar/asar.d.ts +7 -0
  6. package/dist/types/ar/bytes.d.ts +20 -0
  7. package/dist/types/ar/cab.d.ts +5 -0
  8. package/dist/types/ar/checksums.d.ts +10 -0
  9. package/dist/types/ar/codecs/bzip2.d.ts +4 -0
  10. package/dist/types/ar/codecs/gzip.d.ts +6 -0
  11. package/dist/types/ar/codecs/lzma.d.ts +6 -0
  12. package/dist/types/ar/codecs/lzw.d.ts +4 -0
  13. package/dist/types/ar/codecs/lzx.d.ts +7 -0
  14. package/dist/types/ar/codecs/xz.d.ts +4 -0
  15. package/dist/types/ar/codecs/zstd.d.ts +6 -0
  16. package/dist/types/ar/cpio.d.ts +7 -0
  17. package/dist/types/ar/deb.d.ts +5 -0
  18. package/dist/types/ar/entries.d.ts +22 -0
  19. package/dist/types/ar/error.d.ts +8 -0
  20. package/dist/types/ar/index.d.ts +11 -0
  21. package/dist/types/ar/iso.d.ts +5 -0
  22. package/dist/types/ar/limits.d.ts +33 -0
  23. package/dist/types/ar/lzh.d.ts +7 -0
  24. package/dist/types/ar/open.d.ts +45 -0
  25. package/dist/types/ar/paths.d.ts +18 -0
  26. package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
  27. package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
  28. package/dist/types/ar/rar.d.ts +5 -0
  29. package/dist/types/ar/reader.d.ts +28 -0
  30. package/dist/types/ar/registry.d.ts +17 -0
  31. package/dist/types/ar/rpm.d.ts +5 -0
  32. package/dist/types/ar/sevenzip/decode.d.ts +33 -0
  33. package/dist/types/ar/sevenzip.d.ts +5 -0
  34. package/dist/types/ar/source.d.ts +54 -0
  35. package/dist/types/ar/tar.d.ts +9 -0
  36. package/dist/types/ar/types.d.ts +99 -0
  37. package/dist/types/ar/unix-ar.d.ts +7 -0
  38. package/dist/types/ar/write.d.ts +10 -0
  39. package/dist/types/ar/zip.d.ts +10 -0
  40. package/dist/types/postmortem.d.ts +33 -0
  41. package/package.json +9 -3
  42. package/src/ar/arj.ts +314 -0
  43. package/src/ar/asar.ts +480 -0
  44. package/src/ar/bytes.ts +78 -0
  45. package/src/ar/cab.ts +377 -0
  46. package/src/ar/checksums.ts +62 -0
  47. package/src/ar/codecs/bzip2.ts +489 -0
  48. package/src/ar/codecs/gzip.ts +25 -0
  49. package/src/ar/codecs/lzma.ts +437 -0
  50. package/src/ar/codecs/lzw.ts +191 -0
  51. package/src/ar/codecs/lzx.ts +366 -0
  52. package/src/ar/codecs/xz.ts +522 -0
  53. package/src/ar/codecs/zstd.ts +25 -0
  54. package/src/ar/cpio.ts +389 -0
  55. package/src/ar/deb.ts +160 -0
  56. package/src/ar/entries.ts +97 -0
  57. package/src/ar/error.ts +11 -0
  58. package/src/ar/index.ts +16 -0
  59. package/src/ar/iso.ts +712 -0
  60. package/src/ar/limits.ts +80 -0
  61. package/src/ar/lzh.ts +659 -0
  62. package/src/ar/open.ts +225 -0
  63. package/src/ar/paths.ts +70 -0
  64. package/src/ar/rar/rar4-decoder.ts +459 -0
  65. package/src/ar/rar/rar5-decoder.ts +400 -0
  66. package/src/ar/rar.ts +735 -0
  67. package/src/ar/reader.ts +162 -0
  68. package/src/ar/registry.ts +208 -0
  69. package/src/ar/rpm.ts +320 -0
  70. package/src/ar/sevenzip/decode.ts +239 -0
  71. package/src/ar/sevenzip.ts +634 -0
  72. package/src/ar/source.ts +190 -0
  73. package/src/ar/tar.ts +771 -0
  74. package/src/ar/types.ts +131 -0
  75. package/src/ar/unix-ar.ts +312 -0
  76. package/src/ar/write.ts +55 -0
  77. package/src/ar/zip.ts +719 -0
  78. package/src/browsers.ts +2 -149
  79. package/src/docx/converter.ts +9 -9
  80. package/src/postmortem.ts +74 -0
  81. package/dist/types/docx/zip.d.ts +0 -6
  82. package/src/docx/zip.ts +0 -87
@@ -0,0 +1,489 @@
1
+ import { ArchiveError } from "../error";
2
+
3
+ const BLOCK_MAGIC = [0x31, 0x41, 0x59, 0x26, 0x53, 0x59] as const;
4
+ const STREAM_END_MAGIC = [0x17, 0x72, 0x45, 0x38, 0x50, 0x90] as const;
5
+ const MAX_HUFFMAN_LENGTH = 20;
6
+ const MAX_SELECTORS = 18_002;
7
+ const GROUP_SIZE = 50;
8
+
9
+ // The fixed randomization sequence from the original bzip2 format. Randomized
10
+ // blocks are obsolete, but remain part of the bitstream and occur in old data.
11
+ const RANDOM_NUMBERS = new Uint16Array([
12
+ 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, 733, 859, 335,
13
+ 708, 621, 574, 73, 654, 730, 472, 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, 878, 465, 811, 169, 869, 675, 611,
14
+ 697, 867, 561, 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, 150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
15
+ 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, 909, 545, 703,
16
+ 919, 874, 474, 882, 500, 594, 612, 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, 161, 604, 958, 533, 221, 400,
17
+ 386, 867, 600, 782, 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, 98, 553, 163, 354, 666, 933, 424, 341, 533,
18
+ 870, 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, 184, 943,
19
+ 795, 384, 383, 461, 404, 758, 839, 887, 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, 951, 160, 578, 722, 79, 804,
20
+ 96, 409, 713, 940, 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, 645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
21
+ 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, 411, 521, 938, 300,
22
+ 821, 78, 343, 175, 128, 250, 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, 857, 956, 358, 619, 580, 124, 737, 594,
23
+ 701, 612, 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, 344, 805,
24
+ 988, 739, 511, 655, 814, 334, 249, 515, 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, 433, 837, 553, 268, 926,
25
+ 240, 102, 654, 459, 51, 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, 946, 670, 656, 610, 738, 392, 760, 799,
26
+ 887, 653, 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, 707,
27
+ 151, 457, 449, 797, 195, 791, 558, 945, 679, 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, 134, 108, 571, 364, 631,
28
+ 212, 174, 643, 304, 329, 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, 140, 206, 73, 263, 980, 736, 876, 478, 430,
29
+ 305, 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, 804, 378,
30
+ 215, 828, 592, 281, 565, 555, 710, 82, 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, 661, 821, 976, 991, 658, 869,
31
+ 905, 758, 745, 193, 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, 61, 688, 793, 644, 986, 403, 106, 366, 905,
32
+ 644, 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, 920, 176,
33
+ 193, 713, 857, 265, 203, 50, 668, 108, 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, 936, 638,
34
+ ]);
35
+
36
+ const CRC_TABLE = makeCrcTable();
37
+
38
+ function makeCrcTable(): Uint32Array {
39
+ const table = new Uint32Array(256);
40
+ for (let index = 0; index < table.length; index++) {
41
+ let value = index << 24;
42
+ for (let bit = 0; bit < 8; bit++) {
43
+ value = (value & 0x8000_0000) !== 0 ? (value << 1) ^ 0x04c1_1db7 : value << 1;
44
+ }
45
+ table[index] = value >>> 0;
46
+ }
47
+ return table;
48
+ }
49
+
50
+ function updateCrc(crc: number, byte: number): number {
51
+ return (CRC_TABLE[((crc >>> 24) ^ byte) & 0xff]! ^ (crc << 8)) >>> 0;
52
+ }
53
+
54
+ class BitReader {
55
+ readonly #bytes: Uint8Array;
56
+ #bitPosition = 0;
57
+
58
+ constructor(bytes: Uint8Array) {
59
+ this.#bytes = bytes;
60
+ }
61
+
62
+ get done(): boolean {
63
+ return this.#bitPosition === this.#bytes.byteLength * 8;
64
+ }
65
+
66
+ readBit(): number {
67
+ if (this.#bitPosition >= this.#bytes.byteLength * 8) {
68
+ throw new ArchiveError("Truncated bzip2 stream");
69
+ }
70
+ const byte = this.#bytes[this.#bitPosition >>> 3]!;
71
+ const bit = (byte >>> (7 - (this.#bitPosition & 7))) & 1;
72
+ this.#bitPosition++;
73
+ return bit;
74
+ }
75
+
76
+ readBits(count: number): number {
77
+ let value = 0;
78
+ for (let bit = 0; bit < count; bit++) {
79
+ value = value * 2 + this.readBit();
80
+ }
81
+ return value;
82
+ }
83
+
84
+ readUint32(): number {
85
+ return (this.readBits(16) * 0x1_0000 + this.readBits(16)) >>> 0;
86
+ }
87
+
88
+ readMarker(): readonly number[] {
89
+ return [
90
+ this.readBits(8),
91
+ this.readBits(8),
92
+ this.readBits(8),
93
+ this.readBits(8),
94
+ this.readBits(8),
95
+ this.readBits(8),
96
+ ];
97
+ }
98
+
99
+ alignToByte(): void {
100
+ while ((this.#bitPosition & 7) !== 0) {
101
+ if (this.readBit() !== 0) {
102
+ throw new ArchiveError("Invalid non-zero bzip2 stream padding");
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ class BoundedOutput {
109
+ readonly #limit: number;
110
+ #bytes: Uint8Array;
111
+ #length = 0;
112
+
113
+ constructor(limit: number, inputSize: number) {
114
+ this.#limit = limit;
115
+ this.#bytes = new Uint8Array(Math.min(limit, Math.max(64, Math.min(inputSize * 2, 64 * 1024))));
116
+ }
117
+
118
+ push(byte: number): void {
119
+ this.#ensure(1);
120
+ this.#bytes[this.#length++] = byte;
121
+ }
122
+
123
+ pushRepeated(byte: number, count: number): void {
124
+ this.#ensure(count);
125
+ this.#bytes.fill(byte, this.#length, this.#length + count);
126
+ this.#length += count;
127
+ }
128
+
129
+ finish(): Uint8Array {
130
+ return this.#bytes.slice(0, this.#length);
131
+ }
132
+
133
+ #ensure(additional: number): void {
134
+ const needed = this.#length + additional;
135
+ if (!Number.isSafeInteger(needed) || needed > this.#limit) {
136
+ throw new ArchiveError(`Bzip2 output exceeds the ${this.#limit}-byte limit`);
137
+ }
138
+ if (needed <= this.#bytes.byteLength) {
139
+ return;
140
+ }
141
+ let capacity = Math.max(needed, Math.min(this.#limit, Math.max(64, this.#bytes.byteLength * 2)));
142
+ if (capacity > this.#limit) {
143
+ capacity = this.#limit;
144
+ }
145
+ const grown = new Uint8Array(capacity);
146
+ grown.set(this.#bytes.subarray(0, this.#length));
147
+ this.#bytes = grown;
148
+ }
149
+ }
150
+
151
+ class HuffmanTable {
152
+ readonly #counts = new Uint16Array(MAX_HUFFMAN_LENGTH + 1);
153
+ readonly #firstCodes = new Uint32Array(MAX_HUFFMAN_LENGTH + 1);
154
+ readonly #offsets = new Uint16Array(MAX_HUFFMAN_LENGTH + 1);
155
+ readonly #symbols: Uint16Array;
156
+
157
+ constructor(lengths: Uint8Array) {
158
+ for (const length of lengths) {
159
+ if (length < 1 || length > MAX_HUFFMAN_LENGTH) {
160
+ throw new ArchiveError("Invalid bzip2 Huffman code length");
161
+ }
162
+ this.#counts[length]++;
163
+ }
164
+
165
+ let code = 0;
166
+ let offset = 0;
167
+ for (let length = 1; length <= MAX_HUFFMAN_LENGTH; length++) {
168
+ code = (code + this.#counts[length - 1]!) * 2;
169
+ if (code + this.#counts[length]! > 2 ** length) {
170
+ throw new ArchiveError("Oversubscribed bzip2 Huffman table");
171
+ }
172
+ this.#firstCodes[length] = code;
173
+ this.#offsets[length] = offset;
174
+ offset += this.#counts[length]!;
175
+ }
176
+
177
+ this.#symbols = new Uint16Array(lengths.length);
178
+ const next = new Uint16Array(this.#offsets);
179
+ for (let symbol = 0; symbol < lengths.length; symbol++) {
180
+ const length = lengths[symbol]!;
181
+ this.#symbols[next[length]!] = symbol;
182
+ next[length]++;
183
+ }
184
+ }
185
+
186
+ decode(reader: BitReader): number {
187
+ let code = 0;
188
+ for (let length = 1; length <= MAX_HUFFMAN_LENGTH; length++) {
189
+ code = code * 2 + reader.readBit();
190
+ const relative = code - this.#firstCodes[length]!;
191
+ if (relative >= 0 && relative < this.#counts[length]!) {
192
+ return this.#symbols[this.#offsets[length]! + relative]!;
193
+ }
194
+ }
195
+ throw new ArchiveError("Invalid bzip2 Huffman code");
196
+ }
197
+ }
198
+
199
+ function markerEquals(actual: readonly number[], expected: readonly number[]): boolean {
200
+ return actual.every((byte, index) => byte === expected[index]);
201
+ }
202
+
203
+ function readStreamHeader(reader: BitReader): number {
204
+ if (reader.readBits(8) !== 0x42 || reader.readBits(8) !== 0x5a || reader.readBits(8) !== 0x68) {
205
+ throw new ArchiveError("Invalid bzip2 stream header");
206
+ }
207
+ const level = reader.readBits(8) - 0x30;
208
+ if (level < 1 || level > 9) {
209
+ throw new ArchiveError("Invalid bzip2 block-size level");
210
+ }
211
+ return level * 100_000;
212
+ }
213
+
214
+ function readHuffmanTables(
215
+ reader: BitReader,
216
+ usedBytes: Uint8Array,
217
+ ): { selectors: Uint8Array; tables: HuffmanTable[] } {
218
+ const groupCount = reader.readBits(3);
219
+ if (groupCount < 2 || groupCount > 6) {
220
+ throw new ArchiveError("Invalid bzip2 Huffman group count");
221
+ }
222
+ const selectorCount = reader.readBits(15);
223
+ if (selectorCount < 1 || selectorCount > MAX_SELECTORS) {
224
+ throw new ArchiveError("Invalid bzip2 selector count");
225
+ }
226
+
227
+ const selectors = new Uint8Array(selectorCount);
228
+ const selectorMtf = new Uint8Array(groupCount);
229
+ for (let index = 0; index < groupCount; index++) {
230
+ selectorMtf[index] = index;
231
+ }
232
+ for (let index = 0; index < selectorCount; index++) {
233
+ let position = 0;
234
+ while (reader.readBit() !== 0) {
235
+ position++;
236
+ if (position >= groupCount) {
237
+ throw new ArchiveError("Invalid bzip2 selector MTF value");
238
+ }
239
+ }
240
+ const selector = selectorMtf[position]!;
241
+ selectors[index] = selector;
242
+ selectorMtf.copyWithin(1, 0, position);
243
+ selectorMtf[0] = selector;
244
+ }
245
+
246
+ const alphaSize = usedBytes.length + 2;
247
+ const tables: HuffmanTable[] = [];
248
+ for (let group = 0; group < groupCount; group++) {
249
+ const lengths = new Uint8Array(alphaSize);
250
+ let length = reader.readBits(5);
251
+ for (let symbol = 0; symbol < alphaSize; symbol++) {
252
+ while (reader.readBit() !== 0) {
253
+ length += reader.readBit() === 0 ? 1 : -1;
254
+ if (length < 1 || length > MAX_HUFFMAN_LENGTH) {
255
+ throw new ArchiveError("Invalid bzip2 Huffman code length");
256
+ }
257
+ }
258
+ lengths[symbol] = length;
259
+ }
260
+ tables.push(new HuffmanTable(lengths));
261
+ }
262
+ return { selectors, tables };
263
+ }
264
+
265
+ function decodeBlockData(
266
+ reader: BitReader,
267
+ blockSizeLimit: number,
268
+ usedBytes: Uint8Array,
269
+ selectors: Uint8Array,
270
+ tables: HuffmanTable[],
271
+ ): Uint8Array {
272
+ const mtf = new Uint8Array(usedBytes);
273
+ const block = new Uint8Array(blockSizeLimit);
274
+ let blockLength = 0;
275
+ let selectorIndex = 0;
276
+ let groupRemaining = 0;
277
+ let table: HuffmanTable | undefined;
278
+
279
+ const nextSymbol = (): number => {
280
+ if (groupRemaining === 0) {
281
+ if (selectorIndex >= selectors.length) {
282
+ throw new ArchiveError("Bzip2 block exhausted its Huffman selectors");
283
+ }
284
+ table = tables[selectors[selectorIndex++]!];
285
+ groupRemaining = GROUP_SIZE;
286
+ }
287
+ groupRemaining--;
288
+ return table!.decode(reader);
289
+ };
290
+
291
+ const append = (byte: number, count: number): void => {
292
+ if (count < 0 || blockLength + count > blockSizeLimit) {
293
+ throw new ArchiveError("Bzip2 block exceeds its declared block-size level");
294
+ }
295
+ block.fill(byte, blockLength, blockLength + count);
296
+ blockLength += count;
297
+ };
298
+
299
+ const endSymbol = usedBytes.length + 1;
300
+ let symbol = nextSymbol();
301
+ while (symbol !== endSymbol) {
302
+ if (symbol === 0 || symbol === 1) {
303
+ let runLength = 0;
304
+ let power = 1;
305
+ do {
306
+ runLength += symbol === 0 ? power : power * 2;
307
+ if (runLength > blockSizeLimit || power > blockSizeLimit) {
308
+ throw new ArchiveError("Invalid bzip2 RLE run length");
309
+ }
310
+ power *= 2;
311
+ symbol = nextSymbol();
312
+ } while (symbol === 0 || symbol === 1);
313
+ append(mtf[0]!, runLength);
314
+ if (symbol === endSymbol) {
315
+ break;
316
+ }
317
+ }
318
+
319
+ const position = symbol - 1;
320
+ if (position < 0 || position >= mtf.length) {
321
+ throw new ArchiveError("Invalid bzip2 MTF symbol");
322
+ }
323
+ const byte = mtf[position]!;
324
+ mtf.copyWithin(1, 0, position);
325
+ mtf[0] = byte;
326
+ append(byte, 1);
327
+ symbol = nextSymbol();
328
+ }
329
+ return block.slice(0, blockLength);
330
+ }
331
+
332
+ function inverseBwt(block: Uint8Array, originalPointer: number): Uint8Array {
333
+ if (block.length === 0 || originalPointer < 0 || originalPointer >= block.length) {
334
+ throw new ArchiveError("Invalid bzip2 BWT origin pointer");
335
+ }
336
+ const counts = new Uint32Array(257);
337
+ for (const byte of block) {
338
+ counts[byte + 1]++;
339
+ }
340
+ for (let index = 1; index < counts.length; index++) {
341
+ counts[index] += counts[index - 1]!;
342
+ }
343
+ const positions = new Uint32Array(counts.subarray(0, 256));
344
+ const next = new Uint32Array(block.length);
345
+ for (let index = 0; index < block.length; index++) {
346
+ const byte = block[index]!;
347
+ next[positions[byte]!] = index;
348
+ positions[byte]++;
349
+ }
350
+ const decoded = new Uint8Array(block.length);
351
+ let position = next[originalPointer]!;
352
+ for (let index = 0; index < decoded.length; index++) {
353
+ decoded[index] = block[position]!;
354
+ position = next[position]!;
355
+ }
356
+ return decoded;
357
+ }
358
+
359
+ function appendRle1(decoded: Uint8Array, randomized: boolean, output: BoundedOutput): number {
360
+ let crc = 0xffff_ffff;
361
+ let previous = -1;
362
+ let repetitions = 0;
363
+ let randomIndex = 0;
364
+ let randomRemaining = 0;
365
+
366
+ const appendByte = (byte: number): void => {
367
+ output.push(byte);
368
+ crc = updateCrc(crc, byte);
369
+ };
370
+ const appendRepeated = (byte: number, count: number): void => {
371
+ output.pushRepeated(byte, count);
372
+ for (let index = 0; index < count; index++) {
373
+ crc = updateCrc(crc, byte);
374
+ }
375
+ };
376
+
377
+ for (let byte of decoded) {
378
+ if (randomized) {
379
+ if (randomRemaining === 0) {
380
+ randomRemaining = RANDOM_NUMBERS[randomIndex]!;
381
+ randomIndex = (randomIndex + 1) & 511;
382
+ }
383
+ randomRemaining--;
384
+ if (randomRemaining === 1) {
385
+ byte ^= 1;
386
+ }
387
+ }
388
+
389
+ if (repetitions === 4) {
390
+ appendRepeated(previous, byte);
391
+ repetitions = 0;
392
+ continue;
393
+ }
394
+ appendByte(byte);
395
+ if (byte === previous) {
396
+ repetitions++;
397
+ } else {
398
+ previous = byte;
399
+ repetitions = 1;
400
+ }
401
+ }
402
+ if (repetitions === 4) {
403
+ throw new ArchiveError("Truncated bzip2 RLE run");
404
+ }
405
+ return (crc ^ 0xffff_ffff) >>> 0;
406
+ }
407
+
408
+ function decode(bytes: Uint8Array, maxOutput: number): Uint8Array {
409
+ if (!Number.isSafeInteger(maxOutput) || maxOutput < 0) {
410
+ throw new ArchiveError("Invalid bzip2 output limit");
411
+ }
412
+ const reader = new BitReader(bytes);
413
+ const output = new BoundedOutput(maxOutput, bytes.byteLength);
414
+
415
+ for (;;) {
416
+ const blockSizeLimit = readStreamHeader(reader);
417
+ let combinedCrc = 0;
418
+ for (;;) {
419
+ const marker = reader.readMarker();
420
+ if (markerEquals(marker, STREAM_END_MAGIC)) {
421
+ const expectedCombinedCrc = reader.readUint32();
422
+ if (combinedCrc !== expectedCombinedCrc) {
423
+ throw new ArchiveError("Bzip2 stream CRC mismatch");
424
+ }
425
+ reader.alignToByte();
426
+ break;
427
+ }
428
+ if (!markerEquals(marker, BLOCK_MAGIC)) {
429
+ throw new ArchiveError("Invalid bzip2 block marker");
430
+ }
431
+
432
+ const expectedBlockCrc = reader.readUint32();
433
+ const randomized = reader.readBit() !== 0;
434
+ const originalPointer = reader.readBits(24);
435
+ const usedRanges = reader.readBits(16);
436
+ const used = new Uint8Array(256);
437
+ let usedCount = 0;
438
+ for (let range = 0; range < 16; range++) {
439
+ if ((usedRanges & (0x8000 >>> range)) === 0) {
440
+ continue;
441
+ }
442
+ for (let low = 0; low < 16; low++) {
443
+ if (reader.readBit() !== 0) {
444
+ used[usedCount++] = range * 16 + low;
445
+ }
446
+ }
447
+ }
448
+ if (usedCount === 0) {
449
+ throw new ArchiveError("Bzip2 block has an empty symbol map");
450
+ }
451
+ const usedBytes = used.slice(0, usedCount);
452
+ const { selectors, tables } = readHuffmanTables(reader, usedBytes);
453
+ const block = decodeBlockData(reader, blockSizeLimit, usedBytes, selectors, tables);
454
+ const decoded = inverseBwt(block, originalPointer);
455
+ const actualBlockCrc = appendRle1(decoded, randomized, output);
456
+ if (actualBlockCrc !== expectedBlockCrc) {
457
+ throw new ArchiveError("Bzip2 block CRC mismatch");
458
+ }
459
+ combinedCrc = (((combinedCrc << 1) | (combinedCrc >>> 31)) ^ actualBlockCrc) >>> 0;
460
+ }
461
+ if (reader.done) {
462
+ return output.finish();
463
+ }
464
+ }
465
+ }
466
+
467
+ /** Return whether bytes begin with a valid bzip2 stream signature. */
468
+ export function isBzip2(bytes: Uint8Array): boolean {
469
+ return (
470
+ bytes.byteLength >= 4 &&
471
+ bytes[0] === 0x42 &&
472
+ bytes[1] === 0x5a &&
473
+ bytes[2] === 0x68 &&
474
+ bytes[3]! >= 0x31 &&
475
+ bytes[3]! <= 0x39
476
+ );
477
+ }
478
+
479
+ /** Decompress concatenated bzip2 streams while enforcing a hard output bound. */
480
+ export async function bzip2Decompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
481
+ try {
482
+ return decode(bytes, maxOutput);
483
+ } catch (error) {
484
+ if (error instanceof ArchiveError) {
485
+ throw error;
486
+ }
487
+ throw new ArchiveError(error instanceof Error ? error.message : String(error));
488
+ }
489
+ }
@@ -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 gzipAsync = promisify(zlib.gzip);
6
+ const gunzipAsync = promisify(zlib.gunzip);
7
+
8
+ /** gzip magic: 0x1f 0x8b. */
9
+ export function isGzip(bytes: Uint8Array): boolean {
10
+ return bytes.byteLength >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
11
+ }
12
+
13
+ /** Decompress one gzip stream, bounded so a gzip bomb cannot inflate without limit. */
14
+ export async function gzipDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
15
+ try {
16
+ return await gunzipAsync(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 gzip stream (tar.gz writing). */
23
+ export function gzipCompress(bytes: Uint8Array): Promise<Uint8Array> {
24
+ return gzipAsync(bytes);
25
+ }