@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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +345 -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/encoder.d.ts +44 -0
  24. package/dist/encoder.js +114 -0
  25. package/dist/engine.d.ts +44 -0
  26. package/dist/engine.js +753 -0
  27. package/dist/errors.d.ts +64 -0
  28. package/dist/errors.js +98 -0
  29. package/dist/filters.d.ts +34 -0
  30. package/dist/filters.js +385 -0
  31. package/dist/findings.d.ts +26 -0
  32. package/dist/findings.js +176 -0
  33. package/dist/formats.d.ts +14 -0
  34. package/dist/formats.js +97 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.js +50 -0
  37. package/dist/index.mjs +3 -0
  38. package/dist/inflate64.d.ts +6 -0
  39. package/dist/inflate64.js +388 -0
  40. package/dist/io.d.ts +78 -0
  41. package/dist/io.js +379 -0
  42. package/dist/lzma-formats.d.ts +75 -0
  43. package/dist/lzma-formats.js +373 -0
  44. package/dist/lzma-sniff.d.ts +20 -0
  45. package/dist/lzma-sniff.js +73 -0
  46. package/dist/lzma-writers.d.ts +35 -0
  47. package/dist/lzma-writers.js +276 -0
  48. package/dist/lzma1/LICENSE +201 -0
  49. package/dist/lzma1/NOTICE +36 -0
  50. package/dist/lzma1/crc32.d.ts +4 -0
  51. package/dist/lzma1/crc32.js +19 -0
  52. package/dist/lzma1/hash234.d.ts +22 -0
  53. package/dist/lzma1/hash234.js +75 -0
  54. package/dist/lzma1/hc4.d.ts +30 -0
  55. package/dist/lzma1/hc4.js +142 -0
  56. package/dist/lzma1/header.d.ts +32 -0
  57. package/dist/lzma1/header.js +73 -0
  58. package/dist/lzma1/lz-decoder.d.ts +41 -0
  59. package/dist/lzma1/lz-decoder.js +128 -0
  60. package/dist/lzma1/lz-encoder.d.ts +87 -0
  61. package/dist/lzma1/lz-encoder.js +166 -0
  62. package/dist/lzma1/lzma-coder.d.ts +107 -0
  63. package/dist/lzma1/lzma-coder.js +162 -0
  64. package/dist/lzma1/lzma-decoder.d.ts +36 -0
  65. package/dist/lzma1/lzma-decoder.js +484 -0
  66. package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
  67. package/dist/lzma1/lzma-encoder-fast.js +114 -0
  68. package/dist/lzma1/lzma-encoder.d.ts +105 -0
  69. package/dist/lzma1/lzma-encoder.js +497 -0
  70. package/dist/lzma1/lzma2-decoder.d.ts +85 -0
  71. package/dist/lzma1/lzma2-decoder.js +249 -0
  72. package/dist/lzma1/range-coder.d.ts +18 -0
  73. package/dist/lzma1/range-coder.js +23 -0
  74. package/dist/lzma1/range-decoder.d.ts +41 -0
  75. package/dist/lzma1/range-decoder.js +78 -0
  76. package/dist/lzma1/range-encoder.d.ts +53 -0
  77. package/dist/lzma1/range-encoder.js +269 -0
  78. package/dist/lzma1-stream.d.ts +60 -0
  79. package/dist/lzma1-stream.js +202 -0
  80. package/dist/model.d.ts +141 -0
  81. package/dist/model.js +16 -0
  82. package/dist/names.d.ts +13 -0
  83. package/dist/names.js +66 -0
  84. package/dist/pack.d.ts +72 -0
  85. package/dist/pack.js +514 -0
  86. package/dist/plugins.d.ts +13 -0
  87. package/dist/plugins.js +55 -0
  88. package/dist/rules.d.ts +25 -0
  89. package/dist/rules.js +403 -0
  90. package/dist/score.d.ts +17 -0
  91. package/dist/score.js +58 -0
  92. package/dist/seven.d.ts +75 -0
  93. package/dist/seven.js +492 -0
  94. package/dist/sevenzip.d.ts +133 -0
  95. package/dist/sevenzip.js +529 -0
  96. package/dist/single.d.ts +103 -0
  97. package/dist/single.js +453 -0
  98. package/dist/sniff.d.ts +20 -0
  99. package/dist/sniff.js +224 -0
  100. package/dist/stream-meter.d.ts +39 -0
  101. package/dist/stream-meter.js +54 -0
  102. package/dist/tar.d.ts +102 -0
  103. package/dist/tar.js +765 -0
  104. package/dist/types.d.ts +284 -0
  105. package/dist/types.js +133 -0
  106. package/dist/unpack.d.ts +67 -0
  107. package/dist/unpack.js +194 -0
  108. package/dist/xz.d.ts +25 -0
  109. package/dist/xz.js +333 -0
  110. package/dist/zip-lzma.d.ts +15 -0
  111. package/dist/zip-lzma.js +137 -0
  112. package/dist/zip-writer.d.ts +41 -0
  113. package/dist/zip-writer.js +263 -0
  114. package/dist/zip.d.ts +99 -0
  115. package/dist/zip.js +901 -0
  116. package/dist/zipcrypt.d.ts +47 -0
  117. package/dist/zipcrypt.js +233 -0
  118. package/dist/zstd.d.ts +25 -0
  119. package/dist/zstd.js +160 -0
  120. package/package.json +94 -4
