From e7298df6104153c7a7bf4b71345d97435468d07b Mon Sep 17 00:00:00 2001
From: mdecimus
Date: Thu, 19 Dec 2024 18:12:47 +0100
Subject: [PATCH] Port Spam filter to Rust - part 9
---
crates/common/src/config/jmap/settings.rs | 13 -
crates/common/src/config/mod.rs | 2 +-
crates/common/src/config/spamfilter.rs | 81 +-
crates/common/src/enterprise/config.rs | 2 +-
crates/common/src/expr/mod.rs | 2 +-
crates/common/src/scripts/functions/html.rs | 422 ------
crates/common/src/scripts/functions/mod.rs | 9 +-
crates/common/src/scripts/functions/text.rs | 5 +
crates/common/src/scripts/plugins/text.rs | 3 +-
crates/jmap/src/email/ingest.rs | 5 +-
crates/nlp/src/tokenizers/types.rs | 2 +-
crates/smtp/src/inbound/mod.rs | 1 +
crates/smtp/src/inbound/spam.rs | 61 +
crates/smtp/src/scripts/event_loop.rs | 27 -
crates/smtp/src/scripts/mod.rs | 13 -
crates/spam-filter/src/analysis/bayes.rs | 3 +-
crates/spam-filter/src/analysis/date.rs | 41 +-
crates/spam-filter/src/analysis/dmarc.rs | 118 +-
crates/spam-filter/src/analysis/domain.rs | 44 +-
crates/spam-filter/src/analysis/from.rs | 2 +-
crates/spam-filter/src/analysis/headers.rs | 3 +-
crates/spam-filter/src/analysis/html.rs | 11 +-
crates/spam-filter/src/analysis/init.rs | 47 +-
crates/spam-filter/src/analysis/ip.rs | 28 +-
crates/spam-filter/src/analysis/messageid.rs | 31 +-
crates/spam-filter/src/analysis/mime.rs | 27 +-
crates/spam-filter/src/analysis/received.rs | 14 +-
crates/spam-filter/src/analysis/recipient.rs | 76 +-
crates/spam-filter/src/analysis/reputation.rs | 56 +-
crates/spam-filter/src/analysis/rules.rs | 6 +-
crates/spam-filter/src/analysis/score.rs | 50 +-
crates/spam-filter/src/analysis/subject.rs | 18 +-
crates/spam-filter/src/analysis/url.rs | 90 +-
crates/spam-filter/src/lib.rs | 21 +-
crates/spam-filter/src/modules/bayes.rs | 12 +-
crates/spam-filter/src/modules/dnsbl.rs | 29 +-
crates/spam-filter/src/modules/expression.rs | 43 +-
crates/spam-filter/src/modules/mod.rs | 2 +-
crates/spam-filter/src/modules/remote_list.rs | 10 +-
tests/resources/smtp/antispam/bounce.test | 15 +-
tests/resources/smtp/antispam/combined.test | 9 +-
tests/resources/smtp/antispam/from.test | 9 +-
tests/resources/smtp/antispam/headers.test | 6 +-
tests/resources/smtp/antispam/html.test | 2 +-
tests/resources/smtp/antispam/messageid.test | 6 +
tests/resources/smtp/antispam/mime.test | 2 +-
tests/resources/smtp/antispam/rbl.test | 19 +-
tests/resources/smtp/antispam/received.test | 9 +
tests/resources/smtp/antispam/recipient.test | 4 +-
tests/resources/smtp/antispam/replyto.test | 6 +-
tests/resources/smtp/antispam/subject.test | 27 +-
tests/resources/smtp/antispam/url.test | 18 +-
tests/src/smtp/inbound/antispam.rs | 1296 ++++++++++-------
53 files changed, 1506 insertions(+), 1352 deletions(-)
delete mode 100644 crates/common/src/scripts/functions/html.rs
create mode 100644 crates/smtp/src/inbound/spam.rs
diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs
index 78f4c4a0..cf6b105e 100644
--- a/crates/common/src/config/jmap/settings.rs
+++ b/crates/common/src/config/jmap/settings.rs
@@ -7,7 +7,6 @@
use std::{str::FromStr, time::Duration};
use jmap_proto::request::capability::BaseCapabilities;
-use mail_parser::HeaderName;
use nlp::language::Language;
use utils::config::{cron::SimpleCron, utils::ParseValue, Config, Rate};
@@ -65,7 +64,6 @@ pub struct JmapConfig {
pub fallback_admin: Option<(String, String)>,
pub master_user: Option<(String, String)>,
- pub spam_header: Option<(HeaderName<'static>, String)>,
pub default_folders: Vec,
pub shared_folder: String,
@@ -334,17 +332,6 @@ impl JmapConfig {
encrypt_append: config
.property_or_default("storage.encryption.append", "false")
.unwrap_or(false),
- spam_header: config
- .property_or_default::
ßĂΒγ ",
- "don't hurt me.
"
- ),
- vec![
- Variable::from("",
- "this is the actual text"
- ),
- vec![
- Variable::from(
- concat!(
- "",
- "text",
- "< a href = test ignore>text",
- "< a href = fudge href ignore>text",
- " a href = \"unknown\" ",
- ),
- vec![
- Variable::from("hello world".to_string()),
- Variable::from("test".to_string()),
- Variable::from("fudge".to_string()),
- Variable::from("foobar".to_string()),
- ],
- ),
- ] {
- assert_eq!(
- html_attr_tokens(input, "a", vec![Cow::from("href")]),
- expected,
- "Failed for '{:?}'",
- input
- );
- }
-
- for (tag, attr_name, expected) in [
- ("
",
- "20",
- "30",
- "
",
- "
"
- ))),
- 92600
- );
-}
-
trait ParseConfigValue: Sized {
fn from_str(value: &str) -> Self;
}
@@ -819,3 +681,465 @@ impl ParseConfigValue for Policy {
}
}
}
+
+#[test]
+fn html_tokens() {
+ for (input, expected) in [
+ (
+ concat!("hello
world
"),
+ vec![
+ HtmlToken::StartTag {
+ name: 1819112552,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "hello".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ HtmlToken::Text {
+ text: "world".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ HtmlToken::EndTag { name: 1819112552 },
+ ],
+ ),
+ (
+ concat!("using <>
"),
+ vec![
+ HtmlToken::StartTag {
+ name: 1819112552,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "using <>".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ HtmlToken::EndTag { name: 1819112552 },
+ ],
+ ),
+ (
+ concat!("test tag
"),
+ vec![
+ HtmlToken::Text {
+ text: "test".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 7630702,
+ attributes: vec![(29282, None)],
+ is_self_closing: true,
+ },
+ HtmlToken::Text {
+ text: " tag".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ ],
+ ),
+ (
+ concat!("<>< >>hello world< br \n />"),
+ vec![
+ HtmlToken::StartTag {
+ name: 6775156,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ HtmlToken::Text {
+ text: ">hello world".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: true,
+ },
+ ],
+ ),
+ (
+ concat!(
+ "
ignore headxyz<body><",
+ "/h1>"
+ ),
+ vec![
+ HtmlToken::StartTag {
+ name: 1684104552,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::StartTag {
+ name: 435611265396,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "ignore head".to_string(),
+ },
+ HtmlToken::EndTag { name: 435611265396 },
+ HtmlToken::StartTag {
+ name: 7630702,
+ attributes: vec![(1684104552, None)],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "xyz".to_string(),
+ },
+ HtmlToken::EndTag { name: 7630702 },
+ HtmlToken::EndTag { name: 1684104552 },
+ HtmlToken::StartTag {
+ name: 12648,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "".to_string(),
+ },
+ HtmlToken::EndTag { name: 12648 },
+ ],
+ ),
+ (
+ concat!(
+ "
what is ♥?
ß&",
+ "Abreve;Βγ don't hurt me.",
+ "
"
+ ),
+ vec![
+ HtmlToken::StartTag {
+ name: 112,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "what is ♥?".to_string(),
+ },
+ HtmlToken::EndTag { name: 112 },
+ HtmlToken::StartTag {
+ name: 112,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "ßĂΒγ don't hurt me.".to_string(),
+ },
+ HtmlToken::EndTag { name: 112 },
+ ],
+ ),
+ (
+ concat!(
+ "this is the actual text"
+ ),
+ vec![
+ HtmlToken::Comment {
+ text: concat!(
+ "!--[if mso]> < < < < ignore > -> here --".to_string(),
+ },
+ HtmlToken::Text {
+ text: " the actual".to_string(),
+ },
+ HtmlToken::Comment {
+ text: "!--".to_string(),
+ },
+ HtmlToken::Text {
+ text: " text".to_string(),
+ },
+ ],
+ ),
+ (
+ concat!(
+ " < p > hello < / p > < p > world < / ",
+ "p > !!! < br > "
+ ),
+ vec![
+ HtmlToken::StartTag {
+ name: 112,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "hello".to_string(),
+ },
+ HtmlToken::EndTag { name: 112 },
+ HtmlToken::StartTag {
+ name: 112,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: " world".to_string(),
+ },
+ HtmlToken::EndTag { name: 112 },
+ HtmlToken::Text {
+ text: " !!!".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 29282,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ ],
+ ),
+ (
+ concat!(" please unsubscribe here.
"),
+ vec![
+ HtmlToken::StartTag {
+ name: 112,
+ attributes: vec![],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "please unsubscribe".to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("#".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: " here".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::Text {
+ text: ".".to_string(),
+ },
+ HtmlToken::EndTag { name: 112 },
+ ],
+ ),
+ (
+ concat!(
+ "texttexttexttext",
+ "< a href = \"e\" >texttext< anchor href = \"x\">t",
+ "ext"
+ ),
+ vec![
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("a".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("b".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("c".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("d".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("e".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(125779835187816, Some("ignore".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 125822818283105,
+ attributes: vec![(1717924456, Some("x".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ ],
+ ),
+ (
+ concat!(
+ "texttexttexttext< a ",
+ "href = e >textt",
+ "exttext"
+ ),
+ vec![
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("a".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("b".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("c".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("d".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("e".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(125779835187816, Some("ignore".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 125822818283105,
+ attributes: vec![(1717924456, Some("x".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ ],
+ ),
+ (
+ concat!(
+ "text< a href = test igno",
+ "re>text< a href = fudge href ignor",
+ "e>text a href = \"unkn",
+ "own\" "
+ ),
+ vec![
+ HtmlToken::Comment {
+ text: "!-- texttext--text--"
+ .to_string(),
+ },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("hello world".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![
+ (1717924456, Some("test".to_string())),
+ (111542170183529, None),
+ ],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![
+ (1717924456, Some("fudge".to_string())),
+ (1717924456, None),
+ (111542170183529, None),
+ ],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "text".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ HtmlToken::StartTag {
+ name: 97,
+ attributes: vec![(1717924456, Some("foobar".to_string()))],
+ is_self_closing: false,
+ },
+ HtmlToken::Text {
+ text: "a href = \"unknown\"".to_string(),
+ },
+ HtmlToken::EndTag { name: 97 },
+ ],
+ ),
+ ] {
+ assert_eq!(expected, html_to_tokens(input), "failed for {input:?}");
+ }
+}