spamscanner 7.0.3 → 7.0.5

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/dist/esm/arf.js CHANGED
@@ -1,16 +1,262 @@
1
1
  // src/arf.js
2
- import { Buffer } from "node:buffer";
2
+ import { Buffer as Buffer2 } from "node:buffer";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { isIP } from "node:net";
5
- import { simpleParser } from "mailparser";
6
- var VALID_FEEDBACK_TYPES = /* @__PURE__ */ new Set(["abuse", "fraud", "virus", "other", "not-spam", "auth-failure", "dmarc"]);
7
- var MULTIPLE = /* @__PURE__ */ new Set(["original_rcpt_to", "authentication_results", "reported_domain", "reported_uri"]);
5
+
6
+ // src/parse.js
7
+ import { Buffer } from "node:buffer";
8
+ import { createHash } from "node:crypto";
9
+ import { Readable } from "node:stream";
10
+ import { MailParser, simpleParser } from "mailparser";
11
+ var ADDRESS_FIELDS = /* @__PURE__ */ new Set(["from", "to", "cc", "bcc", "sender", "reply-to", "delivered-to", "return-path", "disposition-notification-to"]);
12
+ var MAX_ADDRESS_FIELD = 16 * 1024;
13
+ var MAX_ADDRESS_SEPARATORS = 2e3;
14
+ var MAX_ADDRESS_GROUPS = 16;
15
+ var MAX_ADDRESS_TOTAL = 64 * 1024;
16
+ var ADDRESS_OPERATORS = /* @__PURE__ */ new Map([['"', '"'], ["(", ")"], ["<", ">"], [",", ""], [":", ";"], [";", ""]]);
17
+ var SINGLE_FIELDS = /* @__PURE__ */ new Set(["message-id", "content-id", "from", "sender", "in-reply-to", "reply-to", "subject", "date", "content-disposition", "content-type", "content-transfer-encoding", "priority", "mime-version", "content-description", "precedence", "errors-to", "disposition-notification-to"]);
18
+ var PRIORITY_FIELDS = /* @__PURE__ */ new Set(["priority", "x-priority", "x-msmail-priority", "importance"]);
19
+ var BOUNDED = /* @__PURE__ */ Symbol("bounded address fields");
20
+ var ROOM = /* @__PURE__ */ Symbol("room left for address fields");
8
21
  var PARSER_OPTIONS = {
9
22
  skipHtmlToText: true,
10
- skipTextToHtml: true,
11
23
  skipImageLinks: true,
12
- skipTextLinks: true
24
+ skipTextToHtml: true,
25
+ skipTextLinks: true,
26
+ [BOUNDED]: true
13
27
  };
