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:
@@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(®ex_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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user