@patrickdk77/office-defuse 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +317 -2
- package/dist/biff.d.ts +62 -0
- package/dist/biff.js +95 -0
- package/dist/cfb.d.ts +58 -0
- package/dist/cfb.js +456 -0
- package/dist/context.d.ts +66 -0
- package/dist/context.js +121 -0
- package/dist/crc32.d.ts +8 -0
- package/dist/crc32.js +58 -0
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +200 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.js +33 -0
- package/dist/fields.d.ts +29 -0
- package/dist/fields.js +97 -0
- package/dist/findings.d.ts +25 -0
- package/dist/findings.js +188 -0
- package/dist/formats.d.ts +10 -0
- package/dist/formats.js +46 -0
- package/dist/formulas.d.ts +22 -0
- package/dist/formulas.js +77 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +30 -0
- package/dist/index.mjs +3 -0
- package/dist/io.d.ts +23 -0
- package/dist/io.js +139 -0
- package/dist/links.d.ts +26 -0
- package/dist/links.js +145 -0
- package/dist/msg.d.ts +7 -0
- package/dist/msg.js +97 -0
- package/dist/objects.d.ts +42 -0
- package/dist/objects.js +134 -0
- package/dist/odf.d.ts +3 -0
- package/dist/odf.js +297 -0
- package/dist/offcrypto.d.ts +31 -0
- package/dist/offcrypto.js +408 -0
- package/dist/ole.d.ts +12 -0
- package/dist/ole.js +127 -0
- package/dist/oleobj.d.ts +74 -0
- package/dist/oleobj.js +198 -0
- package/dist/ooxml.d.ts +8 -0
- package/dist/ooxml.js +468 -0
- package/dist/package.d.ts +3 -0
- package/dist/package.js +21 -0
- package/dist/ppt.d.ts +8 -0
- package/dist/ppt.js +241 -0
- package/dist/rtf.d.ts +8 -0
- package/dist/rtf.js +539 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +63 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.js +152 -0
- package/dist/word.d.ts +9 -0
- package/dist/word.js +160 -0
- package/dist/xls.d.ts +27 -0
- package/dist/xls.js +174 -0
- package/dist/xlsb.d.ts +19 -0
- package/dist/xlsb.js +101 -0
- package/dist/xml.d.ts +256 -0
- package/dist/xml.js +1105 -0
- package/dist/zip-writer.d.ts +38 -0
- package/dist/zip-writer.js +182 -0
- package/dist/zip.d.ts +55 -0
- package/dist/zip.js +535 -0
- package/package.json +96 -4
package/dist/odf.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inspectOdf = inspectOdf;
|
|
4
|
+
const context_1 = require("./context");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const formats_1 = require("./formats");
|
|
7
|
+
const formulas_1 = require("./formulas");
|
|
8
|
+
const links_1 = require("./links");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const zip_1 = require("./zip");
|
|
11
|
+
const NS = {
|
|
12
|
+
manifest: 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
|
|
13
|
+
office: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
|
|
14
|
+
script: 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
|
15
|
+
presentation: 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
|
|
16
|
+
text: 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
|
|
17
|
+
draw: 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
|
|
18
|
+
table: 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
|
|
19
|
+
form: 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
|
|
20
|
+
meta: 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
|
21
|
+
style: 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
|
22
|
+
database: 'urn:oasis:names:tc:opendocument:xmlns:database:1.0',
|
|
23
|
+
xlink: 'http://www.w3.org/1999/xlink',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Folders of code and settings. Each is reported by name, as a whole, so the files in them are inflated and checked
|
|
27
|
+
* but not parsed.
|
|
28
|
+
*/
|
|
29
|
+
const FOLDERS = [
|
|
30
|
+
['basic', types_1.OfficeDetail.OdfBasic, types_1.OfficeCategory.Macro],
|
|
31
|
+
['scripts', types_1.OfficeDetail.OdfScripts, types_1.OfficeCategory.Macro],
|
|
32
|
+
['dialogs', types_1.OfficeDetail.OdfDialogs, types_1.OfficeCategory.Macro],
|
|
33
|
+
['configurations2', types_1.OfficeDetail.OdfConfiguration, types_1.OfficeCategory.Ui],
|
|
34
|
+
];
|
|
35
|
+
/** Event names that fire as the document opens. Focus and activation count only for the document's own events. */
|
|
36
|
+
const ON_OPEN = new Set(['load', 'new', 'create', 'loadfinished', 'viewcreated']);
|
|
37
|
+
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`]);
|
|
39
|
+
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],
|
|
50
|
+
]);
|
|
51
|
+
/** 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`]);
|
|
53
|
+
/**
|
|
54
|
+
* LibreOffice reads urn:oasis:names:tc:<any>:xmlns:<name>:1.<any> as the OpenDocument namespace <name> 1.0, so this
|
|
55
|
+
* does too. The comparison ignores case, which can only find more.
|
|
56
|
+
*/
|
|
57
|
+
function odfNs(ns) {
|
|
58
|
+
const m = /^urn:oasis:names:tc:[^:]*:xmlns:([^:]*):1\.[^:]+$/i.exec(ns);
|
|
59
|
+
return m ? `urn:oasis:names:tc:opendocument:xmlns:${m[1].toLowerCase()}:1.0` : ns;
|
|
60
|
+
}
|
|
61
|
+
/** An attribute by normalized namespace and local name. */
|
|
62
|
+
function oattr(el, ns, local) {
|
|
63
|
+
for (const a of el.attributes)
|
|
64
|
+
if (a.local === local && odfNs(a.ns) === ns)
|
|
65
|
+
return a.value;
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** A path inside the package as the manifest and the zip name it: without "./" and leading slashes. */
|
|
69
|
+
const packagePath = (href) => (0, zip_1.nameKey)(href.trim().replace(/^(\.\/)+/, ''));
|
|
70
|
+
/** The rules that read inside one XML entry of an ODF package. */
|
|
71
|
+
class EntryRules {
|
|
72
|
+
ctx;
|
|
73
|
+
entry;
|
|
74
|
+
oleRefs;
|
|
75
|
+
/** Depth of the office:scripts element being read, 0 outside one. */
|
|
76
|
+
scriptsDepth = 0;
|
|
77
|
+
/** An office:script element being read, with whether it holds anything. */
|
|
78
|
+
script;
|
|
79
|
+
constructor(ctx, entry,
|
|
80
|
+
/** Whether an object refers to each entry, by key. Only entries are kept, so a part full of references costs no memory. */
|
|
81
|
+
oleRefs) {
|
|
82
|
+
this.ctx = ctx;
|
|
83
|
+
this.entry = entry;
|
|
84
|
+
this.oleRefs = oleRefs;
|
|
85
|
+
}
|
|
86
|
+
report(category, detail, data) {
|
|
87
|
+
this.ctx.report(category, detail, this.entry, data);
|
|
88
|
+
}
|
|
89
|
+
start(raw) {
|
|
90
|
+
if (this.script)
|
|
91
|
+
this.script.content = true;
|
|
92
|
+
const el = { ...raw, ns: odfNs(raw.ns) };
|
|
93
|
+
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)
|
|
96
|
+
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')
|
|
100
|
+
this.scriptEvent(el, href);
|
|
101
|
+
else if (el.ns === NS.presentation && el.local === 'event-listener')
|
|
102
|
+
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') ?? '') });
|
|
105
|
+
}
|
|
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) } : {}) });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
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) } : {}) });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
else if (el.ns === NS.draw && el.local === 'object-ole' && (href === undefined || (0, links_1.isInsidePackage)(href))) {
|
|
115
|
+
this.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, href ? { target: (0, context_1.clip)(href) } : undefined);
|
|
116
|
+
if (href && this.oleRefs.has(packagePath(href)))
|
|
117
|
+
this.oleRefs.set(packagePath(href), true);
|
|
118
|
+
}
|
|
119
|
+
for (const a of el.attributes) {
|
|
120
|
+
if (odfNs(a.ns) === NS.table && (a.local === 'formula' || a.local === 'expression') && (0, formulas_1.callsDde)(a.value))
|
|
121
|
+
this.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Formula, { formula: (0, context_1.clip)(a.value) });
|
|
122
|
+
}
|
|
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'))
|
|
124
|
+
return;
|
|
125
|
+
// 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()))
|
|
127
|
+
return;
|
|
128
|
+
if (LINK_ELEMENTS.has(key) || (el.ns === NS.form && el.local !== 'form'))
|
|
129
|
+
this.ctx.link(href, this.entry, { element: (0, context_1.clip)(el.qname) });
|
|
130
|
+
else if ((ALWAYS_OUTSIDE.has(key) || !(0, links_1.isInsidePackage)(href)) && !(el.ns === NS.form && el.local === 'form')) {
|
|
131
|
+
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));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
text(t) {
|
|
135
|
+
if (this.script && t.trim())
|
|
136
|
+
this.script.content = true;
|
|
137
|
+
}
|
|
138
|
+
end(el) {
|
|
139
|
+
if (this.scriptsDepth === el.depth && odfNs(el.ns) === NS.office && el.local === 'scripts')
|
|
140
|
+
this.scriptsDepth = 0;
|
|
141
|
+
if (this.script && this.script.depth === el.depth) {
|
|
142
|
+
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) });
|
|
144
|
+
this.script = undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
scriptEvent(el, href) {
|
|
148
|
+
const event = oattr(el, NS.script, 'event-name') ?? '';
|
|
149
|
+
// LibreOffice runs nothing for a listener whose script URL and macro name are both empty. An empty href does not
|
|
150
|
+
// hide a macro name.
|
|
151
|
+
const target = [href, oattr(el, NS.script, 'macro-name')].find(v => v !== undefined && v !== '');
|
|
152
|
+
if (target === undefined)
|
|
153
|
+
return;
|
|
154
|
+
const name = event
|
|
155
|
+
.slice(event.lastIndexOf(':') + 1)
|
|
156
|
+
.toLowerCase()
|
|
157
|
+
.replace(/^on/, '')
|
|
158
|
+
.replace(/-/g, '');
|
|
159
|
+
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
|
+
if (onOpen)
|
|
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);
|
|
165
|
+
}
|
|
166
|
+
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) } : {}) };
|
|
169
|
+
if (action === 'execute')
|
|
170
|
+
this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, data);
|
|
171
|
+
else if (action === 'verb')
|
|
172
|
+
this.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, data);
|
|
173
|
+
else if (href !== undefined && !(0, links_1.isFragment)(href))
|
|
174
|
+
this.ctx.link(href, this.entry, data);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** The manifest. Only paths that name an entry of the archive are kept, so a manifest of millions costs no memory. */
|
|
178
|
+
async function readManifest(ctx, entry) {
|
|
179
|
+
const keys = new Set(ctx.zip.entries.map(e => (0, zip_1.nameKey)(e.name)));
|
|
180
|
+
const m = { paths: new Set(), encrypted: false };
|
|
181
|
+
await ctx.parse(entry, {
|
|
182
|
+
start(el) {
|
|
183
|
+
if (odfNs(el.ns) !== NS.manifest)
|
|
184
|
+
return;
|
|
185
|
+
if (el.local === 'encryption-data')
|
|
186
|
+
m.encrypted = true;
|
|
187
|
+
if (el.local !== 'file-entry')
|
|
188
|
+
return;
|
|
189
|
+
const path = oattr(el, NS.manifest, 'full-path');
|
|
190
|
+
if (path === undefined)
|
|
191
|
+
return;
|
|
192
|
+
if (path === '/') {
|
|
193
|
+
if (m.root !== undefined)
|
|
194
|
+
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') ?? '';
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const key = packagePath(path);
|
|
199
|
+
if (!keys.has(key))
|
|
200
|
+
return;
|
|
201
|
+
if (m.paths.has(key))
|
|
202
|
+
ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.DuplicateEntry, entry.name, { kind: 'manifest path', name: (0, context_1.clip)(path) });
|
|
203
|
+
m.paths.add(key);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return m;
|
|
207
|
+
}
|
|
208
|
+
/** The code folder an entry sits in, as the folder's path and its finding. */
|
|
209
|
+
function folderOf(name) {
|
|
210
|
+
const segs = name.split('/');
|
|
211
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
212
|
+
const hit = FOLDERS.find(([folder]) => folder === segs[i].toLowerCase());
|
|
213
|
+
if (hit)
|
|
214
|
+
return { prefix: `${segs.slice(0, i + 1).join('/')}/`, detail: hit[1], category: hit[2] };
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
/** The most of the mimetype entry that is kept. The longest media type in the table is 56 characters. */
|
|
219
|
+
const MAX_MIMETYPE = 256;
|
|
220
|
+
/** Inspects an OpenDocument package. */
|
|
221
|
+
async function inspectOdf(ctx) {
|
|
222
|
+
const zip = ctx.zip;
|
|
223
|
+
const mimetype = zip.entries.find(e => e.name === 'mimetype');
|
|
224
|
+
const manifestEntry = zip.get('META-INF/manifest.xml');
|
|
225
|
+
const layout = (reason) => ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.OdfLayout, mimetype?.name ?? 'META-INF/manifest.xml', { reason });
|
|
226
|
+
let declared;
|
|
227
|
+
if (mimetype) {
|
|
228
|
+
let head = Buffer.alloc(0);
|
|
229
|
+
await ctx.drain(mimetype, c => {
|
|
230
|
+
if (head.length < MAX_MIMETYPE)
|
|
231
|
+
head = Buffer.concat([head, c.subarray(0, MAX_MIMETYPE - head.length)]);
|
|
232
|
+
});
|
|
233
|
+
// A longer entry holds no media type this package reads, whatever its start says.
|
|
234
|
+
declared = head.toString('latin1') + (mimetype.size > MAX_MIMETYPE ? '...' : '');
|
|
235
|
+
const first = zip.entries.reduce((a, b) => (b.localOffset < a.localOffset ? b : a));
|
|
236
|
+
if (first !== mimetype)
|
|
237
|
+
layout('mimetype is not the first entry');
|
|
238
|
+
if (mimetype.method !== 0)
|
|
239
|
+
layout('mimetype is compressed');
|
|
240
|
+
if (mimetype.localExtra)
|
|
241
|
+
layout('mimetype has an extra field');
|
|
242
|
+
if (declared !== declared.trim())
|
|
243
|
+
layout('mimetype has white space around the media type');
|
|
244
|
+
declared = declared.trim();
|
|
245
|
+
}
|
|
246
|
+
else
|
|
247
|
+
layout('there is no mimetype entry');
|
|
248
|
+
const manifest = manifestEntry ? await readManifest(ctx, manifestEntry) : undefined;
|
|
249
|
+
if (!manifest)
|
|
250
|
+
layout('there is no manifest');
|
|
251
|
+
const root = manifest?.root;
|
|
252
|
+
if (declared !== undefined && root !== undefined && declared !== root && formats_1.ODF_FORMATS.has(declared) && formats_1.ODF_FORMATS.has(root)) {
|
|
253
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Package, types_1.OfficeDetail.AmbiguousFormat, 'The mimetype entry and the manifest name different formats', { mimetype: (0, context_1.clip)(declared), manifest: (0, context_1.clip)(root) });
|
|
254
|
+
}
|
|
255
|
+
const type = declared ?? root ?? '';
|
|
256
|
+
const format = formats_1.ODF_FORMATS.get(type);
|
|
257
|
+
if (!format)
|
|
258
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unrecognized, 'The OpenDocument media type is not one this package reads', { mediaType: (0, context_1.clip)(type) });
|
|
259
|
+
ctx.format = format;
|
|
260
|
+
if (manifest?.encrypted)
|
|
261
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.PasswordRequired, 'The manifest lists encrypted entries');
|
|
262
|
+
const folders = new Map();
|
|
263
|
+
const oleRefs = new Map(zip.entries.map(e => [(0, zip_1.nameKey)(e.name), false]));
|
|
264
|
+
const oleEntries = [];
|
|
265
|
+
for (const entry of zip.entries) {
|
|
266
|
+
if (entry === mimetype || entry === manifestEntry)
|
|
267
|
+
continue;
|
|
268
|
+
const folder = folderOf(entry.name);
|
|
269
|
+
if (folder) {
|
|
270
|
+
// LibreOffice writes an empty Configurations2/accelerator/current.xml into the documents it saves. An empty
|
|
271
|
+
// file holds no code or setting.
|
|
272
|
+
if (entry.size > 0) {
|
|
273
|
+
const f = folders.get(folder.prefix) ?? { detail: folder.detail, category: folder.category, entries: 0 };
|
|
274
|
+
f.entries++;
|
|
275
|
+
folders.set(folder.prefix, f);
|
|
276
|
+
}
|
|
277
|
+
await ctx.drain(entry);
|
|
278
|
+
}
|
|
279
|
+
else if (!entry.directory && /\.xml$/i.test(entry.name))
|
|
280
|
+
await ctx.parse(entry, new EntryRules(ctx, entry.name, oleRefs));
|
|
281
|
+
else if (await ctx.drain(entry))
|
|
282
|
+
oleEntries.push(entry);
|
|
283
|
+
}
|
|
284
|
+
for (const [prefix, f] of folders)
|
|
285
|
+
ctx.report(f.category, f.detail, prefix, { entries: f.entries });
|
|
286
|
+
for (const e of oleEntries)
|
|
287
|
+
if (!oleRefs.get((0, zip_1.nameKey)(e.name)))
|
|
288
|
+
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleFile, e.name, { part: e.name });
|
|
289
|
+
if (manifest) {
|
|
290
|
+
for (const e of zip.entries) {
|
|
291
|
+
if (e.directory || e === mimetype || e === manifestEntry || /^meta-inf\//i.test(e.name))
|
|
292
|
+
continue;
|
|
293
|
+
if (!manifest.paths.has((0, zip_1.nameKey)(e.name)))
|
|
294
|
+
ctx.report(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreferencedPart, e.name, { part: e.name });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BiffRecord } from './biff';
|
|
2
|
+
import type { CfbEntry, CompoundFile } from './cfb';
|
|
3
|
+
import type { RunContext } from './context';
|
|
4
|
+
import { type ByteSource } from './types';
|
|
5
|
+
import type { Decrypt } from './xls';
|
|
6
|
+
/** The password Excel opens a workbook with when nobody set one to open it (MS-OFFCRYPTO 2.4.3). */
|
|
7
|
+
export declare const DEFAULT_PASSWORD = "VelvetSweatshop";
|
|
8
|
+
/** RC4, which OpenSSL 3 leaves out of its default provider. */
|
|
9
|
+
export declare class Rc4 {
|
|
10
|
+
private readonly s;
|
|
11
|
+
private i;
|
|
12
|
+
private j;
|
|
13
|
+
constructor(key: Uint8Array);
|
|
14
|
+
/** XORs the next keystream bytes into a copy of `data`. */
|
|
15
|
+
crypt(data: Uint8Array): Uint8Array;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Opens a workbook that its FILEPASS record (MS-XLS 2.4.117) encrypts with RC4 or RC4 CryptoAPI, with the caller's
|
|
19
|
+
* 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.
|
|
21
|
+
*/
|
|
22
|
+
export declare function unlockWorkbook(ctx: RunContext, filePass: BiffRecord, stream: string): Decrypt;
|
|
23
|
+
/**
|
|
24
|
+
* Inspects an encrypted OOXML package: EncryptionInfo (MS-OFFCRYPTO 2.3.4) names the cipher, and the caller's password
|
|
25
|
+
* or VelvetSweatshop gives the key. The package it decrypts goes through the OOXML rules, read in place: Agile
|
|
26
|
+
* encryption works in 4,096-byte segments and Standard in 16-byte blocks, so nothing is decrypted that is not read.
|
|
27
|
+
*/
|
|
28
|
+
export declare function openPackage(ctx: RunContext, cfb: CompoundFile, info: CfbEntry, pkg: CfbEntry): Promise<{
|
|
29
|
+
source: ByteSource;
|
|
30
|
+
size: number;
|
|
31
|
+
}>;
|