Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+787
View File
@@ -0,0 +1,787 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* 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<Variable<'x>>, V: ResolveVariable>(
&'x self,
if_block: &'x IfBlock,
resolver: &'x V,
session_id: u64,
) -> Option<R> {
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<Variable<'x>>, V: ResolveVariable>(
&'x self,
expr: &'x Expression,
resolver: &'x V,
obj_id: ObjectId,
property: Property,
session_id: u64,
) -> Option<R> {
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<CompactString>> {
async fn eval(&mut self) -> trc::Result<Variable<'x>> {
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<CompactString>> {
async fn eval(&mut self) -> trc::Result<Variable<'x>> {
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::<Vec<_>>())
}
(Variable::Array(a), b) => {
Variable::Array(a.into_iter().chain([b]).collect::<Vec<_>>())
}
(a, Variable::Array(b)) => {
Variable::Array([a].into_iter().chain(b).collect::<Vec<_>>())
}
(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::<Vec<_>>())
}
(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::<i64>() {
Variable::Integer(n)
} else if let Ok(n) = s.as_str().parse::<f64>() {
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<Variable<'static>> {
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::<Vec<_>>()),
}
}
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<i64> {
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::<i64>().ok(),
_ => None,
}
}
pub fn to_usize(&self) -> Option<usize> {
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::<usize>().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<Variable<'x>> {
match self {
Variable::Array(l) => l,
v if !v.is_empty() => vec![v],
_ => vec![],
}
}
pub fn to_array(&self) -> Vec<Variable<'_>> {
match self {
Variable::Array(l) => l.iter().map(|v| v.to_ref()).collect::<Vec<_>>(),
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<Ordering> {
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<Variable<'x>> for CompactString {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
if let Variable::String(s) = value {
Ok(match s {
StringCow::Borrowed(v) => v.into(),
StringCow::Owned(v) => v,
})
} else {
Err(())
}
}
}
impl<'x> TryFrom<Variable<'x>> for String {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
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<Variable<'x>> for bool {
fn from(val: Variable<'x>) -> Self {
val.to_bool()
}
}
impl<'x> TryFrom<Variable<'x>> for i64 {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
value.to_integer().ok_or(())
}
}
impl<'x> TryFrom<Variable<'x>> for u64 {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
value.to_integer().map(|v| v as u64).ok_or(())
}
}
impl<'x> TryFrom<Variable<'x>> for usize {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
value.to_usize().ok_or(())
}
}
impl<'x> TryFrom<Variable<'x>> for StatusCode {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value.to_integer() {
Some(v) => match StatusCode::from_u16(v as u16) {
Ok(status) => Ok(status),
Err(_) => Err(()),
},
None => Err(()),
}
}
}
+65
View File
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::expr::Variable;
pub(crate) fn fn_count(v: Vec<Variable>) -> Variable {
match &v[0] {
Variable::Array(a) => a.len(),
v => {
if !v.is_empty() {
1
} else {
0
}
}
}
.into()
}
pub(crate) fn fn_sort(mut v: Vec<Variable>) -> Variable {
let is_asc = v[1].to_bool();
let mut arr = v.remove(0).into_array();
if is_asc {
arr.sort_unstable();
} else {
arr.sort_unstable_by(|a, b| b.cmp(a));
}
arr.into()
}
pub(crate) fn fn_dedup(mut v: Vec<Variable>) -> Variable {
let arr = v.remove(0).into_array();
let mut result = Vec::with_capacity(arr.len());
for item in arr {
if !result.contains(&item) {
result.push(item);
}
}
result.into()
}
pub(crate) fn fn_is_intersect(v: Vec<Variable>) -> Variable {
match (&v[0], &v[1]) {
(Variable::Array(a), Variable::Array(b)) => a.iter().any(|x| b.contains(x)),
(Variable::Array(a), item) | (item, Variable::Array(a)) => a.contains(item),
_ => false,
}
.into()
}
pub(crate) fn fn_winnow(mut v: Vec<Variable>) -> Variable {
match v.remove(0) {
Variable::Array(a) => a
.into_iter()
.filter(|i| !i.is_empty())
.collect::<Vec<_>>()
.into(),
v => v,
}
}
+385
View File
@@ -0,0 +1,385 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::*;
use crate::{Server, expr::StringCow};
use compact_str::{CompactString, ToCompactString};
use mail_auth::IpLookupStrategy;
use std::{cmp::Ordering, net::IpAddr, vec::IntoIter};
use store::{Deserialize, Rows, Value, dispatch::lookup::KeyValue};
use trc::AddContext;
impl Server {
pub(crate) async fn eval_fnc<'x>(
&self,
fnc_id: u32,
params: Vec<Variable<'x>>,
session_id: u64,
) -> trc::Result<Variable<'x>> {
let mut params = FncParams::new(params);
match fnc_id {
F_IS_LOCAL_DOMAIN => {
let domain = params.next_as_string();
self.domain(domain.as_str())
.await
.caused_by(trc::location!())
.map(|v| v.is_some().into())
}
F_IS_LOCAL_ADDRESS => {
let address = params.next_as_string();
self.rcpt_id_from_email(address.as_ref())
.await
.caused_by(trc::location!())
.map(|v| v.is_some().into())
}
F_KEY_GET => {
let Some(store) = self.get_lookup_store(params.next_as_string().as_str()) else {
return Ok(Variable::default());
};
let key = params.next_as_string();
store
.key_get::<VariableWrapper>(key.as_str())
.await
.map(|value| value.map(|v| v.into_inner()).unwrap_or_default())
.caused_by(trc::location!())
}
F_KEY_EXISTS => {
let Some(store) = self.get_lookup_store(params.next_as_string().as_str()) else {
return Ok(Variable::default());
};
let key = params.next_as_string();
store
.key_exists(key.as_str())
.await
.caused_by(trc::location!())
.map(|v| v.into())
}
F_KEY_SET => {
let Some(store) = self.get_lookup_store(params.next_as_string().as_str()) else {
return Ok(Variable::default());
};
let key = params.next_as_string();
let value = params.next_as_string();
store
.key_set(KeyValue::new(
key.as_bytes().to_vec(),
value.as_bytes().to_vec(),
))
.await
.map(|_| true)
.caused_by(trc::location!())
.map(|v| v.into())
}
F_COUNTER_INCR => {
let Some(store) = self.get_lookup_store(params.next_as_string().as_str()) else {
return Ok(Variable::default());
};
let key = params.next_as_string();
let value = params.next_as_integer();
store
.counter_incr(KeyValue::new(key.into_owned(), value), true)
.await
.map(Variable::Integer)
.caused_by(trc::location!())
}
F_COUNTER_GET => {
let Some(store) = self.get_lookup_store(params.next_as_string().as_str()) else {
return Ok(Variable::default());
};
let key = params.next_as_string();
store
.counter_get(key.as_bytes().to_vec())
.await
.map(Variable::Integer)
.caused_by(trc::location!())
}
F_DNS_QUERY => self.dns_query(params).await,
F_SQL_QUERY => self.sql_query(params, session_id).await,
_ => Ok(Variable::default()),
}
}
async fn sql_query<'x>(
&self,
mut arguments: FncParams<'x>,
session_id: u64,
) -> trc::Result<Variable<'x>> {
let store_name = arguments.next_as_string();
let Some(store) = self
.get_lookup_store(store_name.as_ref())
.and_then(|v| v.into_store())
else {
return Err(trc::EventType::Eval(trc::EvalEvent::Error)
.into_err()
.id(store_name.into_owned())
.span_id(session_id)
.details("Store not found or is not a SQL store"));
};
let query = arguments.next_as_string();
if query.is_empty() {
return Err(trc::EventType::Eval(trc::EvalEvent::Error)
.into_err()
.details("Empty query string")
.span_id(session_id));
}
// Obtain arguments
let arguments = match arguments.next() {
Variable::Array(l) => l.into_iter().map(to_store_value).collect(),
v => vec![to_store_value(v)],
};
// Run query
if query
.as_bytes()
.get(..6)
.is_some_and(|q| q.eq_ignore_ascii_case(b"SELECT"))
{
let mut rows = store
.sql_query::<Rows>(query.as_str(), arguments)
.await
.caused_by(trc::location!())?;
Ok(match rows.rows.len().cmp(&1) {
Ordering::Equal => {
let mut row = rows.rows.pop().unwrap().values;
match row.len().cmp(&1) {
Ordering::Equal if !matches!(row.first(), Some(Value::Null)) => {
row.pop().map(into_variable).unwrap()
}
Ordering::Less => Variable::default(),
_ => {
Variable::Array(row.into_iter().map(into_variable).collect::<Vec<_>>())
}
}
}
Ordering::Less => Variable::default(),
Ordering::Greater => rows
.rows
.into_iter()
.map(|r| {
Variable::Array(r.values.into_iter().map(into_variable).collect::<Vec<_>>())
})
.collect::<Vec<_>>()
.into(),
})
} else {
store
.sql_query::<usize>(query.as_str(), arguments)
.await
.caused_by(trc::location!())
.map(|v| v.into())
}
}
async fn dns_query<'x>(&self, mut arguments: FncParams<'x>) -> trc::Result<Variable<'x>> {
let entry = arguments.next_as_string();
let record_type = arguments.next_as_string();
if record_type.as_str().eq_ignore_ascii_case("ip") {
self.core
.smtp
.resolvers
.dns
.ip_lookup(
entry.as_ref(),
IpLookupStrategy::Ipv4thenIpv6,
10,
Some(&self.inner.cache.dns_ipv4),
Some(&self.inner.cache.dns_ipv6),
)
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| {
result
.iter()
.map(|ip| Variable::from(ip.to_compact_string()))
.collect::<Vec<_>>()
.into()
})
} else if record_type.as_str().eq_ignore_ascii_case("mx") {
self.core
.smtp
.resolvers
.dns
.mx_lookup(entry.as_str(), Some(&self.inner.cache.dns_mx))
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| {
result
.rrset
.iter()
.flat_map(|mx| {
mx.exchanges.iter().map(|host| {
Variable::String(StringCow::Owned(
host.strip_suffix('.').unwrap_or(host).to_compact_string(),
))
})
})
.collect::<Vec<_>>()
.into()
})
} else if record_type.as_str().eq_ignore_ascii_case("txt") {
self.core
.smtp
.resolvers
.dns
.txt_raw_lookup(entry.as_str())
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| Variable::from(CompactString::from_utf8(result).unwrap_or_default()))
} else if record_type.as_str().eq_ignore_ascii_case("ptr") {
self.core
.smtp
.resolvers
.dns
.ptr_lookup(
entry.as_str().parse::<IpAddr>().map_err(|err| {
trc::EventType::Eval(trc::EvalEvent::Error)
.into_err()
.details("Failed to parse IP address")
.reason(err)
})?,
Some(&self.inner.cache.dns_ptr),
)
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| {
result
.rrset
.iter()
.map(|host| Variable::from(host.to_compact_string()))
.collect::<Vec<_>>()
.into()
})
} else if record_type.as_str().eq_ignore_ascii_case("ipv4") {
self.core
.smtp
.resolvers
.dns
.ipv4_lookup(entry.as_str(), Some(&self.inner.cache.dns_ipv4))
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| {
result
.rrset
.iter()
.map(|ip| Variable::from(ip.to_compact_string()))
.collect::<Vec<_>>()
.into()
})
} else if record_type.as_str().eq_ignore_ascii_case("ipv6") {
self.core
.smtp
.resolvers
.dns
.ipv6_lookup(entry.as_str(), Some(&self.inner.cache.dns_ipv6))
.await
.map_err(|err| trc::Error::from(err).caused_by(trc::location!()))
.map(|result| {
result
.rrset
.iter()
.map(|ip| Variable::from(ip.to_compact_string()))
.collect::<Vec<_>>()
.into()
})
} else {
Ok(Variable::default())
}
}
}
struct FncParams<'x> {
params: IntoIter<Variable<'x>>,
}
impl<'x> FncParams<'x> {
pub fn new(params: Vec<Variable<'x>>) -> Self {
Self {
params: params.into_iter(),
}
}
pub fn next_as_string(&mut self) -> StringCow<'x> {
self.params.next().unwrap().into_string()
}
pub fn next_as_integer(&mut self) -> i64 {
self.params.next().unwrap().to_integer().unwrap_or_default()
}
pub fn next(&mut self) -> Variable<'x> {
self.params.next().unwrap()
}
}
#[derive(Debug)]
struct VariableWrapper(Variable<'static>);
impl From<i64> for VariableWrapper {
fn from(value: i64) -> Self {
VariableWrapper(Variable::Integer(value))
}
}
impl Deserialize for VariableWrapper {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(VariableWrapper(Variable::String(StringCow::Owned(
CompactString::from_utf8_lossy(bytes),
))))
}
}
impl From<store::Value<'static>> for VariableWrapper {
fn from(value: store::Value<'static>) -> Self {
VariableWrapper(match value {
Value::Integer(v) => Variable::Integer(v),
Value::Bool(v) => Variable::Integer(v as i64),
Value::Float(v) => Variable::Float(v),
Value::Text(v) => Variable::String(StringCow::Owned(v.into())),
Value::Blob(v) => Variable::String(StringCow::Owned(match v {
std::borrow::Cow::Borrowed(v) => CompactString::from_utf8_lossy(v),
std::borrow::Cow::Owned(v) => CompactString::from_utf8_lossy(&v),
})),
Value::Null => Variable::String(StringCow::Borrowed("")),
})
}
}
impl VariableWrapper {
pub fn into_inner(self) -> Variable<'static> {
self.0
}
}
fn to_store_value(value: Variable) -> Value {
match value {
Variable::String(v) => Value::Text(v.to_string().into()),
Variable::Integer(v) => Value::Integer(v),
Variable::Float(v) => Value::Float(v),
v => Value::Text(v.to_string().into_owned().into()),
}
}
fn into_variable(value: Value) -> Variable {
match value {
Value::Integer(v) => Variable::Integer(v),
Value::Bool(v) => Variable::Integer(i64::from(v)),
Value::Float(v) => Variable::Float(v),
Value::Text(v) => Variable::String(v.into()),
Value::Blob(v) => Variable::String(StringCow::Owned(CompactString::from_utf8_lossy(&v))),
Value::Null => Variable::default(),
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use compact_str::CompactString;
use crate::expr::{StringCow, Variable};
pub(crate) fn fn_is_email(v: Vec<Variable>) -> Variable {
let mut last_ch = 0;
let mut in_quote = false;
let mut at_count = 0;
let mut dot_count = 0;
let mut lp_len = 0;
let mut value = 0;
for &ch in v[0].to_string().as_bytes() {
match ch {
b'0'..=b'9'
| b'a'..=b'z'
| b'A'..=b'Z'
| b'!'
| b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'/'
| b'='
| b'?'
| b'^'
| b'_'
| b'`'
| b'{'
| b'|'
| b'}'
| b'~'
| 0x7f..=u8::MAX => {
value += 1;
}
b'.' if !in_quote => {
if last_ch != b'.' && last_ch != b'@' && value != 0 {
value += 1;
if at_count == 1 {
dot_count += 1;
}
} else {
return false.into();
}
}
b'@' if !in_quote => {
at_count += 1;
lp_len = value;
value = 0;
}
b'>' | b':' | b',' | b' ' if in_quote => {
value += 1;
}
b'\"' if !in_quote || last_ch != b'\\' => {
in_quote = !in_quote;
}
b'\\' if in_quote && last_ch != b'\\' => (),
_ => {
if !in_quote {
return false.into();
}
}
}
last_ch = ch;
}
(at_count == 1 && dot_count > 0 && lp_len > 0 && value > 0).into()
}
pub(crate) fn fn_email_part(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap();
let part = v.next().unwrap().into_string();
value.transform(|s| match s {
StringCow::Borrowed(s) => s
.rsplit_once('@')
.map(|(u, d)| match part.as_str() {
"local" => Variable::from(u.trim()),
"domain" => Variable::from(d.trim()),
_ => Variable::default(),
})
.unwrap_or_default(),
StringCow::Owned(s) => s
.rsplit_once('@')
.map(|(u, d)| match part.as_str() {
"local" => Variable::from(CompactString::new(u.trim())),
"domain" => Variable::from(CompactString::new(d.trim())),
_ => Variable::default(),
})
.unwrap_or_default(),
})
}
+78
View File
@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::expr::Variable;
use compact_str::CompactString;
use mail_auth::common::resolver::ToReverseName;
use registry::types::ipmask::IpAddrOrMask;
use std::{net::IpAddr, str::FromStr};
pub(crate) fn fn_is_empty(v: Vec<Variable>) -> Variable {
match &v[0] {
Variable::String(s) => s.is_empty(),
Variable::Integer(_) | Variable::Float(_) | Variable::Constant(_) => false,
Variable::Array(a) => a.is_empty(),
}
.into()
}
pub(crate) fn fn_is_number(v: Vec<Variable>) -> Variable {
matches!(&v[0], Variable::Integer(_) | Variable::Float(_)).into()
}
pub(crate) fn fn_is_ip_addr(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.parse::<std::net::IpAddr>()
.is_ok()
.into()
}
pub(crate) fn fn_is_ipv4_addr(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.parse::<std::net::IpAddr>()
.is_ok_and(|ip| matches!(ip, IpAddr::V4(_)))
.into()
}
pub(crate) fn fn_is_ipv6_addr(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.parse::<std::net::IpAddr>()
.is_ok_and(|ip| matches!(ip, IpAddr::V6(_)))
.into()
}
pub(crate) fn fn_is_ip_in_cidr(v: Vec<Variable>) -> Variable {
let Ok(ip) = v[0].to_string().as_str().parse::<IpAddr>() else {
return false.into();
};
IpAddrOrMask::from_str(v[1].to_string().as_str())
.map(|mask| mask.matches(&ip))
.unwrap_or(false)
.into()
}
pub(crate) fn fn_ip_reverse_name(v: Vec<Variable>) -> Variable {
CompactString::new(
v[0].to_string()
.as_str()
.parse::<std::net::IpAddr>()
.map(|ip| ip.to_reverse_name())
.unwrap_or_default(),
)
.into()
}
pub(crate) fn fn_if_then(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let condition = v.next().unwrap();
let iff = v.next().unwrap();
let then = v.next().unwrap();
if condition.to_bool() { iff } else { then }
}
+118
View File
@@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{StringCow, Variable};
use registry::schema::enums::ExpressionVariable;
pub mod array;
pub mod asynch;
pub mod email;
pub mod misc;
pub mod text;
pub trait ResolveVariable: Sync + Send {
fn resolve_variable(&self, variable: ExpressionVariable) -> Variable<'_>;
fn resolve_global(&self, variable: &str) -> Variable<'_>;
}
impl<'x> Variable<'x> {
fn transform(self, f: impl Fn(StringCow<'x>) -> Variable<'x>) -> Variable<'x> {
match self {
Variable::String(s) => f(s),
Variable::Array(list) => Variable::Array(
list.into_iter()
.map(|v| match v {
Variable::String(s) => f(s),
v => f(v.into_string()),
})
.collect::<Vec<_>>(),
),
v => f(v.into_string()),
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) const FUNCTIONS: &[(&str, fn(Vec<Variable>) -> Variable, u32)] = &[
("count", array::fn_count, 1),
("sort", array::fn_sort, 2),
("dedup", array::fn_dedup, 1),
("winnow", array::fn_winnow, 1),
("is_intersect", array::fn_is_intersect, 2),
("is_email", email::fn_is_email, 1),
("email_part", email::fn_email_part, 2),
("is_empty", misc::fn_is_empty, 1),
("is_number", misc::fn_is_number, 1),
("is_ip_addr", misc::fn_is_ip_addr, 1),
("is_ipv4_addr", misc::fn_is_ipv4_addr, 1),
("is_ipv6_addr", misc::fn_is_ipv6_addr, 1),
("is_ip_in_cidr", misc::fn_is_ip_in_cidr, 2),
("ip_reverse_name", misc::fn_ip_reverse_name, 1),
("trim", text::fn_trim, 1),
("trim_end", text::fn_trim_end, 1),
("trim_start", text::fn_trim_start, 1),
("len", text::fn_len, 1),
("to_lowercase", text::fn_to_lowercase, 1),
("to_uppercase", text::fn_to_uppercase, 1),
("is_uppercase", text::fn_is_uppercase, 1),
("is_lowercase", text::fn_is_lowercase, 1),
("has_digits", text::fn_has_digits, 1),
("count_spaces", text::fn_count_spaces, 1),
("count_uppercase", text::fn_count_uppercase, 1),
("count_lowercase", text::fn_count_lowercase, 1),
("count_chars", text::fn_count_chars, 1),
("contains", text::fn_contains, 2),
("contains_ignore_case", text::fn_contains_ignore_case, 2),
("eq_ignore_case", text::fn_eq_ignore_case, 2),
("starts_with", text::fn_starts_with, 2),
("ends_with", text::fn_ends_with, 2),
("lines", text::fn_lines, 1),
("substring", text::fn_substring, 3),
("strip_prefix", text::fn_strip_prefix, 2),
("strip_suffix", text::fn_strip_suffix, 2),
("split", text::fn_split, 2),
("rsplit", text::fn_rsplit, 2),
("split_once", text::fn_split_once, 2),
("rsplit_once", text::fn_rsplit_once, 2),
("split_n", text::fn_split_n, 3),
("split_words", text::fn_split_words, 1),
("hash", text::fn_hash, 2),
("if_then", misc::fn_if_then, 3),
];
pub const F_IS_LOCAL_DOMAIN: u32 = 0;
pub const F_IS_LOCAL_ADDRESS: u32 = 1;
pub const F_KEY_GET: u32 = 2;
pub const F_KEY_EXISTS: u32 = 3;
pub const F_KEY_SET: u32 = 4;
pub const F_COUNTER_INCR: u32 = 5;
pub const F_COUNTER_GET: u32 = 6;
pub const F_SQL_QUERY: u32 = 7;
pub const F_DNS_QUERY: u32 = 8;
pub const ASYNC_FUNCTIONS: &[(&str, u32, u32)] = &[
("is_local_domain", F_IS_LOCAL_DOMAIN, 1),
("is_local_address", F_IS_LOCAL_ADDRESS, 1),
("key_get", F_KEY_GET, 2),
("key_exists", F_KEY_EXISTS, 2),
("key_set", F_KEY_SET, 3),
("counter_incr", F_COUNTER_INCR, 3),
("counter_get", F_COUNTER_GET, 2),
("dns_query", F_DNS_QUERY, 2),
("sql_query", F_SQL_QUERY, 3),
];
pub struct EmptyResolver;
impl ResolveVariable for EmptyResolver {
fn resolve_variable(&self, _: ExpressionVariable) -> Variable<'_> {
Variable::Integer(0)
}
fn resolve_global(&self, _: &str) -> Variable<'_> {
Variable::Integer(0)
}
}
+359
View File
@@ -0,0 +1,359 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use compact_str::{CompactString, ToCompactString, format_compact};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use utils::HexEncode;
use crate::expr::{StringCow, Variable};
pub(crate) fn fn_trim(mut v: Vec<Variable>) -> Variable {
v.remove(0).transform(|s| match s {
StringCow::Borrowed(s) => Variable::from(s.trim()),
StringCow::Owned(s) => Variable::from(s.trim().to_compact_string()),
})
}
pub(crate) fn fn_trim_end(mut v: Vec<Variable>) -> Variable {
v.remove(0).transform(|s| match s {
StringCow::Borrowed(s) => Variable::from(s.trim_end()),
StringCow::Owned(s) => Variable::from(s.trim_end().to_compact_string()),
})
}
pub(crate) fn fn_trim_start(mut v: Vec<Variable>) -> Variable {
v.remove(0).transform(|s| match s {
StringCow::Borrowed(s) => Variable::from(s.trim_start()),
StringCow::Owned(s) => Variable::from(s.trim_start().to_compact_string()),
})
}
pub(crate) fn fn_len(v: Vec<Variable>) -> Variable {
match &v[0] {
Variable::String(s) => s.len(),
Variable::Array(a) => a.len(),
v => v.to_string().len(),
}
.into()
}
pub(crate) fn fn_to_lowercase(mut v: Vec<Variable>) -> Variable {
v.remove(0)
.transform(|s| Variable::from(CompactString::from_str_to_lowercase(s.as_str())))
}
pub(crate) fn fn_to_uppercase(mut v: Vec<Variable>) -> Variable {
v.remove(0)
.transform(|s| Variable::from(CompactString::from_str_to_uppercase(s.as_str())))
}
pub(crate) fn fn_is_uppercase(mut v: Vec<Variable>) -> Variable {
v.remove(0).transform(|s| {
s.as_str()
.chars()
.filter(|c| c.is_alphabetic())
.all(|c| c.is_uppercase())
.into()
})
}
pub(crate) fn fn_is_lowercase(mut v: Vec<Variable>) -> Variable {
v.remove(0).transform(|s| {
s.as_str()
.chars()
.filter(|c| c.is_alphabetic())
.all(|c| c.is_lowercase())
.into()
})
}
pub(crate) fn fn_has_digits(mut v: Vec<Variable>) -> Variable {
v.remove(0)
.transform(|s| s.as_str().chars().any(|c| c.is_ascii_digit()).into())
}
pub(crate) fn fn_split_words(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.split_whitespace()
.filter(|word| word.chars().all(|c| c.is_alphanumeric()))
.map(|word| Variable::from(CompactString::new(word)))
.collect::<Vec<_>>()
.into()
}
pub(crate) fn fn_count_spaces(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.chars()
.filter(|c| c.is_whitespace())
.count()
.into()
}
pub(crate) fn fn_count_uppercase(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.chars()
.filter(|c| c.is_alphabetic() && c.is_uppercase())
.count()
.into()
}
pub(crate) fn fn_count_lowercase(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.chars()
.filter(|c| c.is_alphabetic() && c.is_lowercase())
.count()
.into()
}
pub(crate) fn fn_count_chars(v: Vec<Variable>) -> Variable {
v[0].to_string().as_str().chars().count().into()
}
pub(crate) fn fn_eq_ignore_case(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.eq_ignore_ascii_case(v[1].to_string().as_str())
.into()
}
pub(crate) fn fn_contains(v: Vec<Variable>) -> Variable {
match &v[0] {
Variable::String(s) => s.as_str().contains(v[1].to_string().as_str()),
Variable::Array(arr) => arr.contains(&v[1]),
val => val.to_string().as_str().contains(v[1].to_string().as_str()),
}
.into()
}
pub(crate) fn fn_contains_ignore_case(v: Vec<Variable>) -> Variable {
let needle = v[1].to_string();
match &v[0] {
Variable::String(s) => s
.as_str()
.to_lowercase()
.contains(&needle.as_str().to_lowercase()),
Variable::Array(arr) => arr.iter().any(|v| match v {
Variable::String(s) => s.as_str().eq_ignore_ascii_case(needle.as_str()),
_ => false,
}),
val => val.to_string().as_str().contains(needle.as_str()),
}
.into()
}
pub(crate) fn fn_starts_with(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.starts_with(v[1].to_string().as_str())
.into()
}
pub(crate) fn fn_ends_with(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.ends_with(v[1].to_string().as_str())
.into()
}
pub(crate) fn fn_lines(mut v: Vec<Variable>) -> Variable {
match v.remove(0) {
Variable::String(s) => s
.as_str()
.lines()
.map(|s| Variable::from(CompactString::new(s)))
.collect::<Vec<_>>()
.into(),
val => val,
}
}
pub(crate) fn fn_substring(v: Vec<Variable>) -> Variable {
v[0].to_string()
.as_str()
.chars()
.skip(v[1].to_usize().unwrap_or_default())
.take(v[2].to_usize().unwrap_or_default())
.collect::<CompactString>()
.into()
}
pub(crate) fn fn_strip_prefix(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap();
let prefix = v.next().unwrap().into_string();
value.transform(|s| match s {
StringCow::Borrowed(s) => s
.strip_prefix(prefix.as_str())
.map(Variable::from)
.unwrap_or_default(),
StringCow::Owned(s) => s
.strip_prefix(prefix.as_str())
.map(|s| Variable::from(CompactString::new(s)))
.unwrap_or_default(),
})
}
pub(crate) fn fn_strip_suffix(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap();
let suffix = v.next().unwrap().into_string();
value.transform(|s| match s {
StringCow::Borrowed(s) => s
.strip_suffix(suffix.as_str())
.map(Variable::from)
.unwrap_or_default(),
StringCow::Owned(s) => s
.strip_suffix(suffix.as_str())
.map(|s| Variable::from(CompactString::new(s)))
.unwrap_or_default(),
})
}
pub(crate) fn fn_split(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let arg = v.next().unwrap().into_string();
match value {
StringCow::Borrowed(s) => s
.split(arg.as_str())
.map(Variable::from)
.collect::<Vec<_>>()
.into(),
StringCow::Owned(s) => s
.split(arg.as_str())
.map(|s| Variable::from(CompactString::new(s)))
.collect::<Vec<_>>()
.into(),
}
}
pub(crate) fn fn_rsplit(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let arg = v.next().unwrap().into_string();
match value {
StringCow::Borrowed(s) => s
.rsplit(arg.as_str())
.map(Variable::from)
.collect::<Vec<_>>()
.into(),
StringCow::Owned(s) => s
.rsplit(arg.as_str())
.map(|s| Variable::from(CompactString::new(s)))
.collect::<Vec<_>>()
.into(),
}
}
pub(crate) fn fn_split_n(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let arg = v.next().unwrap().into_string();
let num = v.next().unwrap().to_integer().unwrap_or_default() as usize;
fn split_n<'x, 'y>(s: &'x str, arg: &'y str, num: usize, mut f: impl FnMut(&'x str)) {
let mut s = s;
for _ in 0..num {
if let Some((a, b)) = s.split_once(arg) {
f(a);
s = b;
} else {
break;
}
}
f(s);
}
let mut result = Vec::new();
match value {
StringCow::Borrowed(s) => split_n(s, arg.as_str(), num, |s| result.push(Variable::from(s))),
StringCow::Owned(s) => split_n(&s, arg.as_str(), num, |s| {
result.push(Variable::from(CompactString::new(s)))
}),
}
result.into()
}
pub(crate) fn fn_split_once(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let arg = v.next().unwrap().into_string();
match value {
StringCow::Borrowed(s) => s
.split_once(arg.as_str())
.map(|(a, b)| Variable::Array(vec![Variable::from(a), Variable::from(b)]))
.unwrap_or_default(),
StringCow::Owned(s) => s
.split_once(arg.as_str())
.map(|(a, b)| {
Variable::Array(vec![
Variable::from(CompactString::new(a)),
Variable::from(CompactString::new(b)),
])
})
.unwrap_or_default(),
}
}
pub(crate) fn fn_rsplit_once(v: Vec<Variable>) -> Variable {
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let arg = v.next().unwrap().into_string();
match value {
StringCow::Borrowed(s) => s
.rsplit_once(arg.as_str())
.map(|(a, b)| Variable::Array(vec![Variable::from(a), Variable::from(b)]))
.unwrap_or_default(),
StringCow::Owned(s) => s
.rsplit_once(arg.as_str())
.map(|(a, b)| {
Variable::Array(vec![
Variable::from(CompactString::new(a)),
Variable::from(CompactString::new(b)),
])
})
.unwrap_or_default(),
}
}
pub(crate) fn fn_hash(v: Vec<Variable>) -> Variable {
use sha1::Digest;
let mut v = v.into_iter();
let value = v.next().unwrap().into_string();
let algo = v.next().unwrap().into_string();
match algo.as_str() {
"md5" => format_compact!("{:x}", md5::compute(value.as_bytes())).into(),
"sha1" => {
let mut hasher = Sha1::new();
hasher.update(value.as_bytes());
hasher.finalize().hex_encode().to_compact_string().into()
}
"sha256" => {
let mut hasher = Sha256::new();
hasher.update(value.as_bytes());
hasher.finalize().hex_encode().to_compact_string().into()
}
"sha512" => {
let mut hasher = Sha512::new();
hasher.update(value.as_bytes());
hasher.finalize().hex_encode().to_compact_string().into()
}
_ => Variable::default(),
}
}
+250
View File
@@ -0,0 +1,250 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{
ExpressionItem,
parser::ExpressionParser,
tokenizer::{TokenMap, Tokenizer},
};
use crate::expr::{Constant, Expression};
use compact_str::CompactString;
use registry::{
schema::{
prelude::{ExpressionContext, Property},
structs,
},
types::id::ObjectId,
};
use store::registry::bootstrap::Bootstrap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfThen {
pub expr: Expression,
pub then: Expression,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfBlock {
pub id: ObjectId,
pub property: Property,
pub if_then: Box<[IfThen]>,
pub default: Expression,
}
impl IfBlock {
pub fn new_default(id: ObjectId, expr_ctx: ExpressionContext<'_>) -> Self {
let token_map = TokenMap::default();
if let Some(default) = expr_ctx.default {
Self {
id,
property: expr_ctx.property,
if_then: default
.match_
.into_iter()
.map(|match_| IfThen {
expr: Expression::parse(&token_map, &match_.if_),
then: Expression::parse(&token_map, &match_.then),
})
.collect(),
default: Expression::parse(&token_map, &default.else_),
}
} else {
Self::empty(id, expr_ctx.property)
}
}
pub fn empty(id: ObjectId, property: Property) -> Self {
Self {
id,
property,
if_then: Default::default(),
default: Expression {
items: Default::default(),
},
}
}
pub fn is_empty(&self) -> bool {
self.default.is_empty() && self.if_then.is_empty()
}
}
impl Expression {
pub fn parse(token_map: &TokenMap, expr: &str) -> Self {
ExpressionParser::new(Tokenizer::new(expr, token_map))
.parse()
.unwrap()
}
}
pub trait BootstrapExprExt {
fn compile_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock;
fn compile_default_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock;
fn try_compile_expr(
&mut self,
id: ObjectId,
expr_ctx: &ExpressionContext<'_>,
expr: &structs::Expression,
) -> Option<IfBlock>;
}
impl BootstrapExprExt for Bootstrap {
fn compile_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock {
if expr_ctx.expr.else_.is_empty() && expr_ctx.expr.match_.is_empty() {
return IfBlock::empty(id, expr_ctx.property);
}
if let Some(if_block) = self.try_compile_expr(id, expr_ctx, expr_ctx.expr) {
if_block
} else {
self.compile_default_expr(id, expr_ctx)
}
}
fn compile_default_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock {
if let Some(default) = &expr_ctx.default {
self.try_compile_expr(id, expr_ctx, default)
.expect("Valid default expression")
} else {
IfBlock::empty(id, expr_ctx.property)
}
}
fn try_compile_expr(
&mut self,
id: ObjectId,
expr_ctx: &ExpressionContext<'_>,
expr: &structs::Expression,
) -> Option<IfBlock> {
// Parse conditions
let mut if_then = Vec::with_capacity(expr.match_.len());
if expr.else_.is_empty() {
if !expr.match_.is_empty() {
self.invalid_property(
id,
expr_ctx.property,
"Missing 'else' block in 'if' expression",
);
}
return None;
}
if expr
.match_
.iter()
.any(|m| m.if_.is_empty() || m.then.is_empty())
{
self.invalid_property(
id,
expr_ctx.property,
"All 'if' and 'then' blocks must be non-empty",
);
return None;
}
let token_map = TokenMap::default()
.with_variables(expr_ctx.allowed_variables)
.with_constants(expr_ctx.allowed_constants);
let default = match ExpressionParser::new(Tokenizer::new(&expr.else_, &token_map)).parse() {
Ok(expr) => expr,
Err(err) => {
self.invalid_property(
id,
expr_ctx.property,
format!("Error parsing 'else' expression: {}", err),
);
return None;
}
};
for (num, match_) in expr.match_.iter().enumerate() {
match ExpressionParser::new(Tokenizer::new(&match_.if_, &token_map)).parse() {
Ok(if_expr) => {
match ExpressionParser::new(Tokenizer::new(&match_.then, &token_map)).parse() {
Ok(then_expr) => {
if_then.push(IfThen {
expr: if_expr,
then: then_expr,
});
}
Err(err) => {
self.invalid_property(
id,
expr_ctx.property,
format!(
"Error parsing 'then' expression in condition #{}: {}",
num + 1,
err
),
);
return None;
}
}
}
Err(err) => {
self.invalid_property(
id,
expr_ctx.property,
format!(
"Error parsing 'if' expression in condition #{}: {}",
num + 1,
err
),
);
return None;
}
}
}
Some(IfBlock {
id,
property: expr_ctx.property,
if_then: if_then.into_boxed_slice(),
default,
})
}
}
impl IfBlock {
pub fn into_default(self, id: ObjectId, property: Property) -> IfBlock {
IfBlock {
id,
property,
if_then: Default::default(),
default: self.default,
}
}
pub fn all_items(&self) -> impl Iterator<Item = &ExpressionItem> {
self.if_then
.iter()
.flat_map(|if_then| if_then.expr.items().iter().chain(if_then.then.items()))
.chain(self.default.items())
}
pub fn default_string(&self) -> Option<&str> {
for expr_item in &self.default.items {
if let ExpressionItem::Constant(Constant::String(value)) = expr_item {
return Some(value.as_str());
}
}
None
}
pub fn into_default_string(self) -> Option<CompactString> {
for expr_item in self.default.items {
if let ExpressionItem::Constant(Constant::String(value)) = expr_item {
return Some(value);
}
}
None
}
}
+523
View File
@@ -0,0 +1,523 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use compact_str::CompactString;
use regex::Regex;
use registry::schema::{
enums::{ExpressionConstant, ExpressionVariable},
structs::Rate,
};
use std::{
borrow::Cow,
fmt::{Display, Formatter},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
time::Duration,
};
use trc::MetricType;
use utils::cache::CacheItemWeight;
use crate::expr::if_block::IfBlock;
pub mod eval;
pub mod functions;
pub mod if_block;
pub mod parser;
pub mod tokenizer;
#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[repr(transparent)]
pub struct Expression {
pub items: Box<[ExpressionItem]>,
}
#[derive(Debug, Clone)]
pub enum ExpressionItem {
Variable(ExpressionVariable),
Global(CompactString),
System(SystemVariable),
Capture(u32),
Constant(Constant),
BinaryOperator(BinaryOperator),
UnaryOperator(UnaryOperator),
Regex(Regex),
JmpIf { val: bool, pos: u32 },
Function { id: u32, num_args: u32 },
ArrayAccess,
ArrayBuild(u32),
}
#[derive(Debug, Clone)]
pub enum Variable<'x> {
String(StringCow<'x>),
Integer(i64),
Float(f64),
Array(Vec<Variable<'x>>),
Constant(ExpressionConstant),
}
#[derive(Debug, Clone)]
pub enum StringCow<'x> {
Owned(CompactString),
Borrowed(&'x str),
}
impl Default for Variable<'_> {
fn default() -> Self {
Variable::String(StringCow::Borrowed(""))
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Constant {
Static(ExpressionConstant),
Integer(i64),
Float(f64),
String(CompactString),
}
impl Eq for Constant {}
impl From<CompactString> for Constant {
fn from(value: CompactString) -> Self {
Constant::String(value)
}
}
impl From<bool> for Constant {
fn from(value: bool) -> Self {
Constant::Integer(value as i64)
}
}
impl From<i64> for Constant {
fn from(value: i64) -> Self {
Constant::Integer(value)
}
}
impl From<i32> for Constant {
fn from(value: i32) -> Self {
Constant::Integer(value as i64)
}
}
impl From<i16> for Constant {
fn from(value: i16) -> Self {
Constant::Integer(value as i64)
}
}
impl From<f64> for Constant {
fn from(value: f64) -> Self {
Constant::Float(value)
}
}
impl From<usize> for Constant {
fn from(value: usize) -> Self {
Constant::Integer(value as i64)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BinaryOperator {
Add,
Subtract,
Multiply,
Divide,
And,
Or,
Xor,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum UnaryOperator {
Not,
Minus,
}
#[derive(Debug, Clone)]
pub enum Token {
Variable(ExpressionVariable),
Global(CompactString),
Capture(u32),
Function {
name: Cow<'static, str>,
id: u32,
num_args: u32,
},
Constant(Constant),
System(SystemVariable),
Regex(Regex),
BinaryOperator(BinaryOperator),
UnaryOperator(UnaryOperator),
OpenParen,
CloseParen,
OpenBracket,
CloseBracket,
Comma,
}
#[derive(Debug, Clone)]
pub enum SystemVariable {
Hostname,
Domain,
NodeId,
NodeHostname,
NodeRole,
Metric(MetricType),
}
impl From<usize> for Variable<'_> {
fn from(value: usize) -> Self {
Variable::Integer(value as i64)
}
}
impl From<i64> for Variable<'_> {
fn from(value: i64) -> Self {
Variable::Integer(value)
}
}
impl From<u64> for Variable<'_> {
fn from(value: u64) -> Self {
Variable::Integer(value as i64)
}
}
impl From<i32> for Variable<'_> {
fn from(value: i32) -> Self {
Variable::Integer(value as i64)
}
}
impl From<u32> for Variable<'_> {
fn from(value: u32) -> Self {
Variable::Integer(value as i64)
}
}
impl From<u16> for Variable<'_> {
fn from(value: u16) -> Self {
Variable::Integer(value as i64)
}
}
impl From<i16> for Variable<'_> {
fn from(value: i16) -> Self {
Variable::Integer(value as i64)
}
}
impl From<f64> for Variable<'_> {
fn from(value: f64) -> Self {
Variable::Float(value)
}
}
impl<'x> From<&'x str> for Variable<'x> {
fn from(value: &'x str) -> Self {
Variable::String(StringCow::Borrowed(value))
}
}
impl From<CompactString> for Variable<'_> {
fn from(value: CompactString) -> Self {
Variable::String(StringCow::Owned(value))
}
}
impl<'x> From<Vec<Variable<'x>>> for Variable<'x> {
fn from(value: Vec<Variable<'x>>) -> Self {
Variable::Array(value)
}
}
impl From<bool> for Variable<'_> {
fn from(value: bool) -> Self {
Variable::Integer(value as i64)
}
}
impl<T: Into<Constant>> From<T> for Expression {
fn from(value: T) -> Self {
Expression {
items: Box::new([ExpressionItem::Constant(value.into())]),
}
}
}
impl PartialEq for ExpressionItem {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Variable(l0), Self::Variable(r0)) => l0 == r0,
(Self::Constant(l0), Self::Constant(r0)) => l0 == r0,
(Self::BinaryOperator(l0), Self::BinaryOperator(r0)) => l0 == r0,
(Self::UnaryOperator(l0), Self::UnaryOperator(r0)) => l0 == r0,
(Self::Regex(_), Self::Regex(_)) => true,
(
Self::JmpIf {
val: l_val,
pos: l_pos,
},
Self::JmpIf {
val: r_val,
pos: r_pos,
},
) => l_val == r_val && l_pos == r_pos,
(
Self::Function {
id: l_id,
num_args: l_num_args,
},
Self::Function {
id: r_id,
num_args: r_num_args,
},
) => l_id == r_id && l_num_args == r_num_args,
(Self::ArrayBuild(l0), Self::ArrayBuild(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl Eq for ExpressionItem {}
impl PartialEq for Token {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Variable(l0), Self::Variable(r0)) => l0 == r0,
(
Self::Function {
name: l_name,
id: l_id,
num_args: l_num_args,
},
Self::Function {
name: r_name,
id: r_id,
num_args: r_num_args,
},
) => l_name == r_name && l_id == r_id && l_num_args == r_num_args,
(Self::Constant(l0), Self::Constant(r0)) => l0 == r0,
(Self::Regex(_), Self::Regex(_)) => true,
(Self::BinaryOperator(l0), Self::BinaryOperator(r0)) => l0 == r0,
(Self::UnaryOperator(l0), Self::UnaryOperator(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl Eq for Token {}
impl From<()> for Constant {
fn from(_: ()) -> Self {
Constant::Integer(0)
}
}
impl<'x> TryFrom<Variable<'x>> for () {
type Error = ();
fn try_from(_: Variable<'x>) -> Result<Self, Self::Error> {
Ok(())
}
}
impl<'x> TryFrom<Variable<'x>> for Duration {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::Integer(value) if value > 0 => Ok(Duration::from_millis(value as u64)),
Variable::Float(value) if value > 0.0 => Ok(Duration::from_millis(value as u64)),
Variable::String(value) if !value.is_empty() => {
registry::types::duration::Duration::from_str(value.as_str())
.map(|v| v.into_inner())
.map_err(|_| ())
}
_ => Err(()),
}
}
}
impl StringCow<'_> {
pub fn as_str(&self) -> &str {
match self {
StringCow::Owned(s) => s.as_str(),
StringCow::Borrowed(s) => s,
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
StringCow::Owned(s) => s.as_bytes(),
StringCow::Borrowed(s) => s.as_bytes(),
}
}
pub fn is_empty(&self) -> bool {
match self {
StringCow::Owned(s) => s.is_empty(),
StringCow::Borrowed(s) => s.is_empty(),
}
}
pub fn len(&self) -> usize {
match self {
StringCow::Owned(s) => s.len(),
StringCow::Borrowed(s) => s.len(),
}
}
pub fn into_owned(self) -> CompactString {
match self {
StringCow::Owned(s) => s,
StringCow::Borrowed(s) => s.into(),
}
}
}
impl<'x> From<Cow<'x, str>> for StringCow<'x> {
fn from(value: Cow<'x, str>) -> Self {
match value {
Cow::Borrowed(s) => StringCow::Borrowed(s),
Cow::Owned(s) => StringCow::Owned(s.into()),
}
}
}
impl From<CompactString> for StringCow<'_> {
fn from(value: CompactString) -> Self {
StringCow::Owned(value)
}
}
impl AsRef<str> for StringCow<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for StringCow<'_> {
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl Display for StringCow<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
StringCow::Owned(s) => write!(f, "{}", s),
StringCow::Borrowed(s) => write!(f, "{}", s),
}
}
}
impl From<Duration> for Constant {
fn from(value: Duration) -> Self {
Constant::Integer(value.as_millis() as i64)
}
}
impl<'x> TryFrom<Variable<'x>> for Rate {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::Array(items) if items.len() == 2 => {
let requests = items[0].to_integer().ok_or(())?;
let period = items[1].to_integer().ok_or(())?;
if requests > 0 && period > 0 {
Ok(Rate {
count: requests as u64,
period: registry::types::duration::Duration::from_millis(period as u64),
})
} else {
Err(())
}
}
_ => Err(()),
}
}
}
impl<'x> TryFrom<Variable<'x>> for Ipv4Addr {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::String(value) => value.as_str().parse().map_err(|_| ()),
_ => Err(()),
}
}
}
impl<'x> TryFrom<Variable<'x>> for Ipv6Addr {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::String(value) => value.as_str().parse().map_err(|_| ()),
_ => Err(()),
}
}
}
impl<'x> TryFrom<Variable<'x>> for IpAddr {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::String(value) => value.as_str().parse().map_err(|_| ()),
_ => Err(()),
}
}
}
impl<'x, T: TryFrom<Variable<'x>>> TryFrom<Variable<'x>> for Vec<T>
where
Result<Vec<T>, ()>: FromIterator<Result<T, <T as TryFrom<Variable<'x>>>::Error>>,
{
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
value
.into_array()
.into_iter()
.map(|v| T::try_from(v))
.collect()
}
}
impl CacheItemWeight for Expression {
fn weight(&self) -> u64 {
self.items.len() as u64 * std::mem::size_of::<ExpressionItem>() as u64
}
}
impl CacheItemWeight for IfBlock {
fn weight(&self) -> u64 {
std::mem::size_of::<IfBlock>() as u64
+ self
.if_then
.iter()
.map(|if_then| if_then.expr.weight() + if_then.then.weight())
.sum::<u64>()
+ self.default.weight()
}
}
+277
View File
@@ -0,0 +1,277 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{BinaryOperator, Expression, ExpressionItem, Token, tokenizer::Tokenizer};
pub struct ExpressionParser<'x> {
pub(crate) tokenizer: Tokenizer<'x>,
pub(crate) output: Vec<ExpressionItem>,
operator_stack: Vec<(Token, Option<usize>)>,
arg_count: Vec<i32>,
}
pub(crate) const ID_ARRAY_ACCESS: u32 = u32::MAX;
pub(crate) const ID_ARRAY_BUILD: u32 = u32::MAX - 1;
impl<'x> ExpressionParser<'x> {
pub fn new(tokenizer: Tokenizer<'x>) -> Self {
Self {
tokenizer,
output: Vec::new(),
operator_stack: Vec::new(),
arg_count: Vec::new(),
}
}
pub fn parse(mut self) -> Result<Expression, String> {
let mut last_is_var_or_fnc = false;
while let Some(token) = self.tokenizer.next()? {
let mut is_var_or_fnc = false;
match token {
Token::Variable(v) => {
self.inc_arg_count();
is_var_or_fnc = true;
self.output.push(ExpressionItem::Variable(v))
}
Token::Constant(c) => {
self.inc_arg_count();
self.output.push(ExpressionItem::Constant(c))
}
Token::Global(g) => {
self.inc_arg_count();
self.output.push(ExpressionItem::Global(g))
}
Token::Capture(c) => {
self.inc_arg_count();
self.output.push(ExpressionItem::Capture(c))
}
Token::UnaryOperator(uop) => {
self.operator_stack.push((Token::UnaryOperator(uop), None))
}
Token::OpenParen => self.operator_stack.push((token, None)),
Token::CloseParen | Token::CloseBracket => {
let expect_token = if matches!(token, Token::CloseParen) {
Token::OpenParen
} else {
Token::OpenBracket
};
loop {
match self.operator_stack.pop() {
Some((t, _)) if t == expect_token => {
break;
}
Some((Token::BinaryOperator(bop), jmp_pos)) => {
self.update_jmp_pos(jmp_pos);
self.output.push(ExpressionItem::BinaryOperator(bop))
}
Some((Token::UnaryOperator(uop), _)) => {
self.output.push(ExpressionItem::UnaryOperator(uop))
}
_ => return Err("Mismatched parentheses".to_string()),
}
}
match self.operator_stack.last() {
Some((Token::Function { id, num_args, name }, _)) => {
let got_args = self.arg_count.pop().unwrap();
if got_args != *num_args as i32 {
return Err(if *id != u32::MAX {
format!(
"Expression function {:?} expected {} arguments, got {}",
name, num_args, got_args
)
} else {
"Missing array index".to_string()
});
}
let expr = match *id {
ID_ARRAY_ACCESS => ExpressionItem::ArrayAccess,
ID_ARRAY_BUILD => ExpressionItem::ArrayBuild(*num_args),
id => ExpressionItem::Function {
id,
num_args: *num_args,
},
};
self.operator_stack.pop();
self.output.push(expr);
}
Some((Token::Regex(regex), _)) => {
if self.arg_count.pop().unwrap() != 1 {
return Err("Expression function \"matches\" expected 2 arguments"
.to_string());
}
self.output.push(ExpressionItem::Regex(regex.clone()));
self.operator_stack.pop();
}
Some((Token::System(setting), _)) => {
if self.arg_count.pop().unwrap() != 0 {
return Err("Expression function expected 1 argument".to_string());
}
self.output.push(ExpressionItem::System(setting.clone()));
self.operator_stack.pop();
}
_ => {}
}
is_var_or_fnc = true;
}
Token::BinaryOperator(bop) => {
self.dec_arg_count();
while let Some((top_token, prev_jmp_pos)) = self.operator_stack.last() {
match top_token {
Token::BinaryOperator(top_bop) => {
if bop.precedence() <= top_bop.precedence() {
let top_bop = *top_bop;
let jmp_pos = *prev_jmp_pos;
self.update_jmp_pos(jmp_pos);
self.operator_stack.pop();
self.output.push(ExpressionItem::BinaryOperator(top_bop));
} else {
break;
}
}
Token::UnaryOperator(top_uop) => {
let top_uop = *top_uop;
self.operator_stack.pop();
self.output.push(ExpressionItem::UnaryOperator(top_uop));
}
_ => break,
}
}
// Add jump instruction for short-circuiting
let jmp_pos = match bop {
BinaryOperator::And => {
self.output
.push(ExpressionItem::JmpIf { val: false, pos: 0 });
Some(self.output.len() - 1)
}
BinaryOperator::Or => {
self.output
.push(ExpressionItem::JmpIf { val: true, pos: 0 });
Some(self.output.len() - 1)
}
_ => None,
};
self.operator_stack
.push((Token::BinaryOperator(bop), jmp_pos));
}
token @ (Token::Function { .. } | Token::Regex(_) | Token::System(_)) => {
self.inc_arg_count();
self.arg_count.push(0);
self.operator_stack.push((token, None))
}
Token::OpenBracket => {
// Array functions
let (id, num_args, arg_count) = if last_is_var_or_fnc {
(ID_ARRAY_ACCESS, 2, 1)
} else {
self.inc_arg_count();
(ID_ARRAY_BUILD, 0, 0)
};
self.arg_count.push(arg_count);
self.operator_stack.push((
Token::Function {
id,
name: "array".into(),
num_args,
},
None,
));
self.operator_stack.push((token, None));
}
Token::Comma => {
while let Some((token, jmp_pos)) = self.operator_stack.last() {
match token {
Token::OpenParen => break,
Token::BinaryOperator(bop) => {
let bop = *bop;
let jmp_pos = *jmp_pos;
self.update_jmp_pos(jmp_pos);
self.output.push(ExpressionItem::BinaryOperator(bop));
self.operator_stack.pop();
}
Token::UnaryOperator(uop) => {
self.output.push(ExpressionItem::UnaryOperator(*uop));
self.operator_stack.pop();
}
_ => break,
}
}
}
}
last_is_var_or_fnc = is_var_or_fnc;
}
while let Some((token, jmp_pos)) = self.operator_stack.pop() {
match token {
Token::BinaryOperator(bop) => {
self.update_jmp_pos(jmp_pos);
self.output.push(ExpressionItem::BinaryOperator(bop))
}
Token::UnaryOperator(uop) => self.output.push(ExpressionItem::UnaryOperator(uop)),
_ => return Err("Invalid token on the operator stack".to_string()),
}
}
if self.operator_stack.is_empty() {
Ok(Expression {
items: self.output.into_boxed_slice(),
})
} else {
Err("Invalid expression".to_string())
}
}
fn inc_arg_count(&mut self) {
if let Some(x) = self.arg_count.last_mut() {
*x = x.saturating_add(1);
let op_pos = self.operator_stack.len().saturating_sub(2);
match self.operator_stack.get_mut(op_pos) {
Some((Token::Function { num_args, id, .. }, _)) if *id == ID_ARRAY_BUILD => {
*num_args += 1;
}
_ => {}
}
}
}
fn dec_arg_count(&mut self) {
if let Some(x) = self.arg_count.last_mut() {
*x = x.saturating_sub(1);
}
}
fn update_jmp_pos(&mut self, jmp_pos: Option<usize>) {
if let Some(jmp_pos) = jmp_pos {
let cur_pos = self.output.len();
if let ExpressionItem::JmpIf { pos, .. } = &mut self.output[jmp_pos] {
*pos = (cur_pos - jmp_pos) as u32;
} else {
#[cfg(test)]
panic!("Invalid jump position");
}
}
}
}
impl BinaryOperator {
fn precedence(&self) -> i32 {
match self {
BinaryOperator::Multiply | BinaryOperator::Divide => 7,
BinaryOperator::Add | BinaryOperator::Subtract => 6,
BinaryOperator::Gt | BinaryOperator::Ge | BinaryOperator::Lt | BinaryOperator::Le => 5,
BinaryOperator::Eq | BinaryOperator::Ne => 4,
BinaryOperator::Xor => 3,
BinaryOperator::And => 2,
BinaryOperator::Or => 1,
}
}
}
+401
View File
@@ -0,0 +1,401 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{
functions::{ASYNC_FUNCTIONS, FUNCTIONS},
*,
};
use ahash::AHashSet;
use regex::Regex;
use registry::{schema::enums::ExpressionConstant, types::EnumImpl};
use std::{borrow::Cow, iter::Peekable, slice::Iter};
use trc::MetricType;
pub struct Tokenizer<'x> {
pub(crate) iter: Peekable<Iter<'x, u8>>,
token_map: &'x TokenMap,
buf: Vec<u8>,
depth: u32,
next_token: Vec<Token>,
has_number: bool,
has_dot: bool,
has_alpha: bool,
is_start: bool,
is_eof: bool,
}
#[derive(Debug, Default, Clone)]
pub struct TokenMap {
pub variables: AHashSet<ExpressionVariable>,
pub constants: AHashSet<ExpressionConstant>,
}
impl<'x> Tokenizer<'x> {
#[allow(clippy::should_implement_trait)]
pub fn new(expr: &'x str, token_map: &'x TokenMap) -> Self {
Self {
iter: expr.as_bytes().iter().peekable(),
buf: Vec::new(),
depth: 0,
next_token: Vec::with_capacity(2),
has_number: false,
has_dot: false,
has_alpha: false,
is_start: true,
is_eof: false,
token_map,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<Token>, String> {
if let Some(token) = self.next_token.pop() {
return Ok(Some(token));
} else if self.is_eof {
return Ok(None);
}
while let Some(&ch) = self.iter.next() {
match ch {
b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'$' => {
self.buf.push(ch);
self.has_alpha = true;
}
b'0'..=b'9' => {
self.buf.push(ch);
self.has_number = true;
}
b'.' => {
self.buf.push(ch);
self.has_dot = true;
}
b'}' => {
self.is_eof = true;
break;
}
b'-' if self.buf.last().is_some_and(|c| *c == b'[') => {
self.buf.push(ch);
}
b':' if self.buf.contains(&b'.') => {
self.buf.push(ch);
}
b']' if self.buf.contains(&b'[') => {
self.buf.push(b']');
}
b'*' if self.buf.last().is_some_and(|&c| c == b'[' || c == b'.') => {
self.buf.push(ch);
}
_ => {
let (prev_token, ch) = if ch == b'(' && !self.buf.is_empty() {
match self.buf.as_slice() {
b"matches" => {
// Parse regular expressions
let stop_ch = self.find_char(b"\"'")?;
let regex_str = self.parse_string(stop_ch)?;
let regex = Regex::new(&regex_str).map_err(|e| {
format!("Invalid regular expression {:?}: {}", regex_str, e)
})?;
self.has_alpha = false;
self.buf.clear();
self.find_char(b",")?;
(Token::Regex(regex).into(), b'(')
}
b"metric" => {
let stop_ch = self.find_char(b"\"'")?;
let metric_str = self.parse_string(stop_ch)?;
let metric = MetricType::parse(&metric_str).ok_or_else(|| {
format!("Invalid metric name {:?}", metric_str)
})?;
self.has_alpha = false;
self.buf.clear();
(Token::System(SystemVariable::Metric(metric)).into(), b'(')
}
b"system" => {
let stop_ch = self.find_char(b"\"'")?;
let var = match self.parse_string(stop_ch)?.as_str() {
"domain" => SystemVariable::Domain,
"hostname" => SystemVariable::Hostname,
"node_id" => SystemVariable::NodeId,
"node_hostname" => SystemVariable::NodeHostname,
"node_role" => SystemVariable::NodeRole,
other => {
return Err(format!(
"Invalid system variable name {:?}",
other
));
}
};
self.has_alpha = false;
self.buf.clear();
(Token::System(var).into(), b'(')
}
_ => {
self.is_start = false;
(self.parse_buf()?.into(), ch)
}
}
} else if !self.buf.is_empty() {
self.is_start = false;
(self.parse_buf()?.into(), ch)
} else {
(None, ch)
};
let token = match ch {
b'&' => {
if matches!(self.iter.peek(), Some(b'&')) {
self.iter.next();
}
Token::BinaryOperator(BinaryOperator::And)
}
b'|' => {
if matches!(self.iter.peek(), Some(b'|')) {
self.iter.next();
}
Token::BinaryOperator(BinaryOperator::Or)
}
b'!' => {
if matches!(self.iter.peek(), Some(b'=')) {
self.iter.next();
Token::BinaryOperator(BinaryOperator::Ne)
} else {
Token::UnaryOperator(UnaryOperator::Not)
}
}
b'^' => Token::BinaryOperator(BinaryOperator::Xor),
b'(' => {
self.depth += 1;
Token::OpenParen
}
b')' => {
if self.depth == 0 {
return Err("Unmatched close parenthesis".to_string());
}
self.depth -= 1;
Token::CloseParen
}
b'+' => Token::BinaryOperator(BinaryOperator::Add),
b'*' => Token::BinaryOperator(BinaryOperator::Multiply),
b'/' => Token::BinaryOperator(BinaryOperator::Divide),
b'-' => {
if self.is_start {
Token::UnaryOperator(UnaryOperator::Minus)
} else {
Token::BinaryOperator(BinaryOperator::Subtract)
}
}
b'=' => match self.iter.next() {
Some(b'=') => Token::BinaryOperator(BinaryOperator::Eq),
Some(b'>') => Token::BinaryOperator(BinaryOperator::Ge),
Some(b'<') => Token::BinaryOperator(BinaryOperator::Le),
_ => Token::BinaryOperator(BinaryOperator::Eq),
},
b'>' => match self.iter.peek() {
Some(b'=') => {
self.iter.next();
Token::BinaryOperator(BinaryOperator::Ge)
}
_ => Token::BinaryOperator(BinaryOperator::Gt),
},
b'<' => match self.iter.peek() {
Some(b'=') => {
self.iter.next();
Token::BinaryOperator(BinaryOperator::Le)
}
_ => Token::BinaryOperator(BinaryOperator::Lt),
},
b',' => Token::Comma,
b'[' => Token::OpenBracket,
b']' => Token::CloseBracket,
b' ' | b'\r' | b'\n' => {
if prev_token.is_some() {
return Ok(prev_token);
} else {
continue;
}
}
b'\"' | b'\'' => Token::Constant(Constant::String(self.parse_string(ch)?)),
_ => {
return Err(format!("Invalid character {:?}", char::from(ch),));
}
};
self.is_start = matches!(
token,
Token::OpenParen | Token::Comma | Token::BinaryOperator(_)
);
return if prev_token.is_some() {
self.next_token.push(token);
Ok(prev_token)
} else {
Ok(Some(token))
};
}
}
}
if self.depth > 0 {
Err("Unmatched open parenthesis".to_string())
} else if !self.buf.is_empty() {
self.parse_buf().map(Some)
} else {
Ok(None)
}
}
fn find_char(&mut self, chars: &[u8]) -> Result<u8, String> {
for &ch in self.iter.by_ref() {
if !ch.is_ascii_whitespace() {
return if chars.contains(&ch) {
Ok(ch)
} else {
Err(format!(
"Expected {:?}, found invalid character {:?}",
char::from(chars[0]),
char::from(ch),
))
};
}
}
Err("Unexpected end of expression".to_string())
}
fn parse_string(&mut self, stop_ch: u8) -> Result<CompactString, String> {
let mut buf = Vec::with_capacity(16);
let mut last_ch = 0;
let mut found_end = false;
for &ch in self.iter.by_ref() {
if last_ch != b'\\' {
if ch != stop_ch {
buf.push(ch);
} else {
found_end = true;
break;
}
} else {
match ch {
b'n' => {
buf.push(b'\n');
}
b'r' => {
buf.push(b'\r');
}
b't' => {
buf.push(b'\t');
}
_ => {
buf.push(ch);
}
}
}
last_ch = ch;
}
if found_end {
CompactString::from_utf8(buf).map_err(|_| "Invalid UTF-8".into())
} else {
Err("Unterminated string".to_string())
}
}
fn parse_buf(&mut self) -> Result<Token, String> {
let buf = String::from_utf8(std::mem::take(&mut self.buf)).unwrap_or_default();
if self.has_number && !self.has_alpha {
self.has_number = false;
if self.has_dot {
self.has_dot = false;
buf.parse::<f64>()
.map(|f| Token::Constant(Constant::Float(f)))
.map_err(|_| format!("Invalid float value {}", buf,))
} else {
buf.parse::<i64>()
.map(|i| Token::Constant(Constant::Integer(i)))
.map_err(|_| format!("Invalid integer value {}", buf,))
}
} else {
let has_dot = self.has_dot;
let has_number = self.has_number;
self.has_alpha = false;
self.has_number = false;
self.has_dot = false;
if !has_number && !has_dot && [4, 5].contains(&buf.len()) {
if buf == "true" {
return Ok(Token::Constant(Constant::Integer(1)));
} else if buf == "false" {
return Ok(Token::Constant(Constant::Integer(0)));
}
}
if let Some(variable) = buf.strip_prefix('$').filter(|s| !s.is_empty()) {
if variable.chars().all(|c| c.is_ascii_digit()) {
Ok(variable
.parse::<u32>()
.map(Token::Capture)
.unwrap_or_else(|_| Token::Global(variable.into())))
} else {
Ok(Token::Global(variable.into()))
}
} else if let Some((idx, (name, _, num_args))) = FUNCTIONS
.iter()
.enumerate()
.find(|(_, (name, _, _))| name == &buf)
{
Ok(Token::Function {
name: Cow::Borrowed(*name),
id: idx as u32,
num_args: *num_args,
})
} else if let Some((name, idx, num_args)) =
ASYNC_FUNCTIONS.iter().find(|(name, _, _)| name == &buf)
{
Ok(Token::Function {
name: Cow::Borrowed(*name),
id: *idx + FUNCTIONS.len() as u32,
num_args: *num_args,
})
} else if let Some(variable) = ExpressionVariable::parse(buf.as_str()) {
if self.token_map.variables.is_empty()
|| self.token_map.variables.contains(&variable)
{
Ok(Token::Variable(variable))
} else {
Err(format!("Variable {:?} not allowed in this context", buf))
}
} else if let Some(constant) = ExpressionConstant::parse(buf.as_str()) {
if self.token_map.constants.is_empty()
|| self.token_map.constants.contains(&constant)
{
Ok(Token::Constant(Constant::Static(constant)))
} else {
Err(format!("Constant {:?} not allowed in this context", buf))
}
} else if let Ok(duration) = registry::types::duration::Duration::from_str(&buf) {
Ok(Token::Constant(Constant::Integer(
duration.as_millis() as i64
)))
} else {
Err(format!("Invalid variable or constant {buf:?}"))
}
}
}
}
impl TokenMap {
pub fn with_variables(mut self, variables: &[ExpressionVariable]) -> Self {
self.variables.extend(variables.iter().copied());
self
}
pub fn with_constants(mut self, constants: &[ExpressionConstant]) -> Self {
self.constants.extend(constants.iter().copied());
self
}
}