/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use super::{ BinaryOperator, Constant, Expression, ExpressionItem, StringCow, SystemVariable, UnaryOperator, Variable, functions::{FUNCTIONS, ResolveVariable}, if_block::IfBlock, }; use crate::Server; use compact_str::{CompactString, ToCompactString, format_compact}; use hyper::StatusCode; use registry::{ schema::prelude::Property, types::{EnumImpl, id::ObjectId}, }; use std::{cmp::Ordering, fmt::Display}; use trc::{Collector, EvalEvent}; impl Server { pub async fn eval_if<'x, R: TryFrom>, V: ResolveVariable>( &'x self, if_block: &'x IfBlock, resolver: &'x V, session_id: u64, ) -> Option { if if_block.is_empty() { trc::event!( Eval(EvalEvent::Result), SpanId = session_id, Id = if_block.id.to_string(), Key = if_block.property.as_str(), Result = "" ); return None; } match (EvalContext { resolver, core: self, expr: if_block, captures: Vec::new(), session_id, }) .eval() .await { Ok(result) => { trc::event!( Eval(EvalEvent::Result), SpanId = session_id, Id = if_block.id.to_string(), Key = if_block.property.as_str(), Result = format!("{result:?}"), ); match result.try_into() { Ok(value) => Some(value), Err(_) => { trc::event!( Eval(EvalEvent::Result), SpanId = session_id, Id = if_block.id.to_string(), Key = if_block.property.as_str(), Result = "", ); None } } } Err(err) => { trc::event!( Eval(EvalEvent::Error), SpanId = session_id, Id = if_block.id.to_string(), Key = if_block.property.as_str(), CausedBy = err, ); None } } } pub async fn eval_expr<'x, R: TryFrom>, V: ResolveVariable>( &'x self, expr: &'x Expression, resolver: &'x V, obj_id: ObjectId, property: Property, session_id: u64, ) -> Option { if expr.is_empty() { return None; } match (EvalContext { resolver, core: self, expr, captures: &mut Vec::new(), session_id, }) .eval() .await { Ok(result) => { trc::event!( Eval(EvalEvent::Result), SpanId = session_id, Id = obj_id.to_string(), Key = property.as_str(), Result = format!("{result:?}"), ); match result.try_into() { Ok(value) => Some(value), Err(_) => { trc::event!( Eval(EvalEvent::Error), SpanId = session_id, Id = obj_id.to_string(), Key = property.as_str(), Details = "Failed to convert result", ); None } } } Err(err) => { trc::event!( Eval(EvalEvent::Error), SpanId = session_id, Id = obj_id.to_string(), Key = property.as_str(), CausedBy = err, ); None } } } } struct EvalContext<'x, V: ResolveVariable, T, C> { resolver: &'x V, core: &'x Server, expr: &'x T, captures: C, session_id: u64, } impl<'x, V: ResolveVariable> EvalContext<'x, V, IfBlock, Vec> { async fn eval(&mut self) -> trc::Result> { for if_then in &self.expr.if_then { if (EvalContext { resolver: self.resolver, core: self.core, expr: &if_then.expr, captures: &mut self.captures, session_id: self.session_id, }) .eval() .await? .to_bool() { return (EvalContext { resolver: self.resolver, core: self.core, expr: &if_then.then, captures: &mut self.captures, session_id: self.session_id, }) .eval() .await; } } (EvalContext { resolver: self.resolver, core: self.core, expr: &self.expr.default, captures: &mut self.captures, session_id: self.session_id, }) .eval() .await } } impl<'x, V: ResolveVariable> EvalContext<'x, V, Expression, &mut Vec> { async fn eval(&mut self) -> trc::Result> { let mut stack = Vec::new(); let mut exprs = self.expr.items.iter(); while let Some(expr) = exprs.next() { match expr { ExpressionItem::Variable(v) => { stack.push(self.resolver.resolve_variable(*v)); } ExpressionItem::Global(v) => { stack.push(self.resolver.resolve_global(v)); } ExpressionItem::Constant(val) => { stack.push(Variable::from(val)); } ExpressionItem::Capture(v) => { stack.push(Variable::String(StringCow::Owned( self.captures .get(*v as usize) .map(|v| v.as_str()) .unwrap_or_default() .to_compact_string(), ))); } ExpressionItem::System(setting) => match setting { SystemVariable::Hostname => { stack.push(self.core.core.network.server_name.as_str().into()) } SystemVariable::Domain => { stack.push(self.core.core.email.default_domain_name.as_str().into()) } SystemVariable::NodeId => stack.push(self.core.core.network.node_id.into()), SystemVariable::NodeHostname => { stack.push(self.core.registry().local_hostname().into()) } SystemVariable::NodeRole => stack.push( self.core .registry() .cluster_role() .unwrap_or_default() .into(), ), SystemVariable::Metric(variable) => { stack.push(Variable::Float(Collector::read_metric(*variable))); } }, ExpressionItem::UnaryOperator(op) => { let value = stack.pop().unwrap_or_default(); stack.push(match op { UnaryOperator::Not => value.op_not(), UnaryOperator::Minus => value.op_minus(), }); } ExpressionItem::BinaryOperator(op) => { let right = stack.pop().unwrap_or_default(); let left = stack.pop().unwrap_or_default(); stack.push(match op { BinaryOperator::Add => left.op_add(right), BinaryOperator::Subtract => left.op_subtract(right), BinaryOperator::Multiply => left.op_multiply(right), BinaryOperator::Divide => left.op_divide(right), BinaryOperator::And => left.op_and(right), BinaryOperator::Or => left.op_or(right), BinaryOperator::Xor => left.op_xor(right), BinaryOperator::Eq => left.op_eq(right), BinaryOperator::Ne => left.op_ne(right), BinaryOperator::Lt => left.op_lt(right), BinaryOperator::Le => left.op_le(right), BinaryOperator::Gt => left.op_gt(right), BinaryOperator::Ge => left.op_ge(right), }); } ExpressionItem::Function { id, num_args } => { let num_args = *num_args as usize; let mut arguments = Variable::array(num_args); for arg_num in 0..num_args { arguments[num_args - arg_num - 1] = stack.pop().unwrap_or_default(); } let result = if let Some((_, fnc, _)) = FUNCTIONS.get(*id as usize) { (fnc)(arguments) } else { Box::pin(self.core.eval_fnc( *id - FUNCTIONS.len() as u32, arguments, self.session_id, )) .await? }; stack.push(result); } ExpressionItem::JmpIf { val, pos } => { if stack.last().is_some_and(|v| v.to_bool()) == *val { for _ in 0..*pos { exprs.next(); } } } ExpressionItem::ArrayAccess => { let index = stack .pop() .unwrap_or_default() .to_usize() .unwrap_or_default(); let array = stack.pop().unwrap_or_default().into_array(); stack.push(array.into_iter().nth(index).unwrap_or_default()); } ExpressionItem::ArrayBuild(num_items) => { let num_items = *num_items as usize; let mut items = Variable::array(num_items); for arg_num in 0..num_items { items[num_items - arg_num - 1] = stack.pop().unwrap_or_default(); } stack.push(Variable::Array(items)); } ExpressionItem::Regex(regex) => { self.captures.clear(); let value = stack.pop().unwrap_or_default().into_string(); if let Some(captures_) = regex.captures(value.as_ref()) { for capture in captures_.iter() { self.captures .push(capture.map_or("", |m| m.as_str()).to_compact_string()); } } stack.push(Variable::Integer(!self.captures.is_empty() as i64)); } } } Ok(stack.pop().unwrap_or_default()) } } impl Expression { pub fn is_empty(&self) -> bool { self.items.is_empty() } pub fn items(&self) -> &[ExpressionItem] { &self.items } } impl<'x> Variable<'x> { pub fn op_add(self, other: Variable<'x>) -> Variable<'x> { match (self, other) { (Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_add(b)), (Variable::Float(a), Variable::Float(b)) => Variable::Float(a + b), (Variable::Integer(i), Variable::Float(f)) | (Variable::Float(f), Variable::Integer(i)) => Variable::Float(i as f64 + f), (Variable::Array(a), Variable::Array(b)) => { Variable::Array(a.into_iter().chain(b).collect::>()) } (Variable::Array(a), b) => { Variable::Array(a.into_iter().chain([b]).collect::>()) } (a, Variable::Array(b)) => { Variable::Array([a].into_iter().chain(b).collect::>()) } (Variable::String(a), b) => { if !a.is_empty() { Variable::String(StringCow::Owned(format_compact!("{}{}", a, b))) } else { b } } (a, Variable::String(b)) => { if !b.is_empty() { Variable::String(StringCow::Owned(format_compact!("{}{}", a, b))) } else { a } } (a, Variable::Constant(_)) => a, (Variable::Constant(_), b) => b, } } pub fn op_subtract(self, other: Variable<'x>) -> Variable<'x> { match (self, other) { (Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_sub(b)), (Variable::Float(a), Variable::Float(b)) => Variable::Float(a - b), (Variable::Integer(a), Variable::Float(b)) => Variable::Float(a as f64 - b), (Variable::Float(a), Variable::Integer(b)) => Variable::Float(a - b as f64), (Variable::Array(a), b) | (b, Variable::Array(a)) => { Variable::Array(a.into_iter().filter(|v| v != &b).collect::>()) } (a, b) => a.parse_number().op_subtract(b.parse_number()), } } pub fn op_multiply(self, other: Variable<'x>) -> Variable<'x> { match (self, other) { (Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_mul(b)), (Variable::Float(a), Variable::Float(b)) => Variable::Float(a * b), (Variable::Integer(i), Variable::Float(f)) | (Variable::Float(f), Variable::Integer(i)) => Variable::Float(i as f64 * f), (a, b) => a.parse_number().op_multiply(b.parse_number()), } } pub fn op_divide(self, other: Variable<'x>) -> Variable<'x> { match (self, other) { (Variable::Integer(a), Variable::Integer(b)) => { Variable::Float(if b != 0 { a as f64 / b as f64 } else { 0.0 }) } (Variable::Float(a), Variable::Float(b)) => { Variable::Float(if b != 0.0 { a / b } else { 0.0 }) } (Variable::Integer(a), Variable::Float(b)) => { Variable::Float(if b != 0.0 { a as f64 / b } else { 0.0 }) } (Variable::Float(a), Variable::Integer(b)) => { Variable::Float(if b != 0 { a / b as f64 } else { 0.0 }) } (a, b) => a.parse_number().op_divide(b.parse_number()), } } pub fn op_and(self, other: Variable) -> Variable { Variable::Integer(i64::from(self.to_bool() & other.to_bool())) } pub fn op_or(self, other: Variable) -> Variable { Variable::Integer(i64::from(self.to_bool() | other.to_bool())) } pub fn op_xor(self, other: Variable) -> Variable { Variable::Integer(i64::from(self.to_bool() ^ other.to_bool())) } pub fn op_eq(self, other: Variable) -> Variable { Variable::Integer(i64::from(self == other)) } pub fn op_ne(self, other: Variable) -> Variable { Variable::Integer(i64::from(self != other)) } pub fn op_lt(self, other: Variable) -> Variable { Variable::Integer(i64::from(self < other)) } pub fn op_le(self, other: Variable) -> Variable { Variable::Integer(i64::from(self <= other)) } pub fn op_gt(self, other: Variable) -> Variable { Variable::Integer(i64::from(self > other)) } pub fn op_ge(self, other: Variable) -> Variable { Variable::Integer(i64::from(self >= other)) } pub fn op_not(self) -> Variable<'static> { Variable::Integer(i64::from(!self.to_bool())) } pub fn op_minus(self) -> Variable<'static> { match self { Variable::Integer(n) => Variable::Integer(-n), Variable::Float(n) => Variable::Float(-n), _ => self.parse_number().op_minus(), } } pub fn parse_number(&self) -> Variable<'static> { match self { Variable::String(s) if !s.is_empty() => { if let Ok(n) = s.as_str().parse::() { Variable::Integer(n) } else if let Ok(n) = s.as_str().parse::() { Variable::Float(n) } else { Variable::Integer(0) } } Variable::Integer(n) => Variable::Integer(*n), Variable::Float(n) => Variable::Float(*n), Variable::Array(l) => Variable::Integer(l.is_empty() as i64), _ => Variable::Integer(0), } } #[inline(always)] fn array(num_items: usize) -> Vec> { let mut items = Vec::with_capacity(num_items); for _ in 0..num_items { items.push(Variable::Integer(0)); } items } pub fn to_ref<'y: 'x>(&'y self) -> Variable<'x> { match self { Variable::String(s) => Variable::String(StringCow::Borrowed(s.as_str())), Variable::Integer(n) => Variable::Integer(*n), Variable::Float(n) => Variable::Float(*n), Variable::Constant(c) => Variable::Constant(*c), Variable::Array(l) => Variable::Array(l.iter().map(|v| v.to_ref()).collect::>()), } } pub fn to_bool(&self) -> bool { match self { Variable::Float(f) => *f != 0.0, Variable::Integer(n) => *n != 0, Variable::String(s) => !s.is_empty(), Variable::Array(a) => !a.is_empty(), Variable::Constant(_) => true, } } pub fn to_string(&'_ self) -> StringCow<'_> { match self { Variable::String(s) => StringCow::Borrowed(s.as_str()), Variable::Integer(n) => StringCow::Owned(n.to_compact_string()), Variable::Float(n) => StringCow::Owned(n.to_compact_string()), Variable::Array(l) => { let mut result = CompactString::with_capacity(self.len() * 10); for item in l { if !result.is_empty() { result.push_str("\r\n"); } match item { Variable::String(v) => result.push_str(v.as_str()), Variable::Integer(v) => result.push_str(&v.to_compact_string()), Variable::Float(v) => result.push_str(&v.to_compact_string()), Variable::Array(_) => {} Variable::Constant(c) => result.push_str(c.as_str()), } } StringCow::Owned(result) } Variable::Constant(c) => StringCow::Borrowed(c.as_str()), } } pub fn into_string(self) -> StringCow<'x> { match self { Variable::String(s) => s, Variable::Integer(n) => StringCow::Owned(n.to_compact_string()), Variable::Float(n) => StringCow::Owned(n.to_compact_string()), Variable::Array(l) => { let mut result = CompactString::with_capacity(l.len() * 10); for item in l { if !result.is_empty() { result.push_str("\r\n"); } match item { Variable::String(v) => result.push_str(v.as_ref()), Variable::Integer(v) => result.push_str(&v.to_compact_string()), Variable::Float(v) => result.push_str(&v.to_compact_string()), Variable::Array(_) => {} Variable::Constant(c) => result.push_str(c.as_str()), } } StringCow::Owned(result) } Variable::Constant(c) => StringCow::Borrowed(c.as_str()), } } pub fn to_integer(&self) -> Option { match self { Variable::Integer(n) => Some(*n), Variable::Float(n) => Some(*n as i64), Variable::String(s) if !s.is_empty() => s.as_str().parse::().ok(), _ => None, } } pub fn to_usize(&self) -> Option { match self { Variable::Integer(n) => Some(*n as usize), Variable::Float(n) => Some(*n as usize), Variable::String(s) if !s.is_empty() => s.as_str().parse::().ok(), _ => None, } } pub fn len(&self) -> usize { match self { Variable::String(s) => s.len(), Variable::Integer(_) | Variable::Float(_) => 2, Variable::Array(l) => l.iter().map(|v| v.len() + 2).sum(), Variable::Constant(c) => c.as_str().len(), } } pub fn is_empty(&self) -> bool { match self { Variable::String(s) => s.is_empty(), _ => false, } } pub fn as_array(&'_ self) -> Option<&'_ [Variable<'_>]> { match self { Variable::Array(l) => Some(l), _ => None, } } pub fn into_array(self) -> Vec> { match self { Variable::Array(l) => l, v if !v.is_empty() => vec![v], _ => vec![], } } pub fn to_array(&self) -> Vec> { match self { Variable::Array(l) => l.iter().map(|v| v.to_ref()).collect::>(), v if !v.is_empty() => vec![v.to_ref()], _ => vec![], } } pub fn into_owned(self) -> Variable<'static> { match self { Variable::String(s) => Variable::String(StringCow::Owned(s.into_owned())), Variable::Integer(n) => Variable::Integer(n), Variable::Float(n) => Variable::Float(n), Variable::Constant(c) => Variable::Constant(c), Variable::Array(l) => Variable::Array(l.into_iter().map(|v| v.into_owned()).collect()), } } } impl PartialEq for Variable<'_> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Integer(a), Self::Integer(b)) => a == b, (Self::Float(a), Self::Float(b)) => a == b, (Self::Integer(a), Self::Float(b)) | (Self::Float(b), Self::Integer(a)) => { *a as f64 == *b } (Self::String(a), Self::String(b)) => a.as_str() == b.as_str(), (Self::String(_), Self::Integer(_) | Self::Float(_)) => &self.parse_number() == other, (Self::Integer(_) | Self::Float(_), Self::String(_)) => self == &other.parse_number(), (Self::Array(a), Self::Array(b)) => a == b, _ => false, } } } impl Eq for Variable<'_> {} #[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for Variable<'_> { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Self::Integer(a), Self::Integer(b)) => a.partial_cmp(b), (Self::Float(a), Self::Float(b)) => a.partial_cmp(b), (Self::Integer(a), Self::Float(b)) => (*a as f64).partial_cmp(b), (Self::Float(a), Self::Integer(b)) => a.partial_cmp(&(*b as f64)), (Self::String(a), Self::String(b)) => a.as_str().partial_cmp(b.as_str()), (Self::String(_), Self::Integer(_) | Self::Float(_)) => { self.parse_number().partial_cmp(other) } (Self::Integer(_) | Self::Float(_), Self::String(_)) => { self.partial_cmp(&other.parse_number()) } (Self::Array(a), Self::Array(b)) => a.partial_cmp(b), (Self::Array(_) | Self::String(_), _) => Ordering::Greater.into(), (Self::Constant(a), Self::Constant(b)) => a.to_id().partial_cmp(&b.to_id()), (_, Self::Array(_) | Self::Constant(_)) | (Self::Constant(_), _) => { Ordering::Less.into() } } } } impl Ord for Variable<'_> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap_or(Ordering::Greater) } } impl Display for Variable<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Variable::String(v) => v.fmt(f), Variable::Integer(v) => v.fmt(f), Variable::Float(v) => v.fmt(f), Variable::Array(v) => { for (i, v) in v.iter().enumerate() { if i > 0 { f.write_str("\n")?; } v.fmt(f)?; } Ok(()) } Variable::Constant(c) => c.as_str().fmt(f), } } } impl<'x> From<&'x Constant> for Variable<'x> { fn from(value: &'x Constant) -> Self { match value { Constant::Integer(i) => Variable::Integer(*i), Constant::Float(f) => Variable::Float(*f), Constant::String(s) => Variable::String(StringCow::Borrowed(s.as_str())), Constant::Static(c) => Variable::Constant(*c), } } } impl<'x> TryFrom> for CompactString { type Error = (); fn try_from(value: Variable<'x>) -> Result { if let Variable::String(s) = value { Ok(match s { StringCow::Borrowed(v) => v.into(), StringCow::Owned(v) => v, }) } else { Err(()) } } } impl<'x> TryFrom> for String { type Error = (); fn try_from(value: Variable<'x>) -> Result { if let Variable::String(s) = value { Ok(match s { StringCow::Borrowed(v) => v.to_string(), StringCow::Owned(v) => v.into_string(), }) } else { Err(()) } } } impl<'x> From> for bool { fn from(val: Variable<'x>) -> Self { val.to_bool() } } impl<'x> TryFrom> for i64 { type Error = (); fn try_from(value: Variable<'x>) -> Result { value.to_integer().ok_or(()) } } impl<'x> TryFrom> for u64 { type Error = (); fn try_from(value: Variable<'x>) -> Result { value.to_integer().map(|v| v as u64).ok_or(()) } } impl<'x> TryFrom> for usize { type Error = (); fn try_from(value: Variable<'x>) -> Result { value.to_usize().ok_or(()) } } impl<'x> TryFrom> for StatusCode { type Error = (); fn try_from(value: Variable<'x>) -> Result { match value.to_integer() { Some(v) => match StatusCode::from_u16(v as u16) { Ok(status) => Ok(status), Err(_) => Err(()), }, None => Err(()), } } }