@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.
Files changed (88) hide show
  1. package/README.md +278 -36
  2. package/dist/biff.d.ts +62 -0
  3. package/dist/biff.js +95 -0
  4. package/dist/cfb-writer.d.ts +25 -0
  5. package/dist/cfb-writer.js +314 -0
  6. package/dist/cfb.d.ts +58 -0
  7. package/dist/cfb.js +456 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +357 -0
  10. package/dist/contained.d.ts +46 -0
  11. package/dist/contained.js +152 -0
  12. package/dist/context.d.ts +140 -17
  13. package/dist/context.js +135 -37
  14. package/dist/crc32.d.ts +8 -2
  15. package/dist/crc32.js +44 -4
  16. package/dist/engine.d.ts +12 -1
  17. package/dist/engine.js +336 -42
  18. package/dist/errors.d.ts +17 -0
  19. package/dist/errors.js +27 -1
  20. package/dist/fields.d.ts +36 -0
  21. package/dist/fields.js +139 -0
  22. package/dist/findings.d.ts +2 -1
  23. package/dist/findings.js +44 -9
  24. package/dist/flat.d.ts +33 -0
  25. package/dist/flat.js +486 -0
  26. package/dist/formats.d.ts +7 -0
  27. package/dist/formats.js +5 -1
  28. package/dist/formulas.d.ts +27 -0
  29. package/dist/formulas.js +157 -1
  30. package/dist/index.d.ts +3 -2
  31. package/dist/index.js +8 -1
  32. package/dist/index.mjs +1 -1
  33. package/dist/io.d.ts +62 -1
  34. package/dist/io.js +245 -1
  35. package/dist/links.d.ts +27 -0
  36. package/dist/links.js +106 -1
  37. package/dist/msg.d.ts +8 -0
  38. package/dist/msg.js +251 -0
  39. package/dist/objects.d.ts +45 -0
  40. package/dist/objects.js +204 -0
  41. package/dist/odf.d.ts +66 -2
  42. package/dist/odf.js +181 -64
  43. package/dist/offcrypto.d.ts +54 -0
  44. package/dist/offcrypto.js +647 -0
  45. package/dist/ole.d.ts +12 -0
  46. package/dist/ole.js +139 -0
  47. package/dist/oleobj.d.ts +85 -0
  48. package/dist/oleobj.js +215 -0
  49. package/dist/ooxml.d.ts +83 -2
  50. package/dist/ooxml.js +358 -145
  51. package/dist/package.d.ts +3 -0
  52. package/dist/package.js +21 -0
  53. package/dist/plan.d.ts +104 -0
  54. package/dist/plan.js +73 -0
  55. package/dist/plugins.d.ts +8 -0
  56. package/dist/plugins.js +60 -0
  57. package/dist/ppt.d.ts +8 -0
  58. package/dist/ppt.js +281 -0
  59. package/dist/rewrite-cfb.d.ts +14 -0
  60. package/dist/rewrite-cfb.js +52 -0
  61. package/dist/rewrite-rtf.d.ts +9 -0
  62. package/dist/rewrite-rtf.js +45 -0
  63. package/dist/rewrite-xml.d.ts +36 -0
  64. package/dist/rewrite-xml.js +341 -0
  65. package/dist/rewrite-zip.d.ts +8 -0
  66. package/dist/rewrite-zip.js +498 -0
  67. package/dist/rtf.d.ts +8 -0
  68. package/dist/rtf.js +712 -0
  69. package/dist/rtfcomp.d.ts +7 -0
  70. package/dist/rtfcomp.js +80 -0
  71. package/dist/score.js +1 -1
  72. package/dist/sniff.d.ts +5 -0
  73. package/dist/sniff.js +38 -0
  74. package/dist/types.d.ts +66 -2
  75. package/dist/types.js +33 -0
  76. package/dist/vsd.d.ts +13 -0
  77. package/dist/vsd.js +689 -0
  78. package/dist/word.d.ts +9 -0
  79. package/dist/word.js +194 -0
  80. package/dist/xls.d.ts +27 -0
  81. package/dist/xls.js +208 -0
  82. package/dist/xml.d.ts +30 -0
  83. package/dist/xml.js +45 -23
  84. package/dist/zip-writer.d.ts +4 -0
  85. package/dist/zip-writer.js +20 -7
  86. package/dist/zip.d.ts +9 -2
  87. package/dist/zip.js +61 -4
  88. package/package.json +6 -3
