spamscanner 7.0.12 → 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/esm/index.js CHANGED
@@ -5695,8 +5695,8 @@ var MAX_EMBEDDED_LENGTH = 10 * 1024 * 1024;
5695
5695
  var MAX_ATTACHED = 100;
5696
5696
  var MAX_ATTACHED_BYTES = 50 * 1024 * 1024;
5697
5697
  var MAX_READ_TIME = 5e3;
5698
- var MAX_CARD_BYTES = 4 * 1024 * 1024;
5699
5698
  var MAX_SHOWN_BYTES = 1024 * 1024;
5699
+ var MAX_CARD_TEXT = 8 * 1024 * 1024;
5700
5700
  function isHostCode2(code) {
5701
5701
  return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 45 || code === 46;
5702
5702
  }
@@ -5816,9 +5816,22 @@ function* attributesOf(html) {
5816
5816
  }
5817
5817
  }
5818
5818
  var MAX_VALUE2 = 1024 * 1024;
5819
+ var MAX_REFERENCES = 64 * 1024;
5820
+ function isCut(value) {
5821
+ if (value.length <= MAX_VALUE2) {
5822
+ return false;
5823
+ }
5824
+ let references = 0;
5825
+ for (let index = value.indexOf("&"); index !== -1; index = value.indexOf("&", index + 1)) {
5826
+ if (++references > MAX_REFERENCES) {
5827
+ return true;
5828
+ }
5829
+ }
5830
+ return false;
5831
+ }
5819
5832
  var LONG_VALUE = 64 * 1024;
