use crate::{rule_state::RuleState, vsl_guard_ok, ExecutionStage};
use vsmtp_common::status::Status;
pub mod action;
#[cfg(feature = "delegation")]
pub mod delegation;
pub mod rule;
pub type Directives = std::collections::BTreeMap<ExecutionStage, Vec<Directive>>;
#[derive(strum::AsRefStr)]
#[strum(serialize_all = "lowercase")]
pub enum Directive {
Rule {
name: String,
pointer: rhai::FnPtr,
},
Action {
name: String,
pointer: rhai::FnPtr,
},
#[cfg(feature = "delegation")]
Delegation {
name: String,
pointer: rhai::FnPtr,
service: std::sync::Arc<crate::dsl::smtp::service::Smtp>,
},
}
impl std::fmt::Debug for Directive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(self.as_ref())
.field("name", &self.name())
.finish_non_exhaustive()
}
}
#[must_use]
#[derive(Debug, thiserror::Error)]
pub enum ExecutionError {
#[error("vsl execution produced an error: {0}")]
RuntimeError(#[from] Box<rhai::EvalAltResult>),
}
impl Directive {
pub(crate) fn parse_directive(
symbols: &[rhai::ImmutableString],
look_ahead: &str,
state: &mut rhai::Dynamic,
) -> Result<Option<rhai::ImmutableString>, rhai::ParseError> {
if symbols.len() == 1 {
*state = rhai::Dynamic::from(symbols[0].clone());
}
let directive_type = state.to_string();
match symbols.len() {
#[cfg(feature = "delegation")]
1 if directive_type.as_str() == "delegate" => Ok(Some("$expr$".into())),
#[cfg(feature = "delegation")]
2 if directive_type.as_str() == "delegate" => Ok(Some("$string$".into())),
#[cfg(feature = "delegation")]
3 if directive_type.as_str() == "delegate" => Ok(Some("$expr$".into())),
#[cfg(feature = "delegation")]
4 if directive_type.as_str() == "delegate" => Ok(None),
1 => Ok(Some("$string$".into())),
2 => Ok(Some("$expr$".into())),
3 => Ok(None),
_ => Err(rhai::ParseError(
Box::new(rhai::ParseErrorType::BadInput(
rhai::LexError::UnexpectedInput(format!(
"Improper {directive_type} declaration: the '{look_ahead}' keyword is unknown.",
)),
)),
rhai::Position::NONE,
)),
}
}
pub(crate) fn name(&self) -> &str {
match self {
#[cfg(feature = "delegation")]
Self::Delegation { name, .. } => name,
Self::Rule { name, .. } | Self::Action { name, .. } => name,
}
}
#[tracing::instrument(skip(rule_state, ast, stage), ret, err)]
pub(crate) fn execute(
&self,
rule_state: &RuleState,
ast: &rhai::AST,
stage: ExecutionStage,
) -> Result<Status, ExecutionError> {
match self {
Self::Rule { pointer, .. } => rule_state
.engine()
.call_fn(&mut rhai::Scope::new(), ast, pointer.fn_name(), ())
.map_err(Into::into),
Self::Action { pointer, .. } => {
let _: rhai::Dynamic = rule_state.engine().call_fn::<rhai::Dynamic>(
&mut rhai::Scope::new(),
ast,
pointer.fn_name(),
(),
)?;
Ok(Status::Next)
}
#[cfg(feature = "delegation")]
Self::Delegation {
pointer,
service,
name,
} => {
let args = vsl_guard_ok!(rule_state.message().read())
.get_header("X-VSMTP-DELEGATION")
.and_then(|header| {
vsmtp_mail_parser::get_mime_header("X-VSMTP-DELEGATION", &header)
.args
.get("directive")
.cloned()
});
match args {
Some(header_directive) if header_directive == *name => rule_state
.engine()
.call_fn(&mut rhai::Scope::new(), ast, pointer.fn_name(), ())
.map_err(Into::into),
_ => {
vsl_guard_ok!(rule_state.message().write()).prepend_header(
"X-VSMTP-DELEGATION",
&format!(
"sent; stage={stage}; directive=\"{name}\"; id=\"{}\"",
vsl_guard_ok!(rule_state.context().read())
.message_uuid()
.unwrap()
),
);
Ok(Status::Delegated(service.delegator.clone()))
}
}
}
}
}
}