From 529ad0221d262a5f3cc85c5ebb7c714c94e47fb3 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Tue, 26 Sep 2023 16:34:09 +0200 Subject: [PATCH] Allow passing a context object to external functions --- src/compiler/mod.rs | 2 +- src/lib.rs | 18 +- src/runtime/actions/action_convert.rs | 2 +- src/runtime/actions/action_editheader.rs | 6 +- src/runtime/actions/action_fileinto.rs | 2 +- src/runtime/actions/action_flags.rs | 4 +- src/runtime/actions/action_include.rs | 2 +- src/runtime/actions/action_mime.rs | 8 +- src/runtime/actions/action_notify.rs | 2 +- src/runtime/actions/action_redirect.rs | 2 +- src/runtime/actions/action_set.rs | 6 +- src/runtime/actions/action_vacation.rs | 4 +- src/runtime/context.rs | 10 +- src/runtime/eval.rs | 2 +- src/runtime/expression.rs | 2 +- src/runtime/mod.rs | 92 +++--- src/runtime/tests/comparator.rs | 15 +- src/runtime/tests/glob.rs | 386 ++++++++++++----------- src/runtime/tests/mime.rs | 10 +- src/runtime/tests/mod.rs | 2 +- src/runtime/tests/test_address.rs | 4 +- src/runtime/tests/test_body.rs | 2 +- src/runtime/tests/test_date.rs | 6 +- src/runtime/tests/test_duplicate.rs | 2 +- src/runtime/tests/test_envelope.rs | 4 +- src/runtime/tests/test_exists.rs | 2 +- src/runtime/tests/test_extlists.rs | 2 +- src/runtime/tests/test_hasflag.rs | 2 +- src/runtime/tests/test_header.rs | 4 +- src/runtime/tests/test_metadata.rs | 4 +- src/runtime/tests/test_notify.rs | 4 +- src/runtime/tests/test_size.rs | 2 +- src/runtime/tests/test_spamtest.rs | 4 +- src/runtime/tests/test_string.rs | 2 +- src/runtime/variables.rs | 2 +- 35 files changed, 324 insertions(+), 299 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index a0fc088..2da7d1a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -343,7 +343,7 @@ impl Compiler { }) } - pub fn register_functions(mut self, fnc_map: &mut FunctionMap) -> Self { + pub fn register_functions(mut self, fnc_map: &mut FunctionMap) -> Self { self.functions = std::mem::take(&mut fnc_map.map); self } diff --git a/src/lib.rs b/src/lib.rs index e125e97..6bed467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -312,16 +312,16 @@ pub struct Compiler { pub(crate) functions: AHashMap, } -pub type Function = for<'x> fn(&'x Context<'x>, Vec>) -> Variable<'x>; +pub type Function = for<'x> fn(&'x Context<'x, C>, Vec>) -> Variable<'x>; #[derive(Default, Clone)] -pub struct FunctionMap { +pub struct FunctionMap { pub(crate) map: AHashMap, - pub(crate) functions: Vec, + pub(crate) functions: Vec>, } #[derive(Debug, Clone)] -pub struct Runtime { +pub struct Runtime { pub(crate) allowed_capabilities: AHashSet, pub(crate) valid_notification_uris: AHashSet>, pub(crate) valid_ext_lists: AHashSet>, @@ -330,7 +330,7 @@ pub struct Runtime { pub(crate) metadata: Vec<(Metadata, Cow<'static, str>)>, pub(crate) include_scripts: AHashMap>, pub(crate) local_hostname: Cow<'static, str>, - pub(crate) functions: Vec, + pub(crate) functions: Vec>, pub(crate) max_nested_includes: usize, pub(crate) cpu_limit: usize, @@ -346,14 +346,16 @@ pub struct Runtime { pub(crate) vacation_use_orig_rcpt: bool, pub(crate) vacation_default_subject: Cow<'static, str>, pub(crate) vacation_subject_prefix: Cow<'static, str>, + + pub(crate) context: C, } #[derive(Clone, Debug)] -pub struct Context<'x> { +pub struct Context<'x, C> { #[cfg(test)] - pub(crate) runtime: Runtime, + pub(crate) runtime: Runtime, #[cfg(not(test))] - pub(crate) runtime: &'x Runtime, + pub(crate) runtime: &'x Runtime, pub(crate) user_address: Cow<'x, str>, pub(crate) user_full_name: Cow<'x, str>, pub(crate) current_time: i64, diff --git a/src/runtime/actions/action_convert.rs b/src/runtime/actions/action_convert.rs index 8de8254..eda4af3 100644 --- a/src/runtime/actions/action_convert.rs +++ b/src/runtime/actions/action_convert.rs @@ -38,7 +38,7 @@ enum Conversion { } impl Convert { - pub(crate) fn exec(&self, ctx: &mut Context) -> TestResult { + pub(crate) fn exec(&self, ctx: &mut Context) -> TestResult { let from_media_type = ctx.eval_value(&self.from_media_type).into_cow(); let to_media_type = ctx.eval_value(&self.to_media_type).into_cow(); diff --git a/src/runtime/actions/action_editheader.rs b/src/runtime/actions/action_editheader.rs index ea6a980..55ba6d2 100644 --- a/src/runtime/actions/action_editheader.rs +++ b/src/runtime/actions/action_editheader.rs @@ -38,7 +38,7 @@ use crate::{ }; impl AddHeader { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let header_name_ = ctx.eval_value(&self.field_name).into_cow(); let mut header_name = String::with_capacity(header_name_.len()); @@ -68,7 +68,7 @@ impl AddHeader { } impl DeleteHeader { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let header_name = if let Some(header_name) = HeaderName::parse(ctx.eval_value(&self.field_name).into_cow()) { @@ -175,7 +175,7 @@ impl RemoveCrLf for &str { } } -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn insert_header( &mut self, part_id: usize, diff --git a/src/runtime/actions/action_fileinto.rs b/src/runtime/actions/action_fileinto.rs index a87c3cb..07b1bd6 100644 --- a/src/runtime/actions/action_fileinto.rs +++ b/src/runtime/actions/action_fileinto.rs @@ -24,7 +24,7 @@ use crate::{compiler::grammar::actions::action_fileinto::FileInto, Context, Event}; impl FileInto { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let folder = ctx.eval_value(&self.folder).into_string(); let mut events = Vec::with_capacity(2); if let Some(event) = ctx.build_message_id() { diff --git a/src/runtime/actions/action_flags.rs b/src/runtime/actions/action_flags.rs index 763166c..bd75be0 100644 --- a/src/runtime/actions/action_flags.rs +++ b/src/runtime/actions/action_flags.rs @@ -30,7 +30,7 @@ use crate::{ }; impl EditFlags { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let mut var_name_ = None; let var_name = self.name.as_ref().unwrap_or_else(|| { var_name_.get_or_insert_with(|| VariableType::Global("__flags".to_string())) @@ -103,7 +103,7 @@ impl EditFlags { } } -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn tokenize_flags( &self, strings: &[Value], diff --git a/src/runtime/actions/action_include.rs b/src/runtime/actions/action_include.rs index 5a93248..dc0dc39 100644 --- a/src/runtime/actions/action_include.rs +++ b/src/runtime/actions/action_include.rs @@ -37,7 +37,7 @@ pub(crate) enum IncludeResult { } impl Include { - pub(crate) fn exec(&self, ctx: &Context) -> IncludeResult { + pub(crate) fn exec(&self, ctx: &Context) -> IncludeResult { let script_name = ctx.eval_value(&self.value); if !script_name.is_empty() { let script_name = if self.location == Location::Global { diff --git a/src/runtime/actions/action_mime.rs b/src/runtime/actions/action_mime.rs index d7392aa..605e3c1 100644 --- a/src/runtime/actions/action_mime.rs +++ b/src/runtime/actions/action_mime.rs @@ -41,7 +41,7 @@ use super::action_editheader::RemoveCrLf; use mail_builder::headers::message_id::generate_message_id_header; impl Replace { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { // Delete children parts let mut part_ids = ctx.find_nested_parts_ids(false); part_ids.sort_unstable_by_key(|a| Reverse(*a)); @@ -182,7 +182,7 @@ impl Replace { } impl Enclose { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let body = ctx.eval_value(&self.value).into_string(); let subject = self .subject @@ -341,7 +341,7 @@ impl Enclose { } impl ExtractText { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let mut value = String::new(); if !ctx.part_iter_stack.is_empty() { @@ -400,7 +400,7 @@ enum StackItem<'x> { None, } -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn build_message_id(&mut self) -> Option { if self.has_changes { self.last_message_id += 1; diff --git a/src/runtime/actions/action_notify.rs b/src/runtime/actions/action_notify.rs index fcdba1b..4a8008a 100644 --- a/src/runtime/actions/action_notify.rs +++ b/src/runtime/actions/action_notify.rs @@ -35,7 +35,7 @@ use crate::{ use super::action_vacation::MAX_SUBJECT_LEN; impl Notify { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { // Do not notify on Auto-Submitted messages for header in &ctx.message.parts[0].headers { if matches!(&header.name, HeaderName::Other(name) if name.eq_ignore_ascii_case("Auto-Submitted")) diff --git a/src/runtime/actions/action_redirect.rs b/src/runtime/actions/action_redirect.rs index 0946f09..583ef49 100644 --- a/src/runtime/actions/action_redirect.rs +++ b/src/runtime/actions/action_redirect.rs @@ -29,7 +29,7 @@ use crate::{ }; impl Redirect { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { if let Some(address) = sanitize_address(ctx.eval_value(&self.address).into_cow().as_ref()) { if ctx.num_redirects < ctx.runtime.max_redirects && ctx.num_out_messages < ctx.runtime.max_out_messages diff --git a/src/runtime/actions/action_set.rs b/src/runtime/actions/action_set.rs index f855c70..a1881e6 100644 --- a/src/runtime/actions/action_set.rs +++ b/src/runtime/actions/action_set.rs @@ -32,7 +32,7 @@ use crate::{ use std::fmt::Write; impl Set { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let mut value = ctx.eval_value(&self.value).into_owned(); for modifier in &self.modifiers { value = modifier.apply(value.into_cow().as_ref(), ctx).into(); @@ -42,7 +42,7 @@ impl Set { } } -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn set_variable(&mut self, var_name: &VariableType, mut variable: Variable<'x>) { if variable.len() > self.runtime.max_variable_size { let mut new_variable = String::with_capacity(self.runtime.max_variable_size); @@ -100,7 +100,7 @@ impl<'x> Context<'x> { } impl Modifier { - pub(crate) fn apply(&self, input: &str, ctx: &Context) -> String { + pub(crate) fn apply(&self, input: &str, ctx: &Context) -> String { let max_len = ctx.runtime.max_variable_size; match self { Modifier::Lower => input.to_lowercase(), diff --git a/src/runtime/actions/action_vacation.rs b/src/runtime/actions/action_vacation.rs index 8fbd25a..726be1d 100644 --- a/src/runtime/actions/action_vacation.rs +++ b/src/runtime/actions/action_vacation.rs @@ -41,7 +41,7 @@ use crate::{ pub(crate) const MAX_SUBJECT_LEN: usize = 256; impl TestVacation { - pub(crate) fn exec(&self, ctx: &mut Context) -> TestResult { + pub(crate) fn exec(&self, ctx: &mut Context) -> TestResult { let mut from = String::new(); let mut user_addresses = Vec::new(); @@ -177,7 +177,7 @@ impl TestVacation { } impl Vacation { - pub(crate) fn exec(&self, ctx: &mut Context) { + pub(crate) fn exec(&self, ctx: &mut Context) { let mut vacation_to = Cow::from(""); for (name, value) in &ctx.envelope { diff --git a/src/runtime/context.rs b/src/runtime/context.rs index 5464b66..345db36 100644 --- a/src/runtime/context.rs +++ b/src/runtime/context.rs @@ -46,8 +46,8 @@ pub(crate) struct ScriptStack { pub(crate) prev_vars_match: Vec>, } -impl<'x> Context<'x> { - pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self { +impl<'x, C: Clone> Context<'x, C> { + pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self { Context { #[cfg(test)] runtime: runtime.clone(), @@ -90,7 +90,9 @@ impl<'x> Context<'x> { spam_status: SpamStatus::Unknown, } } +} +impl<'x, C> Context<'x, C> { #[allow(clippy::while_let_on_iterator)] pub fn run(&mut self, input: Input) -> Option> { match input { @@ -632,4 +634,8 @@ impl<'x> Context<'x> { pub fn part(&self) -> usize { self.part } + + pub fn context(&self) -> &C { + &self.runtime.context + } } diff --git a/src/runtime/eval.rs b/src/runtime/eval.rs index 5ddb325..1d11771 100644 --- a/src/runtime/eval.rs +++ b/src/runtime/eval.rs @@ -39,7 +39,7 @@ use crate::{ use super::Variable; -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn variable<'y: 'x>(&'y self, var: &VariableType) -> Option> { match var { VariableType::Local(var_num) => self.vars_local.get(*var_num).map(|v| v.as_ref()), diff --git a/src/runtime/expression.rs b/src/runtime/expression.rs index 002f64c..4f033f4 100644 --- a/src/runtime/expression.rs +++ b/src/runtime/expression.rs @@ -27,7 +27,7 @@ use crate::{compiler::Number, runtime::Variable, Context}; use crate::compiler::grammar::expr::{BinaryOperator, Constant, Expression, UnaryOperator}; -impl<'x> Context<'x> { +impl<'x, C> Context<'x, C> { pub(crate) fn eval_expression<'y: 'x>( &'y self, expr: &'x [Expression], diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index b1ae0c0..b46804a 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -426,8 +426,47 @@ impl PluginArgument { } } -impl Runtime { - pub fn new() -> Self { +impl Runtime { + pub fn filter<'z: 'x, 'x>(&'z self, raw_message: &'x [u8]) -> Context<'x, C> { + Context::new( + self, + MessageParser::new() + .parse(raw_message) + .unwrap_or_else(|| Message { + parts: vec![MessagePart { + headers: vec![], + is_encoding_problem: false, + body: PartType::Text("".into()), + encoding: Encoding::None, + offset_header: 0, + offset_body: 0, + offset_end: 0, + }], + raw_message: b""[..].into(), + ..Default::default() + }), + ) + } + + pub fn filter_parsed<'z: 'x, 'x>(&'z self, message: Message<'x>) -> Context<'x, C> { + Context::new(self, message) + } +} + +impl Runtime<()> { + pub fn new() -> Runtime<()> { + Self::new_with_context(()) + } +} + +impl Default for Runtime<()> { + fn default() -> Self { + Self::new() + } +} + +impl Runtime { + pub fn new_with_context(context: C) -> Self { #[allow(unused_mut)] let mut allowed_capabilities = AHashSet::from_iter(Capability::all().iter().cloned()); @@ -462,6 +501,7 @@ impl Runtime { default_duplicate_expiry: 7 * 86400, local_hostname: "localhost".into(), functions: Vec::new(), + context, } } @@ -700,58 +740,36 @@ impl Runtime { self } - pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self { + pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self { self.functions = std::mem::take(&mut fnc_map.functions); self } - pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) { + pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) { self.functions = std::mem::take(&mut fnc_map.functions); } - - pub fn filter<'z: 'x, 'x>(&'z self, raw_message: &'x [u8]) -> Context<'x> { - Context::new( - self, - MessageParser::new() - .parse(raw_message) - .unwrap_or_else(|| Message { - parts: vec![MessagePart { - headers: vec![], - is_encoding_problem: false, - body: PartType::Text("".into()), - encoding: Encoding::None, - offset_header: 0, - offset_body: 0, - offset_end: 0, - }], - raw_message: b""[..].into(), - ..Default::default() - }), - ) - } - - pub fn filter_parsed<'z: 'x, 'x>(&'z self, message: Message<'x>) -> Context<'x> { - Context::new(self, message) - } } -impl FunctionMap { +impl FunctionMap { pub fn new() -> Self { - Self::default() + FunctionMap { + map: Default::default(), + functions: Default::default(), + } } - pub fn with_function(self, name: impl Into, fnc: Function) -> Self { + pub fn with_function(self, name: impl Into, fnc: Function) -> Self { self.with_function_args(name, fnc, 1) } - pub fn with_function_no_args(self, name: impl Into, fnc: Function) -> Self { + pub fn with_function_no_args(self, name: impl Into, fnc: Function) -> Self { self.with_function_args(name, fnc, 0) } pub fn with_function_args( mut self, name: impl Into, - fnc: Function, + fnc: Function, num_args: u32, ) -> Self { self.map @@ -761,12 +779,6 @@ impl FunctionMap { } } -impl Default for Runtime { - fn default() -> Self { - Self::new() - } -} - impl Input { pub fn script(name: impl Into