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