@patrickdk77/office-defuse 0.1.0 → 0.2.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 (88) hide show
  1. package/README.md +278 -36
  2. package/dist/biff.d.ts +62 -0
  3. package/dist/biff.js +95 -0
  4. package/dist/cfb-writer.d.ts +25 -0
  5. package/dist/cfb-writer.js +314 -0
  6. package/dist/cfb.d.ts +58 -0
  7. package/dist/cfb.js +456 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +357 -0
  10. package/dist/contained.d.ts +46 -0
  11. package/dist/contained.js +152 -0
  12. package/dist/context.d.ts +140 -17
  13. package/dist/context.js +135 -37
  14. package/dist/crc32.d.ts +8 -2
  15. package/dist/crc32.js +44 -4
  16. package/dist/engine.d.ts +12 -1
  17. package/dist/engine.js +336 -42
  18. package/dist/errors.d.ts +17 -0
  19. package/dist/errors.js +27 -1
  20. package/dist/fields.d.ts +36 -0
  21. package/dist/fields.js +139 -0
  22. package/dist/findings.d.ts +2 -1
  23. package/dist/findings.js +44 -9
  24. package/dist/flat.d.ts +33 -0
  25. package/dist/flat.js +486 -0
  26. package/dist/formats.d.ts +7 -0
  27. package/dist/formats.js +5 -1
  28. package/dist/formulas.d.ts +27 -0
  29. package/dist/formulas.js +157 -1
  30. package/dist/index.d.ts +3 -2
  31. package/dist/index.js +8 -1
  32. package/dist/index.mjs +1 -1
  33. package/dist/io.d.ts +62 -1
  34. package/dist/io.js +245 -1
  35. package/dist/links.d.ts +27 -0
  36. package/dist/links.js +106 -1
  37. package/dist/msg.d.ts +8 -0
  38. package/dist/msg.js +251 -0
  39. package/dist/objects.d.ts +45 -0
  40. package/dist/objects.js +204 -0
  41. package/dist/odf.d.ts +66 -2
  42. package/dist/odf.js +181 -64
  43. package/dist/offcrypto.d.ts +54 -0
  44. package/dist/offcrypto.js +647 -0
  45. package/dist/ole.d.ts +12 -0
  46. package/dist/ole.js +139 -0
  47. package/dist/oleobj.d.ts +85 -0
  48. package/dist/oleobj.js +215 -0
  49. package/dist/ooxml.d.ts +83 -2
  50. package/dist/ooxml.js +358 -145
  51. package/dist/package.d.ts +3 -0
  52. package/dist/package.js +21 -0
  53. package/dist/plan.d.ts +104 -0
  54. package/dist/plan.js +73 -0
  55. package/dist/plugins.d.ts +8 -0
  56. package/dist/plugins.js +60 -0
  57. package/dist/ppt.d.ts +8 -0
  58. package/dist/ppt.js +281 -0
  59. package/dist/rewrite-cfb.d.ts +14 -0
  60. package/dist/rewrite-cfb.js +52 -0
  61. package/dist/rewrite-rtf.d.ts +9 -0
  62. package/dist/rewrite-rtf.js +45 -0
  63. package/dist/rewrite-xml.d.ts +36 -0
  64. package/dist/rewrite-xml.js +341 -0
  65. package/dist/rewrite-zip.d.ts +8 -0
  66. package/dist/rewrite-zip.js +498 -0
  67. package/dist/rtf.d.ts +8 -0
  68. package/dist/rtf.js +712 -0
  69. package/dist/rtfcomp.d.ts +7 -0
  70. package/dist/rtfcomp.js +80 -0
  71. package/dist/score.js +1 -1
  72. package/dist/sniff.d.ts +5 -0
  73. package/dist/sniff.js +38 -0
  74. package/dist/types.d.ts +66 -2
  75. package/dist/types.js +33 -0
  76. package/dist/vsd.d.ts +13 -0
  77. package/dist/vsd.js +689 -0
  78. package/dist/word.d.ts +9 -0
  79. package/dist/word.js +194 -0
  80. package/dist/xls.d.ts +27 -0
  81. package/dist/xls.js +208 -0
  82. package/dist/xml.d.ts +30 -0
  83. package/dist/xml.js +45 -23
  84. package/dist/zip-writer.d.ts +4 -0
  85. package/dist/zip-writer.js +20 -7
  86. package/dist/zip.d.ts +9 -2
  87. package/dist/zip.js +61 -4
  88. package/package.json +6 -3
