@patrickdk77/office-defuse 0.0.0-stage → 0.1.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.
@@ -0,0 +1,247 @@
1
+ /** High-level kind of a finding. */
2
+ export declare enum OfficeCategory {
3
+ Container = "CONTAINER",
4
+ Xml = "XML",
5
+ Limit = "LIMIT",
6
+ Package = "PACKAGE",
7
+ Encrypted = "ENCRYPTED",
8
+ Macro = "MACRO",
9
+ Ui = "UI",
10
+ Action = "ACTION",
11
+ Dde = "DDE",
12
+ Field = "FIELD",
13
+ External = "EXTERNAL",
14
+ Link = "LINK",
15
+ Embedded = "EMBEDDED",
16
+ Control = "CONTROL"
17
+ }
18
+ /** Exact variant of a finding. Some details appear under more than one category. */
19
+ export declare enum OfficeDetail {
20
+ NotZip = "NOT_ZIP",
21
+ AmbiguousEnd = "AMBIGUOUS_END",
22
+ MultiDisk = "MULTI_DISK",
23
+ DirectoryMismatch = "DIRECTORY_MISMATCH",
24
+ OutOfRange = "OUT_OF_RANGE",
25
+ Overlap = "OVERLAP",
26
+ HeaderMismatch = "HEADER_MISMATCH",
27
+ DuplicateName = "DUPLICATE_NAME",
28
+ UnsafeName = "UNSAFE_NAME",
29
+ NameEncoding = "NAME_ENCODING",
30
+ EncryptedEntry = "ENCRYPTED_ENTRY",
31
+ UnsupportedMethod = "UNSUPPORTED_METHOD",
32
+ Zip64Mismatch = "ZIP64_MISMATCH",
33
+ BadExtraField = "BAD_EXTRA_FIELD",
34
+ DataError = "DATA_ERROR",
35
+ LeadingBytes = "LEADING_BYTES",
36
+ HiddenData = "HIDDEN_DATA",
37
+ TrailingBytes = "TRAILING_BYTES",
38
+ Doctype = "DOCTYPE",
39
+ InternalSubset = "INTERNAL_SUBSET",
40
+ ExternalEntity = "EXTERNAL_ENTITY",
41
+ Malformed = "MALFORMED",
42
+ Encoding = "ENCODING",
43
+ FileSize = "FILE_SIZE",
44
+ EntryCount = "ENTRY_COUNT",
45
+ DecompressedSize = "DECOMPRESSED_SIZE",
46
+ TotalDecompressedSize = "TOTAL_DECOMPRESSED_SIZE",
47
+ XmlDepth = "XML_DEPTH",
48
+ XmlTokenSize = "XML_TOKEN_SIZE",
49
+ XmlEntityDepth = "XML_ENTITY_DEPTH",
50
+ XmlEntityExpansions = "XML_ENTITY_EXPANSIONS",
51
+ XmlEntitySize = "XML_ENTITY_SIZE",
52
+ Time = "TIME",
53
+ Unrecognized = "UNRECOGNIZED",
54
+ AmbiguousFormat = "AMBIGUOUS_FORMAT",
55
+ Unreadable = "UNREADABLE",
56
+ DuplicateEntry = "DUPLICATE_ENTRY",
57
+ UntypedPart = "UNTYPED_PART",
58
+ UnreferencedPart = "UNREFERENCED_PART",
59
+ OdfLayout = "ODF_LAYOUT",
60
+ PasswordRequired = "PASSWORD_REQUIRED",
61
+ VbaProject = "VBA_PROJECT",
62
+ XlmMacrosheet = "XLM_MACROSHEET",
63
+ OdfBasic = "ODF_BASIC",
64
+ OdfScripts = "ODF_SCRIPTS",
65
+ OdfDialogs = "ODF_DIALOGS",
66
+ EventOnOpen = "EVENT_ON_OPEN",
67
+ Event = "EVENT",
68
+ Link = "LINK",
69
+ Ribbon = "RIBBON",
70
+ Callback = "CALLBACK",
71
+ OdfConfiguration = "ODF_CONFIGURATION",
72
+ RunProgram = "RUN_PROGRAM",
73
+ OleVerb = "OLE_VERB",
74
+ Field = "FIELD",
75
+ Formula = "FORMULA",
76
+ Macrobutton = "MACROBUTTON",
77
+ Computed = "COMPUTED",
78
+ Template = "TEMPLATE",
79
+ Object = "OBJECT",
80
+ Image = "IMAGE",
81
+ Frame = "FRAME",
82
+ Include = "INCLUDE",
83
+ Workbook = "WORKBOOK",
84
+ Reload = "RELOAD",
85
+ Other = "OTHER",
86
+ LocalFile = "LOCAL_FILE",
87
+ Safe = "SAFE",
88
+ FileUrl = "FILE_URL",
89
+ NetworkPath = "NETWORK_PATH",
90
+ DataUrl = "DATA_URL",
91
+ ScriptUrl = "SCRIPT_URL",
92
+ OtherScheme = "OTHER_SCHEME",
93
+ Credentials = "CREDENTIALS",
94
+ IpHost = "IP_HOST",
95
+ LookalikeHost = "LOOKALIKE_HOST",
96
+ EncodedHost = "ENCODED_HOST",
97
+ Relative = "RELATIVE",
98
+ OleObject = "OLE_OBJECT",
99
+ Package = "PACKAGE",
100
+ OleFile = "OLE_FILE",
101
+ Applet = "APPLET",
102
+ Plugin = "PLUGIN",
103
+ ActiveX = "ACTIVEX"
104
+ }
105
+ export type OfficeFindingAction = 'reject' | 'strip' | 'info';
106
+ /**
107
+ * A finding as any package of the defuse family reports it. Category and detail are plain strings, since the string
108
+ * enums of two packages never match each other.
109
+ */
110
+ export interface DefuseFinding {
111
+ category: string;
112
+ detail: string;
113
+ /** Plain sentence, safe to show the uploader. */
114
+ description: string;
115
+ /** Default action after any caller override. */
116
+ action: 'reject' | 'strip' | 'info';
117
+ /** Where in the file, such as a part name or a relationship. */
118
+ location?: string;
119
+ /** Extra values: the target, the field code, the content type, counts. */
120
+ data?: Record<string, string | number>;
121
+ /**
122
+ * Set when the finding comes from inside a contained file: the file's name, with " > " between nesting levels.
123
+ * `location` then starts with where that file sits.
124
+ */
125
+ attachment?: string;
126
+ /** How much the finding adds to a score, set by the package that made it. */
127
+ weight?: number;
128
+ }
129
+ /** A finding office-defuse makes itself. */
130
+ export interface OfficeFinding extends DefuseFinding {
131
+ category: OfficeCategory;
132
+ detail: OfficeDetail;
133
+ }
134
+ export declare enum OfficeRisk {
135
+ None = "NONE",
136
+ Low = "LOW",
137
+ Medium = "MEDIUM",
138
+ High = "HIGH",
139
+ Critical = "CRITICAL",
140
+ /** The file could not be inspected: a refusal of the container or XML layer, or a limit, stopped the run. */
141
+ Unknown = "UNKNOWN"
142
+ }
143
+ export interface OfficeInspection {
144
+ status: 'clean' | 'strippable' | 'rejected';
145
+ /** 0 to 100, null when risk is UNKNOWN. */
146
+ score: number | null;
147
+ risk: OfficeRisk;
148
+ findings: DefuseFinding[];
149
+ /** The format the content says, as its usual file extension without the dot, such as 'docx' or 'ods'. '' when not recognized. */
150
+ format: string;
151
+ /** The MIME type of a file in that format. '' when not recognized. */
152
+ mimeType: string;
153
+ }
154
+ /** Random-access input. A buffer, a local file and an S3 ranged GetObject all fit. */
155
+ export interface ByteSource {
156
+ size(): Promise<number>;
157
+ read(offset: number, length: number): Promise<Uint8Array>;
158
+ close?(): Promise<void>;
159
+ }
160
+ /** Append-only output. Never seeks. */
161
+ export interface ByteSink {
162
+ write(chunk: Uint8Array): Promise<void>;
163
+ close(): Promise<void>;
164
+ /** Called instead of close() when copying to the sink fails, so a partial output is not taken for a whole one. */
165
+ abort?(error: unknown): Promise<void>;
166
+ }
167
+ export interface OfficeLimits {
168
+ /** Bytes in the upload. Not enforced unless set. */
169
+ fileSize?: number;
170
+ /** Entries in the ZIP central directory. Default 10,000. */
171
+ entries?: number;
172
+ /** Declared decompressed size of any one entry, in bytes. Default 512 MiB. */
173
+ decompressedBytes?: number;
174
+ /** Declared decompressed sizes of all entries together, in bytes. Default 2 GiB. */
175
+ totalDecompressedBytes?: number;
176
+ /** Element nesting depth in one XML part. Default 256. */
177
+ xmlDepth?: number;
178
+ /** Characters in one XML tag, comment, processing instruction or CDATA section. Default 16 Mi. */
179
+ xmlTokenSize?: number;
180
+ /** Entity references nested inside entity text. Default 16. */
181
+ xmlEntityDepth?: number;
182
+ /** Entity references expanded in the whole run. Default 100,000. */
183
+ xmlEntityExpansions?: number;
184
+ /** Characters of entity text expanded in the whole run. Default 16 Mi. */
185
+ xmlEntitySize?: number;
186
+ /** Milliseconds for the whole run. Not enforced unless set. */
187
+ timeMs?: number;
188
+ }
189
+ /** What a contained file plugin learns of the run, with nothing format-specific, so any package of the family can make one. */
190
+ export interface PluginContext {
191
+ /** The depth of the file itself: 0 for the upload, 1 for a file inside it, and so on, whatever the formats. */
192
+ depth: number;
193
+ /** When a time limit is set, the Date.now() value the whole run must finish by. Nested runs share it. */
194
+ deadline?: number;
195
+ }
196
+ export interface ContainedFile {
197
+ name?: string;
198
+ /** The MIME type the container declares for the file, such as a part's content type. */
199
+ declaredType?: string;
200
+ /** From the first bytes. */
201
+ sniffedType?: string;
202
+ /** Decoded size. */
203
+ size: number;
204
+ location: string;
205
+ /** The depth of the file itself: 0 for the upload, 1 for a file inside it, and so on, whatever the formats. */
206
+ depth: number;
207
+ /** Decoded bytes. */
208
+ source: ByteSource;
209
+ }
210
+ /** What a file plugin did, plus anything it found inside the file. */
211
+ export interface ContainedFileResult {
212
+ result: 'scrubbed' | 'passed' | 'removed';
213
+ /** Findings inside the file as it arrived. They go into the report of the file that contains it. */
214
+ findings?: DefuseFinding[];
215
+ /** Findings inside the bytes the plugin kept. They go into the report of the output. */
216
+ outputFindings?: DefuseFinding[];
217
+ }
218
+ export interface ContainedFilePlugin {
219
+ kind: 'file';
220
+ name: string;
221
+ accepts(file: ContainedFile): boolean | Promise<boolean>;
222
+ /** Writes the bytes to keep. Returning 'removed' drops the file after all. */
223
+ process(file: ContainedFile, sink: ByteSink, context: PluginContext): Promise<'scrubbed' | 'passed' | 'removed' | ContainedFileResult>;
224
+ }
225
+ export interface OfficeActionOverride {
226
+ category: OfficeCategory;
227
+ detail?: OfficeDetail;
228
+ action: OfficeFindingAction;
229
+ }
230
+ export interface OfficeScoreWeight {
231
+ category: OfficeCategory;
232
+ detail?: OfficeDetail;
233
+ weight: number;
234
+ }
235
+ /** Lower bound of each band. */
236
+ export interface OfficeScoreBands {
237
+ low: number;
238
+ medium: number;
239
+ high: number;
240
+ critical: number;
241
+ }
242
+ export interface OfficeOptions {
243
+ limits?: OfficeLimits;
244
+ actionOverrides?: OfficeActionOverride[];
245
+ scoreWeights?: OfficeScoreWeight[];
246
+ scoreBands?: OfficeScoreBands;
247
+ }
package/dist/types.js ADDED
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OfficeRisk = exports.OfficeDetail = exports.OfficeCategory = void 0;
4
+ /** High-level kind of a finding. */
5
+ var OfficeCategory;
6
+ (function (OfficeCategory) {
7
+ OfficeCategory["Container"] = "CONTAINER";
8
+ OfficeCategory["Xml"] = "XML";
9
+ OfficeCategory["Limit"] = "LIMIT";
10
+ OfficeCategory["Package"] = "PACKAGE";
11
+ OfficeCategory["Encrypted"] = "ENCRYPTED";
12
+ OfficeCategory["Macro"] = "MACRO";
13
+ OfficeCategory["Ui"] = "UI";
14
+ OfficeCategory["Action"] = "ACTION";
15
+ OfficeCategory["Dde"] = "DDE";
16
+ OfficeCategory["Field"] = "FIELD";
17
+ OfficeCategory["External"] = "EXTERNAL";
18
+ OfficeCategory["Link"] = "LINK";
19
+ OfficeCategory["Embedded"] = "EMBEDDED";
20
+ OfficeCategory["Control"] = "CONTROL";
21
+ })(OfficeCategory || (exports.OfficeCategory = OfficeCategory = {}));
22
+ /** Exact variant of a finding. Some details appear under more than one category. */
23
+ var OfficeDetail;
24
+ (function (OfficeDetail) {
25
+ // CONTAINER
26
+ OfficeDetail["NotZip"] = "NOT_ZIP";
27
+ OfficeDetail["AmbiguousEnd"] = "AMBIGUOUS_END";
28
+ OfficeDetail["MultiDisk"] = "MULTI_DISK";
29
+ OfficeDetail["DirectoryMismatch"] = "DIRECTORY_MISMATCH";
30
+ OfficeDetail["OutOfRange"] = "OUT_OF_RANGE";
31
+ OfficeDetail["Overlap"] = "OVERLAP";
32
+ OfficeDetail["HeaderMismatch"] = "HEADER_MISMATCH";
33
+ OfficeDetail["DuplicateName"] = "DUPLICATE_NAME";
34
+ OfficeDetail["UnsafeName"] = "UNSAFE_NAME";
35
+ OfficeDetail["NameEncoding"] = "NAME_ENCODING";
36
+ OfficeDetail["EncryptedEntry"] = "ENCRYPTED_ENTRY";
37
+ OfficeDetail["UnsupportedMethod"] = "UNSUPPORTED_METHOD";
38
+ OfficeDetail["Zip64Mismatch"] = "ZIP64_MISMATCH";
39
+ OfficeDetail["BadExtraField"] = "BAD_EXTRA_FIELD";
40
+ OfficeDetail["DataError"] = "DATA_ERROR";
41
+ OfficeDetail["LeadingBytes"] = "LEADING_BYTES";
42
+ OfficeDetail["HiddenData"] = "HIDDEN_DATA";
43
+ OfficeDetail["TrailingBytes"] = "TRAILING_BYTES";
44
+ // XML
45
+ OfficeDetail["Doctype"] = "DOCTYPE";
46
+ OfficeDetail["InternalSubset"] = "INTERNAL_SUBSET";
47
+ OfficeDetail["ExternalEntity"] = "EXTERNAL_ENTITY";
48
+ OfficeDetail["Malformed"] = "MALFORMED";
49
+ OfficeDetail["Encoding"] = "ENCODING";
50
+ // LIMIT
51
+ OfficeDetail["FileSize"] = "FILE_SIZE";
52
+ OfficeDetail["EntryCount"] = "ENTRY_COUNT";
53
+ OfficeDetail["DecompressedSize"] = "DECOMPRESSED_SIZE";
54
+ OfficeDetail["TotalDecompressedSize"] = "TOTAL_DECOMPRESSED_SIZE";
55
+ OfficeDetail["XmlDepth"] = "XML_DEPTH";
56
+ OfficeDetail["XmlTokenSize"] = "XML_TOKEN_SIZE";
57
+ OfficeDetail["XmlEntityDepth"] = "XML_ENTITY_DEPTH";
58
+ OfficeDetail["XmlEntityExpansions"] = "XML_ENTITY_EXPANSIONS";
59
+ OfficeDetail["XmlEntitySize"] = "XML_ENTITY_SIZE";
60
+ OfficeDetail["Time"] = "TIME";
61
+ // PACKAGE
62
+ OfficeDetail["Unrecognized"] = "UNRECOGNIZED";
63
+ OfficeDetail["AmbiguousFormat"] = "AMBIGUOUS_FORMAT";
64
+ OfficeDetail["Unreadable"] = "UNREADABLE";
65
+ OfficeDetail["DuplicateEntry"] = "DUPLICATE_ENTRY";
66
+ OfficeDetail["UntypedPart"] = "UNTYPED_PART";
67
+ OfficeDetail["UnreferencedPart"] = "UNREFERENCED_PART";
68
+ OfficeDetail["OdfLayout"] = "ODF_LAYOUT";
69
+ // ENCRYPTED
70
+ OfficeDetail["PasswordRequired"] = "PASSWORD_REQUIRED";
71
+ // MACRO
72
+ OfficeDetail["VbaProject"] = "VBA_PROJECT";
73
+ OfficeDetail["XlmMacrosheet"] = "XLM_MACROSHEET";
74
+ OfficeDetail["OdfBasic"] = "ODF_BASIC";
75
+ OfficeDetail["OdfScripts"] = "ODF_SCRIPTS";
76
+ OfficeDetail["OdfDialogs"] = "ODF_DIALOGS";
77
+ OfficeDetail["EventOnOpen"] = "EVENT_ON_OPEN";
78
+ OfficeDetail["Event"] = "EVENT";
79
+ OfficeDetail["Link"] = "LINK";
80
+ // UI
81
+ OfficeDetail["Ribbon"] = "RIBBON";
82
+ OfficeDetail["Callback"] = "CALLBACK";
83
+ OfficeDetail["OdfConfiguration"] = "ODF_CONFIGURATION";
84
+ // ACTION
85
+ OfficeDetail["RunProgram"] = "RUN_PROGRAM";
86
+ OfficeDetail["OleVerb"] = "OLE_VERB";
87
+ // DDE
88
+ OfficeDetail["Field"] = "FIELD";
89
+ OfficeDetail["Formula"] = "FORMULA";
90
+ // FIELD
91
+ OfficeDetail["Macrobutton"] = "MACROBUTTON";
92
+ OfficeDetail["Computed"] = "COMPUTED";
93
+ // EXTERNAL
94
+ OfficeDetail["Template"] = "TEMPLATE";
95
+ OfficeDetail["Object"] = "OBJECT";
96
+ OfficeDetail["Image"] = "IMAGE";
97
+ OfficeDetail["Frame"] = "FRAME";
98
+ OfficeDetail["Include"] = "INCLUDE";
99
+ OfficeDetail["Workbook"] = "WORKBOOK";
100
+ OfficeDetail["Reload"] = "RELOAD";
101
+ OfficeDetail["Other"] = "OTHER";
102
+ OfficeDetail["LocalFile"] = "LOCAL_FILE";
103
+ // LINK
104
+ OfficeDetail["Safe"] = "SAFE";
105
+ OfficeDetail["FileUrl"] = "FILE_URL";
106
+ OfficeDetail["NetworkPath"] = "NETWORK_PATH";
107
+ OfficeDetail["DataUrl"] = "DATA_URL";
108
+ OfficeDetail["ScriptUrl"] = "SCRIPT_URL";
109
+ OfficeDetail["OtherScheme"] = "OTHER_SCHEME";
110
+ OfficeDetail["Credentials"] = "CREDENTIALS";
111
+ OfficeDetail["IpHost"] = "IP_HOST";
112
+ OfficeDetail["LookalikeHost"] = "LOOKALIKE_HOST";
113
+ OfficeDetail["EncodedHost"] = "ENCODED_HOST";
114
+ OfficeDetail["Relative"] = "RELATIVE";
115
+ // EMBEDDED
116
+ OfficeDetail["OleObject"] = "OLE_OBJECT";
117
+ OfficeDetail["Package"] = "PACKAGE";
118
+ OfficeDetail["OleFile"] = "OLE_FILE";
119
+ OfficeDetail["Applet"] = "APPLET";
120
+ OfficeDetail["Plugin"] = "PLUGIN";
121
+ // CONTROL
122
+ OfficeDetail["ActiveX"] = "ACTIVEX";
123
+ })(OfficeDetail || (exports.OfficeDetail = OfficeDetail = {}));
124
+ var OfficeRisk;
125
+ (function (OfficeRisk) {
126
+ OfficeRisk["None"] = "NONE";
127
+ OfficeRisk["Low"] = "LOW";
128
+ OfficeRisk["Medium"] = "MEDIUM";
129
+ OfficeRisk["High"] = "HIGH";
130
+ OfficeRisk["Critical"] = "CRITICAL";
131
+ /** The file could not be inspected: a refusal of the container or XML layer, or a limit, stopped the run. */
132
+ OfficeRisk["Unknown"] = "UNKNOWN";
133
+ })(OfficeRisk || (exports.OfficeRisk = OfficeRisk = {}));
package/dist/xlsb.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface DdeLink {
2
+ application: string;
3
+ topic: string;
4
+ }
5
+ /**
6
+ * Reads the records of an .xlsb external link part as its chunks arrive, and collects its DDE links. Each record
7
+ * body is skipped, except the first 64 KiB of a BrtBeginSupBook. A header that breaks the record format ends the
8
+ * reading; Excel cannot read such a part either.
9
+ */
10
+ export declare class XlsbLinks {
11
+ readonly dde: DdeLink[];
12
+ private head;
13
+ private left;
14
+ private body;
15
+ private kept;
16
+ private broken;
17
+ write(chunk: Uint8Array): void;
18
+ private finish;
19
+ }
package/dist/xlsb.js ADDED
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XlsbLinks = void 0;
4
+ /** BrtBeginSupBook, the record that opens each link of an .xlsb external link part. */
5
+ const BEGIN_SUPBOOK = 360;
6
+ /** The link type BrtBeginSupBook gives a DDE link. */
7
+ const SUPBOOK_DDE = 1;
8
+ /** How much of one BrtBeginSupBook is kept: far more than the two names of a DDE link need. */
9
+ const KEEP = 65536;
10
+ /**
11
+ * The record header at the start of `b`: the record's type and size, undefined while `b` is too short, or 'bad' when
12
+ * the header runs longer than the format allows. A type takes one or two bytes and a size one to four, seven bits to
13
+ * a byte, and a byte with its high bit clear ends each.
14
+ */
15
+ function header(b) {
16
+ let i = 0;
17
+ let type = 0;
18
+ for (let shift = 0;; shift += 7) {
19
+ if (i >= b.length)
20
+ return undefined;
21
+ type |= (b[i] & 0x7f) << shift;
22
+ if (!(b[i++] & 0x80))
23
+ break;
24
+ if (shift === 7)
25
+ return 'bad';
26
+ }
27
+ let size = 0;
28
+ for (let shift = 0;; shift += 7) {
29
+ if (i >= b.length)
30
+ return undefined;
31
+ size += (b[i] & 0x7f) * 2 ** shift;
32
+ if (!(b[i++] & 0x80))
33
+ break;
34
+ if (shift === 21)
35
+ return 'bad';
36
+ }
37
+ return { type, size };
38
+ }
39
+ /** An XLWideString at `at`: a 32-bit character count, then UTF-16LE. Cut short at the end of what was kept. */
40
+ function wideString(b, at) {
41
+ if (at + 4 > b.length)
42
+ return { text: '', next: b.length };
43
+ const end = Math.min(b.length, at + 4 + b.readUInt32LE(at) * 2);
44
+ return { text: b.toString('utf16le', at + 4, end), next: end };
45
+ }
46
+ /**
47
+ * Reads the records of an .xlsb external link part as its chunks arrive, and collects its DDE links. Each record
48
+ * body is skipped, except the first 64 KiB of a BrtBeginSupBook. A header that breaks the record format ends the
49
+ * reading; Excel cannot read such a part either.
50
+ */
51
+ class XlsbLinks {
52
+ dde = [];
53
+ head = [];
54
+ left = 0;
55
+ body;
56
+ kept = 0;
57
+ broken = false;
58
+ write(chunk) {
59
+ let i = 0;
60
+ while (i < chunk.length && !this.broken) {
61
+ if (this.left > 0) {
62
+ const n = Math.min(this.left, chunk.length - i);
63
+ if (this.body && this.kept < KEEP) {
64
+ const part = Buffer.from(chunk.subarray(i, i + Math.min(n, KEEP - this.kept)));
65
+ this.body.push(part);
66
+ this.kept += part.length;
67
+ }
68
+ this.left -= n;
69
+ i += n;
70
+ if (this.left === 0)
71
+ this.finish();
72
+ continue;
73
+ }
74
+ this.head.push(chunk[i++]);
75
+ const h = header(this.head);
76
+ if (h === undefined)
77
+ continue;
78
+ if (h === 'bad') {
79
+ this.broken = true;
80
+ return;
81
+ }
82
+ this.head = [];
83
+ this.body = h.type === BEGIN_SUPBOOK ? [] : undefined;
84
+ this.kept = 0;
85
+ this.left = h.size;
86
+ if (this.left === 0)
87
+ this.finish();
88
+ }
89
+ }
90
+ finish() {
91
+ if (!this.body)
92
+ return;
93
+ const b = Buffer.concat(this.body);
94
+ this.body = undefined;
95
+ if (b.length < 2 || b.readUInt16LE(0) !== SUPBOOK_DDE)
96
+ return;
97
+ const application = wideString(b, 2);
98
+ this.dde.push({ application: application.text, topic: wideString(b, application.next).text });
99
+ }
100
+ }
101
+ exports.XlsbLinks = XlsbLinks;