spamscanner 7.0.2 → 7.0.4

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/cjs/arf.cjs CHANGED
@@ -31,10 +31,23 @@ __export(arf_exports, {
31
31
  tryParse: () => tryParse
32
32
  });
33
33
  module.exports = __toCommonJS(arf_exports);
34
+ var import_node_buffer2 = require("node:buffer");
35
+ var import_node_crypto2 = require("node:crypto");
36
+ var import_node_net = require("node:net");
37
+ var import_mailparser2 = require("mailparser");
38
+
39
+ // src/parse.js
34
40
  var import_node_buffer = require("node:buffer");
35
41
  var import_node_crypto = require("node:crypto");
36
- var import_node_net = require("node:net");
37
42
  var import_mailparser = require("mailparser");
43
+ var MAX_HEADER = 512 * 1024;
44
+ var MAX_NESTED_BYTES = 25 * 1024 * 1024;
45
+ function finishable(raw) {
46
+ const buffer = import_node_buffer.Buffer.isBuffer(raw) ? raw : import_node_buffer.Buffer.from(raw);
47
+ return import_node_buffer.Buffer.concat([buffer, import_node_buffer.Buffer.from(buffer.at(-1) === 10 ? "\r\n\r\n" : "\r\n\r\n\r\n")]);
48
+ }
49
+
50
+ // src/arf.js
38
51
  var VALID_FEEDBACK_TYPES = /* @__PURE__ */ new Set(["abuse", "fraud", "virus", "other", "not-spam", "auth-failure", "dmarc"]);
39
52
  var MULTIPLE = /* @__PURE__ */ new Set(["original_rcpt_to", "authentication_results", "reported_domain", "reported_uri"]);