package/dist/fields.js ADDED
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FieldStack = void 0;
4
+ exports.evaluateField = evaluateField;
5
+ const context_1 = require("./context");
6
+ const formulas_1 = require("./formulas");
7
+ const links_1 = require("./links");
8
+ const types_1 = require("./types");
9
+ const xml_1 = require("./xml");
10
+ /** How much of a field code is kept, counted from its first word, which names the field. */
11
+ const MAX_FIELD_CODE = 4096;
12
+ /**
13
+ * Reports a Word field by its code, and tells whether any of its findings strips it. A field whose name holds the
14
+ * content of an external entity has a type the file does not say, as one whose name another field builds does. A
15
+ * HYPERLINK field's result is the text the reader sees, and goes with its tooltip into the label check.
16
+ */
17
+ function evaluateField(ctx, location, code, computed, result = '') {
18
+ const data = { code: (0, context_1.clip)(code.trim()) };
19
+ const words = (0, formulas_1.fieldWords)(code);
20
+ const first = words[0]?.text ?? '';
21
+ const entity = (0, xml_1.unknownEntity)(first);
22
+ const actions = [];
23
+ if (computed || entity !== undefined)
24
+ actions.push(ctx.report(types_1.OfficeCategory.Field, types_1.OfficeDetail.Computed, location, entity === undefined ? data : { ...data, entity: (0, context_1.clip)(entity) }).action);
25
+ const name = first.toUpperCase();
26
+ // An argument comes before any switch, and a switch is an unquoted word starting with a backslash.
27
+ const switchAt = words.findIndex(w => !w.quoted && w.text.startsWith('\\'));
28
+ const arg = (n) => (words[n] !== undefined && (switchAt < 0 || n < switchAt) ? words[n].text : undefined);
29
+ if (name === 'DDE' || name === 'DDEAUTO')
30
+ actions.push(ctx.report(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Field, location, data).action);
31
+ else if (name === 'MACROBUTTON')
32
+ actions.push(ctx.report(types_1.OfficeCategory.Field, types_1.OfficeDetail.Macrobutton, location, { ...data, macro: (0, context_1.clip)(words[1]?.text ?? '') }).action);
33
+ else if (name === 'INCLUDETEXT' || name === 'INCLUDEPICTURE' || name === 'IMPORT' || name === 'LINK') {
34
+ const target = arg(name === 'LINK' ? 2 : 1);
35
+ if (target === undefined)
36
+ actions.push(ctx.report(types_1.OfficeCategory.External, types_1.OfficeDetail.Include, location, data).action);
37
+ else
38
+ actions.push(ctx.external(types_1.OfficeDetail.Include, target, location, data, 'field').action);
39
+ }
40
+ else if (name === 'HYPERLINK') {
41
+ const target = arg(1);
42
+ if (target !== undefined && !(0, links_1.isFragment)(target)) {
43
+ const r = ctx.link(target, location, { ...data, source: 'HYPERLINK field' });
44
+ actions.push(r.action);
45
+ // The \o switch gives the tooltip.
46
+ const tip = words.findIndex(w => !w.quoted && w.text.toLowerCase() === '\\o');
47
+ const labels = [result, tip >= 0 ? words[tip + 1]?.text : undefined];
48
+ if (r.action !== 'strip') {
49
+ const m = ctx.labelMismatch(target, labels, location, { ...data, source: 'HYPERLINK field' });
50
+ if (m)
51
+ actions.push(m.action);
52
+ }
53
+ }
54
+ }
55
+ return actions.includes('strip');
56
+ }
57
+ /**
58
+ * Word field codes, followed through nested fields as the text of a document arrives: a field begins, its code
59
+ * comes, a separator starts its result, and it ends. Each format says where those marks are.
60
+ */
61
+ /** The most of a field's result that is kept, for the label check. */
62
+ const MAX_RESULT = 4096;
63
+ class FieldStack {
64
+ ctx;
65
+ location;
66
+ onStrip;
67
+ stack = [];
68
+ /**
69
+ * `onStrip` hears of each field a strip finding names, with the marks the format gave its begin, separator and end,
70
+ * so the rewrite can find the field again.
71
+ */
72
+ constructor(ctx, location, onStrip) {
73
+ this.ctx = ctx;
74
+ this.location = location;
75
+ this.onStrip = onStrip;
76
+ }
77
+ /** A field starts. One that starts before its parent's code has a word in it makes that code at run time. */
78
+ begin(mark = -1) {
79
+ this.nest();
80
+ this.stack.push({ code: '', inCode: true, computed: false, marks: { begin: mark, simple: false }, result: '' });
81
+ }
82
+ /** A field whose code is given whole, such as OOXML's fldSimple, whose result follows until simpleEnd(). */
83
+ simpleBegin(code, mark = -1) {
84
+ this.nest();
85
+ this.stack.push({ code, inCode: false, computed: false, marks: { begin: mark, simple: true }, result: '' });
86
+ }
87
+ simpleEnd() {
88
+ const top = this.stack[this.stack.length - 1];
89
+ if (!top?.marks.simple)
90
+ return;
91
+ this.stack.pop();
92
+ this.decide(top);
93
+ }
94
+ separate(mark = -1) {
95
+ const top = this.stack[this.stack.length - 1];
96
+ if (!top)
97
+ return;
98
+ top.inCode = false;
99
+ top.marks.separate ??= mark;
100
+ }
101
+ end(mark = -1) {
102
+ const f = this.stack.pop();
103
+ if (!f)
104
+ return;
105
+ f.marks.end = mark;
106
+ this.decide(f);
107
+ }
108
+ decide(f) {
109
+ if (evaluateField(this.ctx, this.location, f.code, f.computed, f.result))
110
+ this.onStrip?.(f.marks);
111
+ }
112
+ /** Whether a field's code is being read now. */
113
+ get inCode() {
114
+ return this.stack[this.stack.length - 1]?.inCode === true;
115
+ }
116
+ /** Text of the innermost field's code. White space before the first word counts for nothing, so no amount of it can push the field's name out. */
117
+ text(t) {
118
+ const top = this.stack[this.stack.length - 1];
119
+ if (top?.inCode && top.code.length < MAX_FIELD_CODE)
120
+ top.code = top.code ? top.code + t : t.trimStart();
121
+ }
122
+ /** Text of the innermost field's result, the text the reader sees. */
123
+ result(t) {
124
+ const top = this.stack[this.stack.length - 1];
125
+ if (top && !top.inCode && top.result.length < MAX_RESULT)
126
+ top.result += t;
127
+ }
128
+ /** A document can end inside a field. Word still reads the code it has. */
129
+ finish() {
130
+ for (let f = this.stack.pop(); f; f = this.stack.pop())
131
+ this.decide(f);
132
+ }
133
+ nest() {
134
+ const top = this.stack[this.stack.length - 1];
135
+ if (top?.inCode && top.code === '')
136
+ top.computed = true;
137
+ }
138
+ }
139
+ exports.FieldStack = FieldStack;
@@ -19,7 +19,8 @@ export declare function allFindingSpecs(): Array<{
19
19
  export declare class FindingFactory {
20
20
  private readonly overrides;
21
21
  constructor(overrides?: OfficeActionOverride[]);
22
- private actionFor;
22
+ /** The action of a kind of finding, after the caller's overrides. */
23
+ actionFor(category: C, detail: D): OfficeFindingAction;
23
24
  make(category: C, detail: D, location?: string, data?: Record<string, string | number>): OfficeFinding;
24
25
  }
25
26
  export {};
package/dist/findings.js CHANGED
@@ -13,8 +13,8 @@ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.NotZip, 'reject', 0,
13
13
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.AmbiguousEnd, 'reject', 0, 'The archive has more than one end record a reader could pick');
14
14
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.MultiDisk, 'reject', 0, 'The archive claims to span several disks');
15
15
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.DirectoryMismatch, 'reject', 0, "The archive's end record disagrees with its central directory");
16
- def(types_1.OfficeCategory.Container, types_1.OfficeDetail.OutOfRange, 'reject', 0, 'An entry or the central directory lies outside the file');
17
- def(types_1.OfficeCategory.Container, types_1.OfficeDetail.Overlap, 'reject', 0, 'Two entries in the archive share bytes');
16
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.OutOfRange, 'reject', 0, 'An entry, a sector or a directory lies outside the file');
17
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.Overlap, 'reject', 0, 'Two entries or streams of the file share bytes');
18
18
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.HeaderMismatch, 'reject', 0, 'A local header contradicts the central directory<: {name}|>');
19
19
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.DuplicateName, 'reject', 0, 'Two entries have the same name, ignoring case<: {name}|>');
20
20
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.UnsafeName, 'reject', 0, 'An entry name is unsafe to use as a path<: {name}|>');
@@ -23,10 +23,16 @@ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.EncryptedEntry, 'reje
23
23
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.UnsupportedMethod, 'reject', 0, 'An entry uses a compression method other than stored or deflate<: {name}|>');
24
24
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.Zip64Mismatch, 'reject', 0, 'ZIP64 fields disagree with the fields they extend');
25
25
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.BadExtraField, 'reject', 0, 'An extra field block does not parse<: {name}|>');
26
- def(types_1.OfficeCategory.Container, types_1.OfficeDetail.DataError, 'reject', 0, "An entry's data does not match its declared size and checksum<: {name}|>");
26
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.DataError, 'reject', 0, "An entry's data does not match its declared size or checksum<: {name}|>");
27
27
  def(types_1.OfficeCategory.Container, types_1.OfficeDetail.LeadingBytes, 'strip', 10, 'Data before the first entry of the archive');
