summaryrefslogtreecommitdiff
path: root/crates/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'crates/smtp')
-rw-r--r--crates/smtp/src/config/mod.rs1
-rw-r--r--crates/smtp/src/config/queue.rs7
-rw-r--r--crates/smtp/src/config/scripts.rs18
-rw-r--r--crates/smtp/src/inbound/data.rs4
-rw-r--r--crates/smtp/src/inbound/ehlo.rs8
-rw-r--r--crates/smtp/src/inbound/mail.rs2
-rw-r--r--crates/smtp/src/inbound/milter/message.rs2
-rw-r--r--crates/smtp/src/inbound/rcpt.rs2
-rw-r--r--crates/smtp/src/outbound/delivery.rs14
-rw-r--r--crates/smtp/src/scripts/exec.rs2
-rw-r--r--crates/smtp/src/scripts/functions/array.rs78
-rw-r--r--crates/smtp/src/scripts/functions/email.rs19
-rw-r--r--crates/smtp/src/scripts/functions/header.rs31
-rw-r--r--crates/smtp/src/scripts/functions/html.rs51
-rw-r--r--crates/smtp/src/scripts/functions/image.rs9
-rw-r--r--crates/smtp/src/scripts/functions/misc.rs44
-rw-r--r--crates/smtp/src/scripts/functions/mod.rs25
-rw-r--r--crates/smtp/src/scripts/functions/text.rs311
-rw-r--r--crates/smtp/src/scripts/functions/unicode.rs45
-rw-r--r--crates/smtp/src/scripts/functions/url.rs22
-rw-r--r--crates/smtp/src/scripts/mod.rs10
-rw-r--r--crates/smtp/src/scripts/plugins/bayes.rs71
-rw-r--r--crates/smtp/src/scripts/plugins/dns.rs28
-rw-r--r--crates/smtp/src/scripts/plugins/exec.rs4
-rw-r--r--crates/smtp/src/scripts/plugins/http.rs14
-rw-r--r--crates/smtp/src/scripts/plugins/lookup.rs60
-rw-r--r--crates/smtp/src/scripts/plugins/mod.rs6
-rw-r--r--crates/smtp/src/scripts/plugins/query.rs45
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()