@patrickdk77/office-defuse 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -36
- package/dist/biff.d.ts +62 -0
- package/dist/biff.js +95 -0
- package/dist/cfb-writer.d.ts +25 -0
- package/dist/cfb-writer.js +314 -0
- package/dist/cfb.d.ts +58 -0
- package/dist/cfb.js +456 -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 +140 -17
- package/dist/context.js +135 -37
- package/dist/crc32.d.ts +8 -2
- package/dist/crc32.js +44 -4
- package/dist/engine.d.ts +12 -1
- package/dist/engine.js +336 -42
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +27 -1
- package/dist/fields.d.ts +36 -0
- package/dist/fields.js +139 -0
- package/dist/findings.d.ts +2 -1
- package/dist/findings.js +44 -9
- 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 +8 -0
- package/dist/msg.js +251 -0
- package/dist/objects.d.ts +45 -0
- package/dist/objects.js +204 -0
- package/dist/odf.d.ts +66 -2
- package/dist/odf.js +181 -64
- package/dist/offcrypto.d.ts +54 -0
- package/dist/offcrypto.js +647 -0
- package/dist/ole.d.ts +12 -0
- package/dist/ole.js +139 -0
- package/dist/oleobj.d.ts +85 -0
- package/dist/oleobj.js +215 -0
- package/dist/ooxml.d.ts +83 -2
- package/dist/ooxml.js +358 -145
- package/dist/package.d.ts +3 -0
- package/dist/package.js +21 -0
- 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.d.ts +8 -0
- package/dist/ppt.js +281 -0
- 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.d.ts +8 -0
- package/dist/rtf.js +712 -0
- package/dist/rtfcomp.d.ts +7 -0
- package/dist/rtfcomp.js +80 -0
- package/dist/score.js +1 -1
- package/dist/sniff.d.ts +5 -0
- package/dist/sniff.js +38 -0
- package/dist/types.d.ts +66 -2
- package/dist/types.js +33 -0
- package/dist/vsd.d.ts +13 -0
- package/dist/vsd.js +689 -0
- package/dist/word.d.ts +9 -0
- package/dist/word.js +194 -0
- package/dist/xls.d.ts +27 -0
- package/dist/xls.js +208 -0
- 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 +6 -3
package/dist/context.d.ts
CHANGED
|
@@ -1,38 +1,161 @@
|
|
|
1
|
+
import type { CompoundFile } from './cfb';
|
|
1
2
|
import type { FindingFactory } from './findings';
|
|
2
3
|
import type { Format } from './formats';
|
|
3
|
-
import {
|
|
4
|
-
import { type
|
|
4
|
+
import type { SpillSink, TempDir } from './io';
|
|
5
|
+
import { type CompoundPlan, type FlatOpcLayout, PackagePlan, type RtfCut } from './plan';
|
|
6
|
+
import { type ByteSource, OfficeCategory as C, OfficeDetail as D, type DefuseFinding, type OfficeFinding, type OfficeFindingAction, type OfficeOptions } from './types';
|
|
7
|
+
import { type EntityBudget, type XmlHandler, type XmlLimits } from './xml';
|
|
5
8
|
import type { ZipArchive, ZipEntry } from './zip';
|
|
6
9
|
/** Cuts text from the file to a length that fits a finding's data. A reference to an external entity shows as `&name;`. */
|
|
7
10
|
export declare const clip: (raw: string, n?: number) => string;
|
|
11
|
+
/** The limits that bound a container and what is decoded from it, and the caller's password. */
|
|
12
|
+
export interface RunSettings {
|
|
13
|
+
entries: number;
|
|
14
|
+
decompressedBytes: number;
|
|
15
|
+
totalDecompressedBytes: number;
|
|
16
|
+
/** Group nesting in an RTF file, and destinations open at once. */
|
|
17
|
+
rtfDepth: number;
|
|
18
|
+
deadline?: number;
|
|
19
|
+
/** Tried in order on an encrypted file, before the password the format opens with by default. */
|
|
20
|
+
passwords: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** What a plugin returned for a file, which the output check expects to find again by the MD5 of its bytes. */
|
|
23
|
+
export interface Expected {
|
|
24
|
+
result: 'passed' | 'scrubbed';
|
|
25
|
+
plugin: string;
|
|
26
|
+
findings?: DefuseFinding[];
|
|
27
|
+
}
|
|
28
|
+
/** What the rewrite starts from: the container the run read, and what its findings remove. */
|
|
29
|
+
export type Output = {
|
|
30
|
+
kind: 'zip';
|
|
31
|
+
zip: ZipArchive;
|
|
32
|
+
plan: PackagePlan;
|
|
33
|
+
format: Format;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'cfb';
|
|
36
|
+
cfb: CompoundFile;
|
|
37
|
+
plan: CompoundPlan;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'rtf';
|
|
40
|
+
size: number;
|
|
41
|
+
cuts: RtfCut[];
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'flatodf';
|
|
44
|
+
plan: PackagePlan;
|
|
45
|
+
}
|
|
46
|
+
/** A Flat OPC package, unpacked into `zip`, whose OOXML format is `format`. */
|
|
47
|
+
| {
|
|
48
|
+
kind: 'flatopc';
|
|
49
|
+
zip: ZipArchive;
|
|
50
|
+
plan: PackagePlan;
|
|
51
|
+
format: Format;
|
|
52
|
+
layout: FlatOpcLayout;
|
|
53
|
+
};
|
|
54
|
+
/** What one run of the engine holds besides its findings: the caller's options, temporary files and plugin results. */
|
|
55
|
+
export interface RunState {
|
|
56
|
+
options: OfficeOptions;
|
|
57
|
+
/** The depth of the file in the upload: 0 for the upload itself. */
|
|
58
|
+
depth: number;
|
|
59
|
+
deadline?: number;
|
|
60
|
+
temp: TempDir;
|
|
61
|
+
/** memoryThreshold, in bytes. */
|
|
62
|
+
threshold: number;
|
|
63
|
+
/** Bytes of plugin output held in memory until the rewrite. */
|
|
64
|
+
retained: number;
|
|
65
|
+
/** Set when the run checks an output: what the plugins returned for the upload's files. */
|
|
66
|
+
verify?: Map<string, Expected>;
|
|
67
|
+
/** What the plugins returned in this run, by the MD5 of the bytes kept. */
|
|
68
|
+
kept: Map<string, Expected>;
|
|
69
|
+
/** Plugin output that waits for the rewrite. The run disposes of it when it ends. */
|
|
70
|
+
held: SpillSink[];
|
|
71
|
+
/**
|
|
72
|
+
* Findings whose content a plugin kept, such as EMBEDDED/PACKAGE for a package a plugin passed. They stay in the
|
|
73
|
+
* report as info and leave the score to what the plugin found, as pdf-defuse leaves out a kept attached file.
|
|
74
|
+
*/
|
|
75
|
+
superseded: WeakSet<DefuseFinding>;
|
|
76
|
+
/** Sources the rewrite reads from, such as a compound file a plugin returned, which the run closes when it ends. */
|
|
77
|
+
sources: ByteSource[];
|
|
78
|
+
/** The container, once the format rules have read it. */
|
|
79
|
+
output?: Output;
|
|
80
|
+
}
|
|
81
|
+
/** A finding the rules made, with its action, which the rewrite reads. Past 200 of one kind, `finding` is absent. */
|
|
82
|
+
export interface Reported {
|
|
83
|
+
action: OfficeFindingAction;
|
|
84
|
+
finding?: OfficeFinding;
|
|
85
|
+
}
|
|
86
|
+
/** What every context of one inspection shares, so a package inside the file reports into the same list. */
|
|
87
|
+
interface Shared {
|
|
88
|
+
findings: DefuseFinding[];
|
|
89
|
+
perKind: Map<string, number>;
|
|
90
|
+
overflow: Map<string, Record<string, string | number>>;
|
|
91
|
+
/** Entity expansion in every part, which the entity limits bound. */
|
|
92
|
+
budget: EntityBudget;
|
|
93
|
+
format?: Format;
|
|
94
|
+
run: RunState;
|
|
95
|
+
/** Strip findings made so far, counted past the 200 kept of each kind too. */
|
|
96
|
+
strips: number;
|
|
97
|
+
}
|
|
8
98
|
/** What one inspection holds while the format rules run. */
|
|
9
99
|
export declare class RunContext {
|
|
10
|
-
readonly zip: ZipArchive;
|
|
11
100
|
readonly factory: FindingFactory;
|
|
12
101
|
readonly xmlLimits: XmlLimits;
|
|
13
|
-
readonly
|
|
102
|
+
readonly settings: RunSettings;
|
|
103
|
+
protected readonly shared: Shared;
|
|
104
|
+
constructor(factory: FindingFactory, xmlLimits: XmlLimits, settings: RunSettings, shared: Shared | RunState);
|
|
105
|
+
get findings(): DefuseFinding[];
|
|
106
|
+
get run(): RunState;
|
|
107
|
+
/** How many strip findings the run has made, so a rule can tell whether anything it read made one. */
|
|
108
|
+
get strips(): number;
|
|
14
109
|
/** Set as soon as the rules know it, so a run that stops later still names the format. */
|
|
15
|
-
format
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
110
|
+
get format(): Format | undefined;
|
|
111
|
+
set format(f: Format | undefined);
|
|
112
|
+
/** A context for a ZIP archive the rules read, reporting into the same findings, with a plan of its own. */
|
|
113
|
+
withZip(zip: ZipArchive): ZipContext;
|
|
114
|
+
/** A context for rules that fill in a plan of their own without an archive, as for a flat XML file. */
|
|
115
|
+
withPlan(): PlanContext;
|
|
116
|
+
/**
|
|
117
|
+
* Records a finding. Past 200 of one kind, as in pdf-defuse, one more finding counts the rest. `action` replaces the
|
|
118
|
+
* action the overrides give, for content whose fate a plugin decided.
|
|
119
|
+
*/
|
|
120
|
+
report(category: C, detail: D, location?: string, data?: Record<string, string | number>, action?: OfficeFindingAction): Reported;
|
|
121
|
+
/** Reports content that goes whatever the overrides say: its action is strip, or reject when the caller asked for that. */
|
|
122
|
+
removal(category: C, detail: D, location?: string, data?: Record<string, string | number>): Reported;
|
|
123
|
+
/** Adds findings from another package as they are, such as those a plugin made inside a file. */
|
|
124
|
+
push(findings: DefuseFinding[]): void;
|
|
23
125
|
/** Reports a link, classified by pdf-defuse's checks of its target. */
|
|
24
|
-
link(target: string, location: string, data?: Record<string, string>):
|
|
126
|
+
link(target: string, location: string, data?: Record<string, string>): Reported;
|
|
127
|
+
/**
|
|
128
|
+
* Reports a safe link whose text or tooltip names another site than the one it opens, as LINK/TEXT_MISMATCH. Returns
|
|
129
|
+
* undefined when no label names a site, or every site a label names is the target's.
|
|
130
|
+
*/
|
|
131
|
+
labelMismatch(target: string, labels: ReadonlyArray<string | undefined>, location: string, data?: Record<string, string>): Reported | undefined;
|
|
25
132
|
/**
|
|
26
133
|
* Reports a reference that loads from outside the file on its own. It is EXTERNAL/LOCAL_FILE, with `kind` saying
|
|
27
134
|
* what refers, when the target is on this machine, and `detail` otherwise.
|
|
28
135
|
*/
|
|
29
|
-
external(detail: D, target: string, location: string, data: Record<string, string>, kind: string):
|
|
136
|
+
external(detail: D, target: string, location: string, data: Record<string, string>, kind: string): Reported;
|
|
30
137
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
138
|
+
* Tokenizes XML text, or Office's VML dialect, handing the events to `handler`. A document type declaration is
|
|
139
|
+
* XML/DOCTYPE without an internal subset and XML/INTERNAL_SUBSET with one. A reference to an external entity in
|
|
140
|
+
* content is XML/EXTERNAL_ENTITY.
|
|
34
141
|
*/
|
|
142
|
+
parseXml(part: string, chunks: AsyncIterable<Uint8Array> | Iterable<Uint8Array>, handler: XmlHandler, vml?: boolean, plan?: PackagePlan, after?: () => Promise<void>): Promise<void>;
|
|
143
|
+
}
|
|
144
|
+
/** The context of rules that fill in a rewrite plan: a ZIP package's, or a flat XML file's. */
|
|
145
|
+
export declare class PlanContext extends RunContext {
|
|
146
|
+
/** What the rewrite removes, which the rules fill in as they report. */
|
|
147
|
+
readonly plan: PackagePlan;
|
|
148
|
+
constructor(
|
|
149
|
+
/** What the rewrite removes, which the rules fill in as they report. */
|
|
150
|
+
plan: PackagePlan, factory: FindingFactory, xmlLimits: XmlLimits, settings: RunSettings, shared: Shared);
|
|
151
|
+
}
|
|
152
|
+
/** The context of the rules that read a ZIP archive. */
|
|
153
|
+
export declare class ZipContext extends PlanContext {
|
|
154
|
+
readonly zip: ZipArchive;
|
|
155
|
+
constructor(zip: ZipArchive, plan: PackagePlan, factory: FindingFactory, xmlLimits: XmlLimits, settings: RunSettings, shared: Shared);
|
|
156
|
+
/** Inflates an entry and tokenizes it as XML, or as Office's VML dialect, handing the events to `handler`. */
|
|
35
157
|
parse(entry: ZipEntry, handler: XmlHandler, vml?: boolean): Promise<void>;
|
|
36
158
|
/** Inflates an entry, which checks it, hands each chunk to `visit`, and tells whether its bytes are an OLE file. */
|
|
37
159
|
drain(entry: ZipEntry, visit?: (chunk: Uint8Array) => void): Promise<boolean>;
|
|
38
160
|
}
|
|
161
|
+
export {};
|
package/dist/context.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RunContext = exports.clip = void 0;
|
|
3
|
+
exports.ZipContext = exports.PlanContext = exports.RunContext = exports.clip = void 0;
|
|
4
4
|
const links_1 = require("./links");
|
|
5
|
+
const plan_1 = require("./plan");
|
|
5
6
|
const types_1 = require("./types");
|
|
6
7
|
const xml_1 = require("./xml");
|
|
7
8
|
const OLE_SIGNATURE = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
|
|
@@ -13,43 +14,98 @@ const clip = (raw, n = 200) => {
|
|
|
13
14
|
exports.clip = clip;
|
|
14
15
|
/** What one inspection holds while the format rules run. */
|
|
15
16
|
class RunContext {
|
|
16
|
-
zip;
|
|
17
17
|
factory;
|
|
18
18
|
xmlLimits;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
perKind = new Map();
|
|
23
|
-
overflow = new Map();
|
|
24
|
-
/** Entity expansion in every part, which the entity limits bound. */
|
|
25
|
-
budget = { expansions: 0, size: 0 };
|
|
26
|
-
constructor(zip, factory, xmlLimits) {
|
|
27
|
-
this.zip = zip;
|
|
19
|
+
settings;
|
|
20
|
+
shared;
|
|
21
|
+
constructor(factory, xmlLimits, settings, shared) {
|
|
28
22
|
this.factory = factory;
|
|
29
23
|
this.xmlLimits = xmlLimits;
|
|
24
|
+
this.settings = settings;
|
|
25
|
+
this.shared = 'run' in shared ? shared : { findings: [], perKind: new Map(), overflow: new Map(), budget: { expansions: 0, size: 0 }, run: shared, strips: 0 };
|
|
26
|
+
}
|
|
27
|
+
get findings() {
|
|
28
|
+
return this.shared.findings;
|
|
29
|
+
}
|
|
30
|
+
get run() {
|
|
31
|
+
return this.shared.run;
|
|
32
|
+
}
|
|
33
|
+
/** How many strip findings the run has made, so a rule can tell whether anything it read made one. */
|
|
34
|
+
get strips() {
|
|
35
|
+
return this.shared.strips;
|
|
36
|
+
}
|
|
37
|
+
/** Set as soon as the rules know it, so a run that stops later still names the format. */
|
|
38
|
+
get format() {
|
|
39
|
+
return this.shared.format;
|
|
30
40
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
set format(f) {
|
|
42
|
+
this.shared.format = f;
|
|
43
|
+
}
|
|
44
|
+
/** A context for a ZIP archive the rules read, reporting into the same findings, with a plan of its own. */
|
|
45
|
+
withZip(zip) {
|
|
46
|
+
return new ZipContext(zip, new plan_1.PackagePlan(), this.factory, this.xmlLimits, this.settings, this.shared);
|
|
47
|
+
}
|
|
48
|
+
/** A context for rules that fill in a plan of their own without an archive, as for a flat XML file. */
|
|
49
|
+
withPlan() {
|
|
50
|
+
return new PlanContext(new plan_1.PackagePlan(), this.factory, this.xmlLimits, this.settings, this.shared);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Records a finding. Past 200 of one kind, as in pdf-defuse, one more finding counts the rest. `action` replaces the
|
|
54
|
+
* action the overrides give, for content whose fate a plugin decided.
|
|
55
|
+
*/
|
|
56
|
+
report(category, detail, location, data, action) {
|
|
57
|
+
const act = action ?? this.factory.actionFor(category, detail);
|
|
58
|
+
if (act === 'strip')
|
|
59
|
+
this.shared.strips++;
|
|
60
|
+
const k = `${category}/${detail}/${act}`;
|
|
61
|
+
const n = (this.shared.perKind.get(k) ?? 0) + 1;
|
|
62
|
+
this.shared.perKind.set(k, n);
|
|
36
63
|
if (n <= 200) {
|
|
37
|
-
this.
|
|
38
|
-
|
|
64
|
+
const finding = this.factory.make(category, detail, location, data);
|
|
65
|
+
finding.action = act;
|
|
66
|
+
if (action === 'info')
|
|
67
|
+
this.run.superseded.add(finding);
|
|
68
|
+
this.shared.findings.push(finding);
|
|
69
|
+
return { action: act, finding };
|
|
39
70
|
}
|
|
40
|
-
let counted = this.overflow.get(k);
|
|
71
|
+
let counted = this.shared.overflow.get(k);
|
|
41
72
|
if (!counted) {
|
|
42
73
|
// The first overflow fills the description's placeholders.
|
|
43
74
|
counted = { ...data, count: 0 };
|
|
44
|
-
this.overflow.set(k, counted);
|
|
45
|
-
this.
|
|
75
|
+
this.shared.overflow.set(k, counted);
|
|
76
|
+
const finding = this.factory.make(category, detail, 'additional occurrences', counted);
|
|
77
|
+
finding.action = act;
|
|
78
|
+
if (action === 'info')
|
|
79
|
+
this.run.superseded.add(finding);
|
|
80
|
+
this.shared.findings.push(finding);
|
|
46
81
|
}
|
|
47
82
|
counted.count = Number(counted.count) + 1;
|
|
83
|
+
return { action: act };
|
|
84
|
+
}
|
|
85
|
+
/** Reports content that goes whatever the overrides say: its action is strip, or reject when the caller asked for that. */
|
|
86
|
+
removal(category, detail, location, data) {
|
|
87
|
+
const act = this.factory.actionFor(category, detail);
|
|
88
|
+
return this.report(category, detail, location, data, act === 'info' ? 'strip' : act);
|
|
89
|
+
}
|
|
90
|
+
/** Adds findings from another package as they are, such as those a plugin made inside a file. */
|
|
91
|
+
push(findings) {
|
|
92
|
+
this.shared.findings.push(...findings);
|
|
93
|
+
this.shared.strips += findings.filter(f => f.action === 'strip').length;
|
|
48
94
|
}
|
|
49
95
|
/** Reports a link, classified by pdf-defuse's checks of its target. */
|
|
50
96
|
link(target, location, data = {}) {
|
|
51
97
|
const v = (0, links_1.checkLink)(target);
|
|
52
|
-
this.report(v.category, v.detail, location, { ...data, target: (0, exports.clip)(target, 2000), ...(v.host ? { host: (0, exports.clip)(v.host) } : {}) });
|
|
98
|
+
return this.report(v.category, v.detail, location, { ...data, target: (0, exports.clip)(target, 2000), ...(v.host ? { host: (0, exports.clip)(v.host) } : {}) });
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reports a safe link whose text or tooltip names another site than the one it opens, as LINK/TEXT_MISMATCH. Returns
|
|
102
|
+
* undefined when no label names a site, or every site a label names is the target's.
|
|
103
|
+
*/
|
|
104
|
+
labelMismatch(target, labels, location, data = {}) {
|
|
105
|
+
const label = (0, links_1.mismatchedLabel)(target, labels);
|
|
106
|
+
if (label === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
return this.report(types_1.OfficeCategory.Link, types_1.OfficeDetail.TextMismatch, location, { ...data, target: (0, exports.clip)(target, 2000), label: (0, exports.clip)(label) });
|
|
53
109
|
}
|
|
54
110
|
/**
|
|
55
111
|
* Reports a reference that loads from outside the file on its own. It is EXTERNAL/LOCAL_FILE, with `kind` saying
|
|
@@ -58,33 +114,75 @@ class RunContext {
|
|
|
58
114
|
external(detail, target, location, data, kind) {
|
|
59
115
|
const d = { ...data, target: (0, exports.clip)(target, 2000) };
|
|
60
116
|
if ((0, links_1.isLocalTarget)(target))
|
|
61
|
-
this.report(types_1.OfficeCategory.External, types_1.OfficeDetail.LocalFile, location, { ...d, kind });
|
|
62
|
-
|
|
63
|
-
this.report(types_1.OfficeCategory.External, detail, location, d);
|
|
117
|
+
return this.report(types_1.OfficeCategory.External, types_1.OfficeDetail.LocalFile, location, { ...d, kind });
|
|
118
|
+
return this.report(types_1.OfficeCategory.External, detail, location, d);
|
|
64
119
|
}
|
|
65
120
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
121
|
+
* Tokenizes XML text, or Office's VML dialect, handing the events to `handler`. A document type declaration is
|
|
122
|
+
* XML/DOCTYPE without an internal subset and XML/INTERNAL_SUBSET with one. A reference to an external entity in
|
|
123
|
+
* content is XML/EXTERNAL_ENTITY.
|
|
69
124
|
*/
|
|
70
|
-
async
|
|
71
|
-
const part = entry.name;
|
|
125
|
+
async parseXml(part, chunks, handler, vml = false, plan, after) {
|
|
72
126
|
const notes = {
|
|
73
127
|
doctype: d => {
|
|
74
128
|
const ids = { ...(d.publicId === undefined ? {} : { publicId: (0, exports.clip)(d.publicId) }), ...(d.systemId === undefined ? {} : { systemId: (0, exports.clip)(d.systemId) }) };
|
|
75
|
-
if (!d.internalSubset)
|
|
76
|
-
this.report(types_1.OfficeCategory.Xml, types_1.OfficeDetail.Doctype, part, { part, name: (0, exports.clip)(d.name), ...ids })
|
|
129
|
+
if (!d.internalSubset) {
|
|
130
|
+
if (this.report(types_1.OfficeCategory.Xml, types_1.OfficeDetail.Doctype, part, { part, name: (0, exports.clip)(d.name), ...ids }).action === 'strip' && plan)
|
|
131
|
+
plan.part(part).doctype = true;
|
|
132
|
+
}
|
|
77
133
|
else {
|
|
78
134
|
const counts = { entities: d.entities, externalEntities: d.externalEntities, attributeDefaults: d.attributeDefaults, skipped: d.skipped };
|
|
79
|
-
this.report(types_1.OfficeCategory.Xml, types_1.OfficeDetail.InternalSubset, part, { part, name: (0, exports.clip)(d.name), ...ids, ...counts })
|
|
135
|
+
if (this.report(types_1.OfficeCategory.Xml, types_1.OfficeDetail.InternalSubset, part, { part, name: (0, exports.clip)(d.name), ...ids, ...counts }).action === 'strip' && plan)
|
|
136
|
+
plan.part(part).subset = true;
|
|
80
137
|
}
|
|
81
138
|
},
|
|
82
|
-
externalEntity: r =>
|
|
139
|
+
externalEntity: r => {
|
|
140
|
+
const reported = this.report(types_1.OfficeCategory.Xml, types_1.OfficeDetail.ExternalEntity, part, {
|
|
141
|
+
part,
|
|
142
|
+
entity: (0, exports.clip)(r.name),
|
|
143
|
+
target: (0, exports.clip)(r.systemId, 2000),
|
|
144
|
+
...(r.publicId === undefined ? {} : { publicId: (0, exports.clip)(r.publicId) }),
|
|
145
|
+
});
|
|
146
|
+
if (!plan)
|
|
147
|
+
return;
|
|
148
|
+
const p = plan.part(part);
|
|
149
|
+
p.hasExternal = true;
|
|
150
|
+
if (reported.action === 'strip')
|
|
151
|
+
p.external = true;
|
|
152
|
+
},
|
|
83
153
|
};
|
|
84
|
-
const t = new xml_1.XmlTokenizer(handler, this.xmlLimits, part, { vml, notes, budget: this.budget });
|
|
85
|
-
|
|
154
|
+
const t = new xml_1.XmlTokenizer(handler, this.xmlLimits, part, { vml, notes, budget: this.shared.budget });
|
|
155
|
+
// `after` runs between chunks, so a handler can hand what it gathered to something that waits.
|
|
156
|
+
for await (const chunk of chunks) {
|
|
86
157
|
t.write(chunk);
|
|
158
|
+
await after?.();
|
|
159
|
+
}
|
|
87
160
|
t.end();
|
|
161
|
+
await after?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.RunContext = RunContext;
|
|
165
|
+
/** The context of rules that fill in a rewrite plan: a ZIP package's, or a flat XML file's. */
|
|
166
|
+
class PlanContext extends RunContext {
|
|
167
|
+
plan;
|
|
168
|
+
constructor(
|
|
169
|
+
/** What the rewrite removes, which the rules fill in as they report. */
|
|
170
|
+
plan, factory, xmlLimits, settings, shared) {
|
|
171
|
+
super(factory, xmlLimits, settings, shared);
|
|
172
|
+
this.plan = plan;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.PlanContext = PlanContext;
|
|
176
|
+
/** The context of the rules that read a ZIP archive. */
|
|
177
|
+
class ZipContext extends PlanContext {
|
|
178
|
+
zip;
|
|
179
|
+
constructor(zip, plan, factory, xmlLimits, settings, shared) {
|
|
180
|
+
super(plan, factory, xmlLimits, settings, shared);
|
|
181
|
+
this.zip = zip;
|
|
182
|
+
}
|
|
183
|
+
/** Inflates an entry and tokenizes it as XML, or as Office's VML dialect, handing the events to `handler`. */
|
|
184
|
+
parse(entry, handler, vml = false) {
|
|
185
|
+
return this.parseXml(entry.name, this.zip.read(entry), handler, vml, this.plan);
|
|
88
186
|
}
|
|
89
187
|
/** Inflates an entry, which checks it, hands each chunk to `visit`, and tells whether its bytes are an OLE file. */
|
|
90
188
|
async drain(entry, visit) {
|
|
@@ -97,4 +195,4 @@ class RunContext {
|
|
|
97
195
|
return head.length === OLE_SIGNATURE.length && head.every((b, i) => b === OLE_SIGNATURE[i]);
|
|
98
196
|
}
|
|
99
197
|
}
|
|
100
|
-
exports.
|
|
198
|
+
exports.ZipContext = ZipContext;
|
package/dist/crc32.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
/** Continues a CRC-32, the one ZIP uses, over `bytes
|
|
2
|
-
export declare function
|
|
1
|
+
/** Continues a CRC-32, the one ZIP uses, over `bytes`, with a lookup table. Start from 0. */
|
|
2
|
+
export declare function tableCrc32(bytes: Uint8Array, crc?: number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Continues a CRC-32, the one ZIP uses, over `bytes`. Start from 0. This is zlib.crc32 where Node has it, about 50
|
|
5
|
+
* times faster, and the table otherwise. Node 18 and early releases of Node 20 and 22 lack it. The choice is made
|
|
6
|
+
* once, when the module loads.
|
|
7
|
+
*/
|
|
8
|
+
export declare const crc32: (bytes: Uint8Array, crc?: number) => number;
|
package/dist/crc32.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.crc32 =
|
|
4
|
-
|
|
36
|
+
exports.crc32 = void 0;
|
|
37
|
+
exports.tableCrc32 = tableCrc32;
|
|
38
|
+
const zlib = __importStar(require("node:zlib"));
|
|
5
39
|
const TABLE = new Uint32Array(256);
|
|
6
40
|
for (let n = 0; n < 256; n++) {
|
|
7
41
|
let c = n;
|
|
@@ -9,10 +43,16 @@ for (let n = 0; n < 256; n++) {
|
|
|
9
43
|
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
10
44
|
TABLE[n] = c >>> 0;
|
|
11
45
|
}
|
|
12
|
-
/** Continues a CRC-32, the one ZIP uses, over `bytes
|
|
13
|
-
function
|
|
46
|
+
/** Continues a CRC-32, the one ZIP uses, over `bytes`, with a lookup table. Start from 0. */
|
|
47
|
+
function tableCrc32(bytes, crc = 0) {
|
|
14
48
|
let c = ~crc >>> 0;
|
|
15
49
|
for (const b of bytes)
|
|
16
50
|
c = TABLE[(c ^ b) & 0xff] ^ (c >>> 8);
|
|
17
51
|
return ~c >>> 0;
|
|
18
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Continues a CRC-32, the one ZIP uses, over `bytes`. Start from 0. This is zlib.crc32 where Node has it, about 50
|
|
55
|
+
* times faster, and the table otherwise. Node 18 and early releases of Node 20 and 22 lack it. The choice is made
|
|
56
|
+
* once, when the module loads.
|
|
57
|
+
*/
|
|
58
|
+
exports.crc32 = zlib.crc32 ?? tableCrc32;
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { type ByteSource, type OfficeInspection, type OfficeOptions } from './types';
|
|
1
|
+
import { type ByteSink, type ByteSource, type OfficeDisarmResult, type OfficeInspection, type OfficeOptions } from './types';
|
|
2
2
|
/** Inspects an Office or OpenDocument file without changing it. The source is closed when the run ends. */
|
|
3
3
|
export declare function inspectOfficeSource(source: ByteSource, options?: OfficeOptions): Promise<OfficeInspection>;
|
|
4
4
|
/** Inspects Office or OpenDocument bytes. */
|
|
5
5
|
export declare function inspectOffice(bytes: Uint8Array, options?: OfficeOptions): Promise<OfficeInspection>;
|
|
6
|
+
/**
|
|
7
|
+
* Writes a defused copy of `source` to `sink`. Nothing reaches the sink unless the output passes inspection. The
|
|
8
|
+
* source is closed when the run ends.
|
|
9
|
+
*/
|
|
10
|
+
export declare function disarmOfficeSource(source: ByteSource, sink: ByteSink, options?: OfficeOptions): Promise<OfficeDisarmResult>;
|
|
11
|
+
/** Internal entry that carries the depth of a file inside another, and the deadline it shares with the upload. */
|
|
12
|
+
export declare function disarmAtDepth(source: ByteSource, sink: ByteSink, options: OfficeOptions, depth: number, deadline?: number | undefined): Promise<OfficeDisarmResult>;
|
|
13
|
+
/** Defuses Office or OpenDocument bytes. `bytes` holds the output unless the file was rejected. */
|
|
14
|
+
export declare function disarmOffice(bytes: Uint8Array, options?: OfficeOptions): Promise<OfficeDisarmResult & {
|
|
15
|
+
bytes?: Uint8Array;
|
|
16
|
+
}>;
|