28
- def(types_1.OfficeCategory.Container, types_1.OfficeDetail.HiddenData, 'strip', 20, 'Data between entries of the archive that no entry covers');
29
- def(types_1.OfficeCategory.Container, types_1.OfficeDetail.TrailingBytes, 'strip', 10, 'Data after the end of the archive');
28
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.HiddenData, 'strip', 20, 'Data in the file that no entry or stream covers');
29
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.HiddenEntry, 'strip', 40, 'A local file header the central directory does not list, which a reader that walks the file in order extracts<: at offset {offset}|>');
30
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.TrailingBytes, 'strip', 10, 'Data after the end of the archive or compound file');
31
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.BadHeader, 'reject', 0, 'The compound file header is invalid<: {reason}|>');
32
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.BrokenChain, 'reject', 0, 'A sector chain of the compound file runs into a free or reserved sector');
33
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.ChainLoop, 'reject', 0, 'A sector chain of the compound file loops');
34
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.DirectoryLoop, 'reject', 0, 'The compound file directory reaches one entry twice');
35
+ def(types_1.OfficeCategory.Container, types_1.OfficeDetail.BadEntry, 'reject', 0, 'A compound file directory entry is invalid<: {reason}|>');
30
36
  // XML. MALFORMED and ENCODING stop the run.
