spamscanner 7.0.14 → 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/esm/index.js
CHANGED
|
@@ -3305,6 +3305,7 @@ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3305
3305
|
"apk",
|
|
3306
3306
|
"app",
|
|
3307
3307
|
"appimage",
|
|
3308
|
+
"appinstaller",
|
|
3308
3309
|
"application",
|
|
3309
3310
|
"appref-ms",
|
|
3310
3311
|
"appx",
|
|
@@ -3341,6 +3342,7 @@ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3341
3342
|
"inf",
|
|
3342
3343
|
"ins",
|
|
3343
3344
|
"ipa",
|
|
3345
|
+
"iqy",
|
|
3344
3346
|
"iso",
|
|
3345
3347
|
"isp",
|
|
3346
3348
|
"its",
|
|
@@ -3349,6 +3351,7 @@ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3349
3351
|
"js",
|
|
3350
3352
|
"jse",
|
|
3351
3353
|
"ksh",
|
|
3354
|
+
"library-ms",
|
|
3352
3355
|
"lnk",
|
|
3353
3356
|
"mad",
|
|
3354
3357
|
"maf",
|
|
@@ -3380,6 +3383,8 @@ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3380
3383
|
"mst",
|
|
3381
3384
|
"msu",
|
|
3382
3385
|
"nsh",
|
|
3386
|
+
"one",
|
|
3387
|
+
"onepkg",
|
|
3383
3388
|
"ops",
|
|
3384
3389
|
"osd",
|
|
3385
3390
|
"pcd",
|
|
@@ -3410,6 +3415,8 @@ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3410
3415
|
"scpt",
|
|
3411
3416
|
"scr",
|
|
3412
3417
|
"sct",
|
|
3418
|
+
"search-ms",
|
|
3419
|
+
"searchconnector-ms",
|
|
3413
3420
|
"settingcontent-ms",
|
|
3414
3421
|
"sh",
|
|
3415
3422
|
"shb",
|
|
@@ -3637,6 +3644,14 @@ function extensionOf(name) {
|
|
|
3637
3644
|
}
|
|
3638
3645
|
return whole;
|
|
3639
3646
|
}
|
|
3647
|
+
var COMPRESSED_EXTENSIONS = /* @__PURE__ */ new Set(["gz", "bz2", "xz", "zst"]);
|
|
3648
|
+
function uncompressedName(name) {
|
|
3649
|
+
if (!COMPRESSED_EXTENSIONS.has(extensionOf(name))) {
|
|
3650
|
+
return String(name);
|
|
3651
|
+
}
|
|
3652
|
+
const trimmed = trimEndOf(String(name).replace(ALL_BIDI_CONTROLS, ""), ". ");
|
|
3653
|
+
return trimmed.slice(0, trimmed.lastIndexOf("."));
|
|
3654
|
+
}
|
|
3640
3655
|
function lastExtension(base) {
|
|
3641
3656
|
const dot = base.lastIndexOf(".");
|
|
3642
3657
|
return dot === -1 ? "" : base.slice(dot + 1).trim();
|
|
@@ -3689,7 +3704,20 @@ function inspectAttachments(attachments = []) {
|
|
|
3689
3704
|
} else if (RISKY_EXTENSIONS.has(extension)) {
|
|
3690
3705
|
add("risky_attachment", `Attachment "${name}" is a risky file type (.${extension})`, { extension });
|
|
3691
3706
|
}
|
|
3707
|
+
const held = COMPRESSED_EXTENSIONS.has(extension) ? extensionOf(uncompressedName(filename)) : "";
|
|
3708
|
+
if (EXECUTABLE_EXTENSIONS.has(held)) {
|
|
3709
|
+
const script = SCRIPT_EXTENSIONS.has(held);
|
|
3710
|
+
add(script ? "script_in_archive" : "executable_in_archive", `Archive "${name}" contains ${script ? "a script" : "a program"} (${shorten(uncompressedName(filename).split(/[/\\]/).pop())})`, { extension: held, extensions: [held] });
|
|
3711
|
+
}
|
|
3712
|
+
const webPage = detected?.type === "html" || detected?.type === "svg" || ["htm", "html", "mht", "mhtml", "shtml", "xht", "xhtml", "svg"].includes(extension);
|
|
3713
|
+
const addWebPage = () => {
|
|
3714
|
+
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"));
|
|
3715
|
+
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 });
|
|
3716
|
+
};
|
|
3692
3717
|
if (!content) {
|
|
3718
|
+
if (webPage) {
|
|
3719
|
+
addWebPage();
|
|
3720
|
+
}
|
|
3693
3721
|
continue;
|
|
3694
3722
|
}
|
|
3695
3723
|
const zipFamily = ZIP_FAMILY.has(detected?.type) || extension === "zip" && !detected?.executable && entriesOf(content).length > 0;
|
|
@@ -3701,10 +3729,10 @@ function inspectAttachments(attachments = []) {
|
|
|
3701
3729
|
} else if (office && entries.some((entry) => MACRO_EXTENSIONS.has(extensionOf(entry.name)))) {
|
|
3702
3730
|
add("macro", `Attachment "${name}" is an Office document holding another with macros`, { extension });
|
|
3703
3731
|
}
|
|
3704
|
-
const inner = entries.filter((entry) => EXECUTABLE_EXTENSIONS.has(extensionOf(entry.name)) && !(office && OFFICE_EMBEDDED.has(extensionOf(entry.name))));
|
|
3705
|
-
const details = { extension: extensionOf(inner[0]?.name ?? ""), extensions: [...new Set(inner.map((entry) => extensionOf(entry.name)))] };
|
|
3732
|
+
const inner = entries.filter((entry) => EXECUTABLE_EXTENSIONS.has(extensionOf(uncompressedName(entry.name))) && !(office && OFFICE_EMBEDDED.has(extensionOf(entry.name))));
|
|
3733
|
+
const details = { extension: extensionOf(uncompressedName(inner[0]?.name ?? "")), extensions: [...new Set(inner.map((entry) => extensionOf(uncompressedName(entry.name))))] };
|
|
3706
3734
|
const listed = inner.slice(0, 3).map((entry) => shorten(entry.name.split("/").pop())).join(", ");
|
|
3707
|
-
if (inner.some((entry) => !SCRIPT_EXTENSIONS.has(extensionOf(entry.name)))) {
|
|
3735
|
+
if (inner.some((entry) => !SCRIPT_EXTENSIONS.has(extensionOf(uncompressedName(entry.name))))) {
|
|
3708
3736
|
add("executable_in_archive", `Archive "${name}" contains ${inner.length === 1 ? "a program" : `${inner.length} programs`} (${listed})`, details);
|
|
3709
3737
|
} else if (inner.length > 0) {
|
|
3710
3738
|
add("script_in_archive", `Archive "${name}" contains ${inner.length === 1 ? "a script" : `${inner.length} scripts`} (${listed})`, details);
|
|
@@ -3723,10 +3751,8 @@ function inspectAttachments(attachments = []) {
|
|
|
3723
3751
|
} else if (detected?.type === "rtf" && /\\object\b|\\objdata\b|\\objupdate\b/.test(content.toString("latin1"))) {
|
|
3724
3752
|
add("rtf_object", `RTF document "${name}" embeds an OLE object`, { extension: "rtf" });
|
|
3725
3753
|
}
|
|
3726
|
-
if (
|
|
3727
|
-
|
|
3728
|
-
const active = /<script\b|<form\b|\bon(?:load|error|click)\s*=|javascript:|window\.location|document\.location|atob\(/i.test(text);
|
|
3729
|
-
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 });
|
|
3754
|
+
if (webPage) {
|
|
3755
|
+
addWebPage();
|
|
3730
3756
|
}
|
|
3731
3757
|
}
|
|
3732
3758
|
return findings;
|
|
@@ -3901,6 +3927,7 @@ var MAX_LINE = 998;
|
|
|
3901
3927
|
var MAX_FIELD = 8192;
|
|
3902
3928
|
var MAX_HEADER = 512 * 1024;
|
|
3903
3929
|
var MAX_REPEATED = 100;
|
|
3930
|
+
var MIN_LINE_COST = 64;
|
|
3904
3931
|
var KEPT_FIELDS = /* @__PURE__ */ new Set([
|
|
3905
3932
|
"from",
|
|
3906
3933
|
"sender",
|
|
@@ -3935,18 +3962,106 @@ var MAX_ATTACHMENTS = 100;
|
|
|
3935
3962
|
var MAX_NESTED_BYTES = 25 * 1024 * 1024;
|
|
3936
3963
|
var MAX_ATTACHED_MESSAGES = 100;
|
|
3937
3964
|
var PARSE_TIMEOUT = 6e4;
|
|
3965
|
+
var NAMED_TEXT = /* @__PURE__ */ Symbol("named text parts");
|
|
3966
|
+
var ENCODED_FILES = /^(?:begin [0-7]{3,4} |=ybegin\b[^\r\n\u2028\u2029]*?\bname=)([^\r\n\u2028\u2029]+)\r?$/gm;
|
|
3967
|
+
var MAX_ENCODED_FILES = 1e3;
|
|
3968
|
+
var foundIn = /* @__PURE__ */ new WeakMap();
|
|
3969
|
+
function addFinding(list, part) {
|
|
3970
|
+
const found = foundIn.get(list) ?? /* @__PURE__ */ new Set();
|
|
3971
|
+
foundIn.set(list, found);
|
|
3972
|
+
const types = inspectAttachments([part]).map((finding2) => finding2.type);
|
|
3973
|
+
if (types.some((type) => !found.has(type))) {
|
|
3974
|
+
list.push(part);
|
|
3975
|
+
for (const type of types) {
|
|
3976
|
+
found.add(type);
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
function parametersOf(value) {
|
|
3981
|
+
const parameters = [];
|
|
3982
|
+
let start = -1;
|
|
3983
|
+
let quoted = false;
|
|
3984
|
+
for (let i = 0; i < value.length; i++) {
|
|
3985
|
+
if (quoted && value[i] === "\\") {
|
|
3986
|
+
i++;
|
|
3987
|
+
} else if (value[i] === '"') {
|
|
3988
|
+
quoted = !quoted;
|
|
3989
|
+
} else if (value[i] === ";" && !quoted) {
|
|
3990
|
+
if (start !== -1) {
|
|
3991
|
+
parameters.push(value.slice(start, i));
|
|
3992
|
+
}
|
|
3993
|
+
start = i + 1;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
if (start !== -1) {
|
|
3997
|
+
parameters.push(value.slice(start));
|
|
3998
|
+
}
|
|
3999
|
+
return parameters;
|
|
4000
|
+
}
|
|
4001
|
+
var MAX_NAME_FIELDS = 8;
|
|
4002
|
+
var MAX_NAME_PARAMETERS = 32;
|
|
4003
|
+
function repeatedNames(node) {
|
|
4004
|
+
const names = [];
|
|
4005
|
+
const { headers, libmime } = node ?? {};
|
|
4006
|
+
if (typeof headers?.get !== "function" || typeof libmime?.parseHeaderValue !== "function") {
|
|
4007
|
+
return names;
|
|
4008
|
+
}
|
|
4009
|
+
const read = (value, parameter) => {
|
|
4010
|
+
let name = "";
|
|
4011
|
+
try {
|
|
4012
|
+
name = libmime.decodeWords(String(libmime.parseHeaderValue(value, { stripComments: true }).params[parameter] ?? "")).trim();
|
|
4013
|
+
} catch {
|
|
4014
|
+
}
|
|
4015
|
+
if (name !== "") {
|
|
4016
|
+
names.push(name);
|
|
4017
|
+
}
|
|
4018
|
+
};
|
|
4019
|
+
for (const [field, parameter] of [["content-disposition", "filename"], ["content-type", "name"]]) {
|
|
4020
|
+
const values = headers.get(field).slice(0, MAX_NAME_FIELDS);
|
|
4021
|
+
for (const value of values) {
|
|
4022
|
+
const parameters = parametersOf(value).slice(0, MAX_NAME_PARAMETERS).map((text) => ({ text, key: text.slice(0, Math.max(0, text.indexOf("="))).trim().toLowerCase() }));
|
|
4023
|
+
const repeated = parameters.filter(({ key }) => (key === parameter || key.startsWith(`${parameter}*`)) && parameters.filter((other) => other.key === key).length > 1);
|
|
4024
|
+
if (values.length > 1) {
|
|
4025
|
+
read(value, parameter);
|
|
4026
|
+
}
|
|
4027
|
+
for (const { text } of repeated) {
|
|
4028
|
+
read(`x;${text}`, text.slice(0, text.indexOf("=")).trim().toLowerCase().replace(/\*.*$/, ""));
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
return names;
|
|
4033
|
+
}
|
|
4034
|
+
function namedTextParts(mail) {
|
|
4035
|
+
return mail[NAMED_TEXT] ?? [];
|
|
4036
|
+
}
|
|
3938
4037
|
async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = simpleParser } = {}) {
|
|
3939
4038
|
const input = Readable.from([finishable(unfoldHeaders(raw))], { objectMode: false, autoDestroy: false });
|
|
3940
4039
|
input.on("error", () => {
|
|
3941
4040
|
});
|
|
4041
|
+
const named = [];
|
|
3942
4042
|
const { pipe } = input;
|
|
3943
4043
|
input.pipe = function(parser2, options) {
|
|
3944
4044
|
parser2.splitter?.on("error", (error) => input.destroy(error));
|
|
4045
|
+
const { createNode } = parser2;
|
|
4046
|
+
if (typeof createNode === "function") {
|
|
4047
|
+
parser2.createNode = function(node) {
|
|
4048
|
+
const created = createNode.call(this, node);
|
|
4049
|
+
if (created?.isAttachment === false && node?.filename) {
|
|
4050
|
+
addFinding(named, { filename: String(node.filename), contentType: created.contentType, content: null });
|
|
4051
|
+
}
|
|
4052
|
+
for (const filename of repeatedNames(node)) {
|
|
4053
|
+
if (filename !== node?.filename) {
|
|
4054
|
+
addFinding(named, { filename, contentType: created?.contentType || "application/octet-stream", content: null });
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
return created;
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
3945
4060
|
return pipe.call(this, parser2, options);
|
|
3946
4061
|
};
|
|
3947
4062
|
let timer;
|
|
3948
4063
|
try {
|
|
3949
|
-
|
|
4064
|
+
const mail = await Promise.race([
|
|
3950
4065
|
parser(input, PARSER_OPTIONS),
|
|
3951
4066
|
new Promise((resolve, reject) => {
|
|
3952
4067
|
timer = setTimeout(() => {
|
|
@@ -3956,6 +4071,17 @@ async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = simpleParser
|
|
|
3956
4071
|
}, timeout);
|
|
3957
4072
|
})
|
|
3958
4073
|
]);
|
|
4074
|
+
if (mail && typeof mail === "object") {
|
|
4075
|
+
let count = 0;
|
|
4076
|
+
for (const [, filename] of String(mail.text ?? "").matchAll(ENCODED_FILES)) {
|
|
4077
|
+
addFinding(named, { filename: filename.trim(), contentType: "application/octet-stream", content: null });
|
|
4078
|
+
if (++count >= MAX_ENCODED_FILES) {
|
|
4079
|
+
break;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
Object.defineProperty(mail, NAMED_TEXT, { value: named });
|
|
4083
|
+
}
|
|
4084
|
+
return mail;
|
|
3959
4085
|
} finally {
|
|
3960
4086
|
clearTimeout(timer);
|
|
3961
4087
|
input.destroy();
|
|
@@ -4088,6 +4214,9 @@ function unfoldHeaders(raw) {
|
|
|
4088
4214
|
chunks.push(raw.subarray(copied));
|
|
4089
4215
|
return Buffer2.concat(chunks);
|
|
4090
4216
|
}
|
|
4217
|
+
function lineCost(line) {
|
|
4218
|
+
return Math.max(line.length, MIN_LINE_COST);
|
|
4219
|
+
}
|
|
4091
4220
|
function upToLine(raw, offset) {
|
|
4092
4221
|
return raw.subarray(0, Math.max(raw.lastIndexOf(10, offset - 1) + 1, 0));
|
|
4093
4222
|
}
|
|
@@ -4114,9 +4243,9 @@ function smallerHeader(raw) {
|
|
|
4114
4243
|
const whole = text.slice(start, stop).replace(/\r$/, "");
|
|
4115
4244
|
if (last && (whole.startsWith(" ") || whole.startsWith(" "))) {
|
|
4116
4245
|
const line = last.address ? whole : whole.slice(0, MAX_LINE);
|
|
4117
|
-
if (last.address || last.size + line
|
|
4246
|
+
if (last.address || last.size + lineCost(line) <= MAX_FIELD) {
|
|
4118
4247
|
last.lines.push(line);
|
|
4119
|
-
last.size += line
|
|
4248
|
+
last.size += lineCost(line);
|
|
4120
4249
|
}
|
|
4121
4250
|
} else {
|
|
4122
4251
|
const name = whole.slice(0, whole.indexOf(":")).trim().toLowerCase();
|
|
@@ -4126,7 +4255,7 @@ function smallerHeader(raw) {
|
|
|
4126
4255
|
name,
|
|
4127
4256
|
address: address2,
|
|
4128
4257
|
lines: [line],
|
|
4129
|
-
size: line
|
|
4258
|
+
size: lineCost(line)
|
|
4130
4259
|
});
|
|
4131
4260
|
}
|
|
4132
4261
|
start = stop + 1;
|
|
@@ -4137,7 +4266,7 @@ function smallerHeader(raw) {
|
|
|
4137
4266
|
const bounded = boundAddressField(line, Number.POSITIVE_INFINITY);
|
|
4138
4267
|
fromCut ||= field.name === "from" && bounded !== line;
|
|
4139
4268
|
field.lines = [bounded];
|
|
4140
|
-
field.size = bounded
|
|
4269
|
+
field.size = lineCost(bounded);
|
|
4141
4270
|
}
|
|
4142
4271
|
const total = fields.reduce((sum, field) => sum + field.size, 0);
|
|
4143
4272
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4170,7 +4299,7 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
|
|
|
4170
4299
|
shorter = fewerParts(input);
|
|
4171
4300
|
} else if (limit === HEADER_TOO_LARGE) {
|
|
4172
4301
|
const smaller = smallerHeader(input);
|
|
4173
|
-
shorter = smaller.raw.length < input.length ? smaller.raw : upToLine(input, MAX_PREFIX_BYTES);
|
|
4302
|
+
shorter = smaller.raw.length < input.length ? smaller.raw : upToLine(input, input.length > MAX_PREFIX_BYTES ? MAX_PREFIX_BYTES : Math.floor(input.length / 2));
|
|
4174
4303
|
fromCut ||= smaller.raw === shorter && smaller.fromCut;
|
|
4175
4304
|
}
|
|
4176
4305
|
if (shorter.length >= input.length) {
|
|
@@ -4182,7 +4311,13 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
|
|
|
4182
4311
|
}
|
|
4183
4312
|
}
|
|
4184
4313
|
async function expandAttachments(attachments = [], errors = [], parse = parseMessage, gets = {}) {
|
|
4185
|
-
const { unopened = [], mails = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
|
|
4314
|
+
const { unopened = [], mails = [], named = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
|
|
4315
|
+
const addNamed = (...parts) => {
|
|
4316
|
+
for (const part of parts) {
|
|
4317
|
+
addFinding(named, part);
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
const nameOf = (value) => trimEndOf(String(value ?? "").trim().toLowerCase(), ". ");
|
|
4186
4321
|
const all = [];
|
|
4187
4322
|
const deadline = Date.now() + PARSE_TIMEOUT;
|
|
4188
4323
|
let budget = MAX_NESTED_BYTES;
|
|
@@ -4204,6 +4339,10 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
4204
4339
|
const name = attachment.filename || (prefix ? "unnamed attachment" : "");
|
|
4205
4340
|
const filename = prefix ? `${prefix}/${name}` : name || "attached message";
|
|
4206
4341
|
all.push(prefix ? { ...attachment, filename: `${prefix}/${name}` } : attachment);
|
|
4342
|
+
const typeName = String(attachment.headers?.get?.("content-type")?.params?.name ?? "").trim();
|
|
4343
|
+
if (typeName && nameOf(typeName) !== nameOf(attachment.filename)) {
|
|
4344
|
+
addNamed({ filename: prefix ? `${prefix}/${typeName}` : typeName, contentType: attachment.contentType, content: null });
|
|
4345
|
+
}
|
|
4207
4346
|
const { content } = attachment;
|
|
4208
4347
|
const attached = isAttachedMessage(attachment);
|
|
4209
4348
|
if (attached && Buffer2.isBuffer(content) && depth >= MAX_NESTED_DEPTH) {
|
|
@@ -4232,7 +4371,9 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
4232
4371
|
errors.push(inner.error);
|
|
4233
4372
|
}
|
|
4234
4373
|
mails.push(inner.mail);
|
|
4235
|
-
|
|
4374
|
+
const carrier = prefix ? `${prefix}/${name}` : name || "attached message";
|
|
4375
|
+
addNamed(...namedTextParts(inner.mail).map((part) => ({ ...part, filename: `${carrier}/${part.filename}` })));
|
|
4376
|
+
await visit(inner.mail.attachments, carrier, depth + 1);
|
|
4236
4377
|
}
|
|
4237
4378
|
}
|
|
4238
4379
|
};
|
|
@@ -6354,7 +6495,7 @@ function withoutBinaryValues(text, limit = Number.POSITIVE_INFINITY) {
|
|
|
6354
6495
|
}
|
|
6355
6496
|
return out.join("\n");
|
|
6356
6497
|
}
|
|
6357
|
-
var HTML_TYPES = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml", "image/svg+xml", "text/xml", "application/xml"]);
|
|
6498
|
+
var HTML_TYPES = /* @__PURE__ */ new Set(["text/html", "text/x-amp-html", "application/xhtml+xml", "image/svg+xml", "text/xml", "application/xml"]);
|
|
6358
6499
|
var HTML_EXTENSIONS = /* @__PURE__ */ new Set(["html", "htm", "shtml", "xhtml", "xht", "svg", "xml"]);
|
|
6359
6500
|
var CARD_TYPES = /* @__PURE__ */ new Set(["text/calendar", "text/x-vcalendar", "application/ics", "text/vcard", "text/x-vcard", "text/directory"]);
|
|
6360
6501
|
var CARD_EXTENSIONS = /* @__PURE__ */ new Set(["ics", "ical", "ifb", "vcs", "vcf", "vcard"]);
|
|
@@ -7609,18 +7750,27 @@ function parseVerdict(text) {
|
|
|
7609
7750
|
sources.push([text.slice(0, 2e4), answered]);
|
|
7610
7751
|
}
|
|
7611
7752
|
for (const [source, wanted] of sources) {
|
|
7753
|
+
let found = null;
|
|
7754
|
+
let end = -1;
|
|
7612
7755
|
for (const candidate of jsonObjects(source)) {
|
|
7613
|
-
if (!wanted(candidate)) {
|
|
7756
|
+
if (!wanted(candidate) || candidate[0] < end) {
|
|
7614
7757
|
continue;
|
|
7615
7758
|
}
|
|
7616
7759
|
try {
|
|
7617
7760
|
const result = normalizeVerdict(JSON.parse(candidate[1]));
|
|
7618
7761
|
if (result) {
|
|
7619
|
-
|
|
7762
|
+
if (found && found.verdict !== result.verdict) {
|
|
7763
|
+
return null;
|
|
7764
|
+
}
|
|
7765
|
+
found ??= result;
|
|
7766
|
+
end = candidate[0] + candidate[1].length;
|
|
7620
7767
|
}
|
|
7621
7768
|
} catch {
|
|
7622
7769
|
}
|
|
7623
7770
|
}
|
|
7771
|
+
if (found) {
|
|
7772
|
+
return found;
|
|
7773
|
+
}
|
|
7624
7774
|
}
|
|
7625
7775
|
const prose = visible.toLowerCase();
|
|
7626
7776
|
let deniedEnd = -1;
|
|
@@ -8319,7 +8469,7 @@ function scoreResults(results, options) {
|
|
|
8319
8469
|
var package_default = {
|
|
8320
8470
|
name: "spamscanner",
|
|
8321
8471
|
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.",
|
|
8322
|
-
version: "7.0.
|
|
8472
|
+
version: "7.0.15",
|
|
8323
8473
|
author: "Forward Email (https://forwardemail.net)",
|
|
8324
8474
|
bin: {
|
|
8325
8475
|
spamscanner: "./dist/esm/cli.js"
|
|
@@ -10523,14 +10673,15 @@ ${visibleText(html.slice(0, config.maxLength * 2)).text}`, { maxLength: config.m
|
|
|
10523
10673
|
};
|
|
10524
10674
|
const unopened = [];
|
|
10525
10675
|
const opened = [];
|
|
10526
|
-
const
|
|
10676
|
+
const named = [];
|
|
10677
|
+
const attached = await expandAttachments(mail.attachments, parseErrors, void 0, { unopened, mails: opened, named });
|
|
10527
10678
|
const carried = config.phishing.attachments === true && (config.phishing.cloudflare || config.dnsbl.domain?.length > 0) ? carriedLinkHosts(mail, {
|
|
10528
10679
|
mails: opened,
|
|
10529
10680
|
attachments: attached,
|
|
10530
10681
|
maxHosts: config.phishing.maxHosts,
|
|
10531
10682
|
hostsInText: config.phishing.hostsInText === true
|
|
10532
10683
|
}) : null;
|
|
10533
|
-
const attachments = config.attachments === false ? [] : inspectAttachments(attached).filter((item) => config.macros !== false || !["macro", "pdf_active", "rtf_object"].includes(item.type)).map((item) => finding(item));
|
|
10684
|
+
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));
|
|
10534
10685
|
const [links, authentication, checkedReputation, dnsbl, virusScan, toxicity, nsfw] = await Promise.all([
|
|
10535
10686
|
this.checkLinks(checkedLinks, config, extracted.hosts, { from: fromDomain(mail), carried }),
|
|
10536
10687
|
config.authenticationResults && typeof config.authenticationResults === "object" ? normalizeAuthOutput(config.authenticationResults) : config.authentication && session.remoteAddress ? withTimeout(authenticate(raw, {
|