Bootstrap from registry - part 2

This commit is contained in:
mdecimus
2026-01-25 19:31:08 +01:00
parent 1c2c96b89d
commit da85cad9b7
35 changed files with 1148 additions and 1659 deletions

View File

@@ -6,7 +6,7 @@
use common::{
config::spamfilter::*,
expr::{StringCow, Variable, functions::ResolveVariable},
expr::{Expression, StringCow, Variable, functions::ResolveVariable},
};
use compact_str::{CompactString, ToCompactString, format_compact};
use mail_parser::{Header, HeaderValue};
@@ -31,25 +31,28 @@ impl<'x, T: ResolveVariable> SpamFilterResolver<'x, T> {
}
impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
fn resolve_variable(&self, variable: u32) -> Variable<'_> {
fn resolve_variable(&self, variable: ExpressionVariable) -> Variable<'_> {
match variable {
0..100 => self.item.resolve_variable(variable),
V_SPAM_REMOTE_IP => self.ctx.input.remote_ip.to_compact_string().into(),
V_SPAM_REMOTE_IP_PTR => self
ExpressionVariable::RemoteIp => self.ctx.input.remote_ip.to_compact_string().into(),
ExpressionVariable::RemoteIpPtr => self
.ctx
.output
.iprev_ptr
.as_deref()
.unwrap_or_default()
.into(),
V_SPAM_EHLO_DOMAIN => self.ctx.output.ehlo_host.fqdn.as_str().into(),
V_SPAM_AUTH_AS => self.ctx.input.authenticated_as.unwrap_or_default().into(),
V_SPAM_ASN => self.ctx.input.asn.unwrap_or_default().into(),
V_SPAM_COUNTRY => self.ctx.input.country.unwrap_or_default().into(),
V_SPAM_IS_TLS => self.ctx.input.is_tls.into(),
V_SPAM_ENV_FROM => self.ctx.output.env_from_addr.address.as_str().into(),
V_SPAM_ENV_FROM_LOCAL => self.ctx.output.env_from_addr.local_part.as_str().into(),
V_SPAM_ENV_FROM_DOMAIN => self
ExpressionVariable::HeloDomain => self.ctx.output.ehlo_host.fqdn.as_str().into(),
ExpressionVariable::AuthenticatedAs => {
self.ctx.input.authenticated_as.unwrap_or_default().into()
}
ExpressionVariable::Asn => self.ctx.input.asn.unwrap_or_default().into(),
ExpressionVariable::Country => self.ctx.input.country.unwrap_or_default().into(),
ExpressionVariable::IsTls => self.ctx.input.is_tls.into(),
ExpressionVariable::EnvFrom => self.ctx.output.env_from_addr.address.as_str().into(),
ExpressionVariable::EnvFromLocal => {
self.ctx.output.env_from_addr.local_part.as_str().into()
}
ExpressionVariable::EnvFromDomain => self
.ctx
.output
.env_from_addr
@@ -57,7 +60,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.fqdn
.as_str()
.into(),
V_SPAM_ENV_TO => self
ExpressionVariable::EnvTo => self
.ctx
.output
.env_to_addr
@@ -65,8 +68,8 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|e| Variable::from(e.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_FROM => self.ctx.output.from.email.address.as_str().into(),
V_SPAM_FROM_NAME => self
ExpressionVariable::From => self.ctx.output.from.email.address.as_str().into(),
ExpressionVariable::FromName => self
.ctx
.output
.from
@@ -74,9 +77,11 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.as_deref()
.unwrap_or_default()
.into(),
V_SPAM_FROM_LOCAL => self.ctx.output.from.email.local_part.as_str().into(),
V_SPAM_FROM_DOMAIN => self.ctx.output.from.email.domain_part.fqdn.as_str().into(),
V_SPAM_REPLY_TO => self
ExpressionVariable::FromLocal => self.ctx.output.from.email.local_part.as_str().into(),
ExpressionVariable::FromDomain => {
self.ctx.output.from.email.domain_part.fqdn.as_str().into()
}
ExpressionVariable::ReplyTo => self
.ctx
.output
.reply_to
@@ -84,7 +89,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| r.email.address.as_str())
.unwrap_or_default()
.into(),
V_SPAM_REPLY_TO_NAME => self
ExpressionVariable::ReplyToName => self
.ctx
.output
.reply_to
@@ -92,7 +97,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.and_then(|r| r.name.as_deref())
.unwrap_or_default()
.into(),
V_SPAM_REPLY_TO_LOCAL => self
ExpressionVariable::ReplyToLocal => self
.ctx
.output
.reply_to
@@ -100,7 +105,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| r.email.local_part.as_str())
.unwrap_or_default()
.into(),
V_SPAM_REPLY_TO_DOMAIN => self
ExpressionVariable::ReplyToDomain => self
.ctx
.output
.reply_to
@@ -108,7 +113,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| r.email.domain_part.fqdn.as_str())
.unwrap_or_default()
.into(),
V_SPAM_TO => self
ExpressionVariable::To => self
.ctx
.output
.recipients_to
@@ -116,7 +121,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_TO_NAME => self
ExpressionVariable::ToName => self
.ctx
.output
.recipients_to
@@ -124,7 +129,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.filter_map(|r| Variable::from(r.name.as_deref()?).into())
.collect::<Vec<_>>()
.into(),
V_SPAM_TO_LOCAL => self
ExpressionVariable::ToLocal => self
.ctx
.output
.recipients_to
@@ -132,7 +137,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.local_part.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_TO_DOMAIN => self
ExpressionVariable::ToDomain => self
.ctx
.output
.recipients_to
@@ -140,7 +145,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.domain_part.fqdn.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_CC => self
ExpressionVariable::Cc => self
.ctx
.output
.recipients_cc
@@ -148,7 +153,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_CC_NAME => self
ExpressionVariable::CcName => self
.ctx
.output
.recipients_cc
@@ -156,7 +161,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.filter_map(|r| Variable::from(r.name.as_deref()?).into())
.collect::<Vec<_>>()
.into(),
V_SPAM_CC_LOCAL => self
ExpressionVariable::CcLocal => self
.ctx
.output
.recipients_cc
@@ -164,7 +169,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.local_part.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_CC_DOMAIN => self
ExpressionVariable::CcDomain => self
.ctx
.output
.recipients_cc
@@ -172,7 +177,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.domain_part.fqdn.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_BCC => self
ExpressionVariable::Bcc => self
.ctx
.output
.recipients_bcc
@@ -180,7 +185,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_BCC_NAME => self
ExpressionVariable::BccName => self
.ctx
.output
.recipients_bcc
@@ -188,7 +193,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.filter_map(|r| Variable::from(r.name.as_deref()?).into())
.collect::<Vec<_>>()
.into(),
V_SPAM_BCC_LOCAL => self
ExpressionVariable::BccLocal => self
.ctx
.output
.recipients_bcc
@@ -196,7 +201,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.local_part.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_BCC_DOMAIN => self
ExpressionVariable::BccDomain => self
.ctx
.output
.recipients_bcc
@@ -204,8 +209,10 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
.map(|r| Variable::from(r.email.domain_part.fqdn.as_str()))
.collect::<Vec<_>>()
.into(),
V_SPAM_BODY_TEXT => self.ctx.text_body().unwrap_or_default().into(),
V_SPAM_BODY_HTML => self
ExpressionVariable::Body | ExpressionVariable::BodyText => {
self.ctx.text_body().unwrap_or_default().into()
}
ExpressionVariable::BodyHtml => self
.ctx
.input
.message
@@ -221,13 +228,13 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
})
.unwrap_or_default()
.into(),
V_SPAM_BODY_RAW => Variable::from(CompactString::from_utf8_lossy(
ExpressionVariable::BodyRaw => Variable::from(CompactString::from_utf8_lossy(
self.ctx.input.message.raw_message(),
)),
V_SPAM_SUBJECT => self.ctx.output.subject_lc.as_str().into(),
V_SPAM_SUBJECT_THREAD => self.ctx.output.subject_thread_lc.as_str().into(),
V_SPAM_LOCATION => self.location.as_str().into(),
V_WORDS_SUBJECT => self
ExpressionVariable::Subject => self.ctx.output.subject_lc.as_str().into(),
ExpressionVariable::SubjectThread => self.ctx.output.subject_thread_lc.as_str().into(),
ExpressionVariable::Location => self.location.as_str().into(),
ExpressionVariable::SubjectWords => self
.ctx
.output
.subject_tokens
@@ -241,7 +248,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
})
.collect::<Vec<_>>()
.into(),
V_WORDS_BODY => self
ExpressionVariable::BodyWords => self
.ctx
.input
.message
@@ -263,7 +270,7 @@ impl<T: ResolveVariable> ResolveVariable for SpamFilterResolver<'_, T> {
})
.unwrap_or_default()
.into(),
_ => Variable::Integer(0),
variable => self.item.resolve_variable(variable),
}
}
@@ -278,13 +285,17 @@ pub(crate) struct EmailHeader<'x> {
}
impl ResolveVariable for EmailHeader<'_> {
fn resolve_variable(&self, variable: u32) -> Variable<'_> {
fn resolve_variable(&self, variable: ExpressionVariable) -> Variable<'_> {
match variable {
V_HEADER_NAME => self.header.name().into(),
V_HEADER_NAME_LOWER => CompactString::from_str_to_lowercase(self.header.name()).into(),
V_HEADER_VALUE | V_HEADER_VALUE_LOWER | V_HEADER_PROPERTY => match &self.header.value {
ExpressionVariable::Name => self.header.name().into(),
ExpressionVariable::NameLower => {
CompactString::from_str_to_lowercase(self.header.name()).into()
}
ExpressionVariable::Value
| ExpressionVariable::ValueLower
| ExpressionVariable::Attributes => match &self.header.value {
HeaderValue::Text(text) => {
if variable == V_HEADER_VALUE_LOWER {
if variable == ExpressionVariable::ValueLower {
CompactString::from_str_to_lowercase(text).into()
} else {
text.as_ref().into()
@@ -293,7 +304,7 @@ impl ResolveVariable for EmailHeader<'_> {
HeaderValue::TextList(list) => Variable::Array(
list.iter()
.map(|text| {
Variable::String(if variable == V_HEADER_VALUE_LOWER {
Variable::String(if variable == ExpressionVariable::ValueLower {
StringCow::Owned(CompactString::from_str_to_lowercase(text))
} else {
StringCow::Borrowed(text.as_ref())
@@ -306,7 +317,7 @@ impl ResolveVariable for EmailHeader<'_> {
.iter()
.filter_map(|a| {
a.address.as_ref().map(|text| {
Variable::String(if variable == V_HEADER_VALUE_LOWER {
Variable::String(if variable == ExpressionVariable::ValueLower {
StringCow::Owned(CompactString::from_str_to_lowercase(text))
} else {
StringCow::Borrowed(text.as_ref())
@@ -319,7 +330,7 @@ impl ResolveVariable for EmailHeader<'_> {
.iter()
.filter_map(|a| {
a.name.as_ref().map(|text| {
Variable::String(if variable == V_HEADER_VALUE_LOWER {
Variable::String(if variable == ExpressionVariable::ValueLower {
StringCow::Owned(CompactString::from_str_to_lowercase(text))
} else {
StringCow::Borrowed(text.as_ref())
@@ -332,7 +343,7 @@ impl ResolveVariable for EmailHeader<'_> {
CompactString::new(date_time.to_rfc3339()).into()
}
HeaderValue::ContentType(ct) => {
if variable != V_HEADER_PROPERTY {
if variable != ExpressionVariable::Attributes {
if let Some(st) = ct.subtype() {
format_compact!("{}/{}", ct.ctype(), st).into()
} else {
@@ -355,7 +366,7 @@ impl ResolveVariable for EmailHeader<'_> {
}
}
HeaderValue::Received(_) => {
if variable == V_HEADER_VALUE_LOWER {
if variable == ExpressionVariable::ValueLower {
CompactString::from_str_to_lowercase(self.raw.trim()).into()
} else {
self.raw.trim().into()
@@ -363,8 +374,8 @@ impl ResolveVariable for EmailHeader<'_> {
}
HeaderValue::Empty => "".into(),
},
V_HEADER_RAW => self.raw.into(),
V_HEADER_RAW_LOWER => CompactString::from_str_to_lowercase(self.raw).into(),
ExpressionVariable::Raw => self.raw.into(),
ExpressionVariable::RawLower => CompactString::from_str_to_lowercase(self.raw).into(),
_ => Variable::Integer(0),
}
}
@@ -375,13 +386,15 @@ impl ResolveVariable for EmailHeader<'_> {
}
impl ResolveVariable for Recipient {
fn resolve_variable(&self, variable: u32) -> Variable<'_> {
fn resolve_variable(&self, variable: ExpressionVariable) -> Variable<'_> {
match variable {
V_RCPT_EMAIL => Variable::from(self.email.address.as_str()),
V_RCPT_NAME => Variable::from(self.name.as_deref().unwrap_or_default()),
V_RCPT_LOCAL => Variable::from(self.email.local_part.as_str()),
V_RCPT_DOMAIN => Variable::from(self.email.domain_part.fqdn.as_str()),
V_RCPT_DOMAIN_SLD => Variable::from(self.email.domain_part.sld_or_default()),
ExpressionVariable::Email | ExpressionVariable::Value => {
Variable::from(self.email.address.as_str())
}
ExpressionVariable::Name => Variable::from(self.name.as_deref().unwrap_or_default()),
ExpressionVariable::Local => Variable::from(self.email.local_part.as_str()),
ExpressionVariable::Domain => Variable::from(self.email.domain_part.fqdn.as_str()),
ExpressionVariable::Sld => Variable::from(self.email.domain_part.sld_or_default()),
_ => Variable::Integer(0),
}
}
@@ -392,52 +405,54 @@ impl ResolveVariable for Recipient {
}
impl ResolveVariable for UrlParts<'_> {
fn resolve_variable(&self, variable: u32) -> Variable<'_> {
fn resolve_variable(&self, variable: ExpressionVariable) -> Variable<'_> {
match variable {
V_URL_FULL => Variable::from(self.url.as_str()),
V_URL_PATH_QUERY => Variable::from(
ExpressionVariable::Url | ExpressionVariable::Value => {
Variable::from(self.url.as_str())
}
ExpressionVariable::PathQuery => Variable::from(
self.url_parsed
.as_ref()
.and_then(|p| p.parts.path_and_query().map(|p| p.as_str()))
.unwrap_or_default(),
),
V_URL_PATH => Variable::from(
ExpressionVariable::UrlPath => Variable::from(
self.url_parsed
.as_ref()
.map(|p| p.parts.path())
.unwrap_or_default(),
),
V_URL_QUERY => Variable::from(
ExpressionVariable::Query => Variable::from(
self.url_parsed
.as_ref()
.and_then(|p| p.parts.query())
.unwrap_or_default(),
),
V_URL_SCHEME => Variable::from(
ExpressionVariable::Scheme => Variable::from(
self.url_parsed
.as_ref()
.and_then(|p| p.parts.scheme_str())
.unwrap_or_default(),
),
V_URL_AUTHORITY => Variable::from(
ExpressionVariable::Authority => Variable::from(
self.url_parsed
.as_ref()
.and_then(|p| p.parts.authority().map(|a| a.as_str()))
.unwrap_or_default(),
),
V_URL_HOST => Variable::from(
ExpressionVariable::Host => Variable::from(
self.url_parsed
.as_ref()
.map(|p| p.host.fqdn.as_str())
.unwrap_or_default(),
),
V_URL_HOST_SLD => Variable::from(
ExpressionVariable::Sld => Variable::from(
self.url_parsed
.as_ref()
.map(|p| p.host.sld_or_default())
.unwrap_or_default(),
),
V_URL_PORT => Variable::Integer(
ExpressionVariable::Port => Variable::Integer(
self.url_parsed
.as_ref()
.and_then(|p| p.parts.port_u16())
@@ -455,7 +470,7 @@ impl ResolveVariable for UrlParts<'_> {
pub struct StringResolver<'x>(pub &'x str);
impl ResolveVariable for StringResolver<'_> {
fn resolve_variable(&self, _: u32) -> Variable<'_> {
fn resolve_variable(&self, _: ExpressionVariable) -> Variable<'_> {
Variable::from(self.0)
}
@@ -467,7 +482,7 @@ impl ResolveVariable for StringResolver<'_> {
pub struct StringListResolver<'x>(pub &'x [String]);
impl ResolveVariable for StringListResolver<'_> {
fn resolve_variable(&self, _: u32) -> Variable<'_> {
fn resolve_variable(&self, _: ExpressionVariable) -> Variable<'_> {
Variable::Array(self.0.iter().map(|v| Variable::from(v.as_str())).collect())
}