@zone-eu/mailsplit 5.4.24 → 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 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 Infinity) limits message header size in bytes
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
 
@@ -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._headerlen + (extra || 0) > this.maxHeadSize) {
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 || (line.length === 1 && line[0] === 0x0a) || (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a)) {
639
+ if (final || endsBlock) {
639
640
  let currentNode = this.node;
640
641
 
641
642
  currentNode.parseHeaders();
@@ -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): void;
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 {void}
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): void;
145
+ addHeaderChunk(line?: Buffer | false): boolean;
144
146
 
145
147
  /**
146
148
  * Parses collected header bytes and populates MIME metadata fields.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zone-eu/mailsplit",
3
- "version": "5.4.24",
3
+ "version": "5.4.25",
4
4
  "description": "Split email messages into an object stream",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",