40
53
  var PARSER_OPTIONS = {
@@ -65,12 +78,13 @@ function extractEmail(value) {
65
78
  if (!value) {
66
79
  return null;
67
80
  }
68
- const angle = /<([^<>]*)>/.exec(value);
81
+ const head = value.slice(0, 1024);
82
+ const angle = /<([^<>]*)>/.exec(head);
69
83
  if (angle) {
70
84
  return angle[1].trim() || null;
71
85
  }
72
- const plain = /[^\s@<>]{1,256}@[^\s@<>]+/.exec(value);
73
- return plain ? plain[0] : value.trim();
86
+ const plain = /[^\s@<>]{1,256}@[^\s@<>]+/.exec(head);
87
+ return plain ? plain[0] : head.trim();
74
88
  }
75
89
  function parseIp(value) {
76
90
  if (!value) {
@@ -95,17 +109,18 @@ function parseReportingMta(value) {
95
109
  return match && match[0].length === trimmed.length ? { type: match[1].toLowerCase(), name: match[2].trim() } : { type: "unknown", name: value.trim() };
96
110
  }
97
111
  function decodeQuotedPrintable(text) {
98
- const bytes = [];
99
112
  const soft = text.replaceAll(/=\r?\n/g, "");
113
+ const bytes = import_node_buffer2.Buffer.allocUnsafe(soft.length);
114
+ let length = 0;
100
115
  for (let i = 0; i < soft.length; i++) {
101
116
  if (soft[i] === "=" && /^[\da-f]{2}$/i.test(soft.slice(i + 1, i + 3))) {
102
- bytes.push(Number.parseInt(soft.slice(i + 1, i + 3), 16));
117
+ bytes[length++] = Number.parseInt(soft.slice(i + 1, i + 3), 16);
103
118
  i += 2;
104
119
  } else {
105
- bytes.push(soft.codePointAt(i) % 256);
120
+ bytes[length++] = soft.codePointAt(i) % 256;
106
121
  }
107
122
  }
108
- return import_node_buffer.Buffer.from(bytes);
123
+ return bytes.subarray(0, length);
109
124
  }
110
125
  function findPart(raw, boundary, types) {
111
126
  const escaped = boundary.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -123,11 +138,11 @@ function findPart(raw, boundary, types) {
123
138
  const encoding = (/^content-transfer-encoding:\s*(\S+)/im.exec(headers)?.[1] || "").toLowerCase();
124
139
  let content;
125
140
  if (encoding === "base64") {
126
- content = import_node_buffer.Buffer.from(match[2], "base64");
141
+ content = import_node_buffer2.Buffer.from(match[2], "base64");
127
142
  } else if (encoding === "quoted-printable") {
128
143
  content = decodeQuotedPrintable(match[2]);
129
144
  } else {
130
- content = import_node_buffer.Buffer.from(match[2], "latin1");
145
+ content = import_node_buffer2.Buffer.from(match[2], "latin1");
131
146
  }
132
147
  return { type, content };
133
148
  }
@@ -143,7 +158,7 @@ function isArfMessage(parsed) {
143
158
  return /^multipart\/report\b/i.test(String(value)) && type.toLowerCase() === "feedback-report";
144
159
  }
145
160
  async function parse(source) {
146
- const parsed = await (0, import_mailparser.simpleParser)(source, PARSER_OPTIONS);
161
+ const parsed = await (0, import_mailparser2.simpleParser)(finishable(source), PARSER_OPTIONS);
147
162
  if (!isArfMessage(parsed)) {
148
163
  throw new Error("Not an ARF report: expected multipart/report with report-type=feedback-report");
149
164
  }
@@ -187,7 +202,7 @@ async function parse(source) {
187
202
  originalHeaders: null,
188
203
  rawFeedbackReport: raw
189
204
  };
190
- const text = (import_node_buffer.Buffer.isBuffer(source) ? source : import_node_buffer.Buffer.from(String(source))).toString("latin1");
205
+ const text = (import_node_buffer2.Buffer.isBuffer(source) ? source : import_node_buffer2.Buffer.from(String(source))).toString("latin1");
191
206
  const { boundary } = parsed.headers.get("content-type").params;
192
207
  const human = findPart(text, boundary, ["text/plain"]);
193
208
  result.humanReadable = human ? human.content.toString("utf8").trim() || null : null;
@@ -195,9 +210,9 @@ async function parse(source) {
195
210
  if (original) {
196
211
  const content = original.content.toString("utf8");
197
212
  result.originalMessage = content;
198
- const inner = await (0, import_mailparser.simpleParser)(original.type === "message/rfc822" ? content : `${content.trimEnd()}\r
213
+ const inner = await (0, import_mailparser2.simpleParser)(finishable(original.type === "message/rfc822" ? content : `${content.trimEnd()}\r
199
214
  \r
200
- `, PARSER_OPTIONS);
215
+ `), PARSER_OPTIONS);
201
216
  result.originalHeaders = Object.fromEntries([...inner.headers].map(([key, value]) => [key, value]));
202
217
  }
203
218
  return result;
@@ -234,7 +249,7 @@ function create(options = {}) {
234
249
  if (!feedbackType || !userAgent || !from || !to || !originalMessage) {
235
250
  throw new TypeError("An ARF report needs feedbackType, userAgent, from, to and originalMessage");
236
251
  }
237
- const boundary = `arf-${(0, import_node_crypto.randomBytes)(12).toString("hex")}`;
252
+ const boundary = `arf-${(0, import_node_crypto2.randomBytes)(12).toString("hex")}`;
238
253
  const report = [
239
254
  `Feedback-Type: ${clean(feedbackType, "feedbackType")}`,
240
255
  `User-Agent: ${clean(userAgent, "userAgent")}`,
@@ -246,20 +261,24 @@ function create(options = {}) {
246
261
  if (originalMailFrom) {
247
262
  report.push(`Original-Mail-From: <${clean(originalMailFrom, "originalMailFrom")}>`);
248
263
  }
249
- for (const rcpt of originalRcptTo) {
264
+ for (const rcpt of [originalRcptTo].flat()) {
250
265
  report.push(`Original-Rcpt-To: <${clean(rcpt, "originalRcptTo")}>`);
251
266
  }
252
267
  if (arrivalDate) {
253
- report.push(`Arrival-Date: ${arrivalDate.toUTCString()}`);
268
+ const date = new Date(arrivalDate);
269
+ if (Number.isNaN(date.getTime())) {
270
+ throw new TypeError("arrivalDate must be a date");
271
+ }
272
+ report.push(`Arrival-Date: ${date.toUTCString()}`);
254
273
  }
255
274
  if (reportingMta) {
256
275
  report.push(`Reporting-MTA: dns; ${clean(reportingMta, "reportingMta")}`);
257
276
  }
258
- return [
277
+ const head = [
259
278
  `From: ${clean(from, "from")}`,
260
279
  `To: ${clean(to, "to")}`,
261
280
  `Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`,
262
- `Subject: ${clean(subject, "subject")}`,
281
+ `Subject: ${encodeWords(clean(subject, "subject"))}`,
263
282
  "MIME-Version: 1.0",
264
283
  `Content-Type: multipart/report; report-type=feedback-report; boundary="${boundary}"`,
265
284
  "",
@@ -278,10 +297,32 @@ function create(options = {}) {
278
297
  "Content-Type: message/rfc822",
279
298
  "Content-Disposition: inline",
280
299
  "",
281
- String(originalMessage).replace(/\r?\n$/, ""),
282
- `--${boundary}--`,
283
300
  ""
284
301
  ].join("\r\n");
302
+ const tail = `\r
303
+ --${boundary}--\r
304
+ `;
305
+ if (import_node_buffer2.Buffer.isBuffer(originalMessage)) {
306
+ const end = originalMessage.at(-1) === 10 ? originalMessage.at(-2) === 13 ? 2 : 1 : 0;
307
+ return import_node_buffer2.Buffer.concat([import_node_buffer2.Buffer.from(head), originalMessage.subarray(0, originalMessage.length - end), import_node_buffer2.Buffer.from(tail)]);
308
+ }
309
+ return head + String(originalMessage).replace(/\r?\n$/, "") + tail;
310
+ }
311
+ function encodeWords(text) {
312
+ if (/^[\u0020-\u007E]*$/.test(text)) {
313
+ return text;
314
+ }
315
+ const words = [];
316
+ let word = "";
317
+ for (const character of text) {
318
+ if (import_node_buffer2.Buffer.byteLength(word + character) > 45) {
319
+ words.push(word);
320
+ word = "";
321
+ }
322
+ word += character;
323
+ }
324
+ words.push(word);
325
+ return words.map((part) => `=?UTF-8?B?${import_node_buffer2.Buffer.from(part).toString("base64")}?=`).join("\r\n ");
285
326
  }
286
327
  var ArfParser = {
287
328
  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 {simpleParser} from 'mailparser';\nimport {finishable} 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\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\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|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(source) {\n\tconst parsed = await simpleParser(finishable(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(finishable(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. (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 {simpleParser} from 'mailparser';\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,\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\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/**\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 * @returns {Promise<object>} the parsed message\n */\nexport async function parseMessage(raw) {\n\tconst buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n\tconst mail = await simpleParser(finishable(buffer), PARSER_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// 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.\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 line = text.slice(start, Math.min(stop, start + MAX_LINE)).replace(/\\r$/, '');\n\t\tconst last = fields.at(-1);\n\t\tif (last && (line.startsWith(' ') || line.startsWith('\\t'))) {\n\t\t\tif (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\tfields.push({name: line.slice(0, line.indexOf(':')).trim().toLowerCase(), lines: [line], size: line.length});\n\t\t}\n\n\t\tstart = stop + 1;\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\treturn KEPT_FIELDS.has(field.name) && counts.get(field.name) <= MAX_REPEATED;\n\t\t})\n\t\t: fields;\n\tconst header = kept.flatMap(field => field.lines).join('\\r\\n');\n\treturn Buffer.concat([Buffer.from(`${header}\\r\\n\\r\\n`, 'latin1'), raw.subarray(blank ? blank.index + blank[0].length : raw.length)]);\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).\n * @param {Buffer} raw\n * @param {(raw: Buffer) => Promise<object>} [parse] - for tests\n * @returns {Promise<{mail: object, error: string|null}>}\n */\nexport async function parseWithin(raw, parse = parseMessage) {\n\tlet input = raw;\n\tlet reason = null;\n\tfor (;;) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\treturn {mail: await parse(input), 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\tshorter = smallerHeader(input);\n\t\t\t\tshorter = shorter.length < input.length ? shorter : upToLine(input, MAX_PREFIX_BYTES);\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\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);\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": ["import_node_buffer", "import_node_crypto", "import_mailparser"]
7
7
  }