diff options
Diffstat (limited to 'crates/smtp')
28 files changed, 372 insertions, 561 deletions
diff --git a/crates/smtp/src/config/mod.rs b/crates/smtp/src/config/mod.rs index 13c4b1fc..f39881eb 100644 --- a/crates/smtp/src/config/mod.rs +++ b/crates/smtp/src/config/mod.rs @@ -417,6 +417,7 @@ pub struct QueueOutboundTls { pub dane: IfBlock<RequireOptional>, pub mta_sts: IfBlock<RequireOptional>, pub start: IfBlock<RequireOptional>, + pub invalid_certs: IfBlock<bool>, } pub struct QueueOutboundTimeout { diff --git a/crates/smtp/src/config/queue.rs b/crates/smtp/src/config/queue.rs index b666370b..09b0470a 100644 --- a/crates/smtp/src/config/queue.rs +++ b/crates/smtp/src/config/queue.rs @@ -148,6 +148,13 @@ impl ConfigQueue for Config { start: self .parse_if_block("queue.outbound.tls.starttls", ctx, &mx_envelope_keys)? .unwrap_or_else(|| IfBlock::new(RequireOptional::Optional)), + invalid_certs: self + .parse_if_block( + "queue.outbound.tls.allow-invalid-certs", + ctx, + &mx_envelope_keys, + )? + .unwrap_or_else(|| IfBlock::new(false)), }, throttle: self.parse_queue_throttle(ctx)?, quota: self.parse_queue_quota(ctx)?, diff --git a/crates/smtp/src/config/scripts.rs b/crates/smtp/src/config/scripts.rs index c841e2b6..71040842 100644 --- a/crates/smtp/src/config/scripts.rs +++ b/crates/smtp/src/config/scripts.rs @@ -21,9 +21,8 @@ * for more details. */ -use std::{sync::Arc, time::Duration}; +use std::time::Duration; -use directory::Lookup; use nlp::bayes::{cache::BayesTokenCache, BayesClassifier}; use sieve::{compiler::grammar::Capability, Compiler, Runtime}; @@ -42,12 +41,11 @@ pub trait ConfigSieve { fn parse_sieve(&self, ctx: &mut ConfigContext) -> super::Result<SieveCore>; } +#[derive(Default)] pub struct SieveContext { pub psl: PublicSuffix, pub bayes_classify: BayesClassifier, pub bayes_cache: BayesTokenCache, - pub lookup_classify: Arc<Lookup>, - pub lookup_train: Arc<Lookup>, } impl ConfigSieve for Config { @@ -67,18 +65,6 @@ impl ConfigSieve for Config { self.property_or_static("bayes.cache.ttl.positive", "1h")?, self.property_or_static("bayes.cache.ttl.negative", "1h")?, ), - lookup_classify: ctx - .directory - .lookups - .get("bayes.tokens.classify") - .ok_or("No lookup found for key bayes.tokens.classify.".to_string())? - .clone(), - lookup_train: ctx - .directory - .lookups - .get("bayes.tokens.train") - .ok_or("No lookup found for key bayes.tokens.train.".to_string())? - .clone(), }; // Allocate compiler and runtime diff --git a/crates/smtp/src/inbound/data.rs b/crates/smtp/src/inbound/data.rs index a1c87c5b..11afb640 100644 --- a/crates/smtp/src/inbound/data.rs +++ b/crates/smtp/src/inbound/data.rs @@ -440,7 +440,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { .filter_map(|r| { if matches!(r.result(), DkimResult::Pass) { r.signature() - .map(|s| Variable::String(s.domain().to_lowercase())) + .map(|s| Variable::from(s.domain().to_lowercase())) } else { None } @@ -478,7 +478,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { edited_message = Arc::new(message).into(); } ScriptResult::Reject(message) => { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "sieve", event = "reject", reason = message); diff --git a/crates/smtp/src/inbound/ehlo.rs b/crates/smtp/src/inbound/ehlo.rs index d8521eaa..f36d7d68 100644 --- a/crates/smtp/src/inbound/ehlo.rs +++ b/crates/smtp/src/inbound/ehlo.rs @@ -41,7 +41,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { if domain != self.data.helo_domain { // Reject non-FQDN EHLO domains - simply checks that the hostname has at least one dot if self.params.ehlo_reject_non_fqdn && !domain.as_str().has_labels() { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "ehlo", event = "reject", reason = "invalid", @@ -101,7 +101,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { .run_script(script.clone(), self.build_script_parameters("ehlo")) .await { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "sieve", event = "reject", domain = &self.data.helo_domain, @@ -242,7 +242,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { }) .await { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = context, event = "reject", reason = "dnsbl", @@ -268,7 +268,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { .is_dns_blocked(self.data.remote_ip.to_dnsbl(dnsbl)) .await { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "connect", event = "reject", reason = "dnsbl", diff --git a/crates/smtp/src/inbound/mail.rs b/crates/smtp/src/inbound/mail.rs index 82f78a26..ed52d5c8 100644 --- a/crates/smtp/src/inbound/mail.rs +++ b/crates/smtp/src/inbound/mail.rs @@ -155,7 +155,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin + IsTls> Session<T> { } } ScriptResult::Reject(message) => { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "sieve", event = "reject", address = &self.data.mail_from.as_ref().unwrap().address, diff --git a/crates/smtp/src/inbound/milter/message.rs b/crates/smtp/src/inbound/milter/message.rs index 2de5a43b..2fef32ae 100644 --- a/crates/smtp/src/inbound/milter/message.rs +++ b/crates/smtp/src/inbound/milter/message.rs @@ -70,7 +70,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { } } Err(Rejection::Action(action)) => { - tracing::debug!( + tracing::info!( parent: &self.span, milter.host = &milter.hostname, milter.port = &milter.port, diff --git a/crates/smtp/src/inbound/rcpt.rs b/crates/smtp/src/inbound/rcpt.rs index b4f5829e..7b627686 100644 --- a/crates/smtp/src/inbound/rcpt.rs +++ b/crates/smtp/src/inbound/rcpt.rs @@ -106,7 +106,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin + IsTls> Session<T> { } } ScriptResult::Reject(message) => { - tracing::debug!(parent: &self.span, + tracing::info!(parent: &self.span, context = "sieve", event = "reject", address = self.data.rcpt_to.last().unwrap().address, diff --git a/crates/smtp/src/outbound/delivery.rs b/crates/smtp/src/outbound/delivery.rs index 02d10ef4..b19f77af 100644 --- a/crates/smtp/src/outbound/delivery.rs +++ b/crates/smtp/src/outbound/delivery.rs @@ -192,6 +192,7 @@ impl DeliveryAttempt { mta_sts: *queue_config.tls.mta_sts.eval(&envelope).await, ..Default::default() }; + let allow_invalid_certs = *queue_config.tls.invalid_certs.eval(&envelope).await; // Obtain TLS reporting let tls_report = match core.report.config.tls.send.eval(&envelope).await { @@ -638,13 +639,12 @@ impl DeliveryAttempt { || (self.message.flags & MAIL_REQUIRETLS) != 0 || mta_sts_policy.is_some() || dane_policy.is_some(); - let tls_connector = if !is_strict_tls || remote_host.allow_invalid_certs() { - // Many mail servers on the internet have invalid certificates, if TLS is set to optional and - // the remote host does not have a DANE or MTA-STS policy, then we allow invalid certificates. - &core.queue.connectors.dummy_verify - } else { - &core.queue.connectors.pki_verify - }; + let tls_connector = + if allow_invalid_certs || remote_host.allow_invalid_certs() { + &core.queue.connectors.dummy_verify + } else { + &core.queue.connectors.pki_verify + }; let delivery_result = if !remote_host.implicit_tls() { // Read greeting diff --git a/crates/smtp/src/scripts/exec.rs b/crates/smtp/src/scripts/exec.rs index b961b65c..f21bd2cd 100644 --- a/crates/smtp/src/scripts/exec.rs +++ b/crates/smtp/src/scripts/exec.rs @@ -106,7 +106,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin + IsTls> Session<T> { // Build recipients list let mut recipients = vec![]; for rcpt in &self.data.rcpt_to { - recipients.push(Variable::String(rcpt.address_lcase.to_string())); + recipients.push(Variable::from(rcpt.address_lcase.to_string())); } params.envelope.push((Envelope::To, recipients.into())); } diff --git a/crates/smtp/src/scripts/functions/array.rs b/crates/smtp/src/scripts/functions/array.rs index e9943db5..450de724 100644 --- a/crates/smtp/src/scripts/functions/array.rs +++ b/crates/smtp/src/scripts/functions/array.rs @@ -21,19 +21,15 @@ * for more details. */ -use std::{ - borrow::Cow, - collections::{HashMap, HashSet}, -}; +use std::collections::{HashMap, HashSet}; use sieve::{runtime::Variable, Context}; use crate::config::scripts::SieveContext; -pub fn fn_count<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_count<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::Array(a) => a.len(), - Variable::ArrayRef(a) => a.len(), v => { if !v.is_empty() { 1 @@ -45,9 +41,9 @@ pub fn fn_count<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> V .into() } -pub fn fn_sort<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_sort<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { let is_asc = v[1].to_bool(); - let mut arr = v.into_iter().next().unwrap().into_array(); + let mut arr = (*v[0].to_array()).clone(); if is_asc { arr.sort_unstable_by(|a, b| b.cmp(a)); } else { @@ -56,39 +52,31 @@ pub fn fn_sort<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Va arr.into() } -pub fn fn_dedup<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - let arr = v.into_iter().next().unwrap().into_array(); +pub fn fn_dedup<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let arr = v[0].to_array(); let mut result = Vec::with_capacity(arr.len()); - for item in arr { - if !result.contains(&item) { - result.push(item); + for item in arr.iter() { + if !result.contains(item) { + result.push(item.clone()); } } result.into() } -pub fn fn_cosine_similarity<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let mut word_freq: HashMap<Cow<str>, [u32; 2]> = HashMap::new(); +pub fn fn_cosine_similarity<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let mut word_freq: HashMap<Variable, [u32; 2]> = HashMap::new(); for (idx, var) in v.into_iter().enumerate() { match var { Variable::Array(l) => { - for item in l { - word_freq.entry(item.into_cow()).or_insert([0, 0])[idx] += 1; - } - } - Variable::ArrayRef(l) => { - for item in l { - word_freq.entry(item.to_cow()).or_insert([0, 0])[idx] += 1; + for item in l.iter() { + word_freq.entry(item.clone()).or_insert([0, 0])[idx] += 1; } } _ => { - for char in var.to_cow().chars() { + for char in var.to_string().chars() { word_freq.entry(char.to_string().into()).or_insert([0, 0])[idx] += 1; } } @@ -113,26 +101,18 @@ pub fn fn_cosine_similarity<'x>( .into() } -pub fn fn_jaccard_similarity<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { let mut word_freq = [HashSet::new(), HashSet::new()]; for (idx, var) in v.into_iter().enumerate() { match var { Variable::Array(l) => { - for item in l { - word_freq[idx].insert(item.into_cow()); - } - } - Variable::ArrayRef(l) => { - for item in l { - word_freq[idx].insert(item.to_cow()); + for item in l.iter() { + word_freq[idx].insert(item.clone()); } } _ => { - for char in var.to_cow().chars() { + for char in var.to_string().chars() { word_freq[idx].insert(char.to_string().into()); } } @@ -150,35 +130,21 @@ pub fn fn_jaccard_similarity<'x>( .into() } -pub fn fn_is_intersect<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_intersect<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match (&v[0], &v[1]) { (Variable::Array(a), Variable::Array(b)) => a.iter().any(|x| b.contains(x)), - (Variable::ArrayRef(a), Variable::ArrayRef(b)) => a.iter().any(|x| b.contains(x)), - (Variable::Array(a), Variable::ArrayRef(b)) - | (Variable::ArrayRef(b), Variable::Array(a)) => a.iter().any(|x| b.contains(x)), (Variable::Array(a), item) | (item, Variable::Array(a)) => a.contains(item), - (Variable::ArrayRef(a), item) | (item, Variable::ArrayRef(a)) => a.contains(item), _ => false, } .into() } -pub fn fn_winnow<'x>(_: &'x Context<'x, SieveContext>, mut v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_winnow<'x>(_: &'x Context<'x, SieveContext>, mut v: Vec<Variable>) -> Variable { match v.remove(0) { Variable::Array(a) => a - .into_iter() - .filter(|i| !i.is_empty()) - .collect::<Vec<_>>() - .into(), - Variable::ArrayRef(a) => a .iter() - .filter_map(|i| { - if !i.is_empty() { - i.clone().into() - } else { - None - } - }) + .filter(|i| !i.is_empty()) + .cloned() .collect::<Vec<_>>() .into(), v => v, diff --git a/crates/smtp/src/scripts/functions/email.rs b/crates/smtp/src/scripts/functions/email.rs index 70424579..93bf4264 100644 --- a/crates/smtp/src/scripts/functions/email.rs +++ b/crates/smtp/src/scripts/functions/email.rs @@ -27,7 +27,7 @@ use crate::config::scripts::SieveContext; use super::ApplyString; -pub fn fn_is_email<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_email<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { let mut last_ch = 0; let mut in_quote = false; let mut at_count = 0; @@ -35,7 +35,7 @@ pub fn fn_is_email<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) - let mut lp_len = 0; let mut value = 0; - for ch in v[0].to_cow().bytes() { + for ch in v[0].to_string().bytes() { match ch { b'0'..=b'9' | b'a'..=b'z' @@ -97,12 +97,12 @@ pub fn fn_is_email<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) - (at_count == 1 && dot_count > 0 && lp_len > 0 && value > 0).into() } -pub fn fn_email_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_email_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|s| { s.rsplit_once('@') - .map(|(u, d)| match v[1].to_cow().as_ref() { - "local" => Variable::StringRef(u.trim()), - "domain" => Variable::StringRef(d.trim()), + .map(|(u, d)| match v[1].to_string().as_ref() { + "local" => Variable::from(u.trim()), + "domain" => Variable::from(d.trim()), _ => Variable::default(), }) .unwrap_or_default() @@ -116,11 +116,8 @@ enum MatchPart { Host, } -pub fn fn_domain_part<'x>( - ctx: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let match_part = match v[1].to_cow().as_ref() { +pub fn fn_domain_part<'x>(ctx: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let match_part = match v[1].to_string().as_ref() { "sld" => MatchPart::Sld, "tld" => MatchPart::Tld, "host" => MatchPart::Host, diff --git a/crates/smtp/src/scripts/functions/header.rs b/crates/smtp/src/scripts/functions/header.rs index c54f1495..84180f76 100644 --- a/crates/smtp/src/scripts/functions/header.rs +++ b/crates/smtp/src/scripts/functions/header.rs @@ -28,12 +28,9 @@ use crate::config::scripts::SieveContext; use super::ApplyString; -pub fn fn_received_part<'x>( - ctx: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_received_part<'x>(ctx: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { if let (Ok(part), Some(HeaderValue::Received(rcvd))) = ( - ReceivedPart::try_from(v[1].to_cow().as_ref()), + ReceivedPart::try_from(v[1].to_string().as_ref()), ctx.message() .part(ctx.part()) .and_then(|p| { @@ -52,8 +49,8 @@ pub fn fn_received_part<'x>( pub fn fn_is_encoding_problem<'x>( ctx: &'x Context<'x, SieveContext>, - _: Vec<Variable<'x>>, -) -> Variable<'x> { + _: Vec<Variable>, +) -> Variable { ctx.message() .part(ctx.part()) .map(|p| p.is_encoding_problem) @@ -61,22 +58,16 @@ pub fn fn_is_encoding_problem<'x>( .into() } -pub fn fn_is_attachment<'x>( - ctx: &'x Context<'x, SieveContext>, - _: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_is_attachment<'x>(ctx: &'x Context<'x, SieveContext>, _: Vec<Variable>) -> Variable { ctx.message().attachments.contains(&ctx.part()).into() } -pub fn fn_is_body<'x>(ctx: &'x Context<'x, SieveContext>, _: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_body<'x>(ctx: &'x Context<'x, SieveContext>, _: Vec<Variable>) -> Variable { (ctx.message().text_body.contains(&ctx.part()) || ctx.message().html_body.contains(&ctx.part())) .into() } -pub fn fn_attachment_name<'x>( - ctx: &'x Context<'x, SieveContext>, - _: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_attachment_name<'x>(ctx: &'x Context<'x, SieveContext>, _: Vec<Variable>) -> Variable { ctx.message() .part(ctx.part()) .and_then(|p| p.attachment_name()) @@ -84,20 +75,20 @@ pub fn fn_attachment_name<'x>( .into() } -pub fn fn_thread_name<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_thread_name<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|s| thread_name(s).into()) } pub fn fn_is_header_utf8_valid<'x>( ctx: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { + v: Vec<Variable>, +) -> Variable { ctx.message() .part(ctx.part()) .map(|p| { let raw = ctx.message().raw_message(); let mut is_valid = true; - if let Some(header_name) = HeaderName::parse(v[0].to_cow().as_ref()) { + if let Some(header_name) = HeaderName::parse(v[0].to_string().as_ref()) { for header in &p.headers { if header.name == header_name && raw diff --git a/crates/smtp/src/scripts/functions/html.rs b/crates/smtp/src/scripts/functions/html.rs index 874b5294..9879075f 100644 --- a/crates/smtp/src/scripts/functions/html.rs +++ b/crates/smtp/src/scripts/functions/html.rs @@ -28,16 +28,16 @@ use sieve::{runtime::Variable, Context}; use crate::config::scripts::SieveContext; -pub fn fn_html_to_text<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - html_to_text(v[0].to_cow().as_ref()).into() +pub fn fn_html_to_text<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + html_to_text(v[0].to_string().as_ref()).into() } -pub fn fn_html_has_tag<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_html_has_tag<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].as_array() .map(|arr| { - let token = v[1].to_cow(); + let token = v[1].to_string(); arr.iter().any(|v| { - v.to_cow() + v.to_string() .as_ref() .strip_prefix('<') .map_or(false, |tag| tag.starts_with(token.as_ref())) @@ -47,14 +47,11 @@ pub fn fn_html_has_tag<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x> .into() } -pub fn fn_html_attr_size<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let t = v[0].to_cow(); +pub fn fn_html_attr_size<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let t = v[0].to_string(); let mut dimension = None; - if let Some(value) = get_attribute(t.as_ref(), v[1].to_cow().as_ref()) { + if let Some(value) = get_attribute(t.as_ref(), v[1].to_string().as_ref()) { let value = value.trim(); if let Some(pct) = value.strip_suffix('%') { if let Ok(pct) = pct.trim().parse::<u32>() { @@ -68,22 +65,22 @@ pub fn fn_html_attr_size<'x>( dimension.map(Variable::Integer).unwrap_or_default() } -pub fn fn_html_attrs<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_html_attrs<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { html_attr_tokens( - v[0].to_cow().as_ref(), - v[1].to_cow().as_ref(), + v[0].to_string().as_ref(), + v[1].to_string().as_ref(), v[2].to_string_array(), ) .into() } -pub fn fn_html_attr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - get_attribute(v[0].to_cow().as_ref(), v[1].to_cow().as_ref()) - .map(|s| Variable::String(s.to_string())) +pub fn fn_html_attr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + get_attribute(v[0].to_string().as_ref(), v[1].to_string().as_ref()) + .map(Variable::from) .unwrap_or_default() } -pub fn html_to_tokens(input: &str) -> Vec<Variable<'static>> { +pub fn html_to_tokens(input: &str) -> Vec<Variable> { let input = input.as_bytes(); let mut iter = input.iter().enumerate(); let mut tags = vec![]; @@ -110,7 +107,7 @@ pub fn html_to_tokens(input: &str) -> Vec<Variable<'static>> { is_token_start = true; } if text.len() > 1 { - tags.push(Variable::String(text)); + tags.push(Variable::String(text.into())); text = String::from("_"); } @@ -174,7 +171,9 @@ pub fn html_to_tokens(input: &str) -> Vec<Variable<'static>> { last_ch = ch; } } - tags.push(Variable::String(String::from_utf8(tag).unwrap_or_default())); + tags.push(Variable::String( + String::from_utf8(tag).unwrap_or_default().into(), + )); continue; } b' ' | b'\t' | b'\r' | b'\n' => { @@ -229,13 +228,13 @@ pub fn html_to_tokens(input: &str) -> Vec<Variable<'static>> { ); } if text.len() > 1 { - tags.push(Variable::String(text)); + tags.push(Variable::String(text.into())); } tags } -pub fn html_attr_tokens(input: &str, tag: &str, attrs: Vec<Cow<str>>) -> Vec<Variable<'static>> { +pub fn html_attr_tokens(input: &str, tag: &str, attrs: Vec<Cow<str>>) -> Vec<Variable> { let input = input.as_bytes(); let mut iter = input.iter().enumerate().peekable(); let mut tags = vec![]; @@ -279,7 +278,7 @@ pub fn html_attr_tokens(input: &str, tag: &str, attrs: Vec<Cow<str>>) -> Vec<Var b'>' if !in_quote => { if !tag.is_empty() { tags.push(Variable::String( - String::from_utf8(tag).unwrap_or_default(), + String::from_utf8(tag).unwrap_or_default().into(), )); } break 'outer; @@ -303,7 +302,7 @@ pub fn html_attr_tokens(input: &str, tag: &str, attrs: Vec<Cow<str>>) -> Vec<Var if !tag.is_empty() { tags.push(Variable::String( - String::from_utf8(tag).unwrap_or_default(), + String::from_utf8(tag).unwrap_or_default().into(), )); } } @@ -331,10 +330,10 @@ pub fn html_attr_tokens(input: &str, tag: &str, attrs: Vec<Cow<str>>) -> Vec<Var tags } -pub fn html_img_area(arr: &[Variable<'_>]) -> u32 { +pub fn html_img_area(arr: &[Variable]) -> u32 { arr.iter() .filter_map(|v| { - let t = v.to_cow(); + let t = v.to_string(); if t.starts_with("<img") { let mut dimensions = [200u32, 200u32]; diff --git a/crates/smtp/src/scripts/functions/image.rs b/crates/smtp/src/scripts/functions/image.rs index d01924d1..ffba52bd 100644 --- a/crates/smtp/src/scripts/functions/image.rs +++ b/crates/smtp/src/scripts/functions/image.rs @@ -25,18 +25,15 @@ use sieve::{runtime::Variable, Context}; use crate::config::scripts::SieveContext; -pub fn fn_img_metadata<'x>( - ctx: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_img_metadata<'x>(ctx: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { ctx.message() .part(ctx.part()) .map(|p| p.contents()) .and_then(|bytes| { - let arg = v[1].to_cow(); + let arg = v[1].to_string(); match arg.as_ref() { "type" => imagesize::image_type(bytes).ok().map(|t| { - Variable::StringRef(match t { + Variable::from(match t { imagesize::ImageType::Aseprite => "aseprite", imagesize::ImageType::Avif => "avif", imagesize::ImageType::Bmp => "bmp", diff --git a/crates/smtp/src/scripts/functions/misc.rs b/crates/smtp/src/scripts/functions/misc.rs index 5ad80087..ed1ae329 100644 --- a/crates/smtp/src/scripts/functions/misc.rs +++ b/crates/smtp/src/scripts/functions/misc.rs @@ -32,56 +32,48 @@ use crate::config::scripts::SieveContext; use super::ApplyString; -pub fn fn_is_empty<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_empty<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::String(s) => s.is_empty(), - Variable::StringRef(s) => s.is_empty(), Variable::Integer(_) | Variable::Float(_) => false, Variable::Array(a) => a.is_empty(), - Variable::ArrayRef(a) => a.is_empty(), } .into() } -pub fn fn_is_ip_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow().parse::<std::net::IpAddr>().is_ok().into() +pub fn fn_is_ip_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string().parse::<std::net::IpAddr>().is_ok().into() } -pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow() +pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .parse::<std::net::IpAddr>() .map_or(false, |ip| matches!(ip, IpAddr::V4(_))) .into() } -pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow() +pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .parse::<std::net::IpAddr>() .map_or(false, |ip| matches!(ip, IpAddr::V6(_))) .into() } -pub fn fn_ip_reverse_name<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - v[0].to_cow() +pub fn fn_ip_reverse_name<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .parse::<std::net::IpAddr>() .map(|ip| ip.to_reverse_name()) .unwrap_or_default() .into() } -pub fn fn_detect_file_type<'x>( - ctx: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_detect_file_type<'x>(ctx: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { ctx.message() .part(ctx.part()) .and_then(|p| infer::get(p.contents())) .map(|t| { - Variable::String( - if v[0].to_cow() != "ext" { + Variable::from( + if v[0].to_string() != "ext" { t.mime_type() } else { t.extension() @@ -92,9 +84,9 @@ pub fn fn_detect_file_type<'x>( .unwrap_or_default() } -pub fn fn_hash<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_hash<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { use sha1::Digest; - let hash = v[1].to_cow(); + let hash = v[1].to_string(); v[0].transform(|value| match hash.as_ref() { "md5" => format!("{:x}", md5::compute(value.as_bytes())).into(), @@ -117,13 +109,11 @@ pub fn fn_hash<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Va }) } -pub fn fn_is_var_names<'x>( - ctx: &'x Context<'x, SieveContext>, - _: Vec<Variable<'x>>, -) -> Variable<'x> { +pub fn fn_is_var_names<'x>(ctx: &'x Context<'x, SieveContext>, _: Vec<Variable>) -> Variable { Variable::Array( ctx.global_variable_names() .map(|v| Variable::from(v.to_string())) - .collect(), + .collect::<Vec<_>>() + .into(), ) } diff --git a/crates/smtp/src/scripts/functions/mod.rs b/crates/smtp/src/scripts/functions/mod.rs index b6632e98..c09a9239 100644 --- a/crates/smtp/src/scripts/functions/mod.rs +++ b/crates/smtp/src/scripts/functions/mod.rs @@ -112,33 +112,22 @@ pub fn register_functions() -> FunctionMap<SieveContext> { } pub trait ApplyString<'x> { - fn transform(&self, f: impl Fn(&'_ str) -> Variable<'_>) -> Variable<'x>; + fn transform(&self, f: impl Fn(&'_ str) -> Variable) -> Variable; } -impl<'x> ApplyString<'x> for Variable<'x> { - fn transform(&self, f: impl Fn(&'_ str) -> Variable<'_>) -> Variable<'x> { +impl<'x> ApplyString<'x> for Variable { + fn transform(&self, f: impl Fn(&'_ str) -> Variable) -> Variable { match self { - Variable::StringRef(s) => f(s), - Variable::String(s) => f(s).into_owned(), - Variable::ArrayRef(list) => list - .iter() - .map(|v| match v { - Variable::String(s) => f(s), - Variable::StringRef(s) => f(s), - v => f(v.to_cow().as_ref()).into_owned(), - }) - .collect::<Vec<_>>() - .into(), + Variable::String(s) => f(s), Variable::Array(list) => list .iter() .map(|v| match v { - Variable::StringRef(s) => f(s), - Variable::String(s) => f(s).into_owned(), - v => f(v.to_cow().as_ref()).into_owned(), + Variable::String(s) => f(s), + v => f(v.to_string().as_ref()), }) .collect::<Vec<_>>() .into(), - v => f(v.to_cow().as_ref()).into_owned(), + v => f(v.to_string().as_ref()), } } } diff --git a/crates/smtp/src/scripts/functions/text.rs b/crates/smtp/src/scripts/functions/text.rs index 1fc06be3..567a43cf 100644 --- a/crates/smtp/src/scripts/functions/text.rs +++ b/crates/smtp/src/scripts/functions/text.rs @@ -28,38 +28,36 @@ use crate::config::scripts::SieveContext; use super::{html::html_to_tokens, ApplyString}; -pub fn fn_trim<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].transform(|s| Variable::StringRef(s.trim())) +pub fn fn_trim<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].transform(|s| Variable::from(s.trim())) } -pub fn fn_trim_end<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].transform(|s| Variable::StringRef(s.trim_end())) +pub fn fn_trim_end<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].transform(|s| Variable::from(s.trim_end())) } -pub fn fn_trim_start<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].transform(|s| Variable::StringRef(s.trim_start())) +pub fn fn_trim_start<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].transform(|s| Variable::from(s.trim_start())) } -pub fn fn_len<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_len<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::String(s) => s.len(), - Variable::StringRef(s) => s.len(), Variable::Array(a) => a.len(), - Variable::ArrayRef(a) => a.len(), v => v.to_string().len(), } .into() } -pub fn fn_to_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].transform(|s| Variable::String(s.to_lowercase())) +pub fn fn_to_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].transform(|s| Variable::from(s.to_lowercase())) } -pub fn fn_to_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].transform(|s| Variable::String(s.to_uppercase())) +pub fn fn_to_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].transform(|s| Variable::from(s.to_uppercase())) } -pub fn fn_is_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|s| { s.chars() .filter(|c| c.is_alphabetic()) @@ -68,7 +66,7 @@ pub fn fn_is_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x> }) } -pub fn fn_is_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|s| { s.chars() .filter(|c| c.is_alphabetic()) @@ -77,38 +75,22 @@ pub fn fn_is_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x> }) } -pub fn fn_has_digits<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_has_digits<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|s| s.chars().any(|c| c.is_ascii_digit()).into()) } -pub fn tokenize_words<'x>(v: &Variable<'x>) -> Variable<'x> { - match v { - Variable::StringRef(s) => s - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(Variable::from) - .collect::<Vec<_>>(), - Variable::String(s) => s - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(|word| Variable::from(word.to_string())) - .collect::<Vec<_>>(), - v => v - .to_string() - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(|word| Variable::from(word.to_string())) - .collect::<Vec<_>>(), - } - .into() +pub fn tokenize_words(v: &Variable) -> Variable { + v.to_string() + .split_whitespace() + .filter(|word| word.chars().all(|c| c.is_alphanumeric())) + .map(|word| Variable::from(word.to_string())) + .collect::<Vec<_>>() + .into() } -pub fn fn_tokenize<'x>( - ctx: &'x Context<'x, SieveContext>, - mut v: Vec<Variable<'x>>, -) -> Variable<'x> { - let (urls, urls_without_scheme, emails) = match v[1].to_cow().as_ref() { - "html" => return html_to_tokens(v[0].to_cow().as_ref()).into(), +pub fn fn_tokenize<'x>(ctx: &'x Context<'x, SieveContext>, mut v: Vec<Variable>) -> Variable { + let (urls, urls_without_scheme, emails) = match v[1].to_string().as_ref() { + "html" => return html_to_tokens(v[0].to_string().as_ref()).into(), "words" => return tokenize_words(&v[0]), "uri" | "url" => (true, true, true), "uri_strict" | "url_strict" => (true, false, false), @@ -117,33 +99,18 @@ pub fn fn_tokenize<'x>( }; match v.remove(0) { - Variable::StringRef(text) => TypesTokenizer::new(text, &ctx.context().psl) - .tokenize_numbers(false) - .tokenize_urls(urls) - .tokenize_urls_without_scheme(urls_without_scheme) - .tokenize_emails(emails) - .filter_map(|t| match t.word { - TokenType::Url(text) if urls => Variable::StringRef(text).into(), - TokenType::UrlNoScheme(text) if urls_without_scheme => { - Variable::String(format!("https://{text}")).into() - } - TokenType::Email(text) if emails => Variable::StringRef(text).into(), - _ => None, - }) - .collect::<Vec<_>>() - .into(), - v @ (Variable::String(_) | Variable::Array(_) | Variable::ArrayRef(_)) => { - TypesTokenizer::new(v.to_cow().as_ref(), &ctx.context().psl) + v @ (Variable::String(_) | Variable::Array(_)) => { + TypesTokenizer::new(v.to_string().as_ref(), &ctx.context().psl) .tokenize_numbers(false) .tokenize_urls(urls) .tokenize_urls_without_scheme(urls_without_scheme) .tokenize_emails(emails) .filter_map(|t| match t.word { - TokenType::Url(text) if urls => Variable::String(text.to_string()).into(), + TokenType::Url(text) if urls => Variable::from(text.to_string()).into(), TokenType::UrlNoScheme(text) if urls_without_scheme => { - Variable::String(format!("https://{text}")).into() + Variable::from(format!("https://{text}")).into() } - TokenType::Email(text) if emails => Variable::String(text.to_string()).into(), + TokenType::Email(text) if emails => Variable::from(text.to_string()).into(), _ => None, }) .collect::<Vec<_>>() @@ -153,8 +120,8 @@ pub fn fn_tokenize<'x>( } } -pub fn fn_count_spaces<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow() +pub fn fn_count_spaces<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .as_ref() .chars() .filter(|c| c.is_whitespace()) @@ -162,11 +129,8 @@ pub fn fn_count_spaces<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x> .into() } -pub fn fn_count_uppercase<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - v[0].to_cow() +pub fn fn_count_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .as_ref() .chars() .filter(|c| c.is_alphabetic() && c.is_uppercase()) @@ -174,11 +138,8 @@ pub fn fn_count_uppercase<'x>( .into() } -pub fn fn_count_lowercase<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - v[0].to_cow() +pub fn fn_count_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .as_ref() .chars() .filter(|c| c.is_alphabetic() && c.is_lowercase()) @@ -186,46 +147,31 @@ pub fn fn_count_lowercase<'x>( .into() } -pub fn fn_count_chars<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow().as_ref().chars().count().into() +pub fn fn_count_chars<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string().as_ref().chars().count().into() } -pub fn fn_eq_ignore_case<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - v[0].to_cow() - .eq_ignore_ascii_case(v[1].to_cow().as_ref()) +pub fn fn_eq_ignore_case<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .eq_ignore_ascii_case(v[1].to_string().as_ref()) .into() } -pub fn fn_contains<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_contains<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { - Variable::String(s) => s.contains(v[1].to_cow().as_ref()), - Variable::StringRef(s) => s.contains(v[1].to_cow().as_ref()), + Variable::String(s) => s.contains(v[1].to_string().as_ref()), Variable::Array(arr) => arr.contains(&v[1]), - Variable::ArrayRef(arr) => arr.contains(&v[1]), - val => val.to_string().contains(v[1].to_cow().as_ref()), + val => val.to_string().contains(v[1].to_string().as_ref()), } .into() } -pub fn fn_contains_ignore_case<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let needle = v[1].to_cow(); +pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let needle = v[1].to_string(); match &v[0] { Variable::String(s) => s.to_lowercase().contains(&needle.to_lowercase()), - Variable::StringRef(s) => s.to_lowercase().contains(&needle.to_lowercase()), Variable::Array(arr) => arr.iter().any(|v| match v { Variable::String(s) => s.eq_ignore_ascii_case(needle.as_ref()), - Variable::StringRef(s) => s.eq_ignore_ascii_case(needle.as_ref()), - _ => false, - }), - Variable::ArrayRef(arr) => arr.iter().any(|v| match v { - Variable::String(s) => s.eq_ignore_ascii_case(needle.as_ref()), - Variable::StringRef(s) => s.eq_ignore_ascii_case(needle.as_ref()), _ => false, }), val => val.to_string().contains(needle.as_ref()), @@ -233,28 +179,29 @@ pub fn fn_contains_ignore_case<'x>( .into() } -pub fn fn_starts_with<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow().starts_with(v[1].to_cow().as_ref()).into() +pub fn fn_starts_with<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .starts_with(v[1].to_string().as_ref()) + .into() } -pub fn fn_ends_with<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow().ends_with(v[1].to_cow().as_ref()).into() +pub fn fn_ends_with<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string().ends_with(v[1].to_string().as_ref()).into() } -pub fn fn_lines<'x>(_: &'x Context<'x, SieveContext>, mut v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_lines<'x>(_: &'x Context<'x, SieveContext>, mut v: Vec<Variable>) -> Variable { match v.remove(0) { - Variable::StringRef(s) => s.lines().map(Variable::from).collect::<Vec<_>>().into(), Variable::String(s) => s .lines() - .map(|s| Variable::String(s.to_string())) + .map(|s| Variable::from(s.to_string())) .collect::<Vec<_>>() .into(), val => val, } } -pub fn fn_substring<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - v[0].to_cow() +pub fn fn_substring<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() .chars() .skip(v[1].to_usize()) .take(v[2].to_usize()) @@ -262,120 +209,60 @@ pub fn fn_substring<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) .into() } -pub fn fn_strip_prefix<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - let prefix = v[1].to_cow(); +pub fn fn_strip_prefix<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let prefix = v[1].to_string(); v[0].transform(|s| { s.strip_prefix(prefix.as_ref()) - .map(Variable::StringRef) + .map(Variable::from) .unwrap_or_default() }) } -pub fn fn_strip_suffix<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - let suffix = v[1].to_cow(); +pub fn fn_strip_suffix<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let suffix = v[1].to_string(); v[0].transform(|s| { s.strip_suffix(suffix.as_ref()) - .map(Variable::StringRef) + .map(Variable::from) .unwrap_or_default() }) } -pub fn fn_split<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - match &v[0] { - Variable::StringRef(s) => s - .split(v[1].to_cow().as_ref()) - .map(Variable::from) - .collect::<Vec<_>>() - .into(), - Variable::String(s) => s - .split(v[1].to_cow().as_ref()) - .map(|s| Variable::String(s.to_string())) - .collect::<Vec<_>>() - .into(), - val => val - .to_string() - .split(v[1].to_cow().as_ref()) - .map(|s| Variable::String(s.to_string())) - .collect::<Vec<_>>() - .into(), - } +pub fn fn_split<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .split(v[1].to_string().as_ref()) + .map(|s| Variable::from(s.to_string())) + .collect::<Vec<_>>() + .into() } -pub fn fn_rsplit<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - match &v[0] { - Variable::StringRef(s) => s - .rsplit(v[1].to_cow().as_ref()) - .map(Variable::from) - .collect::<Vec<_>>() - .into(), - Variable::String(s) => s - .rsplit(v[1].to_cow().as_ref()) - .map(|s| Variable::String(s.to_string())) - .collect::<Vec<_>>() - .into(), - val => val - .to_string() - .rsplit(v[1].to_cow().as_ref()) - .map(|s| Variable::String(s.to_string())) - .collect::<Vec<_>>() - .into(), - } +pub fn fn_rsplit<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .rsplit(v[1].to_string().as_ref()) + .map(|s| Variable::from(s.to_string())) + .collect::<Vec<_>>() + .into() } -pub fn fn_split_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - match &v[0] { - Variable::StringRef(s) => s - .split_once(v[1].to_cow().as_ref()) - .map(|(a, b)| Variable::Array(vec![Variable::StringRef(a), Variable::StringRef(b)])) - .unwrap_or_default(), - Variable::String(s) => s - .split_once(v[1].to_cow().as_ref()) - .map(|(a, b)| { - Variable::Array(vec![ - Variable::String(a.to_string()), - Variable::String(b.to_string()), - ]) - }) - .unwrap_or_default(), - val => val - .to_string() - .split_once(v[1].to_cow().as_ref()) - .map(|(a, b)| { - Variable::Array(vec![ - Variable::String(a.to_string()), - Variable::String(b.to_string()), - ]) - }) - .unwrap_or_default(), - } +pub fn fn_split_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .split_once(v[1].to_string().as_ref()) + .map(|(a, b)| { + Variable::Array( + vec![Variable::from(a.to_string()), Variable::from(b.to_string())].into(), + ) + }) + .unwrap_or_default() } -pub fn fn_rsplit_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - match &v[0] { - Variable::StringRef(s) => s - .rsplit_once(v[1].to_cow().as_ref()) - .map(|(a, b)| Variable::Array(vec![Variable::StringRef(a), Variable::StringRef(b)])) - .unwrap_or_default(), - Variable::String(s) => s - .rsplit_once(v[1].to_cow().as_ref()) - .map(|(a, b)| { - Variable::Array(vec![ - Variable::String(a.to_string()), - Variable::String(b.to_string()), - ]) - }) - .unwrap_or_default(), - val => val - .to_string() - .rsplit_once(v[1].to_cow().as_ref()) - .map(|(a, b)| { - Variable::Array(vec![ - Variable::String(a.to_string()), - Variable::String(b.to_string()), - ]) - }) - .unwrap_or_default(), - } +pub fn fn_rsplit_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + v[0].to_string() + .rsplit_once(v[1].to_string().as_ref()) + .map(|(a, b)| { + Variable::Array( + vec![Variable::from(a.to_string()), Variable::from(b.to_string())].into(), + ) + }) + .unwrap_or_default() } /** @@ -385,12 +272,9 @@ pub fn fn_rsplit_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>> * * Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com> */ -pub fn fn_levenshtein_distance<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let a = v[0].to_cow(); - let b = v[1].to_cow(); +pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let a = v[0].to_string(); + let b = v[1].to_string(); let mut result = 0; @@ -449,11 +333,8 @@ pub fn fn_levenshtein_distance<'x>( result.into() } -pub fn fn_detect_language<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - whatlang::detect_lang(v[0].to_cow().as_ref()) +pub fn fn_detect_language<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + whatlang::detect_lang(v[0].to_string().as_ref()) .map(|l| l.code()) .unwrap_or("unknown") .into() diff --git a/crates/smtp/src/scripts/functions/unicode.rs b/crates/smtp/src/scripts/functions/unicode.rs index 21fbca67..c11721e3 100644 --- a/crates/smtp/src/scripts/functions/unicode.rs +++ b/crates/smtp/src/scripts/functions/unicode.rs @@ -26,37 +26,23 @@ use unicode_security::MixedScript; use crate::config::scripts::SieveContext; -pub fn fn_is_ascii<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_is_ascii<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), Variable::Integer(_) | Variable::Float(_) => true, Variable::Array(a) => a.iter().all(|v| match v { Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), - _ => true, - }), - Variable::ArrayRef(a) => a.iter().all(|v| match v { - Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), _ => true, }), } .into() } -pub fn fn_has_zwsp<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_has_zwsp<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::String(s) => s.chars().any(|c| c.is_zwsp()), - Variable::StringRef(s) => s.chars().any(|c| c.is_zwsp()), Variable::Array(a) => a.iter().any(|v| match v { Variable::String(s) => s.chars().any(|c| c.is_zwsp()), - Variable::StringRef(s) => s.chars().any(|c| c.is_zwsp()), - _ => true, - }), - Variable::ArrayRef(a) => a.iter().any(|v| match v { - Variable::String(s) => s.chars().any(|c| c.is_zwsp()), - Variable::StringRef(s) => s.chars().any(|c| c.is_zwsp()), _ => true, }), Variable::Integer(_) | Variable::Float(_) => false, @@ -64,18 +50,11 @@ pub fn fn_has_zwsp<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) - .into() } -pub fn fn_has_obscured<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_has_obscured<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { match &v[0] { Variable::String(s) => s.chars().any(|c| c.is_obscured()), - Variable::StringRef(s) => s.chars().any(|c| c.is_obscured()), Variable::Array(a) => a.iter().any(|v| match v { Variable::String(s) => s.chars().any(|c| c.is_obscured()), - Variable::StringRef(s) => s.chars().any(|c| c.is_obscured()), - _ => true, - }), - Variable::ArrayRef(a) => a.iter().any(|v| match v { - Variable::String(s) => s.chars().any(|c| c.is_obscured()), - Variable::StringRef(s) => s.chars().any(|c| c.is_obscured()), _ => true, }), Variable::Integer(_) | Variable::Float(_) => false, @@ -107,24 +86,18 @@ impl CharUtils for char { } } -pub fn fn_cure_text<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - decancer::cure(v[0].to_cow().as_ref()).into_str().into() +pub fn fn_cure_text<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + decancer::cure(v[0].to_string().as_ref()).into_str().into() } -pub fn fn_unicode_skeleton<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - unicode_security::skeleton(v[0].to_cow().as_ref()) +pub fn fn_unicode_skeleton<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + unicode_security::skeleton(v[0].to_string().as_ref()) .collect::<String>() .into() } -pub fn fn_is_single_script<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec<Variable<'x>>, -) -> Variable<'x> { - let text = v[0].to_cow(); +pub fn fn_is_single_script<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let text = v[0].to_string(); if !text.is_empty() { text.as_ref().is_single_script() } else { diff --git a/crates/smtp/src/scripts/functions/url.rs b/crates/smtp/src/scripts/functions/url.rs index 9ecbfd5a..b1f9ef3b 100644 --- a/crates/smtp/src/scripts/functions/url.rs +++ b/crates/smtp/src/scripts/functions/url.rs @@ -28,32 +28,30 @@ use crate::config::scripts::SieveContext; use super::ApplyString; -pub fn fn_uri_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { - let part = v[1].to_cow(); +pub fn fn_uri_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { + let part = v[1].to_string(); v[0].transform(|uri| { uri.parse::<Uri>() .ok() .and_then(|uri| match part.as_ref() { - "scheme" => uri.scheme_str().map(|s| Variable::String(s.to_string())), - "host" => uri.host().map(|s| Variable::String(s.to_string())), + "scheme" => uri.scheme_str().map(|s| Variable::from(s.to_string())), + "host" => uri.host().map(|s| Variable::from(s.to_string())), "scheme_host" => uri .scheme_str() .and_then(|s| (s, uri.host()?).into()) - .map(|(s, h)| Variable::String(format!("{}://{}", s, h))), - "path" => Variable::String(uri.path().to_string()).into(), + .map(|(s, h)| Variable::from(format!("{}://{}", s, h))), + "path" => Variable::from(uri.path().to_string()).into(), "port" => uri.port_u16().map(|port| Variable::Integer(port as i64)), - "query" => uri.query().map(|s| Variable::String(s.to_string())), - "path_query" => uri - .path_and_query() - .map(|s| Variable::String(s.to_string())), - "authority" => uri.authority().map(|s| Variable::String(s.to_string())), + "query" => uri.query().map(|s| Variable::from(s.to_string())), + "path_query" => uri.path_and_query().map(|s| Variable::from(s.to_string())), + "authority" => uri.authority().map(|s| Variable::from(s.to_string())), _ => None, }) .unwrap_or_default() }) } -pub fn fn_puny_decode<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable<'x>>) -> Variable<'x> { +pub fn fn_puny_decode<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable { v[0].transform(|domain| { if domain.contains("xn--") { let mut decoded = String::with_capacity(domain.len()); diff --git a/crates/smtp/src/scripts/mod.rs b/crates/smtp/src/scripts/mod.rs index d3a9dc9c..c53b686d 100644 --- a/crates/smtp/src/scripts/mod.rs +++ b/crates/smtp/src/scripts/mod.rs @@ -47,10 +47,10 @@ pub enum ScriptResult { pub struct ScriptParameters { message: Option<Arc<Vec<u8>>>, - variables: AHashMap<Cow<'static, str>, Variable<'static>>, - envelope: Vec<(Envelope, Variable<'static>)>, + variables: AHashMap<Cow<'static, str>, Variable>, + envelope: Vec<(Envelope, Variable)>, #[cfg(feature = "test_mode")] - expected_variables: Option<AHashMap<String, Variable<'static>>>, + expected_variables: Option<AHashMap<String, Variable>>, } impl ScriptParameters { @@ -74,7 +74,7 @@ impl ScriptParameters { pub fn set_variable( mut self, name: impl Into<Cow<'static, str>>, - value: impl Into<Variable<'static>>, + value: impl Into<Variable>, ) -> Self { self.variables.insert(name.into(), value.into()); self @@ -83,7 +83,7 @@ impl ScriptParameters { #[cfg(feature = "test_mode")] pub fn with_expected_variables( mut self, - expected_variables: AHashMap<String, Variable<'static>>, + expected_variables: AHashMap<String, Variable>, ) -> Self { self.expected_variables = expected_variables.into(); self diff --git a/crates/smtp/src/scripts/plugins/bayes.rs b/crates/smtp/src/scripts/plugins/bayes.rs index e14f6b37..50648c69 100644 --- a/crates/smtp/src/scripts/plugins/bayes.rs +++ b/crates/smtp/src/scripts/plugins/bayes.rs @@ -34,28 +34,42 @@ use crate::config::scripts::SieveContext; use super::PluginContext; pub fn register_train(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { - fnc_map.set_external_function("bayes_train", plugin_id, 2); + fnc_map.set_external_function("bayes_train", plugin_id, 3); } pub fn register_untrain(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { - fnc_map.set_external_function("bayes_untrain", plugin_id, 2); + fnc_map.set_external_function("bayes_untrain", plugin_id, 3); } pub fn register_classify(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { - fnc_map.set_external_function("bayes_classify", plugin_id, 1); + fnc_map.set_external_function("bayes_classify", plugin_id, 2); } -pub fn exec_train(ctx: PluginContext<'_>) -> Variable<'static> { +pub fn exec_train(ctx: PluginContext<'_>) -> Variable { train(ctx, true) } -pub fn exec_untrain(ctx: PluginContext<'_>) -> Variable<'static> { +pub fn exec_untrain(ctx: PluginContext<'_>) -> Variable { train(ctx, false) } -fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { - let mut arguments = ctx.arguments.into_iter(); - let text = arguments.next().unwrap().into_string(); +fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable { + let span = ctx.span; + let lookup_id = ctx.arguments[0].to_string(); + let lookup_train = if let Some(lookup_train) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) { + lookup_train + } else { + tracing::warn!( + parent: span, + context = "sieve:bayes_train", + event = "failed", + reason = "Unknown lookup id", + lookup_id = %lookup_id, + ); + return false.into(); + }; + let text = ctx.arguments[1].to_string(); + let is_spam = ctx.arguments[2].to_bool(); if text.is_empty() { return false.into(); } @@ -63,7 +77,6 @@ fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { let ctx = ctx.core.sieve.runtime.context(); // Train the model - let is_spam = arguments.next().unwrap().to_bool(); let mut model = BayesModel::default(); model.train( OsbTokenizer::new(BayesTokenizer::new(text.as_ref(), &ctx.psl), 5), @@ -74,7 +87,6 @@ fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { } // Update weight and invalidate cache - let upsert = &ctx.lookup_train; for (hash, weights) in model.weights { let (s_weight, h_weight) = if is_train { (weights.spam as i64, weights.ham as i64) @@ -82,7 +94,7 @@ fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { (-(weights.spam as i64), -(weights.ham as i64)) }; if handle - .block_on(upsert.lookup(&[ + .block_on(lookup_train.lookup(&[ hash.h1.into(), hash.h2.into(), s_weight.into(), @@ -103,7 +115,7 @@ fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { (0i64, train_val) }; if handle - .block_on(upsert.query(&[ + .block_on(lookup_train.query(&[ 0i64.into(), 0i64.into(), spam_count.into(), @@ -118,29 +130,42 @@ fn train(ctx: PluginContext<'_>, is_train: bool) -> Variable<'static> { true.into() } -pub fn exec_classify(ctx: PluginContext<'_>) -> Variable<'static> { - let mut arguments = ctx.arguments.into_iter(); - let text = arguments.next().unwrap().into_string(); +pub fn exec_classify(ctx: PluginContext<'_>) -> Variable { + let span = ctx.span; + let lookup_id = ctx.arguments[0].to_string(); + let lookup_classify = + if let Some(lookup_classify) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) { + lookup_classify + } else { + tracing::warn!( + parent: span, + context = "sieve:bayes_classify", + event = "failed", + reason = "Unknown lookup id", + lookup_id = %lookup_id, + ); + return Variable::default(); + }; + let text = ctx.arguments[1].to_string(); if text.is_empty() { - return 0.into(); + return Variable::default(); } let handle = ctx.handle; let ctx = ctx.core.sieve.runtime.context(); - let get_token = &ctx.lookup_classify; // Obtain training counts let (spam_learns, ham_learns) = if let Some(weights) = ctx.bayes_cache - .get_or_update(TokenHash::default(), handle, get_token) + .get_or_update(TokenHash::default(), handle, lookup_classify) { (weights.spam, weights.ham) } else { - return 0.into(); + return Variable::default(); }; // Make sure we have enough training data if spam_learns < ctx.bayes_classify.min_learns || ham_learns < ctx.bayes_classify.min_learns { - return 0.into(); + return Variable::default(); } // Classify the text @@ -149,7 +174,9 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable<'static> { OsbTokenizer::<_, TokenHash>::new(BayesTokenizer::new(text.as_ref(), &ctx.psl), 5) .filter_map(|t| { OsbToken { - inner: ctx.bayes_cache.get_or_update(t.inner, handle, get_token)?, + inner: ctx + .bayes_cache + .get_or_update(t.inner, handle, lookup_classify)?, idx: t.idx, } .into() @@ -157,8 +184,8 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable<'static> { ham_learns, spam_learns, ) + .map(Variable::from) .unwrap_or_default() - .into() } trait LookupOrInsert { diff --git a/crates/smtp/src/scripts/plugins/dns.rs b/crates/smtp/src/scripts/plugins/dns.rs index d938be6d..a7fc7e30 100644 --- a/crates/smtp/src/scripts/plugins/dns.rs +++ b/crates/smtp/src/scripts/plugins/dns.rs @@ -38,9 +38,9 @@ pub fn register_exists(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) fnc_map.set_external_function("dns_exists", plugin_id, 2); } -pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { - let entry = ctx.arguments[0].to_cow(); - let record_type = ctx.arguments[1].to_cow(); +pub fn exec(ctx: PluginContext<'_>) -> Variable { + let entry = ctx.arguments[0].to_string(); + let record_type = ctx.arguments[1].to_string(); if record_type.eq_ignore_ascii_case("ip") { match ctx.handle.block_on(ctx.core.resolvers.dns.ip_lookup( @@ -50,7 +50,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { )) { Ok(result) => result .iter() - .map(|ip| Variable::String(ip.to_string())) + .map(|ip| Variable::from(ip.to_string())) .collect::<Vec<_>>() .into(), Err(err) => err.short_error().into(), @@ -65,7 +65,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { .flat_map(|mx| { mx.exchanges .iter() - .map(|host| Variable::String(format!("{} {}", mx.preference, host))) + .map(|host| Variable::from(format!("{} {}", mx.preference, host))) }) .collect::<Vec<_>>() .into(), @@ -75,7 +75,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { #[cfg(feature = "test_mode")] { if entry.contains("origin") { - return Variable::String("23028|US|arin|2002-01-04".to_string()); + return Variable::from("23028|US|arin|2002-01-04".to_string()); } } @@ -83,7 +83,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { .handle .block_on(ctx.core.resolvers.dns.txt_raw_lookup(entry.as_ref())) { - Ok(result) => Variable::String(String::from_utf8(result).unwrap_or_default()), + Ok(result) => Variable::from(String::from_utf8(result).unwrap_or_default()), Err(err) => err.short_error().into(), } } else if record_type.eq_ignore_ascii_case("ptr") { @@ -91,7 +91,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { match ctx.handle.block_on(ctx.core.resolvers.dns.ptr_lookup(addr)) { Ok(result) => result .iter() - .map(|host| Variable::String(host.to_string())) + .map(|host| Variable::from(host.to_string())) .collect::<Vec<_>>() .into(), Err(err) => err.short_error().into(), @@ -104,7 +104,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { { if entry.contains(".168.192.") { let parts = entry.split('.').collect::<Vec<_>>(); - return vec![Variable::String(format!("127.0.{}.{}", parts[1], parts[0]))].into(); + return vec![Variable::from(format!("127.0.{}.{}", parts[1], parts[0]))].into(); } } @@ -114,7 +114,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { { Ok(result) => result .iter() - .map(|ip| Variable::String(ip.to_string())) + .map(|ip| Variable::from(ip.to_string())) .collect::<Vec<_>>() .into(), Err(err) => err.short_error().into(), @@ -126,7 +126,7 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { { Ok(result) => result .iter() - .map(|ip| Variable::String(ip.to_string())) + .map(|ip| Variable::from(ip.to_string())) .collect::<Vec<_>>() .into(), Err(err) => err.short_error().into(), @@ -136,9 +136,9 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { } } -pub fn exec_exists(ctx: PluginContext<'_>) -> Variable<'static> { - let entry = ctx.arguments[0].to_cow(); - let record_type = ctx.arguments[1].to_cow(); +pub fn exec_exists(ctx: PluginContext<'_>) -> Variable { + let entry = ctx.arguments[0].to_string(); + let record_type = ctx.arguments[1].to_string(); if record_type.eq_ignore_ascii_case("ip") { match ctx.handle.block_on(ctx.core.resolvers.dns.ip_lookup( diff --git a/crates/smtp/src/scripts/plugins/exec.rs b/crates/smtp/src/scripts/plugins/exec.rs index 8096829f..053e7912 100644 --- a/crates/smtp/src/scripts/plugins/exec.rs +++ b/crates/smtp/src/scripts/plugins/exec.rs @@ -33,13 +33,13 @@ pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { fnc_map.set_external_function("exec", plugin_id, 2); } -pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { +pub fn exec(ctx: PluginContext<'_>) -> Variable { let span = ctx.span; let mut arguments = ctx.arguments.into_iter(); match Command::new( arguments .next() - .map(|a| a.into_string()) + .map(|a| a.to_string().into_owned()) .unwrap_or_default(), ) .args( diff --git a/crates/smtp/src/scripts/plugins/http.rs b/crates/smtp/src/scripts/plugins/http.rs index 54ecb4b5..77dc4bad 100644 --- a/crates/smtp/src/scripts/plugins/http.rs +++ b/crates/smtp/src/scripts/plugins/http.rs @@ -34,15 +34,15 @@ pub fn register_header(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) fnc_map.set_external_function("http_header", plugin_id, 4); } -pub fn exec_header(ctx: PluginContext<'_>) -> Variable<'static> { - let url = ctx.arguments[0].to_cow(); - let header = ctx.arguments[1].to_cow(); - let agent = ctx.arguments[2].to_cow(); - let timeout = ctx.arguments[3].to_cow().parse::<u64>().unwrap_or(5000); +pub fn exec_header(ctx: PluginContext<'_>) -> Variable { + let url = ctx.arguments[0].to_string(); + let header = ctx.arguments[1].to_string(); + let agent = ctx.arguments[2].to_string(); + let timeout = ctx.arguments[3].to_string().parse::<u64>().unwrap_or(5000); #[cfg(feature = "test_mode")] if url.contains("redirect.") { - return Variable::String(url.split_once("/?").unwrap().1.to_string()); + return Variable::from(url.split_once("/?").unwrap().1.to_string()); } if let Ok(client) = reqwest::Client::builder() @@ -61,7 +61,7 @@ pub fn exec_header(ctx: PluginContext<'_>) -> Variable<'static> { .headers() .get(header.as_ref()) .and_then(|h| h.to_str().ok()) - .map(|h| Variable::String(h.to_string())) + .map(|h| Variable::from(h.to_string())) }) .unwrap_or_default() } else { diff --git a/crates/smtp/src/scripts/plugins/lookup.rs b/crates/smtp/src/scripts/plugins/lookup.rs index 706cbaed..a300ebdd 100644 --- a/crates/smtp/src/scripts/plugins/lookup.rs +++ b/crates/smtp/src/scripts/plugins/lookup.rs @@ -36,39 +36,43 @@ pub fn register_map(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { fnc_map.set_external_function("lookup_map", plugin_id, 2); } -pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { - let lookup_id = ctx.arguments[0].to_cow(); - let item = ctx.arguments[1].to_cow(); +pub fn exec(ctx: PluginContext<'_>) -> Variable { + let lookup_id = ctx.arguments[0].to_string(); let span = ctx.span; - - if !lookup_id.is_empty() && !item.is_empty() { - if let Some(lookup) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) { - return ctx - .handle - .block_on(lookup.contains(item.as_ref())) - .unwrap_or(false) - .into(); - } else { - tracing::warn!( - parent: span, - context = "sieve:lookup", - event = "failed", - reason = "Unknown lookup id", - lookup_id = %lookup_id, - ); + if let Some(lookup) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) { + match &ctx.arguments[1] { + Variable::Array(items) => { + for item in items.iter() { + if !item.is_empty() + && ctx.handle.block_on(lookup.contains(item)).unwrap_or(false) + { + return true.into(); + } + } + false + } + v if !v.is_empty() => ctx.handle.block_on(lookup.contains(v)).unwrap_or(false), + _ => false, } + } else { + tracing::warn!( + parent: span, + context = "sieve:lookup", + event = "failed", + reason = "Unknown lookup id", + lookup_id = %lookup_id, + ); + false } - - false.into() + .into() } -pub fn exec_map(ctx: PluginContext<'_>) -> Variable<'static> { - let mut arguments = ctx.arguments.into_iter(); - let lookup_id = arguments.next().unwrap().into_cow(); - let items = match arguments.next().unwrap() { - Variable::Array(l) => l.into_iter().map(DatabaseColumn::from).collect(), - Variable::ArrayRef(l) => l.iter().map(DatabaseColumn::from).collect(), - v => vec![DatabaseColumn::from(v)], +pub fn exec_map(ctx: PluginContext<'_>) -> Variable { + let lookup_id = ctx.arguments[0].to_string(); + let items = match &ctx.arguments[1] { + Variable::Array(l) => l.iter().map(DatabaseColumn::from).collect(), + v if !v.is_empty() => vec![DatabaseColumn::from(v)], + _ => vec![], }; let span = ctx.span; diff --git a/crates/smtp/src/scripts/plugins/mod.rs b/crates/smtp/src/scripts/plugins/mod.rs index 5909654b..d82b9457 100644 --- a/crates/smtp/src/scripts/plugins/mod.rs +++ b/crates/smtp/src/scripts/plugins/mod.rs @@ -35,14 +35,14 @@ use tokio::runtime::Handle; use crate::{config::scripts::SieveContext, core::SMTP}; type RegisterPluginFnc = fn(u32, &mut FunctionMap<SieveContext>) -> (); -type ExecPluginFnc = fn(PluginContext<'_>) -> Variable<'static>; +type ExecPluginFnc = fn(PluginContext<'_>) -> Variable; pub struct PluginContext<'x> { pub span: &'x tracing::Span, pub handle: &'x Handle, pub core: &'x SMTP, pub message: &'x Message<'x>, - pub arguments: Vec<Variable<'static>>, + pub arguments: Vec<Variable>, } const PLUGINS_EXEC: [ExecPluginFnc; 10] = [ @@ -105,6 +105,6 @@ impl SMTP { #[cfg(feature = "test_mode")] pub fn test_print(ctx: PluginContext<'_>) -> Input { - println!("{}", ctx.arguments[0].to_cow()); + println!("{}", ctx.arguments[0].to_string()); Input::True } diff --git a/crates/smtp/src/scripts/plugins/query.rs b/crates/smtp/src/scripts/plugins/query.rs index 60ad0255..31b55db2 100644 --- a/crates/smtp/src/scripts/plugins/query.rs +++ b/crates/smtp/src/scripts/plugins/query.rs @@ -31,27 +31,27 @@ pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) { fnc_map.set_external_function("query", plugin_id, 3); } -pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { +pub fn exec(ctx: PluginContext<'_>) -> Variable { let span = ctx.span; - let mut arguments = ctx.arguments.into_iter(); // Obtain directory name - let directory = arguments.next().unwrap().into_string(); - let directory = if let Some(directory_) = ctx.core.sieve.config.directories.get(&directory) { - directory_ - } else { - tracing::warn!( - parent: span, - context = "sieve:query", - event = "failed", - reason = "Unknown directory", - directory = %directory, - ); - return false.into(); - }; + let directory = ctx.arguments[0].to_string(); + let directory = + if let Some(directory_) = ctx.core.sieve.config.directories.get(directory.as_ref()) { + directory_ + } else { + tracing::warn!( + parent: span, + context = "sieve:query", + event = "failed", + reason = "Unknown directory", + directory = %directory, + ); + return false.into(); + }; // Obtain query string - let query = arguments.next().unwrap().into_string(); + let query = ctx.arguments[1].to_string(); if query.is_empty() { tracing::warn!( parent: span, @@ -63,9 +63,8 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { } // Obtain arguments - let arguments = match arguments.next().unwrap() { - Variable::Array(l) => l.into_iter().map(DatabaseColumn::from).collect(), - Variable::ArrayRef(l) => l.iter().map(DatabaseColumn::from).collect(), + let arguments = match &ctx.arguments[2] { + Variable::Array(l) => l.iter().map(DatabaseColumn::from).collect(), v => vec![DatabaseColumn::from(v)], }; @@ -81,7 +80,13 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { query_columns.pop().map(Variable::from).unwrap() } 0 => Variable::default(), - _ => Variable::Array(query_columns.into_iter().map(Variable::from).collect()), + _ => Variable::Array( + query_columns + .into_iter() + .map(Variable::from) + .collect::<Vec<_>>() + .into(), + ), } } else { false.into() |