package/dist/biff.js ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RecordReader = exports.R = void 0;
4
+ exports.xlString = xlString;
5
+ exports.parseBoundSheet = parseBoundSheet;
6
+ exports.parseLbl = parseLbl;
7
+ exports.autoName = autoName;
8
+ const errors_1 = require("./errors");
9
+ const io_1 = require("./io");
10
+ const oleobj_1 = require("./oleobj");
11
+ /** Record types this reader acts on (MS-XLS 2.3). */
12
+ exports.R = {
13
+ bof: 0x0809,
14
+ eof: 0x000a,
15
+ filePass: 0x002f,
16
+ continue: 0x003c,
17
+ lbl: 0x0018,
18
+ externName: 0x0023,
19
+ boundSheet: 0x0085,
20
+ template: 0x0060,
21
+ addIn: 0x0087,
22
+ supBook: 0x01ae,
23
+ externSheet: 0x0017,
24
+ obj: 0x005d,
25
+ hlink: 0x01b8,
26
+ };
27
+ /**
28
+ * Reads the records of a workbook stream one at a time, from `start` to the end of the stream. A record's body may
29
+ * run past the stream's end only by being cut short, which stops the reading as Excel stops.
30
+ */
31
+ class RecordReader {
32
+ stream;
33
+ deadline;
34
+ reader;
35
+ pos;
36
+ constructor(cfb, stream, start, deadline) {
37
+ this.stream = stream;
38
+ this.deadline = deadline;
39
+ this.reader = new io_1.Reader(cfb.source(stream), stream.size, 16384, 8);
40
+ this.pos = start;
41
+ }
42
+ async next() {
43
+ if (this.pos + 4 > this.stream.size)
44
+ return undefined;
45
+ const h = await this.reader.read(this.pos, 4);
46
+ const type = h[0] | (h[1] << 8);
47
+ const length = h[2] | (h[3] << 8);
48
+ if (this.pos + 4 + length > this.stream.size)
49
+ return undefined;
50
+ const offset = this.pos;
51
+ const body = await this.reader.read(this.pos + 4, length);
52
+ this.pos += 4 + length;
53
+ if ((offset & 0xffff) < 4)
54
+ (0, errors_1.checkTime)(this.deadline);
55
+ return { type, offset, body };
56
+ }
57
+ }
58
+ exports.RecordReader = RecordReader;
59
+ /** BIFF8 writes strings with a flag byte: 8-bit characters, or 16-bit ones when its low bit is set. */
60
+ function xlString(b, at, chars) {
61
+ if (at >= b.length)
62
+ return { text: '', next: at };
63
+ const wide = (b[at] & 1) === 1;
64
+ const start = at + 1;
65
+ const end = Math.min(b.length, start + chars * (wide ? 2 : 1));
66
+ return { text: wide ? (0, oleobj_1.utf16)(b.subarray(start, end)) : (0, oleobj_1.latin1)(b.subarray(start, end)), next: end };
67
+ }
68
+ /** A BoundSheet record, in BIFF8 or BIFF5. */
69
+ function parseBoundSheet(body, biff8) {
70
+ const position = (body[0] | (body[1] << 8) | (body[2] << 16) | (body[3] << 24)) >>> 0;
71
+ const state = body[4] & 3;
72
+ const type = body[5];
73
+ const cch = body[6] ?? 0;
74
+ const name = biff8 ? xlString(body, 7, cch).text : (0, oleobj_1.latin1)(body.subarray(7, 7 + cch));
75
+ return { position, state, type, name };
76
+ }
77
+ /** Built-in names Excel runs as macros (MS-XLS 2.5.112): Auto_Open and Auto_Activate as the workbook opens, Auto_Close and Auto_Deactivate as it closes. */
78
+ const BUILTIN_NAMES = { 1: 'Auto_Open', 2: 'Auto_Close', 10: 'Auto_Activate', 11: 'Auto_Deactivate' };
79
+ /** A defined name, in BIFF8 or BIFF5, with a built-in name spelled out. */
80
+ function parseLbl(body, biff8) {
81
+ const flags = body[0] | (body[1] << 8);
82
+ const cch = body[3] ?? 0;
83
+ const builtin = (flags & 0x20) !== 0;
84
+ const name = biff8 ? xlString(body, 14, cch).text : (0, oleobj_1.latin1)(body.subarray(14, 14 + cch));
85
+ const shown = builtin && name.length > 0 ? (BUILTIN_NAMES[name.charCodeAt(0)] ?? `built-in name ${name.charCodeAt(0)}`) : name;
86
+ return { name: shown, builtin, hidden: (flags & 1) !== 0 };
87
+ }
88
+ /** The event a defined name runs a macro on, when Excel runs it on one: any name that starts with Auto_Open and so on. */
89
+ function autoName(name) {
90
+ const m = /^(?:_xlnm\.)?(auto_open|auto_close|auto_activate|auto_deactivate)/i.exec(name);
91
+ if (!m)
92
+ return undefined;
93
+ const event = m[1].toLowerCase();
94
+ return { event: `${event[0].toUpperCase()}${event.slice(1, 5)}${event[5].toUpperCase()}${event.slice(6)}`, onOpen: event === 'auto_open' || event === 'auto_activate' };
95
+ }
@@ -0,0 +1,25 @@
1
+ import type { ByteSink, ByteSource } from './types';
2
+ /** A stream or storage to write. A storage's children may come in any order; the writer sorts them. */
3
+ export type CfbNode = {
4
+ kind: 'storage';
5
+ name: string;
6
+ clsid: string;
7
+ children: CfbNode[];
8
+ } | {
9
+ kind: 'stream';
10
+ name: string;
11
+ size: number;
12
+ source: ByteSource;
13
+ };
14
+ /** The root of the file to write: its class id and what it holds. */
15
+ export interface CfbTree {
16
+ clsid: string;
17
+ children: CfbNode[];
18
+ }
19
+ /**
20
+ * Writes a compound file (MS-CFB, version 3 with 512-byte sectors) to an append-only sink. The children of each
21
+ * storage go into a balanced tree in MS-CFB's name order, colored as a valid red-black tree. Streams under 4,096 bytes
22
+ * go into the mini stream. The writer holds the allocation tables and the directory, and reads each stream once, in
23
+ * order, from its source.
24
+ */
25
+ export declare function writeCfb(tree: CfbTree, sink: ByteSink, deadline?: number): Promise<void>;
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeCfb = writeCfb;
4
+ const errors_1 = require("./errors");
5
+ const MINI_SECTOR = 64;
6
+ const MINI_CUTOFF = 4096;
7
+ const FREE = 0xffffffff;
8
+ const END_OF_CHAIN = 0xfffffffe;
9
+ const FAT_SECTOR = 0xfffffffd;
10
+ const DIFAT_SECTOR = 0xfffffffc;
11
+ const NO_STREAM = 0xffffffff;
12
+ const HEADER_FAT = 109;
13
+ /** A GUID as Windows shows it, back to its 16 bytes. '' gives zeros. */
14
+ function guidBytes(clsid) {
15
+ const out = new Uint8Array(16);
16
+ const h = clsid.replace(/-/g, '');
17
+ if (h.length !== 32)
18
+ return out;
19
+ const byte = (i) => Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
20
+ const order = [3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15];
21
+ for (const [i, from] of order.entries())
22
+ out[i] = byte(from);
23
+ return out;
24
+ }
25
+ /** MS-CFB's order of names in a storage: the shorter first, then by their upper-case code units. */
26
+ function compareNames(a, b) {
27
+ if (a.length !== b.length)
28
+ return a.length - b.length;
29
+ const ua = a.toUpperCase();
30
+ const ub = b.toUpperCase();
31
+ return ua < ub ? -1 : ua > ub ? 1 : 0;
32
+ }
33
+ class Bytes {
34
+ b;
35
+ constructor(n) {
36
+ this.b = Buffer.alloc(n);
37
+ }
38
+ u16(at, v) {
39
+ this.b.writeUInt16LE(v, at);
40
+ }
41
+ u32(at, v) {
42
+ this.b.writeUInt32LE(v >>> 0, at);
43
+ }
44
+ }
45
+ /**
46
+ * Writes a compound file (MS-CFB, version 3 with 512-byte sectors) to an append-only sink. The children of each
47
+ * storage go into a balanced tree in MS-CFB's name order, colored as a valid red-black tree. Streams under 4,096 bytes
48
+ * go into the mini stream. The writer holds the allocation tables and the directory, and reads each stream once, in
49
+ * order, from its source.
50
+ */
51
+ async function writeCfb(tree, sink, deadline) {
52
+ const ss = 512;
53
+ const per = ss / 4;
54
+ // The directory: the root, then every storage's children, depth first.
55
+ const entries = [{ name: 'Root Entry', type: 5, clsid: guidBytes(tree.clsid), left: NO_STREAM, right: NO_STREAM, child: NO_STREAM, red: false, start: END_OF_CHAIN, size: 0 }];
56
+ const place = (children) => {
57
+ const sorted = [...children].sort((a, b) => compareNames(a.name, b.name));
58
+ const ids = [];
59
+ for (const c of sorted) {
60
+ ids.push(entries.length);
61
+ entries.push({
62
+ name: c.name,
63
+ type: c.kind === 'storage' ? 1 : 2,
64
+ clsid: c.kind === 'storage' ? guidBytes(c.clsid) : new Uint8Array(16),
65
+ left: NO_STREAM,
66
+ right: NO_STREAM,
67
+ child: NO_STREAM,
68
+ red: false,
69
+ start: END_OF_CHAIN,
70
+ size: c.kind === 'stream' ? c.size : 0,
71
+ node: c,
72
+ });
73
+ }
74
+ // A median split puts every leaf on the last two levels. The deepest level is red and the rest black, so every
75
+ // path from the root down passes the same number of black entries.
76
+ const height = Math.ceil(Math.log2(sorted.length + 1));
77
+ const build = (lo, hi, depth) => {
78
+ if (lo > hi)
79
+ return NO_STREAM;
80
+ const mid = (lo + hi) >>> 1;
81
+ const e = entries[ids[mid]];
82
+ e.left = build(lo, mid - 1, depth + 1);
83
+ e.right = build(mid + 1, hi, depth + 1);
84
+ e.red = depth === height - 1 && height > 1;
85
+ return ids[mid];
86
+ };
87
+ const root = build(0, sorted.length - 1, 0);
88
+ for (const [i, c] of sorted.entries())
89
+ if (c.kind === 'storage')
90
+ entries[ids[i]].child = place(c.children);
91
+ return root;
92
+ };
93
+ entries[0].child = place(tree.children);
94
+ // Sectors: the FAT and the DIFAT, then the mini stream, the large streams, the directory and the mini FAT, then any
95
+ // free sectors the tail needs. The first sector after the header is always a FAT sector, whose sector numbers can
96
+ // never spell a signature, so no stream lands where PDF readers look for a header.
97
+ // Version 3 keeps sizes in 32 bits.
98
+ if (entries.some(e => e.size > 0xffffffff))
99
+ throw new Error('A stream is too large for a version 3 compound file');
100
+ const small = entries.filter(e => e.type === 2 && e.size > 0 && e.size < MINI_CUTOFF);
101
+ const large = entries.filter(e => e.type === 2 && e.size >= MINI_CUTOFF);
102
+ let miniSectors = 0;
103
+ const miniStart = new Map();
104
+ for (const e of small) {
105
+ miniStart.set(e, miniSectors);
106
+ miniSectors += Math.ceil(e.size / MINI_SECTOR);
107
+ }
108
+ const miniStreamSize = miniSectors * MINI_SECTOR;
109
+ const miniStreamSectors = Math.ceil(miniStreamSize / ss);
110
+ const largeSectors = large.reduce((t, e) => t + Math.ceil(e.size / ss), 0);
111
+ const dirSectors = Math.ceil(entries.length / (ss / 128));
112
+ const miniFatSectors = Math.ceil(miniSectors / per);
113
+ const content = miniStreamSectors + largeSectors + dirSectors + miniFatSectors;
114
+ /** Where everything goes with `pad` free sectors at the end. */
115
+ const layout = (pad) => {
116
+ // The FAT must describe its own sectors and the DIFAT's, so their counts grow together until they fit.
117
+ let fatSectors = 0;
118
+ let difatSectors = 0;
119
+ for (;;) {
120
+ const total = content + pad + fatSectors + difatSectors;
121
+ const needFat = Math.ceil(total / per);
122
+ const needDifat = needFat > HEADER_FAT ? Math.ceil((needFat - HEADER_FAT) / (per - 1)) : 0;
123
+ if (needFat === fatSectors && needDifat === difatSectors)
124
+ break;
125
+ fatSectors = needFat;
126
+ difatSectors = needDifat;
127
+ }
128
+ const base = fatSectors + difatSectors;
129
+ let next = base + miniStreamSectors;
130
+ const starts = new Map();
131
+ for (const e of large) {
132
+ starts.set(e, next);
133
+ next += Math.ceil(e.size / ss);
134
+ }
135
+ const dirStart = next;
136
+ const miniFatStart = miniSectors ? dirStart + dirSectors : END_OF_CHAIN;
137
+ const sectors = base + content + pad;
138
+ return { fatSectors, difatSectors, base, starts, dirStart, miniFatStart, pad, sectors, size: (sectors + 1) * ss };
139
+ };
140
+ const apply = (l) => {
141
+ for (const e of small)
142
+ e.start = miniStart.get(e) ?? 0;
143
+ for (const e of large)
144
+ e.start = l.starts.get(e) ?? 0;
145
+ entries[0].start = miniStreamSectors ? l.base : END_OF_CHAIN;
146
+ entries[0].size = miniStreamSize;
147
+ };
148
+ const fatOf = (l) => {
149
+ const fat = new Uint32Array(l.fatSectors * per).fill(FREE);
150
+ const chain = (start, count) => {
151
+ for (let i = 0; i < count; i++)
152
+ fat[start + i] = i === count - 1 ? END_OF_CHAIN : start + i + 1;
153
+ };
154
+ for (let i = 0; i < l.fatSectors; i++)
155
+ fat[i] = FAT_SECTOR;
156
+ for (let i = 0; i < l.difatSectors; i++)
157
+ fat[l.fatSectors + i] = DIFAT_SECTOR;
158
+ if (miniStreamSectors)
159
+ chain(l.base, miniStreamSectors);
160
+ for (const e of large)
161
+ chain(e.start, Math.ceil(e.size / ss));
162
+ chain(l.dirStart, dirSectors);
163
+ if (miniFatSectors)
164
+ chain(l.miniFatStart, miniFatSectors);
165
+ return fat;
166
+ };
167
+ const directoryOf = () => {
168
+ const dir = new Bytes(dirSectors * ss);
169
+ for (let id = 0; id < dirSectors * (ss / 128); id++) {
170
+ const at = id * 128;
171
+ const e = entries[id];
172
+ if (!e) {
173
+ dir.u32(at + 0x44, NO_STREAM);
174
+ dir.u32(at + 0x48, NO_STREAM);
175
+ dir.u32(at + 0x4c, NO_STREAM);
176
+ continue;
177
+ }
178
+ const name = Buffer.from(e.name, 'utf16le').subarray(0, 62);
179
+ name.copy(dir.b, at);
180
+ dir.u16(at + 0x40, name.length + 2);
181
+ dir.b[at + 0x42] = e.type;
182
+ dir.b[at + 0x43] = e.red ? 0 : 1;
183
+ dir.u32(at + 0x44, e.left);
184
+ dir.u32(at + 0x48, e.right);
185
+ dir.u32(at + 0x4c, e.child);
186
+ Buffer.from(e.clsid).copy(dir.b, at + 0x50);
187
+ dir.u32(at + 0x74, e.type === 1 ? 0 : e.size === 0 && e.type === 2 ? END_OF_CHAIN : e.start);
188
+ dir.u32(at + 0x78, e.size);
189
+ }
190
+ return dir.b;
191
+ };
192
+ const miniFat = new Uint32Array(miniFatSectors * per).fill(FREE);
193
+ for (const e of small) {
194
+ const first = miniStart.get(e) ?? 0;
195
+ const n = Math.ceil(e.size / MINI_SECTOR);
196
+ for (let i = 0; i < n; i++)
197
+ miniFat[first + i] = i === n - 1 ? END_OF_CHAIN : first + i + 1;
198
+ }
199
+ /**
200
+ * The bytes the file holds in [from, to), with streams read from their sources. The header and the DIFAT read as
201
+ * zeros, since what the header holds is fixed and the DIFAT holds only sector numbers.
202
+ */
203
+ const bytesAt = async (l, from, to) => {
204
+ const out = Buffer.alloc(Math.max(0, to - from));
205
+ const put = (at, b) => {
206
+ const s = Math.max(at, from);
207
+ const e = Math.min(at + b.length, to);
208
+ if (e > s)
209
+ out.set(b.subarray(s - at, e - at), s - from);
210
+ };
211
+ const putStream = async (at, e) => {
212
+ const node = e.node;
213
+ const s = Math.max(at, from);
214
+ const end = Math.min(at + e.size, to);
215
+ if (end <= s || node?.kind !== 'stream')
216
+ return;
217
+ put(s, await node.source.read(s - at, end - s));
218
+ };
219
+ const fat = fatOf(l);
220
+ put(ss, Buffer.from(fat.buffer));
221
+ for (const e of small)
222
+ await putStream((l.base + 1) * ss + (miniStart.get(e) ?? 0) * MINI_SECTOR, e);
223
+ for (const e of large)
224
+ await putStream((e.start + 1) * ss, e);
225
+ put((l.dirStart + 1) * ss, directoryOf());
226
+ if (miniFatSectors)
227
+ put((l.miniFatStart + 1) * ss, Buffer.from(miniFat.buffer));
228
+ return out;
229
+ };
230
+ // ZIP readers look for an end record in the last 65,557 bytes. A stream that ends with one, such as an embedded
231
+ // package, would make the file open as that archive too, so free sectors of zeros push it out of their reach.
232
+ const ZIP_WINDOW = 22 + 0xffff;
233
+ const END_RECORD = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
234
+ const lastEndRecord = async (l) => {
235
+ const from = Math.max(ss, l.size - ZIP_WINDOW);
236
+ const found = (await bytesAt(l, from, l.size)).lastIndexOf(END_RECORD);
237
+ return found < 0 ? -1 : from + found;
238
+ };
239
+ let plan = layout(0);
240
+ apply(plan);
241
+ const endAt = await lastEndRecord(plan);
242
+ if (endAt >= 0) {
243
+ plan = layout(Math.ceil((endAt + 1 - (plan.size - ZIP_WINDOW)) / ss));
244
+ apply(plan);
245
+ }
246
+ // Both rules hold by construction. Checking them costs two small reads and turns a mistake into a failed write.
247
+ if ((await lastEndRecord(plan)) >= 0)
248
+ throw new Error('A ZIP end record is still within reach of ZIP readers');
249
+ if ((await bytesAt(plan, 0, Math.min(1024, plan.size))).includes('%PDF-'))
250
+ throw new Error('A PDF header is within the first 1024 bytes');
251
+ const { fatSectors, difatSectors, dirStart, miniFatStart } = plan;
252
+ const fatStart = 0;
253
+ const difatStart = fatSectors;
254
+ const fat = fatOf(plan);
255
+ const header = new Bytes(ss);
256
+ Buffer.from('d0cf11e0a1b11ae1', 'hex').copy(header.b, 0);
257
+ header.u16(0x18, 0x3e);
258
+ header.u16(0x1a, 3);
259
+ header.u16(0x1c, 0xfffe);
260
+ header.u16(0x1e, 9);
261
+ header.u16(0x20, 6);
262
+ header.u32(0x2c, fatSectors);
263
+ header.u32(0x30, dirStart);
264
+ header.u32(0x38, MINI_CUTOFF);
265
+ header.u32(0x3c, miniFatStart);
266
+ header.u32(0x40, miniFatSectors);
267
+ header.u32(0x44, difatSectors ? difatStart : END_OF_CHAIN);
268
+ header.u32(0x48, difatSectors);
269
+ for (let i = 0; i < HEADER_FAT; i++)
270
+ header.u32(0x4c + 4 * i, i < fatSectors ? fatStart + i : FREE);
271
+ await sink.write(header.b);
272
+ await sink.write(Buffer.from(fat.buffer));
273
+ if (difatSectors) {
274
+ const d = new Bytes(difatSectors * ss);
275
+ const rest = Math.max(0, fatSectors - HEADER_FAT);
276
+ for (let k = 0; k < difatSectors; k++) {
277
+ for (let i = 0; i < per - 1; i++) {
278
+ const n = k * (per - 1) + i;
279
+ d.u32(k * ss + 4 * i, n < rest ? fatStart + HEADER_FAT + n : FREE);
280
+ }
281
+ d.u32(k * ss + 4 * (per - 1), k === difatSectors - 1 ? END_OF_CHAIN : difatStart + k + 1);
282
+ }
283
+ await sink.write(d.b);
284
+ }
285
+ /** Copies a stream's bytes and pads them to `unit`. */
286
+ const copy = async (e, unit) => {
287
+ const node = e.node;
288
+ if (node?.kind !== 'stream')
289
+ return;
290
+ for (let at = 0; at < e.size; at += 262144) {
291
+ (0, errors_1.checkTime)(deadline);
292
+ const want = Math.min(262144, e.size - at);
293
+ const b = await node.source.read(at, want);
294
+ if (b.length !== want)
295
+ throw Object.assign(new Error(`A stream returned ${b.length} bytes where ${want} were expected`), { code: 'EIO' });
296
+ await sink.write(b);
297
+ }
298
+ const pad = (unit - (e.size % unit)) % unit;
299
+ if (pad)
300
+ await sink.write(new Uint8Array(pad));
301
+ };
302
+ for (const e of small)
303
+ await copy(e, MINI_SECTOR);
304
+ const miniPad = miniStreamSectors * ss - miniStreamSize;
305
+ if (miniPad)
306
+ await sink.write(new Uint8Array(miniPad));
307
+ for (const e of large)
308
+ await copy(e, ss);
309
+ await sink.write(directoryOf());
310
+ if (miniFatSectors)
311
+ await sink.write(Buffer.from(miniFat.buffer));
312
+ for (let i = 0; i < plan.pad; i++)
313
+ await sink.write(new Uint8Array(ss));
314
+ }
package/dist/cfb.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { type ByteSource, OfficeDetail as D } from './types';
2
+ /** The first eight bytes of every compound file. */
3
+ export declare const CFB_SIGNATURE: Uint8Array<ArrayBuffer>;
4
+ export interface CfbLimits {
5
+ entries: number;
6
+ decompressedBytes: number;
7
+ totalDecompressedBytes: number;
8
+ }
9
+ export interface CfbEntry {
10
+ /** The entry's position in the directory. */
11
+ id: number;
12
+ name: string;
13
+ kind: 'root' | 'storage' | 'stream';
14
+ /** The class id, as Windows writes a GUID, in upper case. */
15
+ clsid: string;
16
+ size: number;
17
+ parent?: CfbEntry;
18
+ children: CfbEntry[];
19
+ }
20
+ /** Something the reader saw that it does not refuse. Each is a strip finding, since a rewrite leaves the bytes out. */
21
+ export interface CfbNote {
22
+ detail: D.HiddenData | D.TrailingBytes;
23
+ bytes: number;
24
+ }
25
+ /** A GUID as Windows shows it: the first three fields little-endian, upper case, without braces. */
26
+ export declare function guid(b: Uint8Array, at?: number): string;
27
+ /** An entry name with control and format characters written as \xNN, so a location can show it. */
28
+ export declare const showName: (name: string) => string;
29
+ /** The entry's path from the root, its names joined by "/", which no name may hold. */
30
+ export declare function entryPath(e: CfbEntry): string;
31
+ /**
32
+ * A compound file (MS-CFB), read from its header, its allocation tables and its directory. Every chain is followed as
33
+ * far as its stream needs and every sector has one owner, so two readers cannot read the file differently. Reading a
34
+ * stream maps its offsets through its chain; nothing is held but the tables.
35
+ */
36
+ export declare class CompoundFile {
37
+ private readonly reader;
38
+ readonly sectorSize: number;
39
+ readonly root: CfbEntry;
40
+ /** Every entry the directory tree reaches, the root first, each storage before its children. */
41
+ readonly entries: CfbEntry[];
42
+ readonly notes: CfbNote[];
43
+ private readonly miniStream;
44
+ private readonly deadline;
45
+ private readonly streamChains;
46
+ private constructor();
47
+ /** The child of a storage with this name, compared ignoring case. */
48
+ child(storage: CfbEntry, name: string): CfbEntry | undefined;
49
+ /** Reads a compound file. The caller has seen its signature. */
50
+ static open(source: ByteSource, limits: CfbLimits, deadline?: number): Promise<CompoundFile>;
51
+ /** Bytes of the mini stream, by their offset in it. */
52
+ private readMini;
53
+ private readChain;
54
+ /** A random-access source over a stream's content. */
55
+ source(e: CfbEntry): ByteSource;
56
+ /** A stream's whole content. Callers read only streams whose size they bounded. */
57
+ bytes(e: CfbEntry): Promise<Uint8Array>;
58
+ }