Crates renamed.
This commit is contained in:
@@ -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"]}
|
||||
@@ -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" }
|
||||
@@ -1,4 +1,4 @@
|
||||
use protocol::{
|
||||
use jmap_proto::{
|
||||
error::request::RequestError,
|
||||
method::{get, query},
|
||||
request::{Request, RequestMethod},
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
use protocol::{
|
||||
use jmap_proto::{
|
||||
error::{
|
||||
method::MethodError,
|
||||
set::{SetError, SetErrorType},
|
||||
@@ -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},
|
||||
@@ -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},
|
||||
@@ -1,4 +1,4 @@
|
||||
use protocol::{
|
||||
use jmap_proto::{
|
||||
error::method::MethodError,
|
||||
method::query::{Comparator, Filter, QueryRequest, QueryResponse, SortProperty},
|
||||
object::email::QueryArguments,
|
||||
@@ -1,4 +1,4 @@
|
||||
use protocol::error::method::MethodError;
|
||||
use jmap_proto::error::method::MethodError;
|
||||
use store::{fts::Language, Store};
|
||||
|
||||
pub mod api;
|
||||
@@ -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
7
crates/utils/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
215
crates/utils/src/codec/base32_custom.rs
Normal file
215
crates/utils/src/codec/base32_custom.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
crates/utils/src/codec/leb128.rs
Normal file
180
crates/utils/src/codec/leb128.rs
Normal 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]);
|
||||
25
crates/utils/src/codec/mod.rs
Normal file
25
crates/utils/src/codec/mod.rs
Normal 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;
|
||||
34
crates/utils/src/config/mod.rs
Normal file
34
crates/utils/src/config/mod.rs
Normal 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>;
|
||||
545
crates/utils/src/config/parser.rs
Normal file
545
crates/utils/src/config/parser.rs
Normal 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()
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
499
crates/utils/src/config/utils.rs
Normal file
499
crates/utils/src/config/utils.rs
Normal 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
26
crates/utils/src/lib.rs
Normal 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;
|
||||
205
crates/utils/src/map/bitmap.rs
Normal file
205
crates/utils/src/map/bitmap.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
25
crates/utils/src/map/mod.rs
Normal file
25
crates/utils/src/map/mod.rs
Normal 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;
|
||||
272
crates/utils/src/map/vec_map.rs
Normal file
272
crates/utils/src/map/vec_map.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user