Import upstream v0.16.22, stripped

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

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Borrow, hash::Hash, rc::Rc};
use ahash::AHashMap;
#[derive(Debug)]
#[repr(transparent)]
struct StringRef<T: IdBimapItem>(Rc<T>);
#[derive(Debug)]
#[repr(transparent)]
struct IdRef<T: IdBimapItem>(Rc<T>);
#[derive(Debug, Default)]
pub struct IdBimap<T: IdBimapItem> {
id_to_name: AHashMap<IdRef<T>, Rc<T>>,
name_to_id: AHashMap<StringRef<T>, Rc<T>>,
}
impl<T: IdBimapItem> IdBimap<T> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
id_to_name: AHashMap::with_capacity(capacity),
name_to_id: AHashMap::with_capacity(capacity),
}
}
pub fn insert(&mut self, item: T) {
let item = Rc::new(item);
self.id_to_name.insert(IdRef(item.clone()), item.clone());
self.name_to_id.insert(StringRef(item.clone()), item);
}
pub fn by_name(&self, name: &str) -> Option<&T> {
self.name_to_id.get(name).map(|v| v.as_ref())
}
pub fn by_id(&self, id: u32) -> Option<&T> {
self.id_to_name.get(&id).map(|v| v.as_ref())
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.name_to_id.values().map(|v| v.as_ref())
}
pub fn is_empty(&self) -> bool {
self.name_to_id.is_empty()
}
}
// SAFETY: Safe because Rc<> are never returned from the struct
unsafe impl<T: IdBimapItem> Send for IdBimap<T> {}
unsafe impl<T: IdBimapItem> Sync for IdBimap<T> {}
pub trait IdBimapItem: std::fmt::Debug {
fn id(&self) -> &u32;
fn name(&self) -> &str;
}
impl<T: IdBimapItem> Borrow<str> for StringRef<T> {
fn borrow(&self) -> &str {
self.0.name()
}
}
impl<T: IdBimapItem> Borrow<u32> for IdRef<T> {
fn borrow(&self) -> &u32 {
self.0.id()
}
}
impl<T: IdBimapItem> PartialEq for StringRef<T> {
fn eq(&self, other: &Self) -> bool {
self.0.name() == other.0.name()
}
}
impl<T: IdBimapItem> Eq for StringRef<T> {}
impl<T: IdBimapItem> PartialEq for IdRef<T> {
fn eq(&self, other: &Self) -> bool {
self.0.id() == other.0.id()
}
}
impl<T: IdBimapItem> Eq for IdRef<T> {}
impl<T: IdBimapItem> Hash for StringRef<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.name().hash(state)
}
}
impl<T: IdBimapItem> Hash for IdRef<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.id().hash(state)
}
}
+420
View File
@@ -0,0 +1,420 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use arcstr::ArcStr;
use mail_auth::{DnssecStatus, MX, RecordSet, ResolverCache, Txt};
use quick_cache::{
Equivalent, Options, OptionsBuilder, Weighter,
sync::{DefaultLifecycle, PlaceholderGuard},
};
use std::{
borrow::Borrow,
hash::Hash,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::Arc,
time::{Duration, Instant},
};
pub struct Cache<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight>(
quick_cache::sync::Cache<K, V, CacheItemWeighter, ahash::RandomState>,
);
pub struct CacheWithTtl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight>(
quick_cache::sync::Cache<K, TtlEntry<V>, CacheItemWeighter, ahash::RandomState>,
);
#[derive(Clone)]
pub struct TtlEntry<V: Clone + CacheItemWeight> {
value: V,
expires: Instant,
}
impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> Cache<K, V> {
pub fn new(weight: u64, estimated_weight: u64) -> Self {
Self::new_estimated(weight as usize / estimated_weight as usize, weight)
}
pub fn new_estimated(estimated_items_capacity: usize, weight_capacity: u64) -> Self {
Self(quick_cache::sync::Cache::with_options(
cache_options(estimated_items_capacity, weight_capacity, None),
CacheItemWeighter,
ahash::RandomState::default(),
DefaultLifecycle::default(),
))
}
pub fn new_single_shard(weight: u64, estimated_weight: u64) -> Self {
Self(quick_cache::sync::Cache::with_options(
cache_options(weight as usize / estimated_weight as usize, weight, Some(1)),
CacheItemWeighter,
ahash::RandomState::default(),
DefaultLifecycle::default(),
))
}
#[inline(always)]
pub fn get<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.0.get(key)
}
#[inline(always)]
pub fn peek<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.0.peek(key)
}
#[inline(always)]
pub async fn get_value_or_guard_async<'a, Q>(
&'a self,
key: &Q,
) -> Result<
V,
PlaceholderGuard<'a, K, V, CacheItemWeighter, ahash::RandomState, DefaultLifecycle<K, V>>,
>
where
Q: Hash + Equivalent<K> + ToOwned<Owned = K> + ?Sized,
{
self.0.get_value_or_guard_async(key).await
}
#[inline(always)]
pub fn insert(&self, key: K, value: V) {
self.0.insert(key, value);
}
#[inline(always)]
pub fn update(&self, key: K, value: V) {
if let Err((key, value)) = self.0.replace(key, value, true) {
self.0.insert(key, value);
}
}
#[inline(always)]
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.0.remove(key).map(|(_, v)| v)
}
#[inline(always)]
pub fn clear(&self) {
self.0.clear();
}
#[inline(always)]
pub fn inner(&self) -> &quick_cache::sync::Cache<K, V, CacheItemWeighter, ahash::RandomState> {
&self.0
}
#[inline(always)]
pub fn weight_capacity(&self) -> u64 {
self.0.capacity()
}
}
impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> CacheWithTtl<K, V> {
pub fn new(weight: u64, estimated_weight: u64) -> Self {
Self::new_estimated(weight as usize / estimated_weight as usize, weight)
}
pub fn new_estimated(estimated_items_capacity: usize, weight_capacity: u64) -> Self {
Self(quick_cache::sync::Cache::with_options(
cache_options(estimated_items_capacity, weight_capacity, None),
CacheItemWeighter,
ahash::RandomState::default(),
DefaultLifecycle::default(),
))
}
#[inline(always)]
pub fn get<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.0.get(key).and_then(|v| {
if v.expires > Instant::now() {
Some(v.value)
} else {
self.0.remove(key);
None
}
})
}
#[inline(always)]
pub async fn get_value_or_guard_async<'a, Q>(
&'a self,
key: &Q,
) -> Result<
V,
PlaceholderGuard<
'a,
K,
TtlEntry<V>,
CacheItemWeighter,
ahash::RandomState,
DefaultLifecycle<K, TtlEntry<V>>,
>,
>
where
Q: Hash + Equivalent<K> + ToOwned<Owned = K> + ?Sized,
{
match self.0.get_value_or_guard_async(key).await {
Ok(value) => {
if value.expires > Instant::now() {
Ok(value.value)
} else {
self.0.remove(key);
self.0.get_value_or_guard_async(key).await.map(|v| v.value)
}
}
Err(err) => Err(err),
}
}
#[inline(always)]
pub fn insert(&self, key: K, value: V, expires: Duration) {
self.0.insert(key, TtlEntry::new(value, expires));
}
#[inline(always)]
pub fn insert_with_expiry(&self, key: K, value: V, expires: Instant) {
self.0.insert(key, TtlEntry::with_expiry(value, expires));
}
#[inline(always)]
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.0.remove(key).map(|(_, v)| v.value)
}
#[inline(always)]
pub fn retain(&self, f: impl Fn(&K) -> bool) {
self.0.retain(|key, _| f(key));
}
#[inline(always)]
pub fn clear(&self) {
self.0.clear();
}
}
fn cache_options(
estimated_items_capacity: usize,
weight_capacity: u64,
shards: Option<usize>,
) -> Options {
let mut builder = OptionsBuilder::new();
builder
.estimated_items_capacity(estimated_items_capacity.max(1))
.weight_capacity(weight_capacity);
if let Some(shards) = shards {
builder.shards(shards.max(1));
}
builder.build().unwrap()
}
#[derive(Clone)]
pub struct CacheItemWeighter;
impl<K: CacheItemWeight, V: CacheItemWeight> Weighter<K, V> for CacheItemWeighter {
fn weight(&self, key: &K, val: &V) -> u64 {
key.weight() + val.weight()
}
}
pub trait CacheItemWeight {
fn weight(&self) -> u64;
}
impl<T: Clone + CacheItemWeight> CacheItemWeight for TtlEntry<T> {
fn weight(&self) -> u64 {
self.value.weight() + std::mem::size_of::<Instant>() as u64
}
}
impl<T: Clone + CacheItemWeight> CacheItemWeight for Option<T> {
fn weight(&self) -> u64 {
match self {
Some(v) => v.weight(),
None => std::mem::size_of::<usize>() as u64,
}
}
}
impl<T: CacheItemWeight> CacheItemWeight for Arc<T> {
fn weight(&self) -> u64 {
self.as_ref().weight()
}
}
impl CacheItemWeight for u64 {
fn weight(&self) -> u64 {
std::mem::size_of::<u64>() as u64
}
}
impl CacheItemWeight for String {
fn weight(&self) -> u64 {
self.len() as u64 + std::mem::size_of::<String>() as u64
}
}
impl CacheItemWeight for Box<str> {
fn weight(&self) -> u64 {
self.len() as u64 + std::mem::size_of::<Box<str>>() as u64
}
}
impl<T: CacheItemWeight> CacheItemWeight for Box<[T]> {
fn weight(&self) -> u64 {
std::mem::size_of::<Box<[T]>>() as u64 + self.iter().map(|item| item.weight()).sum::<u64>()
}
}
impl<T: CacheItemWeight> CacheItemWeight for Arc<[T]> {
fn weight(&self) -> u64 {
std::mem::size_of::<Arc<[T]>>() as u64 + self.iter().map(|item| item.weight()).sum::<u64>()
}
}
impl<T: CacheItemWeight> CacheItemWeight for RecordSet<T> {
fn weight(&self) -> u64 {
self.rrset.weight() + std::mem::size_of::<DnssecStatus>() as u64
}
}
impl CacheItemWeight for u32 {
fn weight(&self) -> u64 {
std::mem::size_of::<u32>() as u64
}
}
impl CacheItemWeight for IpAddr {
fn weight(&self) -> u64 {
std::mem::size_of::<IpAddr>() as u64
}
}
impl CacheItemWeight for Ipv4Addr {
fn weight(&self) -> u64 {
std::mem::size_of::<Ipv4Addr>() as u64
}
}
impl CacheItemWeight for Ipv6Addr {
fn weight(&self) -> u64 {
std::mem::size_of::<Ipv6Addr>() as u64
}
}
impl CacheItemWeight for MX {
fn weight(&self) -> u64 {
self.exchanges
.iter()
.map(|e| e.len() as u64 + std::mem::size_of::<Box<str>>() as u64)
.sum::<u64>()
+ std::mem::size_of::<MX>() as u64
}
}
impl CacheItemWeight for Txt {
fn weight(&self) -> u64 {
std::mem::size_of::<Txt>() as u64
}
}
impl CacheItemWeight for bool {
fn weight(&self) -> u64 {
std::mem::size_of::<bool>() as u64
}
}
impl CacheItemWeight for ArcStr {
fn weight(&self) -> u64 {
self.len() as u64 + std::mem::size_of::<ArcStr>() as u64
}
}
impl CacheItemWeight for () {
fn weight(&self) -> u64 {
0
}
}
impl<T: Clone + CacheItemWeight> TtlEntry<T> {
pub fn new(value: T, expires: Duration) -> Self {
Self {
value,
expires: Instant::now() + expires,
}
}
pub fn with_expiry(value: T, expires: Instant) -> Self {
Self { value, expires }
}
}
impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> ResolverCache<K, V>
for CacheWithTtl<K, V>
{
fn get<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
CacheWithTtl::get(self, key)
}
fn remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
CacheWithTtl::remove(self, key)
}
fn insert(&self, key: K, value: V, expires: Instant) {
self.0.insert(key, TtlEntry::with_expiry(value, expires));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_shard_retains_large_entry() {
let capacity = 10_000_000u64;
let cache = Cache::<u32, String>::new_single_shard(capacity, 1000);
assert_eq!(cache.inner().num_shards(), 1);
let value = "x".repeat(9_000_000);
cache.insert(0, value.clone());
assert_eq!(cache.get(&0), Some(value));
}
#[test]
fn sharded_cache_drops_entry_larger_than_a_shard() {
let capacity = 10_000_000u64;
let cache = Cache::<u32, String>::new_estimated(10_000, capacity);
let value = "x".repeat((capacity / 2) as usize);
cache.insert(0, value);
if cache.inner().num_shards() > 1 {
assert_eq!(cache.get(&0), None);
}
}
}
+156
View File
@@ -0,0 +1,156 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Cow, ops::Range};
#[derive(Debug, Clone)]
pub struct ChainedBytes<'x> {
first: &'x [u8],
last: &'x [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SliceRange<'x> {
Single(&'x [u8]),
Split(&'x [u8], &'x [u8]),
None,
}
impl<'x> ChainedBytes<'x> {
pub fn new(first: &'x [u8]) -> Self {
Self { first, last: &[] }
}
pub fn append(&mut self, bytes: &'x [u8]) {
self.last = bytes;
}
pub fn with_last(mut self, bytes: &'x [u8]) -> Self {
self.last = bytes;
self
}
pub fn get(&self, index: Range<usize>) -> Option<Cow<'x, [u8]>> {
let start = index.start;
let end = index.end;
if let Some(bytes) = self.first.get(start..end) {
Some(Cow::Borrowed(bytes))
} else if start >= self.first.len() {
self.last
.get(start - self.first.len()..end - self.first.len())
.map(Cow::Borrowed)
} else if let (Some(first), Some(last)) = (
self.first.get(start..),
self.last.get(..end - self.first.len()),
) {
let mut vec = vec![0u8; first.len() + last.len()];
vec[..first.len()].copy_from_slice(first);
vec[first.len()..].copy_from_slice(last);
Some(Cow::Owned(vec))
} else {
None
}
}
pub fn get_slice_range(&self, index: Range<usize>) -> SliceRange<'x> {
let start = index.start;
let end = index.end;
if let Some(bytes) = self.first.get(start..end) {
SliceRange::Single(bytes)
} else if start >= self.first.len() {
self.last
.get(start - self.first.len()..end - self.first.len())
.map(SliceRange::Single)
.unwrap_or(SliceRange::None)
} else if let (Some(first), Some(last)) = (
self.first.get(start..),
self.last.get(..end - self.first.len()),
) {
SliceRange::Split(first, last)
} else {
SliceRange::None
}
}
pub fn get_full_range(&self) -> SliceRange<'x> {
if self.last.is_empty() {
SliceRange::Single(self.first)
} else {
SliceRange::Split(self.first, self.last)
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = vec![0u8; self.first.len() + self.last.len()];
bytes[..self.first.len()].copy_from_slice(self.first);
bytes[self.first.len()..].copy_from_slice(self.last);
bytes
}
pub fn len(&self) -> usize {
self.first.len() + self.last.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'x> SliceRange<'x> {
pub fn len(&self) -> usize {
match self {
SliceRange::Single(bytes) => bytes.len(),
SliceRange::Split(first, last) => first.len() + last.len(),
SliceRange::None => 0,
}
}
pub fn try_into_bytes(self) -> Option<Cow<'x, [u8]>> {
match self {
SliceRange::Single(bytes) => Some(Cow::Borrowed(bytes)),
SliceRange::Split(first, last) => {
let mut vec = vec![0u8; first.len() + last.len()];
vec[..first.len()].copy_from_slice(first);
vec[first.len()..].copy_from_slice(last);
Some(Cow::Owned(vec))
}
SliceRange::None => None,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn into_pairs(self) -> (&'x [u8], &'x [u8]) {
match self {
SliceRange::Single(bytes) => (bytes, &[][..]),
SliceRange::Split(first, last) => (first, last),
SliceRange::None => (&[][..], &[][..]),
}
}
pub fn is_none(&self) -> bool {
matches!(self, SliceRange::None)
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
}
impl<'x> IntoIterator for SliceRange<'x> {
type Item = &'x u8;
type IntoIter = std::iter::Chain<std::slice::Iter<'x, u8>, std::slice::Iter<'x, u8>>;
fn into_iter(self) -> Self::IntoIter {
let (first, last) = self.into_pairs();
first.iter().chain(last.iter())
}
}
+319
View File
@@ -0,0 +1,319 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use nohash_hasher::IsEnabled;
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt::Debug,
hash::Hash,
str::FromStr,
};
// A hash that can cheekily store small inputs directly without hashing them.
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive,
)]
#[repr(transparent)]
pub struct CheekyHash([u8; HASH_SIZE]);
const HASH_SIZE: usize = std::mem::size_of::<u64>() * 2;
const HASH_PAYLOAD: usize = HASH_SIZE - 1;
pub type CheekyHashSet = HashSet<CheekyHash, nohash_hasher::BuildNoHashHasher<CheekyHash>>;
pub type CheekyHashMap<V> = HashMap<CheekyHash, V, nohash_hasher::BuildNoHashHasher<CheekyHash>>;
pub type CheekyBTreeMap<V> = BTreeMap<CheekyHash, V>;
impl CheekyHash {
pub const HASH_SIZE: usize = HASH_SIZE;
pub const NULL: CheekyHash = CheekyHash([0u8; HASH_SIZE]);
pub const FULL: CheekyHash = CheekyHash([u8::MAX; HASH_SIZE]);
pub fn new(bytes: impl AsRef<[u8]>) -> Self {
let mut hash = [0u8; HASH_SIZE];
let bytes = bytes.as_ref();
if bytes.len() <= HASH_PAYLOAD {
hash[0] = bytes.len() as u8;
hash[1..1 + bytes.len()].copy_from_slice(bytes);
} else {
let h1 = xxhash_rust::xxh3::xxh3_64(bytes).to_be_bytes();
let h2 = farmhash::fingerprint64(bytes).to_be_bytes();
hash[0] = bytes.len().min(u8::MAX as usize) as u8;
hash[1..1 + std::mem::size_of::<u64>()].copy_from_slice(&h1);
hash[1 + std::mem::size_of::<u64>()..]
.copy_from_slice(&h2[..std::mem::size_of::<u64>() - 1]);
}
CheekyHash(hash)
}
pub fn deserialize(bytes: &[u8]) -> Option<Self> {
let len = *bytes.first()?;
let mut hash = [0u8; HASH_SIZE];
let hash_len = 1 + (len as usize).min(HASH_PAYLOAD);
hash[0] = len;
hash[1..hash_len].copy_from_slice(bytes.get(1..hash_len)?);
Some(CheekyHash(hash))
}
#[allow(clippy::len_without_is_empty)]
#[inline(always)]
pub fn len(&self) -> usize {
(self.0[0] as usize).min(HASH_PAYLOAD) + 1
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
&self.0[..self.len()]
}
#[inline(always)]
pub fn as_raw_bytes(&self) -> &[u8; HASH_SIZE] {
&self.0
}
pub fn into_inner(self) -> [u8; HASH_SIZE] {
self.0
}
pub fn payload(&self) -> &[u8] {
let len = self.0[0] as usize;
if len <= HASH_PAYLOAD {
&self.0[1..1 + len]
} else {
&self.0[1..]
}
}
pub fn payload_len(&self) -> u8 {
self.0[0]
}
fn as_u128(&self) -> u128 {
u128::from_be_bytes(self.0)
}
}
impl AsRef<[u8]> for CheekyHash {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl FromStr for CheekyHash {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u128::from_str_radix(s, 16).map(|n| CheekyHash(n.to_be_bytes()))
}
}
impl std::fmt::Display for CheekyHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:032x}", self.as_u128())
}
}
impl Hash for CheekyHash {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let len = self.0[0] as usize;
if len <= HASH_PAYLOAD {
state.write_u64(xxhash_rust::xxh3::xxh3_64(&self.0[1..1 + len]));
} else {
state.write_u64(u64::from_be_bytes(
self.0[1..1 + std::mem::size_of::<u64>()]
.try_into()
.unwrap(),
));
}
}
}
impl IsEnabled for CheekyHash {}
impl ArchivedCheekyHash {
#[inline(always)]
pub fn as_raw_bytes(&self) -> &[u8; HASH_SIZE] {
&self.0
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
let len = self.0[0] as usize;
&self.0[..1 + len.min(HASH_PAYLOAD)]
}
#[inline(always)]
pub fn to_native(&self) -> CheekyHash {
CheekyHash(self.0)
}
}
impl Debug for CheekyHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = self.payload_len();
let payload = self.payload();
let payload_str = if len <= HASH_PAYLOAD as u8 {
std::str::from_utf8(payload).unwrap_or("<non-utf8>")
} else {
"<hashed data>"
};
f.debug_struct("CheekyHash")
.field("length", &len)
.field("bytes", &payload_str)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cheeky_hash_all() {
// Test 1: Empty input
let hash_empty = CheekyHash::new([]);
assert_eq!(
hash_empty.as_bytes()[0],
0,
"Empty input should have length 0"
);
assert_eq!(
hash_empty.as_bytes().len(),
1,
"Empty input should only have length byte"
);
// Test 2: Single byte input
let hash_single = CheekyHash::new([42]);
assert_eq!(
hash_single.as_bytes()[0],
1,
"Single byte should have length 1"
);
assert_eq!(
hash_single.as_bytes()[1],
42,
"Single byte value should be preserved"
);
assert_eq!(hash_single.as_bytes().len(), 2);
// Test 3: Small input (less than HASH_LEN)
let small_data = b"hello";
let hash_small = CheekyHash::new(small_data);
assert_eq!(hash_small.as_bytes()[0], 5, "Length should be 5");
assert_eq!(
&hash_small.as_bytes()[1..6],
small_data,
"Small data should be stored directly"
);
assert_eq!(hash_small.as_bytes().len(), 6);
// Test 4: Input exactly at HASH_PAYLOAD boundary
let boundary_data = vec![1u8; HASH_PAYLOAD - 1];
let hash_boundary = CheekyHash::new(&boundary_data);
assert_eq!(
hash_boundary.as_bytes()[0],
(HASH_PAYLOAD - 1) as u8,
"Length should be HASH_LEN"
);
assert_eq!(
&hash_boundary.as_bytes()[1..],
&boundary_data[..],
"Boundary data should be stored directly"
);
// Test 5: Large input (greater than HASH_LEN) - uses hashing
let large_data = vec![7u8; HASH_SIZE];
let hash_large = CheekyHash::new(&large_data);
assert_eq!(
hash_large.as_bytes()[0],
HASH_SIZE as u8,
"Large data should have length byte set to HASH_LEN"
);
assert_eq!(
hash_large.as_bytes().len(),
HASH_SIZE,
"Large data hash should be full length"
);
// Verify it's actually hashed (not raw data)
assert_ne!(
&hash_large.as_bytes()[1..],
&large_data[..HASH_PAYLOAD],
"Large data should be hashed, not stored directly"
);
// Test 6: AsRef<[u8]> trait
let hash = CheekyHash::new(b"test");
let bytes_ref: &[u8] = hash.as_ref();
assert_eq!(bytes_ref, hash.as_bytes(), "AsRef should match as_bytes");
// Test 7: Copy, Clone, PartialEq traits
let hash1 = CheekyHash::new(b"identical");
let hash2 = hash1; // Copy
assert_eq!(hash1, hash2, "Copied hashes should be equal");
// Test 8: Different inputs produce different hashes
let hash_a = CheekyHash::new(b"abc");
let hash_b = CheekyHash::new(b"def");
assert_ne!(
hash_a, hash_b,
"Different inputs should produce different hashes"
);
// Test 9: Same input produces same hash (deterministic)
let hash_x1 = CheekyHash::new(b"deterministic");
let hash_x2 = CheekyHash::new(b"deterministic");
assert_eq!(
hash_x1, hash_x2,
"Same input should produce identical hashes"
);
// Test 10: Large inputs with different content produce different hashes
let large1 = vec![1u8; 100];
let large2 = vec![2u8; 100];
let hash_large1 = CheekyHash::new(&large1);
let hash_large2 = CheekyHash::new(&large2);
assert_ne!(
hash_large1, hash_large2,
"Different large inputs should produce different hashes"
);
// Test 11: Hash trait (can be used in HashMap/HashSet)
use std::collections::HashMap;
let mut map = HashMap::new();
let key = CheekyHash::new(b"key");
map.insert(key, "value");
assert_eq!(
map.get(&key),
Some(&"value"),
"CheekyHash should work as HashMap key"
);
// Test 12: Debug trait
let hash = CheekyHash::new(b"debug");
let debug_str = format!("{:?}", hash);
assert!(
debug_str.contains("CheekyHash"),
"Debug output should contain type name"
);
// Test 13: CheekyHashSet and CheekyHashMap
let mut cheeky_set: CheekyHashSet = CheekyHashSet::default();
cheeky_set.insert(CheekyHash::new(b"set_item"));
assert!(cheeky_set.contains(&CheekyHash::new(b"set_item")));
let mut cheeky_map: CheekyHashMap<&str> = CheekyHashMap::default();
cheeky_map.insert(CheekyHash::new(b"map_key"), "map_value");
assert_eq!(
cheeky_map.get(&CheekyHash::new(b"map_key")),
Some(&"map_value")
);
println!("All CheekyHash tests passed!");
}
}
+221
View File
@@ -0,0 +1,221 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::leb128::{Leb128Iterator, Leb128Writer};
use std::{io::Write, slice::Iter};
pub static BASE32_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz792013";
pub static BASE32_INVERSE: [u8; 256] = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 29, 30, 28, 31, 255, 255, 255, 26, 255, 27,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
];
pub struct Base32Writer {
last_byte: u8,
pos: usize,
result: String,
}
impl Base32Writer {
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
let mut writer = Base32Writer::with_capacity(bytes.len());
writer.write_all(bytes).unwrap();
writer
}
pub fn with_capacity(capacity: usize) -> Self {
Self::with_raw_capacity(capacity.div_ceil(4) * 5)
}
pub fn with_raw_capacity(capacity: usize) -> Self {
Base32Writer {
result: String::with_capacity(capacity),
last_byte: 0,
pos: 0,
}
}
pub fn push_char(&mut self, ch: char) {
self.result.push(ch);
}
pub fn push_string(&mut self, string: &str) {
self.result.push_str(string);
}
fn push_byte(&mut self, byte: u8, is_remainder: bool) {
let (ch1, ch2) = match self.pos % 5 {
0 => ((byte & 0xF8) >> 3, u8::MAX),
1 => (
(((self.last_byte & 0x07) << 2) | ((byte & 0xC0) >> 6)),
((byte & 0x3E) >> 1),
),
2 => (
(((self.last_byte & 0x01) << 4) | ((byte & 0xF0) >> 4)),
u8::MAX,
),
3 => (
(((self.last_byte & 0x0F) << 1) | (byte >> 7)),
((byte & 0x7C) >> 2),
),
4 => (
(((self.last_byte & 0x03) << 3) | ((byte & 0xE0) >> 5)),
(byte & 0x1F),
),
_ => unreachable!(),
};
self.result.push(char::from(BASE32_ALPHABET[ch1 as usize]));
if !is_remainder {
if ch2 != u8::MAX {
self.result.push(char::from(BASE32_ALPHABET[ch2 as usize]));
}
self.last_byte = byte;
self.pos += 1;
}
}
pub fn finalize(mut self) -> String {
if !self.pos.is_multiple_of(5) {
self.push_byte(0, true);
}
self.result
}
}
impl std::io::Write for Base32Writer {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
let start_pos = self.pos;
for &byte in bytes {
self.push_byte(byte, false);
}
Ok(self.pos - start_pos)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct Base32Reader<'x> {
bytes: Iter<'x, u8>,
last_byte: u8,
pos: usize,
}
impl<'x> Base32Reader<'x> {
pub fn new(bytes: &'x [u8]) -> Self {
Base32Reader {
bytes: bytes.iter(),
pos: 0,
last_byte: 0,
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_iter(bytes: Iter<'x, u8>) -> Self {
Base32Reader {
bytes,
pos: 0,
last_byte: 0,
}
}
#[inline(always)]
fn map_byte(&mut self) -> Option<u8> {
match self.bytes.next() {
Some(&byte) => match BASE32_INVERSE[byte as usize] {
byte if byte != u8::MAX => {
self.last_byte = byte;
Some(byte)
}
_ => None,
},
_ => None,
}
}
}
impl Iterator for Base32Reader<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
let pos = self.pos % 5;
let last_byte = self.last_byte;
let byte = self.map_byte()?;
self.pos += 1;
match pos {
0 => ((byte << 3) | (self.map_byte().unwrap_or(0) >> 2)).into(),
1 => ((last_byte << 6) | (byte << 1) | (self.map_byte().unwrap_or(0) >> 4)).into(),
2 => ((last_byte << 4) | (byte >> 1)).into(),
3 => ((last_byte << 7) | (byte << 2) | (self.map_byte().unwrap_or(0) >> 3)).into(),
4 => ((last_byte << 5) | byte).into(),
_ => None,
}
}
}
impl Leb128Iterator<u8> for Base32Reader<'_> {}
impl Leb128Writer for Base32Writer {}
#[cfg(test)]
mod tests {
use std::io::Write;
use crate::codec::base32_custom::{Base32Reader, Base32Writer};
#[test]
fn base32_roundtrip() {
let mut bytes = Vec::with_capacity(100);
for byte in 0..100 {
bytes.push((100 - byte) as u8);
let mut writer = Base32Writer::with_capacity(10);
writer.write_all(&bytes).unwrap();
let result = writer.finalize();
let mut bytes_result = Vec::new();
for byte in Base32Reader::new(result.as_bytes()) {
bytes_result.push(byte);
}
assert_eq!(bytes, bytes_result);
}
for bytes in [
vec![0],
vec![32, 43, 55, 99, 43, 55],
vec![84, 4, 43, 77, 62, 55, 92],
vec![84, 4, 43, 77, 62, 55, 92],
] {
let mut writer = Base32Writer::with_capacity(10);
writer.write_all(&bytes).unwrap();
let result = writer.finalize();
let mut bytes_result = Vec::new();
for byte in Base32Reader::new(result.as_bytes()) {
bytes_result.push(byte);
}
assert_eq!(bytes, bytes_result);
}
}
}
+189
View File
@@ -0,0 +1,189 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#![allow(dead_code)]
use std::{borrow::Borrow, io::Write};
pub trait Leb128_ {
fn to_leb128_writer(self, out: &mut impl Write) -> std::io::Result<usize>;
fn to_leb128_bytes(self, out: &mut Vec<u8>);
fn from_leb128_bytes_pos(slice: &[u8]) -> Option<(Self, usize)>
where
Self: std::marker::Sized;
fn from_leb128_bytes(slice: &[u8]) -> Option<Self>
where
Self: std::marker::Sized;
fn from_leb128_it<T, I>(it: T) -> Option<Self>
where
Self: std::marker::Sized,
T: Iterator<Item = I>,
I: Borrow<u8>;
}
pub trait Leb128Vec<T: Leb128_> {
fn push_leb128(&mut self, value: T);
}
pub trait Leb128Writer: Write + Sized {
#[inline(always)]
fn write_leb128<T: Leb128_>(&mut self, value: T) -> std::io::Result<usize> {
T::to_leb128_writer(value, self)
}
}
impl<T: Leb128_> Leb128Vec<T> for Vec<u8> {
#[inline(always)]
fn push_leb128(&mut self, value: T) {
T::to_leb128_bytes(value, self);
}
}
pub trait Leb128Iterator<I>: Iterator<Item = I>
where
I: Borrow<u8>,
{
#[inline(always)]
fn next_leb128<T: Leb128_>(&mut self) -> Option<T> {
T::from_leb128_it(self)
}
#[inline(always)]
fn skip_leb128(&mut self) -> Option<()> {
for byte in self {
if (byte.borrow() & 0x80) == 0 {
return Some(());
}
}
None
}
}
pub trait Leb128Reader: AsRef<[u8]> {
#[inline(always)]
fn read_leb128<T: Leb128_>(&self) -> Option<(T, usize)> {
T::from_leb128_bytes_pos(self.as_ref())
}
#[inline(always)]
fn skip_leb128(&self) -> Option<usize> {
for (pos, byte) in self.as_ref().iter().enumerate() {
if (byte & 0x80) == 0 {
return (pos + 1).into();
}
}
None
}
}
impl Leb128Reader for &[u8] {}
impl Leb128Reader for Vec<u8> {}
impl Leb128Reader for Box<[u8]> {}
impl<'x> Leb128Iterator<&'x u8> for std::slice::Iter<'x, u8> {}
// Based on leb128.rs from rustc
macro_rules! impl_unsigned_leb128 {
($int_ty:ident, $shifts:expr) => {
impl Leb128_ for $int_ty {
#[inline(always)]
fn to_leb128_writer(self, out: &mut impl Write) -> std::io::Result<usize> {
let mut value = self;
let mut bytes_written = 0;
loop {
if value < 0x80 {
bytes_written += out.write(&[value as u8])?;
break;
} else {
bytes_written += out.write(&[((value & 0x7f) | 0x80) as u8])?;
value >>= 7;
}
}
Ok(bytes_written)
}
#[inline(always)]
fn to_leb128_bytes(self, out: &mut Vec<u8>) {
let mut value = self;
loop {
if value < 0x80 {
out.push(value as u8);
break;
} else {
out.push(((value & 0x7f) | 0x80) as u8);
value >>= 7;
}
}
}
#[inline(always)]
fn from_leb128_bytes_pos(slice: &[u8]) -> Option<($int_ty, usize)> {
let mut result = 0;
for (shift, (pos, &byte)) in $shifts.into_iter().zip(slice.iter().enumerate()) {
if (byte & 0x80) == 0 {
result |= (byte as $int_ty) << shift;
return Some((result, pos + 1));
} else {
result |= ((byte & 0x7F) as $int_ty) << shift;
}
}
None
}
#[inline(always)]
fn from_leb128_bytes(slice: &[u8]) -> Option<$int_ty> {
let mut result = 0;
for (shift, &byte) in $shifts.into_iter().zip(slice.iter()) {
if (byte & 0x80) == 0 {
result |= (byte as $int_ty) << shift;
return Some(result);
} else {
result |= ((byte & 0x7F) as $int_ty) << shift;
}
}
None
}
#[inline(always)]
fn from_leb128_it<T, I>(it: T) -> Option<$int_ty>
where
T: Iterator<Item = I>,
I: Borrow<u8>,
{
let mut result = 0;
for (shift, byte_) in $shifts.into_iter().zip(it) {
let byte = byte_.borrow();
if (byte & 0x80) == 0 {
result |= (*byte as $int_ty) << shift;
return Some(result);
} else {
result |= ((byte & 0x7F) as $int_ty) << shift;
}
}
None
}
}
};
}
impl_unsigned_leb128!(u8, [0]);
impl_unsigned_leb128!(u16, [0, 7, 14]);
impl_unsigned_leb128!(u32, [0, 7, 14, 21, 28]);
impl_unsigned_leb128!(u64, [0, 7, 14, 21, 28, 35, 42, 49, 56, 63]);
impl_unsigned_leb128!(usize, [0, 7, 14, 21, 28, 35, 42, 49, 56, 63]);
impl Leb128Writer for Vec<u8> {
#[inline(always)]
fn write_leb128<T: Leb128_>(&mut self, value: T) -> std::io::Result<usize> {
T::to_leb128_writer(value, self)
}
}
+8
View File
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod base32_custom;
pub mod leb128;
+138
View File
@@ -0,0 +1,138 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use chrono::{Datelike, Local, TimeDelta, TimeZone, Timelike};
use std::{str::FromStr, time::Duration};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimpleCron {
Day { hour: u32, minute: u32 },
Week { day: u32, hour: u32, minute: u32 },
Hour { minute: u32 },
}
impl SimpleCron {
pub fn time_to_next(&self) -> Duration {
let now = Local::now();
let next = match self {
SimpleCron::Day { hour, minute } => {
let next = Local
.with_ymd_and_hms(now.year(), now.month(), now.day(), *hour, *minute, 0)
.earliest()
.unwrap_or_else(|| now - TimeDelta::try_seconds(1).unwrap_or_default());
if next <= now {
next + TimeDelta::try_days(1).unwrap_or_default()
} else {
next
}
}
SimpleCron::Week { day, hour, minute } => {
let next = Local
.with_ymd_and_hms(now.year(), now.month(), now.day(), *hour, *minute, 0)
.earliest()
.unwrap_or_else(|| now - TimeDelta::try_seconds(1).unwrap_or_default());
if next <= now {
next + TimeDelta::try_days(
(7 - now.weekday().number_from_monday() + *day).into(),
)
.unwrap_or_default()
} else {
next
}
}
SimpleCron::Hour { minute } => {
let next = Local
.with_ymd_and_hms(now.year(), now.month(), now.day(), now.hour(), *minute, 0)
.earliest()
.unwrap_or_else(|| now - TimeDelta::try_seconds(1).unwrap_or_default());
if next <= now {
next + TimeDelta::try_hours(1).unwrap_or_default()
} else {
next
}
}
};
(next - now).to_std().unwrap_or_else(|_| self.as_duration())
}
pub fn as_duration(&self) -> Duration {
match self {
SimpleCron::Day { .. } => Duration::from_secs(24 * 60 * 60),
SimpleCron::Week { .. } => Duration::from_secs(7 * 24 * 60 * 60),
SimpleCron::Hour { .. } => Duration::from_secs(60 * 60),
}
}
}
impl FromStr for SimpleCron {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut hour = 0;
let mut minute = 0;
for (pos, value) in value.split(' ').enumerate() {
if pos == 0 {
minute = value
.parse::<u32>()
.map_err(|_| "Invalid cron key: failed to parse cron minute".to_string())?;
if !(0..=59).contains(&minute) {
return Err(format!(
"Invalid cron key: failed to parse minute, invalid value: {minute}"
));
}
} else if pos == 1 {
if value
.as_bytes()
.first()
.ok_or_else(|| "Invalid cron key: failed to parse cron hour".to_string())?
== &b'*'
{
return Ok(SimpleCron::Hour { minute });
} else {
hour = value
.parse::<u32>()
.map_err(|_| "Invalid cron key: failed to parse cron hour".to_string())?;
if !(0..=23).contains(&hour) {
return Err(format!(
"Invalid cron key: failed to parse hour, invalid value: {hour}"
));
}
}
} else if pos == 2 {
if value
.as_bytes()
.first()
.ok_or_else(|| "Invalid cron key: failed to parse cron weekday".to_string())?
== &b'*'
{
return Ok(SimpleCron::Day { hour, minute });
} else {
let day = value.parse::<u32>().map_err(|_| {
"Invalid cron key: failed to parse cron weekday".to_string()
})?;
if !(1..=7).contains(&hour) {
return Err(format!(
"Invalid cron key: failed to parse weekday, invalid value: {}, range is 1 (Monday) to 7 (Sunday).",
hour,
));
}
return Ok(SimpleCron::Week { day, hour, minute });
}
}
}
Err("Invalid cron key: parse cron expression.".to_string())
}
}
impl Default for SimpleCron {
fn default() -> Self {
SimpleCron::Hour { minute: 0 }
}
}
+264
View File
@@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use ahash::{AHashMap, AHashSet};
use serde::Deserialize;
use std::borrow::Cow;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MatchType {
Equal(String),
StartsWith(String),
EndsWith(String),
Matches(GlobPattern),
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobPattern {
pattern: Vec<PatternChar>,
to_lower: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatternChar {
WildcardMany { num: usize, match_pos: usize },
WildcardSingle { match_pos: usize },
Char { char: char, match_pos: usize },
}
impl GlobPattern {
pub fn compile(pattern: &str, to_lower: bool) -> Self {
let mut chars = Vec::new();
let mut is_escaped = false;
let mut str = pattern.chars().peekable();
while let Some(char) = str.next() {
match char {
'*' if !is_escaped => {
let mut num = 1;
while let Some('*') = str.peek() {
num += 1;
str.next();
}
chars.push(PatternChar::WildcardMany { num, match_pos: 0 });
}
'?' if !is_escaped => {
chars.push(PatternChar::WildcardSingle { match_pos: 0 });
}
'\\' if !is_escaped => {
is_escaped = true;
continue;
}
_ => {
if is_escaped {
is_escaped = false;
}
if to_lower && char.is_uppercase() {
for char in char.to_lowercase() {
chars.push(PatternChar::Char { char, match_pos: 0 });
}
} else {
chars.push(PatternChar::Char { char, match_pos: 0 });
}
}
}
}
GlobPattern {
pattern: chars,
to_lower,
}
}
pub fn try_compile(pattern: &str, to_lower: bool) -> Result<Self, String> {
// Detect if the key is a glob pattern
let mut last_ch = '\0';
let mut has_escape = false;
let mut is_glob = false;
for ch in pattern.chars() {
match ch {
'\\' => {
has_escape = true;
}
'*' | '?' if last_ch != '\\' => {
is_glob = true;
}
_ => {}
}
last_ch = ch;
}
if is_glob {
Ok(GlobPattern::compile(pattern, to_lower))
} else {
Err(if has_escape {
pattern.replace('\\', "")
} else {
pattern.to_string()
})
}
}
// Credits: Algorithm ported from https://research.swtch.com/glob
pub fn matches(&self, value: &str) -> bool {
let value = if self.to_lower {
value.to_lowercase().chars().collect::<Vec<_>>()
} else {
value.chars().collect::<Vec<_>>()
};
let mut px = 0;
let mut nx = 0;
let mut next_px = 0;
let mut next_nx = 0;
while px < self.pattern.len() || nx < value.len() {
match self.pattern.get(px) {
Some(PatternChar::Char { char, .. }) => {
if matches!(value.get(nx), Some(nc) if nc == char ) {
px += 1;
nx += 1;
continue;
}
}
Some(PatternChar::WildcardSingle { .. }) if nx < value.len() => {
px += 1;
nx += 1;
continue;
}
Some(PatternChar::WildcardMany { .. }) => {
next_px = px;
next_nx = nx + 1;
px += 1;
continue;
}
_ => (),
}
if 0 < next_nx && next_nx <= value.len() {
px = next_px;
nx = next_nx;
continue;
}
return false;
}
true
}
}
#[derive(Debug, Clone, Default)]
pub struct GlobSet {
entries: AHashSet<String>,
patterns: Vec<GlobPattern>,
}
#[derive(Debug, Clone)]
pub struct GlobMap<V> {
entries: AHashMap<String, V>,
patterns: Vec<(GlobPattern, V)>,
}
impl GlobSet {
pub fn new() -> Self {
GlobSet::default()
}
pub fn insert_pattern(&mut self, pattern: &str) {
match GlobPattern::try_compile(pattern, false) {
Ok(glob) => {
self.patterns.push(glob);
}
Err(entry) => {
self.entries.insert(entry);
}
}
}
pub fn insert_entry(&mut self, entry: String) {
self.entries.insert(entry);
}
pub fn contains(&self, key: &str) -> bool {
self.entries.contains(key) || self.patterns.iter().any(|pattern| pattern.matches(key))
}
}
impl<V> GlobMap<V> {
pub fn new() -> Self {
GlobMap {
entries: AHashMap::new(),
patterns: Vec::new(),
}
}
pub fn insert_pattern(&mut self, pattern: &str, value: V) {
match GlobPattern::try_compile(pattern, false) {
Ok(glob) => {
self.patterns.push((glob, value));
}
Err(entry) => {
self.entries.insert(entry, value);
}
}
}
pub fn insert_entry(&mut self, entry: String, value: V) {
self.entries.insert(entry, value);
}
pub fn get(&self, key: &str) -> Option<&V> {
self.entries.get(key).or_else(|| {
self.patterns
.iter()
.find_map(|(pattern, value)| pattern.matches(key).then_some(value))
})
}
}
impl<V> Default for GlobMap<V> {
fn default() -> Self {
GlobMap::new()
}
}
impl MatchType {
pub fn parse(value: &str) -> Self {
if value == "*" {
MatchType::All
} else if let Some(value) = value.strip_suffix('*') {
MatchType::StartsWith(value.to_string())
} else if let Some(value) = value.strip_prefix('*') {
MatchType::EndsWith(value.to_string())
} else if value.contains('*') {
MatchType::Matches(GlobPattern::compile(value, false))
} else {
MatchType::Equal(value.to_string())
}
}
pub fn matches(&self, value: &str) -> bool {
match self {
MatchType::Equal(pattern) => value == pattern,
MatchType::StartsWith(pattern) => value.starts_with(pattern),
MatchType::EndsWith(pattern) => value.ends_with(pattern),
MatchType::Matches(pattern) => pattern.matches(value),
MatchType::All => true,
}
}
}
impl<'de> Deserialize<'de> for GlobPattern {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(GlobPattern::compile(
<Cow<&str>>::deserialize(deserializer)?.as_ref(),
true,
))
}
}
+203
View File
@@ -0,0 +1,203 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use base64::{Engine, engine::general_purpose};
use reqwest::{
Client, ClientBuilder,
header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, USER_AGENT},
};
use rustls::{
ClientConfig, DigitallySignedStruct, Error as TlsError, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::{CryptoProvider, aws_lc_rs},
};
use rustls_pki_types::{CertificateDer, ServerName, UnixTime};
use std::{
str::FromStr,
sync::{Arc, LazyLock},
time::Duration,
};
struct SharedTlsConfigs {
strict: ClientConfig,
strict_http1: ClientConfig,
insecure: ClientConfig,
insecure_http1: ClientConfig,
}
#[derive(Debug)]
struct NoCertificateVerification(Arc<CryptoProvider>);
impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
static SHARED_TLS_CONFIGS: LazyLock<SharedTlsConfigs> = LazyLock::new(|| {
let provider = Arc::new(aws_lc_rs::default_provider());
let verifier = rustls_platform_verifier::Verifier::new(provider.clone())
.expect("Failed to load the platform certificate verifier");
let mut strict = ClientConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.expect("Failed to build the TLS client configuration")
.dangerous()
.with_custom_certificate_verifier(Arc::new(verifier))
.with_no_client_auth();
strict.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let mut insecure = ClientConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.expect("Failed to build the TLS client configuration")
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification(provider)))
.with_no_client_auth();
insecure.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let mut strict_http1 = strict.clone();
strict_http1.alpn_protocols = vec![b"http/1.1".to_vec()];
let mut insecure_http1 = insecure.clone();
insecure_http1.alpn_protocols = vec![b"http/1.1".to_vec()];
SharedTlsConfigs {
strict,
strict_http1,
insecure,
insecure_http1,
}
});
pub fn init_shared_tls_configs() {
LazyLock::force(&SHARED_TLS_CONFIGS);
}
pub fn shared_tls_config(allow_invalid_certs: bool) -> ClientConfig {
if allow_invalid_certs {
SHARED_TLS_CONFIGS.insecure.clone()
} else {
SHARED_TLS_CONFIGS.strict.clone()
}
}
pub fn http_client_builder(allow_invalid_certs: bool) -> ClientBuilder {
Client::builder().use_preconfigured_tls(shared_tls_config(allow_invalid_certs))
}
pub fn http1_client_builder(allow_invalid_certs: bool) -> ClientBuilder {
let tls = if allow_invalid_certs {
SHARED_TLS_CONFIGS.insecure_http1.clone()
} else {
SHARED_TLS_CONFIGS.strict_http1.clone()
};
Client::builder().http1_only().use_preconfigured_tls(tls)
}
pub fn unpooled_http_client(allow_invalid_certs: bool) -> Client {
http_client_builder(allow_invalid_certs)
.pool_max_idle_per_host(0)
.build()
.unwrap_or_default()
}
pub fn build_http_client(
raw_headers: impl IntoIterator<Item = (String, String)>,
username: Option<&str>,
password: Option<&str>,
token: Option<&str>,
content_type: Option<&str>,
timeout: Duration,
allow_invalid_certs: bool,
) -> Result<Client, String> {
let mut headers = build_http_headers(raw_headers, username, password, token, content_type)?;
headers.insert(USER_AGENT, "Stalwart/1.0.0".parse().unwrap());
match http_client_builder(allow_invalid_certs)
.connect_timeout(timeout)
.default_headers(headers)
.build()
{
Ok(client) => Ok(client),
Err(err) => Err(format!("Failed to build HTTP client: {}", err)),
}
}
pub fn build_http_headers(
raw_headers: impl IntoIterator<Item = (String, String)>,
username: Option<&str>,
password: Option<&str>,
token: Option<&str>,
content_type: Option<&str>,
) -> Result<HeaderMap, String> {
let mut headers = HeaderMap::new();
if let Some(content_type) = content_type {
headers.insert(CONTENT_TYPE, HeaderValue::from_str(content_type).unwrap());
}
for (header, value) in raw_headers
.into_iter()
.map(|(k, v)| {
Ok((
HeaderName::from_str(k.trim())
.map_err(|err| format!("Invalid header {k:?}: {err}",))?,
HeaderValue::from_str(v.trim())
.map_err(|err| format!("Invalid value {v:?}: {err}",))?,
))
})
.collect::<Result<Vec<(HeaderName, HeaderValue)>, String>>()?
{
headers.insert(header, value);
}
if let (Some(name), Some(secret)) = (username, password) {
headers.insert(
AUTHORIZATION,
format!(
"Basic {}",
general_purpose::STANDARD.encode(format!("{}:{}", name, secret))
)
.parse()
.unwrap(),
);
} else if let Some(token) = token {
headers.insert(AUTHORIZATION, format!("Bearer {}", token).parse().unwrap());
}
Ok(headers)
}
+536
View File
@@ -0,0 +1,536 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#![warn(clippy::large_futures)]
pub mod bimap;
pub mod cache;
pub mod chained_bytes;
pub mod cheeky_hash;
pub mod codec;
pub mod cron;
pub mod glob;
pub mod http;
pub mod map;
pub mod snowflake;
pub mod template;
pub mod tls;
pub mod topological;
pub mod url_params;
use compact_str::ToCompactString;
use futures::StreamExt;
pub use reqwest::Client;
use reqwest::Response;
pub use reqwest::header::HeaderMap;
use std::borrow::Cow;
use std::fmt::Write;
pub trait HttpLimitResponse: Sync + Send {
fn bytes_with_limit(
self,
limit: usize,
) -> impl std::future::Future<Output = reqwest::Result<Option<Vec<u8>>>> + Send;
}
impl HttpLimitResponse for Response {
async fn bytes_with_limit(self, limit: usize) -> reqwest::Result<Option<Vec<u8>>> {
if self
.content_length()
.is_some_and(|len| len as usize > limit)
{
return Ok(None);
}
let mut bytes = Vec::with_capacity(std::cmp::min(limit, 1024));
let mut stream = self.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
if bytes.len() + chunk.len() > limit {
return Ok(None);
}
bytes.extend_from_slice(&chunk);
}
Ok(Some(bytes))
}
}
pub trait UnwrapFailure<T> {
fn failed(self, action: &str) -> T;
}
impl<T> UnwrapFailure<T> for Option<T> {
fn failed(self, message: &str) -> T {
match self {
Some(result) => result,
None => {
trc::event!(
Server(trc::ServerEvent::StartupError),
Details = message.to_compact_string()
);
eprintln!("{message}");
std::process::exit(1);
}
}
}
}
impl<T, E: std::fmt::Display> UnwrapFailure<T> for Result<T, E> {
fn failed(self, message: &str) -> T {
match self {
Ok(result) => result,
Err(err) => {
trc::event!(
Server(trc::ServerEvent::StartupError),
Details = message.to_compact_string(),
Reason = err.to_compact_string()
);
#[cfg(feature = "test_mode")]
panic!("{message}: {err}");
#[cfg(not(feature = "test_mode"))]
{
eprintln!("{message}: {err}");
std::process::exit(1);
}
}
}
}
}
pub fn failed(message: &str) -> ! {
trc::event!(
Server(trc::ServerEvent::StartupError),
Details = message.to_compact_string(),
);
eprintln!("{message}");
std::process::exit(1);
}
pub async fn wait_for_shutdown() {
#[cfg(not(target_env = "msvc"))]
let signal = {
use tokio::signal::unix::{SignalKind, signal};
let mut h_term = signal(SignalKind::terminate()).failed("start signal handler");
let mut h_int = signal(SignalKind::interrupt()).failed("start signal handler");
tokio::select! {
_ = h_term.recv() => "SIGTERM",
_ = h_int.recv() => "SIGINT",
}
};
#[cfg(target_env = "msvc")]
let signal = {
match tokio::signal::ctrl_c().await {
Ok(()) => "SIGINT",
Err(err) => {
trc::event!(
Server(trc::ServerEvent::ThreadError),
Details = "Unable to listen for shutdown signal",
Reason = err.to_string(),
);
"Error"
}
}
};
trc::event!(Server(trc::ServerEvent::Shutdown), CausedBy = signal);
}
pub trait DomainPart {
fn to_lowercase_address(&self, lower_local: bool) -> String;
fn to_canonical_address(&self) -> Cow<'_, str>;
fn domain_part(&self) -> &str;
fn try_domain_part(&self) -> Option<&str>;
fn try_local_part(&self) -> Option<&str>;
fn to_ascii_domain(&self) -> Option<Cow<'_, str>>;
}
impl<T: AsRef<str>> DomainPart for T {
fn to_lowercase_address(&self, lower_local: bool) -> String {
let address = self.as_ref();
if let Some((local, domain)) = address.rsplit_once('@') {
let mut address = String::with_capacity(address.len());
if lower_local {
for ch in local.chars() {
for ch in ch.to_lowercase() {
address.push(ch);
}
}
} else {
address.push_str(local);
}
address.push('@');
if domain.is_ascii() {
for ch in domain.chars() {
for ch in ch.to_lowercase() {
address.push(ch);
}
}
} else {
let domain =
idna::domain_to_ascii(domain).unwrap_or_else(|_| domain.to_lowercase());
address.push_str(&domain);
}
address
} else {
address.to_lowercase()
}
}
fn to_canonical_address(&self) -> Cow<'_, str> {
let address = self.as_ref();
if address
.bytes()
.any(|ch| !ch.is_ascii() || ch.is_ascii_uppercase())
{
Cow::Owned(address.to_lowercase_address(true))
} else {
Cow::Borrowed(address)
}
}
#[inline(always)]
fn try_domain_part(&self) -> Option<&str> {
self.as_ref().rsplit_once('@').map(|(_, d)| d)
}
#[inline(always)]
fn try_local_part(&self) -> Option<&str> {
self.as_ref().rsplit_once('@').map(|(l, _)| l)
}
#[inline(always)]
fn domain_part(&self) -> &str {
self.as_ref()
.rsplit_once('@')
.map(|(_, d)| d)
.unwrap_or_default()
}
#[inline(always)]
fn to_ascii_domain(&self) -> Option<Cow<'_, str>> {
let domain = self.as_ref();
if !domain.is_ascii() {
idna::domain_to_ascii(domain).ok().map(Cow::Owned)
} else if domain.bytes().any(|ch| ch.is_ascii_uppercase()) {
Some(Cow::Owned(domain.to_ascii_lowercase()))
} else {
Some(Cow::Borrowed(domain))
}
}
}
pub trait HexEncode {
fn hex_encode(&self) -> String;
}
impl<T: AsRef<[u8]>> HexEncode for T {
fn hex_encode(&self) -> String {
let bytes = self.as_ref();
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
let _ = write!(&mut s, "{b:02x}");
}
s
}
}
static NIL_CHAR: char = char::from_u32(0).unwrap();
// Basic email sanitizer
pub fn sanitize_email(email: &str) -> Option<String> {
let mut result = String::with_capacity(email.len());
let mut last_ch = NIL_CHAR;
let mut chars = email.chars();
for ch in chars.by_ref() {
match ch {
'.' => {
if last_ch == NIL_CHAR || last_ch == '.' {
return None;
}
result.push('.');
}
'!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' | '_'
| '`' | '{' | '|' | '}' | '~' => {
result.push(ch);
}
' ' | '\x09'..='\x0d' => continue,
'@' => {
if result.is_empty() || last_ch == '.' {
return None;
}
last_ch = ch;
result.push(ch);
break;
}
_ => {
if ch.is_uppercase() {
for ch in ch.to_lowercase() {
result.push(ch);
}
} else if ch.is_alphanumeric() {
result.push(ch);
} else {
return None;
}
}
}
last_ch = ch;
}
if last_ch != '@' {
return None;
}
last_ch = NIL_CHAR;
let domain_start = result.len();
let mut domain_is_ascii = true;
for ch in chars {
match ch {
'.' => {
if !last_ch.is_alphanumeric() {
return None;
}
result.push('.');
}
'-' | '_' => {
if last_ch == NIL_CHAR || last_ch == '.' {
return None;
}
result.push(ch);
}
' ' | '\x09'..='\x0d' => continue,
_ => {
if !ch.is_ascii() {
domain_is_ascii = false;
}
if ch.is_uppercase() {
for ch in ch.to_lowercase() {
result.push(ch);
}
} else if ch.is_alphanumeric() {
result.push(ch);
} else {
return None;
}
}
}
last_ch = ch;
}
if !last_ch.is_alphanumeric() {
return None;
}
if domain_is_ascii {
is_valid_domain(&result[domain_start..]).then_some(result)
} else {
let domain = idna::domain_to_ascii(&result[domain_start..]).ok()?;
if !is_valid_domain(&domain) {
return None;
}
result.truncate(domain_start);
result.push_str(&domain);
Some(result)
}
}
pub fn sanitize_email_local(local: &str) -> Option<String> {
let mut result = String::with_capacity(local.len());
let mut last_ch = NIL_CHAR;
for ch in local.chars() {
match ch {
'.' => {
if last_ch == NIL_CHAR || last_ch == '.' {
return None;
}
result.push('.');
}
'!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' | '_'
| '`' | '{' | '|' | '}' | '~' => {
result.push(ch);
}
' ' | '\x09'..='\x0d' => continue,
_ => {
if ch.is_uppercase() {
for ch in ch.to_lowercase() {
result.push(ch);
}
} else if ch.is_alphanumeric() {
result.push(ch);
} else {
return None;
}
}
}
last_ch = ch;
}
if !result.is_empty() && last_ch != '.' {
Some(result)
} else {
None
}
}
pub fn sanitize_domain(domain: &str) -> Option<String> {
let mut result = String::with_capacity(domain.len());
let mut found_dot = false;
let mut last_ch = char::from(0);
let mut is_ascii = true;
for ch in domain.chars() {
if !ch.is_whitespace() {
if ch == '.' {
found_dot = true;
if !(last_ch.is_alphanumeric() || last_ch == '-' || last_ch == '_') {
return None;
}
} else if !ch.is_ascii() {
is_ascii = false;
}
last_ch = ch;
for ch in ch.to_lowercase() {
result.push(ch);
}
}
}
if !(found_dot && last_ch != '.') {
return None;
}
if is_ascii {
is_valid_domain(&result).then_some(result)
} else {
let domain = idna::domain_to_ascii(&result).ok()?;
is_valid_domain(&domain).then_some(domain)
}
}
pub fn is_valid_domain(domain: &str) -> bool {
const RESERVED_TLDS: &[&str] = &[
"test",
"localhost",
"local",
"internal",
"lan",
"home",
"corp",
"intranet",
"private",
"localdomain",
];
(domain.contains('.') && psl::suffix(domain.as_bytes()).is_some_and(|s| s.typ().is_some()))
|| RESERVED_TLDS.contains(&domain)
|| domain
.rsplit_once('.')
.is_some_and(|(_, tld)| RESERVED_TLDS.contains(&tld))
}
#[cfg(test)]
mod tests {
use crate::DomainPart;
use super::{sanitize_domain, sanitize_email};
#[test]
fn idn_domains_canonicalize_to_a_label() {
assert_eq!(
sanitize_domain("straß6.de").as_deref(),
Some("xn--stra6-oqa.de")
);
assert_eq!(
sanitize_domain("STRASS.straß6.DE").as_deref(),
Some("strass.xn--stra6-oqa.de")
);
assert_eq!(
sanitize_domain("münchen.de").as_deref(),
Some("xn--mnchen-3ya.de")
);
}
#[test]
fn a_label_and_ascii_domains_are_idempotent() {
assert_eq!(
sanitize_domain("xn--stra6-oqa.de").as_deref(),
Some("xn--stra6-oqa.de")
);
assert_eq!(
sanitize_domain(&sanitize_domain("straß6.de").unwrap()).as_deref(),
Some("xn--stra6-oqa.de")
);
assert_eq!(
sanitize_domain("Example.COM").as_deref(),
Some("example.com")
);
}
#[test]
fn email_domain_part_canonicalizes_local_part_preserved() {
assert_eq!(
sanitize_email("cornelius_strauss@straß6.de").as_deref(),
Some("[email protected]")
);
assert_eq!(
sanitize_email("Foo.Bar@münchen.de").as_deref(),
Some("[email protected]")
);
assert_eq!(
sanitize_email("[email protected]").as_deref(),
Some("[email protected]")
);
}
#[test]
fn bare_public_suffix_domains_are_accepted() {
assert_eq!(
sanitize_email("[email protected]").as_deref(),
Some("[email protected]")
);
assert_eq!(sanitize_email("[email protected]").as_deref(), Some("[email protected]"));
assert_eq!(sanitize_email("user@com"), None);
assert_eq!(sanitize_email("[email protected]"), None);
}
#[test]
fn a_label_email_domains_are_accepted_and_idempotent() {
assert_eq!(
sanitize_email("[email protected]").as_deref(),
Some("[email protected]")
);
assert_eq!(
sanitize_email("User@例子.com").as_deref(),
sanitize_email("[email protected]").as_deref()
);
}
#[test]
fn to_ascii_domain_borrows_ascii_owns_idn() {
assert!(matches!(
"example.com".to_ascii_domain(),
Some(std::borrow::Cow::Borrowed(_))
));
assert!(matches!(
"straß6.de".to_ascii_domain(),
Some(std::borrow::Cow::Owned(_))
));
}
}
+316
View File
@@ -0,0 +1,316 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::ops::Deref;
#[derive(
Debug,
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
serde::Serialize,
serde::Deserialize,
Clone,
Copy,
PartialOrd,
Ord,
PartialEq,
Eq,
Hash,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
#[repr(transparent)]
pub struct Bitmap<T: BitmapItem> {
pub bitmap: u64,
#[serde(skip)]
#[rkyv(omit_bounds)]
_state: std::marker::PhantomData<T>,
}
pub trait BitmapItem: From<u64> + Into<u64> + Sized + Copy {
fn max() -> u64;
fn is_valid(&self) -> bool;
}
pub trait BitPop {
fn bit_push(&mut self, item: u8);
fn bit_pop(&mut self) -> Option<u8>;
}
impl<T: BitmapItem> Bitmap<T> {
pub fn new() -> Self {
Self::default()
}
#[inline(always)]
pub fn all() -> Self {
Self {
bitmap: u64::MAX >> (64 - T::max()),
_state: std::marker::PhantomData,
}
}
#[inline(always)]
pub fn union(&mut self, items: &Bitmap<T>) {
self.bitmap |= items.bitmap;
}
#[inline(always)]
pub fn union_raw(&mut self, items: impl Into<u64>) {
self.bitmap |= items.into();
}
#[inline(always)]
pub fn intersection(&mut self, items: &Bitmap<T>) {
self.bitmap &= items.bitmap;
}
#[inline(always)]
pub fn insert(&mut self, item: T) {
debug_assert!(item.is_valid());
self.bitmap |= 1 << item.into();
}
pub fn insert_many(&mut self, items: impl IntoIterator<Item = T>) {
for item in items.into_iter() {
self.insert(item);
}
}
pub fn remove_many(&mut self, items: impl IntoIterator<Item = T>) {
for item in items.into_iter() {
debug_assert!(item.is_valid());
self.bitmap &= !(1 << item.into());
}
}
#[inline(always)]
pub fn with_item(mut self, item: T) -> Self {
self.insert(item);
self
}
#[inline(always)]
pub fn remove(&mut self, item: T) {
debug_assert!(item.is_valid());
self.bitmap ^= 1 << item.into();
}
#[inline(always)]
pub fn pop(&mut self) -> Option<T> {
if self.bitmap != 0 {
let item = 63 - self.bitmap.leading_zeros();
self.bitmap ^= 1 << item;
Some((item as u64).into())
} else {
None
}
}
#[inline(always)]
pub fn contains(&self, item: T) -> bool {
self.bitmap & (1 << item.into()) != 0
}
#[inline(always)]
pub fn contains_any(&self, items: impl Iterator<Item = T>) -> bool {
for item in items {
if self.bitmap & (1 << item.into()) != 0 {
return true;
}
}
false
}
#[inline(always)]
pub fn contains_all(&self, items: impl Iterator<Item = T>) -> bool {
if !self.is_empty() {
for item in items {
if self.bitmap & (1 << item.into()) == 0 {
return false;
}
}
true
} else {
false
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.bitmap == 0
}
#[inline(always)]
pub fn clear(&mut self) -> Self {
let bitmap = self.bitmap;
self.bitmap = 0;
Bitmap {
bitmap,
_state: std::marker::PhantomData,
}
}
pub fn into_inner(self) -> u64 {
self.bitmap
}
}
impl BitPop for u32 {
fn bit_push(&mut self, item: u8) {
*self |= 1 << item;
}
fn bit_pop(&mut self) -> Option<u8> {
if *self != 0 {
let item = 31 - self.leading_zeros();
*self ^= 1 << item;
Some(item as u8)
} else {
None
}
}
}
impl BitPop for u64 {
fn bit_push(&mut self, item: u8) {
*self |= 1 << item;
}
fn bit_pop(&mut self) -> Option<u8> {
if *self != 0 {
let item = 63 - self.leading_zeros();
*self ^= 1 << item;
Some(item as u8)
} else {
None
}
}
}
impl<T: BitmapItem> From<ArchivedBitmap<T>> for Bitmap<T> {
fn from(value: ArchivedBitmap<T>) -> Self {
Self {
bitmap: value.bitmap.into(),
_state: std::marker::PhantomData,
}
}
}
impl<T: BitmapItem> From<&ArchivedBitmap<T>> for Bitmap<T> {
fn from(value: &ArchivedBitmap<T>) -> Self {
Self {
bitmap: value.bitmap.into(),
_state: std::marker::PhantomData,
}
}
}
impl<T: BitmapItem> From<u64> for Bitmap<T> {
fn from(value: u64) -> Self {
Self {
bitmap: value,
_state: std::marker::PhantomData,
}
}
}
impl<T: BitmapItem> AsRef<u64> for Bitmap<T> {
fn as_ref(&self) -> &u64 {
&self.bitmap
}
}
impl<T: BitmapItem> Deref for Bitmap<T> {
type Target = u64;
fn deref(&self) -> &Self::Target {
&self.bitmap
}
}
impl<T: BitmapItem> From<Bitmap<T>> for u64 {
fn from(value: Bitmap<T>) -> Self {
value.bitmap
}
}
impl<T: BitmapItem> Iterator for Bitmap<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.bitmap != 0 {
let item = 63 - self.bitmap.leading_zeros();
self.bitmap ^= 1 << item;
Some((item as u64).into())
} else {
None
}
}
}
impl<T: BitmapItem> From<Vec<T>> for Bitmap<T> {
fn from(values: Vec<T>) -> Self {
let mut bitmap = Bitmap::default();
for value in values {
if value.is_valid() {
bitmap.insert(value);
}
}
bitmap
}
}
impl<T: BitmapItem> FromIterator<T> for Bitmap<T> {
fn from_iter<U: IntoIterator<Item = T>>(iter: U) -> Self {
let mut bitmap = Bitmap::new();
for value in iter {
if value.is_valid() {
bitmap.insert(value);
}
}
bitmap
}
}
impl<T: BitmapItem> From<&Vec<T>> for Bitmap<T> {
fn from(values: &Vec<T>) -> Self {
let mut bitmap = Bitmap::default();
for value in values {
if value.is_valid() {
bitmap.insert(*value);
}
}
bitmap
}
}
impl<T: BitmapItem> From<T> for Bitmap<T> {
fn from(value: T) -> Self {
let mut bitmap = Bitmap::default();
bitmap.insert(value);
bitmap
}
}
impl<T: BitmapItem> From<Bitmap<T>> for Vec<T> {
fn from(values: Bitmap<T>) -> Self {
let mut list = Vec::new();
for item in values {
list.push(item);
}
list
}
}
impl<T: BitmapItem> Default for Bitmap<T> {
fn default() -> Self {
Bitmap {
bitmap: 0,
_state: std::marker::PhantomData,
}
}
}
+9
View File
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod bitmap;
pub mod mutex_map;
pub mod vec_map;
+71
View File
@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use core::hash::Hash;
use std::hash::Hasher;
use ahash::AHasher;
use tokio::sync::{Mutex, MutexGuard};
pub struct MutexMap<T: Default> {
map: Box<[Mutex<T>]>,
mask: u64,
hasher: AHasher,
}
pub struct MutexMapLockError;
pub type Result<T> = std::result::Result<T, MutexMapLockError>;
#[allow(clippy::mutex_atomic)]
impl<T: Default> MutexMap<T> {
pub fn with_capacity(size: usize) -> MutexMap<T> {
let size = size.next_power_of_two();
MutexMap {
map: (0..size)
.map(|_| T::default().into())
.collect::<Vec<Mutex<T>>>()
.into_boxed_slice(),
mask: (size - 1) as u64,
hasher: AHasher::default(),
}
}
pub async fn lock<U>(&self, key: U) -> MutexGuard<'_, T>
where
U: Into<u64> + Copy,
{
let hash = key.into() & self.mask;
self.map[hash as usize].lock().await
}
/*pub async fn try_lock<U>(&self, key: U, timeout: Duration) -> Option<MutexGuard<'_, T>>
where
U: Into<u64> + Copy,
{
let hash = key.into() & self.mask;
self.map[hash as usize].try_lock(timeout).await
}*/
pub async fn lock_hash<U>(&self, key: U) -> MutexGuard<'_, T>
where
U: Hash,
{
let mut hasher = self.hasher.clone();
key.hash(&mut hasher);
let hash = hasher.finish() & self.mask;
self.map[hash as usize].lock().await
}
/*pub async fn try_lock_hash<U>(&self, key: U, timeout: Duration) -> Option<MutexGuard<'_, T>>
where
U: Hash,
{
let mut hasher = self.hasher.clone();
key.hash(&mut hasher);
let hash = hasher.finish() & self.mask;
self.map[hash as usize].try_lock_for(timeout).await
}*/
}
+396
View File
@@ -0,0 +1,396 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use rkyv::Archive;
use serde::{Deserialize, Serialize, ser::SerializeMap};
use std::{borrow::Borrow, cmp::Ordering, fmt, hash::Hash};
// A map implemented using vectors
// used for small datasets of less than 20 items
// and when deserializing from JSON
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
pub struct VecMap<K: Eq + PartialEq, V> {
pub inner: Vec<KeyValue<K, V>>,
}
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyValue<K: Eq + PartialEq, V> {
pub key: K,
pub value: V,
}
impl<K: Eq + PartialEq, V> Default for VecMap<K, V> {
fn default() -> Self {
VecMap { inner: Vec::new() }
}
}
impl<K: Eq + PartialEq, V> VecMap<K, V> {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: Vec::with_capacity(capacity),
}
}
#[inline(always)]
pub fn set(&mut self, key: impl Into<K>, value: V) -> bool {
let key = key.into();
if let Some(kv) = self.inner.iter_mut().find(|kv| kv.key == key) {
kv.value = value;
false
} else {
self.inner.push(KeyValue { key, value });
true
}
}
#[inline(always)]
pub fn append(&mut self, key: impl Into<K>, value: V) {
self.inner.push(KeyValue {
key: key.into(),
value,
});
}
#[inline(always)]
pub fn with_append(mut self, key: impl Into<K>, value: V) -> Self {
self.append(key, value);
self
}
#[inline(always)]
pub fn insert(&mut self, idx: usize, key: impl Into<K>, value: V) {
self.inner.insert(
idx,
KeyValue {
key: key.into(),
value,
},
);
}
#[inline(always)]
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q> + PartialEq<Q>,
{
self.inner.iter().find_map(|kv| {
if &kv.key == key {
Some(&kv.value)
} else {
None
}
})
}
#[inline(always)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.inner.iter_mut().find_map(|kv| {
if &kv.key == key {
Some(&mut kv.value)
} else {
None
}
})
}
#[inline(always)]
pub fn contains_key(&self, key: &K) -> bool {
self.inner.iter().any(|kv| kv.key == *key)
}
#[inline(always)]
pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q> + PartialEq<Q>,
{
self.inner
.iter()
.position(|kv| kv.key == *key)
.map(|pos| self.inner.remove(pos).value)
}
#[inline(always)]
pub fn remove_all(&mut self, key: &K) {
self.inner.retain(|kv| kv.key != *key);
}
#[inline(always)]
pub fn remove_entry(&mut self, key: &K) -> Option<(K, V)> {
self.inner.iter().position(|k| &k.key == key).map(|pos| {
let kv = self.inner.remove(pos);
(kv.key, kv.value)
})
}
#[inline(always)]
pub fn swap_remove(&mut self, index: usize) -> V {
self.inner.swap_remove(index).value
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline(always)]
pub fn clear(&mut self) {
self.inner.clear();
}
#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.inner.iter().map(|kv| (&kv.key, &kv.value))
}
#[inline(always)]
pub fn iter_by_key<'x, 'y: 'x>(&'x self, key: &'y K) -> impl Iterator<Item = &'x V> + 'x {
self.inner.iter().filter_map(move |kv| {
if &kv.key == key {
Some(&kv.value)
} else {
None
}
})
}
#[inline(always)]
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut K, &mut V)> {
self.inner.iter_mut().map(|kv| (&mut kv.key, &mut kv.value))
}
#[inline(always)]
pub fn iter_mut_by_key<'x, 'y: 'x>(
&'x mut self,
key: &'y K,
) -> impl Iterator<Item = &'x mut V> + 'x {
self.inner.iter_mut().filter_map(move |kv| {
if &kv.key == key {
Some(&mut kv.value)
} else {
None
}
})
}
#[inline(always)]
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.inner.iter().map(|kv| &kv.key)
}
#[inline(always)]
pub fn values(&self) -> impl Iterator<Item = &V> {
self.inner.iter().map(|kv| &kv.value)
}
#[inline(always)]
pub fn last(&self) -> Option<(&K, &V)> {
self.inner.last().map(|kv| (&kv.key, &kv.value))
}
#[inline(always)]
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.inner.iter_mut().map(|kv| &mut kv.value)
}
pub fn get_mut_or_insert_with(&mut self, key: K, fnc: impl FnOnce() -> V) -> &mut V {
if let Some(pos) = self.inner.iter().position(|kv| kv.key == key) {
&mut self.inner[pos].value
} else {
self.inner.push(KeyValue { key, value: fnc() });
&mut self.inner.last_mut().unwrap().value
}
}
pub fn with_key_value(mut self, key: K, value: V) -> Self {
self.append(key, value);
self
}
pub fn sort_unstable(&mut self)
where
K: Ord,
V: Ord,
{
self.inner.sort_unstable_by(|a, b| match a.key.cmp(&b.key) {
Ordering::Equal => a.value.cmp(&b.value),
cmp => cmp,
});
}
pub fn sort_unstable_by_key(&mut self)
where
K: Ord,
{
self.inner.sort_unstable_by(|a, b| a.key.cmp(&b.key));
}
pub fn extend(&mut self, iter: impl IntoIterator<Item = (K, V)>) {
for (k, v) in iter {
self.append(k, v);
}
}
pub fn drain(&mut self) -> impl Iterator<Item = (K, V)> + '_ {
self.inner.drain(..).map(|kv| (kv.key, kv.value))
}
pub fn into_values(self) -> impl Iterator<Item = V> {
self.inner.into_iter().map(|kv| kv.value)
}
pub fn into_keys(self) -> impl Iterator<Item = K> {
self.inner.into_iter().map(|kv| kv.key)
}
}
impl<K: Eq + PartialEq, V: Default> VecMap<K, V> {
pub fn get_mut_or_insert(&mut self, key: K) -> &mut V {
if let Some(pos) = self.inner.iter().position(|kv| kv.key == key) {
&mut self.inner[pos].value
} else {
self.inner.push(KeyValue {
key,
value: V::default(),
});
&mut self.inner.last_mut().unwrap().value
}
}
}
impl<K: Archive + Eq + PartialEq, V: Archive> ArchivedVecMap<K, V> {
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline(always)]
pub fn iter(
&self,
) -> impl Iterator<
Item = (
&<K as rkyv::Archive>::Archived,
&<V as rkyv::Archive>::Archived,
),
> {
self.inner.iter().map(|kv| (&kv.key, &kv.value))
}
}
impl<K: Eq + PartialEq, V> IntoIterator for VecMap<K, V> {
type Item = (K, V);
type IntoIter =
std::iter::Map<std::vec::IntoIter<KeyValue<K, V>>, fn(KeyValue<K, V>) -> (K, V)>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter().map(|kv| (kv.key, kv.value))
}
}
impl<'x, K: Eq + PartialEq, V> IntoIterator for &'x VecMap<K, V> {
type Item = (&'x K, &'x V);
type IntoIter = std::iter::Map<
std::slice::Iter<'x, KeyValue<K, V>>,
fn(&'x KeyValue<K, V>) -> (&'x K, &'x V),
>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter().map(|kv| (&kv.key, &kv.value))
}
}
impl<K, V> Hash for VecMap<K, V>
where
K: Eq + PartialEq + Hash,
V: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
impl<K: Eq + PartialEq, V> FromIterator<(K, V)> for VecMap<K, V> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (K, V)>,
{
let iter = iter.into_iter();
let mut map = VecMap::with_capacity(iter.size_hint().0);
for (k, v) in iter {
map.append(k, v);
}
map
}
}
struct VecMapVisitor<K, V> {
phantom: std::marker::PhantomData<(K, V)>,
}
impl<'de, K: Eq + PartialEq + Deserialize<'de>, V: Deserialize<'de>> serde::de::Visitor<'de>
for VecMapVisitor<K, V>
{
type Value = VecMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
// Duplicates are not checked during deserialization
let mut vec_map = VecMap::new();
while let Some(key) = map.next_key::<K>()? {
vec_map.append(key, map.next_value()?);
}
Ok(vec_map)
}
}
impl<'de, K: Eq + PartialEq + Deserialize<'de>, V: Deserialize<'de>> Deserialize<'de>
for VecMap<K, V>
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(VecMapVisitor {
phantom: std::marker::PhantomData,
})
}
}
impl<K: Eq + PartialEq + Serialize, V: Serialize> Serialize for VecMap<K, V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(self.len().into())?;
for (key, value) in self {
map.serialize_entry(key, value)?
}
map.end()
}
}
+140
View File
@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{
sync::atomic::{AtomicU64, Ordering},
time::{Duration, SystemTime},
};
#[derive(Debug)]
pub struct SnowflakeIdGenerator {
epoch: SystemTime,
sequence: AtomicU64,
}
const SEQUENCE_LEN: u64 = 12;
const NODE_ID_LEN: u64 = 9;
const SEQUENCE_MASK: u64 = (1 << SEQUENCE_LEN) - 1;
const NODE_ID_MASK: u64 = (1 << NODE_ID_LEN) - 1;
pub const MAX_NODE_ID: u16 = NODE_ID_MASK as u16;
const DEFAULT_EPOCH: u64 = 1632280000; // 52 years after UNIX_EPOCH
static mut NODE_ID: u64 = 1;
static SEQUENCE_ID: AtomicU64 = AtomicU64::new(0);
/*
ID characteristics:
- 43 bits for milliseconds since January 1st, 2022: 2^43 / (1000 * 60 * 60 * 24 * 365) = 278.92 years (from year 2022 until 2300)
- 9 bits for a node id: 2^9 = 512 nodes
- 12 bits for a sequence number: 2^12 = 4096 ids per millisecond
*/
#[inline(always)]
fn node_id() -> u64 {
unsafe { std::ptr::read_volatile(&raw const NODE_ID) }
}
impl SnowflakeIdGenerator {
pub fn new() -> Self {
Self {
epoch: SystemTime::UNIX_EPOCH + Duration::from_secs(DEFAULT_EPOCH), // 52 years after UNIX_EPOCH
sequence: 0.into(),
}
}
pub fn set_node_id(set_node_id: u64) {
let set_node_id = set_node_id & NODE_ID_MASK;
if set_node_id != node_id() {
unsafe {
NODE_ID = set_node_id;
}
}
}
pub fn from_duration(period: Duration) -> Option<u64> {
(SystemTime::UNIX_EPOCH + Duration::from_secs(DEFAULT_EPOCH))
.elapsed()
.ok()
.map(|elapsed| {
(elapsed.saturating_sub(period).as_millis() as u64) << (SEQUENCE_LEN + NODE_ID_LEN)
})
}
pub fn from_timestamp(timestamp: u64) -> Option<u64> {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.and_then(|now| now.as_secs().checked_sub(timestamp))
.and_then(|diff| Self::from_duration(Duration::from_secs(diff)))
}
pub fn global_id_from_timestamp(timestamp: u64) -> Option<u64> {
let sequence = SEQUENCE_ID.fetch_add(1, Ordering::Relaxed) & SEQUENCE_MASK;
Self::from_timestamp(timestamp).map(|id| id | (sequence << NODE_ID_LEN) | node_id())
}
pub fn global_id() -> Option<u64> {
let sequence = SEQUENCE_ID.fetch_add(1, Ordering::Relaxed) & SEQUENCE_MASK;
(SystemTime::UNIX_EPOCH + Duration::from_secs(DEFAULT_EPOCH))
.elapsed()
.ok()
.map(|elapsed| {
((elapsed.as_millis() as u64) << (SEQUENCE_LEN + NODE_ID_LEN))
| (sequence << NODE_ID_LEN)
| node_id()
})
}
pub fn to_timestamp(id: u64) -> u64 {
(id >> (SEQUENCE_LEN + NODE_ID_LEN)) / 1000 + DEFAULT_EPOCH
}
#[inline(always)]
pub fn past_id(&self, period: Duration) -> Option<u64> {
self.epoch.elapsed().ok().map(|elapsed| {
(elapsed.saturating_sub(period).as_millis() as u64) << (SEQUENCE_LEN + NODE_ID_LEN)
})
}
pub fn is_valid(&self) -> bool {
self.epoch.elapsed().is_ok()
}
#[inline(always)]
pub fn generate(&self) -> u64 {
let elapsed = self
.epoch
.elapsed()
.map(|e| e.as_millis())
.unwrap_or_default() as u64;
let sequence = self.sequence.fetch_add(1, Ordering::Relaxed) & SEQUENCE_MASK;
(elapsed << (SEQUENCE_LEN + NODE_ID_LEN)) | (sequence << NODE_ID_LEN) | node_id()
}
}
impl Default for SnowflakeIdGenerator {
fn default() -> Self {
Self::new()
}
}
impl Clone for SnowflakeIdGenerator {
fn clone(&self) -> Self {
Self {
epoch: self.epoch,
sequence: 0.into(),
}
}
}
+618
View File
@@ -0,0 +1,618 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use ahash::AHashMap;
use std::{hash::Hash, str::FromStr};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Template<T> {
pub items: Vec<TemplateItem<T>>,
pub size: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplateItem<T> {
Static(String),
Variable { name: T, escape: bool },
If { variable: T, block_end: usize },
ForEach { variable: T, block_end: usize },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Variable<T: Eq + Hash, V: AsRef<str>> {
Single(V),
Block(Vec<AHashMap<T, V>>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variables<T: Eq + Hash, V: AsRef<str>> {
pub items: AHashMap<T, Variable<T, V>>,
}
impl<T: FromStr + Eq + Hash + std::fmt::Debug> Template<T> {
pub fn parse(mut template: &str) -> Result<Self, String> {
let mut items = Vec::new();
let mut block_stack = vec![];
let mut size = 0;
loop {
if let Some((start, end)) = template.split_once("{{") {
if !start.is_empty() {
items.push(TemplateItem::Static(start.to_string()));
size += start.len();
}
let (var, rest) = end.split_once("}}").ok_or("Unmatched {{")?;
template = rest;
let var = var.trim();
if let Some(var_name) = var.strip_prefix("#").map(|v| v.trim()) {
let (is_each, var_name) = if let Some(each) = var_name.strip_prefix("each ") {
(true, each)
} else if let Some(if_cond) = var_name.strip_prefix("if ") {
(false, if_cond)
} else {
return Err(format!("Invalid block start: {}", var_name));
};
let var = T::from_str(var_name)
.map_err(|_| format!("Invalid variable: {}", var_name))?;
block_stack.push((var_name, items.len()));
if is_each {
items.push(TemplateItem::ForEach {
variable: var,
block_end: 0,
});
} else {
items.push(TemplateItem::If {
variable: var,
block_end: 0,
});
}
} else if let Some(var_name) = var.strip_prefix("/").map(|v| v.trim()) {
let (is_each, var_name) = if let Some(each) = var_name.strip_prefix("each ") {
(true, each)
} else if let Some(if_cond) = var_name.strip_prefix("if ") {
(false, if_cond)
} else {
return Err(format!("Invalid block end: {}", var_name));
};
if let Some((expected_name, if_pos)) = block_stack.pop() {
if expected_name != var_name {
return Err(format!(
"Block end does not match start: expected {}, got {}",
expected_name, var_name
));
}
let block_end_idx = items.len();
match &mut items[if_pos] {
TemplateItem::If { block_end, .. } if !is_each => {
*block_end = block_end_idx;
}
TemplateItem::ForEach { block_end, .. } if is_each => {
*block_end = block_end_idx;
}
_ => {
return Err(format!(
"Block end does not match start type for {}",
var_name
));
}
}
}
} else {
let (name, escape) = var.strip_prefix("!").map_or((var, true), |v| (v, false));
let name =
T::from_str(name).map_err(|_| format!("Invalid variable: {}", name))?;
items.push(TemplateItem::Variable { name, escape });
}
} else {
if !template.is_empty() {
items.push(TemplateItem::Static(template.to_string()));
size += template.len();
}
break;
}
}
if block_stack.is_empty() {
Ok(Template { items, size })
} else {
Err(format!("Unmatched {{: {}", block_stack.last().unwrap().0))
}
}
pub fn eval<V>(&self, variables: &Variables<T, V>) -> String
where
V: AsRef<str>,
{
let mut result = String::with_capacity(self.size);
let mut items = self.items.iter().enumerate();
let mut base_offset = 0;
while let Some((idx, item)) = items.next() {
let idx = idx + base_offset;
match item {
TemplateItem::Static(s) => result.push_str(s),
TemplateItem::Variable { name, escape } => {
if let Some(Variable::Single(variable)) = variables.items.get(name) {
if *escape {
html_escape(&mut result, variable.as_ref())
} else {
result.push_str(variable.as_ref());
}
}
}
TemplateItem::If {
variable,
block_end,
} => {
if !variables.items.contains_key(variable) {
items = self.items[*block_end..].iter().enumerate();
base_offset = *block_end;
}
}
TemplateItem::ForEach {
variable,
block_end,
} => {
if let Some(Variable::Block(entries)) = variables.items.get(variable) {
let slice = &self.items[idx + 1..*block_end];
for entry in entries {
let mut slice = slice.iter();
while let Some(sub_item) = slice.next() {
match sub_item {
TemplateItem::Static(s) => result.push_str(s),
TemplateItem::Variable { name, escape } => {
if let Some(variable) = entry.get(name) {
if *escape {
html_escape(&mut result, variable.as_ref())
} else {
result.push_str(variable.as_ref());
}
}
}
TemplateItem::If {
variable,
block_end: start_pos,
} if !entry.contains_key(variable) => {
slice = self.items[*start_pos..*block_end].iter();
}
_ => {}
}
}
}
}
items = self.items[*block_end..].iter().enumerate();
base_offset = *block_end;
}
}
}
result
}
}
fn html_escape(result: &mut String, input: &str) {
for c in input.chars() {
match c {
'&' => result.push_str("&amp;"),
'<' => result.push_str("&lt;"),
'>' => result.push_str("&gt;"),
'"' => result.push_str("&quot;"),
'\'' => result.push_str("&#39;"),
_ => result.push(c),
}
}
}
impl<T: Eq + Hash, V: AsRef<str>> Variables<T, V> {
pub fn new() -> Self {
Self {
items: AHashMap::new(),
}
}
pub fn insert_single(&mut self, key: T, value: V) {
self.items.insert(key, Variable::Single(value));
}
pub fn insert_block<V1, V2>(&mut self, key: T, value: V1)
where
V1: IntoIterator<Item = V2>,
V2: IntoIterator<Item = (T, V)>,
{
self.items.insert(
key,
Variable::Block(value.into_iter().map(AHashMap::from_iter).collect()),
);
}
}
impl<T: Eq + Hash, V: AsRef<str>> Default for Variables<T, V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_variable_substitution() {
let template = Template::parse("Hello {{name}}!").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("name".to_string(), "World".to_string());
let result = template.eval(&vars);
assert_eq!(result, "Hello World!");
}
#[test]
fn test_multiple_variables() {
let template = Template::parse("{{greeting}} {{name}}, today is {{day}}").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("greeting".to_string(), "Hello".to_string());
vars.insert_single("name".to_string(), "Alice".to_string());
vars.insert_single("day".to_string(), "Monday".to_string());
let result = template.eval(&vars);
assert_eq!(result, "Hello Alice, today is Monday");
}
#[test]
fn test_missing_variable() {
let template = Template::parse("Hello {{name}}!").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "Hello !");
}
#[test]
fn test_static_text_only() {
let template = Template::parse("This is just static text").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "This is just static text");
}
#[test]
fn test_empty_template() {
let template = Template::parse("").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "");
}
#[test]
fn test_if_block_with_existing_variable() {
let template =
Template::parse("{{#if show_message}}Hello World!{{/if show_message}}").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("show_message".to_string(), "true".to_string());
let result = template.eval(&vars);
assert_eq!(result, "Hello World!");
}
#[test]
fn test_if_block_with_missing_variable() {
let template =
Template::parse("{{#if show_message}}Hello World!{{/if show_message}}").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "");
}
#[test]
fn test_if_block_with_content_and_variables() {
let template = Template::parse(
"{{#if notifications}}You have notifications: {{count}}{{/if notifications}}",
)
.unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("notifications".to_string(), "true".to_string());
vars.insert_single("count".to_string(), "5".to_string());
let result = template.eval(&vars);
assert_eq!(result, "You have notifications: 5");
}
#[test]
fn test_foreach_block_basic() {
let template = Template::parse("{{#each items}}{{name}} {{/each items}}").unwrap();
let mut vars = Variables::<String, String>::new();
let items = vec![
vec![("name".to_string(), "Item1".to_string())],
vec![("name".to_string(), "Item2".to_string())],
vec![("name".to_string(), "Item3".to_string())],
];
vars.insert_block("items".to_string(), items);
let result = template.eval(&vars);
assert_eq!(result, "Item1 Item2 Item3 ");
}
#[test]
fn test_foreach_block_multiple_variables() {
let template = Template::parse(
"{{#each notifications}}* {{name}} at {{time}}\n{{/each notifications}}",
)
.unwrap();
let mut vars = Variables::<String, String>::new();
let notifications = vec![
vec![
("name".to_string(), "Meeting".to_string()),
("time".to_string(), "10:00".to_string()),
],
vec![
("name".to_string(), "Call".to_string()),
("time".to_string(), "14:30".to_string()),
],
];
vars.insert_block("notifications".to_string(), notifications);
let result = template.eval(&vars);
assert_eq!(result, "* Meeting at 10:00\n* Call at 14:30\n");
}
#[test]
fn test_foreach_block_empty() {
let template = Template::parse("{{#each items}}{{name}}{{/each items}}").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_block("items".to_string(), Vec::<Vec<(String, String)>>::new());
let result = template.eval(&vars);
assert_eq!(result, "");
}
#[test]
fn test_foreach_block_missing_variable() {
let template = Template::parse("{{#each items}}{{name}}{{/each items}}").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "");
}
#[test]
fn test_complex_template_example() {
let template_str = r#"Hello {{name}},
{{#if notifications}}You have the following notifications:
{{#each notifications}}* {{name}} at {{time}}
{{/each notifications}}{{/if notifications}}
Best regards"#;
let template = Template::parse(template_str).unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("name".to_string(), "Alice".to_string());
vars.insert_single("notifications".to_string(), "true".to_string());
let notifications = vec![
vec![
("name".to_string(), "Team Meeting".to_string()),
("time".to_string(), "09:00".to_string()),
],
vec![
("name".to_string(), "Doctor Appointment".to_string()),
("time".to_string(), "15:30".to_string()),
],
];
vars.insert_block("notifications".to_string(), notifications);
let result = template.eval(&vars);
let expected = r#"Hello Alice,
You have the following notifications:
* Team Meeting at 09:00
* Doctor Appointment at 15:30
Best regards"#;
assert_eq!(result, expected);
}
#[test]
fn test_complex_template_no_notifications() {
let template_str = r#"Hello {{name}},
{{#if notifications}}
You have the following notifications:
{{#each notifications}}
* {{name}} at {{time}}
{{/each notifications}}{{/if notifications}}
Best regards"#;
let template = Template::parse(template_str).unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("name".to_string(), "Bob".to_string());
let result = template.eval(&vars);
let expected = r#"Hello Bob,
Best regards"#;
assert_eq!(result, expected);
}
#[test]
fn test_whitespace_handling() {
let template = Template::parse("{{ name }}").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("name".to_string(), "Test".to_string());
let result = template.eval(&vars);
assert_eq!(result, "Test");
}
#[test]
fn test_whitespace_in_blocks() {
let template = Template::parse("{{# if condition }}Content{{/ if condition }}").unwrap();
let mut vars = Variables::<String, String>::new();
vars.insert_single("condition".to_string(), "true".to_string());
let result = template.eval(&vars);
assert_eq!(result, "Content");
}
// Error handling tests
#[test]
fn test_unmatched_opening_brace() {
let result = Template::<String>::parse("Hello {{name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unmatched {{"));
}
#[test]
fn test_invalid_block_start() {
let result = Template::<String>::parse("{{#invalid block}}{{/invalid block}}");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid block start"));
}
#[test]
fn test_invalid_block_end() {
let result = Template::<String>::parse("{{#if test}}{{\\/invalid block}}");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unmatched"));
}
#[test]
fn test_mismatched_block_names() {
let result = Template::<String>::parse("{{#if test}}{{/if different}}");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("Block end does not match start")
);
}
#[test]
fn test_mismatched_block_types() {
let result = Template::<String>::parse("{{#if test}}{{/each test}}");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("Block end does not match start")
);
}
#[test]
fn test_consecutive_braces() {
let template = Template::parse("{{}}").unwrap();
let vars = Variables::<String, String>::new();
let result = template.eval(&vars);
assert_eq!(result, "");
}
#[test]
fn test_foreach_with_missing_inner_variables() {
let template =
Template::parse("{{#each items}}{{name}}: {{missing}}{{/each items}}").unwrap();
let mut vars = Variables::<String, String>::new();
let items = vec![
vec![("name".to_string(), "Item1".to_string())],
vec![("name".to_string(), "Item2".to_string())],
];
vars.insert_block("items".to_string(), items);
let result = template.eval(&vars);
assert_eq!(result, "Item1: Item2: ");
}
/*#[test]
fn test_full() {
// Load static html in memory from resources/email-templates/calendar-alarm.html
let template_str = include_str!("../../../resources/email-templates/calendar-alarm.html");
let template: Template<CalendarTemplateVariable> = Template::parse(template_str).unwrap();
let mut vars = Variables::<CalendarTemplateVariable, String>::new();
vars.insert_single(
CalendarTemplateVariable::PageTitle,
"Test Event".to_string(),
);
vars.insert_single(CalendarTemplateVariable::Header, "Event Header".to_string());
vars.insert_single(CalendarTemplateVariable::Footer, "Event Footer".to_string());
vars.insert_single(
CalendarTemplateVariable::EventTitle,
"Meeting with Team".to_string(),
);
vars.insert_single(
CalendarTemplateVariable::EventDescription,
"Discuss project updates".to_string(),
);
vars.insert_single(
CalendarTemplateVariable::EventDetails,
"Details about the event".to_string(),
);
vars.insert_single(
CalendarTemplateVariable::ActionUrl,
"http://example.com/action".to_string(),
);
vars.insert_single(
CalendarTemplateVariable::ActionName,
"Join Meeting".to_string(),
);
vars.insert_single(
CalendarTemplateVariable::AttendeesTitle,
"Attendees".to_string(),
);
vars.insert_block(
CalendarTemplateVariable::EventDetails,
vec![
vec![
(CalendarTemplateVariable::Key, "Location".to_string()),
(
CalendarTemplateVariable::Value,
"Conference Room A".to_string(),
),
],
vec![
(CalendarTemplateVariable::Key, "Time".to_string()),
(
CalendarTemplateVariable::Value,
"10:00 AM - 11:00 AM".to_string(),
),
],
],
);
vars.insert_block(
CalendarTemplateVariable::Attendees,
vec![
vec![
(CalendarTemplateVariable::Key, "Alice".to_string()),
(
CalendarTemplateVariable::Value,
"[email protected]".to_string(),
),
],
vec![
(CalendarTemplateVariable::Key, "Bob".to_string()),
(
CalendarTemplateVariable::Value,
"[email protected]".to_string(),
),
],
],
);
let result = template.eval(&vars);
// Write result to test.html
std::fs::write("test.html", result).expect("Unable to write file");
}*/
}
+87
View File
@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use rustls::{
ClientConfig, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
};
use rustls_platform_verifier::BuilderVerifierExt;
use std::sync::Arc;
use tokio_rustls::TlsConnector;
pub fn rustls_client_config(allow_invalid_certs: bool) -> Result<ClientConfig, String> {
let config = ClientConfig::builder();
if !allow_invalid_certs {
config
.with_platform_verifier()
.map(|config| config.with_no_client_auth())
.map_err(|err| format!("Failed to build platform verifier: {err}"))
} else {
Ok(config
.dangerous()
.with_custom_certificate_verifier(Arc::new(DummyVerifier {}))
.with_no_client_auth())
}
}
pub fn build_tls_connector(allow_invalid_certs: bool) -> Result<TlsConnector, String> {
rustls_client_config(allow_invalid_certs)
.map(Arc::new)
.map(TlsConnector::from)
}
#[derive(Debug)]
struct DummyVerifier;
impl ServerCertVerifier for DummyVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls_pki_types::CertificateDer<'_>,
_intermediates: &[rustls_pki_types::CertificateDer<'_>],
_server_name: &rustls_pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls_pki_types::UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
+120
View File
@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use ahash::AHashMap;
use std::{collections::VecDeque, hash::Hash};
#[derive(Debug)]
pub struct TopologicalSort<T: Copy + Eq + Hash> {
edges: AHashMap<T, Vec<T>>,
count: AHashMap<T, usize>,
}
impl<T: Copy + Eq + Hash + std::fmt::Debug> TopologicalSort<T> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
edges: AHashMap::with_capacity(capacity),
count: AHashMap::with_capacity(capacity),
}
}
pub fn insert(&mut self, from: T, to: T) {
self.count.entry(from).or_insert(0);
self.edges.entry(from).or_default().push(to);
*self.count.entry(to).or_insert(0) += 1;
}
pub fn into_iterator(mut self) -> TopologicalSortIterator<T> {
let mut no_edges = VecDeque::with_capacity(self.count.len());
self.count.retain(|node, count| {
if *count == 0 {
no_edges.push_back(*node);
false
} else {
true
}
});
TopologicalSortIterator {
edges: self.edges,
count: self.count,
no_edges,
}
}
}
#[derive(Debug)]
pub struct TopologicalSortIterator<T: Copy + Eq + Hash> {
edges: AHashMap<T, Vec<T>>,
count: AHashMap<T, usize>,
no_edges: VecDeque<T>,
}
impl<T: Copy + Eq + Hash> Iterator for TopologicalSortIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let no_edge = self.no_edges.pop_back()?;
if let Some(edges) = self.edges.get(&no_edge) {
for neighbor in edges {
if let Some(count) = self.count.get_mut(neighbor) {
*count -= 1;
if *count == 0 {
self.count.remove(neighbor);
self.no_edges.push_front(*neighbor);
}
}
}
}
Some(no_edge)
}
}
impl<T: Copy + Eq + Hash> TopologicalSortIterator<T> {
pub fn is_valid(&self) -> bool {
self.count.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topological_sort() {
let mut sort = TopologicalSort::with_capacity(6);
sort.insert(1, 2);
sort.insert(1, 3);
sort.insert(2, 4);
sort.insert(3, 4);
sort.insert(4, 5);
sort.insert(5, 6);
let mut iter = sort.into_iterator();
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
assert!(iter.is_valid(), "{:?}", iter);
}
#[test]
fn test_topological_sort_cycle() {
let mut sort = TopologicalSort::with_capacity(6);
sort.insert(1, 2);
sort.insert(2, 3);
sort.insert(3, 1);
let mut iter = sort.into_iterator();
assert_eq!(iter.next(), None);
assert!(!iter.is_valid());
}
}
+45
View File
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Cow, collections::HashMap};
#[derive(Default)]
pub struct UrlParams<'x> {
params: HashMap<Cow<'x, str>, Cow<'x, str>>,
}
impl<'x> UrlParams<'x> {
pub fn new(query: Option<&'x str>) -> Self {
if let Some(query) = query {
Self {
params: form_urlencoded::parse(query.as_bytes())
.filter(|(_, value)| !value.is_empty())
.collect(),
}
} else {
Self::default()
}
}
pub fn get(&self, key: &str) -> Option<&str> {
self.params.get(key).map(|v| v.as_ref())
}
pub fn has_key(&self, key: &str) -> bool {
self.params.contains_key(key)
}
pub fn parse<T>(&self, key: &str) -> Option<T>
where
T: std::str::FromStr,
{
self.get(key).and_then(|v| v.parse().ok())
}
pub fn into_inner(self) -> HashMap<Cow<'x, str>, Cow<'x, str>> {
self.params
}
}