spamscanner 7.0.13 → 7.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/arf.cjs +1261 -30
- package/dist/cjs/arf.cjs.map +4 -4
- package/dist/cjs/index.cjs +176 -23
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/esm/arf.js +1262 -30
- package/dist/esm/arf.js.map +4 -4
- package/dist/esm/cli.js +3227 -3074
- package/dist/esm/cli.js.map +4 -4
- package/dist/esm/index.js +172 -21
- package/dist/esm/index.js.map +3 -3
- package/dist/types/attachments.d.ts +2 -1
- package/dist/types/parse.d.ts +14 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -4022,6 +4022,13 @@ function extensionOf(name) {
|
|
|
4022
4022
|
}
|
|
4023
4023
|
return whole;
|
|
4024
4024
|
}
|
|
4025
|
+
function uncompressedName(name) {
|
|
4026
|
+
if (!COMPRESSED_EXTENSIONS.has(extensionOf(name))) {
|
|
4027
|
+
return String(name);
|
|
4028
|
+
}
|
|
4029
|
+
const trimmed = trimEndOf(String(name).replace(ALL_BIDI_CONTROLS, ""), ". ");
|
|
4030
|
+
return trimmed.slice(0, trimmed.lastIndexOf("."));
|
|
4031
|
+
}
|
|
4025
4032
|
function lastExtension(base) {
|
|
4026
4033
|
const dot = base.lastIndexOf(".");
|
|
4027
4034
|
return dot === -1 ? "" : base.slice(dot + 1).trim();
|
|
@@ -4065,7 +4072,20 @@ function inspectAttachments(attachments = []) {
|
|
|
4065
4072
|
} else if (RISKY_EXTENSIONS.has(extension)) {
|
|
4066
4073
|
add("risky_attachment", `Attachment "${name}" is a risky file type (.${extension})`, { extension });
|
|
4067
4074
|
}
|
|
4075
|
+
const held = COMPRESSED_EXTENSIONS.has(extension) ? extensionOf(uncompressedName(filename)) : "";
|
|
4076
|
+
if (EXECUTABLE_EXTENSIONS.has(held)) {
|
|
4077
|
+
const script2 = SCRIPT_EXTENSIONS.has(held);
|
|
4078
|
+
add(script2 ? "script_in_archive" : "executable_in_archive", `Archive "${name}" contains ${script2 ? "a script" : "a program"} (${shorten(uncompressedName(filename).split(/[/\\]/).pop())})`, { extension: held, extensions: [held] });
|
|
4079
|
+
}
|
|
4080
|
+
const webPage = detected?.type === "html" || detected?.type === "svg" || ["htm", "html", "mht", "mhtml", "shtml", "xht", "xhtml", "svg"].includes(extension);
|
|
4081
|
+
const addWebPage = () => {
|
|
4082
|
+
const active = content !== null && /<script\b|<form\b|\bon(?:load|error|click)\s*=|javascript:|window\.location|document\.location|atob\(/i.test(content.subarray(0, 2e5).toString("utf8"));
|
|
4083
|
+
add("html_attachment", `Attachment "${name}" is ${detected?.type === "svg" || extension === "svg" ? "an SVG image" : "a web page"}${active ? " with scripts or forms" : ""}`, { extension: extension || detected?.type, active });
|
|
4084
|
+
};
|
|
4068
4085
|
if (!content) {
|
|
4086
|
+
if (webPage) {
|
|
4087
|
+
addWebPage();
|
|
4088
|
+
}
|
|
4069
4089
|
continue;
|
|
4070
4090
|
}
|
|
4071
4091
|
const zipFamily = ZIP_FAMILY.has(detected?.type) || extension === "zip" && !detected?.executable && entriesOf(content).length > 0;
|
|
@@ -4077,10 +4097,10 @@ function inspectAttachments(attachments = []) {
|
|
|
4077
4097
|
} else if (office && entries.some((entry) => MACRO_EXTENSIONS.has(extensionOf(entry.name)))) {
|
|
4078
4098
|
add("macro", `Attachment "${name}" is an Office document holding another with macros`, { extension });
|
|
4079
4099
|
}
|
|
4080
|
-
const inner = entries.filter((entry) => EXECUTABLE_EXTENSIONS.has(extensionOf(entry.name)) && !(office && OFFICE_EMBEDDED.has(extensionOf(entry.name))));
|
|
4081
|
-
const details = { extension: extensionOf(inner[0]?.name ?? ""), extensions: [...new Set(inner.map((entry) => extensionOf(entry.name)))] };
|
|
4100
|
+
const inner = entries.filter((entry) => EXECUTABLE_EXTENSIONS.has(extensionOf(uncompressedName(entry.name))) && !(office && OFFICE_EMBEDDED.has(extensionOf(entry.name))));
|
|
4101
|
+
const details = { extension: extensionOf(uncompressedName(inner[0]?.name ?? "")), extensions: [...new Set(inner.map((entry) => extensionOf(uncompressedName(entry.name))))] };
|
|
4082
4102
|
const listed = inner.slice(0, 3).map((entry) => shorten(entry.name.split("/").pop())).join(", ");
|
|
4083
|
-
if (inner.some((entry) => !SCRIPT_EXTENSIONS.has(extensionOf(entry.name)))) {
|
|
4103
|
+
if (inner.some((entry) => !SCRIPT_EXTENSIONS.has(extensionOf(uncompressedName(entry.name))))) {
|
|
4084
4104
|
add("executable_in_archive", `Archive "${name}" contains ${inner.length === 1 ? "a program" : `${inner.length} programs`} (${listed})`, details);
|
|
4085
4105
|
} else if (inner.length > 0) {
|
|
4086
4106
|
add("script_in_archive", `Archive "${name}" contains ${inner.length === 1 ? "a script" : `${inner.length} scripts`} (${listed})`, details);
|
|
@@ -4099,15 +4119,13 @@ function inspectAttachments(attachments = []) {
|
|
|
4099
4119
|
} else if (detected?.type === "rtf" && /\\object\b|\\objdata\b|\\objupdate\b/.test(content.toString("latin1"))) {
|
|
4100
4120
|
add("rtf_object", `RTF document "${name}" embeds an OLE object`, { extension: "rtf" });
|
|
4101
4121
|
}
|
|
4102
|
-
if (
|
|
4103
|
-
|
|
4104
|
-
const active = /<script\b|<form\b|\bon(?:load|error|click)\s*=|javascript:|window\.location|document\.location|atob\(/i.test(text);
|
|
4105
|
-
add("html_attachment", `Attachment "${name}" is ${detected?.type === "svg" || extension === "svg" ? "an SVG image" : "a web page"}${active ? " with scripts or forms" : ""}`, { extension: extension || detected?.type, active });
|
|
4122
|
+
if (webPage) {
|
|
4123
|
+
addWebPage();
|
|
4106
4124
|
}
|
|
4107
4125
|
}
|
|
4108
4126
|
return findings;
|
|
4109
4127
|
}
|
|
4110
|
-
var import_node_buffer, EXECUTABLE_EXTENSIONS, RISKY_EXTENSIONS, SCRIPT_EXTENSIONS, MACRO_EXTENSIONS, DOCUMENT_EXTENSIONS, TEMPORARY_EXTENSIONS, BIDI_CONTROLS, ALL_BIDI_CONTROLS, ARCHIVE_TYPES, ZIP_FAMILY, OFFICE_TYPES, OFFICE_EMBEDDED, ENTRIES, MAX_NAME_LENGTH, MAX_NAME_BYTES, DIRECTORY, isDirectory, VBA_NAMES, PDF_ACTIVE, escaped, WORD_ESCAPE, PDF_ACTIVE_ESCAPED, pdfActive;
|
|
4128
|
+
var import_node_buffer, EXECUTABLE_EXTENSIONS, RISKY_EXTENSIONS, SCRIPT_EXTENSIONS, MACRO_EXTENSIONS, DOCUMENT_EXTENSIONS, TEMPORARY_EXTENSIONS, BIDI_CONTROLS, ALL_BIDI_CONTROLS, ARCHIVE_TYPES, ZIP_FAMILY, OFFICE_TYPES, OFFICE_EMBEDDED, ENTRIES, MAX_NAME_LENGTH, MAX_NAME_BYTES, DIRECTORY, isDirectory, COMPRESSED_EXTENSIONS, VBA_NAMES, PDF_ACTIVE, escaped, WORD_ESCAPE, PDF_ACTIVE_ESCAPED, pdfActive;
|
|
4111
4129
|
var init_attachments = __esm({
|
|
4112
4130
|
"src/attachments.js"() {
|
|
4113
4131
|
import_node_buffer = require("node:buffer");
|
|
@@ -4119,6 +4137,7 @@ var init_attachments = __esm({
|
|
|
4119
4137
|
"apk",
|
|
4120
4138
|
"app",
|
|
4121
4139
|
"appimage",
|
|
4140
|
+
"appinstaller",
|
|
4122
4141
|
"application",
|
|
4123
4142
|
"appref-ms",
|
|
4124
4143
|
"appx",
|
|
@@ -4155,6 +4174,7 @@ var init_attachments = __esm({
|
|
|
4155
4174
|
"inf",
|
|
4156
4175
|
"ins",
|
|
4157
4176
|
"ipa",
|
|
4177
|
+
"iqy",
|
|
4158
4178
|
"iso",
|
|
4159
4179
|
"isp",
|
|
4160
4180
|
"its",
|
|
@@ -4163,6 +4183,7 @@ var init_attachments = __esm({
|
|
|
4163
4183
|
"js",
|
|
4164
4184
|
"jse",
|
|
4165
4185
|
"ksh",
|
|
4186
|
+
"library-ms",
|
|
4166
4187
|
"lnk",
|
|
4167
4188
|
"mad",
|
|
4168
4189
|
"maf",
|
|
@@ -4194,6 +4215,8 @@ var init_attachments = __esm({
|
|
|
4194
4215
|
"mst",
|
|
4195
4216
|
"msu",
|
|
4196
4217
|
"nsh",
|
|
4218
|
+
"one",
|
|
4219
|
+
"onepkg",
|
|
4197
4220
|
"ops",
|
|
4198
4221
|
"osd",
|
|
4199
4222
|
"pcd",
|
|
@@ -4224,6 +4247,8 @@ var init_attachments = __esm({
|
|
|
4224
4247
|
"scpt",
|
|
4225
4248
|
"scr",
|
|
4226
4249
|
"sct",
|
|
4250
|
+
"search-ms",
|
|
4251
|
+
"searchconnector-ms",
|
|
4227
4252
|
"settingcontent-ms",
|
|
4228
4253
|
"sh",
|
|
4229
4254
|
"shb",
|
|
@@ -4268,6 +4293,7 @@ var init_attachments = __esm({
|
|
|
4268
4293
|
MAX_NAME_BYTES = 1024 * 1024;
|
|
4269
4294
|
DIRECTORY = 33639248;
|
|
4270
4295
|
isDirectory = (buffer, offset) => offset >= 0 && offset + 46 <= buffer.length && buffer.readUInt32LE(offset) === DIRECTORY;
|
|
4296
|
+
COMPRESSED_EXTENSIONS = /* @__PURE__ */ new Set(["gz", "bz2", "xz", "zst"]);
|
|
4271
4297
|
VBA_NAMES = /* @__PURE__ */ new Set(["vba", "_vba_project", "_vba_project_cur", "macros"]);
|
|
4272
4298
|
PDF_ACTIVE = linear(String.raw`\/(?:JavaScript|JS|Launch)\b`);
|
|
4273
4299
|
escaped = (character) => {
|
|
@@ -4394,18 +4420,100 @@ function isAttachedMessage(attachment) {
|
|
|
4394
4420
|
const type = String(attachment.contentType).toLowerCase();
|
|
4395
4421
|
return type === "message/rfc822" || type === "message/global" || /\.(?:eml|u8msg)$/i.test(String(attachment.filename));
|
|
4396
4422
|
}
|
|
4423
|
+
function addFinding(list, part) {
|
|
4424
|
+
const found = foundIn.get(list) ?? /* @__PURE__ */ new Set();
|
|
4425
|
+
foundIn.set(list, found);
|
|
4426
|
+
const types = inspectAttachments([part]).map((finding2) => finding2.type);
|
|
4427
|
+
if (types.some((type) => !found.has(type))) {
|
|
4428
|
+
list.push(part);
|
|
4429
|
+
for (const type of types) {
|
|
4430
|
+
found.add(type);
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
function parametersOf(value) {
|
|
4435
|
+
const parameters = [];
|
|
4436
|
+
let start = -1;
|
|
4437
|
+
let quoted = false;
|
|
4438
|
+
for (let i = 0; i < value.length; i++) {
|
|
4439
|
+
if (quoted && value[i] === "\\") {
|
|
4440
|
+
i++;
|
|
4441
|
+
} else if (value[i] === '"') {
|
|
4442
|
+
quoted = !quoted;
|
|
4443
|
+
} else if (value[i] === ";" && !quoted) {
|
|
4444
|
+
if (start !== -1) {
|
|
4445
|
+
parameters.push(value.slice(start, i));
|
|
4446
|
+
}
|
|
4447
|
+
start = i + 1;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
if (start !== -1) {
|
|
4451
|
+
parameters.push(value.slice(start));
|
|
4452
|
+
}
|
|
4453
|
+
return parameters;
|
|
4454
|
+
}
|
|
4455
|
+
function repeatedNames(node) {
|
|
4456
|
+
const names = [];
|
|
4457
|
+
const { headers, libmime } = node ?? {};
|
|
4458
|
+
if (typeof headers?.get !== "function" || typeof libmime?.parseHeaderValue !== "function") {
|
|
4459
|
+
return names;
|
|
4460
|
+
}
|
|
4461
|
+
const read = (value, parameter) => {
|
|
4462
|
+
let name = "";
|
|
4463
|
+
try {
|
|
4464
|
+
name = libmime.decodeWords(String(libmime.parseHeaderValue(value, { stripComments: true }).params[parameter] ?? "")).trim();
|
|
4465
|
+
} catch {
|
|
4466
|
+
}
|
|
4467
|
+
if (name !== "") {
|
|
4468
|
+
names.push(name);
|
|
4469
|
+
}
|
|
4470
|
+
};
|
|
4471
|
+
for (const [field, parameter] of [["content-disposition", "filename"], ["content-type", "name"]]) {
|
|
4472
|
+
const values = headers.get(field).slice(0, MAX_NAME_FIELDS);
|
|
4473
|
+
for (const value of values) {
|
|
4474
|
+
const parameters = parametersOf(value).slice(0, MAX_NAME_PARAMETERS).map((text) => ({ text, key: text.slice(0, Math.max(0, text.indexOf("="))).trim().toLowerCase() }));
|
|
4475
|
+
const repeated = parameters.filter(({ key }) => (key === parameter || key.startsWith(`${parameter}*`)) && parameters.filter((other) => other.key === key).length > 1);
|
|
4476
|
+
if (values.length > 1) {
|
|
4477
|
+
read(value, parameter);
|
|
4478
|
+
}
|
|
4479
|
+
for (const { text } of repeated) {
|
|
4480
|
+
read(`x;${text}`, text.slice(0, text.indexOf("=")).trim().toLowerCase().replace(/\*.*$/, ""));
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
return names;
|
|
4485
|
+
}
|
|
4486
|
+
function namedTextParts(mail) {
|
|
4487
|
+
return mail[NAMED_TEXT] ?? [];
|
|
4488
|
+
}
|
|
4397
4489
|
async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = import_mailparser.simpleParser } = {}) {
|
|
4398
4490
|
const input = import_node_stream.Readable.from([finishable(unfoldHeaders(raw))], { objectMode: false, autoDestroy: false });
|
|
4399
4491
|
input.on("error", () => {
|
|
4400
4492
|
});
|
|
4493
|
+
const named = [];
|
|
4401
4494
|
const { pipe } = input;
|
|
4402
4495
|
input.pipe = function(parser2, options) {
|
|
4403
4496
|
parser2.splitter?.on("error", (error) => input.destroy(error));
|
|
4497
|
+
const { createNode } = parser2;
|
|
4498
|
+
if (typeof createNode === "function") {
|
|
4499
|
+
parser2.createNode = function(node) {
|
|
4500
|
+
const created = createNode.call(this, node);
|
|
4501
|
+
if (created?.isAttachment === false && node?.filename) {
|
|
4502
|
+
addFinding(named, { filename: String(node.filename), contentType: created.contentType, content: null });
|
|
4503
|
+
}
|
|
4504
|
+
for (const filename of repeatedNames(node)) {
|
|
4505
|
+
if (filename !== node?.filename) {
|
|
4506
|
+
addFinding(named, { filename, contentType: created?.contentType || "application/octet-stream", content: null });
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
return created;
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4404
4512
|
return pipe.call(this, parser2, options);
|
|
4405
4513
|
};
|
|
4406
4514
|
let timer;
|
|
4407
4515
|
try {
|
|
4408
|
-
|
|
4516
|
+
const mail = await Promise.race([
|
|
4409
4517
|
parser(input, PARSER_OPTIONS),
|
|
4410
4518
|
new Promise((resolve, reject) => {
|
|
4411
4519
|
timer = setTimeout(() => {
|
|
@@ -4415,6 +4523,17 @@ async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = import_mailp
|
|
|
4415
4523
|
}, timeout);
|
|
4416
4524
|
})
|
|
4417
4525
|
]);
|
|
4526
|
+
if (mail && typeof mail === "object") {
|
|
4527
|
+
let count = 0;
|
|
4528
|
+
for (const [, filename] of String(mail.text ?? "").matchAll(ENCODED_FILES)) {
|
|
4529
|
+
addFinding(named, { filename: filename.trim(), contentType: "application/octet-stream", content: null });
|
|
4530
|
+
if (++count >= MAX_ENCODED_FILES) {
|
|
4531
|
+
break;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
Object.defineProperty(mail, NAMED_TEXT, { value: named });
|
|
4535
|
+
}
|
|
4536
|
+
return mail;
|
|
4418
4537
|
} finally {
|
|
4419
4538
|
clearTimeout(timer);
|
|
4420
4539
|
input.destroy();
|
|
@@ -4546,6 +4665,9 @@ function unfoldHeaders(raw) {
|
|
|
4546
4665
|
chunks.push(raw.subarray(copied));
|
|
4547
4666
|
return import_node_buffer2.Buffer.concat(chunks);
|
|
4548
4667
|
}
|
|
4668
|
+
function lineCost(line) {
|
|
4669
|
+
return Math.max(line.length, MIN_LINE_COST);
|
|
4670
|
+
}
|
|
4549
4671
|
function upToLine(raw, offset) {
|
|
4550
4672
|
return raw.subarray(0, Math.max(raw.lastIndexOf(10, offset - 1) + 1, 0));
|
|
4551
4673
|
}
|
|
@@ -4572,9 +4694,9 @@ function smallerHeader(raw) {
|
|
|
4572
4694
|
const whole = text.slice(start, stop).replace(/\r$/, "");
|
|
4573
4695
|
if (last && (whole.startsWith(" ") || whole.startsWith(" "))) {
|
|
4574
4696
|
const line = last.address ? whole : whole.slice(0, MAX_LINE);
|
|
4575
|
-
if (last.address || last.size + line
|
|
4697
|
+
if (last.address || last.size + lineCost(line) <= MAX_FIELD) {
|
|
4576
4698
|
last.lines.push(line);
|
|
4577
|
-
last.size += line
|
|
4699
|
+
last.size += lineCost(line);
|
|
4578
4700
|
}
|
|
4579
4701
|
} else {
|
|
4580
4702
|
const name = whole.slice(0, whole.indexOf(":")).trim().toLowerCase();
|
|
@@ -4584,7 +4706,7 @@ function smallerHeader(raw) {
|
|
|
4584
4706
|
name,
|
|
4585
4707
|
address: address2,
|
|
4586
4708
|
lines: [line],
|
|
4587
|
-
size: line
|
|
4709
|
+
size: lineCost(line)
|
|
4588
4710
|
});
|
|
4589
4711
|
}
|
|
4590
4712
|
start = stop + 1;
|
|
@@ -4595,7 +4717,7 @@ function smallerHeader(raw) {
|
|
|
4595
4717
|
const bounded = boundAddressField(line, Number.POSITIVE_INFINITY);
|
|
4596
4718
|
fromCut ||= field.name === "from" && bounded !== line;
|
|
4597
4719
|
field.lines = [bounded];
|
|
4598
|
-
field.size = bounded
|
|
4720
|
+
field.size = lineCost(bounded);
|
|
4599
4721
|
}
|
|
4600
4722
|
const total = fields.reduce((sum, field) => sum + field.size, 0);
|
|
4601
4723
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4628,7 +4750,7 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
|
|
|
4628
4750
|
shorter = fewerParts(input);
|
|
4629
4751
|
} else if (limit === HEADER_TOO_LARGE) {
|
|
4630
4752
|
const smaller = smallerHeader(input);
|
|
4631
|
-
shorter = smaller.raw.length < input.length ? smaller.raw : upToLine(input, MAX_PREFIX_BYTES);
|
|
4753
|
+
shorter = smaller.raw.length < input.length ? smaller.raw : upToLine(input, input.length > MAX_PREFIX_BYTES ? MAX_PREFIX_BYTES : Math.floor(input.length / 2));
|
|
4632
4754
|
fromCut ||= smaller.raw === shorter && smaller.fromCut;
|
|
4633
4755
|
}
|
|
4634
4756
|
if (shorter.length >= input.length) {
|
|
@@ -4640,7 +4762,13 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
|
|
|
4640
4762
|
}
|
|
4641
4763
|
}
|
|
4642
4764
|
async function expandAttachments(attachments = [], errors = [], parse = parseMessage, gets = {}) {
|
|
4643
|
-
const { unopened = [], mails = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
|
|
4765
|
+
const { unopened = [], mails = [], named = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
|
|
4766
|
+
const addNamed = (...parts) => {
|
|
4767
|
+
for (const part of parts) {
|
|
4768
|
+
addFinding(named, part);
|
|
4769
|
+
}
|
|
4770
|
+
};
|
|
4771
|
+
const nameOf = (value) => trimEndOf(String(value ?? "").trim().toLowerCase(), ". ");
|
|
4644
4772
|
const all = [];
|
|
4645
4773
|
const deadline = Date.now() + PARSE_TIMEOUT;
|
|
4646
4774
|
let budget = MAX_NESTED_BYTES;
|
|
@@ -4662,6 +4790,10 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
4662
4790
|
const name = attachment.filename || (prefix ? "unnamed attachment" : "");
|
|
4663
4791
|
const filename = prefix ? `${prefix}/${name}` : name || "attached message";
|
|
4664
4792
|
all.push(prefix ? { ...attachment, filename: `${prefix}/${name}` } : attachment);
|
|
4793
|
+
const typeName = String(attachment.headers?.get?.("content-type")?.params?.name ?? "").trim();
|
|
4794
|
+
if (typeName && nameOf(typeName) !== nameOf(attachment.filename)) {
|
|
4795
|
+
addNamed({ filename: prefix ? `${prefix}/${typeName}` : typeName, contentType: attachment.contentType, content: null });
|
|
4796
|
+
}
|
|
4665
4797
|
const { content } = attachment;
|
|
4666
4798
|
const attached = isAttachedMessage(attachment);
|
|
4667
4799
|
if (attached && import_node_buffer2.Buffer.isBuffer(content) && depth >= MAX_NESTED_DEPTH) {
|
|
@@ -4690,20 +4822,24 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
4690
4822
|
errors.push(inner.error);
|
|
4691
4823
|
}
|
|
4692
4824
|
mails.push(inner.mail);
|
|
4693
|
-
|
|
4825
|
+
const carrier = prefix ? `${prefix}/${name}` : name || "attached message";
|
|
4826
|
+
addNamed(...namedTextParts(inner.mail).map((part) => ({ ...part, filename: `${carrier}/${part.filename}` })));
|
|
4827
|
+
await visit(inner.mail.attachments, carrier, depth + 1);
|
|
4694
4828
|
}
|
|
4695
4829
|
}
|
|
4696
4830
|
};
|
|
4697
4831
|
await visit(attachments, "", 0);
|
|
4698
4832
|
return all;
|
|
4699
4833
|
}
|
|
4700
|
-
var import_node_buffer2, import_node_crypto, import_node_stream, import_mailparser, ADDRESS_FIELDS, MAX_ADDRESS_FIELD, MAX_ADDRESS_SEPARATORS, MAX_ADDRESS_GROUPS, MAX_ADDRESS_TOTAL, AUTH_ADDRESS_FIELDS, ADDRESS_OPERATORS, SINGLE_FIELDS, PRIORITY_FIELDS, BOUNDED2, ROOM, PARSER_OPTIONS, processHeaders, TOO_MANY_PARTS, HEADER_TOO_LARGE, MAX_PART_LINES, MAX_PREFIX_BYTES, MAX_LINE, MAX_FIELD, MAX_HEADER, MAX_REPEATED, KEPT_FIELDS, MAX_AUTH_WORK, SIGNATURE_FIELDS, AUTH_FIELDS, MAX_SIGNATURES, MAX_DKIM_SIGNATURES, MAX_NESTED_DEPTH, MAX_ATTACHMENTS, MAX_NESTED_BYTES, MAX_ATTACHED_MESSAGES, PARSE_TIMEOUT, MAX_FOLDS;
|
|
4834
|
+
var import_node_buffer2, import_node_crypto, import_node_stream, import_mailparser, ADDRESS_FIELDS, MAX_ADDRESS_FIELD, MAX_ADDRESS_SEPARATORS, MAX_ADDRESS_GROUPS, MAX_ADDRESS_TOTAL, AUTH_ADDRESS_FIELDS, ADDRESS_OPERATORS, SINGLE_FIELDS, PRIORITY_FIELDS, BOUNDED2, ROOM, PARSER_OPTIONS, processHeaders, TOO_MANY_PARTS, HEADER_TOO_LARGE, MAX_PART_LINES, MAX_PREFIX_BYTES, MAX_LINE, MAX_FIELD, MAX_HEADER, MAX_REPEATED, MIN_LINE_COST, KEPT_FIELDS, MAX_AUTH_WORK, SIGNATURE_FIELDS, AUTH_FIELDS, MAX_SIGNATURES, MAX_DKIM_SIGNATURES, MAX_NESTED_DEPTH, MAX_ATTACHMENTS, MAX_NESTED_BYTES, MAX_ATTACHED_MESSAGES, PARSE_TIMEOUT, NAMED_TEXT, ENCODED_FILES, MAX_ENCODED_FILES, foundIn, MAX_NAME_FIELDS, MAX_NAME_PARAMETERS, MAX_FOLDS;
|
|
4701
4835
|
var init_parse = __esm({
|
|
4702
4836
|
"src/parse.js"() {
|
|
4703
4837
|
import_node_buffer2 = require("node:buffer");
|
|
4704
4838
|
import_node_crypto = require("node:crypto");
|
|
4705
4839
|
import_node_stream = require("node:stream");
|
|
4706
4840
|
import_mailparser = require("mailparser");
|
|
4841
|
+
init_attachments();
|
|
4842
|
+
init_linear();
|
|
4707
4843
|
ADDRESS_FIELDS = /* @__PURE__ */ new Set(["from", "to", "cc", "bcc", "sender", "reply-to", "delivered-to", "return-path", "disposition-notification-to"]);
|
|
4708
4844
|
MAX_ADDRESS_FIELD = 16 * 1024;
|
|
4709
4845
|
MAX_ADDRESS_SEPARATORS = 2e3;
|
|
@@ -4754,6 +4890,7 @@ var init_parse = __esm({
|
|
|
4754
4890
|
MAX_FIELD = 8192;
|
|
4755
4891
|
MAX_HEADER = 512 * 1024;
|
|
4756
4892
|
MAX_REPEATED = 100;
|
|
4893
|
+
MIN_LINE_COST = 64;
|
|
4757
4894
|
KEPT_FIELDS = /* @__PURE__ */ new Set([
|
|
4758
4895
|
"from",
|
|
4759
4896
|
"sender",
|
|
@@ -4784,6 +4921,12 @@ var init_parse = __esm({
|
|
|
4784
4921
|
MAX_NESTED_BYTES = 25 * 1024 * 1024;
|
|
4785
4922
|
MAX_ATTACHED_MESSAGES = 100;
|
|
4786
4923
|
PARSE_TIMEOUT = 6e4;
|
|
4924
|
+
NAMED_TEXT = /* @__PURE__ */ Symbol("named text parts");
|
|
4925
|
+
ENCODED_FILES = /^(?:begin [0-7]{3,4} |=ybegin\b[^\r\n\u2028\u2029]*?\bname=)([^\r\n\u2028\u2029]+)\r?$/gm;
|
|
4926
|
+
MAX_ENCODED_FILES = 1e3;
|
|
4927
|
+
foundIn = /* @__PURE__ */ new WeakMap();
|
|
4928
|
+
MAX_NAME_FIELDS = 8;
|
|
4929
|
+
MAX_NAME_PARAMETERS = 32;
|
|
4787
4930
|
MAX_FOLDS = 256;
|
|
4788
4931
|
}
|
|
4789
4932
|
});
|
|
@@ -7262,7 +7405,7 @@ var init_links = __esm({
|
|
|
7262
7405
|
MEDIA_PROPERTIES = /* @__PURE__ */ new Set(["photo", "logo", "sound", "key", "attach"]);
|
|
7263
7406
|
HTML_PROPERTIES = /^(?:x-alt-desc|styled-description|description|comment)[;:]/i;
|
|
7264
7407
|
BINARY = /;\s*(?:(?:encoding\s*=\s*)?"?(?:b|base64)"?|value\s*=\s*"?binary"?)\s*(?:;|$)/i;
|
|
7265
|
-
HTML_TYPES = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml", "image/svg+xml", "text/xml", "application/xml"]);
|
|
7408
|
+
HTML_TYPES = /* @__PURE__ */ new Set(["text/html", "text/x-amp-html", "application/xhtml+xml", "image/svg+xml", "text/xml", "application/xml"]);
|
|
7266
7409
|
HTML_EXTENSIONS = /* @__PURE__ */ new Set(["html", "htm", "shtml", "xhtml", "xht", "svg", "xml"]);
|
|
7267
7410
|
CARD_TYPES = /* @__PURE__ */ new Set(["text/calendar", "text/x-vcalendar", "application/ics", "text/vcard", "text/x-vcard", "text/directory"]);
|
|
7268
7411
|
CARD_EXTENSIONS = /* @__PURE__ */ new Set(["ics", "ical", "ifb", "vcs", "vcf", "vcard"]);
|
|
@@ -7954,18 +8097,27 @@ function parseVerdict(text) {
|
|
|
7954
8097
|
sources.push([text.slice(0, 2e4), answered]);
|
|
7955
8098
|
}
|
|
7956
8099
|
for (const [source, wanted] of sources) {
|
|
8100
|
+
let found = null;
|
|
8101
|
+
let end = -1;
|
|
7957
8102
|
for (const candidate of jsonObjects(source)) {
|
|
7958
|
-
if (!wanted(candidate)) {
|
|
8103
|
+
if (!wanted(candidate) || candidate[0] < end) {
|
|
7959
8104
|
continue;
|
|
7960
8105
|
}
|
|
7961
8106
|
try {
|
|
7962
8107
|
const result = normalizeVerdict(JSON.parse(candidate[1]));
|
|
7963
8108
|
if (result) {
|
|
7964
|
-
|
|
8109
|
+
if (found && found.verdict !== result.verdict) {
|
|
8110
|
+
return null;
|
|
8111
|
+
}
|
|
8112
|
+
found ??= result;
|
|
8113
|
+
end = candidate[0] + candidate[1].length;
|
|
7965
8114
|
}
|
|
7966
8115
|
} catch {
|
|
7967
8116
|
}
|
|
7968
8117
|
}
|
|
8118
|
+
if (found) {
|
|
8119
|
+
return found;
|
|
8120
|
+
}
|
|
7969
8121
|
}
|
|
7970
8122
|
const prose = visible.toLowerCase();
|
|
7971
8123
|
let deniedEnd = -1;
|
|
@@ -8932,7 +9084,7 @@ var init_package = __esm({
|
|
|
8932
9084
|
package_default = {
|
|
8933
9085
|
name: "spamscanner",
|
|
8934
9086
|
description: "Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",
|
|
8935
|
-
version: "7.0.
|
|
9087
|
+
version: "7.0.15",
|
|
8936
9088
|
author: "Forward Email (https://forwardemail.net)",
|
|
8937
9089
|
bin: {
|
|
8938
9090
|
spamscanner: "./dist/esm/cli.js"
|
|
@@ -11256,14 +11408,15 @@ ${visibleText(html2.slice(0, config.maxLength * 2)).text}`, { maxLength: config.
|
|
|
11256
11408
|
};
|
|
11257
11409
|
const unopened = [];
|
|
11258
11410
|
const opened = [];
|
|
11259
|
-
const
|
|
11411
|
+
const named = [];
|
|
11412
|
+
const attached = await expandAttachments(mail.attachments, parseErrors, void 0, { unopened, mails: opened, named });
|
|
11260
11413
|
const carried = config.phishing.attachments === true && (config.phishing.cloudflare || config.dnsbl.domain?.length > 0) ? carriedLinkHosts(mail, {
|
|
11261
11414
|
mails: opened,
|
|
11262
11415
|
attachments: attached,
|
|
11263
11416
|
maxHosts: config.phishing.maxHosts,
|
|
11264
11417
|
hostsInText: config.phishing.hostsInText === true
|
|
11265
11418
|
}) : null;
|
|
11266
|
-
const attachments = config.attachments === false ? [] : inspectAttachments(attached).filter((item) => config.macros !== false || !["macro", "pdf_active", "rtf_object"].includes(item.type)).map((item) => finding(item));
|
|
11419
|
+
const attachments = config.attachments === false ? [] : inspectAttachments([...attached, ...namedTextParts(mail), ...named]).filter((item) => config.macros !== false || !["macro", "pdf_active", "rtf_object"].includes(item.type)).map((item) => finding(item));
|
|
11267
11420
|
const [links, authentication, checkedReputation, dnsbl, virusScan, toxicity, nsfw] = await Promise.all([
|
|
11268
11421
|
this.checkLinks(checkedLinks, config, extracted.hosts, { from: fromDomain(mail), carried }),
|
|
11269
11422
|
config.authenticationResults && typeof config.authenticationResults === "object" ? normalizeAuthOutput(config.authenticationResults) : config.authentication && session.remoteAddress ? withTimeout(authenticate(raw, {
|