31
37
  def(types_1.OfficeCategory.Xml, types_1.OfficeDetail.Doctype, 'info', 0, 'An XML part declares a document type<: {part}|>');
32
38
  def(types_1.OfficeCategory.Xml, types_1.OfficeDetail.InternalSubset, 'strip', 20, 'An XML part declares entities or other markup in its document type<: {part}|>');
@@ -35,28 +41,42 @@ def(types_1.OfficeCategory.Xml, types_1.OfficeDetail.Malformed, 'reject', 0, 'An
35
41
  def(types_1.OfficeCategory.Xml, types_1.OfficeDetail.Encoding, 'reject', 0, 'An XML part is not in valid UTF-8 or UTF-16<: {part}|>');
36
42
  // LIMIT
37
43
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.FileSize, 'reject', 0, 'The file exceeds the file size limit');
38
- def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.EntryCount, 'reject', 0, 'The archive exceeds the entry count limit');
44
+ def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.EntryCount, 'reject', 0, 'The file exceeds the entry count limit');
39
45
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.DecompressedSize, 'reject', 0, 'An entry exceeds the decompressed size limit');
40
- def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.TotalDecompressedSize, 'reject', 0, 'The archive exceeds the total decompressed size limit');
46
+ def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.TotalDecompressedSize, 'reject', 0, 'The file exceeds the total decompressed size limit');
41
47
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.XmlDepth, 'reject', 0, 'An XML part exceeds the element depth limit');
42
48
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.XmlTokenSize, 'reject', 0, 'An XML part exceeds the token size limit');
43
49
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.XmlEntityDepth, 'reject', 0, 'An XML part exceeds the entity nesting limit');
44
50
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.XmlEntityExpansions, 'reject', 0, 'The file exceeds the entity expansion limit');
45
51
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.XmlEntitySize, 'reject', 0, 'The file exceeds the expanded entity size limit');
52
+ def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.RtfDepth, 'reject', 0, 'The RTF file exceeds the group nesting limit');
53
+ def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.NestingDepth, 'reject', 0, 'The file sits deeper in the upload than the nesting limit allows');
46
54
  def(types_1.OfficeCategory.Limit, types_1.OfficeDetail.Time, 'reject', 0, 'The file exceeds the time limit');
