@patrickdk77/office-defuse 0.1.1 → 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.
- package/README.md +197 -42
- 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/engine.d.ts +12 -1
- package/dist/engine.js +276 -31
- 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 +486 -0
- package/dist/formats.d.ts +7 -0
- package/dist/formats.js +5 -1
- package/dist/formulas.d.ts +27 -0
- package/dist/formulas.js +157 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -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 +65 -1
- package/dist/odf.js +181 -64
- package/dist/offcrypto.d.ts +24 -1
- package/dist/offcrypto.js +308 -69
- package/dist/ole.js +17 -5
- package/dist/oleobj.d.ts +11 -0
- package/dist/oleobj.js +20 -3
- package/dist/ooxml.d.ts +81 -0
- package/dist/ooxml.js +342 -81
- package/dist/plan.d.ts +104 -0
- package/dist/plan.js +73 -0
- package/dist/plugins.d.ts +8 -0
- package/dist/plugins.js +60 -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 +214 -41
- 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 +43 -6
- package/dist/types.js +14 -0
- package/dist/vsd.d.ts +13 -0
- package/dist/vsd.js +689 -0
- package/dist/word.js +51 -17
- package/dist/xls.js +38 -4
- 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 +9 -2
- package/dist/zip.js +61 -4
- package/package.json +4 -1
package/dist/ooxml.js
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.relsSource = exports.R_NS = exports.SHEET_NS = exports.WORD_NS = exports.isXmlType = exports.TYPE = exports.REL = exports.XPS_NS = exports.XPS_MAIN = exports.RELS_NS = exports.CONTENT_TYPES_NS = void 0;
|
|
4
|
+
exports.resolveTarget = resolveTarget;
|
|
5
|
+
exports.resolvePartRef = resolvePartRef;
|
|
6
|
+
exports.xpsResources = xpsResources;
|
|
7
|
+
exports.readRelationships = readRelationships;
|
|
3
8
|
exports.inspectOoxml = inspectOoxml;
|
|
4
9
|
const biff_1 = require("./biff");
|
|
10
|
+
const contained_1 = require("./contained");
|
|
5
11
|
const context_1 = require("./context");
|
|
6
12
|
const errors_1 = require("./errors");
|
|
7
13
|
const fields_1 = require("./fields");
|
|
8
14
|
const formats_1 = require("./formats");
|
|
9
15
|
const formulas_1 = require("./formulas");
|
|
10
16
|
const links_1 = require("./links");
|
|
17
|
+
const plan_1 = require("./plan");
|
|
11
18
|
const types_1 = require("./types");
|
|
12
19
|
const xlsb_1 = require("./xlsb");
|
|
13
20
|
const xml_1 = require("./xml");
|
|
14
21
|
const zip_1 = require("./zip");
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
exports.CONTENT_TYPES_NS = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
|
23
|
+
exports.RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
|
17
24
|
const T = 'http://schemas.openxmlformats.org/officedocument/2006/relationships/';
|
|
18
25
|
const S = 'http://purl.oclc.org/ooxml/officedocument/relationships/';
|
|
19
26
|
const MS = 'http://schemas.microsoft.com/office/2006/relationships/';
|
|
20
27
|
const both = (name) => [T + name, S + name];
|
|
28
|
+
/** The relationship types that name an XPS package's FixedDocumentSequence, lowercased. */
|
|
29
|
+
exports.XPS_MAIN = { xps: 'http://schemas.microsoft.com/xps/2005/06/fixedrepresentation', oxps: 'http://schemas.openxps.org/oxps/v1.0/fixedrepresentation' };
|
|
30
|
+
/** The XPS and OpenXPS markup namespaces, lowercased. */
|
|
31
|
+
exports.XPS_NS = new Set(['http://schemas.microsoft.com/xps/2005/06', 'http://schemas.openxps.org/oxps/v1.0']);
|
|
21
32
|
/** Relationship types, lowercased, as OPC compares them without regard to case. */
|
|
22
|
-
|
|
23
|
-
main: new Set([...both('officedocument'), 'http://schemas.microsoft.com/visio/2010/relationships/document']),
|
|
33
|
+
exports.REL = {
|
|
34
|
+
main: new Set([...both('officedocument'), 'http://schemas.microsoft.com/visio/2010/relationships/document', exports.XPS_MAIN.xps, exports.XPS_MAIN.oxps]),
|
|
24
35
|
hyperlink: new Set(both('hyperlink')),
|
|
25
36
|
template: new Set(both('attachedtemplate')),
|
|
26
37
|
image: new Set([...both('image'), 'http://schemas.microsoft.com/office/2007/relationships/hdphoto']),
|
|
@@ -46,7 +57,7 @@ const REACHED_AS = new Map([
|
|
|
46
57
|
[`${MS}activexcontrolbinary`, AS.controlBinary],
|
|
47
58
|
]);
|
|
48
59
|
/** Content types, lowercased. */
|
|
49
|
-
|
|
60
|
+
exports.TYPE = {
|
|
50
61
|
vba: new Set(['application/vnd.ms-office.vbaproject']),
|
|
51
62
|
macrosheet: new Set(['application/vnd.ms-excel.macrosheet+xml', 'application/vnd.ms-excel.intlmacrosheet+xml', 'application/vnd.ms-excel.macrosheet', 'application/vnd.ms-excel.intlmacrosheet']),
|
|
52
63
|
ole: new Set(['application/vnd.openxmlformats-officedocument.oleobject']),
|
|
@@ -55,30 +66,50 @@ const TYPE = {
|
|
|
55
66
|
vml: 'application/vnd.openxmlformats-officedocument.vmldrawing',
|
|
56
67
|
xlsbExternalLink: 'application/vnd.ms-excel.externallink',
|
|
57
68
|
};
|
|
69
|
+
/** Whether the rules read a part of this content type as XML. */
|
|
70
|
+
const isXmlType = (ct) => ct !== undefined && (ct.endsWith('+xml') || ct === 'application/xml' || ct === 'text/xml' || ct === exports.TYPE.vml);
|
|
71
|
+
exports.isXmlType = isXmlType;
|
|
72
|
+
/**
|
|
73
|
+
* Binary parts that belong to the package's own format rather than being files of their own: the records of a binary
|
|
74
|
+
* workbook, printer settings, obfuscated fonts, VBA project signatures and the origin of digital signatures.
|
|
75
|
+
*/
|
|
76
|
+
const formatBinary = (ct) => ct.startsWith('application/vnd.ms-excel.') ||
|
|
77
|
+
ct.endsWith('.printersettings') ||
|
|
78
|
+
ct === 'application/vnd.openxmlformats-officedocument.obfuscatedfont' ||
|
|
79
|
+
ct === 'application/vnd.ms-package.obfuscated-opentype' ||
|
|
80
|
+
ct.startsWith('application/vnd.ms-office.vbaprojectsignature') ||
|
|
81
|
+
ct === 'application/vnd.openxmlformats-package.digital-signature-origin';
|
|
58
82
|
/** Relationship types that leave the package, by the finding they make. Any other is EXTERNAL/OTHER. */
|
|
59
83
|
const EXTERNAL_KINDS = [
|
|
60
|
-
[REL.template, types_1.OfficeDetail.Template],
|
|
84
|
+
[exports.REL.template, types_1.OfficeDetail.Template],
|
|
61
85
|
[new Set([...both('oleobject'), ...both('package')]), types_1.OfficeDetail.Object],
|
|
62
|
-
[REL.image, types_1.OfficeDetail.Image],
|
|
63
|
-
[REL.frame, types_1.OfficeDetail.Frame],
|
|
64
|
-
[REL.subDocument, types_1.OfficeDetail.Include],
|
|
65
|
-
[REL.workbook, types_1.OfficeDetail.Workbook],
|
|
86
|
+
[exports.REL.image, types_1.OfficeDetail.Image],
|
|
87
|
+
[exports.REL.frame, types_1.OfficeDetail.Frame],
|
|
88
|
+
[exports.REL.subDocument, types_1.OfficeDetail.Include],
|
|
89
|
+
[exports.REL.workbook, types_1.OfficeDetail.Workbook],
|
|
66
90
|
];
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
exports.WORD_NS = new Set(['http://schemas.openxmlformats.org/wordprocessingml/2006/main', 'http://purl.oclc.org/ooxml/wordprocessingml/main']);
|
|
92
|
+
exports.SHEET_NS = new Set(['http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'http://purl.oclc.org/ooxml/spreadsheetml/main']);
|
|
69
93
|
const XDR_NS = new Set(['http://schemas.openxmlformats.org/drawingml/2006/spreadsheetdrawing', 'http://purl.oclc.org/ooxml/drawingml/spreadsheetdrawing']);
|
|
70
|
-
|
|
94
|
+
exports.R_NS = new Set(['http://schemas.openxmlformats.org/officedocument/2006/relationships', 'http://purl.oclc.org/ooxml/officedocument/relationships']);
|
|
71
95
|
const CUSTOM_UI_NS = new Set(['http://schemas.microsoft.com/office/2006/01/customui', 'http://schemas.microsoft.com/office/2009/07/customui']);
|
|
72
96
|
const VML_EXCEL_NS = 'urn:schemas-microsoft-com:office:excel';
|
|
73
97
|
const VISIO_NS = 'http://schemas.microsoft.com/office/visio/2012/main';
|
|
98
|
+
const EXTENDED_NS = new Set(['http://schemas.openxmlformats.org/officedocument/2006/extended-properties', 'http://purl.oclc.org/ooxml/officedocument/extendedproperties']);
|
|
99
|
+
/**
|
|
100
|
+
* The attributes of a SpreadsheetML data connection that name where it loads from (ECMA-376 18.13): an ODC file or an
|
|
101
|
+
* offline cube on the connection, a connection string and command, a web query's URL, or a text file.
|
|
102
|
+
*/
|
|
103
|
+
const CONNECTION_SOURCES = { connection: ['odcFile', 'sourceFile'], dbPr: ['connection', 'command'], webPr: ['url'], textPr: ['sourceFile'] };
|
|
74
104
|
const VISIO_CODE = /\b(RUNADDON|RUNADDONWARGS|CALLTHIS|RUNMACRO|OPENFILE|DOOLEVERB)\s*\(/i;
|
|
75
105
|
/** A Word attribute, qualified as Word writes it or unqualified. */
|
|
76
|
-
const wordAttr = (el, local) => el.attributes.find(a => a.local === local && (a.ns === '' || WORD_NS.has(a.ns.toLowerCase())))?.value;
|
|
106
|
+
const wordAttr = (el, local) => el.attributes.find(a => a.local === local && (a.ns === '' || exports.WORD_NS.has(a.ns.toLowerCase())))?.value;
|
|
77
107
|
/** The key of the part a relationships part belongs to, '' for the package, or undefined for any other part. */
|
|
78
108
|
const relsSource = (relsName) => {
|
|
79
109
|
const m = /^(.*?)_rels\/([^/]*)\.rels$/i.exec(relsName);
|
|
80
110
|
return m ? (0, zip_1.nameKey)(m[1] + m[2]) : undefined;
|
|
81
111
|
};
|
|
112
|
+
exports.relsSource = relsSource;
|
|
82
113
|
/**
|
|
83
114
|
* Resolves an internal target against the folder of the part it belongs to, as RFC 3986 resolves a relative
|
|
84
115
|
* reference: a ".." at the root of the package stays at the root. LibreOffice writes "../customXml/item1.xml" in the
|
|
@@ -96,6 +127,52 @@ function resolveTarget(relsName, target) {
|
|
|
96
127
|
}
|
|
97
128
|
return (0, zip_1.nameKey)(out.join('/'));
|
|
98
129
|
}
|
|
130
|
+
/** A reference in an XPS part to another part: an absolute part name, or a path relative to the part's folder. */
|
|
131
|
+
function resolvePartRef(part, ref) {
|
|
132
|
+
const folder = part.includes('/') ? part.slice(0, part.lastIndexOf('/') + 1) : '';
|
|
133
|
+
const path = (ref.startsWith('/') ? ref.slice(1) : folder + ref).split('#')[0];
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const seg of path.split('/')) {
|
|
136
|
+
if (seg === '..')
|
|
137
|
+
out.pop();
|
|
138
|
+
else if (seg !== '.' && seg !== '')
|
|
139
|
+
out.push(seg);
|
|
140
|
+
}
|
|
141
|
+
return (0, zip_1.nameKey)(out.join('/'));
|
|
142
|
+
}
|
|
143
|
+
/** The URIs an XPS ImageSource names: the value itself, or a ColorConvertedBitmap's image and color profile. */
|
|
144
|
+
function imageSourceUris(value) {
|
|
145
|
+
const t = value.trim();
|
|
146
|
+
if (!t.startsWith('{'))
|
|
147
|
+
return [t];
|
|
148
|
+
const words = t
|
|
149
|
+
.replace(/^\{|\}$/g, '')
|
|
150
|
+
.trim()
|
|
151
|
+
.split(/\s+/);
|
|
152
|
+
return words[0] === 'ColorConvertedBitmap' ? words.slice(1) : [];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The resources an XPS element names in its attributes, as the attribute and the URI: a document, page or resource
|
|
156
|
+
* dictionary's Source, a font's FontUri, an image brush's image and profile, and a ContextColor's profile.
|
|
157
|
+
*/
|
|
158
|
+
function xpsResources(el) {
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const a of el.attributes) {
|
|
161
|
+
if (a.ns !== '')
|
|
162
|
+
continue;
|
|
163
|
+
if (a.local === 'Source' || a.local === 'FontUri')
|
|
164
|
+
out.push([a.local, a.value.trim()]);
|
|
165
|
+
else if (a.local === 'ImageSource')
|
|
166
|
+
for (const uri of imageSourceUris(a.value))
|
|
167
|
+
out.push([a.local, uri]);
|
|
168
|
+
else {
|
|
169
|
+
const m = /^\s*ContextColor\s+(\S+)/.exec(a.value);
|
|
170
|
+
if (m)
|
|
171
|
+
out.push([a.local, m[1]]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out.filter(([, uri]) => uri !== '');
|
|
175
|
+
}
|
|
99
176
|
const duplicate = (ctx, location, kind, name) => ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.DuplicateEntry, location, { kind, name: (0, context_1.clip)(name) });
|
|
100
177
|
/**
|
|
101
178
|
* The defaults and overrides of [Content_Types].xml. Only those that type an entry of the archive are kept, so a
|
|
@@ -108,7 +185,7 @@ async function readContentTypes(ctx, entry) {
|
|
|
108
185
|
const overrides = new Map();
|
|
109
186
|
await ctx.parse(entry, {
|
|
110
187
|
start(el) {
|
|
111
|
-
if (el.ns.toLowerCase() !== CONTENT_TYPES_NS)
|
|
188
|
+
if (el.ns.toLowerCase() !== exports.CONTENT_TYPES_NS)
|
|
112
189
|
return;
|
|
113
190
|
const type = (0, xml_1.attr)(el, '', 'ContentType')?.trim().toLowerCase();
|
|
114
191
|
if (type === undefined)
|
|
@@ -141,7 +218,7 @@ async function readRelationships(ctx, entry, each) {
|
|
|
141
218
|
const ids = new Set();
|
|
142
219
|
await ctx.parse(entry, {
|
|
143
220
|
start(el) {
|
|
144
|
-
if (el.ns.toLowerCase() !== RELS_NS || el.local !== 'Relationship')
|
|
221
|
+
if (el.ns.toLowerCase() !== exports.RELS_NS || el.local !== 'Relationship')
|
|
145
222
|
return;
|
|
146
223
|
const id = (0, xml_1.attr)(el, '', 'Id') ?? '';
|
|
147
224
|
if (ids.has(id))
|
|
@@ -155,35 +232,48 @@ async function readRelationships(ctx, entry, each) {
|
|
|
155
232
|
},
|
|
156
233
|
});
|
|
157
234
|
}
|
|
158
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* Word field codes in WordprocessingML: fldSimple, and fldChar marks around instrText. A field a strip finding names
|
|
237
|
+
* goes into the plan by the element numbers of its marks.
|
|
238
|
+
*/
|
|
159
239
|
class Fields {
|
|
160
240
|
stack;
|
|
161
241
|
capture = false;
|
|
242
|
+
/** Inside a w:t, whose text is part of a field's result when a field's code has ended. */
|
|
243
|
+
shown = false;
|
|
162
244
|
constructor(ctx, part) {
|
|
163
|
-
this.stack = new fields_1.FieldStack(ctx, part);
|
|
245
|
+
this.stack = new fields_1.FieldStack(ctx, part, marks => ctx.plan.part(part).fields.push(marks));
|
|
164
246
|
}
|
|
165
247
|
start(el) {
|
|
166
248
|
if (el.local === 'fldSimple')
|
|
167
|
-
this.stack.
|
|
249
|
+
this.stack.simpleBegin(wordAttr(el, 'instr') ?? '', el.index);
|
|
168
250
|
else if (el.local === 'fldChar') {
|
|
169
251
|
const kind = wordAttr(el, 'fldCharType');
|
|
170
252
|
if (kind === 'begin')
|
|
171
|
-
this.stack.begin();
|
|
253
|
+
this.stack.begin(el.index);
|
|
172
254
|
else if (kind === 'separate')
|
|
173
|
-
this.stack.separate();
|
|
255
|
+
this.stack.separate(el.index);
|
|
174
256
|
else if (kind === 'end')
|
|
175
|
-
this.stack.end();
|
|
257
|
+
this.stack.end(el.index);
|
|
176
258
|
}
|
|
177
259
|
else if (el.local === 'instrText' || el.local === 'delInstrText')
|
|
178
260
|
this.capture = true;
|
|
261
|
+
else if (el.local === 't')
|
|
262
|
+
this.shown = true;
|
|
179
263
|
}
|
|
180
264
|
end(el) {
|
|
181
265
|
if (el.local === 'instrText' || el.local === 'delInstrText')
|
|
182
266
|
this.capture = false;
|
|
267
|
+
else if (el.local === 't')
|
|
268
|
+
this.shown = false;
|
|
269
|
+
else if (el.local === 'fldSimple')
|
|
270
|
+
this.stack.simpleEnd();
|
|
183
271
|
}
|
|
184
272
|
text(t) {
|
|
185
273
|
if (this.capture)
|
|
186
274
|
this.stack.text(t);
|
|
275
|
+
else if (this.shown)
|
|
276
|
+
this.stack.result(t);
|
|
187
277
|
}
|
|
188
278
|
finish() {
|
|
189
279
|
this.stack.finish();
|
|
@@ -211,6 +301,19 @@ class PartRules {
|
|
|
211
301
|
*/
|
|
212
302
|
formula;
|
|
213
303
|
macroText;
|
|
304
|
+
/** The part is an Excel 4.0 macro sheet, whose formulas are its macros and which its own finding covers. */
|
|
305
|
+
macroSheet = false;
|
|
306
|
+
/** Word's hyperlink base, in the extended properties, which relative hyperlinks resolve against. */
|
|
307
|
+
hyperlinkBase;
|
|
308
|
+
/** The parts this XPS part names in its markup, by key, which count as reached from it. */
|
|
309
|
+
refs = new Set();
|
|
310
|
+
/**
|
|
311
|
+
* The labels of the hyperlinks the part names by relationship Id: their text, tooltips and display text. The
|
|
312
|
+
* relationships part comes after the part, so the targets are compared with these as it is read.
|
|
313
|
+
*/
|
|
314
|
+
labels = new Map();
|
|
315
|
+
/** A hyperlink whose text is being read, with the depth of the element whose end ends it. */
|
|
316
|
+
label;
|
|
214
317
|
/** The last namespace seen and its lowercase form. Neighboring elements share one, and this spares a copy each. */
|
|
215
318
|
ns = '';
|
|
216
319
|
lower = '';
|
|
@@ -219,6 +322,11 @@ class PartRules {
|
|
|
219
322
|
this.part = part;
|
|
220
323
|
this.fields = new Fields(ctx, part);
|
|
221
324
|
}
|
|
325
|
+
/** Records that the rewrite removes the element, when the finding strips. */
|
|
326
|
+
remove(r, el) {
|
|
327
|
+
if (r.action === 'strip')
|
|
328
|
+
this.ctx.plan.element(this.part, el.index, { remove: true });
|
|
329
|
+
}
|
|
222
330
|
lowerNs(el) {
|
|
223
331
|
if (el.ns !== this.ns) {
|
|
224
332
|
this.ns = el.ns;
|
|
@@ -226,45 +334,88 @@ class PartRules {
|
|
|
226
334
|
}
|
|
227
335
|
return this.lower;
|
|
228
336
|
}
|
|
337
|
+
/** Adds labels for the hyperlink a relationship Id names. */
|
|
338
|
+
addLabels(id, labels) {
|
|
339
|
+
const list = this.labels.get(id) ?? [];
|
|
340
|
+
for (const l of labels)
|
|
341
|
+
if (l?.trim())
|
|
342
|
+
list.push(l);
|
|
343
|
+
if (list.length)
|
|
344
|
+
this.labels.set(id, list);
|
|
345
|
+
}
|
|
346
|
+
/** The labels a hyperlink element gives: the text of `textDepth`'s element, its tooltip, and an Excel display name. */
|
|
347
|
+
hyperlink(el, textDepth, also) {
|
|
348
|
+
const id = el.attributes.find(a => a.local === 'id' && exports.R_NS.has(a.ns.toLowerCase()))?.value;
|
|
349
|
+
if (id === undefined)
|
|
350
|
+
return;
|
|
351
|
+
if (textDepth === undefined || this.label)
|
|
352
|
+
this.addLabels(id, also);
|
|
353
|
+
else
|
|
354
|
+
this.label = { id, depth: textDepth, text: '', also: also.filter((l) => l !== undefined) };
|
|
355
|
+
}
|
|
229
356
|
start(el) {
|
|
230
357
|
const ns = this.lowerNs(el);
|
|
231
358
|
if (CUSTOM_UI_NS.has(ns)) {
|
|
232
359
|
if (el.depth === 1)
|
|
233
360
|
this.ctx.report(types_1.OfficeCategory.Ui, types_1.OfficeDetail.Ribbon, this.part);
|
|
234
361
|
for (const a of el.attributes) {
|
|
235
|
-
if (a.ns
|
|
236
|
-
|
|
362
|
+
if (a.ns !== '' || !a.value.trim() || !/^(on[A-Z]|get[A-Z]|loadImage$)/.test(a.local))
|
|
363
|
+
continue;
|
|
364
|
+
// The callback goes and the control stays, doing nothing.
|
|
365
|
+
if (this.ctx.report(types_1.OfficeCategory.Ui, types_1.OfficeDetail.Callback, this.part, { callback: `${a.local}=${(0, context_1.clip)(a.value)}` }).action === 'strip') {
|
|
366
|
+
this.ctx.plan.element(this.part, el.index, { strip: new Set([(0, plan_1.attributeKey)('', a.local)]) });
|
|
367
|
+
}
|
|
237
368
|
}
|
|
238
369
|
return;
|
|
239
370
|
}
|
|
240
|
-
if (
|
|
371
|
+
if (el.depth === 1 && el.local === 'macrosheet')
|
|
372
|
+
this.macroSheet = true;
|
|
373
|
+
if (exports.WORD_NS.has(ns)) {
|
|
241
374
|
this.fields.start(el);
|
|
242
|
-
|
|
375
|
+
if (el.local === 'hyperlink')
|
|
376
|
+
this.hyperlink(el, el.depth, [wordAttr(el, 'tooltip')]);
|
|
377
|
+
}
|
|
378
|
+
else if (exports.SHEET_NS.has(ns)) {
|
|
379
|
+
if (el.local === 'hyperlink')
|
|
380
|
+
this.hyperlink(el, undefined, [(0, xml_1.attr)(el, '', 'display'), (0, xml_1.attr)(el, '', 'tooltip')]);
|
|
243
381
|
if (el.local === 'f' || el.local === 'definedName')
|
|
244
|
-
this.formula = { shown: new Shown(), quotes: new formulas_1.Quotes(), ddeBar: false };
|
|
382
|
+
this.formula = { shown: new Shown(), quotes: new formulas_1.Quotes(), ddeBar: false, index: el.index, calls: new formulas_1.FunctionCalls() };
|
|
245
383
|
if (el.local === 'definedName') {
|
|
246
384
|
// Excel runs the macro a name like _xlnm.Auto_Open refers to, as it does in a binary workbook.
|
|
247
385
|
const name = (0, xml_1.attr)(el, '', 'name') ?? '';
|
|
248
386
|
const auto = (0, biff_1.autoName)(name);
|
|
249
387
|
if (auto)
|
|
250
|
-
this.ctx.report(types_1.OfficeCategory.Macro, auto.onOpen ? types_1.OfficeDetail.EventOnOpen : types_1.OfficeDetail.Event, this.part, { event: auto.event, name: (0, context_1.clip)(name) });
|
|
388
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Macro, auto.onOpen ? types_1.OfficeDetail.EventOnOpen : types_1.OfficeDetail.Event, this.part, { event: auto.event, name: (0, context_1.clip)(name) }), el);
|
|
251
389
|
}
|
|
252
390
|
else if (el.local === 'ddeLink')
|
|
253
|
-
this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, this.part, { application: (0, context_1.clip)((0, xml_1.attr)(el, '', 'ddeService') ?? ''), topic: (0, context_1.clip)((0, xml_1.attr)(el, '', 'ddeTopic') ?? '') });
|
|
391
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, this.part, { application: (0, context_1.clip)((0, xml_1.attr)(el, '', 'ddeService') ?? ''), topic: (0, context_1.clip)((0, xml_1.attr)(el, '', 'ddeTopic') ?? '') }), el);
|
|
392
|
+
else if (Object.hasOwn(CONNECTION_SOURCES, el.local))
|
|
393
|
+
this.connection(el);
|
|
254
394
|
}
|
|
255
395
|
else if (XDR_NS.has(ns)) {
|
|
256
396
|
const macro = (0, xml_1.attr)(el, '', 'macro');
|
|
257
|
-
|
|
258
|
-
|
|
397
|
+
// The shape stays, and clicking it runs nothing.
|
|
398
|
+
if (macro?.trim() && this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, { macro: (0, context_1.clip)(macro) }).action === 'strip') {
|
|
399
|
+
this.ctx.plan.element(this.part, el.index, { strip: new Set([(0, plan_1.attributeKey)('', 'macro')]) });
|
|
400
|
+
}
|
|
259
401
|
}
|
|
260
402
|
else if (ns === VML_EXCEL_NS && el.local === 'FmlaMacro')
|
|
261
|
-
this.macroText = new Shown();
|
|
403
|
+
this.macroText = { shown: new Shown(), index: el.index };
|
|
262
404
|
else if (ns === VISIO_NS && el.local === 'Cell')
|
|
263
405
|
this.visioCell(el);
|
|
264
|
-
if (
|
|
406
|
+
else if (exports.XPS_NS.has(ns))
|
|
407
|
+
this.xps(el);
|
|
408
|
+
else if (EXTENDED_NS.has(ns) && el.local === 'HyperlinkBase')
|
|
409
|
+
this.hyperlinkBase = { shown: new Shown(), index: el.index };
|
|
410
|
+
if (el.local === 'hlinkClick' || el.local === 'hlinkHover' || el.local === 'hlinkMouseOver') {
|
|
265
411
|
this.drawingAction(el);
|
|
412
|
+
// In a text run the link sits in the run's properties, and the run's text is what the reader sees.
|
|
413
|
+
this.hyperlink(el, el.depth >= 2 ? el.depth - 2 : undefined, [(0, xml_1.attr)(el, '', 'tooltip')]);
|
|
414
|
+
}
|
|
266
415
|
}
|
|
267
416
|
text(t) {
|
|
417
|
+
if (this.label && this.label.text.length < 4096)
|
|
418
|
+
this.label.text += t;
|
|
268
419
|
this.fields.text(t);
|
|
269
420
|
const f = this.formula;
|
|
270
421
|
if (f) {
|
|
@@ -273,58 +424,120 @@ class PartRules {
|
|
|
273
424
|
for (const c of t)
|
|
274
425
|
if (f.quotes.outside(c) && c === '|')
|
|
275
426
|
f.ddeBar = true;
|
|
427
|
+
f.calls.write(t);
|
|
276
428
|
if (f.entity === undefined && t.includes(xml_1.UNKNOWN))
|
|
277
429
|
f.entity = (0, xml_1.unknownEntity)(t);
|
|
278
430
|
}
|
|
279
|
-
this.macroText?.add(t);
|
|
431
|
+
this.macroText?.shown.add(t);
|
|
432
|
+
this.hyperlinkBase?.shown.add(t);
|
|
280
433
|
}
|
|
281
434
|
end(el) {
|
|
282
435
|
const ns = this.lowerNs(el);
|
|
283
|
-
if (
|
|
436
|
+
if (this.label && el.depth === this.label.depth) {
|
|
437
|
+
this.addLabels(this.label.id, [this.label.text, ...this.label.also]);
|
|
438
|
+
this.label = undefined;
|
|
439
|
+
}
|
|
440
|
+
if (exports.WORD_NS.has(ns))
|
|
284
441
|
this.fields.end(el);
|
|
285
|
-
if (SHEET_NS.has(ns) && (el.local === 'f' || el.local === 'definedName') && this.formula) {
|
|
286
|
-
|
|
442
|
+
if (exports.SHEET_NS.has(ns) && (el.local === 'f' || el.local === 'definedName') && this.formula) {
|
|
443
|
+
// A cell keeps its cached value without the formula. A defined name goes whole.
|
|
444
|
+
const { ddeBar, entity, shown, calls } = this.formula;
|
|
287
445
|
if (ddeBar)
|
|
288
|
-
this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, this.part, { formula: (0, context_1.clip)(shown.text) });
|
|
446
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, this.part, { formula: (0, context_1.clip)(shown.text) }), this.formula);
|
|
289
447
|
else if (entity !== undefined)
|
|
290
|
-
this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, this.part, { formula: (0, context_1.clip)(shown.text), entity: (0, context_1.clip)(entity) });
|
|
448
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, this.part, { formula: (0, context_1.clip)(shown.text), entity: (0, context_1.clip)(entity) }), this.formula);
|
|
449
|
+
if (!this.macroSheet)
|
|
450
|
+
for (const [fn, kind] of calls.found)
|
|
451
|
+
this.remove(this.ctx.report(kind === 'fetch' ? types_1.OfficeCategory.External : types_1.OfficeCategory.Macro, types_1.OfficeDetail.Formula, this.part, { function: (0, context_1.clip)(fn), formula: (0, context_1.clip)(shown.text) }), this.formula);
|
|
291
452
|
this.formula = undefined;
|
|
292
453
|
}
|
|
293
454
|
if (ns === VML_EXCEL_NS && el.local === 'FmlaMacro' && this.macroText) {
|
|
294
|
-
if (this.macroText.content)
|
|
295
|
-
this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, { macro: (0, context_1.clip)(this.macroText.text.trim()) });
|
|
455
|
+
if (this.macroText.shown.content)
|
|
456
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, { macro: (0, context_1.clip)(this.macroText.shown.text.trim()) }), this.macroText);
|
|
296
457
|
this.macroText = undefined;
|
|
297
458
|
}
|
|
459
|
+
if (this.hyperlinkBase && EXTENDED_NS.has(ns) && el.local === 'HyperlinkBase') {
|
|
460
|
+
const base = this.hyperlinkBase.shown.text.trim();
|
|
461
|
+
if (base && !(0, links_1.isFragment)(base))
|
|
462
|
+
this.remove(this.ctx.link(base, this.part, { source: 'hyperlink base' }), this.hyperlinkBase);
|
|
463
|
+
this.hyperlinkBase = undefined;
|
|
464
|
+
}
|
|
298
465
|
if (el.depth === 1)
|
|
299
466
|
this.fields.finish();
|
|
300
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* A data connection's source. Each source a connection names is a finding, and the rewrite empties it, so the
|
|
470
|
+
* connection stays for the query tables and pivot caches that name it and loads nothing.
|
|
471
|
+
*/
|
|
472
|
+
connection(el) {
|
|
473
|
+
const set = new Map();
|
|
474
|
+
for (const name of CONNECTION_SOURCES[el.local]) {
|
|
475
|
+
const a = el.attributes.find(x => x.ns === '' && x.local === name);
|
|
476
|
+
if (!a?.value.trim())
|
|
477
|
+
continue;
|
|
478
|
+
const data = { element: (0, context_1.clip)(el.qname), attribute: name, target: (0, context_1.clip)(a.value, 2000) };
|
|
479
|
+
if (this.ctx.report(types_1.OfficeCategory.External, types_1.OfficeDetail.DataConnection, this.part, data).action === 'strip')
|
|
480
|
+
set.set(a.qname, '');
|
|
481
|
+
}
|
|
482
|
+
if (set.size)
|
|
483
|
+
this.ctx.plan.element(this.part, el.index, { set });
|
|
484
|
+
}
|
|
301
485
|
/** PowerPoint action settings, in a DrawingML hyperlink's action attribute. The relationship it names is reported on its own. */
|
|
302
486
|
drawingAction(el) {
|
|
303
487
|
const written = ((0, xml_1.attr)(el, '', 'action') ?? '').trim();
|
|
304
488
|
const action = written.toLowerCase();
|
|
305
489
|
if (!action)
|
|
306
490
|
return;
|
|
307
|
-
const id = el.attributes.find(a => a.local === 'id' && R_NS.has(a.ns.toLowerCase()))?.value;
|
|
491
|
+
const id = el.attributes.find(a => a.local === 'id' && exports.R_NS.has(a.ns.toLowerCase()))?.value;
|
|
308
492
|
const data = { trigger: el.local, action: (0, context_1.clip)(written), ...(id === undefined ? {} : { relationship: (0, context_1.clip)(id) }) };
|
|
309
493
|
if (action.startsWith('ppaction://program'))
|
|
310
|
-
this.ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, this.part, data);
|
|
494
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, this.part, data), el);
|
|
311
495
|
else if (action.startsWith('ppaction://macro'))
|
|
312
|
-
this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, data);
|
|
496
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, data), el);
|
|
313
497
|
else if (action.startsWith('ppaction://ole'))
|
|
314
|
-
this.ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, this.part, data);
|
|
498
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, this.part, data), el);
|
|
315
499
|
}
|
|
316
500
|
visioCell(el) {
|
|
317
501
|
const n = (0, xml_1.attr)(el, '', 'N') ?? '';
|
|
318
502
|
const formula = (0, xml_1.attr)(el, '', 'F') ?? '';
|
|
319
503
|
if (VISIO_CODE.test(formula)) {
|
|
320
504
|
if (n.startsWith('Event'))
|
|
321
|
-
this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Event, this.part, { event: (0, context_1.clip)(n), formula: (0, context_1.clip)(formula) });
|
|
505
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Event, this.part, { event: (0, context_1.clip)(n), formula: (0, context_1.clip)(formula) }), el);
|
|
322
506
|
else
|
|
323
|
-
this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, { cell: (0, context_1.clip)(n), formula: (0, context_1.clip)(formula) });
|
|
507
|
+
this.remove(this.ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, this.part, { cell: (0, context_1.clip)(n), formula: (0, context_1.clip)(formula) }), el);
|
|
324
508
|
}
|
|
325
509
|
const v = (0, xml_1.attr)(el, '', 'V');
|
|
326
510
|
if (n === 'Address' && v?.trim() && !(0, links_1.isFragment)(v))
|
|
327
|
-
this.ctx.link(v, this.part, { source: 'Visio hyperlink' });
|
|
511
|
+
this.remove(this.ctx.link(v, this.part, { source: 'Visio hyperlink' }), el);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* XPS markup, which names the parts it uses in attributes rather than in relationships. A resource inside the
|
|
515
|
+
* package counts as reached from this part. One outside it loads from outside, and the element that names it goes.
|
|
516
|
+
* A link with a scheme or a network path gets the link checks and loses its target. A relative link moves inside
|
|
517
|
+
* the document.
|
|
518
|
+
*/
|
|
519
|
+
xps(el) {
|
|
520
|
+
for (const a of el.attributes) {
|
|
521
|
+
if (a.ns !== '' || (a.local !== 'FixedPage.NavigateUri' && a.local !== 'OutlineTarget'))
|
|
522
|
+
continue;
|
|
523
|
+
if ((0, links_1.isFragment)(a.value) || !(0, links_1.isAbsoluteTarget)(a.value))
|
|
524
|
+
continue;
|
|
525
|
+
const data = { element: (0, context_1.clip)(el.qname) };
|
|
526
|
+
let r = this.ctx.link(a.value, this.part, data);
|
|
527
|
+
// The characters of a Glyphs element are what the reader sees.
|
|
528
|
+
if (r.action !== 'strip' && el.local === 'Glyphs')
|
|
529
|
+
r = this.ctx.labelMismatch(a.value, [(0, xml_1.attr)(el, '', 'UnicodeString')], this.part, data) ?? r;
|
|
530
|
+
if (r.action === 'strip')
|
|
531
|
+
this.ctx.plan.element(this.part, el.index, { strip: new Set([(0, plan_1.attributeKey)('', a.local)]) });
|
|
532
|
+
}
|
|
533
|
+
for (const [name, uri] of xpsResources(el)) {
|
|
534
|
+
if (!(0, links_1.isAbsoluteTarget)(uri)) {
|
|
535
|
+
this.refs.add(resolvePartRef(this.part, uri));
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const detail = name === 'ImageSource' ? types_1.OfficeDetail.Image : name === 'Source' ? types_1.OfficeDetail.Include : types_1.OfficeDetail.Other;
|
|
539
|
+
this.remove(this.ctx.external(detail, uri, this.part, { element: (0, context_1.clip)(el.qname), attribute: name }, name), el);
|
|
540
|
+
}
|
|
328
541
|
}
|
|
329
542
|
}
|
|
330
543
|
/**
|
|
@@ -336,6 +549,7 @@ async function inspectOoxml(ctx, contentTypes) {
|
|
|
336
549
|
const { defaults, overrides } = await readContentTypes(ctx, contentTypes);
|
|
337
550
|
const parts = new Map();
|
|
338
551
|
const relsOf = new Map();
|
|
552
|
+
const plan = ctx.plan;
|
|
339
553
|
for (const entry of ctx.zip.entries) {
|
|
340
554
|
if (entry === contentTypes)
|
|
341
555
|
continue;
|
|
@@ -344,7 +558,7 @@ async function inspectOoxml(ctx, contentTypes) {
|
|
|
344
558
|
await ctx.drain(entry);
|
|
345
559
|
continue;
|
|
346
560
|
}
|
|
347
|
-
const source = relsSource(entry.name);
|
|
561
|
+
const source = (0, exports.relsSource)(entry.name);
|
|
348
562
|
if (source !== undefined) {
|
|
349
563
|
relsOf.set(source, entry);
|
|
350
564
|
continue;
|
|
@@ -355,30 +569,45 @@ async function inspectOoxml(ctx, contentTypes) {
|
|
|
355
569
|
}
|
|
356
570
|
const roots = new Set();
|
|
357
571
|
const mains = [];
|
|
572
|
+
/** The labels each part gives its hyperlinks, by part key and relationship Id. */
|
|
573
|
+
const labelsOf = new Map();
|
|
358
574
|
const relate = (rels, from, r) => {
|
|
359
575
|
const location = `${rels.name}, ${(0, context_1.clip)(r.id)}`;
|
|
360
576
|
const part = r.external ? undefined : parts.get(resolveTarget(rels.name, r.target));
|
|
361
577
|
if (part) {
|
|
362
578
|
part.reachedAs |= REACHED_AS.get(r.type) ?? 0;
|
|
363
|
-
if (TYPE.control.has(from?.contentType ?? ''))
|
|
579
|
+
if (exports.TYPE.control.has(from?.contentType ?? ''))
|
|
364
580
|
part.claimed = true;
|
|
365
581
|
(from?.targets ?? roots).add(part);
|
|
366
582
|
}
|
|
367
|
-
// A hyperlink to a part of the package, or to a place in the document, goes nowhere else.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
583
|
+
// A hyperlink to a part of the package, or to a place in the document, goes nowhere else. A relationship a strip
|
|
584
|
+
// finding names goes, and so does every element that refers to it.
|
|
585
|
+
let reported;
|
|
586
|
+
if (exports.REL.hyperlink.has(r.type)) {
|
|
587
|
+
if (!part && !(0, links_1.isFragment)(r.target)) {
|
|
588
|
+
reported = ctx.link(r.target, location);
|
|
589
|
+
const labels = from ? labelsOf.get((0, zip_1.nameKey)(from.entry.name))?.get(r.id) : undefined;
|
|
590
|
+
if (labels && reported.action !== 'strip')
|
|
591
|
+
reported = ctx.labelMismatch(r.target, labels, location) ?? reported;
|
|
592
|
+
}
|
|
371
593
|
}
|
|
372
594
|
else if (r.external) {
|
|
373
595
|
const kind = EXTERNAL_KINDS.find(([types]) => types.has(r.type))?.[1] ?? types_1.OfficeDetail.Other;
|
|
374
|
-
ctx.external(kind, r.target, location, { type: (0, context_1.clip)(r.type) }, (0, context_1.clip)(r.type.slice(r.type.lastIndexOf('/') + 1)));
|
|
596
|
+
reported = ctx.external(kind, r.target, location, { type: (0, context_1.clip)(r.type) }, (0, context_1.clip)(r.type.slice(r.type.lastIndexOf('/') + 1)));
|
|
375
597
|
}
|
|
598
|
+
if (reported?.action === 'strip')
|
|
599
|
+
plan.dropRelationship(rels.name, r.id);
|
|
376
600
|
};
|
|
377
601
|
const packageRels = relsOf.get('');
|
|
602
|
+
let strict = false;
|
|
603
|
+
let mainType = '';
|
|
378
604
|
if (packageRels) {
|
|
379
605
|
await readRelationships(ctx, packageRels, r => {
|
|
380
|
-
if (REL.main.has(r.type))
|
|
606
|
+
if (exports.REL.main.has(r.type)) {
|
|
381
607
|
mains.push(r.external ? undefined : parts.get(resolveTarget(packageRels.name, r.target)));
|
|
608
|
+
strict = r.type.startsWith(S);
|
|
609
|
+
mainType = r.type;
|
|
610
|
+
}
|
|
382
611
|
relate(packageRels, undefined, r);
|
|
383
612
|
});
|
|
384
613
|
}
|
|
@@ -387,25 +616,38 @@ async function inspectOoxml(ctx, contentTypes) {
|
|
|
387
616
|
const main = mains[0];
|
|
388
617
|
if (!main)
|
|
389
618
|
throw new errors_1.StopError(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unrecognized, 'The package has no main part', { reason: 'no main document part' });
|
|
390
|
-
const
|
|
619
|
+
const xps = mainType === exports.XPS_MAIN.xps || mainType === exports.XPS_MAIN.oxps;
|
|
620
|
+
// XPS and OpenXPS share their content types, so the relationship type tells them apart.
|
|
621
|
+
const format = xps ? (main.contentType === formats_1.XPS_SEQUENCE ? formats_1.XPS_FORMATS[mainType === exports.XPS_MAIN.oxps ? 'oxps' : 'xps'] : undefined) : formats_1.OOXML_FORMATS.get(main.contentType ?? '');
|
|
391
622
|
if (!format)
|
|
392
623
|
throw new errors_1.StopError(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unrecognized, 'The main part has an unknown content type', { contentType: (0, context_1.clip)(main.contentType ?? '') });
|
|
393
624
|
ctx.format = format;
|
|
394
625
|
for (const [key, p] of parts) {
|
|
395
626
|
const ct = p.contentType;
|
|
396
627
|
if (ct === undefined) {
|
|
397
|
-
ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UntypedPart, p.entry.name, { part: p.entry.name })
|
|
628
|
+
if (ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UntypedPart, p.entry.name, { part: p.entry.name }).action === 'strip')
|
|
629
|
+
plan.drop.add(key);
|
|
398
630
|
p.ole = await ctx.drain(p.entry);
|
|
399
631
|
}
|
|
400
|
-
else if (
|
|
401
|
-
|
|
632
|
+
else if ((0, exports.isXmlType)(ct)) {
|
|
633
|
+
const rules = new PartRules(ctx, p.entry.name);
|
|
634
|
+
await ctx.parse(p.entry, rules, ct === exports.TYPE.vml);
|
|
635
|
+
if (rules.labels.size)
|
|
636
|
+
labelsOf.set(key, rules.labels);
|
|
637
|
+
for (const k of rules.refs) {
|
|
638
|
+
const t = parts.get(k);
|
|
639
|
+
if (t && t !== p)
|
|
640
|
+
p.targets.add(t);
|
|
641
|
+
}
|
|
402
642
|
}
|
|
403
|
-
else if (ct === TYPE.xlsbExternalLink) {
|
|
643
|
+
else if (ct === exports.TYPE.xlsbExternalLink) {
|
|
404
644
|
// The binary form of an Excel ddeLink.
|
|
405
645
|
const links = new xlsb_1.XlsbLinks();
|
|
406
646
|
p.ole = await ctx.drain(p.entry, c => links.write(c));
|
|
647
|
+
// The binary part is the link, so it goes whole.
|
|
407
648
|
for (const l of links.dde)
|
|
408
|
-
ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, p.entry.name, { application: (0, context_1.clip)(l.application), topic: (0, context_1.clip)(l.topic) })
|
|
649
|
+
if (ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, p.entry.name, { application: (0, context_1.clip)(l.application), topic: (0, context_1.clip)(l.topic) }).action === 'strip')
|
|
650
|
+
plan.drop.add(key);
|
|
409
651
|
}
|
|
410
652
|
else
|
|
411
653
|
p.ole = await ctx.drain(p.entry);
|
|
@@ -418,32 +660,50 @@ async function inspectOoxml(ctx, contentTypes) {
|
|
|
418
660
|
if (source !== '' && !parts.has(source))
|
|
419
661
|
await readRelationships(ctx, rels, r => relate(rels, undefined, r));
|
|
420
662
|
for (const p of parts.values())
|
|
421
|
-
partRules(ctx, p);
|
|
663
|
+
await partRules(ctx, p);
|
|
422
664
|
unreferenced(ctx, parts, relsOf, roots);
|
|
665
|
+
plan.graph = { contentTypes, parts, relsOf, roots, strict, xps };
|
|
423
666
|
}
|
|
424
|
-
/**
|
|
425
|
-
|
|
667
|
+
/**
|
|
668
|
+
* Findings about a whole part, from its content type and the relationships that reach it. The files inside the
|
|
669
|
+
* package, its OLE parts and embedded packages, go to the plugins first. A plugin's verdict decides whether such a
|
|
670
|
+
* part stays, and when no plugin takes it, the findings of this function do.
|
|
671
|
+
*/
|
|
672
|
+
async function partRules(ctx, p) {
|
|
426
673
|
const ct = p.contentType ?? '';
|
|
427
674
|
const loc = p.entry.name;
|
|
675
|
+
const key = (0, zip_1.nameKey)(loc);
|
|
428
676
|
const data = { part: loc, contentType: (0, context_1.clip)(ct) };
|
|
429
677
|
const as = (bit) => (p.reachedAs & bit) !== 0;
|
|
430
|
-
const vba = TYPE.vba.has(ct) || as(AS.vba);
|
|
431
|
-
const ole = TYPE.ole.has(ct) || as(AS.ole);
|
|
432
|
-
const control = TYPE.control.has(ct) || as(AS.control);
|
|
433
|
-
const controlBinary = TYPE.controlBinary.has(ct) || as(AS.controlBinary);
|
|
678
|
+
const vba = exports.TYPE.vba.has(ct) || as(AS.vba);
|
|
679
|
+
const ole = exports.TYPE.ole.has(ct) || as(AS.ole);
|
|
680
|
+
const control = exports.TYPE.control.has(ct) || as(AS.control);
|
|
681
|
+
const controlBinary = exports.TYPE.controlBinary.has(ct) || as(AS.controlBinary);
|
|
434
682
|
const pkg = as(AS.package);
|
|
683
|
+
// Besides OLE parts and packages, every part that is a whole file in a format of its own, such as an image, goes
|
|
684
|
+
// to the plugins. Such a part has no rule of its own, so one no plugin takes stays.
|
|
685
|
+
const wholeFile = !(0, exports.isXmlType)(p.contentType) && !formatBinary(ct);
|
|
686
|
+
const contained = vba || ole || pkg || controlBinary || p.ole || wholeFile;
|
|
687
|
+
const decision = contained ? await (0, contained_1.decideContained)(ctx, { name: loc, declaredType: ct || undefined, location: loc, chunks: () => ctx.zip.read(p.entry) }) : undefined;
|
|
688
|
+
const act = (0, contained_1.presence)(decision);
|
|
689
|
+
const found = [];
|
|
435
690
|
if (vba)
|
|
436
|
-
ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, loc, data);
|
|
437
|
-
|
|
438
|
-
|
|
691
|
+
found.push(ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, loc, data, act));
|
|
692
|
+
// A macro sheet becomes an empty worksheet, so the indexes of the sheets after it stay right.
|
|
693
|
+
if ((exports.TYPE.macrosheet.has(ct) || as(AS.macrosheet)) && ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.XlmMacrosheet, loc, data).action === 'strip')
|
|
694
|
+
ctx.plan.sheets.add(key);
|
|
439
695
|
if (ole)
|
|
440
|
-
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, loc, data);
|
|
696
|
+
found.push(ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, loc, data, act));
|
|
441
697
|
if (pkg)
|
|
442
|
-
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Package, loc, data);
|
|
698
|
+
found.push(ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Package, loc, data, act));
|
|
443
699
|
if (control || (controlBinary && !p.claimed))
|
|
444
|
-
ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, loc, data);
|
|
700
|
+
found.push(ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, loc, data, control ? undefined : act));
|
|
445
701
|
if (p.ole && !vba && !ole && !control && !controlBinary && !pkg)
|
|
446
|
-
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleFile, loc, data);
|
|
702
|
+
found.push(ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleFile, loc, data, act));
|
|
703
|
+
if (decision ? !decision.keep : found.some(f => f.action === 'strip'))
|
|
704
|
+
ctx.plan.drop.add(key);
|
|
705
|
+
else if (decision?.replacement)
|
|
706
|
+
ctx.plan.replace.set(key, decision.replacement);
|
|
447
707
|
}
|
|
448
708
|
/** Parts that no chain of internal relationships reaches from the package. */
|
|
449
709
|
function unreferenced(ctx, parts, relsOf, roots) {
|
|
@@ -457,12 +717,13 @@ function unreferenced(ctx, parts, relsOf, roots) {
|
|
|
457
717
|
queue.push(t);
|
|
458
718
|
}
|
|
459
719
|
}
|
|
460
|
-
for (const p of parts.values())
|
|
461
|
-
if (!reached.has(p))
|
|
462
|
-
ctx.
|
|
720
|
+
for (const p of parts.values()) {
|
|
721
|
+
if (!reached.has(p) && ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreferencedPart, p.entry.name, { part: p.entry.name }).action === 'strip')
|
|
722
|
+
ctx.plan.drop.add((0, zip_1.nameKey)(p.entry.name));
|
|
723
|
+
}
|
|
463
724
|
for (const [source, rels] of relsOf) {
|
|
464
725
|
const owner = parts.get(source);
|
|
465
|
-
if (source !== '' && !(owner && reached.has(owner)))
|
|
466
|
-
ctx.
|
|
726
|
+
if (source !== '' && !(owner && reached.has(owner)) && ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreferencedPart, rels.name, { part: rels.name }).action === 'strip')
|
|
727
|
+
ctx.plan.drop.add((0, zip_1.nameKey)(rels.name));
|
|
467
728
|
}
|
|
468
729
|
}
|