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,159 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
pub fn fn_count<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::Array(a) => a.len(),
|
||||
v => {
|
||||
if !v.is_empty() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_sort<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let is_asc = v[1].to_bool();
|
||||
let mut arr = (*v[0].to_array()).clone();
|
||||
if is_asc {
|
||||
arr.sort_unstable();
|
||||
} else {
|
||||
arr.sort_unstable_by(|a, b| b.cmp(a));
|
||||
}
|
||||
arr.into()
|
||||
}
|
||||
|
||||
pub fn fn_dedup<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let arr = v[0].to_array();
|
||||
let mut result = Vec::with_capacity(arr.len());
|
||||
|
||||
for item in arr.iter() {
|
||||
if !result.contains(item) {
|
||||
result.push(item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub fn fn_cosine_similarity<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let mut word_freq: HashMap<Variable, [u32; 2]> = HashMap::new();
|
||||
|
||||
for (idx, var) in v.into_iter().enumerate() {
|
||||
match var {
|
||||
Variable::Array(l) => {
|
||||
for item in l.iter() {
|
||||
word_freq.entry(item.clone()).or_insert([0, 0])[idx] += 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for char in var.to_string().chars() {
|
||||
word_freq.entry(char.to_string().into()).or_insert([0, 0])[idx] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dot_product = 0;
|
||||
let mut magnitude_a = 0;
|
||||
let mut magnitude_b = 0;
|
||||
|
||||
for count in word_freq.values() {
|
||||
dot_product += count[0] * count[1];
|
||||
magnitude_a += count[0] * count[0];
|
||||
magnitude_b += count[1] * count[1];
|
||||
}
|
||||
|
||||
if magnitude_a != 0 && magnitude_b != 0 {
|
||||
dot_product as f64 / (magnitude_a as f64).sqrt() / (magnitude_b as f64).sqrt()
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn cosine_similarity(a: &[&str], b: &[&str]) -> f64 {
|
||||
let mut word_freq: HashMap<&str, [u32; 2]> = HashMap::new();
|
||||
|
||||
for (idx, items) in [a, b].into_iter().enumerate() {
|
||||
for item in items {
|
||||
word_freq.entry(item).or_insert([0, 0])[idx] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut dot_product = 0;
|
||||
let mut magnitude_a = 0;
|
||||
let mut magnitude_b = 0;
|
||||
|
||||
for count in word_freq.values() {
|
||||
dot_product += count[0] * count[1];
|
||||
magnitude_a += count[0] * count[0];
|
||||
magnitude_b += count[1] * count[1];
|
||||
}
|
||||
|
||||
if magnitude_a != 0 && magnitude_b != 0 {
|
||||
dot_product as f64 / (magnitude_a as f64).sqrt() / (magnitude_b as f64).sqrt()
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let mut word_freq = [HashSet::new(), HashSet::new()];
|
||||
|
||||
for (idx, var) in v.into_iter().enumerate() {
|
||||
match var {
|
||||
Variable::Array(l) => {
|
||||
for item in l.iter() {
|
||||
word_freq[idx].insert(item.clone());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for char in var.to_string().chars() {
|
||||
word_freq[idx].insert(char.to_string().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let intersection_size = word_freq[0].intersection(&word_freq[1]).count();
|
||||
let union_size = word_freq[0].union(&word_freq[1]).count();
|
||||
|
||||
if union_size != 0 {
|
||||
intersection_size as f64 / union_size as f64
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_intersect<'x>(_: &'x Context<'x>, 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 fn fn_winnow<'x>(_: &'x Context<'x>, mut v: Vec<Variable>) -> Variable {
|
||||
match v.remove(0) {
|
||||
Variable::Array(a) => a
|
||||
.iter()
|
||||
.filter(|i| !i.is_empty())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
v => v,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
use super::ApplyString;
|
||||
|
||||
pub fn fn_is_email<'x>(_: &'x Context<'x>, 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().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 fn fn_email_part<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| {
|
||||
s.rsplit_once('@')
|
||||
.map(|(u, d)| match v[1].to_string().as_ref() {
|
||||
"local" => Variable::from(u.trim()),
|
||||
"domain" => Variable::from(d.trim()),
|
||||
_ => Variable::default(),
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use mail_parser::{HeaderName, HeaderValue, MimeHeaders, parsers::fields::thread::thread_name};
|
||||
use sieve::{Context, compiler::ReceivedPart, runtime::Variable};
|
||||
|
||||
use super::ApplyString;
|
||||
|
||||
pub fn fn_received_part<'x>(ctx: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
if let (Ok(part), Some(HeaderValue::Received(rcvd))) = (
|
||||
ReceivedPart::try_from(v[1].to_string().as_ref()),
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.and_then(|p| {
|
||||
p.headers
|
||||
.iter()
|
||||
.filter(|h| h.name == HeaderName::Received)
|
||||
.nth((v[0].to_integer() as usize).saturating_sub(1))
|
||||
})
|
||||
.map(|h| &h.value),
|
||||
) {
|
||||
part.eval(rcvd).unwrap_or_default()
|
||||
} else {
|
||||
Variable::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_is_encoding_problem<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.map(|p| p.is_encoding_problem)
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_attachment<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
ctx.message().attachments.contains(&ctx.part()).into()
|
||||
}
|
||||
|
||||
pub fn fn_is_body<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
(ctx.message().text_body.contains(&ctx.part()) || ctx.message().html_body.contains(&ctx.part()))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_attachment_name<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.and_then(|p| p.attachment_name())
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_mime_part_len<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.map(|p| p.len())
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_thread_name<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| thread_name(s).into())
|
||||
}
|
||||
|
||||
pub fn fn_is_header_utf8_valid<'x>(ctx: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.map(|p| {
|
||||
let raw = ctx.message().raw_message();
|
||||
let mut is_valid = true;
|
||||
if let Some(header_name) = HeaderName::parse(v[0].to_string().as_ref()) {
|
||||
for header in &p.headers {
|
||||
if header.name == header_name
|
||||
&& raw
|
||||
.get(header.offset_start() as usize..header.offset_end() as usize)
|
||||
.and_then(|raw| std::str::from_utf8(raw).ok())
|
||||
.is_none()
|
||||
{
|
||||
is_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_valid = raw
|
||||
.get(p.raw_header_offset() as usize..p.raw_body_offset() as usize)
|
||||
.and_then(|raw| std::str::from_utf8(raw).ok())
|
||||
.is_some();
|
||||
}
|
||||
|
||||
Variable::from(is_valid)
|
||||
})
|
||||
.unwrap_or(Variable::Integer(1))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
pub fn fn_img_metadata<'x>(ctx: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.map(|p| p.contents())
|
||||
.and_then(|bytes| {
|
||||
let arg = v[1].to_string();
|
||||
match arg.as_ref() {
|
||||
"type" => imagesize::image_type(bytes).ok().map(|t| {
|
||||
Variable::from(match t {
|
||||
imagesize::ImageType::Aseprite => "aseprite",
|
||||
imagesize::ImageType::Bmp => "bmp",
|
||||
imagesize::ImageType::Dds(_) => "dds",
|
||||
imagesize::ImageType::Exr => "exr",
|
||||
imagesize::ImageType::Farbfeld => "farbfeld",
|
||||
imagesize::ImageType::Gif => "gif",
|
||||
imagesize::ImageType::Hdr => "hdr",
|
||||
imagesize::ImageType::Heif(_) => "heif",
|
||||
imagesize::ImageType::Ico => "ico",
|
||||
imagesize::ImageType::Jpeg => "jpeg",
|
||||
imagesize::ImageType::Jxl => "jxl",
|
||||
imagesize::ImageType::Ktx2 => "ktx2",
|
||||
imagesize::ImageType::Png => "png",
|
||||
imagesize::ImageType::Pnm => "pnm",
|
||||
imagesize::ImageType::Psd => "psd",
|
||||
imagesize::ImageType::Qoi => "qoi",
|
||||
imagesize::ImageType::Tga => "tga",
|
||||
imagesize::ImageType::Tiff => "tiff",
|
||||
imagesize::ImageType::Vtf => "vtf",
|
||||
imagesize::ImageType::Webp => "webp",
|
||||
imagesize::ImageType::Ilbm => "ilbm",
|
||||
_ => "unknown",
|
||||
})
|
||||
}),
|
||||
"width" => imagesize::blob_size(bytes)
|
||||
.ok()
|
||||
.map(|s| Variable::Integer(s.width as i64)),
|
||||
"height" => imagesize::blob_size(bytes)
|
||||
.ok()
|
||||
.map(|s| Variable::Integer(s.height as i64)),
|
||||
"area" => imagesize::blob_size(bytes)
|
||||
.ok()
|
||||
.map(|s| Variable::Integer(s.width.saturating_mul(s.height) as i64)),
|
||||
"dimension" => imagesize::blob_size(bytes)
|
||||
.ok()
|
||||
.map(|s| Variable::Integer(s.width.saturating_add(s.height) as i64)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
|
||||
use mail_auth::common::resolver::ToReverseName;
|
||||
use registry::types::ipmask::IpAddrOrMask;
|
||||
use sha1::Sha1;
|
||||
use sha2::{Sha256, Sha512};
|
||||
use sieve::{Context, runtime::Variable};
|
||||
use utils::HexEncode;
|
||||
|
||||
use super::ApplyString;
|
||||
|
||||
pub fn fn_is_empty<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.is_empty(),
|
||||
Variable::Integer(_) | Variable::Float(_) => false,
|
||||
Variable::Array(a) => a.is_empty(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_number<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
matches!(&v[0], Variable::Integer(_) | Variable::Float(_)).into()
|
||||
}
|
||||
|
||||
pub fn fn_is_ip_addr<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string().parse::<std::net::IpAddr>().is_ok().into()
|
||||
}
|
||||
|
||||
pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.parse::<std::net::IpAddr>()
|
||||
.is_ok_and(|ip| matches!(ip, IpAddr::V4(_)))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.parse::<std::net::IpAddr>()
|
||||
.is_ok_and(|ip| matches!(ip, IpAddr::V6(_)))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_ip_in_cidr<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let Ok(ip) = v[0].to_string().parse::<IpAddr>() else {
|
||||
return false.into();
|
||||
};
|
||||
IpAddrOrMask::from_str(v[1].to_string().as_ref())
|
||||
.map(|mask| mask.matches(&ip))
|
||||
.unwrap_or(false)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_ip_reverse_name<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.parse::<std::net::IpAddr>()
|
||||
.map(|ip| ip.to_reverse_name())
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_detect_file_type<'x>(ctx: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
ctx.message()
|
||||
.part(ctx.part())
|
||||
.and_then(|p| infer::get(p.contents()))
|
||||
.map(|t| {
|
||||
Variable::from(
|
||||
if v[0].to_string() != "ext" {
|
||||
t.mime_type()
|
||||
} else {
|
||||
t.extension()
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fn_hash<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
use sha1::Digest;
|
||||
let hash = v[1].to_string();
|
||||
|
||||
v[0].transform(|value| match hash.as_ref() {
|
||||
"md5" => format!("{:x}", md5::compute(value.as_bytes())).into(),
|
||||
"sha1" => {
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(value.as_bytes());
|
||||
hasher.finalize().hex_encode().into()
|
||||
}
|
||||
"sha256" => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(value.as_bytes());
|
||||
hasher.finalize().hex_encode().into()
|
||||
}
|
||||
"sha512" => {
|
||||
let mut hasher = Sha512::new();
|
||||
hasher.update(value.as_bytes());
|
||||
hasher.finalize().hex_encode().into()
|
||||
}
|
||||
_ => Variable::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_get_var_names<'x>(ctx: &'x Context<'x>, _: Vec<Variable>) -> Variable {
|
||||
Variable::Array(
|
||||
ctx.global_variable_names()
|
||||
.map(|v| Variable::from(v.to_uppercase()))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod array;
|
||||
mod email;
|
||||
mod header;
|
||||
pub mod image;
|
||||
pub mod misc;
|
||||
pub mod text;
|
||||
pub mod unicode;
|
||||
pub mod url;
|
||||
|
||||
use sieve::{FunctionMap, runtime::Variable};
|
||||
|
||||
use self::{array::*, email::*, header::*, image::*, misc::*, text::*, unicode::*, url::*};
|
||||
|
||||
pub fn register_functions_trusted() -> FunctionMap {
|
||||
FunctionMap::new()
|
||||
.with_function("trim", fn_trim)
|
||||
.with_function("trim_start", fn_trim_start)
|
||||
.with_function("trim_end", fn_trim_end)
|
||||
.with_function("len", fn_len)
|
||||
.with_function("count", fn_count)
|
||||
.with_function("is_empty", fn_is_empty)
|
||||
.with_function("is_number", fn_is_number)
|
||||
.with_function("is_ascii", fn_is_ascii)
|
||||
.with_function("to_lowercase", fn_to_lowercase)
|
||||
.with_function("to_uppercase", fn_to_uppercase)
|
||||
.with_function("detect_language", fn_detect_language)
|
||||
.with_function("is_email", fn_is_email)
|
||||
.with_function("thread_name", fn_thread_name)
|
||||
.with_function("html_to_text", fn_html_to_text)
|
||||
.with_function("is_uppercase", fn_is_uppercase)
|
||||
.with_function("is_lowercase", fn_is_lowercase)
|
||||
.with_function("has_digits", fn_has_digits)
|
||||
.with_function("count_spaces", fn_count_spaces)
|
||||
.with_function("count_uppercase", fn_count_uppercase)
|
||||
.with_function("count_lowercase", fn_count_lowercase)
|
||||
.with_function("count_chars", fn_count_chars)
|
||||
.with_function("dedup", fn_dedup)
|
||||
.with_function("lines", fn_lines)
|
||||
.with_function("is_header_utf8_valid", fn_is_header_utf8_valid)
|
||||
.with_function("img_metadata", fn_img_metadata)
|
||||
.with_function("is_ip_addr", fn_is_ip_addr)
|
||||
.with_function("is_ipv4_addr", fn_is_ipv4_addr)
|
||||
.with_function("is_ipv6_addr", fn_is_ipv6_addr)
|
||||
.with_function("ip_reverse_name", fn_ip_reverse_name)
|
||||
.with_function_args("is_ip_in_cidr", fn_is_ip_in_cidr, 2)
|
||||
.with_function("winnow", fn_winnow)
|
||||
.with_function("has_zwsp", fn_has_zwsp)
|
||||
.with_function("has_obscured", fn_has_obscured)
|
||||
.with_function("is_mixed_charset", fn_is_mixed_charset)
|
||||
.with_function("puny_decode", fn_puny_decode)
|
||||
.with_function("unicode_skeleton", fn_unicode_skeleton)
|
||||
.with_function("cure_text", fn_cure_text)
|
||||
.with_function("detect_file_type", fn_detect_file_type)
|
||||
.with_function_args("sort", fn_sort, 2)
|
||||
.with_function_args("email_part", fn_email_part, 2)
|
||||
.with_function_args("eq_ignore_case", fn_eq_ignore_case, 2)
|
||||
.with_function_args("contains", fn_contains, 2)
|
||||
.with_function_args("contains_ignore_case", fn_contains_ignore_case, 2)
|
||||
.with_function_args("starts_with", fn_starts_with, 2)
|
||||
.with_function_args("ends_with", fn_ends_with, 2)
|
||||
.with_function_args("received_part", fn_received_part, 2)
|
||||
.with_function_args("cosine_similarity", fn_cosine_similarity, 2)
|
||||
.with_function_args("jaccard_similarity", fn_jaccard_similarity, 2)
|
||||
.with_function_args("levenshtein_distance", fn_levenshtein_distance, 2)
|
||||
.with_function_args("uri_part", fn_uri_part, 2)
|
||||
.with_function_args("substring", fn_substring, 3)
|
||||
.with_function_args("split", fn_split, 2)
|
||||
.with_function_args("rsplit", fn_rsplit, 2)
|
||||
.with_function_args("split_once", fn_split_once, 2)
|
||||
.with_function_args("rsplit_once", fn_rsplit_once, 2)
|
||||
.with_function_args("split_n", fn_split_n, 3)
|
||||
.with_function_args("strip_prefix", fn_strip_prefix, 2)
|
||||
.with_function_args("strip_suffix", fn_strip_suffix, 2)
|
||||
.with_function_args("is_intersect", fn_is_intersect, 2)
|
||||
.with_function_args("hash", fn_hash, 2)
|
||||
.with_function_no_args("is_encoding_problem", fn_is_encoding_problem)
|
||||
.with_function_no_args("is_attachment", fn_is_attachment)
|
||||
.with_function_no_args("is_body", fn_is_body)
|
||||
.with_function_no_args("var_names", fn_get_var_names)
|
||||
.with_function_no_args("attachment_name", fn_attachment_name)
|
||||
.with_function_no_args("mime_part_len", fn_mime_part_len)
|
||||
}
|
||||
|
||||
pub fn register_functions_untrusted() -> FunctionMap {
|
||||
FunctionMap::new()
|
||||
.with_function("trim", fn_trim)
|
||||
.with_function("trim_start", fn_trim_start)
|
||||
.with_function("trim_end", fn_trim_end)
|
||||
.with_function("len", fn_len)
|
||||
.with_function("count", fn_count)
|
||||
.with_function("is_empty", fn_is_empty)
|
||||
.with_function("is_number", fn_is_number)
|
||||
.with_function("is_ascii", fn_is_ascii)
|
||||
.with_function("to_lowercase", fn_to_lowercase)
|
||||
.with_function("to_uppercase", fn_to_uppercase)
|
||||
.with_function("is_email", fn_is_email)
|
||||
.with_function("thread_name", fn_thread_name)
|
||||
.with_function("html_to_text", fn_html_to_text)
|
||||
.with_function("is_uppercase", fn_is_uppercase)
|
||||
.with_function("is_lowercase", fn_is_lowercase)
|
||||
.with_function("has_digits", fn_has_digits)
|
||||
.with_function("count_spaces", fn_count_spaces)
|
||||
.with_function("count_uppercase", fn_count_uppercase)
|
||||
.with_function("count_lowercase", fn_count_lowercase)
|
||||
.with_function("count_chars", fn_count_chars)
|
||||
.with_function("dedup", fn_dedup)
|
||||
.with_function("lines", fn_lines)
|
||||
.with_function("is_ip_addr", fn_is_ip_addr)
|
||||
.with_function("is_ipv4_addr", fn_is_ipv4_addr)
|
||||
.with_function("is_ipv6_addr", fn_is_ipv6_addr)
|
||||
.with_function("winnow", fn_winnow)
|
||||
.with_function_args("sort", fn_sort, 2)
|
||||
.with_function_args("email_part", fn_email_part, 2)
|
||||
.with_function_args("eq_ignore_case", fn_eq_ignore_case, 2)
|
||||
.with_function_args("contains", fn_contains, 2)
|
||||
.with_function_args("contains_ignore_case", fn_contains_ignore_case, 2)
|
||||
.with_function_args("starts_with", fn_starts_with, 2)
|
||||
.with_function_args("ends_with", fn_ends_with, 2)
|
||||
.with_function_args("uri_part", fn_uri_part, 2)
|
||||
.with_function_args("substring", fn_substring, 3)
|
||||
.with_function_args("split", fn_split, 2)
|
||||
.with_function_args("rsplit", fn_rsplit, 2)
|
||||
.with_function_args("split_once", fn_split_once, 2)
|
||||
.with_function_args("rsplit_once", fn_rsplit_once, 2)
|
||||
.with_function_args("split_n", fn_split_n, 3)
|
||||
.with_function_args("strip_prefix", fn_strip_prefix, 2)
|
||||
.with_function_args("strip_suffix", fn_strip_suffix, 2)
|
||||
.with_function_args("is_intersect", fn_is_intersect, 2)
|
||||
}
|
||||
|
||||
pub trait ApplyString<'x> {
|
||||
fn transform(&self, f: impl Fn(&'_ str) -> Variable) -> Variable;
|
||||
}
|
||||
|
||||
impl ApplyString<'_> for Variable {
|
||||
fn transform(&self, f: impl Fn(&'_ str) -> Variable) -> Variable {
|
||||
match self {
|
||||
Variable::String(s) => f(s),
|
||||
Variable::Array(list) => list
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Variable::String(s) => f(s),
|
||||
v => f(v.to_string().as_ref()),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
v => f(v.to_string().as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use mail_parser::decoders::html::html_to_text;
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
use super::ApplyString;
|
||||
|
||||
pub fn fn_trim<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| Variable::from(s.trim()))
|
||||
}
|
||||
|
||||
pub fn fn_trim_end<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| Variable::from(s.trim_end()))
|
||||
}
|
||||
|
||||
pub fn fn_trim_start<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| Variable::from(s.trim_start()))
|
||||
}
|
||||
|
||||
pub fn fn_len<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.len(),
|
||||
Variable::Array(a) => a.len(),
|
||||
v => v.to_string().len(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_to_lowercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| Variable::from(s.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn fn_to_uppercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| Variable::from(s.to_uppercase()))
|
||||
}
|
||||
|
||||
pub fn fn_is_uppercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| {
|
||||
s.chars()
|
||||
.filter(|c| c.is_alphabetic())
|
||||
.all(|c| c.is_uppercase())
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_is_lowercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| {
|
||||
s.chars()
|
||||
.filter(|c| c.is_alphabetic())
|
||||
.all(|c| c.is_lowercase())
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_has_digits<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|s| s.chars().any(|c| c.is_ascii_digit()).into())
|
||||
}
|
||||
|
||||
pub fn tokenize_words(v: &Variable) -> Variable {
|
||||
v.to_string()
|
||||
.split_whitespace()
|
||||
.filter(|word| word.chars().all(|c| c.is_alphanumeric()))
|
||||
.map(|word| Variable::from(word.to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_count_spaces<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.as_ref()
|
||||
.chars()
|
||||
.filter(|c| c.is_whitespace())
|
||||
.count()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_count_uppercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.as_ref()
|
||||
.chars()
|
||||
.filter(|c| c.is_alphabetic() && c.is_uppercase())
|
||||
.count()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_count_lowercase<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.as_ref()
|
||||
.chars()
|
||||
.filter(|c| c.is_alphabetic() && c.is_lowercase())
|
||||
.count()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_count_chars<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string().as_ref().chars().count().into()
|
||||
}
|
||||
|
||||
pub fn fn_eq_ignore_case<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.eq_ignore_ascii_case(v[1].to_string().as_ref())
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_contains<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.contains(v[1].to_string().as_ref()),
|
||||
Variable::Array(arr) => arr.contains(&v[1]),
|
||||
val => val.to_string().contains(v[1].to_string().as_ref()),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let needle = v[1].to_string();
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.to_lowercase().contains(&needle.to_lowercase()),
|
||||
Variable::Array(arr) => arr.iter().any(|v| match v {
|
||||
Variable::String(s) => s.eq_ignore_ascii_case(needle.as_ref()),
|
||||
_ => false,
|
||||
}),
|
||||
val => val.to_string().contains(needle.as_ref()),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_starts_with<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.starts_with(v[1].to_string().as_ref())
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_ends_with<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string().ends_with(v[1].to_string().as_ref()).into()
|
||||
}
|
||||
|
||||
pub fn fn_lines<'x>(_: &'x Context<'x>, mut v: Vec<Variable>) -> Variable {
|
||||
match v.remove(0) {
|
||||
Variable::String(s) => s
|
||||
.lines()
|
||||
.map(|s| Variable::from(s.to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
val => val,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_substring<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.chars()
|
||||
.skip(v[1].to_usize())
|
||||
.take(v[2].to_usize())
|
||||
.collect::<String>()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_strip_prefix<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let prefix = v[1].to_string();
|
||||
v[0].transform(|s| {
|
||||
s.strip_prefix(prefix.as_ref())
|
||||
.map(Variable::from)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_strip_suffix<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let suffix = v[1].to_string();
|
||||
v[0].transform(|s| {
|
||||
s.strip_suffix(suffix.as_ref())
|
||||
.map(Variable::from)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_split<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.split(v[1].to_string().as_ref())
|
||||
.map(|s| Variable::from(s.to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_rsplit<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.rsplit(v[1].to_string().as_ref())
|
||||
.map(|s| Variable::from(s.to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_split_n<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let value = v[0].to_string();
|
||||
let arg = v[1].to_string();
|
||||
let num = v[2].to_integer() as usize;
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut s = value.as_ref();
|
||||
for _ in 0..num {
|
||||
if let Some((a, b)) = s.split_once(arg.as_ref()) {
|
||||
result.push(Variable::from(a.to_string()));
|
||||
s = b;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push(Variable::from(s.to_string()));
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub fn fn_split_once<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.split_once(v[1].to_string().as_ref())
|
||||
.map(|(a, b)| {
|
||||
Variable::Array(
|
||||
vec![Variable::from(a.to_string()), Variable::from(b.to_string())].into(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fn_rsplit_once<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].to_string()
|
||||
.rsplit_once(v[1].to_string().as_ref())
|
||||
.map(|(a, b)| {
|
||||
Variable::Array(
|
||||
vec![Variable::from(a.to_string()), Variable::from(b.to_string())].into(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/**
|
||||
* `levenshtein-rs` - levenshtein
|
||||
*
|
||||
* MIT licensed.
|
||||
*
|
||||
* Copyright (c) 2016 Titus Wormer <[email protected]>
|
||||
*/
|
||||
pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let a = v[0].to_string();
|
||||
let b = v[1].to_string();
|
||||
|
||||
levenshtein_distance(a.as_ref(), b.as_ref()).into()
|
||||
}
|
||||
|
||||
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
|
||||
let mut result = 0;
|
||||
|
||||
/* Shortcut optimizations / degenerate cases. */
|
||||
if a == b {
|
||||
return result;
|
||||
}
|
||||
|
||||
let length_a = a.chars().count();
|
||||
let length_b = b.chars().count();
|
||||
|
||||
if length_a == 0 {
|
||||
return length_b;
|
||||
} else if length_b == 0 {
|
||||
return length_a;
|
||||
}
|
||||
|
||||
/* Initialize the vector.
|
||||
*
|
||||
* This is why it’s fast, normally a matrix is used,
|
||||
* here we use a single vector. */
|
||||
let mut cache: Vec<usize> = (1..).take(length_a).collect();
|
||||
let mut distance_a;
|
||||
let mut distance_b;
|
||||
|
||||
/* Loop. */
|
||||
for (index_b, code_b) in b.chars().enumerate() {
|
||||
result = index_b;
|
||||
distance_a = index_b;
|
||||
|
||||
for (index_a, code_a) in a.chars().enumerate() {
|
||||
distance_b = if code_a == code_b {
|
||||
distance_a
|
||||
} else {
|
||||
distance_a + 1
|
||||
};
|
||||
|
||||
distance_a = cache[index_a];
|
||||
|
||||
result = if distance_a > result {
|
||||
if distance_b > result {
|
||||
result + 1
|
||||
} else {
|
||||
distance_b
|
||||
}
|
||||
} else if distance_b > distance_a {
|
||||
distance_a + 1
|
||||
} else {
|
||||
distance_b
|
||||
};
|
||||
|
||||
cache[index_a] = result;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn fn_detect_language<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
whatlang::detect_lang(v[0].to_string().as_ref())
|
||||
.map(|l| l.code())
|
||||
.unwrap_or("unknown")
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_html_to_text<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
html_to_text(v[0].to_string().as_ref()).into()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
use crate::scripts::IsMixedCharset;
|
||||
|
||||
pub fn fn_is_ascii<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.is_ascii(),
|
||||
Variable::Integer(_) | Variable::Float(_) => true,
|
||||
Variable::Array(a) => a.iter().all(|v| match v {
|
||||
Variable::String(s) => s.is_ascii(),
|
||||
_ => true,
|
||||
}),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_has_zwsp<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.chars().any(|c| c.is_zwsp()),
|
||||
Variable::Array(a) => a.iter().any(|v| match v {
|
||||
Variable::String(s) => s.chars().any(|c| c.is_zwsp()),
|
||||
_ => true,
|
||||
}),
|
||||
Variable::Integer(_) | Variable::Float(_) => false,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_has_obscured<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
match &v[0] {
|
||||
Variable::String(s) => s.chars().any(|c| c.is_obscured()),
|
||||
Variable::Array(a) => a.iter().any(|v| match v {
|
||||
Variable::String(s) => s.chars().any(|c| c.is_obscured()),
|
||||
_ => true,
|
||||
}),
|
||||
Variable::Integer(_) | Variable::Float(_) => false,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub trait CharUtils {
|
||||
fn is_zwsp(&self) -> bool;
|
||||
fn is_obscured(&self) -> bool;
|
||||
}
|
||||
|
||||
impl CharUtils for char {
|
||||
fn is_zwsp(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
'\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{FEFF}' | '\u{00AD}'
|
||||
)
|
||||
}
|
||||
|
||||
fn is_obscured(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
'\u{200B}'..='\u{200F}'
|
||||
| '\u{2028}'..='\u{202F}'
|
||||
| '\u{205F}'..='\u{206F}'
|
||||
| '\u{FEFF}'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_cure_text<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
decancer::cure(v[0].to_string().as_ref(), decancer::Options::default())
|
||||
.map(String::from)
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_unicode_skeleton<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
unicode_security::skeleton(v[0].to_string().as_ref())
|
||||
.collect::<String>()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn fn_is_mixed_charset<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let text = v[0].to_string();
|
||||
if !text.is_empty() {
|
||||
text.as_ref().is_mixed_charset()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use hyper::Uri;
|
||||
use sieve::{Context, runtime::Variable};
|
||||
|
||||
use super::ApplyString;
|
||||
|
||||
pub fn fn_uri_part<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
let part = v[1].to_string();
|
||||
v[0].transform(|uri| {
|
||||
uri.parse::<Uri>()
|
||||
.ok()
|
||||
.and_then(|uri| match part.as_ref() {
|
||||
"scheme" => uri.scheme_str().map(|s| Variable::from(s.to_string())),
|
||||
"host" => uri.host().map(|s| Variable::from(s.to_string())),
|
||||
"scheme_host" => uri
|
||||
.scheme_str()
|
||||
.and_then(|s| (s, uri.host()?).into())
|
||||
.map(|(s, h)| Variable::from(format!("{}://{}", s, h))),
|
||||
"path" => Variable::from(uri.path().to_string()).into(),
|
||||
"port" => uri.port_u16().map(|port| Variable::Integer(port as i64)),
|
||||
"query" => uri.query().map(|s| Variable::from(s.to_string())),
|
||||
"path_query" => uri.path_and_query().map(|s| Variable::from(s.to_string())),
|
||||
"authority" => uri.authority().map(|s| Variable::from(s.to_string())),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fn_puny_decode<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
v[0].transform(|domain| {
|
||||
if domain.contains("xn--") {
|
||||
let mut decoded = String::with_capacity(domain.len());
|
||||
for part in domain.split('.') {
|
||||
if !decoded.is_empty() {
|
||||
decoded.push('.');
|
||||
}
|
||||
|
||||
if let Some(puny) = part
|
||||
.strip_prefix("xn--")
|
||||
.and_then(idna::punycode::decode_to_string)
|
||||
{
|
||||
decoded.push_str(&puny);
|
||||
} else {
|
||||
decoded.push_str(part);
|
||||
}
|
||||
}
|
||||
decoded.into()
|
||||
} else {
|
||||
domain.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user