47
55
  // PACKAGE
48
- def(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unrecognized, 'reject', 0, 'The file is not a recognized Office or OpenDocument package');
56
+ def(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unrecognized, 'reject', 0, 'The file is not a recognized Office or OpenDocument file');
49
57
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.AmbiguousFormat, 'reject', 0, 'The file can be read as more than one format');
50
58
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.Unreadable, 'reject', 0, 'The file could not be read');
59
+ def(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreadableBody, 'strip', 10, "An Outlook message's RTF body does not decompress<: {stream}|>");
51
60
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.DuplicateEntry, 'reject', 0, 'The package names one thing twice, and readers may pick either');
52
61
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.UntypedPart, 'strip', 10, 'A part has no content type<: {part}|>');
53
62
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.UnreferencedPart, 'strip', 0, 'A part nothing in the package refers to<: {part}|>');
54
63
  def(types_1.OfficeCategory.Package, types_1.OfficeDetail.OdfLayout, 'strip', 10, 'The OpenDocument package layout is irregular<: {reason}|>');
55
64
  // ENCRYPTED
56
65
  def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.PasswordRequired, 'reject', 0, 'The document is encrypted with a password');
66
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.DefaultPassword, 'strip', 10, 'The file is encrypted with <{password}|VelvetSweatshop>, the password <{application}|Excel> opens it with without asking');
67
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.UserPassword, 'strip', 0, 'The file was decrypted with the supplied password');
68
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Rc4_40, 'info', 0, 'Encryption is RC4 with a 40-bit key');
69
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Rc4_128, 'info', 0, 'Encryption is RC4 with a 128-bit key');
70
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Rc4Other, 'info', 0, 'Encryption is RC4 with a <{bits}|longer>-bit key');
71
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Aes128, 'info', 0, 'Encryption is AES with a 128-bit key');
72
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Aes192, 'info', 0, 'Encryption is AES with a 192-bit key');
73
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.Aes256, 'info', 0, 'Encryption is AES with a 256-bit key');
74
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.XorObfuscation, 'info', 0, 'Encryption is XOR obfuscation');
75
+ def(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.UnsupportedAlgorithm, 'reject', 0, 'The file is encrypted with an algorithm office-defuse does not decrypt<: {algorithm}|>');
57
76
  // MACRO
58
77
  def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, 'strip', 70, 'The document holds VBA macros');
59
78
  def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.XlmMacrosheet, 'strip', 70, 'The workbook holds an Excel 4.0 macro sheet');
79
+ def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.WordBasic, 'strip', 70, 'The Word 6.0 or 95 document holds WordBasic macros, which Word runs as VBA');
60
80
  def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.OdfBasic, 'strip', 70, 'The document holds Basic macros');
61
81
  def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.OdfScripts, 'strip', 70, 'The document holds scripts');
