Crates renamed.

This commit is contained in:
Mauro D
2023-04-14 07:04:36 +00:00
parent 98e8febbef
commit 3751133c7d
75 changed files with 2064 additions and 30 deletions

View File

@@ -1,11 +1,11 @@
[package]
name = "protocol"
name = "jmap_proto"
version = "0.1.0"
edition = "2021"
[dependencies]
store = { path = "../store" }
utils = { path = "/home/vagrant/code/utils" }
utils = { path = "../utils" }
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
fast-float = "0.2.0"
serde = { version = "1.0", features = ["derive"]}

View File

@@ -1,12 +1,12 @@
[package]
name = "core"
name = "jmap"
version = "0.1.0"
edition = "2021"
[dependencies]
store = { path = "../store" }
protocol = { path = "../protocol" }
utils = { path = "/home/vagrant/code/utils" }
jmap_proto = { path = "../jmap-proto" }
utils = { path = "../utils" }
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
mail-builder = { git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
mail-send = { git = "https://github.com/stalwartlabs/mail-send" }

View File

@@ -1,4 +1,4 @@
use protocol::{
use jmap_proto::{
error::request::RequestError,
method::{get, query},
request::{Request, RequestMethod},

View File

@@ -1,8 +1,8 @@
use jmap_proto::types::blob::BlobId;
use mail_parser::{
decoders::{base64::base64_decode, quoted_printable::quoted_printable_decode},
Encoding,
};
use protocol::types::blob::BlobId;
use crate::JMAP;

View File

@@ -1,8 +1,8 @@
use mail_parser::{HeaderValue, MessagePart, MimeHeaders, PartType};
use protocol::{
use jmap_proto::{
object::Object,
types::{blob::BlobId, property::Property, value::Value},
};
use mail_parser::{HeaderValue, MessagePart, MimeHeaders, PartType};
use super::headers::HeaderToValue;

View File

@@ -1,10 +1,10 @@
use mail_parser::Message;
use protocol::{
use jmap_proto::{
error::method::MethodError,
method::get::{GetRequest, GetResponse},
object::{email::GetArguments, Object},
types::{blob::BlobId, collection::Collection, property::Property, value::Value},
};
use mail_parser::Message;
use store::ValueKey;
use crate::{email::headers::HeaderToValue, JMAP};

View File

@@ -1,11 +1,11 @@
use mail_parser::{parsers::MessageStream, Addr, HeaderName, HeaderValue, MessagePart, RfcHeader};
use protocol::{
use jmap_proto::{
object::Object,
types::{
property::{HeaderForm, Property},
value::Value,
},
};
use mail_parser::{parsers::MessageStream, Addr, HeaderName, HeaderValue, MessagePart, RfcHeader};
pub trait IntoForm {
fn into_form(self, form: &HeaderForm) -> Value;

View File

@@ -1,4 +1,4 @@
use protocol::{
use jmap_proto::{
error::{
method::MethodError,
set::{SetError, SetErrorType},

View File

@@ -1,11 +1,6 @@
use std::borrow::Cow;
use mail_parser::{
decoders::html::html_to_text,
parsers::{fields::thread::thread_name, preview::preview_text},
Addr, GetHeader, Group, HeaderName, HeaderValue, Message, MessagePart, PartType, RfcHeader,
};
use protocol::{
use jmap_proto::{
object::Object,
types::{
blob::{BlobId, BlobSection},
@@ -15,6 +10,11 @@ use protocol::{
value::Value,
},
};
use mail_parser::{
decoders::html::html_to_text,
parsers::{fields::thread::thread_name, preview::preview_text},
Addr, GetHeader, Group, HeaderName, HeaderValue, Message, MessagePart, PartType, RfcHeader,
};
use store::{
fts::{builder::FtsIndexBuilder, Language},
write::{BatchBuilder, F_BITMAP, F_INDEX, F_VALUE},

View File

@@ -1,13 +1,13 @@
use mail_parser::{
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, RfcHeader,
};
use protocol::{
use jmap_proto::{
object::Object,
types::{
blob::BlobId, collection::Collection, id::Id, keyword::Keyword, property::Property,
value::Value,
},
};
use mail_parser::{
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, RfcHeader,
};
use store::{
query::Filter,
write::{log::ChangeLogBuilder, now, BatchBuilder, F_BITMAP, F_CLEAR, F_VALUE},

View File

@@ -1,4 +1,4 @@
use protocol::{
use jmap_proto::{
error::method::MethodError,
method::query::{Comparator, Filter, QueryRequest, QueryResponse, SortProperty},
object::email::QueryArguments,

View File

@@ -1,4 +1,4 @@
use protocol::error::method::MethodError;
use jmap_proto::error::method::MethodError;
use store::{fts::Language, Store};
pub mod api;

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
utils = { path = "/home/vagrant/code/utils" }
utils = { path = "../utils" }
rocksdb = { version = "0.20.1", optional = true }
foundationdb = { version = "0.7.0", optional = true }
rusqlite = { version = "0.29.0", features = ["bundled"], optional = true }

7
crates/utils/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "utils"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"]}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2020-2022, Stalwart Labs Ltd.
*
* This file is part of the Stalwart JMAP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::slice::Iter;
use super::leb128::{Leb128Iterator, Leb128Writer};
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 with_capacity(capacity: usize) -> Self {
Base32Writer {
result: String::with_capacity((capacity + 3) / 4 * 5),
last_byte: 0,
pos: 0,
}
}
pub fn push_char(&mut self, ch: char) {
self.result.push(ch);
}
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 % 5 != 0 {
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,
}
}
#[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);
}
}
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright (c) 2020-2022, Stalwart Labs Ltd.
*
* This file is part of the Stalwart JMAP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
#![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(slice: &[u8]) -> Option<(Self, usize)>
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(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(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_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]);

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
pub mod base32_custom;
pub mod leb128;

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
pub mod parser;
pub mod utils;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
keys: BTreeMap<String, String>,
}
pub type Result<T> = std::result::Result<T, String>;

View File

@@ -0,0 +1,545 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::{
collections::{btree_map::Entry, BTreeMap},
iter::Peekable,
str::Chars,
};
use super::{Config, Result};
use std::fmt::Write;
const MAX_NEST_LEVEL: usize = 10;
// Simple TOML parser for Stalwart Mail Server configuration files.
impl Config {
pub fn parse(toml: &str) -> Result<Self> {
let mut parser = TomlParser::new(toml);
let mut table_name = String::new();
let mut last_array_name = String::new();
let mut last_array_pos = 0;
while parser.seek_next_char() {
match parser.peek_char()? {
'[' => {
parser.next_char(true, false)?;
table_name.clear();
let mut is_array = match parser.next_char(true, false)? {
'[' => true,
ch => {
table_name.push(ch);
false
}
};
let mut in_quote = false;
let mut last_ch = char::from(0);
loop {
let ch = parser.next_char(!in_quote, false)?;
match ch {
'\"' if !in_quote || last_ch != '\\' => {
in_quote = !in_quote;
}
'\\' if in_quote => (),
']' if !in_quote => {
if table_name.is_empty() {
return Err(format!(
"Empty table name at line {}.",
parser.line
));
}
if is_array {
if table_name == last_array_name {
last_array_pos += 1;
} else {
last_array_pos = 0;
last_array_name = table_name.to_string();
}
is_array = false;
write!(table_name, ".{last_array_pos}").ok();
} else {
break;
}
}
_ => {
if !in_quote {
if ch.is_alphanumeric() || ['.', '-', '_'].contains(&ch) {
table_name.push(ch.to_ascii_lowercase());
} else {
return Err(format!(
"Unexpected character {:?} at line {}.",
ch, parser.line
));
}
} else {
table_name.push(ch);
}
}
}
last_ch = ch;
}
parser.skip_line();
}
'a'..='z' | 'A'..='Z' | '0'..='9' | '\"' => {
let key = parser.key(if !table_name.is_empty() {
format!("{table_name}.")
} else {
String::with_capacity(10)
})?;
parser.value(key, &['\n'], 0)?;
}
'#' => {
parser.skip_line();
}
ch => {
let ch = *ch;
return Err(format!(
"Unexpected character {:?} at line {}.",
ch, parser.line
));
}
}
}
Ok(Self { keys: parser.keys })
}
}
struct TomlParser<'x> {
keys: BTreeMap<String, String>,
iter: Peekable<Chars<'x>>,
line: usize,
}
impl<'x> TomlParser<'x> {
fn new(toml: &'x str) -> Self {
Self {
keys: BTreeMap::new(),
iter: toml.chars().peekable(),
line: 1,
}
}
fn seek_next_char(&mut self) -> bool {
while let Some(ch) = self.iter.peek() {
match ch {
'\n' => {
self.iter.next();
self.line += 1;
}
'\r' | ' ' | '\t' => {
self.iter.next();
}
'#' => {
self.skip_line();
}
_ => {
return true;
}
}
}
false
}
fn peek_char(&mut self) -> Result<&char> {
self.iter.peek().ok_or_else(|| "".to_string())
}
fn next_char(&mut self, skip_wsp: bool, allow_lf: bool) -> Result<char> {
for ch in &mut self.iter {
match ch {
'\r' => (),
' ' | '\t' if skip_wsp => (),
'\n' => {
return if allow_lf {
self.line += 1;
Ok(ch)
} else {
Err(format!("Unexpected end of line at line: {}", self.line))
};
}
_ => {
return Ok(ch);
}
}
}
Err(format!("Unexpected EOF at line: {}", self.line))
}
fn skip_line(&mut self) {
for ch in &mut self.iter {
if ch == '\n' {
self.line += 1;
break;
}
}
}
#[allow(clippy::while_let_on_iterator)]
fn key(&mut self, mut key: String) -> Result<String> {
while let Some(ch) = self.iter.next() {
match ch {
'=' => {
if !key.is_empty() {
return Ok(key);
} else {
return Err(format!("Empty key at line: {}", self.line));
}
}
'a'..='z' | '.' | 'A'..='Z' | '0'..='9' | '_' | '-' => {
key.push(ch);
}
'\"' => {
let mut last_ch = char::from(0);
while let Some(ch) = self.iter.next() {
match ch {
'\\' => (),
'\"' if last_ch != '\\' => {
break;
}
'\n' => {
return Err(format!(
"Unexpected end of line at line: {}",
self.line
));
}
_ => {
key.push(ch);
}
}
last_ch = ch;
}
}
' ' | '\t' | '\r' => (),
'\n' => {
return Err(format!("Unexpected end of line at line: {}", self.line));
}
_ => {
return Err(format!(
"Unexpected character {:?} found in key at line {}.",
ch, self.line
));
}
}
}
Err(format!("Unexpected EOF at line: {}", self.line))
}
fn value(&mut self, key: String, stop_chars: &[char], nest_level: usize) -> Result<char> {
if nest_level == MAX_NEST_LEVEL {
return Err(format!("Too many nested structures at line {}.", self.line));
}
match self.next_char(true, false)? {
'[' => {
let mut array_pos = 0;
self.seek_next_char();
loop {
match self.value(format!("{key}.{array_pos}"), &[',', ']'], nest_level + 1)? {
',' => {
self.seek_next_char();
array_pos += 1;
}
']' => break,
ch => {
return Err(format!(
"Unexpected character {:?} found in array for property {:?} at line {}.",
ch, key, self.line
));
}
}
}
}
'{' => loop {
let sub_key = self.key(format!("{key}."))?;
self.seek_next_char();
match self.value(sub_key, &[',', '}'], nest_level + 1)? {
',' => {
self.seek_next_char();
}
'}' => break,
ch => {
return Err(format!(
"Unexpected character {:?} found in inline table for property {:?} at line {}.",
ch, key, self.line
));
}
}
},
qch @ ('\'' | '\"') => {
let mut value = String::new();
if matches!(self.iter.peek(), Some(ch) if ch == &qch) {
self.iter.next();
if matches!(self.iter.peek(), Some(ch) if ch == &qch) {
self.iter.next();
if matches!(self.iter.peek(), Some(ch) if ch == &'\n') {
self.iter.next();
self.line += 1;
}
let mut last_ch = char::from(0);
let mut prev_last_ch = char::from(0);
loop {
let ch = self.next_char(false, true)?;
if !(ch == qch && last_ch == qch && prev_last_ch == qch) {
value.push(ch);
prev_last_ch = last_ch;
last_ch = ch;
} else {
value.truncate(value.len() - 2);
break;
}
}
}
} else {
let mut last_ch = char::from(0);
loop {
let ch = self.next_char(false, true)?;
match ch {
'\\' if last_ch != '\\' => (),
't' if last_ch == '\\' => {
value.push('\t');
}
'r' if last_ch == '\\' => {
value.push('\r');
}
'n' if last_ch == '\\' => {
value.push('\n');
}
ch => {
if ch != qch || last_ch == '\\' {
value.push(ch);
} else {
break;
}
}
}
last_ch = ch;
}
}
match self.keys.entry(key) {
Entry::Vacant(e) => {
value.shrink_to_fit();
e.insert(value);
}
Entry::Occupied(e) => {
return Err(format!(
"Duplicate key {:?} at line {}.",
e.key(),
self.line
));
}
}
}
ch if ch.is_alphanumeric() || ['.', '+', '-'].contains(&ch) => {
let mut value = String::with_capacity(4);
value.push(ch);
while let Some(ch) = self.iter.peek() {
if ch.is_alphanumeric() || ['.', '+', '-'].contains(ch) {
value.push(self.next_char(true, false)?);
} else {
break;
}
}
match self.keys.entry(key) {
Entry::Vacant(e) => {
value.shrink_to_fit();
e.insert(value);
}
Entry::Occupied(e) => {
return Err(format!(
"Duplicate key {:?} at line {}.",
e.key(),
self.line
));
}
}
}
ch => {
return if stop_chars.contains(&ch) {
Ok(ch)
} else {
Err(format!(
"Expected {:?} but found {:?} in value at line {}.",
stop_chars, ch, self.line
))
}
}
}
loop {
match self.next_char(true, true)? {
'#' => {
self.skip_line();
if stop_chars.contains(&'\n') {
return Ok('\n');
}
}
ch if stop_chars.contains(&ch) => {
return Ok(ch);
}
'\n' if !stop_chars.contains(&'\n') => (),
ch => {
return Err(format!(
"Expected {:?} but found {:?} in value at line {}.",
stop_chars, ch, self.line
));
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, fs, path::PathBuf};
use crate::config::Config;
#[test]
fn toml_parse() {
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
file.push("resources");
file.push("tests");
file.push("config");
file.push("toml-parser.toml");
let config = Config::parse(&fs::read_to_string(file).unwrap()).unwrap();
assert_eq!(
config.keys,
BTreeMap::from_iter([
("arrays.colors.0".to_string(), "red".to_string()),
("arrays.colors.1".to_string(), "yellow".to_string()),
("arrays.colors.2".to_string(), "green".to_string()),
(
"arrays.contributors.0".to_string(),
"Foo Bar <foo@example.com>".to_string()
),
(
"arrays.contributors.1.email".to_string(),
"bazqux@example.com".to_string()
),
(
"arrays.contributors.1.name".to_string(),
"Baz Qux".to_string()
),
(
"arrays.contributors.1.url".to_string(),
"https://example.com/bazqux".to_string()
),
("arrays.integers.0".to_string(), "1".to_string()),
("arrays.integers.1".to_string(), "2".to_string()),
("arrays.integers.2".to_string(), "3".to_string()),
("arrays.integers2.0".to_string(), "1".to_string()),
("arrays.integers2.1".to_string(), "2".to_string()),
("arrays.integers2.2".to_string(), "3".to_string()),
("arrays.integers3.0".to_string(), "4".to_string()),
("arrays.integers3.1".to_string(), "5".to_string()),
(
"arrays.nested_arrays_of_ints.0.0".to_string(),
"1".to_string()
),
(
"arrays.nested_arrays_of_ints.0.1".to_string(),
"2".to_string()
),
(
"arrays.nested_arrays_of_ints.1.0".to_string(),
"3".to_string()
),
(
"arrays.nested_arrays_of_ints.1.1".to_string(),
"4".to_string()
),
(
"arrays.nested_arrays_of_ints.1.2".to_string(),
"5".to_string()
),
("arrays.nested_mixed_array.0.0".to_string(), "1".to_string()),
("arrays.nested_mixed_array.0.1".to_string(), "2".to_string()),
("arrays.nested_mixed_array.1.0".to_string(), "a".to_string()),
("arrays.nested_mixed_array.1.1".to_string(), "b".to_string()),
("arrays.nested_mixed_array.1.2".to_string(), "c".to_string()),
("arrays.numbers.0".to_string(), "0.1".to_string()),
("arrays.numbers.1".to_string(), "0.2".to_string()),
("arrays.numbers.2".to_string(), "0.5".to_string()),
("arrays.numbers.3".to_string(), "1".to_string()),
("arrays.numbers.4".to_string(), "2".to_string()),
("arrays.numbers.5".to_string(), "5".to_string()),
("arrays.string_array.0".to_string(), "all".to_string()),
("arrays.string_array.1".to_string(), "strings".to_string()),
(
"arrays.string_array.2".to_string(),
"are the same".to_string()
),
("arrays.string_array.3".to_string(), "type".to_string()),
("database.data.0.0".to_string(), "delta".to_string()),
("database.data.0.1".to_string(), "phi".to_string()),
("database.data.1.0".to_string(), "3.14".to_string()),
("database.enabled".to_string(), "true".to_string()),
("database.ports.0".to_string(), "8000".to_string()),
("database.ports.1".to_string(), "8001".to_string()),
("database.ports.2".to_string(), "8002".to_string()),
("database.temp_targets.case".to_string(), "72.0".to_string()),
("database.temp_targets.cpu".to_string(), "79.5".to_string()),
("products.0.name".to_string(), "Hammer".to_string()),
("products.0.sku".to_string(), "738594937".to_string()),
("products.2.color".to_string(), "gray".to_string()),
("products.2.name".to_string(), "Nail".to_string()),
("products.2.sku".to_string(), "284758393".to_string()),
("servers.127.0.0.1".to_string(), "value".to_string()),
("servers.alpha.ip".to_string(), "10.0.0.1".to_string()),
("servers.alpha.role".to_string(), "frontend".to_string()),
("servers.beta.ip".to_string(), "10.0.0.2".to_string()),
("servers.beta.role".to_string(), "backend".to_string()),
(
"servers.character encoding".to_string(),
"value".to_string()
),
(
"strings.my \"string\" test.lines".to_string(),
concat!(
"The first newline is\ntrimmed in raw strings.\n",
"All other whitespace\nis preserved.\n"
)
.to_string()
),
(
"strings.my \"string\" test.str1".to_string(),
"I'm a string.".to_string()
),
(
"strings.my \"string\" test.str2".to_string(),
"You can \"quote\" me.".to_string()
),
(
"strings.my \"string\" test.str3".to_string(),
"Name\tTabs\nNew Line.".to_string()
),
])
);
}
}

View File

@@ -0,0 +1,499 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::{net::IpAddr, time::Duration};
use super::Config;
impl Config {
pub fn property<T: ParseValue>(&self, key: impl AsKey) -> super::Result<Option<T>> {
let key = key.as_key();
if let Some(value) = self.keys.get(&key) {
T::parse_value(key, value).map(Some)
} else {
Ok(None)
}
}
pub fn property_or_default<T: ParseValue>(
&self,
key: impl AsKey,
default: impl AsKey,
) -> super::Result<Option<T>> {
match self.property(key) {
Ok(None) => self.property(default),
result => result,
}
}
pub fn property_require<T: ParseValue>(&self, key: impl AsKey) -> super::Result<T> {
match self.property(key.clone()) {
Ok(Some(result)) => Ok(result),
Ok(None) => Err(format!("Missing property {:?}.", key.as_key())),
Err(err) => Err(err),
}
}
pub fn sub_keys<'x, 'y: 'x>(&'y self, prefix: impl AsKey) -> impl Iterator<Item = &str> + 'x {
let mut last_key = "";
let prefix = prefix.as_prefix();
self.keys.keys().filter_map(move |key| {
let key = key.strip_prefix(&prefix)?;
let key = if let Some((key, _)) = key.split_once('.') {
key
} else {
key
};
if last_key != key {
last_key = key;
Some(key)
} else {
None
}
})
}
pub fn properties<T: ParseValue>(
&self,
prefix: impl AsKey,
) -> impl Iterator<Item = super::Result<(&str, T)>> {
let full_prefix = prefix.as_key();
let prefix = prefix.as_prefix();
self.keys.iter().filter_map(move |(key, value)| {
if key.starts_with(&prefix) || key == &full_prefix {
T::parse_value(key.as_str(), value)
.map(|value| (key.as_str(), value))
.into()
} else {
None
}
})
}
pub fn value(&self, key: impl AsKey) -> Option<&str> {
self.keys.get(&key.as_key()).map(|s| s.as_str())
}
pub fn value_require(&self, key: impl AsKey) -> super::Result<&str> {
self.keys
.get(&key.as_key())
.map(|s| s.as_str())
.ok_or_else(|| format!("Missing property {:?}.", key.as_key()))
}
pub fn value_or_default(&self, key: impl AsKey, default: impl AsKey) -> Option<&str> {
self.keys
.get(&key.as_key())
.or_else(|| self.keys.get(&default.as_key()))
.map(|s| s.as_str())
}
pub fn values(&self, prefix: impl AsKey) -> impl Iterator<Item = (&str, &str)> {
let full_prefix = prefix.as_key();
let prefix = prefix.as_prefix();
self.keys.iter().filter_map(move |(key, value)| {
if key.starts_with(&prefix) || key == &full_prefix {
(key.as_str(), value.as_str()).into()
} else {
None
}
})
}
pub fn values_or_default(
&self,
prefix: impl AsKey,
default: impl AsKey,
) -> impl Iterator<Item = (&str, &str)> {
let mut prefix = prefix.as_prefix();
self.values(if self.keys.keys().any(|k| k.starts_with(&prefix)) {
prefix.truncate(prefix.len() - 1);
prefix
} else {
default.as_key()
})
}
pub fn take_value(&mut self, key: &str) -> Option<String> {
self.keys.remove(key)
}
pub fn file_contents(&self, key: impl AsKey) -> super::Result<Vec<u8>> {
let key = key.as_key();
if let Some(value) = self.keys.get(&key) {
if let Some(value) = value.strip_prefix("file://") {
std::fs::read(value).map_err(|err| {
format!("Failed to read file {value:?} for property {key:?}: {err}")
})
} else {
Ok(value.to_string().into_bytes())
}
} else {
Err(format!("Property {key:?} not found in configuration file."))
}
}
}
pub trait ParseValues: Sized + Default {
fn parse_values(key: impl AsKey, values: &Config) -> super::Result<Self>;
fn is_multivalue() -> bool;
}
pub trait ParseValue: Sized {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self>;
}
pub trait ParseKey<T: ParseValue> {
fn parse_key(&self, key: impl AsKey) -> super::Result<T>;
}
impl<T: ParseValue> ParseKey<T> for &str {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self)
}
}
impl<T: ParseValue> ParseKey<T> for String {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self.as_str())
}
}
impl<T: ParseValue> ParseKey<T> for &String {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self.as_str())
}
}
impl<T: ParseValue> ParseValues for Vec<T> {
fn is_multivalue() -> bool {
true
}
fn parse_values(key: impl AsKey, values: &Config) -> super::Result<Self> {
let mut result = Vec::new();
for (key, value) in values.values(key) {
result.push(T::parse_value(key, value)?);
}
Ok(result)
}
}
impl<T: ParseValue + Default> ParseValues for T {
fn is_multivalue() -> bool {
false
}
fn parse_values(key: impl AsKey, values: &Config) -> super::Result<Self> {
let mut iter = values.values(key);
if let Some((key, value)) = iter.next() {
let result = T::parse_value(key, value)?;
if iter.next().is_none() {
Ok(result)
} else {
Err(format!(
"Property {:?} cannot have multiple values.",
key.as_key()
))
}
} else {
Ok(T::default())
}
}
}
impl<T: ParseValue> ParseValue for Option<T> {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
if !value.is_empty()
&& !value.eq_ignore_ascii_case("false")
&& !value.eq_ignore_ascii_case("disable")
&& !value.eq_ignore_ascii_case("disabled")
&& !value.eq_ignore_ascii_case("never")
&& !value.eq("0")
{
T::parse_value(key, value).map(Some)
} else {
Ok(None)
}
}
}
impl ParseValue for String {
fn parse_value(_key: impl AsKey, value: &str) -> super::Result<Self> {
Ok(value.to_string())
}
}
impl ParseValue for u64 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for u16 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for i16 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for u32 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for IpAddr {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid IP address value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for usize {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for bool {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid boolean value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
impl ParseValue for Duration {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
let duration = value.trim_end().to_ascii_lowercase();
let (num, multiplier) = if let Some(num) = duration.strip_suffix('d') {
(num, 24 * 60 * 60 * 1000)
} else if let Some(num) = duration.strip_suffix('h') {
(num, 60 * 60 * 1000)
} else if let Some(num) = duration.strip_suffix('m') {
(num, 60 * 1000)
} else if let Some(num) = duration.strip_suffix("ms") {
(num, 1)
} else if let Some(num) = duration.strip_suffix('s') {
(num, 1000)
} else {
(duration.as_str(), 1)
};
num.trim()
.parse::<u64>()
.ok()
.and_then(|num| {
if num > 0 {
Some(Duration::from_millis(num * multiplier))
} else {
None
}
})
.ok_or_else(|| {
format!(
"Invalid duration value {:?} for property {:?}.",
value,
key.as_key()
)
})
}
}
pub trait AsKey: Clone {
fn as_key(&self) -> String;
fn as_prefix(&self) -> String;
}
impl AsKey for &str {
fn as_key(&self) -> String {
self.to_string()
}
fn as_prefix(&self) -> String {
format!("{self}.")
}
}
impl AsKey for String {
fn as_key(&self) -> String {
self.to_string()
}
fn as_prefix(&self) -> String {
format!("{self}.")
}
}
impl AsKey for (&str, &str) {
fn as_key(&self) -> String {
format!("{}.{}", self.0, self.1)
}
fn as_prefix(&self) -> String {
format!("{}.{}.", self.0, self.1)
}
}
impl AsKey for (&String, &str) {
fn as_key(&self) -> String {
format!("{}.{}", self.0, self.1)
}
fn as_prefix(&self) -> String {
format!("{}.{}.", self.0, self.1)
}
}
impl AsKey for (&str, &str, &str) {
fn as_key(&self) -> String {
format!("{}.{}.{}", self.0, self.1, self.2)
}
fn as_prefix(&self) -> String {
format!("{}.{}.{}.", self.0, self.1, self.2)
}
}
impl AsKey for (&str, &str, &str, &str) {
fn as_key(&self) -> String {
format!("{}.{}.{}.{}", self.0, self.1, self.2, self.3)
}
fn as_prefix(&self) -> String {
format!("{}.{}.{}.{}.", self.0, self.1, self.2, self.3)
}
}
#[cfg(test)]
mod tests {
use std::net::IpAddr;
use crate::config::Config;
#[test]
fn toml_utils() {
let toml = r#"
[queues."z"]
retry = [0, 1, 15, 60, 90]
value = "hi"
[queues."x"]
retry = [3, 60]
value = "hi 2"
[queues.a]
retry = [1, 2, 3, 4]
value = "hi 3"
[servers."my relay"]
hostname = "mx.example.org"
[[servers."my relay".transaction.auth.limits]]
idle = 10
[[servers."my relay".transaction.auth.limits]]
idle = 20
[servers."submissions"]
hostname = "submit.example.org"
ip = "a:b::1:1"
"#;
let config = Config::parse(toml).unwrap();
assert_eq!(
config.sub_keys("queues").collect::<Vec<_>>(),
["a", "x", "z"]
);
assert_eq!(
config.sub_keys("servers").collect::<Vec<_>>(),
["my relay", "submissions"]
);
assert_eq!(
config.sub_keys("queues.z.retry").collect::<Vec<_>>(),
["0", "1", "2", "3", "4"]
);
assert_eq!(
config
.property::<u32>("servers.my relay.transaction.auth.limits.1.idle")
.unwrap()
.unwrap(),
20
);
assert_eq!(
config
.property::<IpAddr>(("servers", "submissions", "ip"))
.unwrap()
.unwrap(),
"a:b::1:1".parse::<IpAddr>().unwrap()
);
}
}

26
crates/utils/src/lib.rs Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
pub mod codec;
pub mod config;
pub mod map;

View File

@@ -0,0 +1,205 @@
/*
* Copyright (c) 2020-2022, Stalwart Labs Ltd.
*
* This file is part of the Stalwart JMAP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::ops::Deref;
#[derive(
Debug, serde::Serialize, serde::Deserialize, Clone, PartialOrd, Ord, PartialEq, Eq, Hash,
)]
pub struct Bitmap<T: BitmapItem> {
pub bitmap: u64,
#[serde(skip)]
_state: std::marker::PhantomData<T>,
}
pub trait BitmapItem: From<u64> + Into<u64> + Sized + Copy {
fn max() -> u64;
fn is_valid(&self) -> bool;
}
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 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();
}
#[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 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,
}
}
}
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> 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,
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
pub mod bitmap;
pub mod vec_map;

View File

@@ -0,0 +1,272 @@
/*
* Copyright (c) 2020-2022, Stalwart Labs Ltd.
*
* This file is part of the Stalwart JMAP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::{borrow::Borrow, fmt};
use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Serialize};
// A map implemented using vectors
// used for small datasets of less than 20 items
// and when deserializing from JSON
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VecMap<K: Eq + PartialEq, V> {
pub k: Vec<K>,
pub v: Vec<V>,
}
impl<K: Eq + PartialEq, V> Default for VecMap<K, V> {
fn default() -> Self {
VecMap {
k: Vec::new(),
v: Vec::new(),
}
}
}
impl<K: Eq + PartialEq, V> VecMap<K, V> {
pub fn new() -> Self {
Self {
k: Vec::new(),
v: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
k: Vec::with_capacity(capacity),
v: Vec::with_capacity(capacity),
}
}
#[inline(always)]
pub fn set(&mut self, key: K, value: V) -> bool {
if let Some(pos) = self.k.iter().position(|k| *k == key) {
self.v[pos] = value;
false
} else {
self.k.push(key);
self.v.push(value);
true
}
}
#[inline(always)]
pub fn append(&mut self, key: K, value: V) {
self.k.push(key);
self.v.push(value);
}
#[inline(always)]
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q> + PartialEq<Q>,
{
self.k.iter().position(|k| k == key).map(|pos| &self.v[pos])
}
#[inline(always)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.k
.iter_mut()
.position(|k| k == key)
.map(|pos| &mut self.v[pos])
}
#[inline(always)]
pub fn contains_key(&self, key: &K) -> bool {
self.k.contains(key)
}
#[inline(always)]
pub fn remove(&mut self, key: &K) -> Option<V> {
self.k.iter().position(|k| k == key).map(|pos| {
self.k.swap_remove(pos);
self.v.swap_remove(pos)
})
}
#[inline(always)]
pub fn remove_entry(&mut self, key: &K) -> Option<(K, V)> {
self.k
.iter()
.position(|k| k == key)
.map(|pos| (self.k.swap_remove(pos), self.v.swap_remove(pos)))
}
#[inline(always)]
pub fn swap_remove(&mut self, index: usize) -> V {
self.k.swap_remove(index);
self.v.swap_remove(index)
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.k.is_empty()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.k.len()
}
#[inline(always)]
pub fn clear(&mut self) {
self.k.clear();
self.v.clear();
}
#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.k.iter().zip(self.v.iter())
}
#[inline(always)]
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut K, &mut V)> {
self.k.iter_mut().zip(self.v.iter_mut())
}
#[inline(always)]
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.k.iter()
}
#[inline(always)]
pub fn values(&self) -> impl Iterator<Item = &V> {
self.v.iter()
}
#[inline(always)]
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.v.iter_mut()
}
pub fn get_mut_or_insert_with(&mut self, key: K, fnc: impl FnOnce() -> V) -> &mut V {
if let Some(pos) = self.k.iter().position(|k| k == &key) {
&mut self.v[pos]
} else {
self.k.push(key);
self.v.push(fnc());
self.v.last_mut().unwrap()
}
}
}
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.k.iter().position(|k| k == &key) {
&mut self.v[pos]
} else {
self.k.push(key);
self.v.push(V::default());
self.v.last_mut().unwrap()
}
}
}
impl<K: Eq + PartialEq, V> IntoIterator for VecMap<K, V> {
type Item = (K, V);
type IntoIter = std::iter::Zip<std::vec::IntoIter<K>, std::vec::IntoIter<V>>;
fn into_iter(self) -> Self::IntoIter {
self.k.into_iter().zip(self.v.into_iter())
}
}
impl<'x, K: Eq + PartialEq, V> IntoIterator for &'x VecMap<K, V> {
type Item = (&'x K, &'x V);
type IntoIter = std::iter::Zip<std::slice::Iter<'x, K>, std::slice::Iter<'x, V>>;
fn into_iter(self) -> Self::IntoIter {
self.k.iter().zip(self.v.iter())
}
}
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 mut map = VecMap::new();
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 + DeserializeOwned, V: DeserializeOwned> 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 + DeserializeOwned, V: DeserializeOwned> 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()
}
}