package/dist/cab.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { Reader } from './io';
2
+ import { type Archive, type Entry, type Meter, type Note, type WriteContext, type WritePlan } from './model';
3
+ import { type ByteSink, type ByteSource } from './types';
4
+ /** The cabinet checksum: the data's 32-bit little-endian words XORed together, with an odd tail taken high byte first. */
5
+ export declare function cabChecksum(b: Uint8Array, seed?: number): number;
6
+ interface DataBlock {
7
+ /** Offset of the block header. */
8
+ at: number;
9
+ checksum: number;
10
+ compressed: number;
11
+ uncompressed: number;
12
+ /** Offset of the block's data, after its reserved area. */
13
+ dataAt: number;
14
+ }
15
+ interface Folder {
16
+ index: number;
17
+ start: number;
18
+ blocks: DataBlock[];
19
+ /** The low four bits: 0 stored, 1 MSZIP, 2 Quantum, 3 LZX. */
20
+ type: number;
21
+ typeCompress: number;
22
+ uncompressed: number;
23
+ /** Bytes the folder's blocks take, headers included. */
24
+ bytes: number;
25
+ }
26
+ export interface CabEntry extends Entry {
27
+ folder: number;
28
+ offset: number;
29
+ date: number;
30
+ time: number;
31
+ attribs: number;
32
+ }
33
+ export declare class CabArchive implements Archive<CabEntry> {
34
+ readonly reader: Reader;
35
+ readonly entries: CabEntry[];
36
+ readonly notes: Note[];
37
+ readonly folders: Folder[];
38
+ readonly setId: number;
39
+ readonly cabinetIndex: number;
40
+ /** The end of the cabinet, as its header gives it. */
41
+ readonly end: number;
42
+ /** Bytes of reserved area each data block carries. */
43
+ readonly blockReserve: number;
44
+ readonly format = "cab";
45
+ readonly single = false;
46
+ readonly lateNotes: Note[];
47
+ private cursor?;
48
+ private constructor();
49
+ static open(source: ByteSource, limits: {
50
+ entries: number;
51
+ decompressedBytes: number;
52
+ totalDecompressedBytes: number;
53
+ }, deadline?: number): Promise<CabArchive>;
54
+ unlock(): Promise<boolean>;
55
+ hasMetadata(): boolean;
56
+ /** Decodes the next block of the cursor's folder. */
57
+ private decodeBlock;
58
+ read(e: CabEntry, meter: Meter): AsyncGenerator<Uint8Array>;
59
+ write(plan: WritePlan<CabEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
60
+ trailing(): AsyncGenerator<Uint8Array>;
61
+ }
62
+ export declare const isCab: (b: Uint8Array) => boolean;
63
+ export {};
package/dist/cab.js ADDED
@@ -0,0 +1,493 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isCab = exports.CabArchive = void 0;
37
+ exports.cabChecksum = cabChecksum;
38
+ // Microsoft cabinets with stored and MSZIP folders. MSZIP is deflate in blocks of up to 32 KiB, each a whole deflate
39
+ // stream that may refer back into the block before it, which Node's zlib takes as a preset dictionary. LZX and Quantum
40
+ // folders cannot be decoded here, and a cabinet that continues into another file cannot be read whole.
41
+ const zlib = __importStar(require("node:zlib"));
42
+ const errors_1 = require("./errors");
43
+ const io_1 = require("./io");
44
+ const model_1 = require("./model");
45
+ const names_1 = require("./names");
46
+ const sniff_1 = require("./sniff");
47
+ const types_1 = require("./types");
48
+ const BLOCK = 32768;
49
+ const NAME_IS_UTF = 0x80;
50
+ /** The cabinet checksum: the data's 32-bit little-endian words XORed together, with an odd tail taken high byte first. */
51
+ function cabChecksum(b, seed = 0) {
52
+ let csum = seed >>> 0;
53
+ const words = Math.floor(b.length / 4);
54
+ for (let i = 0; i < words; i++)
55
+ csum = (csum ^ (b[i * 4] | (b[i * 4 + 1] << 8) | (b[i * 4 + 2] << 16) | (b[i * 4 + 3] << 24))) >>> 0;
56
+ let ul = 0;
57
+ const p = words * 4;
58
+ const rest = b.length % 4;
59
+ if (rest === 3)
60
+ ul = (b[p] << 16) | (b[p + 1] << 8) | b[p + 2];
61
+ else if (rest === 2)
62
+ ul = (b[p] << 8) | b[p + 1];
63
+ else if (rest === 1)
64
+ ul = b[p];
65
+ return (csum ^ ul) >>> 0;
66
+ }
67
+ const methodOf = (type) => {
68
+ if (type === 0)
69
+ return { name: 'stored', readable: true, unusual: false };
70
+ if (type === 1)
71
+ return { name: 'MSZIP', readable: true, unusual: false };
72
+ return { name: type === 2 ? 'Quantum' : type === 3 ? 'LZX' : `cabinet method ${type}`, readable: false, unusual: true };
73
+ };
74
+ class CabArchive {
75
+ reader;
76
+ entries;
77
+ notes;
78
+ folders;
79
+ setId;
80
+ cabinetIndex;
81
+ end;
82
+ blockReserve;
83
+ format = 'cab';
84
+ single = false;
85
+ lateNotes = [];
86
+ cursor;
87
+ constructor(reader, entries, notes, folders, setId, cabinetIndex,
88
+ /** The end of the cabinet, as its header gives it. */
89
+ end,
90
+ /** Bytes of reserved area each data block carries. */
91
+ blockReserve) {
92
+ this.reader = reader;
93
+ this.entries = entries;
94
+ this.notes = notes;
95
+ this.folders = folders;
96
+ this.setId = setId;
97
+ this.cabinetIndex = cabinetIndex;
98
+ this.end = end;
99
+ this.blockReserve = blockReserve;
100
+ }
101
+ static async open(source, limits, deadline) {
102
+ const size = await source.size();
103
+ const reader = new io_1.Reader(source, size);
104
+ const notes = [];
105
+ const h = Buffer.from(await reader.read(0, 36));
106
+ if (h.length < 36 || h.toString('latin1', 0, 4) !== 'MSCF')
107
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'Not a cabinet', { reason: 'no cabinet header' });
108
+ const cbCabinet = h.readUInt32LE(8);
109
+ const coffFiles = h.readUInt32LE(16);
110
+ const cFolders = h.readUInt16LE(26);
111
+ const cFiles = h.readUInt16LE(28);
112
+ const flags = h.readUInt16LE(30);
113
+ if (h[25] !== 1)
114
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'A cabinet version this package does not read', { reason: `cabinet version ${h[25]}.${h[24]}` });
115
+ if (flags & 0x0003)
116
+ throw new errors_1.StopError(types_1.ArchiveCategory.Structure, types_1.ArchiveDetail.MultiVolume, 'The cabinet continues in another file');
117
+ // Readers go by the tables, so a cabinet size past the end of the file only matters if a block runs past it.
118
+ if (cbCabinet > size)
119
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.MalformedHeader, data: { reason: 'the cabinet header gives a size past the end of the file' } });
120
+ const end = Math.min(cbCabinet, size);
121
+ if (cFiles > limits.entries)
122
+ throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.EntryCount, `${cFiles} files`, { entries: cFiles, limit: limits.entries });
123
+ let p = 36;
124
+ let headerReserve = 0;
125
+ let folderReserve = 0;
126
+ let blockReserve = 0;
127
+ if (flags & 0x0004) {
128
+ const r = Buffer.from(await reader.read(36, 4));
129
+ headerReserve = r.readUInt16LE(0);
130
+ folderReserve = r[2];
131
+ blockReserve = r[3];
132
+ p = 40 + headerReserve;
133
+ if (headerReserve || folderReserve || blockReserve)
134
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.ExtraField, data: { field: 'reserved areas in the cabinet' } });
135
+ }
136
+ // Spans the cabinet's parts take, to find bytes no part covers.
137
+ const spans = [[0, p]];
138
+ const folders = [];
139
+ for (let i = 0; i < cFolders; i++) {
140
+ const f = Buffer.from(await reader.read(p, 8));
141
+ if (f.length < 8)
142
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside the folder table', { what: 'folder table' });
143
+ folders.push({ index: i, start: f.readUInt32LE(0), blocks: [], typeCompress: f.readUInt16LE(6), type: f.readUInt16LE(6) & 0x0f, uncompressed: 0, bytes: 0 });
144
+ spans.push([p, p + 8 + folderReserve]);
145
+ p += 8 + folderReserve;
146
+ const count = f.readUInt16LE(4);
147
+ let at = folders[i].start;
148
+ for (let b = 0; b < count; b++) {
149
+ if (b % 256 === 0)
150
+ (0, errors_1.checkTime)(deadline);
151
+ const d = Buffer.from(await reader.read(at, 8));
152
+ if (d.length < 8)
153
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside a data block', { what: 'data block' });
154
+ const block = { at, checksum: d.readUInt32LE(0), compressed: d.readUInt16LE(4), uncompressed: d.readUInt16LE(6), dataAt: at + 8 + blockReserve };
155
+ if (block.dataAt + block.compressed > end)
156
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'A data block runs past the cabinet', { what: 'data block' });
157
+ if (block.uncompressed > BLOCK)
158
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'A data block is larger than 32 KiB', { reason: 'a data block larger than 32 KiB' });
159
+ folders[i].blocks.push(block);
160
+ folders[i].uncompressed += block.uncompressed;
161
+ spans.push([at, block.dataAt + block.compressed]);
162
+ at = block.dataAt + block.compressed;
163
+ }
164
+ folders[i].bytes = at - folders[i].start;
165
+ }
166
+ const entries = [];
167
+ p = coffFiles;
168
+ let total = 0;
169
+ for (let i = 0; i < cFiles; i++) {
170
+ if (i % 256 === 0)
171
+ (0, errors_1.checkTime)(deadline);
172
+ const f = Buffer.from(await reader.read(p, 16 + 256));
173
+ if (f.length < 17)
174
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Truncated, 'The file ends inside the file table', { what: 'file table' });
175
+ const nul = f.indexOf(0, 16);
176
+ if (nul < 0)
177
+ throw new errors_1.StopError(types_1.ArchiveCategory.Corrupted, types_1.ArchiveDetail.Unparseable, 'A file name without an end', { reason: 'a file name longer than 256 bytes' });
178
+ const raw = f.subarray(16, nul);
179
+ const attribs = f.readUInt16LE(14);
180
+ const entryNotes = [];
181
+ let name;
182
+ if (attribs & NAME_IS_UTF) {
183
+ const u = (0, names_1.utf8OrUndefined)(raw);
184
+ name = u ?? raw.toString('utf8');
185
+ if (u === undefined)
186
+ entryNotes.push({ category: types_1.ArchiveCategory.Name, detail: types_1.ArchiveDetail.Encoding, data: { name } });
187
+ }
188
+ else if ((0, names_1.isAscii)(raw))
189
+ name = raw.toString('latin1');
190
+ else {
191
+ name = (0, names_1.utf8OrUndefined)(raw) ?? (0, names_1.cp437)(raw);
192
+ entryNotes.push({ category: types_1.ArchiveCategory.Name, detail: types_1.ArchiveDetail.Encoding, data: { name } });
193
+ }
194
+ // A cabinet separates folders with backslashes, and Windows takes a slash the same way.
195
+ name = name.replace(/\\/g, '/');
196
+ const folderIndex = f.readUInt16LE(8);
197
+ if (folderIndex >= 0xfffd)
198
+ throw new errors_1.StopError(types_1.ArchiveCategory.Structure, types_1.ArchiveDetail.MultiVolume, 'A file continues in another cabinet');
199
+ const e = {
200
+ index: i,
201
+ name,
202
+ kind: 'file',
203
+ size: f.readUInt32LE(0),
204
+ compressedSize: 0,
205
+ notes: entryNotes,
206
+ folder: folderIndex,
207
+ offset: f.readUInt32LE(4),
208
+ date: f.readUInt16LE(10),
209
+ time: f.readUInt16LE(12),
210
+ attribs,
211
+ };
212
+ const folder = folders[folderIndex];
213
+ if (!folder)
214
+ e.broken = 'it names a folder the cabinet does not have';
215
+ else {
216
+ e.method = methodOf(folder.type);
217
+ if (e.offset + e.size > folder.uncompressed)
218
+ e.broken = 'it runs past the end of its folder';
219
+ else
220
+ e.compressedSize = Math.round((folder.bytes * e.size) / Math.max(1, folder.uncompressed));
221
+ }
222
+ if (e.size > limits.decompressedBytes)
223
+ throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.DecompressedSize, `${name} declares ${e.size} bytes`, { size: e.size, limit: limits.decompressedBytes }, name);
224
+ total += e.size;
225
+ if (total > limits.totalDecompressedBytes)
226
+ throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.TotalDecompressedSize, 'The files declare too many bytes', { limit: limits.totalDecompressedBytes });
227
+ entries.push(e);
228
+ spans.push([p, p + 16 + raw.length + 1]);
229
+ p += 16 + raw.length + 1;
230
+ }
231
+ // Bytes of the cabinet no header, table or block covers, and parts of folders no file covers.
232
+ spans.sort((a, b) => a[0] - b[0]);
233
+ let covered = 0;
234
+ let hidden = 0;
235
+ let overlap = false;
236
+ for (const [s, e] of spans) {
237
+ if (s > covered)
238
+ hidden += s - covered;
239
+ else if (s < covered)
240
+ overlap = true;
241
+ covered = Math.max(covered, e);
242
+ }
243
+ if (covered < end)
244
+ hidden += end - covered;
245
+ for (const folder of folders) {
246
+ const ranges = entries.filter(e => e.folder === folder.index && !e.broken).map(e => [e.offset, e.offset + e.size]);
247
+ ranges.sort((a, b) => a[0] - b[0]);
248
+ let at = 0;
249
+ for (const [s, e] of ranges) {
250
+ if (s > at)
251
+ hidden += s - at;
252
+ else if (s < at && e > s)
253
+ overlap = true;
254
+ at = Math.max(at, e);
255
+ }
256
+ if (folder.type <= 1 && at < folder.uncompressed)
257
+ hidden += folder.uncompressed - at;
258
+ }
259
+ if (hidden > 0)
260
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.HiddenData, data: { bytes: hidden } });
261
+ if (overlap)
262
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Overlap });
263
+ if (cbCabinet < size) {
264
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.TrailingBytes, data: { bytes: size - cbCabinet } });
265
+ const type = (0, sniff_1.sniffType)(await reader.read(cbCabinet, Math.min(1024, size - cbCabinet)));
266
+ if (type !== undefined)
267
+ notes.push({ category: types_1.ArchiveCategory.Structure, detail: types_1.ArchiveDetail.Polyglot, data: { type, where: 'after the cabinet' } });
268
+ }
269
+ return new CabArchive(reader, entries, notes, folders, h.readUInt16LE(32), h.readUInt16LE(34), end, blockReserve);
270
+ }
271
+ async unlock() {
272
+ return false;
273
+ }
274
+ hasMetadata() {
275
+ return this.entries.some(e => e.date !== 0x21 || e.time !== 0);
276
+ }
277
+ /** Decodes the next block of the cursor's folder. */
278
+ async decodeBlock(c) {
279
+ const folder = this.folders[c.folder];
280
+ const block = folder.blocks[c.block];
281
+ if (!block)
282
+ throw new errors_1.DataError('the folder ends before the file does', true);
283
+ const data = Buffer.from(await this.reader.read(block.dataAt, block.compressed));
284
+ if (block.checksum !== 0) {
285
+ const head = Buffer.alloc(4);
286
+ head.writeUInt16LE(block.compressed, 0);
287
+ head.writeUInt16LE(block.uncompressed, 2);
288
+ if (cabChecksum(head, cabChecksum(data)) !== block.checksum)
289
+ throw new errors_1.DataError('a data block does not match its checksum');
290
+ }
291
+ let out;
292
+ if (folder.type === 0) {
293
+ if (block.compressed !== block.uncompressed)
294
+ throw new errors_1.DataError('a stored block has two different sizes');
295
+ out = data;
296
+ }
297
+ else {
298
+ if (data[0] !== 0x43 || data[1] !== 0x4b)
299
+ throw new errors_1.DataError('an MSZIP block without its signature');
300
+ try {
301
+ const r = zlib.inflateRawSync(data.subarray(2), { dictionary: c.window.length ? c.window : undefined, maxOutputLength: BLOCK, info: true });
302
+ out = r.buffer;
303
+ if (r.engine.bytesWritten !== data.length - 2)
304
+ throw new errors_1.DataError('an MSZIP block holds data after its deflate stream');
305
+ }
306
+ catch (err) {
307
+ if (err instanceof errors_1.DataError)
308
+ throw err;
309
+ throw new errors_1.DataError(`an MSZIP block does not decode: ${err.message}`);
310
+ }
311
+ }
312
+ if (out.length !== block.uncompressed)
313
+ throw new errors_1.DataError('a data block decodes to another size than it gives');
314
+ c.window = out.length >= BLOCK ? out : Buffer.concat([c.window, out]).subarray(-BLOCK);
315
+ c.base += c.data.length;
316
+ c.data = out;
317
+ c.block++;
318
+ c.consumed += block.compressed;
319
+ }
320
+ async *read(e, meter) {
321
+ if (e.broken)
322
+ throw new errors_1.DataError(e.broken);
323
+ const folder = this.folders[e.folder];
324
+ if (folder.type > 1)
325
+ throw new errors_1.DataError(`folder method ${folder.type}`);
326
+ let c = this.cursor;
327
+ if (!c || c.folder !== e.folder || c.base > e.offset) {
328
+ c = { folder: e.folder, block: 0, base: 0, data: Buffer.alloc(0), window: Buffer.alloc(0), consumed: 0 };
329
+ this.cursor = c;
330
+ }
331
+ const startConsumed = c.consumed;
332
+ let at = e.offset;
333
+ const end = e.offset + e.size;
334
+ let total = 0;
335
+ while (at < end) {
336
+ while (at >= c.base + c.data.length) {
337
+ meter.time();
338
+ await this.decodeBlock(c);
339
+ }
340
+ const piece = c.data.subarray(at - c.base, Math.min(c.data.length, end - c.base));
341
+ at += piece.length;
342
+ total += piece.length;
343
+ meter.add(total, Math.max(1, c.consumed - startConsumed));
344
+ yield piece;
345
+ }
346
+ }
347
+ write(plan, sink, context) {
348
+ return writeCab(this, plan, sink, context);
349
+ }
350
+ trailing() {
351
+ return this.reader.chunks(this.end, this.reader.size - this.end);
352
+ }
353
+ }
354
+ exports.CabArchive = CabArchive;
355
+ /** True when a folder's blocks can be copied: every file in it is kept with its own bytes, and nothing else is in it. */
356
+ function copyable(cab, folder, kept, plan) {
357
+ if (cab.blockReserve !== 0)
358
+ return false;
359
+ const all = cab.entries.filter(e => e.folder === folder.index);
360
+ const mine = kept.filter(k => k.entry.folder === folder.index);
361
+ if (mine.length !== all.length || mine.some(k => k.replacement || k.recompress))
362
+ return false;
363
+ // Bytes of the folder that no file covers go with the HIDDEN_DATA finding unless it is info.
364
+ const ranges = all.map(e => [e.offset, e.offset + e.size]).sort((a, b) => a[0] - b[0]);
365
+ let at = 0;
366
+ for (const [s, e] of ranges) {
367
+ if (s !== at)
368
+ return plan.keepExtraFields && false;
369
+ at = e;
370
+ }
371
+ return at === folder.uncompressed;
372
+ }
373
+ async function writeCab(cab, plan, sink, context) {
374
+ const out = [];
375
+ const spills = [];
376
+ try {
377
+ const byFolder = new Map();
378
+ for (const k of plan.kept)
379
+ byFolder.set(k.entry.folder, [...(byFolder.get(k.entry.folder) ?? []), k]);
380
+ for (const [index, kept] of byFolder) {
381
+ const folder = cab.folders[index];
382
+ if (copyable(cab, folder, kept, plan)) {
383
+ out.push({
384
+ typeCompress: folder.typeCompress,
385
+ blocks: folder.blocks.length,
386
+ data: { copy: folder },
387
+ bytes: folder.bytes,
388
+ files: kept.map(k => ({ k, offset: k.entry.offset, size: k.entry.size })),
389
+ });
390
+ continue;
391
+ }
392
+ // The folder is built again from the content of the files it keeps, in blocks of 32 KiB.
393
+ const spill = new io_1.SpillSink(context.temp, context.threshold);
394
+ spills.push(spill);
395
+ const files = [];
396
+ let offset = 0;
397
+ let pending = Buffer.alloc(0);
398
+ let blocks = 0;
399
+ const mszip = folder.type === 1;
400
+ const flush = async (b) => {
401
+ const data = mszip ? Buffer.concat([Buffer.from('CK'), zlib.deflateRawSync(b)]) : b;
402
+ const head = Buffer.alloc(8);
403
+ head.writeUInt16LE(data.length, 4);
404
+ head.writeUInt16LE(b.length, 6);
405
+ head.writeUInt32LE(cabChecksum(head.subarray(4, 8), cabChecksum(data)), 0);
406
+ await spill.write(head);
407
+ await spill.write(data);
408
+ blocks++;
409
+ };
410
+ for (const k of kept) {
411
+ let size = 0;
412
+ for await (const c of (0, model_1.contentOf)(cab, k, context)) {
413
+ context.meter().time();
414
+ size += c.length;
415
+ pending = Buffer.concat([pending, c]);
416
+ while (pending.length >= BLOCK) {
417
+ await flush(pending.subarray(0, BLOCK));
418
+ pending = pending.subarray(BLOCK);
419
+ }
420
+ }
421
+ files.push({ k, offset, size });
422
+ offset += size;
423
+ }
424
+ if (pending.length)
425
+ await flush(pending);
426
+ await spill.close();
427
+ if (blocks > 0xffff)
428
+ throw new Error('A folder needs more than 65,535 data blocks');
429
+ out.push({ typeCompress: mszip ? 1 : 0, blocks, data: spill, bytes: spill.length, files });
430
+ }
431
+ const names = out.flatMap(f => f.files.map(x => Buffer.from(x.k.name.replace(/\//g, '\\'), 'utf8')));
432
+ const filesBytes = names.reduce((sum, name) => sum + 16 + name.length + 1, 0);
433
+ const coffFiles = 36 + out.length * 8;
434
+ let dataAt = coffFiles + filesBytes;
435
+ const starts = out.map(f => {
436
+ const s = dataAt;
437
+ dataAt += f.bytes;
438
+ return s;
439
+ });
440
+ const header = Buffer.alloc(36);
441
+ header.write('MSCF', 0, 'latin1');
442
+ header.writeUInt32LE(dataAt, 8);
443
+ header.writeUInt32LE(coffFiles, 16);
444
+ header[24] = 3;
445
+ header[25] = 1;
446
+ header.writeUInt16LE(out.length, 26);
447
+ header.writeUInt16LE(names.length, 28);
448
+ header.writeUInt16LE(0, 30);
449
+ header.writeUInt16LE(cab.setId, 32);
450
+ header.writeUInt16LE(cab.cabinetIndex, 34);
451
+ await sink.write(header);
452
+ for (const [i, f] of out.entries()) {
453
+ const b = Buffer.alloc(8);
454
+ b.writeUInt32LE(starts[i], 0);
455
+ b.writeUInt16LE(f.blocks, 4);
456
+ b.writeUInt16LE(f.typeCompress, 6);
457
+ await sink.write(b);
458
+ }
459
+ let n = 0;
460
+ for (const [i, f] of out.entries()) {
461
+ for (const x of f.files) {
462
+ const e = x.k.entry;
463
+ const name = names[n++];
464
+ const b = Buffer.alloc(16);
465
+ b.writeUInt32LE(x.size, 0);
466
+ b.writeUInt32LE(x.offset, 4);
467
+ b.writeUInt16LE(i, 8);
468
+ b.writeUInt16LE(plan.stripMetadata ? 0x21 : e.date, 10);
469
+ b.writeUInt16LE(plan.stripMetadata ? 0 : e.time, 12);
470
+ b.writeUInt16LE((e.attribs & ~NAME_IS_UTF) | ((0, names_1.isAscii)(name) ? 0 : NAME_IS_UTF), 14);
471
+ await sink.write(Buffer.concat([b, name, Buffer.alloc(1)]));
472
+ }
473
+ }
474
+ for (const f of out) {
475
+ if ('copy' in f.data) {
476
+ for await (const c of cab.reader.chunks(f.data.copy.start, f.data.copy.bytes))
477
+ await sink.write(c);
478
+ }
479
+ else
480
+ for await (const c of f.data.read())
481
+ await sink.write(c);
482
+ }
483
+ if (plan.keepTrailing)
484
+ for await (const c of cab.trailing())
485
+ await sink.write(c);
486
+ }
487
+ finally {
488
+ for (const s of spills)
489
+ await s.dispose();
490
+ }
491
+ }
492
+ const isCab = (b) => b.length >= 8 && Buffer.from(b.subarray(0, 8)).toString('latin1') === 'MSCF\0\0\0\0';
493
+ exports.isCab = isCab;
@@ -0,0 +1,23 @@
1
+ /** A running CRC-64 as xz computes it. */
2
+ export declare class Crc64 {
3
+ private hi;
4
+ private lo;
5
+ update(bytes: Uint8Array): void;
6
+ /** The 8 bytes, little-endian, as xz stores them. */
7
+ digest(): Uint8Array;
8
+ }
9
+ /** The integrity check of an xz block, by check ID. */
10
+ export interface Check {
11
+ update(bytes: Uint8Array): void;
12
+ /** The bytes as the block stores them. */
13
+ digest(): Uint8Array;
14
+ }
15
+ /** xz check IDs this package computes. */
16
+ export declare const CHECK_NONE = 0;
17
+ export declare const CHECK_CRC32 = 1;
18
+ export declare const CHECK_CRC64 = 4;
19
+ export declare const CHECK_SHA256 = 10;
20
+ export declare function checkSize(id: number): number;
21
+ export declare function checkName(id: number): string;
22
+ /** A check of a known ID, or undefined for an ID this package cannot compute. */
23
+ export declare function makeCheck(id: number): Check | undefined;