62
82
  def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.OdfDialogs, 'strip', 30, 'The document holds Basic dialogs, which call macros');
@@ -69,7 +89,9 @@ def(types_1.OfficeCategory.Ui, types_1.OfficeDetail.Callback, 'strip', 30, 'The
69
89
  def(types_1.OfficeCategory.Ui, types_1.OfficeDetail.OdfConfiguration, 'strip', 10, 'The document carries its own menu, toolbar or key settings, which can call macros');
70
90
  // ACTION
71
91
  def(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, 'strip', 80, 'A click or hover runs a program or opens a file');
92
+ def(types_1.OfficeCategory.Action, types_1.OfficeDetail.SubmitForm, 'strip', 40, 'A form sends its data to a URL<: {target}|>');
72
93
  def(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, 'strip', 40, 'A click or hover activates an embedded object');
94
+ def(types_1.OfficeCategory.Action, types_1.OfficeDetail.ObjectUpdate, 'strip', 50, 'An embedded object updates itself as the document opens, which starts the program it belongs to');
73
95
  // DDE
74
96
  def(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Field, 'strip', 70, 'A field runs a command through DDE');
75
97
  def(types_1.OfficeCategory.Dde, types_1.OfficeDetail.Link, 'strip', 70, 'A DDE link asks another program for data<: {application}|>');
@@ -86,6 +108,7 @@ def(types_1.OfficeCategory.External, types_1.OfficeDetail.Include, 'strip', 40,
86
108
  def(types_1.OfficeCategory.External, types_1.OfficeDetail.Workbook, 'strip', 30, 'Formulas read another workbook at <{target}|another location>');
87
109
  def(types_1.OfficeCategory.External, types_1.OfficeDetail.Reload, 'strip', 40, 'The document loads <{target}|another address> on its own after a delay');
88
110
  def(types_1.OfficeCategory.External, types_1.OfficeDetail.Other, 'strip', 30, 'The document refers to <{target}|something outside the file>');
111
+ def(types_1.OfficeCategory.External, types_1.OfficeDetail.DataConnection, 'strip', 40, 'A data connection loads from outside the file<: {target}|>');
89
112
  def(types_1.OfficeCategory.External, types_1.OfficeDetail.LocalFile, 'strip', 5, 'The document refers to a local file<: {target}|>');
90
113
  // LINK
91
114
  def(types_1.OfficeCategory.Link, types_1.OfficeDetail.Safe, 'info', 0, 'Web or email link');
@@ -99,14 +122,25 @@ def(types_1.OfficeCategory.Link, types_1.OfficeDetail.IpHost, 'strip', 40, 'A li
99
122
  def(types_1.OfficeCategory.Link, types_1.OfficeDetail.LookalikeHost, 'strip', 40, "A link's host uses punycode or mixed scripts");
100
123
  def(types_1.OfficeCategory.Link, types_1.OfficeDetail.EncodedHost, 'strip', 40, "A link's host is percent-encoded");
101
124
  def(types_1.OfficeCategory.Link, types_1.OfficeDetail.Relative, 'strip', 30, "A relative link, which resolves against the file's own location");
125
+ def(types_1.OfficeCategory.Link, types_1.OfficeDetail.TextMismatch, 'strip', 40, "A link's text or tooltip names a different site than it opens");
126
+ def(types_1.OfficeCategory.External, types_1.OfficeDetail.Formula, 'strip', 40, 'A formula fetches from outside the file when it is calculated<: {function}|>');
127
+ def(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Formula, 'strip', 60, 'A formula runs code outside the file when it is calculated<: {function}|>');
102
128
  // EMBEDDED
103
- def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, 'strip', 40, 'An embedded OLE object');
129
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, 'strip', 40, 'An embedded OLE object<, which holds the file {fileName}|>');
104
130
  def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Package, 'strip', 30, 'An embedded document<: {contentType}|>');
105
131
  def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleFile, 'strip', 40, 'A part holds an OLE file that nothing declares as an object');
106
132
  def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Applet, 'strip', 60, 'An embedded Java applet');
107
133
  def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Plugin, 'strip', 50, 'An embedded browser plug-in object');