28
+ function boundAddressField(line, room) {
29
+ const start = line.indexOf(":") + 1;
30
+ const limit = Math.min(line.length, MAX_ADDRESS_FIELD, room);
31
+ let end = start;
32
+ let expecting = "";
33
+ let escaped = false;
34
+ let separators = 0;
35
+ let groups = 0;
36
+ let index = start;
37
+ for (; index < limit; index++) {
38
+ const chr = line[index];
39
+ if ((chr === "," || chr === ";") && ++separators > MAX_ADDRESS_SEPARATORS) {
40
+ break;
41
+ }
42
+ if (escaped) {
43
+ escaped = false;
44
+ } else if (expecting !== "" && chr === expecting) {
45
+ expecting = "";
46
+ end = chr === ";" ? index + 1 : end;
47
+ } else if (expecting === "" && ADDRESS_OPERATORS.has(chr)) {
48
+ expecting = ADDRESS_OPERATORS.get(chr);
49
+ end = chr === "," || chr === ";" ? index : end;
50
+ } else if (expecting === '"' && chr === "\\") {
51
+ escaped = true;
52
+ }
53
+ if (chr === ":" && expecting === ";" && ++groups > MAX_ADDRESS_GROUPS) {
54
+ break;
55
+ }
56
+ }
57
+ return index === line.length ? line : line.slice(0, end).trimEnd();
58
+ }
59
+ function boundAddressLines(lines, budget, only = null) {
60
+ const cut = /* @__PURE__ */ new Set();
61
+ const bounded = lines.map((entry) => {
62
+ if (only ? !only.has(entry.key) : !ADDRESS_FIELDS.has(entry.key) && !entry.key.startsWith("list-")) {
63
+ return entry;
64
+ }
65
+ const line = boundAddressField(entry.line, budget.room);
66
+ budget.room = Math.max(budget.room - line.length, 0);
67
+ if (line === entry.line) {
68
+ return entry;
69
+ }
70
+ cut.add(entry.key);
71
+ return { ...entry, line };
72
+ });
73
+ return { lines: bounded, cut };
74
+ }
75
+ var { processHeaders } = MailParser.prototype;
76
+ MailParser.prototype.processHeaders = function(lines) {
77
+ if (!this.options[BOUNDED]) {
78
+ return processHeaders.call(this, lines);
79
+ }
80
+ this[ROOM] ??= { room: MAX_ADDRESS_TOTAL };
81
+ const bounded = boundAddressLines(lines, this[ROOM]);
82
+ const headers = processHeaders.call(this, bounded.lines);
83
+ const keyOf = (entry) => PRIORITY_FIELDS.has(entry.key) ? "priority" : entry.key;
84
+ const counts = /* @__PURE__ */ new Map();
85
+ for (const key of bounded.lines.map((entry) => keyOf(entry)).filter((key2) => SINGLE_FIELDS.has(key2))) {
86
+ counts.set(key, (counts.get(key) || 0) + 1);
87
+ }
88
+ for (const key of [...counts.keys()].filter((key2) => counts.get(key2) > 1)) {
89
+ const last = bounded.lines.findLast((entry) => keyOf(entry) === key && processHeaders.call(this, [entry]).has(key));
90
+ if (last) {
91
+ headers.set(key, processHeaders.call(this, [last]).get(key));
92
+ }
93
+ }
94
+ for (const value of [...bounded.cut].flatMap((key) => headers.get(key) ?? [])) {
95
+ value.truncated = true;
96
+ }
97
+ return headers;
98
+ };
99
+ var MAX_HEADER = 512 * 1024;
100
+ var KEPT_FIELDS = /* @__PURE__ */ new Set([
101
+ "from",
102
+ "sender",
103
+ "reply-to",
104
+ "to",
105
+ "cc",
106
+ "subject",
107
+ "date",
108
+ "message-id",
109
+ "return-path",
110
+ "received",
111
+ "authentication-results",
112
+ "arc-authentication-results",
113
+ "dkim-signature",
114
+ "mime-version",
115
+ "content-type",
116
+ "content-transfer-encoding",
117
+ "list-unsubscribe",
118
+ "list-id"
119
+ ]);
120
+ var AUTH_FIELDS = /* @__PURE__ */ new Set([...KEPT_FIELDS, "arc-seal", "arc-message-signature"]);
121
+ var MAX_NESTED_BYTES = 25 * 1024 * 1024;
122
+ var PARSE_TIMEOUT = 6e4;
123
+ async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = simpleParser } = {}) {
124
+ const input = Readable.from([finishable(unfoldHeaders(raw))], { objectMode: false, autoDestroy: false });
125
+ input.on("error", () => {
126
+ });
127
+ const { pipe } = input;
128
+ input.pipe = function(parser2, options) {
129
+ parser2.splitter?.on("error", (error) => input.destroy(error));
130
+ return pipe.call(this, parser2, options);
131
+ };
132
+ let timer;
133
+ try {
134
+ return await Promise.race([
135
+ parser(input, PARSER_OPTIONS),
136
+ new Promise((resolve, reject) => {
137
+ timer = setTimeout(() => {
138
+ const error = Object.assign(new Error(`Message not parsed within ${timeout} ms`), { code: "ETIMEDOUT" });
139
+ input.destroy(error);
140
+ reject(error);
141
+ }, timeout);
142
+ })
143
+ ]);
144
+ } finally {
145
+ clearTimeout(timer);
146
+ input.destroy();
147
+ }
148
+ }
149
+ function finishable(raw) {
150
+ const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
151
+ return Buffer.concat([buffer, Buffer.from(buffer.at(-1) === 10 ? "\r\n\r\n" : "\r\n\r\n\r\n")]);
152
+ }
153
+ var MAX_FOLDS = 256;
154
+ function headerValue(line) {
155
+ const parameters = /* @__PURE__ */ new Map();
156
+ let key = null;
157
+ let inKey = false;
158
+ let quoted = false;
159
+ let escaped = false;
160
+ let value = "";
161
+ for (const chr of line.slice(line.indexOf(":") + 1).replaceAll(/\r?\n[ \t]*/g, " ")) {
162
+ if (inKey && chr === "=") {
163
+ key = value.trim().toLowerCase();
164
+ inKey = false;
165
+ value = "";
166
+ } else if (inKey || escaped) {
167
+ value += chr;
168
+ escaped = false;
169
+ } else if (chr === "\\") {
170
+ escaped = true;
171
+ } else if (chr === '"') {
172
+ quoted = !quoted;
173
+ } else if (chr === ";" && !quoted) {
174
+ parameters.set(key, value.trim());
175
+ inKey = true;
176
+ value = "";
177
+ } else {
178
+ value += chr;
179
+ }
180
+ }
181
+ if (!inKey) {
182
+ parameters.set(key, value.trim());
183
+ }
184
+ return { value: parameters.get(null).toLowerCase(), boundary: parameters.get("boundary") ?? "" };
185
+ }
186
+ function delimiter(raw, start, stop, boundary) {
187
+ const rest = start + 2 + boundary.length;
188
+ if (stop - start < boundary.length + 2 || stop - rest > 4 || raw[start] !== 45 || raw[start + 1] !== 45 || !raw.subarray(start + 2, rest).equals(boundary)) {
189
+ return 0;
190
+ }
191
+ if (raw[rest] === 13 || raw[rest] === 10) {
192
+ return 1;
193
+ }
194
+ const tail = raw.subarray(rest, stop).toString("latin1");
195
+ return /^-{0,2}$|^--\r?\n$|^--\r$/.test(tail) ? 2 : 0;
196
+ }
197
+ function unfoldHeaders(raw) {
198
+ const boundaries = [];
199
+ const unfold = [];
200
+ let head = true;
201
+ let fields = [];
202
+ const endHead = (blank) => {
203
+ unfold.push(...fields.filter((field) => field.folds > MAX_FOLDS));
204
+ const first = /* @__PURE__ */ new Map();
205
+ for (const field of fields) {
206
+ const name = raw.toString("latin1", field.start, Math.min(field.end, field.start + 32));
207
+ const key = name.slice(0, name.indexOf(":")).trim().toLowerCase();
208
+ if (["content-type", "content-transfer-encoding", "content-disposition"].includes(key) && !first.has(key)) {
209
+ first.set(key, headerValue(raw.toString("latin1", field.start, field.end)));
210
+ }
211
+ }
212
+ fields = [];
213
+ const type = first.get("content-type") ?? { value: "", boundary: "" };
214
+ const encoding = (first.get("content-transfer-encoding")?.value ?? "").replaceAll(/\(.*\)/g, "").trim();
215
+ if (blank && type.boundary) {
216
+ boundaries.push(Buffer.from(type.boundary));
217
+ }
218
+ head = blank && type.value === "message/rfc822" && ["", "7bit", "8bit", "binary"].includes(encoding) && first.get("content-disposition")?.value === "inline";
219
+ };
220
+ for (let start = 0; start < raw.length; ) {
221
+ const lf = raw.indexOf(10, start);
222
+ const stop = lf === -1 ? raw.length : lf + 1;
223
+ const end = lf === -1 ? stop : raw[lf - 1] === 13 ? lf - 1 : lf;
224
+ const kind = boundaries.length > 0 ? delimiter(raw, start, stop, boundaries.at(-1)) : 0;
225
+ if (kind !== 0) {
226
+ if (head) {
227
+ endHead(false);
228
+ }
229
+ head = kind === 1;
230
+ if (kind === 2) {
231
+ boundaries.pop();
232
+ }
233
+ } else if (head && end === start) {
234
+ endHead(true);
235
+ } else if (head && fields.length > 0 && (raw[start] === 32 || raw[start] === 9)) {
236
+ fields.at(-1).end = end;
237
+ fields.at(-1).folds++;
238
+ } else if (head) {
239
+ fields.push({ start, end, folds: 0 });
240
+ }
241
+ start = stop;
242
+ }
243
+ unfold.push(...fields.filter((field) => field.folds > MAX_FOLDS));
244
+ if (unfold.length === 0) {
245
+ return raw;
246
+ }
247
+ const chunks = [];
248
+ let copied = 0;
249
+ for (const field of unfold) {
250
+ chunks.push(raw.subarray(copied, field.start), Buffer.from(raw.toString("latin1", field.start, field.end).replaceAll(/\r?\n[ \t]*/g, " "), "latin1"));
251
+ copied = field.end;
252
+ }
253
+ chunks.push(raw.subarray(copied));
254
+ return Buffer.concat(chunks);
255
+ }
256
+
257
+ // src/arf.js
258
+ var VALID_FEEDBACK_TYPES = /* @__PURE__ */ new Set(["abuse", "fraud", "virus", "other", "not-spam", "auth-failure", "dmarc"]);
259
+ var MULTIPLE = /* @__PURE__ */ new Set(["original_rcpt_to", "authentication_results", "reported_domain", "reported_uri"]);
14
260
  function parseReportFields(content) {
15
261
  const fields = {};
16
262
  const unfolded = String(content).replaceAll(/\r?\n[ \t]+/g, " ");
@@ -33,12 +279,13 @@ function extractEmail(value) {
33
279
  if (!value) {
34
280
  return null;
35
281
  }
36
- const angle = /<([^<>]*)>/.exec(value);
282
+ const head = value.slice(0, 1024);
283
+ const angle = /<([^<>]*)>/.exec(head);
37
284
  if (angle) {
38
285
  return angle[1].trim() || null;
39
286
  }
40
- const plain = /[^\s@<>]{1,256}@[^\s@<>]+/.exec(value);
41
- return plain ? plain[0] : value.trim();
287
+ const plain = /[^\s@<>]{1,256}@[^\s@<>]+/.exec(head);
288
+ return plain ? plain[0] : head.trim();
42
289
  }
43
290
  function parseIp(value) {
44
291
  if (!value) {
@@ -63,17 +310,18 @@ function parseReportingMta(value) {
63
310
  return match && match[0].length === trimmed.length ? { type: match[1].toLowerCase(), name: match[2].trim() } : { type: "unknown", name: value.trim() };
64
311
  }
65
312
  function decodeQuotedPrintable(text) {
66
- const bytes = [];
67
313
  const soft = text.replaceAll(/=\r?\n/g, "");
314
+ const bytes = Buffer2.allocUnsafe(soft.length);
315
+ let length = 0;
68
316
  for (let i = 0; i < soft.length; i++) {
69
317
  if (soft[i] === "=" && /^[\da-f]{2}$/i.test(soft.slice(i + 1, i + 3))) {
70
- bytes.push(Number.parseInt(soft.slice(i + 1, i + 3), 16));
318
+ bytes[length++] = Number.parseInt(soft.slice(i + 1, i + 3), 16);
71
319
  i += 2;
72
320
  } else {
73
- bytes.push(soft.codePointAt(i) % 256);
321
+ bytes[length++] = soft.codePointAt(i) % 256;
74
322
  }
75
323
  }
76
- return Buffer.from(bytes);
324
+ return bytes.subarray(0, length);
77
325
  }
78
326
  function findPart(raw, boundary, types) {
79
327
  const escaped = boundary.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -91,11 +339,11 @@ function findPart(raw, boundary, types) {
91
339
  const encoding = (/^content-transfer-encoding:\s*(\S+)/im.exec(headers)?.[1] || "").toLowerCase();
92
340
  let content;
93
341
  if (encoding === "base64") {
94
- content = Buffer.from(match[2], "base64");
342
+ content = Buffer2.from(match[2], "base64");
95
343
  } else if (encoding === "quoted-printable") {
96
344
  content = decodeQuotedPrintable(match[2]);
97
345
  } else {
98
- content = Buffer.from(match[2], "latin1");
346
+ content = Buffer2.from(match[2], "latin1");
99
347
  }
100
348
  return { type, content };
101
349
  }
@@ -110,8 +358,9 @@ function isArfMessage(parsed) {
110
358
  const type = typeof header === "string" ? /report-type\s*=\s*"?([\w-]+)/i.exec(header)?.[1] || "" : header.params?.["report-type"] || "";
111
359
  return /^multipart\/report\b/i.test(String(value)) && type.toLowerCase() === "feedback-report";
112
360
  }
113
- async function parse(source) {
114
- const parsed = await simpleParser(source, PARSER_OPTIONS);
361
+ async function parse(message) {
362
+ const source = message instanceof Uint8Array && !Buffer2.isBuffer(message) ? Buffer2.from(message.buffer, message.byteOffset, message.byteLength) : message;
363
+ const parsed = await simpleParse(source);
115
364
  if (!isArfMessage(parsed)) {
116
365
  throw new Error("Not an ARF report: expected multipart/report with report-type=feedback-report");
117
366
  }
@@ -155,7 +404,7 @@ async function parse(source) {
155
404
  originalHeaders: null,
156
405
  rawFeedbackReport: raw
157
406
  };
158
- const text = (Buffer.isBuffer(source) ? source : Buffer.from(String(source))).toString("latin1");
407
+ const text = (Buffer2.isBuffer(source) ? source : Buffer2.from(String(source))).toString("latin1");
159
408
  const { boundary } = parsed.headers.get("content-type").params;
160
409
  const human = findPart(text, boundary, ["text/plain"]);
161
410
  result.humanReadable = human ? human.content.toString("utf8").trim() || null : null;
@@ -163,9 +412,9 @@ async function parse(source) {
163
412
  if (original) {
164
413
  const content = original.content.toString("utf8");
165
414
  result.originalMessage = content;
166
- const inner = await simpleParser(original.type === "message/rfc822" ? content : `${content.trimEnd()}\r
415
+ const inner = await simpleParse(original.type === "message/rfc822" ? content : `${content.trimEnd()}\r
167
416
  \r
168
- `, PARSER_OPTIONS);
417
+ `);
169
418
  result.originalHeaders = Object.fromEntries([...inner.headers].map(([key, value]) => [key, value]));
170
419
  }
171
420
  return result;
@@ -214,20 +463,24 @@ function create(options = {}) {
214
463
  if (originalMailFrom) {
215
464
  report.push(`Original-Mail-From: <${clean(originalMailFrom, "originalMailFrom")}>`);
216
465
  }
217
- for (const rcpt of originalRcptTo) {
466
+ for (const rcpt of [originalRcptTo].flat()) {
218
467
  report.push(`Original-Rcpt-To: <${clean(rcpt, "originalRcptTo")}>`);
219
468
  }
220
469
  if (arrivalDate) {
221
- report.push(`Arrival-Date: ${arrivalDate.toUTCString()}`);
470
+ const date = new Date(arrivalDate);
471
+ if (Number.isNaN(date.getTime())) {
472
+ throw new TypeError("arrivalDate must be a date");
473
+ }
474
+ report.push(`Arrival-Date: ${date.toUTCString()}`);
222
475
  }
223
476
  if (reportingMta) {
224
477
  report.push(`Reporting-MTA: dns; ${clean(reportingMta, "reportingMta")}`);
225
478
  }
226
- return [
479
+ const head = [
227
480
  `From: ${clean(from, "from")}`,
228
481
  `To: ${clean(to, "to")}`,
229
482
  `Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`,
230
- `Subject: ${clean(subject, "subject")}`,
483
+ `Subject: ${encodeWords(clean(subject, "subject"))}`,
231
484
  "MIME-Version: 1.0",
232
485
  `Content-Type: multipart/report; report-type=feedback-report; boundary="${boundary}"`,
233
486
  "",
@@ -246,10 +499,32 @@ function create(options = {}) {
246
499
  "Content-Type: message/rfc822",
247
500
  "Content-Disposition: inline",
248
501
  "",
249
- String(originalMessage).replace(/\r?\n$/, ""),
250
- `--${boundary}--`,
251
502
  ""
252
503
  ].join("\r\n");
504
+ const tail = `\r
505
+ --${boundary}--\r
506
+ `;
507
+ if (Buffer2.isBuffer(originalMessage)) {
508
+ const end = originalMessage.at(-1) === 10 ? originalMessage.at(-2) === 13 ? 2 : 1 : 0;
509
+ return Buffer2.concat([Buffer2.from(head), originalMessage.subarray(0, originalMessage.length - end), Buffer2.from(tail)]);
510
+ }
511
+ return head + String(originalMessage).replace(/\r?\n$/, "") + tail;
512
+ }
513
+ function encodeWords(text) {
514
+ if (/^[\u0020-\u007E]*$/.test(text)) {
515
+ return text;
516
+ }
517
+ const words = [];
518
+ let word = "";
519
+ for (const character of text) {
520
+ if (Buffer2.byteLength(word + character) > 45) {
521
+ words.push(word);
522
+ word = "";
523
+ }
524
+ word += character;
525
+ }
526
+ words.push(word);
527
+ return words.map((part) => `=?UTF-8?B?${Buffer2.from(part).toString("base64")}?=`).join("\r\n ");
253
528
  }
254
529
  var ArfParser = {
255
530
  isArfMessage,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/arf.js"],
4
- "sourcesContent": ["import {Buffer} from 'node:buffer';\nimport {randomBytes} from 'node:crypto';\nimport {isIP} from 'node:net';\nimport {simpleParser} from 'mailparser';\n\n/**\n * Feedback types from RFC 5965, RFC 6591 (auth-failure) and RFC 7489 (dmarc).\n */\nexport const VALID_FEEDBACK_TYPES = new Set(['abuse', 'fraud', 'virus', 'other', 'not-spam', 'auth-failure', 'dmarc']);\n\n// Fields that may appear more than once (RFC 5965 section 3.2).\nconst MULTIPLE = new Set(['original_rcpt_to', 'authentication_results', 'reported_domain', 'reported_uri']);\n\nconst PARSER_OPTIONS = {\n\tskipHtmlToText: true, skipTextToHtml: true, skipImageLinks: true, skipTextLinks: true,\n};\n\n/**\n * Parse the fields of a message/feedback-report part. Folded lines are\n * unfolded. Fields that may repeat are arrays; for other fields the first\n * occurrence wins.\n * @param {string} content\n * @returns {Record<string, string|string[]>}\n */\nexport function parseReportFields(content) {\n\tconst fields = {};\n\tconst unfolded = String(content).replaceAll(/\\r?\\n[ \\t]+/g, ' ');\n\tfor (const line of unfolded.split(/\\r?\\n/)) {\n\t\t// (no \"$\": a line with a line break of its own fails, as before,\n\t\t// without \"\\s*\" and \".*\" trying every split of it)\n\t\tconst match = /^([\\w-]+):(\\s*)(.*)/.exec(line);\n\t\tif (!match || match[0].length !== line.length) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst name = match[1].toLowerCase().replaceAll('-', '_');\n\t\tconst value = match[3].trim();\n\t\tif (MULTIPLE.has(name)) {\n\t\t\t(fields[name] ||= []).push(value);\n\t\t} else if (!(name in fields)) {\n\t\t\tfields[name] = value;\n\t\t}\n\t}\n\n\treturn fields;\n}\n\nfunction extractEmail(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst angle = /<([^<>]*)>/.exec(value);\n\tif (angle) {\n\t\treturn angle[1].trim() || null;\n\t}\n\n\tconst plain = /[^\\s@<>]{1,256}@[^\\s@<>]+/.exec(value);\n\treturn plain ? plain[0] : value.trim();\n}\n\nfunction parseIp(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst candidate = value.trim().replaceAll(/^\\[|]$/g, '');\n\treturn isIP(candidate) ? candidate : null;\n}\n\nfunction parseDate(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst date = new Date(value);\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\nfunction parseReportingMta(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst trimmed = value.trim();\n\tconst match = /^([\\w-]+);\\s*(.+)/.exec(trimmed);\n\treturn match && match[0].length === trimmed.length ? {type: match[1].toLowerCase(), name: match[2].trim()} : {type: 'unknown', name: value.trim()};\n}\n\nfunction decodeQuotedPrintable(text) {\n\tconst bytes = [];\n\tconst soft = text.replaceAll(/=\\r?\\n/g, '');\n\tfor (let i = 0; i < soft.length; i++) {\n\t\tif (soft[i] === '=' && /^[\\da-f]{2}$/i.test(soft.slice(i + 1, i + 3))) {\n\t\t\tbytes.push(Number.parseInt(soft.slice(i + 1, i + 3), 16));\n\t\t\ti += 2;\n\t\t} else {\n\t\t\tbytes.push(soft.codePointAt(i) % 256);\n\t\t}\n\t}\n\n\treturn Buffer.from(bytes);\n}\n\n/**\n * Find a top-level MIME part by content type in a raw multipart message,\n * splitting only at boundary lines. mailparser merges inline message/rfc822\n * parts into the text, so the original message of a report is read here.\n * @param {string} raw\n * @param {string} boundary\n * @param {string[]} types\n * @returns {{type: string, content: Buffer}|null}\n */\nexport function findPart(raw, boundary, types) {\n\tconst escaped = boundary.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`);\n\tconst parts = raw.split(new RegExp(String.raw`\\r?\\n--${escaped}(?:--)?[ \\t]*(?=\\r?\\n|$)`));\n\tfor (const part of parts.slice(1)) {\n\t\t// A part is a header block (possibly empty), a blank line and a body.\n\t\tconst match = /^\\r?\\n((?:[^\\r\\n]+\\r?\\n)*)\\r?\\n([\\s\\S]*)$/.exec(part);\n\t\tif (!match) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst headers = match[1].replaceAll(/\\r?\\n[ \\t]+/g, ' ');\n\t\tconst type = (/^content-type:\\s*([^;\\s]+)/im.exec(headers)?.[1] || 'text/plain').toLowerCase();\n\t\tif (!types.includes(type)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst encoding = (/^content-transfer-encoding:\\s*(\\S+)/im.exec(headers)?.[1] || '').toLowerCase();\n\t\tlet content;\n\t\tif (encoding === 'base64') {\n\t\t\tcontent = Buffer.from(match[2], 'base64');\n\t\t} else if (encoding === 'quoted-printable') {\n\t\t\tcontent = decodeQuotedPrintable(match[2]);\n\t\t} else {\n\t\t\tcontent = Buffer.from(match[2], 'latin1');\n\t\t}\n\n\t\treturn {type, content};\n\t}\n\n\treturn null;\n}\n\n/**\n * Whether a message parsed by mailparser is an ARF report\n * (multipart/report; report-type=feedback-report).\n * @param {object} parsed\n * @returns {boolean}\n */\nexport function isArfMessage(parsed) {\n\tconst header = typeof parsed?.headers?.get === 'function' ? parsed.headers.get('content-type') : null;\n\tif (!header) {\n\t\treturn false;\n\t}\n\n\tconst value = typeof header === 'string' ? header : header.value;\n\tconst type = typeof header === 'string' ? (/report-type\\s*=\\s*\"?([\\w-]+)/i.exec(header)?.[1] || '') : (header.params?.['report-type'] || '');\n\treturn /^multipart\\/report\\b/i.test(String(value)) && type.toLowerCase() === 'feedback-report';\n}\n\n/**\n * Parse an ARF (RFC 5965) abuse report.\n * @param {Buffer|string} source - the raw report message\n * @returns {Promise<object>} feedbackType, userAgent, version, arrivalDate,\n * sourceIp, originalMailFrom, originalRcptTo, reportingMta,\n * originalEnvelopeId, authenticationResults, reportedDomain, reportedUri,\n * incidents, humanReadable, originalMessage, originalHeaders (always an\n * object of lowercase header name to value) and rawFeedbackReport\n * @throws {Error} when the message is not a valid ARF report\n */\nexport async function parse(source) {\n\tconst parsed = await simpleParser(source, PARSER_OPTIONS);\n\tif (!isArfMessage(parsed)) {\n\t\tthrow new Error('Not an ARF report: expected multipart/report with report-type=feedback-report');\n\t}\n\n\tconst report = parsed.attachments.find(part => String(part.contentType).toLowerCase() === 'message/feedback-report');\n\tif (!report) {\n\t\tthrow new Error('Invalid ARF report: missing the message/feedback-report part');\n\t}\n\n\tconst raw = report.content.toString('utf8');\n\tconst fields = parseReportFields(raw);\n\tif (!fields.feedback_type) {\n\t\tthrow new Error('Invalid ARF report: missing the Feedback-Type field');\n\t}\n\n\tif (!fields.user_agent) {\n\t\tthrow new Error('Invalid ARF report: missing the User-Agent field');\n\t}\n\n\tlet feedbackType = fields.feedback_type.toLowerCase();\n\tlet feedbackTypeOriginal = null;\n\tif (!VALID_FEEDBACK_TYPES.has(feedbackType)) {\n\t\tfeedbackTypeOriginal = feedbackType;\n\t\tfeedbackType = 'other';\n\t}\n\n\tconst incidents = Number.parseInt(fields.incidents, 10);\n\tconst result = {\n\t\tisArf: true,\n\t\tfeedbackType,\n\t\tfeedbackTypeOriginal,\n\t\tuserAgent: fields.user_agent,\n\t\tversion: fields.version || '1',\n\t\tarrivalDate: parseDate(fields.arrival_date || fields.received_date),\n\t\tsourceIp: parseIp(fields.source_ip),\n\t\toriginalMailFrom: extractEmail(fields.original_mail_from),\n\t\toriginalRcptTo: fields.original_rcpt_to ? fields.original_rcpt_to.map(value => extractEmail(value)).filter(Boolean) : [],\n\t\treportingMta: parseReportingMta(fields.reporting_mta),\n\t\toriginalEnvelopeId: fields.original_envelope_id || null,\n\t\tauthenticationResults: fields.authentication_results || [],\n\t\treportedDomain: fields.reported_domain || [],\n\t\treportedUri: fields.reported_uri || [],\n\t\tincidents: Number.isFinite(incidents) && incidents > 0 ? incidents : 1,\n\t\thumanReadable: null,\n\t\toriginalMessage: null,\n\t\toriginalHeaders: null,\n\t\trawFeedbackReport: raw,\n\t};\n\n\tconst text = (Buffer.isBuffer(source) ? source : Buffer.from(String(source))).toString('latin1');\n\tconst {boundary} = parsed.headers.get('content-type').params;\n\tconst human = findPart(text, boundary, ['text/plain']);\n\tresult.humanReadable = human ? human.content.toString('utf8').trim() || null : null;\n\tconst original = findPart(text, boundary, ['message/rfc822', 'text/rfc822-headers', 'message/rfc822-headers']);\n\tif (original) {\n\t\tconst content = original.content.toString('utf8');\n\t\tresult.originalMessage = content;\n\t\tconst inner = await simpleParser(original.type === 'message/rfc822' ? content : `${content.trimEnd()}\\r\\n\\r\\n`, PARSER_OPTIONS);\n\t\tresult.originalHeaders = Object.fromEntries([...inner.headers].map(([key, value]) => [key, value]));\n\t}\n\n\treturn result;\n}\n\n/**\n * Parse an ARF report, or return null when the message is not one.\n * @param {Buffer|string} source\n * @returns {Promise<object|null>}\n */\nexport async function tryParse(source) {\n\ttry {\n\t\treturn await parse(source);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction clean(value, name) {\n\tconst text = String(value);\n\tif (/[\\r\\n]/.test(text)) {\n\t\tthrow new TypeError(`${name} must not contain line breaks`);\n\t}\n\n\treturn text;\n}\n\n/**\n * Write an ARF report.\n * @param {object} options\n * @param {string} options.feedbackType - abuse, fraud, virus, other, not-spam, ...\n * @param {string} options.userAgent\n * @param {string} options.from\n * @param {string} options.to\n * @param {string|Buffer} options.originalMessage\n * @param {string} [options.humanReadable]\n * @param {string} [options.subject]\n * @param {string} [options.sourceIp]\n * @param {string} [options.originalMailFrom]\n * @param {string[]} [options.originalRcptTo]\n * @param {Date} [options.arrivalDate]\n * @param {string} [options.reportingMta]\n * @returns {string}\n */\nexport function create(options = {}) {\n\tconst {\n\t\tfeedbackType, userAgent, from, to, originalMessage, humanReadable = 'This is an abuse report for a message received from your network.', subject = 'Abuse report', sourceIp, originalMailFrom, originalRcptTo = [], arrivalDate, reportingMta,\n\t} = options;\n\tif (!feedbackType || !userAgent || !from || !to || !originalMessage) {\n\t\tthrow new TypeError('An ARF report needs feedbackType, userAgent, from, to and originalMessage');\n\t}\n\n\tconst boundary = `arf-${randomBytes(12).toString('hex')}`;\n\tconst report = [\n\t\t`Feedback-Type: ${clean(feedbackType, 'feedbackType')}`,\n\t\t`User-Agent: ${clean(userAgent, 'userAgent')}`,\n\t\t'Version: 1',\n\t];\n\tif (sourceIp) {\n\t\treport.push(`Source-IP: ${clean(sourceIp, 'sourceIp')}`);\n\t}\n\n\tif (originalMailFrom) {\n\t\treport.push(`Original-Mail-From: <${clean(originalMailFrom, 'originalMailFrom')}>`);\n\t}\n\n\tfor (const rcpt of originalRcptTo) {\n\t\treport.push(`Original-Rcpt-To: <${clean(rcpt, 'originalRcptTo')}>`);\n\t}\n\n\tif (arrivalDate) {\n\t\treport.push(`Arrival-Date: ${arrivalDate.toUTCString()}`);\n\t}\n\n\tif (reportingMta) {\n\t\treport.push(`Reporting-MTA: dns; ${clean(reportingMta, 'reportingMta')}`);\n\t}\n\n\treturn [\n\t\t`From: ${clean(from, 'from')}`,\n\t\t`To: ${clean(to, 'to')}`,\n\t\t`Date: ${new Date().toUTCString()}`,\n\t\t`Subject: ${clean(subject, 'subject')}`,\n\t\t'MIME-Version: 1.0',\n\t\t`Content-Type: multipart/report; report-type=feedback-report; boundary=\"${boundary}\"`,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: text/plain; charset=utf-8',\n\t\t'Content-Transfer-Encoding: 8bit',\n\t\t'',\n\t\thumanReadable,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: message/feedback-report',\n\t\t'',\n\t\t...report,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: message/rfc822',\n\t\t'Content-Disposition: inline',\n\t\t'',\n\t\tString(originalMessage).replace(/\\r?\\n$/, ''),\n\t\t`--${boundary}--`,\n\t\t'',\n\t].join('\\r\\n');\n}\n\n/**\n * The ARF parser as an object, as in earlier versions.\n */\nexport const ArfParser = {\n\tisArfMessage, parse, tryParse, create,\n};\n\nexport default ArfParser;\n"],
5
- "mappings": "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",
6
- "names": []
3
+ "sources": ["../../src/arf.js", "../../src/parse.js"],
4
+ "sourcesContent": ["import {Buffer} from 'node:buffer';\nimport {randomBytes} from 'node:crypto';\nimport {isIP} from 'node:net';\nimport {simpleParse} from './parse.js';\n\n/**\n * Feedback types from RFC 5965, RFC 6591 (auth-failure) and RFC 7489 (dmarc).\n */\nexport const VALID_FEEDBACK_TYPES = new Set(['abuse', 'fraud', 'virus', 'other', 'not-spam', 'auth-failure', 'dmarc']);\n\n// Fields that may appear more than once (RFC 5965 section 3.2).\nconst MULTIPLE = new Set(['original_rcpt_to', 'authentication_results', 'reported_domain', 'reported_uri']);\n\n/**\n * Parse the fields of a message/feedback-report part. Folded lines are\n * unfolded. Fields that may repeat are arrays; for other fields the first\n * occurrence wins.\n * @param {string} content\n * @returns {Record<string, string|string[]>}\n */\nexport function parseReportFields(content) {\n\tconst fields = {};\n\tconst unfolded = String(content).replaceAll(/\\r?\\n[ \\t]+/g, ' ');\n\tfor (const line of unfolded.split(/\\r?\\n/)) {\n\t\t// (no \"$\": a line with a line break of its own fails, as before,\n\t\t// without \"\\s*\" and \".*\" trying every split of it)\n\t\tconst match = /^([\\w-]+):(\\s*)(.*)/.exec(line);\n\t\tif (!match || match[0].length !== line.length) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst name = match[1].toLowerCase().replaceAll('-', '_');\n\t\tconst value = match[3].trim();\n\t\tif (MULTIPLE.has(name)) {\n\t\t\t(fields[name] ||= []).push(value);\n\t\t} else if (!(name in fields)) {\n\t\t\tfields[name] = value;\n\t\t}\n\t}\n\n\treturn fields;\n}\n\nfunction extractEmail(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\t// (an address is short: what follows the first 1024 characters is not read)\n\tconst head = value.slice(0, 1024);\n\tconst angle = /<([^<>]*)>/.exec(head);\n\tif (angle) {\n\t\treturn angle[1].trim() || null;\n\t}\n\n\tconst plain = /[^\\s@<>]{1,256}@[^\\s@<>]+/.exec(head);\n\treturn plain ? plain[0] : head.trim();\n}\n\nfunction parseIp(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst candidate = value.trim().replaceAll(/^\\[|]$/g, '');\n\treturn isIP(candidate) ? candidate : null;\n}\n\nfunction parseDate(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst date = new Date(value);\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\nfunction parseReportingMta(value) {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\tconst trimmed = value.trim();\n\tconst match = /^([\\w-]+);\\s*(.+)/.exec(trimmed);\n\treturn match && match[0].length === trimmed.length ? {type: match[1].toLowerCase(), name: match[2].trim()} : {type: 'unknown', name: value.trim()};\n}\n\n// (written into a Buffer: an array of numbers would take many times the\n// memory of a large original message)\nfunction decodeQuotedPrintable(text) {\n\tconst soft = text.replaceAll(/=\\r?\\n/g, '');\n\tconst bytes = Buffer.allocUnsafe(soft.length);\n\tlet length = 0;\n\tfor (let i = 0; i < soft.length; i++) {\n\t\tif (soft[i] === '=' && /^[\\da-f]{2}$/i.test(soft.slice(i + 1, i + 3))) {\n\t\t\tbytes[length++] = Number.parseInt(soft.slice(i + 1, i + 3), 16);\n\t\t\ti += 2;\n\t\t} else {\n\t\t\tbytes[length++] = soft.codePointAt(i) % 256;\n\t\t}\n\t}\n\n\treturn bytes.subarray(0, length);\n}\n\n/**\n * Find a top-level MIME part by content type in a raw multipart message,\n * splitting only at boundary lines. mailparser merges inline message/rfc822\n * parts into the text, so the original message of a report is read here.\n * @param {string} raw\n * @param {string} boundary\n * @param {string[]} types\n * @returns {{type: string, content: Buffer}|null}\n */\nexport function findPart(raw, boundary, types) {\n\tconst escaped = boundary.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`);\n\tconst parts = raw.split(new RegExp(String.raw`\\r?\\n--${escaped}(?:--)?[ \\t]*(?=\\r?\\n|$)`));\n\tfor (const part of parts.slice(1)) {\n\t\t// A part is a header block (possibly empty), a blank line and a body.\n\t\tconst match = /^\\r?\\n((?:[^\\r\\n]+\\r?\\n)*)\\r?\\n([\\s\\S]*)$/.exec(part);\n\t\tif (!match) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst headers = match[1].replaceAll(/\\r?\\n[ \\t]+/g, ' ');\n\t\tconst type = (/^content-type:\\s*([^;\\s]+)/im.exec(headers)?.[1] || 'text/plain').toLowerCase();\n\t\tif (!types.includes(type)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst encoding = (/^content-transfer-encoding:\\s*(\\S+)/im.exec(headers)?.[1] || '').toLowerCase();\n\t\tlet content;\n\t\tif (encoding === 'base64') {\n\t\t\tcontent = Buffer.from(match[2], 'base64');\n\t\t} else if (encoding === 'quoted-printable') {\n\t\t\tcontent = decodeQuotedPrintable(match[2]);\n\t\t} else {\n\t\t\tcontent = Buffer.from(match[2], 'latin1');\n\t\t}\n\n\t\treturn {type, content};\n\t}\n\n\treturn null;\n}\n\n/**\n * Whether a message parsed by mailparser is an ARF report\n * (multipart/report; report-type=feedback-report).\n * @param {object} parsed\n * @returns {boolean}\n */\nexport function isArfMessage(parsed) {\n\tconst header = typeof parsed?.headers?.get === 'function' ? parsed.headers.get('content-type') : null;\n\tif (!header) {\n\t\treturn false;\n\t}\n\n\tconst value = typeof header === 'string' ? header : header.value;\n\tconst type = typeof header === 'string' ? (/report-type\\s*=\\s*\"?([\\w-]+)/i.exec(header)?.[1] || '') : (header.params?.['report-type'] || '');\n\treturn /^multipart\\/report\\b/i.test(String(value)) && type.toLowerCase() === 'feedback-report';\n}\n\n/**\n * Parse an ARF (RFC 5965) abuse report.\n * @param {Buffer|Uint8Array|string} source - the raw report message\n * @returns {Promise<object>} feedbackType, userAgent, version, arrivalDate,\n * sourceIp, originalMailFrom, originalRcptTo, reportingMta,\n * originalEnvelopeId, authenticationResults, reportedDomain, reportedUri,\n * incidents, humanReadable, originalMessage, originalHeaders (an object of\n * lowercase header name to value, or null when the report has no original\n * message) and rawFeedbackReport\n * @throws {Error} when the message is not a valid ARF report\n */\nexport async function parse(message) {\n\t// (a Uint8Array as the bytes it holds, as a Buffer)\n\tconst source = message instanceof Uint8Array && !Buffer.isBuffer(message) ? Buffer.from(message.buffer, message.byteOffset, message.byteLength) : message;\n\tconst parsed = await simpleParse(source);\n\tif (!isArfMessage(parsed)) {\n\t\tthrow new Error('Not an ARF report: expected multipart/report with report-type=feedback-report');\n\t}\n\n\tconst report = parsed.attachments.find(part => String(part.contentType).toLowerCase() === 'message/feedback-report');\n\tif (!report) {\n\t\tthrow new Error('Invalid ARF report: missing the message/feedback-report part');\n\t}\n\n\tconst raw = report.content.toString('utf8');\n\tconst fields = parseReportFields(raw);\n\tif (!fields.feedback_type) {\n\t\tthrow new Error('Invalid ARF report: missing the Feedback-Type field');\n\t}\n\n\tif (!fields.user_agent) {\n\t\tthrow new Error('Invalid ARF report: missing the User-Agent field');\n\t}\n\n\tlet feedbackType = fields.feedback_type.toLowerCase();\n\tlet feedbackTypeOriginal = null;\n\tif (!VALID_FEEDBACK_TYPES.has(feedbackType)) {\n\t\tfeedbackTypeOriginal = feedbackType;\n\t\tfeedbackType = 'other';\n\t}\n\n\tconst incidents = Number.parseInt(fields.incidents, 10);\n\tconst result = {\n\t\tisArf: true,\n\t\tfeedbackType,\n\t\tfeedbackTypeOriginal,\n\t\tuserAgent: fields.user_agent,\n\t\tversion: fields.version || '1',\n\t\tarrivalDate: parseDate(fields.arrival_date || fields.received_date),\n\t\tsourceIp: parseIp(fields.source_ip),\n\t\toriginalMailFrom: extractEmail(fields.original_mail_from),\n\t\toriginalRcptTo: fields.original_rcpt_to ? fields.original_rcpt_to.map(value => extractEmail(value)).filter(Boolean) : [],\n\t\treportingMta: parseReportingMta(fields.reporting_mta),\n\t\toriginalEnvelopeId: fields.original_envelope_id || null,\n\t\tauthenticationResults: fields.authentication_results || [],\n\t\treportedDomain: fields.reported_domain || [],\n\t\treportedUri: fields.reported_uri || [],\n\t\tincidents: Number.isFinite(incidents) && incidents > 0 ? incidents : 1,\n\t\thumanReadable: null,\n\t\toriginalMessage: null,\n\t\toriginalHeaders: null,\n\t\trawFeedbackReport: raw,\n\t};\n\n\tconst text = (Buffer.isBuffer(source) ? source : Buffer.from(String(source))).toString('latin1');\n\tconst {boundary} = parsed.headers.get('content-type').params;\n\tconst human = findPart(text, boundary, ['text/plain']);\n\tresult.humanReadable = human ? human.content.toString('utf8').trim() || null : null;\n\tconst original = findPart(text, boundary, ['message/rfc822', 'text/rfc822-headers', 'message/rfc822-headers']);\n\tif (original) {\n\t\tconst content = original.content.toString('utf8');\n\t\tresult.originalMessage = content;\n\t\tconst inner = await simpleParse(original.type === 'message/rfc822' ? content : `${content.trimEnd()}\\r\\n\\r\\n`);\n\t\tresult.originalHeaders = Object.fromEntries([...inner.headers].map(([key, value]) => [key, value]));\n\t}\n\n\treturn result;\n}\n\n/**\n * Parse an ARF report, or return null when the message is not one.\n * @param {Buffer|Uint8Array|string} source\n * @returns {Promise<object|null>}\n */\nexport async function tryParse(source) {\n\ttry {\n\t\treturn await parse(source);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction clean(value, name) {\n\tconst text = String(value);\n\tif (/[\\r\\n]/.test(text)) {\n\t\tthrow new TypeError(`${name} must not contain line breaks`);\n\t}\n\n\treturn text;\n}\n\n/**\n * Write an ARF report. (A string message gives a string report, and a\n * Buffer a Buffer, its bytes kept as they are, whatever their character set.)\n * @template {string|Buffer} M\n * @param {object} options\n * @param {string} options.feedbackType - abuse, fraud, virus, other, not-spam, ...\n * @param {string} options.userAgent\n * @param {string} options.from\n * @param {string} options.to\n * @param {M} options.originalMessage\n * @param {string} [options.humanReadable]\n * @param {string} [options.subject]\n * @param {string} [options.sourceIp]\n * @param {string} [options.originalMailFrom]\n * @param {string|string[]} [options.originalRcptTo]\n * @param {Date|string|number} [options.arrivalDate]\n * @param {string} [options.reportingMta]\n * @returns {M extends Buffer ? Buffer : string} the report\n */\nexport function create(options = {}) {\n\tconst {\n\t\tfeedbackType, userAgent, from, to, originalMessage, humanReadable = 'This is an abuse report for a message received from your network.', subject = 'Abuse report', sourceIp, originalMailFrom, originalRcptTo = [], arrivalDate, reportingMta,\n\t} = options;\n\tif (!feedbackType || !userAgent || !from || !to || !originalMessage) {\n\t\tthrow new TypeError('An ARF report needs feedbackType, userAgent, from, to and originalMessage');\n\t}\n\n\tconst boundary = `arf-${randomBytes(12).toString('hex')}`;\n\tconst report = [\n\t\t`Feedback-Type: ${clean(feedbackType, 'feedbackType')}`,\n\t\t`User-Agent: ${clean(userAgent, 'userAgent')}`,\n\t\t'Version: 1',\n\t];\n\tif (sourceIp) {\n\t\treport.push(`Source-IP: ${clean(sourceIp, 'sourceIp')}`);\n\t}\n\n\tif (originalMailFrom) {\n\t\treport.push(`Original-Mail-From: <${clean(originalMailFrom, 'originalMailFrom')}>`);\n\t}\n\n\tfor (const rcpt of [originalRcptTo].flat()) {\n\t\treport.push(`Original-Rcpt-To: <${clean(rcpt, 'originalRcptTo')}>`);\n\t}\n\n\tif (arrivalDate) {\n\t\tconst date = new Date(arrivalDate);\n\t\tif (Number.isNaN(date.getTime())) {\n\t\t\tthrow new TypeError('arrivalDate must be a date');\n\t\t}\n\n\t\treport.push(`Arrival-Date: ${date.toUTCString()}`);\n\t}\n\n\tif (reportingMta) {\n\t\treport.push(`Reporting-MTA: dns; ${clean(reportingMta, 'reportingMta')}`);\n\t}\n\n\tconst head = [\n\t\t`From: ${clean(from, 'from')}`,\n\t\t`To: ${clean(to, 'to')}`,\n\t\t`Date: ${new Date().toUTCString()}`,\n\t\t`Subject: ${encodeWords(clean(subject, 'subject'))}`,\n\t\t'MIME-Version: 1.0',\n\t\t`Content-Type: multipart/report; report-type=feedback-report; boundary=\"${boundary}\"`,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: text/plain; charset=utf-8',\n\t\t'Content-Transfer-Encoding: 8bit',\n\t\t'',\n\t\thumanReadable,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: message/feedback-report',\n\t\t'',\n\t\t...report,\n\t\t'',\n\t\t`--${boundary}`,\n\t\t'Content-Type: message/rfc822',\n\t\t'Content-Disposition: inline',\n\t\t'',\n\t\t'',\n\t].join('\\r\\n');\n\tconst tail = `\\r\\n--${boundary}--\\r\\n`;\n\tif (Buffer.isBuffer(originalMessage)) {\n\t\tconst end = originalMessage.at(-1) === 0x0A ? (originalMessage.at(-2) === 0x0D ? 2 : 1) : 0;\n\t\treturn Buffer.concat([Buffer.from(head), originalMessage.subarray(0, originalMessage.length - end), Buffer.from(tail)]);\n\t}\n\n\treturn head + String(originalMessage).replace(/\\r?\\n$/, '') + tail;\n}\n\n// A header value in RFC 2047 encoded words when it is not plain ASCII, in\n// words of at most 45 bytes (whole characters each), so lines stay short\nfunction encodeWords(text) {\n\tif (/^[\\u0020-\\u007E]*$/.test(text)) {\n\t\treturn text;\n\t}\n\n\tconst words = [];\n\tlet word = '';\n\tfor (const character of text) {\n\t\tif (Buffer.byteLength(word + character) > 45) {\n\t\t\twords.push(word);\n\t\t\tword = '';\n\t\t}\n\n\t\tword += character;\n\t}\n\n\twords.push(word);\n\treturn words.map(part => `=?UTF-8?B?${Buffer.from(part).toString('base64')}?=`).join('\\r\\n ');\n}\n\n/**\n * The ARF parser as an object, as in earlier versions.\n */\nexport const ArfParser = {\n\tisArfMessage, parse, tryParse, create,\n};\n\nexport default ArfParser;\n", "import {Buffer} from 'node:buffer';\nimport {createHash} from 'node:crypto';\nimport {Readable} from 'node:stream';\nimport {MailParser, simpleParser} from 'mailparser';\n\n// Address fields, which mailparser (of every MIME part's header) and mailauth\n// (From and Return-Path of the message's header) read with nodemailer's\n// addressparser: its time grows with the square of the number of addresses\n// in a field, and with the length of the field times the number of groups\n// nested in it (\"g:g:g:...\"), so a field of a megabyte took seconds, and\n// minutes in mailauth. Each is read up to this many characters, this many\n// \",\" and \";\" and this many \":\" in a group, and a message's (each attached\n// message's) fields up to this many characters in all, always to the end of\n// a whole address: what is cut is never part of an address, so no other\n// address than one the field gives is read. A field cut gets `truncated`\n// (mail.from.truncated), and a From field cut gives no credit for the\n// sender's authentication (see trustedReputationValues and authenticate).\nconst ADDRESS_FIELDS = new Set(['from', 'to', 'cc', 'bcc', 'sender', 'reply-to', 'delivered-to', 'return-path', 'disposition-notification-to']);\nconst MAX_ADDRESS_FIELD = 16 * 1024;\nconst MAX_ADDRESS_SEPARATORS = 2000;\nconst MAX_ADDRESS_GROUPS = 16;\nconst MAX_ADDRESS_TOTAL = 64 * 1024;\n// (the address fields mailauth reads with addressparser)\nconst AUTH_ADDRESS_FIELDS = new Set(['from', 'return-path']);\n// (the characters addressparser reads as operators, with the one that ends\n// each: in a group, up to its \";\", everything else is text)\nconst ADDRESS_OPERATORS = new Map([['\"', '\"'], ['(', ')'], ['<', '>'], [',', ''], [':', ';'], [';', '']]);\n// (the fields mailparser reads one value of, the priority of any of four)\nconst SINGLE_FIELDS = new Set(['message-id', 'content-id', 'from', 'sender', 'in-reply-to', 'reply-to', 'subject', 'date', 'content-disposition', 'content-type', 'content-transfer-encoding', 'priority', 'mime-version', 'content-description', 'precedence', 'errors-to', 'disposition-notification-to']);\nconst PRIORITY_FIELDS = new Set(['priority', 'x-priority', 'x-msmail-priority', 'importance']);\nconst BOUNDED = Symbol('bounded address fields');\nconst ROOM = Symbol('room left for address fields');\n\n// (no text made from HTML by mailparser: html-to-text takes quadratic time on\n// deeply nested HTML, and fails on HTML nested a few thousand levels deep;\n// getFeatures reads the text HTML shows instead)\nconst PARSER_OPTIONS = {\n\tskipHtmlToText: true, skipImageLinks: true, skipTextToHtml: true, skipTextLinks: true, [BOUNDED]: true,\n};\n\n// A header field \"Name: value\" (folded or not) up to the end of the last\n// whole address within the limits, and at most `room` characters (but its\n// name), as addressparser reads it: an address ends at a \",\" or \";\" outside\n// quotes, a comment, angle brackets and a group, or at the \";\" ending a\n// group. The field itself when it is within them.\nfunction boundAddressField(line, room) {\n\tconst start = line.indexOf(':') + 1;\n\tconst limit = Math.min(line.length, MAX_ADDRESS_FIELD, room);\n\tlet end = start;\n\tlet expecting = '';\n\tlet escaped = false;\n\tlet separators = 0;\n\tlet groups = 0;\n\tlet index = start;\n\tfor (; index < limit; index++) {\n\t\tconst chr = line[index];\n\t\tif ((chr === ',' || chr === ';') && ++separators > MAX_ADDRESS_SEPARATORS) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t} else if (expecting !== '' && chr === expecting) {\n\t\t\texpecting = '';\n\t\t\tend = chr === ';' ? index + 1 : end;\n\t\t} else if (expecting === '' && ADDRESS_OPERATORS.has(chr)) {\n\t\t\texpecting = ADDRESS_OPERATORS.get(chr);\n\t\t\tend = chr === ',' || chr === ';' ? index : end;\n\t\t} else if (expecting === '\"' && chr === '\\\\') {\n\t\t\tescaped = true;\n\t\t}\n\n\t\t// (a group in a group is read again, and again, for each \":\")\n\t\tif (chr === ':' && expecting === ';' && ++groups > MAX_ADDRESS_GROUPS) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn index === line.length ? line : line.slice(0, end).trimEnd();\n}\n\n// Header fields ({key, line}, key the name in lower case) with their address\n// fields bounded (see boundAddressField), `budget.room` characters of them in\n// all, and the names of the fields cut. `only`: just these fields (by\n// default every address field and List-* field)\nfunction boundAddressLines(lines, budget, only = null) {\n\tconst cut = new Set();\n\tconst bounded = lines.map(entry => {\n\t\tif (only ? !only.has(entry.key) : !ADDRESS_FIELDS.has(entry.key) && !entry.key.startsWith('list-')) {\n\t\t\treturn entry;\n\t\t}\n\n\t\tconst line = boundAddressField(entry.line, budget.room);\n\t\tbudget.room = Math.max(budget.room - line.length, 0);\n\t\tif (line === entry.line) {\n\t\t\treturn entry;\n\t\t}\n\n\t\tcut.add(entry.key);\n\t\treturn {...entry, line};\n\t});\n\treturn {lines: bounded, cut};\n}\n\n// Mailparser reads every header (the message's and each part's) with\n// processHeaders: for a parser of parseMessage (only: others are given what\n// they always were), with address fields bounded, a budget for each message,\n// and those cut marked `truncated`. (simpleParser makes its own MailParser,\n// so this is how its headers are reached.)\nconst {processHeaders} = MailParser.prototype;\nMailParser.prototype.processHeaders = function (lines) {\n\tif (!this.options[BOUNDED]) {\n\t\treturn processHeaders.call(this, lines);\n\t}\n\n\tthis[ROOM] ??= {room: MAX_ADDRESS_TOTAL};\n\tconst bounded = boundAddressLines(lines, this[ROOM]);\n\tconst headers = processHeaders.call(this, bounded.lines);\n\t// (of a field given more than once that is read once, the last with a\n\t// value, as mailparser read it before 3.9.40, which reads the first)\n\tconst keyOf = entry => PRIORITY_FIELDS.has(entry.key) ? 'priority' : entry.key;\n\tconst counts = new Map();\n\tfor (const key of bounded.lines.map(entry => keyOf(entry)).filter(key => SINGLE_FIELDS.has(key))) {\n\t\tcounts.set(key, (counts.get(key) || 0) + 1);\n\t}\n\n\tfor (const key of [...counts.keys()].filter(key => counts.get(key) > 1)) {\n\t\tconst last = bounded.lines.findLast(entry => keyOf(entry) === key && processHeaders.call(this, [entry]).has(key));\n\t\tif (last) {\n\t\t\theaders.set(key, processHeaders.call(this, [last]).get(key));\n\t\t}\n\t}\n\n\t// (List-* fields are read into \"list\")\n\tfor (const value of [...bounded.cut].flatMap(key => headers.get(key) ?? [])) {\n\t\tvalue.truncated = true;\n\t}\n\n\treturn headers;\n};\n\n/**\n * The message as mailauth is to read it: its address fields bounded as\n * parseMessage bounds them (mailauth reads From and Return-Path with the same\n * parser), and, when the header would take mailauth longer than linear time\n * (it joins folded lines, and finds the fields a DKIM signature names, in\n * time that grows with the square of their number), the header cut down:\n * its folded lines unfolded (which relaxed canonicalization of DKIM does\n * too) and only the fields that say who sent the message and how, as many\n * as smallerHeader keeps, each up to MAX_FIELD characters (address fields as\n * bounded), and at most MAX_SIGNATURES signatures. Only the first\n * MAX_DKIM_SIGNATURES DKIM-Signature fields are ever kept (with their folded\n * lines; the body is untouched). `fromCut` is whether a\n * From field was cut or left out: then mailauth did not read the From\n * address the message gives.\n * @param {Buffer} raw\n * @returns {{raw: Buffer, fromCut: boolean}}\n */\nexport function boundedForAuth(raw) {\n\t// (where mailauth finds the end of the header, and its fields: a line\n\t// starting with white space continues the one before, and the name is\n\t// up to the first \":\")\n\tconst ends = [raw.indexOf('\\n\\n'), raw.indexOf('\\n\\r\\n')].filter(index => index !== -1);\n\tconst end = ends.length > 0 ? Math.min(...ends) + 1 : raw.length;\n\tconst body = raw.subarray(ends.length > 0 ? raw.indexOf(0x0A, end) + 1 : raw.length);\n\tconst text = raw.subarray(0, end).toString('latin1');\n\tlet {length} = text;\n\twhile (length > 0 && (text[length - 1] === '\\n' || text[length - 1] === '\\r')) {\n\t\tlength--;\n\t}\n\n\tconst fields = [];\n\tlet folds = 0;\n\tlet work = 0;\n\tfor (let start = 0; start <= length;) {\n\t\tlet stop = text.indexOf('\\n', start);\n\t\tstop = stop === -1 || stop > length ? length : stop;\n\t\tconst lineEnd = stop > start && text[stop - 1] === '\\r' ? stop - 1 : stop;\n\t\tconst last = fields.at(-1);\n\t\tif (last && /\\s/.test(text[start])) {\n\t\t\tlast.end = lineEnd;\n\t\t\tlast.folds++;\n\t\t\tfolds++;\n\t\t\t// (each folded line is moved past the fields after it, and joined\n\t\t\t// to the lines after it of its field)\n\t\t\twork += last.folds;\n\t\t} else {\n\t\t\tfields.push({start, end: lineEnd, folds: 0});\n\t\t}\n\n\t\tstart = stop + 1;\n\t}\n\n\tlet signatures = 0;\n\tlet dkim = 0;\n\tfor (const field of fields) {\n\t\tfield.line = text.slice(field.start, field.end);\n\t\tconst colon = field.line.indexOf(':');\n\t\tfield.key = (colon === -1 ? field.line : field.line.slice(0, colon)).trim().toLowerCase();\n\t\tfield.dropped = field.key === 'dkim-signature' && ++dkim > MAX_DKIM_SIGNATURES;\n\t\tif (SIGNATURE_FIELDS.has(field.key) && !field.dropped) {\n\t\t\t// (each field a signature names is looked for among all the fields)\n\t\t\tsignatures += field.line.split(':').length - 1;\n\t\t}\n\t}\n\n\tconst read = dkim > MAX_DKIM_SIGNATURES ? fields.filter(field => !field.dropped) : fields;\n\twork += (folds + signatures) * read.length;\n\t// (only From and Return-Path, the address fields mailauth reads: others,\n\t// To and Cc among them, are often signed, and cutting them would fail a\n\t// signature that holds)\n\tconst bounded = boundAddressLines(read, {room: MAX_ADDRESS_TOTAL}, AUTH_ADDRESS_FIELDS);\n\tlet kept = bounded.lines;\n\tlet fromCut = bounded.cut.has('from');\n\tif (work > MAX_AUTH_WORK) {\n\t\tconst counts = new Map();\n\t\tkept = kept.filter(field => {\n\t\t\tcounts.set(field.key, (counts.get(field.key) || 0) + 1);\n\t\t\tconst keep = AUTH_FIELDS.has(field.key) && counts.get(field.key) <= (SIGNATURE_FIELDS.has(field.key) ? MAX_SIGNATURES : MAX_REPEATED);\n\t\t\tfromCut ||= field.key === 'from' && !keep;\n\t\t\treturn keep;\n\t\t}).map(field => {\n\t\t\tconst line = field.line.replaceAll(/\\r?\\n(?=\\s)/g, '');\n\t\t\treturn {...field, line: ADDRESS_FIELDS.has(field.key) ? line : line.slice(0, MAX_FIELD)};\n\t\t});\n\t} else if (bounded.cut.size === 0 && read === fields) {\n\t\treturn {raw, fromCut};\n\t}\n\n\tconst header = kept.map(field => field.line).join('\\r\\n');\n\treturn {raw: Buffer.concat([Buffer.from(`${header}\\r\\n\\r\\n`, 'latin1'), body]), fromCut};\n}\n\n// The limits of mailparser (of mailsplit, which it splits messages with) that\n// make it fail: more than 1000 MIME parts (an inline attached message is a\n// part too), and a part's header over 1 MB.\nconst TOO_MANY_PARTS = 'Max allowed child nodes exceeded';\nconst HEADER_TOO_LARGE = 'Max header size for a MIME node exceeded';\n// A message beyond them is read up to this many lines that may start a part\n// (\"--boundary\"), and this many bytes of it if a part's header is too large.\nconst MAX_PART_LINES = 990;\nconst MAX_PREFIX_BYTES = 1_000_000;\n// Of a message header too large, each line is read up to this many\n// characters, each field up to this many, and if the whole is still too\n// large, only the fields that say who sent the message and how.\nconst MAX_LINE = 998;\nconst MAX_FIELD = 8192;\nconst MAX_HEADER = 512 * 1024;\nconst MAX_REPEATED = 100;\nconst KEPT_FIELDS = new Set([\n\t'from', 'sender', 'reply-to', 'to', 'cc', 'subject', 'date', 'message-id', 'return-path', 'received', 'authentication-results', 'arc-authentication-results', 'dkim-signature', 'mime-version', 'content-type', 'content-transfer-encoding', 'list-unsubscribe', 'list-id',\n]);\n// Of a header that would take mailauth too long (more than this much work,\n// in its steps: see boundedForAuth), it reads these fields, and at most this\n// many signatures.\nconst MAX_AUTH_WORK = 10_000_000;\nconst SIGNATURE_FIELDS = new Set(['dkim-signature', 'arc-message-signature']);\nconst AUTH_FIELDS = new Set([...KEPT_FIELDS, 'arc-seal', 'arc-message-signature']);\nconst MAX_SIGNATURES = 10;\n/**\n * DKIM-Signature fields mailauth reads of a message (see boundedForAuth): it\n * checks every signature, hashing the body once for each body length (l=)\n * and canonicalization and looking up each signer's key, so hundreds of them\n * would hold up the process and send as many DNS queries. Real mail carries\n * one to three.\n */\nexport const MAX_DKIM_SIGNATURES = 8;\n\n// Attached messages (message/rfc822, .eml) are opened this many levels deep,\n// for at most this many attachments of theirs and this many bytes of attached\n// messages (the message's own attachments are always all read).\nconst MAX_NESTED_DEPTH = 3;\nconst MAX_ATTACHMENTS = 100;\nconst MAX_NESTED_BYTES = 25 * 1024 * 1024;\n\n// Milliseconds a message (with the tries of parseWithin), or all the messages\n// attached to one, may take to parse. mailparser is driven by its own stream\n// events alone, so a parse it never finishes leaves nothing pending: a scan\n// that would never settle, or a process that exits with it unsettled.\nexport const PARSE_TIMEOUT = 60_000;\n\n/**\n * The simpleParser of mailparser on a message it can finish (see finishable),\n * settled within `timeout`: a parse that has not settled by then is stopped\n * (its parser destroyed) and rejected with an ETIMEDOUT error.\n * @param {Buffer|string} raw\n * @param {object} [options]\n * @param {number} [options.timeout] - milliseconds (PARSE_TIMEOUT)\n * @param {typeof simpleParser} [options.parser] - for tests\n * @returns {Promise<object>} the parsed message\n */\nexport async function simpleParse(raw, {timeout = PARSE_TIMEOUT, parser = simpleParser} = {}) {\n\t// (a stream, not the buffer, so a parse that stalls can be stopped:\n\t// simpleParser destroys its parser when its input fails; the input is\n\t// not destroyed when it ends, so it can still fail)\n\tconst input = Readable.from([finishable(unfoldHeaders(raw))], {objectMode: false, autoDestroy: false});\n\tinput.on('error', () => {});\n\t// (and fails it on an error of the splitter that reads its parts, as\n\t// where its limits are met: mailparser before 3.9.30 finishes instead on\n\t// one that comes after the end of its input, as each one does with the\n\t// mailsplit it uses, which splits a line at a time: with what it read so\n\t// far, or never, as a parse that never finishes. Node.js 18 gets 3.9.21\n\t// at most.)\n\tconst {pipe} = input;\n\tinput.pipe = function (parser, options) {\n\t\tparser.splitter?.on('error', error => input.destroy(error));\n\t\treturn pipe.call(this, parser, options);\n\t};\n\n\tlet timer;\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tparser(input, PARSER_OPTIONS),\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tconst error = Object.assign(new Error(`Message not parsed within ${timeout} ms`), {code: 'ETIMEDOUT'});\n\t\t\t\t\tinput.destroy(error);\n\t\t\t\t\treject(error);\n\t\t\t\t}, timeout);\n\t\t\t}),\n\t\t]);\n\t} finally {\n\t\tclearTimeout(timer);\n\t\tinput.destroy();\n\t}\n}\n\n/**\n * Parse a message with mailparser, which does not make text of HTML here:\n * `text` is that of the plain text parts only (empty for a message of HTML\n * alone), and the text HTML shows is read by getFeatures.\n * @param {Buffer|string} raw\n * @param {{timeout?: number}} [options] - milliseconds for the parse (PARSE_TIMEOUT)\n * @returns {Promise<object>} the parsed message\n */\nexport async function parseMessage(raw, options = {}) {\n\tconst buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n\tconst mail = await simpleParse(buffer, options);\n\t// (without the line breaks that adds to the last part)\n\tconst added = buffer.at(-1) === 0x0A ? 2 : 3;\n\tfor (const key of ['text', 'html']) {\n\t\tif (typeof mail[key] === 'string') {\n\t\t\tmail[key] = mail[key].replace(new RegExp(`\\\\n{0,${added}}$`), '');\n\t\t}\n\t}\n\n\t// (and from an attachment left open at the end, as ClamAV and checksums\n\t// read it: only one whose own bytes end the message, so not one closed\n\t// by a boundary, nor one followed by another part or an epilogue, and\n\t// only one not encoded, whose bytes are those of the message)\n\tconst tail = Buffer.from('\\r\\n'.repeat(added));\n\tconst last = mail.attachments?.at(-1);\n\tconst encoding = String(last?.headers?.get?.('content-transfer-encoding') ?? '7bit').trim().toLowerCase();\n\tconst own = Buffer.isBuffer(last?.content) && last.content.length >= tail.length && last.content.subarray(-tail.length).equals(tail) ? last.content.subarray(0, -tail.length) : null;\n\tif (own && ['7bit', '8bit', 'binary'].includes(encoding) && own.length <= buffer.length && buffer.subarray(buffer.length - own.length).equals(own)) {\n\t\tlast.content = own;\n\t\tlast.size = own.length;\n\t\tlast.checksum = createHash('md5').update(own).digest('hex');\n\t}\n\n\treturn mail;\n}\n\n/**\n * A message mailparser can finish: it never finishes one that ends in the\n * header of an attached message (however that header names or folds its\n * type, or only a file name like \"fw.eml\") without the blank line after it,\n * so every message gets one.\n * @param {Buffer|string} raw\n * @returns {Buffer}\n */\nexport function finishable(raw) {\n\tconst buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n\treturn Buffer.concat([buffer, Buffer.from(buffer.at(-1) === 0x0A ? '\\r\\n\\r\\n' : '\\r\\n\\r\\n\\r\\n')]);\n}\n\n// A header field folded on more lines than this is read unfolded: mailsplit\n// before 5.4.18 (of mailparser before 3.9.30, as Node.js 18 gets) joins the\n// lines of a field in time that grows with their number times its length,\n// seconds for a field of a megabyte. Real fields are folded on a few lines; a\n// list of a thousand recipients on a thousand.\nconst MAX_FOLDS = 256;\n\n// The value and parameters of a header field (\"Name: value; key=value\"), as\n// libmime (parseHeaderValue) reads them for mailsplit: `value` lower case.\nfunction headerValue(line) {\n\tconst parameters = new Map();\n\tlet key = null;\n\tlet inKey = false;\n\tlet quoted = false;\n\tlet escaped = false;\n\tlet value = '';\n\tfor (const chr of line.slice(line.indexOf(':') + 1).replaceAll(/\\r?\\n[ \\t]*/g, ' ')) {\n\t\tif (inKey && chr === '=') {\n\t\t\tkey = value.trim().toLowerCase();\n\t\t\tinKey = false;\n\t\t\tvalue = '';\n\t\t} else if (inKey || escaped) {\n\t\t\tvalue += chr;\n\t\t\tescaped = false;\n\t\t} else if (chr === '\\\\') {\n\t\t\tescaped = true;\n\t\t} else if (chr === '\"') {\n\t\t\tquoted = !quoted;\n\t\t} else if (chr === ';' && !quoted) {\n\t\t\tparameters.set(key, value.trim());\n\t\t\tinKey = true;\n\t\t\tvalue = '';\n\t\t} else {\n\t\t\tvalue += chr;\n\t\t}\n\t}\n\n\tif (!inKey) {\n\t\tparameters.set(key, value.trim());\n\t}\n\n\treturn {value: parameters.get(null).toLowerCase(), boundary: parameters.get('boundary') ?? ''};\n}\n\n// Whether the line at raw[start, stop) is a delimiter of `boundary` as\n// mailsplit reads one: 1 \"--boundary\", 2 \"--boundary--\", 0 neither.\nfunction delimiter(raw, start, stop, boundary) {\n\tconst rest = start + 2 + boundary.length;\n\tif (stop - start < boundary.length + 2 || stop - rest > 4 || raw[start] !== 0x2D || raw[start + 1] !== 0x2D || !raw.subarray(start + 2, rest).equals(boundary)) {\n\t\treturn 0;\n\t}\n\n\tif (raw[rest] === 0x0D || raw[rest] === 0x0A) {\n\t\treturn 1;\n\t}\n\n\t// (\"--\", then <CR><LF>, <LF> or nothing)\n\tconst tail = raw.subarray(rest, stop).toString('latin1');\n\treturn /^-{0,2}$|^--\\r?\\n$|^--\\r$/.test(tail) ? 2 : 0;\n}\n\n/**\n * The message with each header field folded on more than MAX_FOLDS lines\n * unfolded: its line breaks and the white space after each made one space,\n * as mailparser reads a field (libmime's decodeHeader), so what it reads is\n * the same. Header fields are those of the message and, as mailsplit finds\n * them, of each MIME part and each attached message inline; everything else\n * is left as it is. The message itself when nothing is unfolded.\n * @param {Buffer} raw\n * @returns {Buffer}\n */\nexport function unfoldHeaders(raw) {\n\tconst boundaries = [];\n\tconst unfold = [];\n\tlet head = true;\n\tlet fields = [];\n\t// (the end of a header: by a blank line, a part follows)\n\tconst endHead = blank => {\n\t\tunfold.push(...fields.filter(field => field.folds > MAX_FOLDS));\n\t\tconst first = new Map();\n\t\tfor (const field of fields) {\n\t\t\tconst name = raw.toString('latin1', field.start, Math.min(field.end, field.start + 32));\n\t\t\tconst key = name.slice(0, name.indexOf(':')).trim().toLowerCase();\n\t\t\tif (['content-type', 'content-transfer-encoding', 'content-disposition'].includes(key) && !first.has(key)) {\n\t\t\t\tfirst.set(key, headerValue(raw.toString('latin1', field.start, field.end)));\n\t\t\t}\n\t\t}\n\n\t\tfields = [];\n\t\tconst type = first.get('content-type') ?? {value: '', boundary: ''};\n\t\tconst encoding = (first.get('content-transfer-encoding')?.value ?? '').replaceAll(/\\(.*\\)/g, '').trim();\n\t\tif (blank && type.boundary) {\n\t\t\tboundaries.push(Buffer.from(type.boundary));\n\t\t}\n\n\t\t// (an attached message inline: its header follows)\n\t\thead = blank && type.value === 'message/rfc822' && ['', '7bit', '8bit', 'binary'].includes(encoding) && first.get('content-disposition')?.value === 'inline';\n\t};\n\n\tfor (let start = 0; start < raw.length;) {\n\t\tconst lf = raw.indexOf(0x0A, start);\n\t\tconst stop = lf === -1 ? raw.length : lf + 1;\n\t\tconst end = lf === -1 ? stop : (raw[lf - 1] === 0x0D ? lf - 1 : lf);\n\t\tconst kind = boundaries.length > 0 ? delimiter(raw, start, stop, boundaries.at(-1)) : 0;\n\t\tif (kind !== 0) {\n\t\t\tif (head) {\n\t\t\t\tendHead(false);\n\t\t\t}\n\n\t\t\t// (a part, or the end of the parts)\n\t\t\thead = kind === 1;\n\t\t\tif (kind === 2) {\n\t\t\t\tboundaries.pop();\n\t\t\t}\n\t\t} else if (head && end === start) {\n\t\t\tendHead(true);\n\t\t} else if (head && fields.length > 0 && (raw[start] === 0x20 || raw[start] === 0x09)) {\n\t\t\tfields.at(-1).end = end;\n\t\t\tfields.at(-1).folds++;\n\t\t} else if (head) {\n\t\t\tfields.push({start, end, folds: 0});\n\t\t}\n\n\t\tstart = stop;\n\t}\n\n\tunfold.push(...fields.filter(field => field.folds > MAX_FOLDS));\n\tif (unfold.length === 0) {\n\t\treturn raw;\n\t}\n\n\tconst chunks = [];\n\tlet copied = 0;\n\tfor (const field of unfold) {\n\t\tchunks.push(raw.subarray(copied, field.start), Buffer.from(raw.toString('latin1', field.start, field.end).replaceAll(/\\r?\\n[ \\t]*/g, ' '), 'latin1'));\n\t\tcopied = field.end;\n\t}\n\n\tchunks.push(raw.subarray(copied));\n\treturn Buffer.concat(chunks);\n}\n\n// The message up to the start of the line at `offset` or before it.\nfunction upToLine(raw, offset) {\n\treturn raw.subarray(0, Math.max(raw.lastIndexOf(0x0A, offset - 1) + 1, 0));\n}\n\n// The message before its MAX_PART_LINES-th line that starts with \"--\", or,\n// with fewer such lines (attached messages inline are parts without one),\n// its first half.\nfunction fewerParts(raw) {\n\tlet offset = -1;\n\tfor (let count = 0; count < MAX_PART_LINES; count++) {\n\t\toffset = raw.indexOf('\\n--', offset + 1);\n\t\tif (offset === -1) {\n\t\t\treturn upToLine(raw, Math.floor(raw.length / 2));\n\t\t}\n\t}\n\n\treturn raw.subarray(0, offset + 1);\n}\n\n// The message with its header (up to the first blank line) cut down to\n// MAX_LINE characters a line, MAX_FIELD a field, and the KEPT_FIELDS when\n// the whole is over MAX_HEADER, so the sender is read whatever comes before;\n// address fields are cut to whole addresses instead (see boundAddressField).\n// `fromCut` is whether a From field was cut or left out.\nfunction smallerHeader(raw) {\n\tconst text = raw.toString('latin1');\n\tconst blank = /\\r?\\n\\r?\\n/.exec(text);\n\tconst end = blank ? blank.index : text.length;\n\tconst fields = [];\n\tlet start = 0;\n\twhile (start < end) {\n\t\tlet stop = text.indexOf('\\n', start);\n\t\tstop = stop === -1 || stop > end ? end : stop;\n\t\tconst last = fields.at(-1);\n\t\tconst whole = text.slice(start, stop).replace(/\\r$/, '');\n\t\tif (last && (whole.startsWith(' ') || whole.startsWith('\\t'))) {\n\t\t\tconst line = last.address ? whole : whole.slice(0, MAX_LINE);\n\t\t\tif (last.address || last.size + line.length <= MAX_FIELD) {\n\t\t\t\tlast.lines.push(line);\n\t\t\t\tlast.size += line.length;\n\t\t\t}\n\t\t} else {\n\t\t\tconst name = whole.slice(0, whole.indexOf(':')).trim().toLowerCase();\n\t\t\tconst address = ADDRESS_FIELDS.has(name) || name.startsWith('list-');\n\t\t\tconst line = address ? whole : whole.slice(0, MAX_LINE);\n\t\t\tfields.push({\n\t\t\t\tname, address, lines: [line], size: line.length,\n\t\t\t});\n\t\t}\n\n\t\tstart = stop + 1;\n\t}\n\n\tlet fromCut = false;\n\tfor (const field of fields.filter(field => field.address)) {\n\t\tconst line = field.lines.join('\\r\\n');\n\t\tconst bounded = boundAddressField(line, Number.POSITIVE_INFINITY);\n\t\tfromCut ||= field.name === 'from' && bounded !== line;\n\t\tfield.lines = [bounded];\n\t\tfield.size = bounded.length;\n\t}\n\n\tconst total = fields.reduce((sum, field) => sum + field.size, 0);\n\t// (and at most MAX_REPEATED of each, as hundreds of Received fields could\n\t// still be too many)\n\tconst counts = new Map();\n\tconst kept = total > MAX_HEADER\n\t\t? fields.filter(field => {\n\t\t\tcounts.set(field.name, (counts.get(field.name) || 0) + 1);\n\t\t\tconst keep = KEPT_FIELDS.has(field.name) && counts.get(field.name) <= MAX_REPEATED;\n\t\t\tfromCut ||= field.name === 'from' && !keep;\n\t\t\treturn keep;\n\t\t})\n\t\t: fields;\n\tconst header = kept.flatMap(field => field.lines).join('\\r\\n');\n\treturn {raw: Buffer.concat([Buffer.from(`${header}\\r\\n\\r\\n`, 'latin1'), raw.subarray(blank ? blank.index + blank[0].length : raw.length)]), fromCut};\n}\n\n/**\n * Parse a message, reading what can be read of one beyond mailparser's\n * limits (more than 1000 MIME parts, a part's header over 1 MB): its parts\n * before the 990th line that may start one (or its first half, again and\n * again, when attached messages inline make the parts), and, when a header\n * is too large, the message with its header lines cut down, or its first\n * megabyte when the header is that of a later part. `error` is the limit,\n * or null. Other errors are thrown, as earlier versions did (servers defer\n * the message), and so is a parse not finished by `deadline` (ETIMEDOUT).\n * @param {Buffer} raw\n * @param {(raw: Buffer, options: {timeout: number}) => Promise<object>} [parse] - for tests\n * @param {number} [deadline] - Date.now() milliseconds by which every try\n * ends (PARSE_TIMEOUT from now)\n * @returns {Promise<{mail: object, error: string|null}>}\n */\nexport async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PARSE_TIMEOUT) {\n\tlet input = raw;\n\tlet reason = null;\n\tlet fromCut = false;\n\tfor (;;) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\tconst mail = await parse(input, {timeout: Math.max(1, deadline - Date.now())});\n\t\t\t// (a From field cut down is marked as parseMessage marks one)\n\t\t\tif (fromCut && mail.from) {\n\t\t\t\tmail.from.truncated = true;\n\t\t\t}\n\n\t\t\treturn {mail, error: reason};\n\t\t} catch (error) {\n\t\t\tconst limit = error.code === 'EMAXLEN' && [TOO_MANY_PARTS, HEADER_TOO_LARGE].includes(error.message) ? error.message : null;\n\t\t\tlet shorter = input;\n\t\t\tif (limit === TOO_MANY_PARTS) {\n\t\t\t\tshorter = fewerParts(input);\n\t\t\t} else if (limit === HEADER_TOO_LARGE) {\n\t\t\t\tconst smaller = smallerHeader(input);\n\t\t\t\tshorter = smaller.raw.length < input.length ? smaller.raw : upToLine(input, MAX_PREFIX_BYTES);\n\t\t\t\tfromCut ||= smaller.raw === shorter && smaller.fromCut;\n\t\t\t}\n\n\t\t\t// (each try reads less, so this ends)\n\t\t\tif (shorter.length >= input.length) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treason ??= limit;\n\t\t\tinput = shorter;\n\t\t}\n\t}\n}\n\n/**\n * A message's attachments, with those of the messages attached to it (as a\n * forwarded message is), named after the message that carries them:\n * \"fw.eml/invoice.exe\". An attached message that cannot be read (beyond the\n * parser's limits, or failing it) never fails the scan: why goes to `errors`.\n * @param {Array<object>} attachments - mailparser attachments\n * @param {string[]} [errors] - gets why attached messages were not read whole\n * @param {(raw: Buffer) => Promise<object>} [parse] - for tests\n * @returns {Promise<Array<object>>}\n */\nexport async function expandAttachments(attachments = [], errors = [], parse = parseMessage) {\n\tconst all = [];\n\t// (one time limit for all of them)\n\tconst deadline = Date.now() + PARSE_TIMEOUT;\n\tlet budget = MAX_NESTED_BYTES;\n\tlet nested = 0;\n\tconst visit = async (list, prefix, depth) => {\n\t\tfor (const attachment of list) {\n\t\t\tif (prefix) {\n\t\t\t\tif (nested >= MAX_ATTACHMENTS) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tnested++;\n\t\t\t}\n\n\t\t\tconst name = attachment.filename || (prefix ? 'unnamed attachment' : '');\n\t\t\tall.push(prefix ? {...attachment, filename: `${prefix}/${name}`} : attachment);\n\t\t\tconst {content} = attachment;\n\t\t\tconst attached = attachment.contentType === 'message/rfc822' || /\\.eml$/i.test(attachment.filename || '');\n\t\t\tif (attached && depth < MAX_NESTED_DEPTH && Buffer.isBuffer(content) && content.length <= budget) {\n\t\t\t\tbudget -= content.length;\n\t\t\t\tlet inner;\n\t\t\t\ttry {\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tinner = await parseWithin(content, parse, deadline);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// (7.0.3 did not open attached messages, and one never failed it)\n\t\t\t\t\terrors.push(`${name || 'attached message'}: ${error.message}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (inner.error) {\n\t\t\t\t\terrors.push(inner.error);\n\t\t\t\t}\n\n\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\tawait visit(inner.mail.attachments, prefix ? `${prefix}/${name}` : (name || 'attached message'), depth + 1);\n\t\t\t}\n\t\t}\n\t};\n\n\tawait visit(attachments, '', 0);\n\treturn all;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Buffer", "key", "parser", "Buffer"]
7
7
  }