5820
5833
  function linksOf(name, value, { add, embed, cut }) {
5821
- if (value.length > MAX_VALUE2) {
5834
+ if (isCut(value)) {
5822
5835
  cut();
5823
5836
  value = value.slice(0, MAX_VALUE2);
5824
5837
  }
@@ -6241,14 +6254,27 @@ function quotedPrintableValues(text) {
6241
6254
  }
6242
6255
  return out.join("\n");
6243
6256
  }
6244
- function shownText(text) {
6257
+ function hasQuotedPrintable(text) {
6258
+ const unfolded = text.replaceAll(FOLD, "");
6259
+ if (!/quoted-printable/i.test(unfolded)) {
6260
+ return false;
6261
+ }
6262
+ return unfolded.split(LINE_BREAK).some((line) => {
6263
+ const colon = valueColon(line);
6264
+ return colon !== -1 && QUOTED_PRINTABLE.test(line.slice(0, colon));
6265
+ });
6266
+ }
6267
+ function shownText(text, full = true) {
6245
6268
  const unfolded = text.replaceAll(FOLD, "");
6246
6269
  const shown = /* @__PURE__ */ new Set([text, readEscapes(unfolded)]);
6247
- const decoded = quotedPrintableValues(unfolded);
6248
- if (decoded !== unfolded) {
6249
- shown.add(readEscapes(decoded));
6250
- if (unfolded !== text) {
6251
- shown.add(quotedPrintableValues(text));
6270
+ let decoded = unfolded;
6271
+ if (full) {
6272
+ decoded = quotedPrintableValues(unfolded);
6273
+ if (decoded !== unfolded) {
6274
+ shown.add(readEscapes(decoded));
6275
+ if (unfolded !== text) {
6276
+ shown.add(quotedPrintableValues(text));
6277
+ }
6252
6278
  }
6253
6279
  }
6254
6280
  if (/\\[^nN,;\\]/.test(decoded)) {
@@ -6281,40 +6307,50 @@ function htmlDescriptions(text) {
6281
6307
  return html.join("\n");
6282
6308
  }
6283
6309
  var BINARY = /;\s*(?:(?:encoding\s*=\s*)?"?(?:b|base64)"?|value\s*=\s*"?binary"?)\s*(?:;|$)/i;
6284
- function withoutBinaryValues(text) {
6310
+ function withoutBinaryValues(text, limit = Number.POSITIVE_INFINITY) {
6285
6311
  if (!/base64|binary|encoding\s*=\s*"?b|;\s*b\s*[;:]/i.test(text)) {
6286
6312
  return text;
6287
6313
  }
6288
6314
  const lines2 = text.split(LINE_BREAK);
6289
6315
  const out = [];
6316
+ let length = 0;
6317
+ const push = (line) => {
6318
+ out.push(line);
6319
+ length += line.length + 1;
6320
+ };
6290
6321
  for (let i = 0; i < lines2.length; i++) {
6322
+ if (length > limit) {
6323
+ break;
6324
+ }
6291
6325
  const line = lines2[i];
6292
6326
  const colon = valueColon(line);
6293
6327
  if (colon === -1 || !BINARY.test(line.slice(0, colon))) {
6294
- out.push(line);
6328
+ push(line);
6295
6329
  continue;
6296
6330
  }
6297
- const value = [line.slice(colon + 1)];
6331
+ let base64 = /^[\w+/=\s]*$/.test(line.slice(colon + 1));
6298
6332
  let next = i + 1;
6299
6333
  while (next < lines2.length && (lines2[next].startsWith(" ") || lines2[next].startsWith(" "))) {
6300
- value.push(lines2[next++]);
6334
+ base64 &&= /^[\w+/=\s]*$/.test(lines2[next]);
6335
+ next++;
6301
6336
  }
6302
- if (!value.every((part) => /^[\w+/=\s]*$/.test(part))) {
6337
+ if (!base64) {
6303
6338
  for (let index = i; index < next; index++) {
6304
- out.push(lines2[index]);
6339
+ push(lines2[index]);
6305
6340
  }
6306
6341
  i = next - 1;
6307
6342
  continue;
6308
6343
  }
6309
6344
  while (next < lines2.length && lines2[next] !== "" && /^[\w+/=]+$/.test(lines2[next])) {
6310
- value.push(lines2[next++]);
6345
+ next++;
6311
6346
  }
6347
+ const end = next;
6312
6348
  if (next < lines2.length && lines2[next] === "") {
6313
6349
  next++;
6314
6350
  }
6315
- i = next - 1;
6316
6351
  const name = line.slice(0, colon).split(";")[0].split(".").pop().trim().toLowerCase();
6317
- out.push(MEDIA_PROPERTIES.has(name) ? line.slice(0, colon + 1) : line.slice(0, colon + 1) + Buffer7.from(value.join("").replaceAll(/\s/g, ""), "base64").toString("utf8"));
6352
+ push(MEDIA_PROPERTIES.has(name) ? line.slice(0, colon + 1) : line.slice(0, colon + 1) + Buffer7.from([line.slice(colon + 1), ...lines2.slice(i + 1, end)].join("").replaceAll(/\s/g, ""), "base64").toString("utf8"));
6353
+ i = next - 1;
6318
6354
  }
6319
6355
  return out.join("\n");
6320
6356
  }
@@ -6399,16 +6435,20 @@ function* documentsOf(attachments, state, late) {
6399
6435
  yield { text };
6400
6436
  continue;
6401
6437
  }
6402
- let readable = withoutBinaryValues(text.length > MAX_CARD_BYTES ? text.slice(0, MAX_CARD_BYTES) : text);
6403
- if (text.length > MAX_CARD_BYTES || readable.length > MAX_SHOWN_BYTES) {
6438
+ let readable = withoutBinaryValues(text, MAX_CARD_TEXT);
6439
+ if (readable.length > MAX_CARD_TEXT) {
6440
+ state.tooComplex = true;
6441
+ readable = readable.slice(0, MAX_CARD_TEXT);
6442
+ }
6443
+ const full = readable.length <= MAX_SHOWN_BYTES;
6444
+ if (!full && hasQuotedPrintable(readable)) {
6404
6445
  state.tooComplex = true;
6405
- readable = readable.slice(0, MAX_SHOWN_BYTES);
6406
6446
  }
6407
6447
  if (late()) {
6408
6448
  state.incomplete = true;
6409
6449
  return;
6410
6450
  }
6411
- yield { text: shownText(readable) };
6451
+ yield { text: shownText(readable, full) };
6412
6452
  if (late()) {
6413
6453
  state.incomplete = true;
6414
6454
  return;
@@ -6597,10 +6637,11 @@ function carriedLinkHosts(mail, {
6597
6637
  if (done() || spend(link.length)) {
6598
6638
  break;
6599
6639
  }
6600
- if (link.length > MAX_VALUE2) {
6640
+ const cut = isCut(link);
6641
+ if (cut) {
6601
6642
  state.tooComplex = true;
6602
6643
  }
6603
- add(decodeEntities(link.slice(0, MAX_VALUE2), { attribute: true }));
6644
+ add(decodeEntities(cut ? link.slice(0, MAX_VALUE2) : link, { attribute: true }));
6604
6645
  }
6605
6646
  }
6606
6647
  for (const chunk2 of chunksOf(text)) {
@@ -8278,7 +8319,7 @@ function scoreResults(results, options) {
8278
8319
  var package_default = {
8279
8320
  name: "spamscanner",
8280
8321
  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.",
8281
- version: "7.0.12",
8322
+ version: "7.0.13",
8282
8323
  author: "Forward Email (https://forwardemail.net)",
8283
8324
  bin: {
8284
8325
  spamscanner: "./dist/esm/cli.js"