spamscanner 7.0.12 → 7.0.14

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/cli.js CHANGED
@@ -6310,7 +6310,7 @@ import net from "node:net";
6310
6310
  var package_default = {
6311
6311
  name: "spamscanner",
6312
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.",
6313
- version: "7.0.12",
6313
+ version: "7.0.14",
6314
6314
  author: "Forward Email (https://forwardemail.net)",
6315
6315
  bin: {
6316
6316
  spamscanner: "./dist/esm/cli.js"
@@ -8398,8 +8398,8 @@ var MAX_EMBEDDED_LENGTH = 10 * 1024 * 1024;
8398
8398
  var MAX_ATTACHED = 100;
8399
8399
  var MAX_ATTACHED_BYTES = 50 * 1024 * 1024;
8400
8400
  var MAX_READ_TIME = 5e3;
8401
- var MAX_CARD_BYTES = 4 * 1024 * 1024;
8402
8401
  var MAX_SHOWN_BYTES = 1024 * 1024;
8402
+ var MAX_CARD_TEXT = 8 * 1024 * 1024;
8403
8403
  function isHostCode2(code) {
8404
8404
  return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 45 || code === 46;
8405
8405
  }
@@ -8519,9 +8519,22 @@ function* attributesOf(html) {
8519
8519
  }
8520
8520
  }
8521
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
+ }
8522
8535
  var LONG_VALUE = 64 * 1024;
