spamscanner 7.0.11 → 7.0.13
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.map +1 -1
- package/dist/cjs/index.cjs +274 -117
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/esm/arf.js.map +1 -1
- package/dist/esm/cli.js +278 -120
- package/dist/esm/cli.js.map +3 -3
- package/dist/esm/index.js +270 -113
- package/dist/esm/index.js.map +3 -3
- package/dist/types/links.d.ts +5 -3
- package/dist/types/tokenizer.d.ts +9 -0
- package/package.json +1 -1
package/dist/esm/cli.js
CHANGED
|
@@ -549,9 +549,12 @@ function rewriteMessage(source, headers = [], options = {}) {
|
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
const added = headers.map(([name, value]) => foldHeader(name, Buffer2.from(String(value), "utf8").toString("latin1"), newline));
|
|
552
|
-
const mbox = kept.length > 0 && kept[0][0].startsWith("From ") ? kept.shift() : [];
|
|
552
|
+
const mbox = kept.length > 0 && kept[0][0].startsWith("From ") ? kept.shift().slice(0, 1) : [];
|
|
553
|
+
if (kept.length > 0 && /^[ \t]/.test(kept[0][0])) {
|
|
554
|
+
kept.shift();
|
|
555
|
+
}
|
|
553
556
|
const head = [...mbox, ...added, ...kept.map((field) => field.join(newline))].join(newline);
|
|
554
|
-
return Buffer2.concat([Buffer2.from(head === ""
|
|
557
|
+
return Buffer2.concat([Buffer2.from(head === "" ? newline : `${head}${newline}${newline}`, "latin1"), body]);
|
|
555
558
|
}
|
|
556
559
|
|
|
557
560
|
// src/llm.js
|
|
@@ -1887,7 +1890,7 @@ var NAMED = [
|
|
|
1887
1890
|
",itilde=129,iukcy=456,iuml=ef,jcirc=135,jcy=439,jfr=1d527,jmath=237,jopf=1d55b,jscr=1d4bf,jsercy=458",
|
|
1888
1891
|
",jukcy=454,kappa=3ba,kappav=3f0,kcedil=137,kcy=43a,kfr=1d528,kgreen=138,khcy=445,kjcy=45c,kopf=1d55c",
|
|
1889
1892
|
",kscr=1d4c0,lAarr=21da,lArr=21d0,lAtail=291b,lBarr=290e,lE=2266,lEg=2a8b,lHar=2962,lacute=13a,laempt",
|
|
1890
|
-
"yv=29b4,lagran=2112,lambda=3bb,lang=
|
|
1893
|
+
"yv=29b4,lagran=2112,lambda=3bb,lang=27e8,langd=2991,langle=27e8,lap=2a85,laquo=ab,larr=2190,larrb=21",
|
|
1891
1894
|
"e4,larrbfs=291f,larrfs=291d,larrhk=21a9,larrlp=21ab,larrpl=2939,larrsim=2973,larrtl=21a2,lat=2aab,la",
|
|
1892
1895
|
"tail=2919,late=2aad,lates=2aad.fe00,lbarr=290c,lbbrk=2772,lbrace=7b,lbrack=5b,lbrke=298b,lbrksld=298",
|
|
1893
1896
|
"f,lbrkslu=298d,lcaron=13e,lcedil=13c,lceil=2308,lcub=7b,lcy=43b,ldca=2936,ldquo=201c,ldquor=201e,ldr",
|
|
@@ -1945,7 +1948,7 @@ var NAMED = [
|
|
|
1945
1948
|
"line=2312,profsurf=2313,prop=221d,propto=221d,prsim=227e,prurel=22b0,pscr=1d4c5,psi=3c8,puncsp=2008,",
|
|
1946
1949
|
"qfr=1d52e,qint=2a0c,qopf=1d562,qprime=2057,qscr=1d4c6,quaternions=210d,quatint=2a16,quest=3f,questeq",
|
|
1947
1950
|
"=225f,quot=22,rAarr=21db,rArr=21d2,rAtail=291c,rBarr=290f,rHar=2964,race=223d.331,racute=155,radic=2",
|
|
1948
|
-
"21a,raemptyv=29b3,rang=
|
|
1951
|
+
"21a,raemptyv=29b3,rang=27e9,rangd=2992,range=29a5,rangle=27e9,raquo=bb,rarr=2192,rarrap=2975,rarrb=2",
|
|
1949
1952
|
"1e5,rarrbfs=2920,rarrc=2933,rarrfs=291e,rarrhk=21aa,rarrlp=21ac,rarrpl=2945,rarrsim=2974,rarrtl=21a3",
|
|
1950
1953
|
",rarrw=219d,ratail=291a,ratio=2236,rationals=211a,rbarr=290d,rbbrk=2773,rbrace=7d,rbrack=5d,rbrke=29",
|
|
1951
1954
|
"8c,rbrksld=298e,rbrkslu=2990,rcaron=159,rcedil=157,rceil=2309,rcub=7d,rcy=440,rdca=2937,rdldhar=2969",
|
|
@@ -2888,6 +2891,34 @@ function continuesName(text, index) {
|
|
|
2888
2891
|
var FILE_TLDS = /* @__PURE__ */ new Set(["ai", "app", "md", "mov", "pm", "ps", "py", "rs", "sh", "so", "zip"]);
|
|
2889
2892
|
var LINK_FILE_TLDS = /* @__PURE__ */ new Set(["md", "mov", "pm", "ps", "py", "rs", "sh", "so", "zip"]);
|
|
2890
2893
|
var CLICK_TRACKERS = ["sendgrid.net", "list-manage.com", "mandrillapp.com", "awstrack.me", "pstmrk.it", "hubspotlinks.com", "rs6.net", "klclick.com", "mjt.lu", "exacttarget.com", "convertkit-mail.com", "convertkit-mail2.com", "kit-mail3.com", "sendibt2.com", "sendibt3.com", "createsend1.com", "cmail20.com", "clicks.mlsend.com", "acemlnb.com", "link.mail.beehiiv.com", "bhclick.com", "safelinks.protection.outlook.com", "urldefense.com", "urldefense.proofpoint.com", "mimecast.com", "mimecastprotect.com", "cudasvc.com", "secure-web.cisco.com"];
|
|
2894
|
+
var MAX_URL_HOST = 4096;
|
|
2895
|
+
var AUTHORITY_END = /[/\\?#]/g;
|
|
2896
|
+
var IGNORED_IN_HOST = /[\u00AD\u034F\u180B-\u180D\u180F\u200B\u2060\u2064\uFE00-\uFE0F\uFEFF]|\uD82F[\uDCA0-\uDCA3]|\uDB40[\uDD00-\uDDEF]/g;
|
|
2897
|
+
function boundedUrl(address2) {
|
|
2898
|
+
const text = String(address2).replaceAll(/[\t\n\r]/g, "");
|
|
2899
|
+
const colon = text.indexOf(":");
|
|
2900
|
+
let start = colon === -1 ? 0 : colon + 1;
|
|
2901
|
+
while (start < text.length && (text[start] === "/" || text[start] === "\\")) {
|
|
2902
|
+
start++;
|
|
2903
|
+
}
|
|
2904
|
+
AUTHORITY_END.lastIndex = start;
|
|
2905
|
+
const end = AUTHORITY_END.exec(text)?.index ?? text.length;
|
|
2906
|
+
const file = /^[\0- ]*file:/i.test(text);
|
|
2907
|
+
const at = file ? start : Math.max(start, text.lastIndexOf("@", end - 1) + 1);
|
|
2908
|
+
const bracket = text.lastIndexOf("]", end - 1);
|
|
2909
|
+
const port = text.lastIndexOf(":", end - 1);
|
|
2910
|
+
let host = text.slice(at, !file && port > Math.max(at - 1, bracket) ? port : end);
|
|
2911
|
+
if (host.length > MAX_URL_HOST && host.includes("%")) {
|
|
2912
|
+
try {
|
|
2913
|
+
host = decodeURIComponent(host);
|
|
2914
|
+
} catch {
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
if (host.length > MAX_URL_HOST && host.replaceAll(IGNORED_IN_HOST, "").length > MAX_URL_HOST) {
|
|
2918
|
+
throw new TypeError("Invalid URL: host too long");
|
|
2919
|
+
}
|
|
2920
|
+
return new URL(text);
|
|
2921
|
+
}
|
|
2891
2922
|
var isUnder = (host, domain) => host === domain || host.endsWith(`.${domain}`);
|
|
2892
2923
|
function isClickTracker(url) {
|
|
2893
2924
|
const host = url.hostname.toLowerCase();
|
|
@@ -2916,7 +2947,7 @@ var MAX_GATEWAYS = 16;
|
|
|
2916
2947
|
function unwrapLink(address2) {
|
|
2917
2948
|
let url;
|
|
2918
2949
|
try {
|
|
2919
|
-
url =
|
|
2950
|
+
url = boundedUrl(address2);
|
|
2920
2951
|
} catch {
|
|
2921
2952
|
return null;
|
|
2922
2953
|
}
|
|
@@ -2927,7 +2958,7 @@ function unwrapLink(address2) {
|
|
|
2927
2958
|
}
|
|
2928
2959
|
let next;
|
|
2929
2960
|
try {
|
|
2930
|
-
next =
|
|
2961
|
+
next = boundedUrl(target);
|
|
2931
2962
|
} catch {
|
|
2932
2963
|
break;
|
|
2933
2964
|
}
|
|
@@ -3205,7 +3236,7 @@ function extractLinks(text, html, { maxLength = 5e5, bareHosts: bareHosts2 = fal
|
|
|
3205
3236
|
}
|
|
3206
3237
|
let url;
|
|
3207
3238
|
try {
|
|
3208
|
-
url =
|
|
3239
|
+
url = boundedUrl(href);
|
|
3209
3240
|
} catch {
|
|
3210
3241
|
return;
|
|
3211
3242
|
}
|
|
@@ -3530,7 +3561,7 @@ function isDeceptiveLink(link, { from } = {}) {
|
|
|
3530
3561
|
}
|
|
3531
3562
|
let shownUrl;
|
|
3532
3563
|
try {
|
|
3533
|
-
shownUrl =
|
|
3564
|
+
shownUrl = boundedUrl(/^https?:\/\//i.test(link.text) ? link.text : `http://${link.text}`);
|
|
3534
3565
|
} catch {
|
|
3535
3566
|
return false;
|
|
3536
3567
|
}
|
|
@@ -5343,7 +5374,7 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
|
|
|
5343
5374
|
}
|
|
5344
5375
|
}
|
|
5345
5376
|
async function expandAttachments(attachments = [], errors = [], parse = parseMessage, gets = {}) {
|
|
5346
|
-
const { unopened = [], mails = [] } = Array.isArray(gets) ? { unopened: gets } : gets;
|
|
5377
|
+
const { unopened = [], mails = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
|
|
5347
5378
|
const all = [];
|
|
5348
5379
|
const deadline = Date.now() + PARSE_TIMEOUT;
|
|
5349
5380
|
let budget = MAX_NESTED_BYTES;
|
|
@@ -5411,6 +5442,11 @@ import { isIP } from "node:net";
|
|
|
5411
5442
|
import { domainToASCII } from "node:url";
|
|
5412
5443
|
import { getPublicSuffix } from "tldts";
|
|
5413
5444
|
var LOOKUP_LABEL = /^[a-z\d_-]{1,63}$/;
|
|
5445
|
+
var LABEL_SEPARATOR = /[.\u3002\uFF0E\uFF61]/g;
|
|
5446
|
+
function lastLabels(name) {
|
|
5447
|
+
const separators = [...name.matchAll(LABEL_SEPARATOR)];
|
|
5448
|
+
return separators.length < 128 ? 0 : separators.at(-128).index + 1;
|
|
5449
|
+
}
|
|
5414
5450
|
function lookupName(host) {
|
|
5415
5451
|
if (typeof host !== "string") {
|
|
5416
5452
|
return null;
|
|
@@ -5421,6 +5457,7 @@ function lookupName(host) {
|
|
|
5421
5457
|
}
|
|
5422
5458
|
let name = host.slice(0, end).toLowerCase();
|
|
5423
5459
|
if (/[\u0080-\u{10FFFF}]/u.test(name)) {
|
|
5460
|
+
name = name.slice(lastLabels(name));
|
|
5424
5461
|
name = domainToASCII(name) || name;
|
|
5425
5462
|
}
|
|
5426
5463
|
if (isIP(name.replaceAll(/^\[|]$/g, "")) !== 0) {
|
|
@@ -5691,7 +5728,7 @@ function asciiHostname(value) {
|
|
|
5691
5728
|
return null;
|
|
5692
5729
|
}
|
|
5693
5730
|
const lower = value.normalize("NFKC").toLowerCase().replace(/\.$/, "");
|
|
5694
|
-
if (lower === "" || /[\0-,/:-^`{-\u007F]/.test(lower)) {
|
|
5731
|
+
if (lower === "" || lower.length > 1024 || /[\0-,/:-^`{-\u007F]/.test(lower)) {
|
|
5695
5732
|
return null;
|
|
5696
5733
|
}
|
|
5697
5734
|
const ascii = domainToASCII2(lower);
|
|
@@ -5873,8 +5910,8 @@ function trustReputation(result, trusted = []) {
|
|
|
5873
5910
|
for (const value of result.checkedValues.filter((item) => allowed.has(item))) {
|
|
5874
5911
|
const signers = allowed.get(value);
|
|
5875
5912
|
const counts = (entry) => Boolean(entry) && (signers === null || signers.some((signer) => vouches(normalizeEntry(entry), signer)));
|
|
5876
|
-
const detail = result.details[value];
|
|
5877
|
-
const listed = result.allowlisted[value] || (detail?.isAllowlisted ? detail.allowlistValue : null);
|
|
5913
|
+
const detail = Object.hasOwn(result.details, value) ? result.details[value] : void 0;
|
|
5914
|
+
const listed = Object.hasOwn(result.allowlisted, value) && result.allowlisted[value] || (detail?.isAllowlisted ? detail.allowlistValue : null);
|
|
5878
5915
|
if (counts(listed) && !out.isAllowlisted) {
|
|
5879
5916
|
out.isAllowlisted = true;
|
|
5880
5917
|
out.allowlistValue = listed;
|
|
@@ -6273,7 +6310,7 @@ import net from "node:net";
|
|
|
6273
6310
|
var package_default = {
|
|
6274
6311
|
name: "spamscanner",
|
|
6275
6312
|
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.",
|
|
6276
|
-
version: "7.0.
|
|
6313
|
+
version: "7.0.13",
|
|
6277
6314
|
author: "Forward Email (https://forwardemail.net)",
|
|
6278
6315
|
bin: {
|
|
6279
6316
|
spamscanner: "./dist/esm/cli.js"
|
|
@@ -6423,7 +6460,12 @@ var VERSION = package_default.version;
|
|
|
6423
6460
|
var MAX_MESSAGE_SIZE = 25 * 1024 * 1024;
|
|
6424
6461
|
var MAX_CONNECTIONS = 200;
|
|
6425
6462
|
function serializeResult(result, options = {}) {
|
|
6426
|
-
const {
|
|
6463
|
+
const {
|
|
6464
|
+
mail,
|
|
6465
|
+
tokens,
|
|
6466
|
+
attached,
|
|
6467
|
+
...rest
|
|
6468
|
+
} = result;
|
|
6427
6469
|
const output = {
|
|
6428
6470
|
...rest,
|
|
6429
6471
|
results: { ...result.results }
|
|
@@ -7324,6 +7366,7 @@ function reply(code, text, headers = [], body = null) {
|
|
|
7324
7366
|
lines2.push(...headers.map(([name, value]) => `${name}: ${value}`), "", "");
|
|
7325
7367
|
return body === null ? Buffer9.from(lines2.join("\r\n")) : Buffer9.concat([Buffer9.from(lines2.join("\r\n")), body]);
|
|
7326
7368
|
}
|
|
7369
|
+
var oneLine2 = (text) => String(text ?? "").replaceAll(/[\p{Cc}\u061C\u200E\u200F\u2028\u2029\u202A-\u202E\u2066-\u2069]+/gu, " ");
|
|
7327
7370
|
function report(result) {
|
|
7328
7371
|
const lines2 = [
|
|
7329
7372
|
`Spam detection software, running on the system "spamscanner", has ${result.isSpam ? "identified this incoming email as possible spam" : "scanned this incoming email"}.`,
|
|
@@ -7332,7 +7375,7 @@ function report(result) {
|
|
|
7332
7375
|
"",
|
|
7333
7376
|
" pts rule name description",
|
|
7334
7377
|
"---- ---------------------- --------------------------------------------------",
|
|
7335
|
-
...result.tests.map((test) => `${test.score.toFixed(1).padStart(4)} ${test.name.padEnd(22)} ${test.description}`)
|
|
7378
|
+
...result.tests.map((test) => `${test.score.toFixed(1).padStart(4)} ${oneLine2(test.name).padEnd(22)} ${oneLine2(test.description)}`)
|
|
7336
7379
|
];
|
|
7337
7380
|
return `${lines2.join("\r\n")}\r
|
|
7338
7381
|
`;
|
|
@@ -8355,7 +8398,8 @@ var MAX_EMBEDDED_LENGTH = 10 * 1024 * 1024;
|
|
|
8355
8398
|
var MAX_ATTACHED = 100;
|
|
8356
8399
|
var MAX_ATTACHED_BYTES = 50 * 1024 * 1024;
|
|
8357
8400
|
var MAX_READ_TIME = 5e3;
|
|
8358
|
-
var MAX_SHOWN_BYTES =
|
|
8401
|
+
var MAX_SHOWN_BYTES = 1024 * 1024;
|
|
8402
|
+
var MAX_CARD_TEXT = 8 * 1024 * 1024;
|
|
8359
8403
|
function isHostCode2(code) {
|
|
8360
8404
|
return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 45 || code === 46;
|
|
8361
8405
|
}
|
|
@@ -8384,7 +8428,7 @@ function urlOf(value) {
|
|
|
8384
8428
|
return null;
|
|
8385
8429
|
}
|
|
8386
8430
|
try {
|
|
8387
|
-
return
|
|
8431
|
+
return boundedUrl(href);
|
|
8388
8432
|
} catch {
|
|
8389
8433
|
return null;
|
|
8390
8434
|
}
|
|
@@ -8474,7 +8518,26 @@ function* attributesOf(html) {
|
|
|
8474
8518
|
TAG_START.lastIndex = Math.max(i, match.index + 1);
|
|
8475
8519
|
}
|
|
8476
8520
|
}
|
|
8477
|
-
|
|
8521
|
+
var MAX_VALUE2 = 1024 * 1024;
|
|
8522
|
+
var MAX_REFERENCES = 64 * 1024;
|
|
8523
|
+
function isCut(value) {
|
|
8524
|
+
if (value.length <= MAX_VALUE2) {
|
|
8525
|
+
return false;
|
|
8526
|
+
}
|
|
8527
|
+
let references = 0;
|
|
8528
|
+
for (let index = value.indexOf("&"); index !== -1; index = value.indexOf("&", index + 1)) {
|
|
8529
|
+
if (++references > MAX_REFERENCES) {
|
|
8530
|
+
return true;
|
|
8531
|
+
}
|
|
8532
|
+
}
|
|
8533
|
+
return false;
|
|
8534
|
+
}
|
|
8535
|
+
var LONG_VALUE = 64 * 1024;
|
|
8536
|
+
function linksOf(name, value, { add, embed, cut }) {
|
|
8537
|
+
if (isCut(value)) {
|
|
8538
|
+
cut();
|
|
8539
|
+
value = value.slice(0, MAX_VALUE2);
|
|
8540
|
+
}
|
|
8478
8541
|
const decoded = decodeEntities(value, { attribute: true });
|
|
8479
8542
|
switch (name) {
|
|
8480
8543
|
case "srcdoc": {
|
|
@@ -8515,7 +8578,7 @@ function linksOf(name, value, add, embed) {
|
|
|
8515
8578
|
}
|
|
8516
8579
|
var MAX_LENIENT_VALUE = 4096;
|
|
8517
8580
|
var UNQUOTED_END = /* @__PURE__ */ new Set(["<", ">", '"', "'", "`", "="]);
|
|
8518
|
-
function lenientLinks(html,
|
|
8581
|
+
function lenientLinks(html, done, reader) {
|
|
8519
8582
|
LENIENT.lastIndex = 0;
|
|
8520
8583
|
let match;
|
|
8521
8584
|
while (!done() && (match = LENIENT.exec(html)) !== null) {
|
|
@@ -8531,8 +8594,8 @@ function lenientLinks(html, add, done, embed) {
|
|
|
8531
8594
|
}
|
|
8532
8595
|
if (end < html.length && html[end] !== quote) {
|
|
8533
8596
|
const close = html.indexOf(quote, end);
|
|
8534
|
-
if (close !== -1 && close - i <= MAX_LENIENT_VALUE) {
|
|
8535
|
-
linksOf(name, html.slice(i, close),
|
|
8597
|
+
if (close !== -1 && close - i <= MAX_LENIENT_VALUE && !reader.spend(close - i)) {
|
|
8598
|
+
linksOf(name, html.slice(i, close), reader);
|
|
8536
8599
|
}
|
|
8537
8600
|
}
|
|
8538
8601
|
} else {
|
|
@@ -8540,7 +8603,10 @@ function lenientLinks(html, add, done, embed) {
|
|
|
8540
8603
|
end++;
|
|
8541
8604
|
}
|
|
8542
8605
|
}
|
|
8543
|
-
|
|
8606
|
+
if (reader.spend(end - i)) {
|
|
8607
|
+
break;
|
|
8608
|
+
}
|
|
8609
|
+
linksOf(name, html.slice(i, end), reader);
|
|
8544
8610
|
}
|
|
8545
8611
|
}
|
|
8546
8612
|
function endsLink2(text, i) {
|
|
@@ -8711,8 +8777,17 @@ function* chunksOf(html) {
|
|
|
8711
8777
|
cutStart = false;
|
|
8712
8778
|
continue;
|
|
8713
8779
|
}
|
|
8714
|
-
|
|
8715
|
-
start
|
|
8780
|
+
let hard = end;
|
|
8781
|
+
while (hard > start + CHUNK_OVERLAP + 1 && /[\dA-Za-z#]/.test(html[hard - 1])) {
|
|
8782
|
+
hard--;
|
|
8783
|
+
}
|
|
8784
|
+
if (hard < end && html[hard - 1] === "&" && hard - 1 > start + CHUNK_OVERLAP) {
|
|
8785
|
+
hard--;
|
|
8786
|
+
} else {
|
|
8787
|
+
hard = end;
|
|
8788
|
+
}
|
|
8789
|
+
yield { text: html.slice(start, hard), cutStart, cutEnd: true };
|
|
8790
|
+
start = hard - CHUNK_OVERLAP;
|
|
8716
8791
|
cutStart = true;
|
|
8717
8792
|
continue;
|
|
8718
8793
|
}
|
|
@@ -8935,40 +9010,50 @@ function htmlDescriptions(text) {
|
|
|
8935
9010
|
return html.join("\n");
|
|
8936
9011
|
}
|
|
8937
9012
|
var BINARY = /;\s*(?:(?:encoding\s*=\s*)?"?(?:b|base64)"?|value\s*=\s*"?binary"?)\s*(?:;|$)/i;
|
|
8938
|
-
function withoutBinaryValues(text) {
|
|
9013
|
+
function withoutBinaryValues(text, limit = Number.POSITIVE_INFINITY) {
|
|
8939
9014
|
if (!/base64|binary|encoding\s*=\s*"?b|;\s*b\s*[;:]/i.test(text)) {
|
|
8940
9015
|
return text;
|
|
8941
9016
|
}
|
|
8942
9017
|
const lines2 = text.split(LINE_BREAK);
|
|
8943
9018
|
const out = [];
|
|
9019
|
+
let length = 0;
|
|
9020
|
+
const push = (line) => {
|
|
9021
|
+
out.push(line);
|
|
9022
|
+
length += line.length + 1;
|
|
9023
|
+
};
|
|
8944
9024
|
for (let i = 0; i < lines2.length; i++) {
|
|
9025
|
+
if (length > limit) {
|
|
9026
|
+
break;
|
|
9027
|
+
}
|
|
8945
9028
|
const line = lines2[i];
|
|
8946
9029
|
const colon = valueColon(line);
|
|
8947
9030
|
if (colon === -1 || !BINARY.test(line.slice(0, colon))) {
|
|
8948
|
-
|
|
9031
|
+
push(line);
|
|
8949
9032
|
continue;
|
|
8950
9033
|
}
|
|
8951
|
-
|
|
9034
|
+
let base64 = /^[\w+/=\s]*$/.test(line.slice(colon + 1));
|
|
8952
9035
|
let next = i + 1;
|
|
8953
9036
|
while (next < lines2.length && (lines2[next].startsWith(" ") || lines2[next].startsWith(" "))) {
|
|
8954
|
-
|
|
9037
|
+
base64 &&= /^[\w+/=\s]*$/.test(lines2[next]);
|
|
9038
|
+
next++;
|
|
8955
9039
|
}
|
|
8956
|
-
if (!
|
|
9040
|
+
if (!base64) {
|
|
8957
9041
|
for (let index = i; index < next; index++) {
|
|
8958
|
-
|
|
9042
|
+
push(lines2[index]);
|
|
8959
9043
|
}
|
|
8960
9044
|
i = next - 1;
|
|
8961
9045
|
continue;
|
|
8962
9046
|
}
|
|
8963
9047
|
while (next < lines2.length && lines2[next] !== "" && /^[\w+/=]+$/.test(lines2[next])) {
|
|
8964
|
-
|
|
9048
|
+
next++;
|
|
8965
9049
|
}
|
|
9050
|
+
const end = next;
|
|
8966
9051
|
if (next < lines2.length && lines2[next] === "") {
|
|
8967
9052
|
next++;
|
|
8968
9053
|
}
|
|
8969
|
-
i = next - 1;
|
|
8970
9054
|
const name = line.slice(0, colon).split(";")[0].split(".").pop().trim().toLowerCase();
|
|
8971
|
-
|
|
9055
|
+
push(MEDIA_PROPERTIES.has(name) ? line.slice(0, colon + 1) : line.slice(0, colon + 1) + Buffer13.from([line.slice(colon + 1), ...lines2.slice(i + 1, end)].join("").replaceAll(/\s/g, ""), "base64").toString("utf8"));
|
|
9056
|
+
i = next - 1;
|
|
8972
9057
|
}
|
|
8973
9058
|
return out.join("\n");
|
|
8974
9059
|
}
|
|
@@ -9007,59 +9092,77 @@ function charsetOf(attachment) {
|
|
|
9007
9092
|
const charset = headers && typeof headers.get === "function" ? headers.get("content-type")?.params?.charset : null;
|
|
9008
9093
|
return typeof charset === "string" ? charset.toLowerCase() : "";
|
|
9009
9094
|
}
|
|
9010
|
-
function
|
|
9011
|
-
const
|
|
9095
|
+
function kindOf(attachment) {
|
|
9096
|
+
const { content } = attachment ?? {};
|
|
9097
|
+
if (!Buffer13.isBuffer(content) || content.length === 0) {
|
|
9098
|
+
return null;
|
|
9099
|
+
}
|
|
9100
|
+
const type = typeof attachment.contentType === "string" ? attachment.contentType.toLowerCase() : "";
|
|
9101
|
+
const extensions = extensionsOf(attachment);
|
|
9102
|
+
const has = (set) => [...extensions].some((name) => set.has(name));
|
|
9103
|
+
if (MESSAGE_TYPES.has(type) || extensions.has("eml") || extensions.has("u8msg")) {
|
|
9104
|
+
return null;
|
|
9105
|
+
}
|
|
9106
|
+
const isHtml = HTML_TYPES.has(type) || has(HTML_EXTENSIONS);
|
|
9107
|
+
const isCard = !isHtml && (CARD_TYPES.has(type) || has(CARD_EXTENSIONS));
|
|
9108
|
+
const isText = isCard || !isHtml && type === "text/plain";
|
|
9109
|
+
return isHtml || isText ? { isHtml, isText, isCard } : null;
|
|
9110
|
+
}
|
|
9111
|
+
function* documentsOf(attachments, state, late) {
|
|
9112
|
+
const read = [];
|
|
9012
9113
|
let bytes = 0;
|
|
9013
9114
|
let opened = 0;
|
|
9014
9115
|
for (const attachment of attachments) {
|
|
9015
|
-
const
|
|
9016
|
-
if (!
|
|
9116
|
+
const kind = kindOf(attachment);
|
|
9117
|
+
if (!kind) {
|
|
9017
9118
|
continue;
|
|
9018
9119
|
}
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
if (MESSAGE_TYPES.has(type) || extensions.has("eml") || extensions.has("u8msg")) {
|
|
9023
|
-
continue;
|
|
9024
|
-
}
|
|
9025
|
-
const isHtml = HTML_TYPES.has(type) || has(HTML_EXTENSIONS);
|
|
9026
|
-
const isCard = !isHtml && (CARD_TYPES.has(type) || has(CARD_EXTENSIONS));
|
|
9027
|
-
const isText = isCard || !isHtml && type === "text/plain";
|
|
9028
|
-
if (!isHtml && !isText) {
|
|
9029
|
-
continue;
|
|
9030
|
-
}
|
|
9031
|
-
if (!isText && ++opened > MAX_ATTACHED || bytes + content.length > MAX_ATTACHED_BYTES) {
|
|
9032
|
-
documents.tooComplex = true;
|
|
9033
|
-
return documents;
|
|
9120
|
+
if (!kind.isText && ++opened > MAX_ATTACHED || bytes + attachment.content.length > MAX_ATTACHED_BYTES) {
|
|
9121
|
+
state.tooComplex = true;
|
|
9122
|
+
break;
|
|
9034
9123
|
}
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9124
|
+
bytes += attachment.content.length;
|
|
9125
|
+
read.push({ attachment, kind });
|
|
9126
|
+
}
|
|
9127
|
+
for (const { attachment, kind: { isHtml, isCard } } of read) {
|
|
9128
|
+
if (late()) {
|
|
9129
|
+
state.incomplete = true;
|
|
9130
|
+
return;
|
|
9038
9131
|
}
|
|
9039
|
-
|
|
9040
|
-
const text = decodeText(content, charsetOf(attachment));
|
|
9132
|
+
const text = decodeText(attachment.content, charsetOf(attachment));
|
|
9041
9133
|
if (isHtml) {
|
|
9042
|
-
|
|
9134
|
+
yield { html: text };
|
|
9043
9135
|
continue;
|
|
9044
9136
|
}
|
|
9045
9137
|
if (!isCard) {
|
|
9046
|
-
|
|
9138
|
+
yield { text };
|
|
9047
9139
|
continue;
|
|
9048
9140
|
}
|
|
9049
|
-
|
|
9141
|
+
let readable = withoutBinaryValues(text, MAX_CARD_TEXT);
|
|
9142
|
+
if (readable.length > MAX_CARD_TEXT) {
|
|
9143
|
+
state.tooComplex = true;
|
|
9144
|
+
readable = readable.slice(0, MAX_CARD_TEXT);
|
|
9145
|
+
}
|
|
9050
9146
|
const full = readable.length <= MAX_SHOWN_BYTES;
|
|
9051
9147
|
if (!full && hasQuotedPrintable(readable)) {
|
|
9052
|
-
|
|
9053
|
-
|
|
9148
|
+
state.tooComplex = true;
|
|
9149
|
+
}
|
|
9150
|
+
if (late()) {
|
|
9151
|
+
state.incomplete = true;
|
|
9152
|
+
return;
|
|
9153
|
+
}
|
|
9154
|
+
yield { text: shownText(readable, full) };
|
|
9155
|
+
if (late()) {
|
|
9156
|
+
state.incomplete = true;
|
|
9157
|
+
return;
|
|
9054
9158
|
}
|
|
9055
|
-
documents.push({ text: shownText(readable, full) });
|
|
9056
9159
|
const html = htmlDescriptions(readable);
|
|
9057
9160
|
if (html !== "") {
|
|
9058
|
-
|
|
9161
|
+
yield { html };
|
|
9059
9162
|
}
|
|
9060
9163
|
}
|
|
9061
|
-
return documents;
|
|
9062
9164
|
}
|
|
9165
|
+
var LATE_EVERY = 1024;
|
|
9063
9166
|
function carriedLinkHosts(mail, {
|
|
9064
9167
|
mails = [],
|
|
9065
9168
|
attachments = [],
|
|
@@ -9072,40 +9175,45 @@ function carriedLinkHosts(mail, {
|
|
|
9072
9175
|
const seenBare = /* @__PURE__ */ new Set();
|
|
9073
9176
|
const seenLoose = /* @__PURE__ */ new Set();
|
|
9074
9177
|
let more = false;
|
|
9075
|
-
let
|
|
9076
|
-
const
|
|
9077
|
-
const
|
|
9178
|
+
let full = false;
|
|
9179
|
+
const domains = /* @__PURE__ */ new Set();
|
|
9180
|
+
const state = { tooComplex: false, incomplete: false };
|
|
9181
|
+
const until = Date.now() + MAX_READ_TIME;
|
|
9182
|
+
const late = () => {
|
|
9183
|
+
if (!state.incomplete && Date.now() > until) {
|
|
9184
|
+
state.incomplete = true;
|
|
9185
|
+
}
|
|
9186
|
+
return state.incomplete;
|
|
9187
|
+
};
|
|
9188
|
+
let calls = 0;
|
|
9189
|
+
const done = () => full || state.incomplete || ++calls % LATE_EVERY === 0 && late();
|
|
9190
|
+
const looseDone = () => done() || loose.size > maxHosts;
|
|
9078
9191
|
const addLoose = (host) => {
|
|
9079
9192
|
if (!looseDone()) {
|
|
9080
9193
|
loose.add(host);
|
|
9081
9194
|
}
|
|
9082
9195
|
};
|
|
9083
|
-
const
|
|
9084
|
-
|
|
9085
|
-
const late = () => {
|
|
9086
|
-
if (!incomplete && Date.now() > until) {
|
|
9087
|
-
incomplete = true;
|
|
9088
|
-
}
|
|
9089
|
-
return incomplete;
|
|
9090
|
-
};
|
|
9091
|
-
const carried = documentsOf(attachments, until);
|
|
9092
|
-
tooComplex = Boolean(carried.tooComplex);
|
|
9093
|
-
incomplete = Boolean(carried.incomplete);
|
|
9094
|
-
const queue = [mail, ...mails, ...carried].map((document) => ({ document, depth: 0 }));
|
|
9196
|
+
const queue = [mail, ...mails].map((document) => ({ document, depth: 0 }));
|
|
9197
|
+
const carried = documentsOf(attachments, state, late);
|
|
9095
9198
|
let depth = 0;
|
|
9096
9199
|
let embedded = 0;
|
|
9097
9200
|
let embeddedLength = 0;
|
|
9201
|
+
const embeddedSeen = /* @__PURE__ */ new Set();
|
|
9098
9202
|
const embed = (html) => {
|
|
9203
|
+
if (html === "" || embeddedSeen.has(html)) {
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9099
9206
|
if (depth >= MAX_EMBED_DEPTH || embedded >= MAX_EMBEDDED || embeddedLength + html.length > MAX_EMBEDDED_LENGTH) {
|
|
9100
|
-
tooComplex = true;
|
|
9207
|
+
state.tooComplex = true;
|
|
9101
9208
|
return;
|
|
9102
9209
|
}
|
|
9210
|
+
embeddedSeen.add(html);
|
|
9103
9211
|
embedded++;
|
|
9104
9212
|
embeddedLength += html.length;
|
|
9105
9213
|
queue.push({ document: { html }, depth: depth + 1 });
|
|
9106
9214
|
};
|
|
9107
9215
|
const add = (value) => {
|
|
9108
|
-
if (
|
|
9216
|
+
if (full || typeof value !== "string" || value === "" || seen.has(value)) {
|
|
9109
9217
|
return;
|
|
9110
9218
|
}
|
|
9111
9219
|
if (seen.size < MAX_SEEN) {
|
|
@@ -9120,34 +9228,64 @@ function carriedLinkHosts(mail, {
|
|
|
9120
9228
|
}
|
|
9121
9229
|
const found = hostsOf(value);
|
|
9122
9230
|
if (found.unresolved) {
|
|
9123
|
-
tooComplex = true;
|
|
9231
|
+
state.tooComplex = true;
|
|
9124
9232
|
}
|
|
9125
9233
|
for (const host of found) {
|
|
9126
9234
|
if (hosts.has(host)) {
|
|
9127
9235
|
continue;
|
|
9128
9236
|
}
|
|
9129
|
-
|
|
9130
|
-
|
|
9237
|
+
const domain = registrableDomain(host);
|
|
9238
|
+
if (hosts.size < maxHosts || !domains.has(domain) && domains.size < maxHosts) {
|
|
9239
|
+
hosts.add(host);
|
|
9240
|
+
domains.add(domain);
|
|
9241
|
+
continue;
|
|
9242
|
+
}
|
|
9243
|
+
more = true;
|
|
9244
|
+
if (domains.size >= maxHosts) {
|
|
9245
|
+
full = true;
|
|
9131
9246
|
return;
|
|
9132
9247
|
}
|
|
9133
|
-
hosts.add(host);
|
|
9134
9248
|
}
|
|
9135
9249
|
};
|
|
9136
|
-
|
|
9250
|
+
let spent = 0;
|
|
9251
|
+
const spend = (length) => {
|
|
9252
|
+
spent += length;
|
|
9253
|
+
if (spent < LONG_VALUE) {
|
|
9254
|
+
return false;
|
|
9255
|
+
}
|
|
9256
|
+
spent = 0;
|
|
9257
|
+
return late();
|
|
9258
|
+
};
|
|
9259
|
+
const reader = {
|
|
9260
|
+
add,
|
|
9261
|
+
embed,
|
|
9262
|
+
spend,
|
|
9263
|
+
cut() {
|
|
9264
|
+
state.tooComplex = true;
|
|
9265
|
+
}
|
|
9266
|
+
};
|
|
9267
|
+
const readText = (source, { cutStart = false, cutEnd = false, exact = false } = {}) => {
|
|
9137
9268
|
for (const match of source.matchAll(URL_START)) {
|
|
9138
|
-
if (
|
|
9269
|
+
if (done()) {
|
|
9139
9270
|
break;
|
|
9140
9271
|
}
|
|
9141
9272
|
if (cutStart && match.index === 0) {
|
|
9142
9273
|
continue;
|
|
9143
9274
|
}
|
|
9144
9275
|
if (cutEnd && match.index + match[0].length === source.length) {
|
|
9276
|
+
if (exact && source.length - match.index < CHUNK_OVERLAP) {
|
|
9277
|
+
continue;
|
|
9278
|
+
}
|
|
9145
9279
|
const value = match[0];
|
|
9146
9280
|
const at = /^www\./i.test(value) ? 0 : value.indexOf("//") + 2;
|
|
9147
|
-
if (
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9281
|
+
if (!/[/?#\\]/.test(value.slice(at))) {
|
|
9282
|
+
state.tooComplex = true;
|
|
9283
|
+
continue;
|
|
9284
|
+
}
|
|
9285
|
+
add(value);
|
|
9286
|
+
const url = urlOf(value);
|
|
9287
|
+
if (url && isClickTracker(url)) {
|
|
9288
|
+
state.tooComplex = true;
|
|
9151
9289
|
}
|
|
9152
9290
|
continue;
|
|
9153
9291
|
}
|
|
@@ -9169,42 +9307,61 @@ function carriedLinkHosts(mail, {
|
|
|
9169
9307
|
});
|
|
9170
9308
|
}
|
|
9171
9309
|
};
|
|
9172
|
-
for (
|
|
9173
|
-
if (
|
|
9310
|
+
for (let index = 0; ; index++) {
|
|
9311
|
+
if (full || late()) {
|
|
9174
9312
|
break;
|
|
9175
9313
|
}
|
|
9176
|
-
(
|
|
9177
|
-
|
|
9314
|
+
if (index >= queue.length) {
|
|
9315
|
+
const next = carried.next();
|
|
9316
|
+
if (next.done) {
|
|
9317
|
+
break;
|
|
9318
|
+
}
|
|
9319
|
+
queue.push({ document: next.value, depth: 0 });
|
|
9320
|
+
}
|
|
9321
|
+
({ depth } = queue[index]);
|
|
9322
|
+
const { document } = queue[index];
|
|
9178
9323
|
const html = withoutNul(typeof document?.html === "string" ? document.html : "");
|
|
9179
9324
|
const text = withoutNul(typeof document?.text === "string" ? document.text : "");
|
|
9180
9325
|
for (const [name, value] of attributesOf(html)) {
|
|
9181
|
-
if (
|
|
9326
|
+
if (done()) {
|
|
9182
9327
|
break;
|
|
9183
9328
|
}
|
|
9184
|
-
if (LINK_ATTRIBUTES.has(name)) {
|
|
9185
|
-
|
|
9329
|
+
if (!LINK_ATTRIBUTES.has(name)) {
|
|
9330
|
+
continue;
|
|
9331
|
+
}
|
|
9332
|
+
if (spend(value.length)) {
|
|
9333
|
+
break;
|
|
9186
9334
|
}
|
|
9335
|
+
linksOf(name, value, reader);
|
|
9187
9336
|
}
|
|
9188
|
-
lenientLinks(html,
|
|
9337
|
+
lenientLinks(html, done, reader);
|
|
9189
9338
|
for (const pattern of [CSS_URL, CSS_IMPORT]) {
|
|
9190
9339
|
for (const [, link] of html.matchAll(pattern)) {
|
|
9191
|
-
if (
|
|
9340
|
+
if (done() || spend(link.length)) {
|
|
9192
9341
|
break;
|
|
9193
9342
|
}
|
|
9194
|
-
|
|
9343
|
+
const cut = isCut(link);
|
|
9344
|
+
if (cut) {
|
|
9345
|
+
state.tooComplex = true;
|
|
9346
|
+
}
|
|
9347
|
+
add(decodeEntities(cut ? link.slice(0, MAX_VALUE2) : link, { attribute: true }));
|
|
9195
9348
|
}
|
|
9196
9349
|
}
|
|
9197
|
-
|
|
9198
|
-
|
|
9350
|
+
for (const chunk2 of chunksOf(text)) {
|
|
9351
|
+
if (full || late()) {
|
|
9352
|
+
break;
|
|
9353
|
+
}
|
|
9354
|
+
readText(chunk2.text, { ...chunk2, exact: true });
|
|
9199
9355
|
}
|
|
9200
9356
|
for (const chunk2 of chunksOf(html)) {
|
|
9201
|
-
if (
|
|
9357
|
+
if (full || late()) {
|
|
9202
9358
|
break;
|
|
9203
9359
|
}
|
|
9204
|
-
|
|
9360
|
+
const decoded = decodeEntities(chunk2.text);
|
|
9361
|
+
readText(decoded, { ...chunk2, exact: decoded.endsWith(chunk2.text.slice(-CHUNK_OVERLAP)) });
|
|
9205
9362
|
}
|
|
9206
9363
|
}
|
|
9207
|
-
if (!more && !incomplete) {
|
|
9364
|
+
if (!more && !state.incomplete) {
|
|
9208
9365
|
for (const name of loose) {
|
|
9209
9366
|
if (hosts.size >= maxHosts) {
|
|
9210
9367
|
break;
|
|
@@ -9218,8 +9375,8 @@ function carriedLinkHosts(mail, {
|
|
|
9218
9375
|
return {
|
|
9219
9376
|
hosts: [...hosts],
|
|
9220
9377
|
more,
|
|
9221
|
-
tooComplex,
|
|
9222
|
-
incomplete
|
|
9378
|
+
tooComplex: state.tooComplex,
|
|
9379
|
+
incomplete: state.incomplete
|
|
9223
9380
|
};
|
|
9224
9381
|
}
|
|
9225
9382
|
|
|
@@ -10120,7 +10277,7 @@ var SpamScanner = class {
|
|
|
10120
10277
|
// (`from`: the sender's domain; `carried`: the link hosts of the whole
|
|
10121
10278
|
// message and of what it carries, see carriedLinkHosts)
|
|
10122
10279
|
async checkLinks(links, config, hosts = [], options = {}) {
|
|
10123
|
-
const { from = "", carried = null } = typeof options === "string" ? { from: options } : options;
|
|
10280
|
+
const { from = "", carried = null } = typeof options === "string" ? { from: options } : options ?? {};
|
|
10124
10281
|
const phishing = [];
|
|
10125
10282
|
const homographs = [];
|
|
10126
10283
|
const seenHosts = /* @__PURE__ */ new Set();
|
|
@@ -10180,15 +10337,15 @@ var SpamScanner = class {
|
|
|
10180
10337
|
for (const host of carriedHosts) {
|
|
10181
10338
|
seenHosts.add(host);
|
|
10182
10339
|
}
|
|
10183
|
-
const
|
|
10340
|
+
const own = [...found.filter((link) => link.text), ...found.filter((link) => !link.text), ...bare].map((link) => lookupName(destination(link))).filter(Boolean);
|
|
10184
10341
|
const firsts = /* @__PURE__ */ new Map();
|
|
10185
|
-
for (const host of
|
|
10342
|
+
for (const host of [...own, ...carriedHosts]) {
|
|
10186
10343
|
const domain = registrableDomain(host);
|
|
10187
10344
|
if (!firsts.has(domain)) {
|
|
10188
10345
|
firsts.set(domain, host);
|
|
10189
10346
|
}
|
|
10190
10347
|
}
|
|
10191
|
-
const checked = [.../* @__PURE__ */ new Set([...firsts.values(), ...
|
|
10348
|
+
const checked = [.../* @__PURE__ */ new Set([...firsts.values(), ...own, ...carriedHosts])].slice(0, config.phishing.maxHosts);
|
|
10192
10349
|
const domains = [...new Set(checked.map((host) => registrableDomain(host)))];
|
|
10193
10350
|
const blocked = [];
|
|
10194
10351
|
let answered = 0;
|
|
@@ -10925,17 +11082,18 @@ async function readInput(file, stdin, maxSize) {
|
|
|
10925
11082
|
}
|
|
10926
11083
|
return data;
|
|
10927
11084
|
}
|
|
11085
|
+
var printable = (text) => String(text ?? "").replaceAll(/[\p{Cc}\u061C\u200E\u200F\u2028\u2029\u202A-\u202E\u2066-\u2069]+/gu, " ");
|
|
10928
11086
|
function formatResult(result, verbose) {
|
|
10929
11087
|
const lines2 = [`${result.isSpam ? "SPAM" : "HAM"} score ${result.score.toFixed(1)} (spam at ${result.threshold.toFixed(1)}, reject at ${result.rejectThreshold.toFixed(1)}) action: ${result.action}${result.language ? ` language: ${result.language}` : ""}`];
|
|
10930
11088
|
const tests = verbose ? result.tests : result.tests.filter((test) => test.score !== 0);
|
|
10931
11089
|
for (const test of [...tests].sort((a, b) => Math.abs(b.score) - Math.abs(a.score))) {
|
|
10932
|
-
lines2.push(` ${`${test.score > 0 ? "+" : ""}${test.score.toFixed(1)}`.padStart(7)} ${test.name.padEnd(28)} ${test.description}`);
|
|
11090
|
+
lines2.push(` ${`${test.score > 0 ? "+" : ""}${test.score.toFixed(1)}`.padStart(7)} ${printable(test.name).padEnd(28)} ${printable(test.description)}`);
|
|
10933
11091
|
}
|
|
10934
11092
|
if (verbose && result.results.classification.clues?.length > 0) {
|
|
10935
|
-
lines2.push(" Strongest clues:", ...result.results.classification.clues.slice(0, 10).map((clue) => ` ${clue.probability.toFixed(3)} ${clue.feature}`));
|
|
11093
|
+
lines2.push(" Strongest clues:", ...result.results.classification.clues.slice(0, 10).map((clue) => ` ${clue.probability.toFixed(3)} ${printable(clue.feature)}`));
|
|
10936
11094
|
}
|
|
10937
11095
|
if (result.results.llm?.error) {
|
|
10938
|
-
lines2.push(` Language model failed: ${result.results.llm.error}`);
|
|
11096
|
+
lines2.push(` Language model failed: ${printable(result.results.llm.error)}`);
|
|
10939
11097
|
}
|
|
10940
11098
|
return lines2.join("\n");
|
|
10941
11099
|
}
|
|
@@ -11073,11 +11231,11 @@ var HANDLERS = {
|
|
|
11073
11231
|
message = rewriteMessage(raw, spamHeaders(result, { version: VERSION }), { subjectTag: result.isSpam ? values["subject-tag"] ?? null : null });
|
|
11074
11232
|
} catch (error) {
|
|
11075
11233
|
if (!accept) {
|
|
11076
|
-
stderr.write(`filter: scan failed, deferring: ${error.message}
|
|
11234
|
+
stderr.write(`filter: scan failed, deferring: ${printable(error.message)}
|
|
11077
11235
|
`);
|
|
11078
11236
|
return 75;
|
|
11079
11237
|
}
|
|
11080
|
-
stderr.write(`filter: scan failed, passing the message on: ${error.message}
|
|
11238
|
+
stderr.write(`filter: scan failed, passing the message on: ${printable(error.message)}
|
|
11081
11239
|
`);
|
|
11082
11240
|
result = null;
|
|
11083
11241
|
message = rewriteMessage(raw, [["X-Spam-Virus-Scan", "failed"]]);
|
|
@@ -11110,10 +11268,10 @@ var HANDLERS = {
|
|
|
11110
11268
|
hostname: values.name || "spamscanner",
|
|
11111
11269
|
onError: onError(values)
|
|
11112
11270
|
});
|
|
11113
|
-
milter.on("error", (error) => stderr.write(`milter: ${error.message}
|
|
11271
|
+
milter.on("error", (error) => stderr.write(`milter: ${printable(error.message)}
|
|
11114
11272
|
`));
|
|
11115
11273
|
if (values.verbose) {
|
|
11116
|
-
milter.on("scan", ({ session, result }) => stderr.write(`${session.remoteAddress || "-"} ${session.envelope?.mailFrom?.address || "<>"} ${result.score.toFixed(1)} ${result.action} ${result.tests.map((test) => test.name).join(",")}
|
|
11274
|
+
milter.on("scan", ({ session, result }) => stderr.write(`${session.remoteAddress || "-"} ${printable(session.envelope?.mailFrom?.address) || "<>"} ${result.score.toFixed(1)} ${result.action} ${result.tests.map((test) => test.name).join(",")}
|
|
11117
11275
|
`));
|
|
11118
11276
|
}
|
|
11119
11277
|
await listen(milter.server, values, 7831, { stderr });
|