134
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Equation, 'strip', 70, 'An Equation Editor 3.0 object, which CVE-2017-11882 and CVE-2018-0802 exploit');
135
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.Attachment, 'strip', 30, '<An attached file "{name}"|An attached file> (<{type}|unknown type>)');
136
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.PluginPassed, 'info', 20, 'An embedded file was kept by <plugin {plugin}|a plugin> with nothing removed');
137
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.PluginScrubbed, 'info', 10, 'An embedded file was cleaned and kept by <plugin {plugin}|a plugin>');
138
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.PluginFailed, 'strip', 0, '<Plugin {plugin}|A plugin> failed, so the embedded file was removed');
139
+ def(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.PluginRemoved, 'strip', 0, 'An embedded file was removed by <plugin {plugin}|a plugin>');
108
140
  // CONTROL
109
141
  def(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, 'strip', 50, 'An ActiveX control');
142
+ // PROCESSING
143
+ def(types_1.OfficeCategory.Processing, types_1.OfficeDetail.VerificationFailed, 'reject', 0, 'The defused output did not pass its own inspection, so nothing was written');
110
144
  /** The built-in spec for a kind of finding, or undefined for a kind office-defuse does not make, such as one from another package. */
111
145
  function findingSpec(category, detail) {
112
146
  return specs.get(`${category}/${detail}`);
@@ -132,6 +166,7 @@ class FindingFactory {
132
166
  constructor(overrides = []) {
133
167
  this.overrides = overrides;
134
168
  }
169
+ /** The action of a kind of finding, after the caller's overrides. */
135
170
  actionFor(category, detail) {
136
171
  const exact = this.overrides.find(o => o.category === category && o.detail === detail);
137
172
  if (exact)
package/dist/flat.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { type Output, type RunContext, type RunState } from './context';
2
+ import type { Format } from './formats';
3
+ import type { PackagePlan } from './plan';
4
+ import { type ByteSink, type ByteSource } from './types';
5
+ import { type XmlLimits } from './xml';
6
+ import { type ZipLimits } from './zip';
7
+ /** The namespace of a Flat OPC package, the single-file form of OOXML that Word, Excel and PowerPoint save as XML. */
8
+ export declare const PKG_NS = "http://schemas.microsoft.com/office/2006/xmlPackage";
9
+ /** The name findings give the one part of a flat OpenDocument file. */
10
+ export declare const FLAT_ODF_PART = "office:document";
11
+ /** Flat OPC has no media type of its own, and Office saves it with the .xml extension. */
12
+ export declare const FLAT_OPC_FORMAT: Format;
13
+ /** Whether the first bytes of a file read as XML: a byte order mark, white space and then "<". */
14
+ export declare function startsAsXml(head: Uint8Array): boolean;
15
+ /**
16
+ * Whether the start of an XML file names a flat OpenDocument document or a Flat OPC package as its root element. The
17
+ * reading of the whole file decides; this tells a router which package to try.
18
+ */
19
+ export declare function isFlatHead(text: string): boolean;
20
+ /**
21
+ * Inspects a flat XML file: flat OpenDocument, read with the OpenDocument rules over its one part, or Flat OPC,
22
+ * which is unpacked into a package of its parts and read with the OOXML rules. Returns what the rewrite needs.
23
+ */
24
+ export declare function inspectFlat(ctx: RunContext, src: ByteSource, size: number, zipLimits: ZipLimits): Promise<Output | undefined>;
25
+ /** Writes a flat OpenDocument file without what the plan removes. */
26
+ export declare function writeFlatOdf(run: RunState, src: ByteSource, plan: PackagePlan, limits: XmlLimits, sink: ByteSink): Promise<void>;
27
+ /**
28
+ * Writes a Flat OPC package without what the plan removes: the package is rewritten as any OOXML package is, and its
29
+ * parts are written back into one XML file, each with its name, content type and attributes.
30
+ */
31
+ export declare function writeFlatOpc(run: RunState, output: Extract<Output, {
32
+ kind: 'flatopc';
33
+ }>, limits: XmlLimits, zipLimits: ZipLimits, sink: ByteSink): Promise<void>;