8523
8536
  function linksOf(name, value, { add, embed, cut }) {
8524
- if (value.length > MAX_VALUE2) {
8537
+ if (isCut(value)) {
8525
8538
  cut();
8526
8539
  value = value.slice(0, MAX_VALUE2);
8527
8540
  }
@@ -8944,14 +8957,27 @@ function quotedPrintableValues(text) {
8944
8957
  }
8945
8958
  return out.join("\n");
8946
8959
  }
8947
- function shownText(text) {
8960
+ function hasQuotedPrintable(text) {
8961
+ const unfolded = text.replaceAll(FOLD, "");
8962
+ if (!/quoted-printable/i.test(unfolded)) {
8963
+ return false;
8964
+ }
8965
+ return unfolded.split(LINE_BREAK).some((line) => {
8966
+ const colon = valueColon(line);
8967
+ return colon !== -1 && QUOTED_PRINTABLE.test(line.slice(0, colon));
8968
+ });
8969
+ }
8970
+ function shownText(text, full = true) {
8948
8971
  const unfolded = text.replaceAll(FOLD, "");
8949
8972
  const shown = /* @__PURE__ */ new Set([text, readEscapes(unfolded)]);
8950
- const decoded = quotedPrintableValues(unfolded);
8951
- if (decoded !== unfolded) {
8952
- shown.add(readEscapes(decoded));
8953
- if (unfolded !== text) {
8954
- shown.add(quotedPrintableValues(text));
8973
+ let decoded = unfolded;
8974
+ if (full) {
8975
+ decoded = quotedPrintableValues(unfolded);
8976
+ if (decoded !== unfolded) {
8977
+ shown.add(readEscapes(decoded));
8978
+ if (unfolded !== text) {
8979
+ shown.add(quotedPrintableValues(text));
8980
+ }
8955
8981
  }
8956
8982
  }
8957
8983
  if (/\\[^nN,;\\]/.test(decoded)) {
@@ -8984,40 +9010,50 @@ function htmlDescriptions(text) {
8984
9010
  return html.join("\n");
8985
9011
  }
8986
9012
  var BINARY = /;\s*(?:(?:encoding\s*=\s*)?"?(?:b|base64)"?|value\s*=\s*"?binary"?)\s*(?:;|$)/i;
8987
- function withoutBinaryValues(text) {
9013
+ function withoutBinaryValues(text, limit = Number.POSITIVE_INFINITY) {
8988
9014
  if (!/base64|binary|encoding\s*=\s*"?b|;\s*b\s*[;:]/i.test(text)) {
8989
9015
  return text;
8990
9016
  }
8991
9017
  const lines2 = text.split(LINE_BREAK);
8992
9018
  const out = [];
9019
+ let length = 0;
9020
+ const push = (line) => {
9021
+ out.push(line);
9022
+ length += line.length + 1;
9023
+ };
8993
9024
  for (let i = 0; i < lines2.length; i++) {
9025
+ if (length > limit) {
9026
+ break;
9027
+ }
8994
9028
  const line = lines2[i];
8995
9029
  const colon = valueColon(line);
8996
9030
  if (colon === -1 || !BINARY.test(line.slice(0, colon))) {
8997
- out.push(line);
9031
+ push(line);
8998
9032
  continue;
8999
9033
  }
9000
- const value = [line.slice(colon + 1)];
9034
+ let base64 = /^[\w+/=\s]*$/.test(line.slice(colon + 1));
9001
9035
  let next = i + 1;
9002
9036
  while (next < lines2.length && (lines2[next].startsWith(" ") || lines2[next].startsWith(" "))) {
9003
- value.push(lines2[next++]);
9037
+ base64 &&= /^[\w+/=\s]*$/.test(lines2[next]);
9038
+ next++;
9004
9039
  }
9005
- if (!value.every((part) => /^[\w+/=\s]*$/.test(part))) {
9040
+ if (!base64) {
9006
9041
  for (let index = i; index < next; index++) {
9007
- out.push(lines2[index]);
9042
+ push(lines2[index]);
9008
9043
  }
9009
9044
  i = next - 1;
9010
9045
  continue;
9011
9046
  }
9012
9047
  while (next < lines2.length && lines2[next] !== "" && /^[\w+/=]+$/.test(lines2[next])) {
9013
- value.push(lines2[next++]);
9048
+ next++;
9014
9049
  }
9050
+ const end = next;
9015
9051
  if (next < lines2.length && lines2[next] === "") {
9016
9052
  next++;
9017
9053
  }
9018
- i = next - 1;
9019
9054
  const name = line.slice(0, colon).split(";")[0].split(".").pop().trim().toLowerCase();
9020
- out.push(MEDIA_PROPERTIES.has(name) ? line.slice(0, colon + 1) : line.slice(0, colon + 1) + Buffer13.from(value.join("").replaceAll(/\s/g, ""), "base64").toString("utf8"));
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;
9021
9057
  }
9022
9058
  return out.join("\n");
9023
9059
  }
@@ -9102,16 +9138,20 @@ function* documentsOf(attachments, state, late) {
9102
9138
  yield { text };
9103
9139
  continue;
9104
9140
  }
9105
- let readable = withoutBinaryValues(text.length > MAX_CARD_BYTES ? text.slice(0, MAX_CARD_BYTES) : text);
9106
- if (text.length > MAX_CARD_BYTES || readable.length > MAX_SHOWN_BYTES) {
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
+ }
9146
+ const full = readable.length <= MAX_SHOWN_BYTES;
9147
+ if (!full && hasQuotedPrintable(readable)) {
9107
9148
  state.tooComplex = true;
9108
- readable = readable.slice(0, MAX_SHOWN_BYTES);
9109
9149
  }
9110
9150
  if (late()) {
9111
9151
  state.incomplete = true;
9112
9152
  return;
9113
9153
  }
9114
- yield { text: shownText(readable) };
9154
+ yield { text: shownText(readable, full) };
9115
9155
  if (late()) {
9116
9156
  state.incomplete = true;
9117
9157
  return;
@@ -9300,10 +9340,11 @@ function carriedLinkHosts(mail, {
9300
9340
  if (done() || spend(link.length)) {
9301
9341
  break;
9302
9342
  }
9303
- if (link.length > MAX_VALUE2) {
9343
+ const cut = isCut(link);
9344
+ if (cut) {
9304
9345
  state.tooComplex = true;
9305
9346
  }
9306
- add(decodeEntities(link.slice(0, MAX_VALUE2), { attribute: true }));
9347
+ add(decodeEntities(cut ? link.slice(0, MAX_VALUE2) : link, { attribute: true }));
9307
9348
  }
9308
9349
  }
9309
9350
  for (const chunk2 of chunksOf(text)) {