use std::collections::HashMap; pub mod ast; pub mod meta; pub mod spamassassin; pub mod tokenizer; pub mod utils; #[derive(Debug, Default, Clone)] struct Rule { name: String, t: RuleType, scores: Vec, description: HashMap, priority: i32, flags: Vec, forward_score_pos: f64, forward_score_neg: f64, } #[derive(Debug, Default, Clone)] enum RuleType { Header { matches: HeaderMatches, header: Header, part: Vec, if_unset: Option, pattern: String, }, Body { pattern: String, raw: bool, }, Full { pattern: String, }, Uri { pattern: String, }, Eval { function: String, params: Vec, }, Meta { expr: MetaExpression, }, #[default] None, } #[derive(Debug, Clone, Default)] pub struct MetaExpression { pub tokens: Vec, pub expr: Expr, } impl RuleType { pub fn pattern(&mut self) -> Option<&mut String> { match self { RuleType::Header { pattern, .. } => Some(pattern), RuleType::Body { pattern, .. } => Some(pattern), RuleType::Full { pattern, .. } => Some(pattern), RuleType::Uri { pattern, .. } => Some(pattern), _ => None, } } } #[derive(Debug, PartialEq, Eq, Clone)] enum TestFlag { Net, Nice, UserConf, Learn, NoAutoLearn, Publish, Multiple, NoTrim, DomainsOnly, NoSubject, AutoLearnBody, A, MaxHits(u32), DnsBlockRule(String), } #[derive(Debug, Default, PartialEq, Eq, Clone)] enum Header { #[default] All, MessageId, AllExternal, EnvelopeFrom, ToCc, Name(String), } #[derive(Debug, Default, Clone)] enum HeaderMatches { #[default] Matches, NotMatches, Exists, } #[derive(Debug, Default, PartialEq, Eq, Clone)] enum HeaderPart { Name, Addr, #[default] Raw, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Token { Tag(String), Number(u32), Logical(Logical), Comparator(Comparator), Operation(Operation), OpenParen, CloseParen, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Logical { And, Or, Not, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Comparator { Gt, Lt, Eq, Ge, Le, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Operation { Add, Multiply, Divide, Subtract, And, Or, Not, } #[derive(Debug, PartialEq, Clone)] pub enum Expr { UnaryOp(UnaryOperator, Box), BinaryOp(Box, BinaryOperator, Box), Literal(u32), Identifier(String), } impl Default for Expr { fn default() -> Self { Self::Literal(0) } } #[derive(Debug, PartialEq, Clone)] pub enum UnaryOperator { Not, Minus, } #[derive(Debug, PartialEq, Clone)] pub enum BinaryOperator { Or, And, Greater, Lesser, GreaterOrEqual, LesserOrEqual, Equal, Add, Subtract, Multiply, Divide, BitwiseAnd, BitwiseOr, } impl BinaryOperator { pub fn precedence(&self) -> u32 { match self { Self::Or => 1, Self::And => 2, Self::Greater | Self::Lesser | Self::GreaterOrEqual | Self::LesserOrEqual | Self::Equal => 3, Self::Add | Self::Subtract => 4, Self::Multiply | Self::Divide => 5, Self::BitwiseAnd | Self::BitwiseOr => 6, } } } impl Rule { fn score(&self) -> f64 { self.scores.last().copied().unwrap_or_else(|| { if self.name.starts_with("__") { 0.0 } else if self.name.starts_with("T_") { 0.01 } else { 1.0 } }) } } impl Ord for Rule { fn cmp(&self, other: &Self) -> std::cmp::Ordering { let this_score = self.score(); let other_score = other.score(); let this_is_negative = this_score < 0.0; let other_is_negative = other_score < 0.0; if this_is_negative != other_is_negative { if this_is_negative { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater } } else { let this_priority = if this_score != 0.0 { self.priority } else { 9000 }; let other_priority = if other_score != 0.0 { other.priority } else { 9000 }; match this_priority.cmp(&other_priority) { std::cmp::Ordering::Equal => { match other_score.abs().partial_cmp(&this_score.abs()).unwrap() { std::cmp::Ordering::Equal => other.name.cmp(&self.name), x => x, } } x => x, } } } } impl PartialOrd for Rule { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl PartialEq for Rule { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.priority == other.priority && self.scores == other.scores } } impl Eq for Rule {} pub trait UnwrapResult { fn unwrap_result(self, action: &str) -> T; } impl UnwrapResult for Option { fn unwrap_result(self, message: &str) -> T { match self { Some(result) => result, None => { eprintln!("Failed to {}", message); std::process::exit(1); } } } } impl UnwrapResult for Result { fn unwrap_result(self, message: &str) -> T { match self { Ok(result) => result, Err(err) => { eprintln!("Failed to {}: {}", message, err); std::process::exit(1); } } } }