@patrickdk77/office-defuse 0.1.1 → 0.2.1
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/README.md +211 -43
- package/dist/cfb-writer.d.ts +25 -0
- package/dist/cfb-writer.js +314 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +357 -0
- package/dist/contained.d.ts +46 -0
- package/dist/contained.js +152 -0
- package/dist/context.d.ts +107 -12
- package/dist/context.js +101 -24
- package/dist/detect.d.ts +13 -0
- package/dist/detect.js +83 -0
- package/dist/engine.d.ts +66 -1
- package/dist/engine.js +290 -35
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +27 -1
- package/dist/fields.d.ts +20 -13
- package/dist/fields.js +65 -23
- package/dist/findings.d.ts +2 -1
- package/dist/findings.js +17 -1
- package/dist/flat.d.ts +33 -0
- package/dist/flat.js +555 -0
- package/dist/formats.d.ts +9 -0
- package/dist/formats.js +7 -1
- package/dist/formulas.d.ts +27 -0
- package/dist/formulas.js +157 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +11 -1
- package/dist/index.mjs +1 -1
- package/dist/io.d.ts +62 -1
- package/dist/io.js +245 -1
- package/dist/links.d.ts +27 -0
- package/dist/links.js +106 -1
- package/dist/msg.d.ts +3 -2
- package/dist/msg.js +172 -18
- package/dist/objects.d.ts +8 -5
- package/dist/objects.js +90 -20
- package/dist/odf.d.ts +87 -1
- package/dist/odf.js +204 -76
- package/dist/offcrypto.d.ts +24 -1
- package/dist/offcrypto.js +308 -69
- package/dist/ole.d.ts +19 -2
- package/dist/ole.js +98 -58
- package/dist/oleobj.d.ts +11 -0
- package/dist/oleobj.js +20 -3
- package/dist/ooxml.d.ts +108 -2
- package/dist/ooxml.js +392 -109
- package/dist/package.d.ts +14 -0
- package/dist/package.js +23 -7
- package/dist/plan.d.ts +104 -0
- package/dist/plan.js +73 -0
- package/dist/plugins.d.ts +8 -0
- package/dist/plugins.js +35 -0
- package/dist/ppt.js +45 -5
- package/dist/rewrite-cfb.d.ts +14 -0
- package/dist/rewrite-cfb.js +52 -0
- package/dist/rewrite-rtf.d.ts +9 -0
- package/dist/rewrite-rtf.js +45 -0
- package/dist/rewrite-xml.d.ts +36 -0
- package/dist/rewrite-xml.js +341 -0
- package/dist/rewrite-zip.d.ts +8 -0
- package/dist/rewrite-zip.js +498 -0
- package/dist/rtf.js +216 -42
- package/dist/rtfcomp.d.ts +7 -0
- package/dist/rtfcomp.js +80 -0
- package/dist/score.js +1 -1
- package/dist/sniff.d.ts +5 -0
- package/dist/sniff.js +38 -0
- package/dist/types.d.ts +66 -6
- package/dist/types.js +14 -0
- package/dist/vsd.d.ts +13 -0
- package/dist/vsd.js +689 -0
- package/dist/word.d.ts +4 -4
- package/dist/word.js +70 -23
- package/dist/xls.d.ts +10 -4
- package/dist/xls.js +58 -10
- package/dist/xml.d.ts +30 -0
- package/dist/xml.js +45 -23
- package/dist/zip-writer.d.ts +4 -0
- package/dist/zip-writer.js +20 -7
- package/dist/zip.d.ts +11 -2
- package/dist/zip.js +67 -6
- package/package.json +4 -1
package/dist/links.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isAbsoluteTarget = exports.isInsidePackage = exports.isLocalTarget = exports.isFragment = void 0;
|
|
3
|
+
exports.LabelSites = exports.isAbsoluteTarget = exports.isInsidePackage = exports.isLocalTarget = exports.isFragment = void 0;
|
|
4
4
|
exports.checkLink = checkLink;
|
|
5
|
+
exports.labelSite = labelSite;
|
|
6
|
+
exports.mismatchedLabel = mismatchedLabel;
|
|
5
7
|
const types_1 = require("./types");
|
|
6
8
|
const xml_1 = require("./xml");
|
|
7
9
|
const IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
@@ -143,3 +145,106 @@ exports.isInsidePackage = isInsidePackage;
|
|
|
143
145
|
/** Whether an OOXML relationship target is not a part name, whatever TargetMode says: a scheme, a drive or a network path. */
|
|
144
146
|
const isAbsoluteTarget = (rawTarget) => [rawTarget, decoded(rawTarget)].some(t => SCHEME.test(clean(t)) || t.startsWith('\\') || t.startsWith('//'));
|
|
145
147
|
exports.isAbsoluteTarget = isAbsoluteTarget;
|
|
148
|
+
/** A host as LabelSites compares it. */
|
|
149
|
+
const siteName = (h) => h
|
|
150
|
+
.toLowerCase()
|
|
151
|
+
.replace(/^www\./, '')
|
|
152
|
+
.replace(/\.$/, '');
|
|
153
|
+
/**
|
|
154
|
+
* The hosts a label names, as a lookup: has() answers whether a host is one of them or a subdomain of one, or one of
|
|
155
|
+
* them is a subdomain of the host. No public-suffix list is needed: evil.co.uk is not under mybank.co.uk. The same
|
|
156
|
+
* comparison as pdf-defuse's.
|
|
157
|
+
*/
|
|
158
|
+
class LabelSites {
|
|
159
|
+
hosts = new Set();
|
|
160
|
+
/** Every domain some label host is a subdomain of. */
|
|
161
|
+
parents = new Set();
|
|
162
|
+
constructor(names) {
|
|
163
|
+
for (const n of names) {
|
|
164
|
+
const x = siteName(n);
|
|
165
|
+
this.hosts.add(x);
|
|
166
|
+
for (let i = x.indexOf('.'); i >= 0; i = x.indexOf('.', i + 1))
|
|
167
|
+
this.parents.add(x.slice(i + 1));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
get size() {
|
|
171
|
+
return this.hosts.size;
|
|
172
|
+
}
|
|
173
|
+
has(host) {
|
|
174
|
+
const y = siteName(host);
|
|
175
|
+
if (this.hosts.has(y) || this.parents.has(y))
|
|
176
|
+
return true;
|
|
177
|
+
for (let i = y.indexOf('.'); i >= 0; i = y.indexOf('.', i + 1))
|
|
178
|
+
if (this.hosts.has(y.slice(i + 1)))
|
|
179
|
+
return true;
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.LabelSites = LabelSites;
|
|
184
|
+
/**
|
|
185
|
+
* Top-level domains a bare host in a label may end in: the common generic ones and the country codes. A name that
|
|
186
|
+
* ends in anything else, such as an Excel function (BETA.DIST) or a file (moon.htm), is not read as an address.
|
|
187
|
+
*/
|
|
188
|
+
const TLDS = new Set(('com net org edu gov mil int info biz name pro mobi asia tel travel jobs museum aero coop app dev io ai co me tv cc ly ' +
|
|
189
|
+
'xyz online site top shop club store tech live page blog cloud email link news ' +
|
|
190
|
+
'ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi bj bm bn bo br bs bt bw by bz ca cd cf cg ' +
|
|
191
|
+
'ch ci ck cl cm cn cr cu cv cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gd ge gf gg gh gi ' +
|
|
192
|
+
'gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ' +
|
|
193
|
+
'ky kz la lb lc li lk lr ls lt lu lv ma mc md mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni ' +
|
|
194
|
+
'nl no np nr nu nz om pa pe pf pg ph pk pl pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn ' +
|
|
195
|
+
'so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tw tz ua ug uk us uy uz va vc ve vg vi vn vu wf ws ' +
|
|
196
|
+
'ye yt za zm zw').split(' '));
|
|
197
|
+
/** The most of a label that is read. */
|
|
198
|
+
const MAX_LABEL = 4096;
|
|
199
|
+
/**
|
|
200
|
+
* The site a label names when the label is itself an address: a web URL, a mail address, or a host with or without a
|
|
201
|
+
* path that starts with www. or ends in a known top-level domain. Text that only mentions an address, an Excel
|
|
202
|
+
* function name and a file name name no site. Excel's function names are all capitals, as typed hosts never are, so
|
|
203
|
+
* a bare host in capitals counts only after WWW.
|
|
204
|
+
*/
|
|
205
|
+
function labelSite(raw) {
|
|
206
|
+
const t = raw
|
|
207
|
+
.slice(0, MAX_LABEL)
|
|
208
|
+
.replace(/\p{Cc}/gu, '')
|
|
209
|
+
.trim();
|
|
210
|
+
if (/^https?:\/\//i.test(t)) {
|
|
211
|
+
try {
|
|
212
|
+
return new URL(t).hostname.toLowerCase() || undefined;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const mail = /^(?:mailto:)?[^\s@/]+@([a-z0-9-]+(?:\.[a-z0-9-]+)+)$/i.exec(t);
|
|
219
|
+
const host = mail?.[1] ?? /^([a-z0-9-]+(?:\.[a-z0-9-]+)+)(?::\d+)?(?:[/?#]\S*)?$/i.exec(t)?.[1];
|
|
220
|
+
if (!host)
|
|
221
|
+
return undefined;
|
|
222
|
+
const h = host.toLowerCase();
|
|
223
|
+
if (h.startsWith('www.'))
|
|
224
|
+
return h;
|
|
225
|
+
if (!mail && host === host.toUpperCase() && /[A-Z]/.test(host))
|
|
226
|
+
return undefined;
|
|
227
|
+
return TLDS.has(h.slice(h.lastIndexOf('.') + 1)) ? h : undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The first label of a safe link that is an address, and an address of another site than the one the link opens, or
|
|
231
|
+
* undefined. A label is text the reader sees for the link: its text, its tooltip, a display name. A subdomain or a
|
|
232
|
+
* parent of the label's site counts as the same site, as in pdf-defuse. A target that fails on its own gets its own
|
|
233
|
+
* finding instead.
|
|
234
|
+
*/
|
|
235
|
+
function mismatchedLabel(target, labels) {
|
|
236
|
+
if (!labels.some(Boolean))
|
|
237
|
+
return undefined;
|
|
238
|
+
const v = checkLink(target);
|
|
239
|
+
if (v.category !== types_1.OfficeCategory.Link || v.detail !== types_1.OfficeDetail.Safe || !v.host)
|
|
240
|
+
return undefined;
|
|
241
|
+
for (const label of labels) {
|
|
242
|
+
const site = label ? labelSite(label) : undefined;
|
|
243
|
+
// A site without a dot, where the target has one, is a label cut short or a local alias, and names no site.
|
|
244
|
+
if (!site || (!site.includes('.') && v.host.includes('.')))
|
|
245
|
+
continue;
|
|
246
|
+
if (!new LabelSites([site]).has(v.host))
|
|
247
|
+
return label;
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
package/dist/msg.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type CfbEntry, type CompoundFile } from './cfb';
|
|
2
|
-
import {
|
|
2
|
+
import { RunContext } from './context';
|
|
3
|
+
import type { CompoundPlan } from './plan';
|
|
3
4
|
/**
|
|
4
5
|
* Inspects an Outlook message (MS-OXMSG): each attachment is reported with its name and type, and an attachment by
|
|
5
6
|
* reference with what it points to. An embedded message's own attachments are read as well.
|
|
6
7
|
*/
|
|
7
|
-
export declare function messageRules(ctx: RunContext, cfb: CompoundFile, message: CfbEntry, depth?: number): Promise<void>;
|
|
8
|
+
export declare function messageRules(ctx: RunContext, cfb: CompoundFile, message: CfbEntry, depth?: number, plan?: CompoundPlan): Promise<void>;
|
package/dist/msg.js
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.messageRules = messageRules;
|
|
4
4
|
const cfb_1 = require("./cfb");
|
|
5
|
+
const contained_1 = require("./contained");
|
|
5
6
|
const context_1 = require("./context");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const io_1 = require("./io");
|
|
6
9
|
const oleobj_1 = require("./oleobj");
|
|
10
|
+
const rewrite_rtf_1 = require("./rewrite-rtf");
|
|
11
|
+
const rtf_1 = require("./rtf");
|
|
12
|
+
const rtfcomp_1 = require("./rtfcomp");
|
|
7
13
|
const types_1 = require("./types");
|
|
8
14
|
/** Property ids of an attachment (MS-OXPROPS). */
|
|
9
15
|
const P = { method: 0x3705, longName: 0x3707, name: 0x3704, display: 0x3001, mime: 0x370e, data: 0x3701, longPath: 0x370d, path: 0x3708 };
|
|
@@ -11,18 +17,165 @@ const P = { method: 0x3705, longName: 0x3707, name: 0x3704, display: 0x3001, mim
|
|
|
11
17
|
const METHODS = { 1: 'by value', 2: 'by reference', 3: 'by reference, resolved', 4: 'by reference only', 5: 'embedded message', 6: 'OLE object', 7: 'by web reference' };
|
|
12
18
|
/** Embedded messages can hold messages in turn. Past this depth they are still reported, and not read further. */
|
|
13
19
|
const MAX_DEPTH = 16;
|
|
20
|
+
/** A message's body properties (MS-OXPROPS): PidTagRtfCompressed, and PidTagHtml as binary, Unicode or 8-bit text. */
|
|
21
|
+
const RTF_BODY = 0x10090102;
|
|
22
|
+
const HTML_BODIES = [0x10130102, 0x1013001f, 0x1013001e];
|
|
23
|
+
/** The stream that holds a variable-size property (MS-OXMSG 2.1.4.1). */
|
|
24
|
+
const streamName = (tag) => `__SUBSTG1.0_${tag.toString(16).toUpperCase().padStart(8, '0')}`;
|
|
14
25
|
/**
|
|
15
26
|
* Inspects an Outlook message (MS-OXMSG): each attachment is reported with its name and type, and an attachment by
|
|
16
27
|
* reference with what it points to. An embedded message's own attachments are read as well.
|
|
17
28
|
*/
|
|
18
|
-
async function messageRules(ctx, cfb, message, depth = 0) {
|
|
29
|
+
async function messageRules(ctx, cfb, message, depth = 0, plan) {
|
|
30
|
+
const edit = { attachmentsDropped: 0, drop: new Set(), sizes: new Map() };
|
|
31
|
+
await bodyRules(ctx, cfb, message, edit, plan);
|
|
19
32
|
for (const a of message.children) {
|
|
20
33
|
if (a.kind !== 'storage' || !/^__attach_version1\.0_#[0-9A-F]{8}$/i.test(a.name))
|
|
21
34
|
continue;
|
|
22
|
-
|
|
35
|
+
// An attachment a strip finding names goes with its whole storage.
|
|
36
|
+
if ((await attachmentRules(ctx, cfb, a, depth, plan)).some(r => r.action === 'strip') && plan) {
|
|
37
|
+
plan.drop.add(a);
|
|
38
|
+
edit.attachmentsDropped++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (plan && (edit.attachmentsDropped || edit.drop.size || edit.sizes.size))
|
|
42
|
+
await editProperties(ctx, cfb, message, edit, plan);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The property stream of a message after the rewrite (MS-OXMSG 2.4). Its header counts the attachments, at the same
|
|
46
|
+
* place in a message and in an embedded one, and the count drops by the attachments that went. Each property held in
|
|
47
|
+
* a stream has an entry giving that stream's size: the entry goes with a body that went, and takes the new size of a
|
|
48
|
+
* body that was rewritten.
|
|
49
|
+
*/
|
|
50
|
+
async function editProperties(ctx, cfb, message, edit, plan) {
|
|
51
|
+
const s = message.children.find(c => c.kind === 'stream' && c.name.toUpperCase() === '__PROPERTIES_VERSION1.0');
|
|
52
|
+
if (!s || s.size < 24 || s.size > ctx.settings.decompressedBytes)
|
|
53
|
+
return;
|
|
54
|
+
const b = Buffer.from(await cfb.bytes(s));
|
|
55
|
+
const headerSize = message === cfb.root ? 32 : 24;
|
|
56
|
+
if (edit.attachmentsDropped)
|
|
57
|
+
b.writeUInt32LE(Math.max(0, b.readUInt32LE(20) - edit.attachmentsDropped), 20);
|
|
58
|
+
const kept = [b.subarray(0, headerSize)];
|
|
59
|
+
for (let at = headerSize; at + 16 <= b.length; at += 16) {
|
|
60
|
+
const entry = Buffer.from(b.subarray(at, at + 16));
|
|
61
|
+
const tag = entry.readUInt32LE(0);
|
|
62
|
+
if (edit.drop.has(tag))
|
|
63
|
+
continue;
|
|
64
|
+
const size = edit.sizes.get(tag);
|
|
65
|
+
if (size !== undefined)
|
|
66
|
+
entry.writeUInt32LE(size, 8);
|
|
67
|
+
kept.push(entry);
|
|
68
|
+
}
|
|
69
|
+
plan.replace.set(s, await (0, contained_1.spillOf)(ctx.run, Buffer.concat(kept)));
|
|
70
|
+
}
|
|
71
|
+
/** A stream of the message storage by property tag. */
|
|
72
|
+
const bodyStream = (message, tag) => message.children.find(c => c.kind === 'stream' && c.name.toUpperCase() === streamName(tag));
|
|
73
|
+
/** Every byte a sink holds. */
|
|
74
|
+
async function bytesOf(sink) {
|
|
75
|
+
const chunks = [];
|
|
76
|
+
for await (const c of sink.read())
|
|
77
|
+
chunks.push(Buffer.from(c));
|
|
78
|
+
return Buffer.concat(chunks);
|
|
79
|
+
}
|
|
80
|
+
async function* once(b) {
|
|
81
|
+
yield b;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A message's bodies. The RTF body goes to the plugins as the RTF it decompresses to, and a plugin's copy is stored
|
|
85
|
+
* back uncompressed. When no plugin takes it, the RTF rules read it: a strip finding inside rewrites it, and a finding
|
|
86
|
+
* that rejects it, or a body that does not decompress, makes it go, leaving the plain text body. The HTML body goes
|
|
87
|
+
* to the plugins, and when none takes it, it stays, as no rule names it.
|
|
88
|
+
*/
|
|
89
|
+
async function bodyRules(ctx, cfb, message, edit, plan) {
|
|
90
|
+
const replace = async (stream, tag, bytes) => {
|
|
91
|
+
plan?.replace.set(stream, await (0, contained_1.spillOf)(ctx.run, bytes));
|
|
92
|
+
edit.sizes.set(tag, bytes.length);
|
|
93
|
+
};
|
|
94
|
+
const drop = (stream, tag) => {
|
|
95
|
+
plan?.drop.add(stream);
|
|
96
|
+
edit.drop.add(tag);
|
|
97
|
+
};
|
|
98
|
+
const rtf = bodyStream(message, RTF_BODY);
|
|
99
|
+
if (rtf) {
|
|
100
|
+
const location = (0, cfb_1.entryPath)(rtf);
|
|
101
|
+
const body = rtf.size <= ctx.settings.decompressedBytes ? (0, rtfcomp_1.decompressRtf)(await cfb.bytes(rtf), ctx.settings.decompressedBytes) : undefined;
|
|
102
|
+
if (!body) {
|
|
103
|
+
if (ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreadableBody, location, { stream: location }).action === 'strip')
|
|
104
|
+
drop(rtf, RTF_BODY);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: 'RTF body', declaredType: 'application/rtf', location, chunks: () => once(body) });
|
|
108
|
+
if (decision) {
|
|
109
|
+
if (!decision.keep)
|
|
110
|
+
drop(rtf, RTF_BODY);
|
|
111
|
+
else if (decision.replacement)
|
|
112
|
+
await replace(rtf, RTF_BODY, (0, rtfcomp_1.storeRtf)(await bytesOf(decision.replacement)));
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
await rtfRules(ctx, body, location, rtf, edit, plan);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const tag of HTML_BODIES) {
|
|
119
|
+
const html = bodyStream(message, tag);
|
|
120
|
+
if (!html || html.size > ctx.settings.decompressedBytes)
|
|
121
|
+
continue;
|
|
122
|
+
const raw = await cfb.bytes(html);
|
|
123
|
+
const unicode = (tag & 0xffff) === 0x001f;
|
|
124
|
+
const text = unicode ? Buffer.from((0, oleobj_1.utf16)(raw).replace(/\0+$/, ''), 'utf8') : raw;
|
|
125
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: 'HTML body', declaredType: 'text/html', location: (0, cfb_1.entryPath)(html), chunks: () => once(text) });
|
|
126
|
+
if (decision && !decision.keep)
|
|
127
|
+
drop(html, tag);
|
|
128
|
+
else if (decision?.replacement) {
|
|
129
|
+
const out = await bytesOf(decision.replacement);
|
|
130
|
+
await replace(html, tag, unicode ? Buffer.from(`${out.toString('utf8')}\0`, 'utf16le') : out);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
23
133
|
}
|
|
24
134
|
}
|
|
25
|
-
|
|
135
|
+
/**
|
|
136
|
+
* The RTF rules over a message's RTF body, reporting into this run with the body's place. A strip finding rewrites the
|
|
137
|
+
* body, and a reject drops it.
|
|
138
|
+
*/
|
|
139
|
+
async function rtfRules(ctx, body, location, stream, edit, plan) {
|
|
140
|
+
const sub = new context_1.RunContext(ctx.factory, ctx.xmlLimits, ctx.settings, ctx.run);
|
|
141
|
+
// The RTF reader leaves its cuts where a whole file's rewrite finds them, which is the message's own.
|
|
142
|
+
const saved = ctx.run.output;
|
|
143
|
+
let cuts;
|
|
144
|
+
try {
|
|
145
|
+
await (0, rtf_1.inspectRtf)(sub, (0, io_1.bufferSource)(body), body.length);
|
|
146
|
+
const out = ctx.run.output;
|
|
147
|
+
if (out?.kind === 'rtf')
|
|
148
|
+
cuts = out;
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if ((0, errors_1.stopsTree)(e) || !(e instanceof errors_1.StopError))
|
|
152
|
+
throw e;
|
|
153
|
+
sub.report(e.category, e.detail, undefined, { ...e.data, reason: e.data?.reason ?? e.message });
|
|
154
|
+
cuts = undefined;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
ctx.run.output = saved;
|
|
158
|
+
}
|
|
159
|
+
const found = sub.findings;
|
|
160
|
+
(0, contained_1.pushContained)(ctx, found, location, 'RTF body');
|
|
161
|
+
if (found.some(f => f.action === 'reject') || !cuts) {
|
|
162
|
+
if (found.some(f => f.action !== 'info')) {
|
|
163
|
+
plan?.drop.add(stream);
|
|
164
|
+
edit.drop.add(RTF_BODY);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!found.some(f => f.action === 'strip') || !plan)
|
|
169
|
+
return;
|
|
170
|
+
const sink = new io_1.SpillSink(ctx.run.temp, ctx.run.threshold);
|
|
171
|
+
ctx.run.held.push(sink);
|
|
172
|
+
await (0, rewrite_rtf_1.writeRtf)(ctx.run, (0, io_1.bufferSource)(body), body.length, cuts.cuts, sink);
|
|
173
|
+
await sink.close();
|
|
174
|
+
const stored = (0, rtfcomp_1.storeRtf)(await bytesOf(sink));
|
|
175
|
+
plan.replace.set(stream, await (0, contained_1.spillOf)(ctx.run, stored));
|
|
176
|
+
edit.sizes.set(RTF_BODY, stored.length);
|
|
177
|
+
}
|
|
178
|
+
async function attachmentRules(ctx, cfb, a, depth, plan) {
|
|
26
179
|
const location = (0, cfb_1.entryPath)(a);
|
|
27
180
|
const props = await properties(cfb, a);
|
|
28
181
|
const text = async (id) => {
|
|
@@ -48,37 +201,38 @@ async function attachmentRules(ctx, cfb, a, depth) {
|
|
|
48
201
|
info.type = (0, context_1.clip)(declaredType);
|
|
49
202
|
if (method >= 2 && method <= 4) {
|
|
50
203
|
const path = (await text(P.longPath)) || (await text(P.path));
|
|
51
|
-
|
|
52
|
-
ctx.external(types_1.OfficeDetail.Other, path, location, { source: 'attachment by reference', ...(name ? { name: (0, context_1.clip)(name) } : {}) }, 'attachment');
|
|
53
|
-
return;
|
|
204
|
+
return path ? [ctx.external(types_1.OfficeDetail.Other, path, location, { source: 'attachment by reference', ...(name ? { name: (0, context_1.clip)(name) } : {}) }, 'attachment')] : [];
|
|
54
205
|
}
|
|
55
206
|
if (method === 7) {
|
|
56
207
|
const path = (await text(P.longPath)) || (await text(P.path));
|
|
57
|
-
|
|
58
|
-
ctx.external(types_1.OfficeDetail.Other, path, location, { source: 'attachment by web reference', ...(name ? { name: (0, context_1.clip)(name) } : {}) }, 'attachment');
|
|
59
|
-
return;
|
|
208
|
+
return path ? [ctx.external(types_1.OfficeDetail.Other, path, location, { source: 'attachment by web reference', ...(name ? { name: (0, context_1.clip)(name) } : {}) }, 'attachment')] : [];
|
|
60
209
|
}
|
|
61
210
|
if (!data)
|
|
62
|
-
return;
|
|
211
|
+
return [];
|
|
63
212
|
if (data.kind === 'stream') {
|
|
64
|
-
attachment(ctx, { name: name || undefined, declaredType: declaredType || undefined,
|
|
65
|
-
return;
|
|
213
|
+
return [await attachment(ctx, cfb, data, { name: name || undefined, declaredType: declaredType || undefined, location }, info, plan)];
|
|
66
214
|
}
|
|
67
215
|
// An embedded message or an OLE object is a storage of this file. The object rules read an object's storage, and
|
|
68
216
|
// an embedded message's attachments are read here.
|
|
69
217
|
info.size = 0;
|
|
70
218
|
if (method === 5)
|
|
71
219
|
info.type ??= 'application/vnd.ms-outlook';
|
|
72
|
-
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Attachment, location, info);
|
|
220
|
+
const r = ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Attachment, location, info);
|
|
73
221
|
if (method === 5 && depth < MAX_DEPTH)
|
|
74
|
-
await messageRules(ctx, cfb, data, depth + 1);
|
|
222
|
+
await messageRules(ctx, cfb, data, depth + 1, plan);
|
|
223
|
+
return [r];
|
|
75
224
|
}
|
|
76
225
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
226
|
+
* An attached file held in a stream. It goes to the plugins, whose verdict decides whether it stays. When no plugin
|
|
227
|
+
* takes it, EMBEDDED/ATTACHMENT does.
|
|
79
228
|
*/
|
|
80
|
-
function attachment(ctx, file, info) {
|
|
81
|
-
|
|
229
|
+
async function attachment(ctx, cfb, data, file, info, plan) {
|
|
230
|
+
const source = cfb.source(data);
|
|
231
|
+
const decision = await (0, contained_1.decideContained)(ctx, { ...file, chunks: () => (0, io_1.chunksOf)(source, data.size) });
|
|
232
|
+
if (decision?.replacement)
|
|
233
|
+
plan?.replace.set(data, decision.replacement);
|
|
234
|
+
const r = ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Attachment, file.location, { ...info, size: data.size }, (0, contained_1.presence)(decision));
|
|
235
|
+
return decision && !decision.keep ? { ...r, action: 'strip' } : r;
|
|
82
236
|
}
|
|
83
237
|
/** The fixed-size values of a property stream (MS-OXMSG 2.4.2), by property id. An attachment's has an 8-byte header. */
|
|
84
238
|
async function properties(cfb, storage) {
|
package/dist/objects.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type CfbEntry,
|
|
2
|
-
import { type
|
|
1
|
+
import { type CfbEntry, CompoundFile } from './cfb';
|
|
2
|
+
import { type ContainedDecision } from './contained';
|
|
3
|
+
import { type Reported, type RunContext } from './context';
|
|
3
4
|
import { type PackagedFile } from './oleobj';
|
|
5
|
+
import type { CompoundPlan } from './plan';
|
|
4
6
|
/** Equation Editor 3.0's ProgID, which rtfobj matches as a prefix. */
|
|
5
7
|
export declare const isEquationClass: (name: string) => boolean;
|
|
6
8
|
/** Reports what the reader of a compound file inside this one noted, at the place it sits. */
|
|
@@ -8,9 +10,10 @@ export declare function nestedNotes(ctx: RunContext, cfb: CompoundFile, location
|
|
|
8
10
|
/**
|
|
9
11
|
* The rules every compound file gets: VBA projects, embedded objects and controls, wherever they sit in the tree.
|
|
10
12
|
* `prefix` names where the file sits when it is inside another, and `rootIsObject` says that its root is itself an
|
|
11
|
-
* OLE object's storage, as an object in a presentation or an RTF file is.
|
|
13
|
+
* OLE object's storage, as an object in a presentation or an RTF file is. `plan`, given for the file the rewrite
|
|
14
|
+
* writes, collects the storages a strip finding names.
|
|
12
15
|
*/
|
|
13
|
-
export declare function compoundRules(ctx: RunContext, cfb: CompoundFile, prefix: string, rootIsObject: boolean): Promise<void>;
|
|
16
|
+
export declare function compoundRules(ctx: RunContext, cfb: CompoundFile, prefix: string, rootIsObject: boolean, plan?: CompoundPlan, rootDecision?: ContainedDecision): Promise<void>;
|
|
14
17
|
/** What an OLE object's storage says about the object. */
|
|
15
18
|
export interface ObjectInfo {
|
|
16
19
|
clsid: string;
|
|
@@ -37,6 +40,6 @@ export declare function describeObject(cfb: CompoundFile, e: CfbEntry, _location
|
|
|
37
40
|
export declare function linkedObject(ctx: RunContext, d: {
|
|
38
41
|
target: string;
|
|
39
42
|
url: boolean;
|
|
40
|
-
}, location: string, data: Record<string, string>):
|
|
43
|
+
}, location: string, data: Record<string, string>): Reported;
|
|
41
44
|
/** What a finding shows of a Packager object's file: its name and paths, and its size when the object holds it. */
|
|
42
45
|
export declare function packageData(p: PackagedFile): Record<string, string | number>;
|
package/dist/objects.js
CHANGED
|
@@ -7,8 +7,13 @@ exports.describeObject = describeObject;
|
|
|
7
7
|
exports.linkedObject = linkedObject;
|
|
8
8
|
exports.packageData = packageData;
|
|
9
9
|
const cfb_1 = require("./cfb");
|
|
10
|
+
const cfb_writer_1 = require("./cfb-writer");
|
|
11
|
+
const contained_1 = require("./contained");
|
|
10
12
|
const context_1 = require("./context");
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
const io_1 = require("./io");
|
|
11
15
|
const oleobj_1 = require("./oleobj");
|
|
16
|
+
const rewrite_cfb_1 = require("./rewrite-cfb");
|
|
12
17
|
const types_1 = require("./types");
|
|
13
18
|
/** The class ids of Equation Editor 3.0 and its kin, which CVE-2017-11882 and CVE-2018-0802 exploit, as oletools lists them. */
|
|
14
19
|
const EQUATION_CLSIDS = new Set(['0002CE02-0000-0000-C000-000000000046', '00021700-0000-0000-C000-000000000046', '0003000B-0000-0000-C000-000000000046', '0004A6B0-0000-0000-C000-000000000046']);
|
|
@@ -34,23 +39,29 @@ const at = (prefix, e) => [prefix, (0, cfb_1.entryPath)(e)].filter(Boolean).join
|
|
|
34
39
|
/**
|
|
35
40
|
* The rules every compound file gets: VBA projects, embedded objects and controls, wherever they sit in the tree.
|
|
36
41
|
* `prefix` names where the file sits when it is inside another, and `rootIsObject` says that its root is itself an
|
|
37
|
-
* OLE object's storage, as an object in a presentation or an RTF file is.
|
|
42
|
+
* OLE object's storage, as an object in a presentation or an RTF file is. `plan`, given for the file the rewrite
|
|
43
|
+
* writes, collects the storages a strip finding names.
|
|
38
44
|
*/
|
|
39
|
-
async function compoundRules(ctx, cfb, prefix, rootIsObject) {
|
|
45
|
+
async function compoundRules(ctx, cfb, prefix, rootIsObject, plan, rootDecision) {
|
|
40
46
|
const projects = new Set();
|
|
47
|
+
const drop = (r, e) => {
|
|
48
|
+
if (r.action === 'strip' && e.kind !== 'root')
|
|
49
|
+
plan?.drop.add(e);
|
|
50
|
+
};
|
|
41
51
|
for (const e of cfb.entries) {
|
|
42
52
|
if (e.kind === 'stream')
|
|
43
53
|
continue;
|
|
44
54
|
const child = (name) => e.children.find(c => upper(c) === name);
|
|
45
|
-
// A VBA project: a VBA storage with a dir stream, which names the modules (MS-OVBA 2.2).
|
|
55
|
+
// A VBA project: a VBA storage with a dir stream, which names the modules (MS-OVBA 2.2). The storage that holds
|
|
56
|
+
// the project goes, or the VBA storage when that is the root.
|
|
46
57
|
const vba = child('VBA');
|
|
47
58
|
if (vba?.kind === 'storage' && vba.children.some(c => c.kind === 'stream' && upper(c) === 'DIR')) {
|
|
48
59
|
projects.add(e);
|
|
49
|
-
ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, at(prefix, vba), { storage: at(prefix, vba) });
|
|
60
|
+
drop(ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, at(prefix, vba), { storage: at(prefix, vba) }), e.kind === 'root' ? vba : e);
|
|
50
61
|
}
|
|
51
62
|
if (e.kind === 'storage' && (upper(e) === '_VBA_PROJECT_CUR' || upper(e) === 'MACROS') && e.children.length && !projects.has(e)) {
|
|
52
63
|
// Excel's and Word's project storages, without the dir stream a whole project has.
|
|
53
|
-
ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, at(prefix, e), { storage: at(prefix, e) });
|
|
64
|
+
drop(ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, at(prefix, e), { storage: at(prefix, e) }), e);
|
|
54
65
|
}
|
|
55
66
|
const names = new Set(e.children.map(upper));
|
|
56
67
|
const parent = e.parent ? upper(e.parent) : '';
|
|
@@ -60,7 +71,8 @@ async function compoundRules(ctx, cfb, prefix, rootIsObject) {
|
|
|
60
71
|
(parent === 'OBJECTPOOL' && e.parent?.parent?.kind === 'root') ||
|
|
61
72
|
(e.parent?.kind === 'root' && /^MBD[0-9A-F]{8}$/i.test(e.name));
|
|
62
73
|
if (isObject)
|
|
63
|
-
await objectRules(ctx, cfb, e, at(prefix, e))
|
|
74
|
+
for (const r of await objectRules(ctx, cfb, e, at(prefix, e), plan, e.kind === 'root' ? rootDecision : undefined))
|
|
75
|
+
drop(r, e);
|
|
64
76
|
}
|
|
65
77
|
}
|
|
66
78
|
/** Reads an OLE object's storage: its class, its CompObj and OLE streams, and Packager's native data. */
|
|
@@ -92,8 +104,63 @@ async function describeObject(cfb, e, _location) {
|
|
|
92
104
|
}
|
|
93
105
|
return info;
|
|
94
106
|
}
|
|
95
|
-
/**
|
|
96
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Shows an embedded object of the compound file the rewrite writes to the plugins: the file a Packager object holds,
|
|
109
|
+
* or the object's storage written out as a compound file of its own. Bytes a plugin wrote take the file's place in
|
|
110
|
+
* the Ole10Native stream, or the storage's place as the storages and streams of the compound file they hold.
|
|
111
|
+
*/
|
|
112
|
+
async function objectFile(ctx, cfb, e, d, location, plan) {
|
|
113
|
+
const run = ctx.run;
|
|
114
|
+
const native = e.children.find(c => c.kind === 'stream' && upper(c) === '\x01OLE10NATIVE');
|
|
115
|
+
if (d.pkg?.embedded && native) {
|
|
116
|
+
const pkg = d.pkg;
|
|
117
|
+
const source = cfb.source(native);
|
|
118
|
+
const start = 4 + pkg.dataAt;
|
|
119
|
+
const length = Math.max(0, Math.min(pkg.size, native.size - start));
|
|
120
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: pkg.fileName || undefined, location, chunks: () => (0, io_1.rangeChunks)(source, start, length) });
|
|
121
|
+
if (decision?.replacement) {
|
|
122
|
+
const all = await cfb.bytes(native);
|
|
123
|
+
plan.replace.set(native, await (0, contained_1.spillOf)(run, (0, oleobj_1.sizedStream)((0, oleobj_1.replaceSized)(all.subarray(4), pkg.dataAt, pkg.size, await (0, io_1.readAll)(decision.replacement)))));
|
|
124
|
+
}
|
|
125
|
+
return decision;
|
|
126
|
+
}
|
|
127
|
+
const written = new io_1.SpillSink(run.temp, run.threshold);
|
|
128
|
+
run.held.push(written);
|
|
129
|
+
await (0, cfb_writer_1.writeCfb)((0, rewrite_cfb_1.subtree)(cfb, e), written, run.deadline);
|
|
130
|
+
await written.close();
|
|
131
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: d.userType || d.progId || undefined, location, chunks: () => written.read() });
|
|
132
|
+
const replacement = decision?.replacement;
|
|
133
|
+
if (!replacement)
|
|
134
|
+
return decision;
|
|
135
|
+
const source = replacement.source();
|
|
136
|
+
run.sources.push(source);
|
|
137
|
+
let graft;
|
|
138
|
+
try {
|
|
139
|
+
graft = await cfb_1.CompoundFile.open(source, ctx.settings, run.deadline);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (!(err instanceof errors_1.StopError))
|
|
143
|
+
throw err;
|
|
144
|
+
ctx.removal(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.PluginFailed, location, { reason: 'its output is not a compound file' });
|
|
145
|
+
return { keep: false };
|
|
146
|
+
}
|
|
147
|
+
plan.grafts.set(e, graft);
|
|
148
|
+
// The output check writes the storage out again, so it expects the bytes this writer makes of the graft.
|
|
149
|
+
const again = new io_1.SpillSink(run.temp, run.threshold);
|
|
150
|
+
run.held.push(again);
|
|
151
|
+
await (0, cfb_writer_1.writeCfb)((0, rewrite_cfb_1.subtree)(graft, graft.root), again, run.deadline);
|
|
152
|
+
await again.close();
|
|
153
|
+
const expected = run.kept.get(replacement.digest());
|
|
154
|
+
if (expected)
|
|
155
|
+
run.kept.set(again.digest(), expected);
|
|
156
|
+
return decision;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One OLE object's storage: a control, a linked object, or an embedded one, with what it holds. Returns its findings,
|
|
160
|
+
* and a strip when a plugin removed the object. In the file the rewrite writes, `plan` is set and an embedded object
|
|
161
|
+
* goes to the plugins first. `given` is the verdict on an object whose container showed it to the plugins already.
|
|
162
|
+
*/
|
|
163
|
+
async function objectRules(ctx, cfb, e, location, plan, given) {
|
|
97
164
|
const d = await describeObject(cfb, e, location);
|
|
98
165
|
const data = { storage: location };
|
|
99
166
|
if (d.clsid)
|
|
@@ -102,21 +169,25 @@ async function objectRules(ctx, cfb, e, location) {
|
|
|
102
169
|
data.progId = (0, context_1.clip)(d.progId);
|
|
103
170
|
if (d.userType)
|
|
104
171
|
data.className = (0, context_1.clip)(d.userType);
|
|
105
|
-
if (d.control)
|
|
106
|
-
ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, location, d.controlName ? { ...data, name: (0, context_1.clip)(d.controlName) } : data);
|
|
107
|
-
|
|
108
|
-
}
|
|
172
|
+
if (d.control)
|
|
173
|
+
return [ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, location, d.controlName ? { ...data, name: (0, context_1.clip)(d.controlName) } : data)];
|
|
174
|
+
const found = [];
|
|
109
175
|
if (d.pkg) {
|
|
110
176
|
Object.assign(data, packageData(d.pkg));
|
|
111
177
|
if (!d.pkg.embedded && d.pkg.sourcePath)
|
|
112
|
-
ctx.external(types_1.OfficeDetail.Object, d.pkg.sourcePath, location, { storage: location }, 'package link');
|
|
178
|
+
found.push(ctx.external(types_1.OfficeDetail.Object, d.pkg.sourcePath, location, { storage: location }, 'package link'));
|
|
113
179
|
}
|
|
114
180
|
if (d.linked)
|
|
115
|
-
linkedObject(ctx, d, location, { storage: location, ...(d.clsid ? { clsid: d.clsid } : {}) });
|
|
116
|
-
else
|
|
117
|
-
ctx
|
|
181
|
+
found.push(linkedObject(ctx, d, location, { storage: location, ...(d.clsid ? { clsid: d.clsid } : {}) }));
|
|
182
|
+
else {
|
|
183
|
+
const decision = given ?? (plan ? await objectFile(ctx, cfb, e, d, location, plan) : undefined);
|
|
184
|
+
found.push(ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, location, data, (0, contained_1.presence)(decision)));
|
|
185
|
+
if (decision && !decision.keep)
|
|
186
|
+
found.push({ action: 'strip' });
|
|
187
|
+
}
|
|
118
188
|
if (d.equation)
|
|
119
|
-
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Equation, location, data);
|
|
189
|
+
found.push(ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Equation, location, data));
|
|
190
|
+
return found;
|
|
120
191
|
}
|
|
121
192
|
/**
|
|
122
193
|
* Reports a linked object. A URL moniker's target loads from outside the file whatever its text looks like: a
|
|
@@ -124,9 +195,8 @@ async function objectRules(ctx, cfb, e, location) {
|
|
|
124
195
|
*/
|
|
125
196
|
function linkedObject(ctx, d, location, data) {
|
|
126
197
|
if (d.target && !d.url)
|
|
127
|
-
ctx.external(types_1.OfficeDetail.Object, d.target, location, data, 'linked object');
|
|
128
|
-
|
|
129
|
-
ctx.report(types_1.OfficeCategory.External, types_1.OfficeDetail.Object, location, d.target ? { ...data, target: (0, context_1.clip)(d.target, 2000) } : data);
|
|
198
|
+
return ctx.external(types_1.OfficeDetail.Object, d.target, location, data, 'linked object');
|
|
199
|
+
return ctx.report(types_1.OfficeCategory.External, types_1.OfficeDetail.Object, location, d.target ? { ...data, target: (0, context_1.clip)(d.target, 2000) } : data);
|
|
130
200
|
}
|
|
131
201
|
/** What a finding shows of a Packager object's file: its name and paths, and its size when the object holds it. */
|
|
132
202
|
function packageData(p) {
|
package/dist/odf.d.ts
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
|
-
import { type ZipContext } from './context';
|
|
1
|
+
import { type PlanContext, type ZipContext } from './context';
|
|
2
|
+
import { type Format } from './formats';
|
|
3
|
+
import { OfficeCategory as C, OfficeDetail as D } from './types';
|
|
4
|
+
import type { XmlElement, XmlHandler } from './xml';
|
|
5
|
+
import { type ZipEntry } from './zip';
|
|
6
|
+
export declare const NS: {
|
|
7
|
+
manifest: string;
|
|
8
|
+
office: string;
|
|
9
|
+
script: string;
|
|
10
|
+
presentation: string;
|
|
11
|
+
text: string;
|
|
12
|
+
draw: string;
|
|
13
|
+
table: string;
|
|
14
|
+
form: string;
|
|
15
|
+
meta: string;
|
|
16
|
+
style: string;
|
|
17
|
+
database: string;
|
|
18
|
+
chart: string;
|
|
19
|
+
xlink: string;
|
|
20
|
+
};
|
|
21
|
+
/** An attribute by normalized namespace and local name. */
|
|
22
|
+
export declare function oattr(el: XmlElement, ns: string, local: string): string | undefined;
|
|
23
|
+
/** The ODF_NS-normalized namespace of `ns`, exported for the rewrite. */
|
|
24
|
+
export declare const odfNamespace: (ns: string) => string;
|
|
25
|
+
/** The xlink:href of an element, for the rewrite. */
|
|
26
|
+
export declare const hrefOf: (el: XmlElement) => string | undefined;
|
|
27
|
+
/** A path inside the package as the manifest and the zip name it: without "./" and leading slashes. */
|
|
28
|
+
export declare const packagePath: (href: string) => string;
|
|
29
|
+
/**
|
|
30
|
+
* The rules that read inside one XML entry of an ODF package, or inside a flat OpenDocument file. A flat file has no
|
|
31
|
+
* entries, so in one every relative reference names a file beside the document, which loads from outside it.
|
|
32
|
+
*/
|
|
33
|
+
export declare class EntryRules implements XmlHandler {
|
|
34
|
+
private readonly ctx;
|
|
35
|
+
private readonly entry;
|
|
36
|
+
/** Whether an object refers to each entry, by key. Only entries are kept, so a part full of references costs no memory. */
|
|
37
|
+
private readonly oleRefs;
|
|
38
|
+
private readonly flat;
|
|
39
|
+
/** Depth of the office:scripts element being read, 0 outside one. */
|
|
40
|
+
private scriptsDepth;
|
|
41
|
+
/** An office:script element being read, with whether it holds anything. */
|
|
42
|
+
private script?;
|
|
43
|
+
/** A link element being read, whose text and title go into the label check at its end. */
|
|
44
|
+
private label?;
|
|
45
|
+
constructor(ctx: PlanContext, entry: string,
|
|
46
|
+
/** Whether an object refers to each entry, by key. Only entries are kept, so a part full of references costs no memory. */
|
|
47
|
+
oleRefs: Map<string, boolean>, flat?: boolean);
|
|
48
|
+
/**
|
|
49
|
+
* Whether a reference names an entry of the package. LibreOffice's vnd.sun.star.Package: URLs name one too. In a
|
|
50
|
+
* flat file no entry exists, so only such a URL stays inside, naming nothing.
|
|
51
|
+
*/
|
|
52
|
+
private inside;
|
|
53
|
+
private report;
|
|
54
|
+
/** Records the edit for the element when the finding strips. */
|
|
55
|
+
private edit;
|
|
56
|
+
start(raw: XmlElement): void;
|
|
57
|
+
text(t: string): void;
|
|
58
|
+
end(el: XmlElement): void;
|
|
59
|
+
private scriptEvent;
|
|
60
|
+
private presentationEvent;
|
|
61
|
+
}
|
|
62
|
+
export interface Manifest {
|
|
63
|
+
paths: Set<string>;
|
|
64
|
+
root?: string;
|
|
65
|
+
encrypted: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** The code folder an entry sits in, as the folder's path and its finding. */
|
|
68
|
+
export declare function folderOf(name: string): {
|
|
69
|
+
prefix: string;
|
|
70
|
+
detail: D;
|
|
71
|
+
category: C;
|
|
72
|
+
} | undefined;
|
|
73
|
+
/** What names an OpenDocument package: the media type its format comes from, and the entries that give it. */
|
|
74
|
+
export interface OdfNaming {
|
|
75
|
+
format: Format;
|
|
76
|
+
/** The media type the format was named by. */
|
|
77
|
+
mediaType: string;
|
|
78
|
+
mimetype?: ZipEntry;
|
|
79
|
+
manifestEntry?: ZipEntry;
|
|
80
|
+
manifest?: Manifest;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Names an OpenDocument package's format by the media type in its mimetype entry or, without one, its manifest's root
|
|
84
|
+
* entry. Throws when the two name different formats, or the media type is not one this package reads. `layout` hears,
|
|
85
|
+
* with the entry it concerns, of each way the mimetype entry and the manifest leave the layout OpenDocument asks for.
|
|
86
|
+
*/
|
|
87
|
+
export declare function nameOdf(ctx: ZipContext, layout?: (location: string, reason: string) => void): Promise<OdfNaming>;
|
|
2
88
|
/** Inspects an OpenDocument package. */
|
|
3
89
|
export declare function inspectOdf(ctx: ZipContext): Promise<void>;
|