@zone-eu/mailsplit 5.4.23 → 5.4.25
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 +1 -1
- package/lib/headers.js +10 -4
- package/lib/message-splitter.js +5 -4
- package/lib/mime-node.d.ts +4 -1
- package/lib/mime-node.js +23 -2
- package/lib/types.d.ts +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Where
|
|
|
31
31
|
|
|
32
32
|
- **options** is an optional options object
|
|
33
33
|
- **options.ignoreEmbedded** (boolean, defaults to false) if true then treat message/rfc822 node as normal leaf node and do not try to parse it
|
|
34
|
-
- **options.maxHeadSize** (number, defaults to
|
|
34
|
+
- **options.maxHeadSize** (number, defaults to 1 MiB) limits the header size of a MIME node in bytes, every header line counting for at least 64 bytes
|
|
35
35
|
|
|
36
36
|
#### Events
|
|
37
37
|
|
package/lib/headers.js
CHANGED
|
@@ -365,10 +365,16 @@ class Headers {
|
|
|
365
365
|
return;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
let
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
368
|
+
let raw = this.headers.toString('binary');
|
|
369
|
+
|
|
370
|
+
// Trailing line breaks go with an index scan. /[\r\n]+$/ retried a run of carriage
|
|
371
|
+
// returns from every position in it, quadratic on a header line made of them
|
|
372
|
+
let end = raw.length;
|
|
373
|
+
while (end > 0 && (raw.charCodeAt(end - 1) === 0x0a || raw.charCodeAt(end - 1) === 0x0d)) {
|
|
374
|
+
end--;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let rawLines = raw.slice(0, end).split(/\r?\n/);
|
|
372
378
|
|
|
373
379
|
// Single forward pass. Splicing folded lines out of the source array in place was
|
|
374
380
|
// quadratic on a header block with many continuation lines.
|
package/lib/message-splitter.js
CHANGED
|
@@ -488,13 +488,14 @@ class MessageSplitter extends Transform {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
/**
|
|
491
|
-
* Checks the header bytes collected for the current node against maxHeadSize.
|
|
491
|
+
* Checks the header bytes collected for the current node against maxHeadSize. See
|
|
492
|
+
* MimeNode#headerCost.
|
|
492
493
|
*
|
|
493
494
|
* @param {number} [extra] Bytes that belong to the header block but are not stored yet.
|
|
494
495
|
* @returns {(Error & {code?: string}) | null} Error object if the limit was exceeded.
|
|
495
496
|
*/
|
|
496
497
|
checkHeadSize(extra) {
|
|
497
|
-
if (this.node.
|
|
498
|
+
if (this.node.headerCost(extra) > this.maxHeadSize) {
|
|
498
499
|
return maxLenError('Max header size for a MIME node exceeded');
|
|
499
500
|
}
|
|
500
501
|
return null;
|
|
@@ -630,12 +631,12 @@ class MessageSplitter extends Transform {
|
|
|
630
631
|
|
|
631
632
|
switch (this.state) {
|
|
632
633
|
case HEAD: {
|
|
633
|
-
this.node.addHeaderChunk(line);
|
|
634
|
+
let endsBlock = this.node.addHeaderChunk(line);
|
|
634
635
|
let headSizeError = this.checkHeadSize();
|
|
635
636
|
if (headSizeError) {
|
|
636
637
|
return next(headSizeError);
|
|
637
638
|
}
|
|
638
|
-
if (final ||
|
|
639
|
+
if (final || endsBlock) {
|
|
639
640
|
let currentNode = this.node;
|
|
640
641
|
|
|
641
642
|
currentNode.parseHeaders();
|
package/lib/mime-node.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ declare class MimeNode implements MimeNodeShape {
|
|
|
23
23
|
|
|
24
24
|
/** Length, in bytes, of the raw header block collected for this node. */
|
|
25
25
|
_headerlen: number;
|
|
26
|
+
_headerCost: number;
|
|
26
27
|
|
|
27
28
|
/** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
|
|
28
29
|
multipart: string | false;
|
|
@@ -88,7 +89,9 @@ declare class MimeNode implements MimeNodeShape {
|
|
|
88
89
|
* @param line Raw header line bytes; falsy values are ignored.
|
|
89
90
|
* @returns Nothing.
|
|
90
91
|
*/
|
|
91
|
-
addHeaderChunk(line?: Buffer | false):
|
|
92
|
+
addHeaderChunk(line?: Buffer | false): boolean;
|
|
93
|
+
/** What the header lines count for against maxHeadSize, every line at least 64 bytes */
|
|
94
|
+
headerCost(extra?: number): number;
|
|
92
95
|
|
|
93
96
|
/**
|
|
94
97
|
* Parses collected header bytes and populates MIME metadata fields.
|
package/lib/mime-node.js
CHANGED
|
@@ -18,6 +18,10 @@ const pathlib = require('path');
|
|
|
18
18
|
|
|
19
19
|
const Libmime = /** @type {any} */ (libmime.Libmime);
|
|
20
20
|
|
|
21
|
+
// The least a header line counts for against maxHeadSize. A parsed line costs a fixed
|
|
22
|
+
// overhead on top of its bytes, so 1 MiB of header is at most 16k lines
|
|
23
|
+
const HEADER_LINE_MIN_COST = 64;
|
|
24
|
+
|
|
21
25
|
/**
|
|
22
26
|
* Removes everything from the first `(` to the last `)` of each line, the same result as
|
|
23
27
|
* `value.replace(/\(.*\)/g, '')`, but in linear time. The regex backtracks quadratically
|
|
@@ -117,6 +121,7 @@ class MimeNode {
|
|
|
117
121
|
/** @type {Buffer[]} */
|
|
118
122
|
this._headersLines = [];
|
|
119
123
|
this._headerlen = 0;
|
|
124
|
+
this._headerCost = 0;
|
|
120
125
|
|
|
121
126
|
/** @type {StructuredHeader | false} */
|
|
122
127
|
this._parsedContentType = false;
|
|
@@ -171,14 +176,28 @@ class MimeNode {
|
|
|
171
176
|
|
|
172
177
|
/**
|
|
173
178
|
* @param {Buffer | false} [line]
|
|
174
|
-
* @returns {
|
|
179
|
+
* @returns {boolean} Whether the line is the empty line that ends the header block.
|
|
175
180
|
*/
|
|
176
181
|
addHeaderChunk(line) {
|
|
177
182
|
if (!line) {
|
|
178
|
-
return;
|
|
183
|
+
return false;
|
|
179
184
|
}
|
|
180
185
|
this._headersLines.push(line);
|
|
181
186
|
this._headerlen += line.length;
|
|
187
|
+
let endsBlock = (line.length === 1 && line[0] === 0x0a) || (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a);
|
|
188
|
+
// the empty line that ends the block costs its bytes, every other line at least the floor
|
|
189
|
+
this._headerCost += endsBlock ? line.length : Math.max(line.length, HEADER_LINE_MIN_COST);
|
|
190
|
+
return endsBlock;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* What the header lines of this node count for against maxHeadSize, see HEADER_LINE_MIN_COST.
|
|
195
|
+
*
|
|
196
|
+
* @param {number} [extra] Bytes of a header line that is not stored yet.
|
|
197
|
+
* @returns {number} The cost in bytes.
|
|
198
|
+
*/
|
|
199
|
+
headerCost(extra) {
|
|
200
|
+
return this._headerCost + (extra ? Math.max(extra, HEADER_LINE_MIN_COST) : 0);
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/**
|
|
@@ -189,6 +208,8 @@ class MimeNode {
|
|
|
189
208
|
return;
|
|
190
209
|
}
|
|
191
210
|
this.headers = new Headers(Buffer.concat(this._headersLines, this._headerlen), this.config);
|
|
211
|
+
// the views held the input chunks alive, the block lives on in the copy
|
|
212
|
+
this._headersLines = [];
|
|
192
213
|
let headers = this.headers;
|
|
193
214
|
|
|
194
215
|
let dispositionHeader = headers.getFirst('Content-Disposition');
|
package/lib/types.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ export interface MimeNode {
|
|
|
83
83
|
|
|
84
84
|
/** Length, in bytes, of the raw header block collected for this node. */
|
|
85
85
|
_headerlen: number;
|
|
86
|
+
_headerCost: number;
|
|
87
|
+
headerCost(extra?: number): number;
|
|
86
88
|
|
|
87
89
|
/** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
|
|
88
90
|
multipart: string | false;
|
|
@@ -140,7 +142,7 @@ export interface MimeNode {
|
|
|
140
142
|
* @param line Raw header line bytes; falsy values are ignored.
|
|
141
143
|
* @returns Nothing.
|
|
142
144
|
*/
|
|
143
|
-
addHeaderChunk(line?: Buffer | false):
|
|
145
|
+
addHeaderChunk(line?: Buffer | false): boolean;
|
|
144
146
|
|
|
145
147
|
/**
|
|
146
148
|
* Parses collected header bytes and populates MIME metadata fields.
|