@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/odf.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EntryRules = exports.packagePath = exports.hrefOf = exports.odfNamespace = exports.NS = void 0;
|
|
4
|
+
exports.oattr = oattr;
|
|
5
|
+
exports.folderOf = folderOf;
|
|
3
6
|
exports.inspectOdf = inspectOdf;
|
|
7
|
+
const contained_1 = require("./contained");
|
|
4
8
|
const context_1 = require("./context");
|
|
5
9
|
const errors_1 = require("./errors");
|
|
6
10
|
const formats_1 = require("./formats");
|
|
7
11
|
const formulas_1 = require("./formulas");
|
|
8
12
|
const links_1 = require("./links");
|
|
13
|
+
const plan_1 = require("./plan");
|
|
9
14
|
const types_1 = require("./types");
|
|
10
15
|
const zip_1 = require("./zip");
|
|
11
|
-
|
|
16
|
+
exports.NS = {
|
|
12
17
|
manifest: 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
|
|
13
18
|
office: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
|
|
14
19
|
script: 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
|
@@ -20,6 +25,7 @@ const NS = {
|
|
|
20
25
|
meta: 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
|
21
26
|
style: 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
|
22
27
|
database: 'urn:oasis:names:tc:opendocument:xmlns:database:1.0',
|
|
28
|
+
chart: 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
|
|
23
29
|
xlink: 'http://www.w3.org/1999/xlink',
|
|
24
30
|
};
|
|
25
31
|
/**
|
|
@@ -35,21 +41,21 @@ const FOLDERS = [
|
|
|
35
41
|
/** Event names that fire as the document opens. Focus and activation count only for the document's own events. */
|
|
36
42
|
const ON_OPEN = new Set(['load', 'new', 'create', 'loadfinished', 'viewcreated']);
|
|
37
43
|
const ON_OPEN_DOCUMENT = new Set(['focus', 'activate']);
|
|
38
|
-
const LINK_ELEMENTS = new Set([`${NS.text} a`, `${NS.draw} a`, `${NS.draw} area-rectangle`, `${NS.draw} area-circle`, `${NS.draw} area-polygon`]);
|
|
44
|
+
const LINK_ELEMENTS = new Set([`${exports.NS.text} a`, `${exports.NS.draw} a`, `${exports.NS.draw} area-rectangle`, `${exports.NS.draw} area-circle`, `${exports.NS.draw} area-polygon`]);
|
|
39
45
|
const EXTERNAL_KIND = new Map([
|
|
40
|
-
[`${NS.meta} template`, types_1.OfficeDetail.Template],
|
|
41
|
-
[`${NS.meta} auto-reload`, types_1.OfficeDetail.Reload],
|
|
42
|
-
[`${NS.draw} image`, types_1.OfficeDetail.Image],
|
|
43
|
-
[`${NS.draw} fill-image`, types_1.OfficeDetail.Image],
|
|
44
|
-
[`${NS.style} background-image`, types_1.OfficeDetail.Image],
|
|
45
|
-
[`${NS.text} section-source`, types_1.OfficeDetail.Include],
|
|
46
|
-
[`${NS.table} table-source`, types_1.OfficeDetail.Include],
|
|
47
|
-
[`${NS.draw} object`, types_1.OfficeDetail.Object],
|
|
48
|
-
[`${NS.draw} object-ole`, types_1.OfficeDetail.Object],
|
|
49
|
-
[`${NS.draw} floating-frame`, types_1.OfficeDetail.Frame],
|
|
46
|
+
[`${exports.NS.meta} template`, types_1.OfficeDetail.Template],
|
|
47
|
+
[`${exports.NS.meta} auto-reload`, types_1.OfficeDetail.Reload],
|
|
48
|
+
[`${exports.NS.draw} image`, types_1.OfficeDetail.Image],
|
|
49
|
+
[`${exports.NS.draw} fill-image`, types_1.OfficeDetail.Image],
|
|
50
|
+
[`${exports.NS.style} background-image`, types_1.OfficeDetail.Image],
|
|
51
|
+
[`${exports.NS.text} section-source`, types_1.OfficeDetail.Include],
|
|
52
|
+
[`${exports.NS.table} table-source`, types_1.OfficeDetail.Include],
|
|
53
|
+
[`${exports.NS.draw} object`, types_1.OfficeDetail.Object],
|
|
54
|
+
[`${exports.NS.draw} object-ole`, types_1.OfficeDetail.Object],
|
|
55
|
+
[`${exports.NS.draw} floating-frame`, types_1.OfficeDetail.Frame],
|
|
50
56
|
]);
|
|
51
57
|
/** Elements whose reference always leaves the package: it resolves against the document's own location. */
|
|
52
|
-
const ALWAYS_OUTSIDE = new Set([`${NS.meta} template`, `${NS.meta} auto-reload`, `${NS.text} section-source`, `${NS.table} table-source`]);
|
|
58
|
+
const ALWAYS_OUTSIDE = new Set([`${exports.NS.meta} template`, `${exports.NS.meta} auto-reload`, `${exports.NS.text} section-source`, `${exports.NS.table} table-source`]);
|
|
53
59
|
/**
|
|
54
60
|
* LibreOffice reads urn:oasis:names:tc:<any>:xmlns:<name>:1.<any> as the OpenDocument namespace <name> 1.0, so this
|
|
55
61
|
* does too. The comparison ignores case, which can only find more.
|
|
@@ -65,90 +71,155 @@ function oattr(el, ns, local) {
|
|
|
65
71
|
return a.value;
|
|
66
72
|
return undefined;
|
|
67
73
|
}
|
|
74
|
+
/** The ODF_NS-normalized namespace of `ns`, exported for the rewrite. */
|
|
75
|
+
const odfNamespace = (ns) => odfNs(ns);
|
|
76
|
+
exports.odfNamespace = odfNamespace;
|
|
77
|
+
/** The edit that takes an element's xlink:href away, under whatever namespace URI the attribute has. */
|
|
78
|
+
function stripHref(el) {
|
|
79
|
+
return { strip: new Set(el.attributes.filter(a => a.local === 'href' && odfNs(a.ns) === exports.NS.xlink).map(a => (0, plan_1.attributeKey)(a.ns, a.local))) };
|
|
80
|
+
}
|
|
81
|
+
/** The xlink:href of an element, for the rewrite. */
|
|
82
|
+
const hrefOf = (el) => oattr(el, exports.NS.xlink, 'href');
|
|
83
|
+
exports.hrefOf = hrefOf;
|
|
68
84
|
/** A path inside the package as the manifest and the zip name it: without "./" and leading slashes. */
|
|
69
85
|
const packagePath = (href) => (0, zip_1.nameKey)(href.trim().replace(/^(\.\/)+/, ''));
|
|
70
|
-
|
|
86
|
+
exports.packagePath = packagePath;
|
|
87
|
+
/**
|
|
88
|
+
* The rules that read inside one XML entry of an ODF package, or inside a flat OpenDocument file. A flat file has no
|
|
89
|
+
* entries, so in one every relative reference names a file beside the document, which loads from outside it.
|
|
90
|
+
*/
|
|
71
91
|
class EntryRules {
|
|
72
92
|
ctx;
|
|
73
93
|
entry;
|
|
74
94
|
oleRefs;
|
|
95
|
+
flat;
|
|
75
96
|
/** Depth of the office:scripts element being read, 0 outside one. */
|
|
76
97
|
scriptsDepth = 0;
|
|
77
98
|
/** An office:script element being read, with whether it holds anything. */
|
|
78
99
|
script;
|
|
100
|
+
/** A link element being read, whose text and title go into the label check at its end. */
|
|
101
|
+
label;
|
|
79
102
|
constructor(ctx, entry,
|
|
80
103
|
/** Whether an object refers to each entry, by key. Only entries are kept, so a part full of references costs no memory. */
|
|
81
|
-
oleRefs) {
|
|
104
|
+
oleRefs, flat = false) {
|
|
82
105
|
this.ctx = ctx;
|
|
83
106
|
this.entry = entry;
|
|
84
107
|
this.oleRefs = oleRefs;
|
|
108
|
+
this.flat = flat;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether a reference names an entry of the package. LibreOffice's vnd.sun.star.Package: URLs name one too. In a
|
|
112
|
+
* flat file no entry exists, so only such a URL stays inside, naming nothing.
|
|
113
|
+
*/
|
|
114
|
+
inside(href) {
|
|
115
|
+
const own = /^\s*vnd\.sun\.star\.package:(.*)$/is.exec(href);
|
|
116
|
+
if (own)
|
|
117
|
+
return this.flat || (0, links_1.isInsidePackage)(own[1]);
|
|
118
|
+
return !this.flat && (0, links_1.isInsidePackage)(href);
|
|
85
119
|
}
|
|
86
120
|
report(category, detail, data) {
|
|
87
|
-
this.ctx.report(category, detail, this.entry, data);
|
|
121
|
+
return this.ctx.report(category, detail, this.entry, data);
|
|
122
|
+
}
|
|
123
|
+
/** Records the edit for the element when the finding strips. */
|
|
124
|
+
edit(r, el, edit = { remove: true }) {
|
|
125
|
+
if (r.action === 'strip')
|
|
126
|
+
this.ctx.plan.element(this.entry, el.index, edit);
|
|
88
127
|
}
|
|
89
128
|
start(raw) {
|
|
90
129
|
if (this.script)
|
|
91
130
|
this.script.content = true;
|
|
92
131
|
const el = { ...raw, ns: odfNs(raw.ns) };
|
|
93
132
|
const key = `${el.ns} ${el.local}`;
|
|
94
|
-
const href = oattr(el, NS.xlink, 'href');
|
|
95
|
-
if (el.ns === NS.office && el.local === 'scripts' && !this.scriptsDepth)
|
|
133
|
+
const href = oattr(el, exports.NS.xlink, 'href');
|
|
134
|
+
if (el.ns === exports.NS.office && el.local === 'scripts' && !this.scriptsDepth)
|
|
96
135
|
this.scriptsDepth = el.depth;
|
|
97
|
-
else if (el.ns === NS.office && el.local === 'script')
|
|
98
|
-
this.script = { depth: el.depth, language: oattr(el, NS.script, 'language') ?? '', content: false };
|
|
99
|
-
else if (el.ns === NS.script && el.local === 'event-listener')
|
|
136
|
+
else if (el.ns === exports.NS.office && el.local === 'script')
|
|
137
|
+
this.script = { depth: el.depth, language: oattr(el, exports.NS.script, 'language') ?? '', content: false, index: el.index };
|
|
138
|
+
else if (el.ns === exports.NS.script && el.local === 'event-listener')
|
|
100
139
|
this.scriptEvent(el, href);
|
|
101
|
-
else if (el.ns === NS.presentation && el.local === 'event-listener')
|
|
140
|
+
else if (el.ns === exports.NS.presentation && el.local === 'event-listener')
|
|
102
141
|
this.presentationEvent(el, href);
|
|
103
|
-
else if ((el.ns === NS.text && el.local === 'dde-connection-decl') || (el.ns === NS.office && el.local === 'dde-source')) {
|
|
104
|
-
this.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, { application: (0, context_1.clip)(oattr(el, NS.office, 'dde-application') ?? ''), topic: (0, context_1.clip)(oattr(el, NS.office, 'dde-topic') ?? '') });
|
|
142
|
+
else if ((el.ns === exports.NS.text && el.local === 'dde-connection-decl') || (el.ns === exports.NS.office && el.local === 'dde-source')) {
|
|
143
|
+
this.edit(this.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, { application: (0, context_1.clip)(oattr(el, exports.NS.office, 'dde-application') ?? ''), topic: (0, context_1.clip)(oattr(el, exports.NS.office, 'dde-topic') ?? '') }), raw);
|
|
105
144
|
}
|
|
106
|
-
else if (el.ns === NS.draw && el.local === 'applet') {
|
|
107
|
-
this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Applet, { code: (0, context_1.clip)(oattr(el, NS.draw, 'code') ?? ''), ...(href ? { target: (0, context_1.clip)(href) } : {}) });
|
|
145
|
+
else if (el.ns === exports.NS.draw && el.local === 'applet') {
|
|
146
|
+
this.edit(this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Applet, { code: (0, context_1.clip)(oattr(el, exports.NS.draw, 'code') ?? ''), ...(href ? { target: (0, context_1.clip)(href) } : {}) }), raw);
|
|
108
147
|
return;
|
|
109
148
|
}
|
|
110
|
-
else if (el.ns === NS.draw && el.local === 'plugin') {
|
|
111
|
-
this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Plugin, { mimeType: (0, context_1.clip)(oattr(el, NS.draw, 'mime-type') ?? ''), ...(href ? { target: (0, context_1.clip)(href) } : {}) });
|
|
149
|
+
else if (el.ns === exports.NS.draw && el.local === 'plugin') {
|
|
150
|
+
this.edit(this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Plugin, { mimeType: (0, context_1.clip)(oattr(el, exports.NS.draw, 'mime-type') ?? ''), ...(href ? { target: (0, context_1.clip)(href) } : {}) }), raw);
|
|
112
151
|
return;
|
|
113
152
|
}
|
|
114
|
-
else if (el.ns === NS.draw && el.local === 'object-ole' && (href === undefined ||
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
else if (el.ns === exports.NS.draw && el.local === 'object-ole' && (href === undefined || this.inside(href))) {
|
|
154
|
+
// The object's entry may still be ahead in the archive, so its plugin verdict decides this element later. The
|
|
155
|
+
// preview image beside the element stays either way.
|
|
156
|
+
const r = this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, href ? { target: (0, context_1.clip)(href) } : undefined);
|
|
157
|
+
this.ctx.plan.objects.push({ part: this.entry, index: el.index, target: href ? (0, exports.packagePath)(href) : '', finding: r.finding, strips: r.action === 'strip' });
|
|
158
|
+
if (href && this.oleRefs.has((0, exports.packagePath)(href)))
|
|
159
|
+
this.oleRefs.set((0, exports.packagePath)(href), true);
|
|
118
160
|
}
|
|
119
161
|
for (const a of el.attributes) {
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
// The cell keeps its value without the formula.
|
|
163
|
+
if (odfNs(a.ns) !== exports.NS.table || (a.local !== 'formula' && a.local !== 'expression'))
|
|
164
|
+
continue;
|
|
165
|
+
const strip = { strip: new Set([(0, plan_1.attributeKey)(a.ns, a.local)]) };
|
|
166
|
+
if ((0, formulas_1.callsDde)(a.value))
|
|
167
|
+
this.edit(this.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, { formula: (0, context_1.clip)(a.value) }), raw, strip);
|
|
168
|
+
for (const [fn, kind] of (0, formulas_1.formulaCalls)(a.value))
|
|
169
|
+
this.edit(this.report(kind === 'fetch' ? types_1.OfficeCategory.External : types_1.OfficeCategory.Macro, types_1.OfficeDetail.Formula, { function: (0, context_1.clip)(fn), formula: (0, context_1.clip)(a.value) }), raw, strip);
|
|
122
170
|
}
|
|
123
|
-
if (href === undefined || (0, links_1.isFragment)(href) || (el.ns === NS.script && el.local === 'event-listener') || (el.ns === NS.presentation && el.local === 'event-listener'))
|
|
171
|
+
if (href === undefined || (0, links_1.isFragment)(href) || (el.ns === exports.NS.script && el.local === 'event-listener') || (el.ns === exports.NS.presentation && el.local === 'event-listener'))
|
|
124
172
|
return;
|
|
125
173
|
// A database document names its own embedded database this way.
|
|
126
|
-
if (el.ns === NS.database && el.local === 'connection-resource' && /^sdbc:embedded:/i.test(href.trim()))
|
|
174
|
+
if (el.ns === exports.NS.database && el.local === 'connection-resource' && /^sdbc:embedded:/i.test(href.trim()))
|
|
175
|
+
return;
|
|
176
|
+
// A chart names the document that holds it, its data source, as "..".
|
|
177
|
+
if (el.ns === exports.NS.chart && el.local === 'chart' && /^\.\.\/?$/.test(href.trim()))
|
|
127
178
|
return;
|
|
128
|
-
if (LINK_ELEMENTS.has(key)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.
|
|
179
|
+
if (LINK_ELEMENTS.has(key)) {
|
|
180
|
+
// The text of a link stays without it. An image map area is nothing but its link.
|
|
181
|
+
const r = this.ctx.link(href, this.entry, { element: (0, context_1.clip)(el.qname) });
|
|
182
|
+
this.edit(r, raw, el.local === 'a' ? { unwrap: true } : { remove: true });
|
|
183
|
+
if (r.action !== 'strip' && !this.label)
|
|
184
|
+
this.label = { index: el.index, href, text: '', title: oattr(el, exports.NS.office, 'title') ?? oattr(el, exports.NS.xlink, 'title'), unwrap: el.local === 'a' };
|
|
185
|
+
}
|
|
186
|
+
else if (el.ns === exports.NS.form && el.local === 'form') {
|
|
187
|
+
// A form's own href is where it sends its fields, which goes as pdf-defuse's form submit actions go.
|
|
188
|
+
this.edit(this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.SubmitForm, { element: (0, context_1.clip)(el.qname), target: (0, context_1.clip)(href, 2000) }), raw, stripHref(raw));
|
|
189
|
+
}
|
|
190
|
+
else if (el.ns === exports.NS.form)
|
|
191
|
+
this.edit(this.ctx.link(href, this.entry, { element: (0, context_1.clip)(el.qname) }), raw, stripHref(raw));
|
|
192
|
+
else if (ALWAYS_OUTSIDE.has(key) || !this.inside(href)) {
|
|
193
|
+
this.edit(this.ctx.external(EXTERNAL_KIND.get(key) ?? types_1.OfficeDetail.Other, href, this.entry, { element: (0, context_1.clip)(el.qname) }, (0, context_1.clip)(el.local)), raw);
|
|
132
194
|
}
|
|
133
195
|
}
|
|
134
196
|
text(t) {
|
|
135
197
|
if (this.script && t.trim())
|
|
136
198
|
this.script.content = true;
|
|
199
|
+
if (this.label && this.label.text.length < 4096)
|
|
200
|
+
this.label.text += t;
|
|
137
201
|
}
|
|
138
202
|
end(el) {
|
|
139
|
-
|
|
203
|
+
const label = this.label;
|
|
204
|
+
if (label && label.index === el.index) {
|
|
205
|
+
this.label = undefined;
|
|
206
|
+
const r = this.ctx.labelMismatch(label.href, [label.text, label.title], this.entry, { element: (0, context_1.clip)(el.qname) });
|
|
207
|
+
if (r)
|
|
208
|
+
this.edit(r, el, label.unwrap ? { unwrap: true } : { remove: true });
|
|
209
|
+
}
|
|
210
|
+
if (this.scriptsDepth === el.depth && odfNs(el.ns) === exports.NS.office && el.local === 'scripts')
|
|
140
211
|
this.scriptsDepth = 0;
|
|
141
212
|
if (this.script && this.script.depth === el.depth) {
|
|
142
213
|
if (this.script.content)
|
|
143
|
-
this.report(types_1.OfficeCategory.Macro, /basic/i.test(this.script.language) ? types_1.OfficeDetail.OdfBasic : types_1.OfficeDetail.OdfScripts, { language: (0, context_1.clip)(this.script.language) });
|
|
214
|
+
this.edit(this.report(types_1.OfficeCategory.Macro, /basic/i.test(this.script.language) ? types_1.OfficeDetail.OdfBasic : types_1.OfficeDetail.OdfScripts, { language: (0, context_1.clip)(this.script.language) }), el, { remove: true });
|
|
144
215
|
this.script = undefined;
|
|
145
216
|
}
|
|
146
217
|
}
|
|
147
218
|
scriptEvent(el, href) {
|
|
148
|
-
const event = oattr(el, NS.script, 'event-name') ?? '';
|
|
219
|
+
const event = oattr(el, exports.NS.script, 'event-name') ?? '';
|
|
149
220
|
// LibreOffice runs nothing for a listener whose script URL and macro name are both empty. An empty href does not
|
|
150
221
|
// hide a macro name.
|
|
151
|
-
const target = [href, oattr(el, NS.script, 'macro-name')].find(v => v !== undefined && v !== '');
|
|
222
|
+
const target = [href, oattr(el, exports.NS.script, 'macro-name')].find(v => v !== undefined && v !== '');
|
|
152
223
|
if (target === undefined)
|
|
153
224
|
return;
|
|
154
225
|
const name = event
|
|
@@ -157,45 +228,43 @@ class EntryRules {
|
|
|
157
228
|
.replace(/^on/, '')
|
|
158
229
|
.replace(/-/g, '');
|
|
159
230
|
const onOpen = ON_OPEN.has(name) || (this.scriptsDepth > 0 && ON_OPEN_DOCUMENT.has(name));
|
|
160
|
-
const data = { event: (0, context_1.clip)(event), target: (0, context_1.clip)(target), language: (0, context_1.clip)(oattr(el, NS.script, 'language') ?? '') };
|
|
161
|
-
|
|
162
|
-
this.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.EventOnOpen, data);
|
|
163
|
-
else
|
|
164
|
-
this.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Event, data);
|
|
231
|
+
const data = { event: (0, context_1.clip)(event), target: (0, context_1.clip)(target), language: (0, context_1.clip)(oattr(el, exports.NS.script, 'language') ?? '') };
|
|
232
|
+
this.edit(this.report(types_1.OfficeCategory.Macro, onOpen ? types_1.OfficeDetail.EventOnOpen : types_1.OfficeDetail.Event, data), el);
|
|
165
233
|
}
|
|
166
234
|
presentationEvent(el, href) {
|
|
167
|
-
const action = oattr(el, NS.presentation, 'action') ?? '';
|
|
168
|
-
const data = { event: (0, context_1.clip)(oattr(el, NS.script, 'event-name') ?? ''), action: (0, context_1.clip)(action), ...(href ? { target: (0, context_1.clip)(href) } : {}) };
|
|
235
|
+
const action = oattr(el, exports.NS.presentation, 'action') ?? '';
|
|
236
|
+
const data = { event: (0, context_1.clip)(oattr(el, exports.NS.script, 'event-name') ?? ''), action: (0, context_1.clip)(action), ...(href ? { target: (0, context_1.clip)(href) } : {}) };
|
|
169
237
|
if (action === 'execute')
|
|
170
|
-
this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, data);
|
|
238
|
+
this.edit(this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, data), el);
|
|
171
239
|
else if (action === 'verb')
|
|
172
|
-
this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, data);
|
|
240
|
+
this.edit(this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, data), el);
|
|
173
241
|
else if (href !== undefined && !(0, links_1.isFragment)(href))
|
|
174
|
-
this.ctx.link(href, this.entry, data);
|
|
242
|
+
this.edit(this.ctx.link(href, this.entry, data), el);
|
|
175
243
|
}
|
|
176
244
|
}
|
|
245
|
+
exports.EntryRules = EntryRules;
|
|
177
246
|
/** The manifest. Only paths that name an entry of the archive are kept, so a manifest of millions costs no memory. */
|
|
178
247
|
async function readManifest(ctx, entry) {
|
|
179
248
|
const keys = new Set(ctx.zip.entries.map(e => (0, zip_1.nameKey)(e.name)));
|
|
180
249
|
const m = { paths: new Set(), encrypted: false };
|
|
181
250
|
await ctx.parse(entry, {
|
|
182
251
|
start(el) {
|
|
183
|
-
if (odfNs(el.ns) !== NS.manifest)
|
|
252
|
+
if (odfNs(el.ns) !== exports.NS.manifest)
|
|
184
253
|
return;
|
|
185
254
|
if (el.local === 'encryption-data')
|
|
186
255
|
m.encrypted = true;
|
|
187
256
|
if (el.local !== 'file-entry')
|
|
188
257
|
return;
|
|
189
|
-
const path = oattr(el, NS.manifest, 'full-path');
|
|
258
|
+
const path = oattr(el, exports.NS.manifest, 'full-path');
|
|
190
259
|
if (path === undefined)
|
|
191
260
|
return;
|
|
192
261
|
if (path === '/') {
|
|
193
262
|
if (m.root !== undefined)
|
|
194
263
|
ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.DuplicateEntry, entry.name, { kind: 'manifest path', name: '/' });
|
|
195
|
-
m.root = oattr(el, NS.manifest, 'media-type') ?? '';
|
|
264
|
+
m.root = oattr(el, exports.NS.manifest, 'media-type') ?? '';
|
|
196
265
|
return;
|
|
197
266
|
}
|
|
198
|
-
const key = packagePath(path);
|
|
267
|
+
const key = (0, exports.packagePath)(path);
|
|
199
268
|
if (!keys.has(key))
|
|
200
269
|
return;
|
|
201
270
|
if (m.paths.has(key))
|
|
@@ -262,6 +331,8 @@ async function inspectOdf(ctx) {
|
|
|
262
331
|
const folders = new Map();
|
|
263
332
|
const oleRefs = new Map(zip.entries.map(e => [(0, zip_1.nameKey)(e.name), false]));
|
|
264
333
|
const oleEntries = [];
|
|
334
|
+
/** Entries that are whole files in formats of their own, such as pictures, which go to the plugins. */
|
|
335
|
+
const wholeFiles = [];
|
|
265
336
|
for (const entry of zip.entries) {
|
|
266
337
|
if (entry === mimetype || entry === manifestEntry)
|
|
267
338
|
continue;
|
|
@@ -280,18 +351,64 @@ async function inspectOdf(ctx) {
|
|
|
280
351
|
await ctx.parse(entry, new EntryRules(ctx, entry.name, oleRefs));
|
|
281
352
|
else if (await ctx.drain(entry))
|
|
282
353
|
oleEntries.push(entry);
|
|
354
|
+
else if (!entry.directory && !/^meta-inf\//i.test(entry.name))
|
|
355
|
+
wholeFiles.push(entry);
|
|
283
356
|
}
|
|
357
|
+
const plan = ctx.plan;
|
|
358
|
+
// A folder goes whole, its empty files with it.
|
|
284
359
|
for (const [prefix, f] of folders)
|
|
285
|
-
ctx.report(f.category, f.detail, prefix, { entries: f.entries })
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
360
|
+
if (ctx.report(f.category, f.detail, prefix, { entries: f.entries }).action === 'strip')
|
|
361
|
+
plan.folders.add(`${(0, zip_1.nameKey)(prefix)}/`);
|
|
362
|
+
// OLE entries go to the plugins. When none takes one, the elements that refer to it decide, or EMBEDDED/OLE_FILE.
|
|
363
|
+
const decided = new Map();
|
|
364
|
+
for (const e of oleEntries) {
|
|
365
|
+
const key = (0, zip_1.nameKey)(e.name);
|
|
366
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: e.name, location: e.name, chunks: () => zip.read(e) });
|
|
367
|
+
if (decision) {
|
|
368
|
+
decided.set(key, decision.keep);
|
|
369
|
+
if (!decision.keep)
|
|
370
|
+
plan.drop.add(key);
|
|
371
|
+
else if (decision.replacement)
|
|
372
|
+
plan.replace.set(key, decision.replacement);
|
|
373
|
+
}
|
|
374
|
+
if (!oleRefs.get(key)) {
|
|
375
|
+
const r = ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleFile, e.name, { part: e.name }, (0, contained_1.presence)(decision));
|
|
376
|
+
if (!decision && r.action === 'strip')
|
|
377
|
+
plan.drop.add(key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// A whole file no plugin takes stays, since no rule names it.
|
|
381
|
+
for (const e of wholeFiles) {
|
|
382
|
+
const decision = await (0, contained_1.decideContained)(ctx, { name: e.name, location: e.name, chunks: () => zip.read(e) });
|
|
383
|
+
if (decision && !decision.keep)
|
|
384
|
+
plan.drop.add((0, zip_1.nameKey)(e.name));
|
|
385
|
+
else if (decision?.replacement)
|
|
386
|
+
plan.replace.set((0, zip_1.nameKey)(e.name), decision.replacement);
|
|
387
|
+
}
|
|
388
|
+
const files = new Set(zip.entries.filter(e => !e.directory).map(e => (0, zip_1.nameKey)(e.name)));
|
|
389
|
+
for (const o of plan.objects) {
|
|
390
|
+
const kept = decided.get(o.target);
|
|
391
|
+
if (kept === true) {
|
|
392
|
+
// A plugin kept the object's file, so the object stays.
|
|
393
|
+
if (o.finding) {
|
|
394
|
+
o.finding.action = 'info';
|
|
395
|
+
ctx.run.superseded.add(o.finding);
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (kept === false || o.strips) {
|
|
400
|
+
plan.element(o.part, o.index, { remove: true });
|
|
401
|
+
if (files.has(o.target))
|
|
402
|
+
plan.drop.add(o.target);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
289
405
|
if (manifest) {
|
|
290
406
|
for (const e of zip.entries) {
|
|
291
407
|
if (e.directory || e === mimetype || e === manifestEntry || /^meta-inf\//i.test(e.name))
|
|
292
408
|
continue;
|
|
293
|
-
if (!manifest.paths.has((0, zip_1.nameKey)(e.name)))
|
|
294
|
-
|
|
409
|
+
if (!manifest.paths.has((0, zip_1.nameKey)(e.name)) && ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreferencedPart, e.name, { part: e.name }).action === 'strip')
|
|
410
|
+
plan.drop.add((0, zip_1.nameKey)(e.name));
|
|
295
411
|
}
|
|
296
412
|
}
|
|
413
|
+
plan.odf = { mimetype, manifest: manifestEntry, mediaType: type };
|
|
297
414
|
}
|
package/dist/offcrypto.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { type ByteSource } from './types';
|
|
|
5
5
|
import type { Decrypt } from './xls';
|
|
6
6
|
/** The password Excel opens a workbook with when nobody set one to open it (MS-OFFCRYPTO 2.4.3). */
|
|
7
7
|
export declare const DEFAULT_PASSWORD = "VelvetSweatshop";
|
|
8
|
+
/** The password PowerPoint 97 to 2003 opens a presentation with when nobody set one to open it. */
|
|
9
|
+
export declare const POWERPOINT_PASSWORD = "/01Hannes Ruescher/01";
|
|
8
10
|
/** RC4, which OpenSSL 3 leaves out of its default provider. */
|
|
9
11
|
export declare class Rc4 {
|
|
10
12
|
private readonly s;
|
|
@@ -17,9 +19,30 @@ export declare class Rc4 {
|
|
|
17
19
|
/**
|
|
18
20
|
* Opens a workbook that its FILEPASS record (MS-XLS 2.4.117) encrypts with RC4 or RC4 CryptoAPI, with the caller's
|
|
19
21
|
* password or VelvetSweatshop. The records after FILEPASS are encrypted in 1024-byte blocks of the stream, each with its
|
|
20
|
-
* own key. Their headers are not, but take keystream bytes, and some records are never encrypted.
|
|
22
|
+
* own key. Their headers are not, but take keystream bytes, and some records are never encrypted. XOR obfuscation,
|
|
23
|
+
* the only kind an Excel 5.0 or 95 workbook has, is undone the same way record by record.
|
|
21
24
|
*/
|
|
22
25
|
export declare function unlockWorkbook(ctx: RunContext, filePass: BiffRecord, stream: string): Decrypt;
|
|
26
|
+
/** A stream decrypted where it is read. */
|
|
27
|
+
export type StreamDecrypt = (offset: number, bytes: Uint8Array) => Uint8Array;
|
|
28
|
+
/** What opens an encrypted Word 97 to 2003 document: a decryption for each of its three encrypted streams. */
|
|
29
|
+
export interface WordKey {
|
|
30
|
+
word: StreamDecrypt;
|
|
31
|
+
table: StreamDecrypt;
|
|
32
|
+
data: StreamDecrypt;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Opens an encrypted Word 97 to 2003 document (MS-DOC 2.2.6) with the caller's passwords. XOR obfuscation keeps its
|
|
36
|
+
* verifier in FibBase.lKey. RC4 and RC4 CryptoAPI keep their EncryptionHeader in the table stream's first lKey bytes.
|
|
37
|
+
* The WordDocument stream stays plain for its first 68 bytes, and Word has no default password.
|
|
38
|
+
*/
|
|
39
|
+
export declare function unlockDocument(ctx: RunContext, fib: Uint8Array, table: ByteSource, stream: string): Promise<WordKey>;
|
|
40
|
+
/**
|
|
41
|
+
* Opens an encrypted PowerPoint 97 to 2003 presentation (MS-PPT 2.3.7): `session` is the CryptSession10Container's
|
|
42
|
+
* data, an RC4 CryptoAPI EncryptionHeader. Returns the key of each persist object, whose block number is its persist
|
|
43
|
+
* id, after the caller's passwords and PowerPoint's own default have been tried.
|
|
44
|
+
*/
|
|
45
|
+
export declare function unlockPresentation(ctx: RunContext, session: Uint8Array, stream: string): (id: number) => Rc4;
|
|
23
46
|
/**
|
|
24
47
|
* Inspects an encrypted OOXML package: EncryptionInfo (MS-OFFCRYPTO 2.3.4) names the cipher, and the caller's password
|
|
25
48
|
* or VelvetSweatshop gives the key. The package it decrypts goes through the OOXML rules, read in place: Agile
|