Import upstream v0.16.22, stripped

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

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "types"
version = "0.16.22"
edition = "2024"
[dependencies]
utils = { path = "../utils" }
trc = { path = "../trc" }
jmap-tools = { version = "0.1" }
hashify = "0.2"
serde = { version = "1.0", features = ["derive"]}
rkyv = { version = "0.8.18", features = ["little_endian"] }
compact_str = { version = "0.10.0", features = ["rkyv", "serde"] }
blake3 = "1.8.7"
[features]
test_mode = []
[lints]
workspace = true
+148
View File
@@ -0,0 +1,148 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::{self, Display};
use utils::map::bitmap::{Bitmap, BitmapItem};
#[derive(
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Copy,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
#[repr(u8)]
pub enum Acl {
Read = 0,
Modify = 1,
Delete = 2,
ReadItems = 3,
AddItems = 4,
ModifyItems = 5,
RemoveItems = 6,
CreateChild = 7,
Share = 8,
Submit = 9,
SchedulingReadFreeBusy = 10,
SchedulingInvite = 11,
SchedulingReply = 12,
ModifyItemsOwn = 13,
ModifyPrivateProperties = 14,
ModifyRSVP = 15,
None = 16,
}
#[derive(
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
Debug,
Clone,
PartialEq,
Eq,
serde::Serialize,
Default,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct AclGrant {
pub account_id: u32,
pub grants: Bitmap<Acl>,
}
impl Acl {
fn as_str(&self) -> &'static str {
match self {
Acl::Read => "read",
Acl::Modify => "modify",
Acl::Delete => "delete",
Acl::ReadItems => "readItems",
Acl::AddItems => "addItems",
Acl::ModifyItems => "modifyItems",
Acl::RemoveItems => "removeItems",
Acl::CreateChild => "createChild",
Acl::Share => "share",
Acl::Submit => "submit",
Acl::ModifyItemsOwn => "modifyItemsOwn",
Acl::ModifyPrivateProperties => "modifyPrivateProperties",
Acl::None => "",
Acl::SchedulingReadFreeBusy => "schedulingReadFreeBusy",
Acl::SchedulingInvite => "schedulingInvite",
Acl::SchedulingReply => "schedulingReply",
Acl::ModifyRSVP => "modifyRSVP",
}
}
}
impl Display for Acl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl serde::Serialize for Acl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl BitmapItem for Acl {
fn max() -> u64 {
Acl::None as u64
}
fn is_valid(&self) -> bool {
!matches!(self, Acl::None)
}
}
impl From<Acl> for u64 {
fn from(value: Acl) -> Self {
value as u64
}
}
impl From<u64> for Acl {
fn from(value: u64) -> Self {
match value {
0 => Acl::Read,
1 => Acl::Modify,
2 => Acl::Delete,
3 => Acl::ReadItems,
4 => Acl::AddItems,
5 => Acl::ModifyItems,
6 => Acl::RemoveItems,
7 => Acl::CreateChild,
8 => Acl::Share,
9 => Acl::Submit,
10 => Acl::SchedulingReadFreeBusy,
11 => Acl::SchedulingInvite,
12 => Acl::SchedulingReply,
13 => Acl::ModifyItemsOwn,
14 => Acl::ModifyPrivateProperties,
15 => Acl::ModifyRSVP,
_ => Acl::None,
}
}
}
impl From<&ArchivedAclGrant> for AclGrant {
fn from(value: &ArchivedAclGrant) -> Self {
Self {
account_id: u32::from(value.account_id),
grants: (&value.grants).into(),
}
}
}
+267
View File
@@ -0,0 +1,267 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Property, Value};
use std::{borrow::Borrow, str::FromStr, time::SystemTime};
use utils::codec::{
base32_custom::{Base32Reader, Base32Writer},
leb128::{Leb128Iterator, Leb128Writer},
};
use crate::blob_hash::BlobHash;
const B_LINKED: u8 = 0x10;
const B_RESERVED: u8 = 0x20;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BlobClass {
Reserved {
account_id: u32,
expires: u64,
},
Linked {
account_id: u32,
collection: u8,
document_id: u32,
},
}
impl Default for BlobClass {
fn default() -> Self {
BlobClass::Reserved {
account_id: u32::MAX,
expires: u64::MAX,
}
}
}
impl AsRef<BlobClass> for BlobClass {
fn as_ref(&self) -> &BlobClass {
self
}
}
impl BlobClass {
pub fn account_id(&self) -> u32 {
match self {
BlobClass::Reserved { account_id, .. } | BlobClass::Linked { account_id, .. } => {
*account_id
}
}
}
pub fn is_valid(&self) -> bool {
match self {
BlobClass::Reserved { expires, .. } => {
*expires
> SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}
BlobClass::Linked { .. } => true,
}
}
pub fn is_superuser(&self) -> bool {
matches!(self, BlobClass::Reserved { account_id, expires } if *account_id == u32::MAX && *expires == u64::MAX)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlobId {
pub hash: BlobHash,
pub class: BlobClass,
pub section: Option<BlobSection>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlobSection {
pub offset_start: usize,
pub size: usize,
pub encoding: u8,
}
impl FromStr for BlobId {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
BlobId::from_base32(s).ok_or(())
}
}
impl BlobId {
pub fn new(hash: BlobHash, class: BlobClass) -> Self {
BlobId {
hash,
class,
section: None,
}
}
pub fn new_section(
hash: BlobHash,
class: BlobClass,
offset_start: usize,
offset_end: usize,
encoding: impl Into<u8>,
) -> Self {
BlobId {
hash,
class,
section: BlobSection {
offset_start,
size: offset_end - offset_start,
encoding: encoding.into(),
}
.into(),
}
}
pub fn with_section_size(mut self, size: usize) -> Self {
self.section.get_or_insert_with(Default::default).size = size;
self
}
#[inline]
pub fn from_base32(value: impl AsRef<[u8]>) -> Option<Self> {
BlobId::from_iter(&mut Base32Reader::new(value.as_ref()))
}
#[allow(clippy::should_implement_trait)]
pub fn from_iter<T, U>(it: &mut T) -> Option<Self>
where
T: Iterator<Item = U> + Leb128Iterator<U>,
U: Borrow<u8>,
{
let class = *it.next()?.borrow();
let encoding = class & 0x0F;
let mut hash = BlobHash::default();
for byte in hash.as_mut().iter_mut() {
*byte = *it.next()?.borrow();
}
let account_id: u32 = it.next_leb128()?;
BlobId {
hash,
class: if (class & B_LINKED) != 0 {
BlobClass::Linked {
account_id,
collection: *it.next()?.borrow(),
document_id: it.next_leb128()?,
}
} else {
BlobClass::Reserved {
account_id,
expires: it.next_leb128()?,
}
},
section: if encoding != 0 {
BlobSection {
offset_start: it.next_leb128()?,
size: it.next_leb128()?,
encoding: encoding - 1,
}
.into()
} else {
None
},
}
.into()
}
fn serialize_as(&self, writer: &mut impl Leb128Writer) {
let marker = self
.section
.as_ref()
.map_or(0, |section| section.encoding + 1)
| if matches!(
self,
BlobId {
class: BlobClass::Linked { .. },
..
}
) {
B_LINKED
} else {
B_RESERVED
};
let _ = writer.write(&[marker]);
let _ = writer.write(self.hash.as_ref());
match &self.class {
BlobClass::Reserved {
account_id,
expires,
} => {
let _ = writer.write_leb128(*account_id);
let _ = writer.write_leb128(*expires);
}
BlobClass::Linked {
account_id,
collection,
document_id,
} => {
let _ = writer.write_leb128(*account_id);
let _ = writer.write(&[*collection]);
let _ = writer.write_leb128(*document_id);
}
}
if let Some(section) = &self.section {
let _ = writer.write_leb128(section.offset_start);
let _ = writer.write_leb128(section.size);
}
}
pub fn start_offset(&self) -> usize {
if let Some(section) = &self.section {
section.offset_start
} else {
0
}
}
pub fn is_empty(&self) -> bool {
self.hash.is_empty()
}
}
impl serde::Serialize for BlobId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> serde::Deserialize<'de> for BlobId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
BlobId::from_str(<&str>::deserialize(deserializer)?)
.map_err(|_| serde::de::Error::custom("invalid BlobId"))
}
}
impl std::fmt::Display for BlobId {
#[allow(clippy::unused_io_amount)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut writer = Base32Writer::with_capacity(std::mem::size_of::<BlobId>() * 2);
self.serialize_as(&mut writer);
f.write_str(&writer.finalize())
}
}
impl<'x, P: Property, E: Element + From<BlobId>> From<BlobId> for Value<'x, P, E> {
fn from(id: BlobId) -> Self {
Value::Element(E::from(id))
}
}
+86
View File
@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub const BLOB_HASH_LEN: usize = 32;
#[derive(
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
Clone,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
)]
#[rkyv(derive(Debug))]
#[repr(transparent)]
pub struct BlobHash(pub [u8; BLOB_HASH_LEN]);
impl BlobHash {
pub fn new_max() -> Self {
BlobHash([u8::MAX; BLOB_HASH_LEN])
}
pub fn generate(value: impl AsRef<[u8]>) -> Self {
BlobHash(blake3::hash(value.as_ref()).into())
}
pub fn try_from_hash_slice(value: &[u8]) -> Result<BlobHash, std::array::TryFromSliceError> {
value.try_into().map(BlobHash)
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}
pub fn to_hex(&self) -> String {
let mut hex = String::with_capacity(BLOB_HASH_LEN * 2);
for byte in self.0.iter() {
hex.push_str(&format!("{:02x}", byte));
}
hex
}
pub fn is_empty(&self) -> bool {
self.0 == [0; BLOB_HASH_LEN]
}
}
impl From<&ArchivedBlobHash> for BlobHash {
fn from(value: &ArchivedBlobHash) -> Self {
BlobHash(value.0)
}
}
impl AsRef<BlobHash> for BlobHash {
fn as_ref(&self) -> &BlobHash {
self
}
}
impl From<BlobHash> for Vec<u8> {
fn from(value: BlobHash) -> Self {
value.0.to_vec()
}
}
impl AsRef<[u8]> for BlobHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsMut<[u8]> for BlobHash {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
+447
View File
@@ -0,0 +1,447 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::type_state::DataType;
use compact_str::CompactString;
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
use utils::map::bitmap::BitmapItem;
#[derive(
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
Debug,
Clone,
Copy,
Hash,
PartialEq,
Eq,
Default,
)]
#[repr(u8)]
pub enum Collection {
Email = 0,
Mailbox = 1,
Thread = 2,
Identity = 3,
EmailSubmission = 4,
SieveScript = 5,
PushSubscription = 6,
Principal = 7,
Calendar = 8,
CalendarEvent = 9,
AddressBook = 10,
ContactCard = 11,
FileNode = 12,
CalendarEventNotification = 13,
#[default]
None = 14,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum SyncCollection {
Email = 0,
Thread = 1,
Calendar = 2,
AddressBook = 3,
FileNode = 4,
Identity = 5,
EmailSubmission = 6,
SieveScript = 7,
CalendarEventNotification = 8,
ShareNotification = 9,
#[default]
None = 10,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[repr(u8)]
pub enum VanishedCollection {
Email = 251,
Calendar = 252,
AddressBook = 253,
FileNode = 254,
}
impl Collection {
pub const MAX: usize = Collection::None as usize;
pub fn main_collection(&self) -> Collection {
match self {
Collection::Email => Collection::Mailbox,
Collection::CalendarEvent => Collection::Calendar,
Collection::ContactCard => Collection::AddressBook,
_ => *self,
}
}
pub fn parent_collection(&self) -> Option<Collection> {
match self {
Collection::Email => Some(Collection::Mailbox),
Collection::CalendarEvent => Some(Collection::Calendar),
Collection::ContactCard => Some(Collection::AddressBook),
Collection::FileNode => Some(Collection::FileNode),
Collection::CalendarEventNotification => Some(Collection::CalendarEventNotification),
_ => None,
}
}
pub fn child_collection(&self) -> Option<Collection> {
match self {
Collection::Mailbox => Some(Collection::Email),
Collection::Calendar => Some(Collection::CalendarEvent),
Collection::AddressBook => Some(Collection::ContactCard),
Collection::FileNode => Some(Collection::FileNode),
Collection::CalendarEventNotification => Some(Collection::CalendarEventNotification),
_ => None,
}
}
}
impl SyncCollection {
pub fn collection(&self, is_container: bool) -> Collection {
match self {
SyncCollection::Email => {
if is_container {
Collection::Mailbox
} else {
Collection::Email
}
}
SyncCollection::Thread => Collection::Thread,
SyncCollection::Calendar => {
if is_container {
Collection::Calendar
} else {
Collection::CalendarEvent
}
}
SyncCollection::AddressBook => {
if is_container {
Collection::AddressBook
} else {
Collection::ContactCard
}
}
SyncCollection::FileNode => Collection::FileNode,
SyncCollection::Identity => Collection::Identity,
SyncCollection::EmailSubmission => Collection::EmailSubmission,
SyncCollection::SieveScript => Collection::SieveScript,
SyncCollection::CalendarEventNotification => Collection::CalendarEventNotification,
SyncCollection::ShareNotification | SyncCollection::None => Collection::None,
}
}
pub fn vanished_collection(&self) -> Option<VanishedCollection> {
match self {
SyncCollection::Email => Some(VanishedCollection::Email),
SyncCollection::Calendar => Some(VanishedCollection::Calendar),
SyncCollection::AddressBook => Some(VanishedCollection::AddressBook),
SyncCollection::FileNode => Some(VanishedCollection::FileNode),
_ => None,
}
}
}
impl From<Collection> for SyncCollection {
fn from(v: Collection) -> Self {
match v {
Collection::Email => SyncCollection::Email,
Collection::Mailbox => SyncCollection::Email,
Collection::Thread => SyncCollection::Thread,
Collection::Identity => SyncCollection::Identity,
Collection::EmailSubmission => SyncCollection::EmailSubmission,
Collection::SieveScript => SyncCollection::SieveScript,
Collection::PushSubscription => SyncCollection::None,
Collection::Principal => SyncCollection::None,
Collection::Calendar => SyncCollection::Calendar,
Collection::CalendarEvent => SyncCollection::Calendar,
Collection::CalendarEventNotification => SyncCollection::CalendarEventNotification,
Collection::AddressBook => SyncCollection::AddressBook,
Collection::ContactCard => SyncCollection::AddressBook,
Collection::FileNode => SyncCollection::FileNode,
_ => SyncCollection::None,
}
}
}
impl From<u8> for Collection {
fn from(v: u8) -> Self {
match v {
0 => Collection::Email,
1 => Collection::Mailbox,
2 => Collection::Thread,
3 => Collection::Identity,
4 => Collection::EmailSubmission,
5 => Collection::SieveScript,
6 => Collection::PushSubscription,
7 => Collection::Principal,
8 => Collection::Calendar,
9 => Collection::CalendarEvent,
10 => Collection::AddressBook,
11 => Collection::ContactCard,
12 => Collection::FileNode,
13 => Collection::CalendarEventNotification,
_ => Collection::None,
}
}
}
impl From<u8> for SyncCollection {
fn from(v: u8) -> Self {
match v {
0 => SyncCollection::Email,
1 => SyncCollection::Thread,
2 => SyncCollection::Calendar,
3 => SyncCollection::AddressBook,
4 => SyncCollection::FileNode,
5 => SyncCollection::Identity,
6 => SyncCollection::EmailSubmission,
7 => SyncCollection::SieveScript,
8 => SyncCollection::CalendarEventNotification,
9 => SyncCollection::ShareNotification,
_ => SyncCollection::None,
}
}
}
impl From<u64> for SyncCollection {
fn from(v: u64) -> Self {
match v {
0 => SyncCollection::Email,
1 => SyncCollection::Thread,
2 => SyncCollection::Calendar,
3 => SyncCollection::AddressBook,
4 => SyncCollection::FileNode,
5 => SyncCollection::Identity,
6 => SyncCollection::EmailSubmission,
7 => SyncCollection::SieveScript,
8 => SyncCollection::CalendarEventNotification,
9 => SyncCollection::ShareNotification,
_ => SyncCollection::None,
}
}
}
impl From<u64> for Collection {
fn from(v: u64) -> Self {
match v {
0 => Collection::Email,
1 => Collection::Mailbox,
2 => Collection::Thread,
3 => Collection::Identity,
4 => Collection::EmailSubmission,
5 => Collection::SieveScript,
6 => Collection::PushSubscription,
7 => Collection::Principal,
8 => Collection::Calendar,
9 => Collection::CalendarEvent,
10 => Collection::AddressBook,
11 => Collection::ContactCard,
12 => Collection::FileNode,
13 => Collection::CalendarEventNotification,
_ => Collection::None,
}
}
}
impl From<Collection> for u8 {
fn from(v: Collection) -> Self {
v as u8
}
}
impl From<SyncCollection> for u8 {
fn from(v: SyncCollection) -> Self {
v as u8
}
}
impl From<SyncCollection> for u64 {
fn from(v: SyncCollection) -> Self {
v as u64
}
}
impl From<VanishedCollection> for u8 {
fn from(v: VanishedCollection) -> Self {
v as u8
}
}
impl From<Collection> for u64 {
fn from(collection: Collection) -> u64 {
collection as u64
}
}
impl TryFrom<Collection> for DataType {
type Error = ();
fn try_from(value: Collection) -> Result<Self, Self::Error> {
match value {
Collection::Email => Ok(DataType::Email),
Collection::Mailbox => Ok(DataType::Mailbox),
Collection::Thread => Ok(DataType::Thread),
Collection::Identity => Ok(DataType::Identity),
Collection::EmailSubmission => Ok(DataType::EmailSubmission),
Collection::SieveScript => Ok(DataType::SieveScript),
Collection::PushSubscription => Ok(DataType::PushSubscription),
Collection::Principal => Ok(DataType::Principal),
Collection::Calendar => Ok(DataType::Calendar),
Collection::CalendarEvent => Ok(DataType::CalendarEvent),
Collection::AddressBook => Ok(DataType::AddressBook),
Collection::ContactCard => Ok(DataType::ContactCard),
Collection::FileNode => Ok(DataType::FileNode),
Collection::CalendarEventNotification => Ok(DataType::CalendarEventNotification),
_ => Err(()),
}
}
}
impl TryFrom<DataType> for Collection {
type Error = ();
fn try_from(value: DataType) -> Result<Self, Self::Error> {
match value {
DataType::Email => Ok(Collection::Email),
DataType::Mailbox => Ok(Collection::Mailbox),
DataType::Thread => Ok(Collection::Thread),
DataType::Identity => Ok(Collection::Identity),
DataType::EmailSubmission => Ok(Collection::EmailSubmission),
DataType::SieveScript => Ok(Collection::SieveScript),
DataType::PushSubscription => Ok(Collection::PushSubscription),
DataType::Principal => Ok(Collection::Principal),
DataType::Calendar => Ok(Collection::Calendar),
DataType::CalendarEvent => Ok(Collection::CalendarEvent),
DataType::AddressBook => Ok(Collection::AddressBook),
DataType::ContactCard => Ok(Collection::ContactCard),
DataType::FileNode => Ok(Collection::FileNode),
DataType::CalendarEventNotification => Ok(Collection::CalendarEventNotification),
_ => Err(()),
}
}
}
impl Display for Collection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl Collection {
pub fn as_str(&self) -> &'static str {
match self {
Collection::PushSubscription => "pushSubscription",
Collection::Email => "email",
Collection::Mailbox => "mailbox",
Collection::Thread => "thread",
Collection::Identity => "identity",
Collection::EmailSubmission => "emailSubmission",
Collection::SieveScript => "sieveScript",
Collection::Principal => "principal",
Collection::Calendar => "calendar",
Collection::CalendarEvent => "calendarEvent",
Collection::AddressBook => "addressBook",
Collection::ContactCard => "contactCard",
Collection::FileNode => "fileNode",
Collection::CalendarEventNotification => "calendarEventNotification",
Collection::None => "",
}
}
pub fn as_config_case(&self) -> &'static str {
match self {
Collection::PushSubscription => "push-subscription",
Collection::Email => "email",
Collection::Mailbox => "mailbox",
Collection::Thread => "thread",
Collection::Identity => "identity",
Collection::EmailSubmission => "email-submission",
Collection::SieveScript => "sieve-script",
Collection::Principal => "principal",
Collection::Calendar => "calendar",
Collection::CalendarEvent => "calendar-event",
Collection::AddressBook => "address-book",
Collection::ContactCard => "contact-card",
Collection::FileNode => "file-node",
Collection::CalendarEventNotification => "calendar-event-notification",
Collection::None => "",
}
}
}
impl FromStr for Collection {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
hashify::tiny_map!(s.as_bytes(),
"pushSubscription" => Collection::PushSubscription,
"email" => Collection::Email,
"mailbox" => Collection::Mailbox,
"thread" => Collection::Thread,
"identity" => Collection::Identity,
"emailSubmission" => Collection::EmailSubmission,
"sieveScript" => Collection::SieveScript,
"principal" => Collection::Principal,
"calendar" => Collection::Calendar,
"calendarEvent" => Collection::CalendarEvent,
"addressBook" => Collection::AddressBook,
"contactCard" => Collection::ContactCard,
"fileNode" => Collection::FileNode,
"calendarEventNotification" => Collection::CalendarEventNotification,
)
.ok_or(())
}
}
impl From<Collection> for trc::Value {
fn from(value: Collection) -> Self {
trc::Value::String(CompactString::const_new(value.as_str()))
}
}
impl BitmapItem for Collection {
fn max() -> u64 {
Collection::None as u64
}
fn is_valid(&self) -> bool {
!matches!(self, Collection::None)
}
}
impl BitmapItem for SyncCollection {
fn max() -> u64 {
SyncCollection::None as u64
}
fn is_valid(&self) -> bool {
!matches!(self, SyncCollection::None)
}
}
impl SyncCollection {
pub fn as_str(&self) -> &'static str {
match self {
SyncCollection::Email => "email",
SyncCollection::Thread => "thread",
SyncCollection::Calendar => "calendar",
SyncCollection::AddressBook => "addressBook",
SyncCollection::FileNode => "fileNode",
SyncCollection::Identity => "identity",
SyncCollection::EmailSubmission => "emailSubmission",
SyncCollection::SieveScript => "sieveScript",
SyncCollection::CalendarEventNotification => "calendarEventNotification",
SyncCollection::ShareNotification => "shareNotification",
SyncCollection::None => "",
}
}
}
+191
View File
@@ -0,0 +1,191 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "test_mode", serde(tag = "type", content = "data"))]
#[rkyv(derive(Debug))]
pub enum DeadPropertyTag {
ElementStart(DeadElementTag),
ElementEnd,
Text(String),
}
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
#[rkyv(derive(Debug))]
pub struct DeadElementTag {
pub name: String,
pub attrs: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "test_mode", serde(transparent))]
#[rkyv(derive(Debug))]
#[repr(transparent)]
pub struct DeadProperty(pub Vec<DeadPropertyTag>);
impl From<&ArchivedDeadProperty> for DeadProperty {
fn from(value: &ArchivedDeadProperty) -> Self {
DeadProperty(value.0.iter().map(|tag| tag.into()).collect::<Vec<_>>())
}
}
impl From<&ArchivedDeadPropertyTag> for DeadPropertyTag {
fn from(tag: &ArchivedDeadPropertyTag) -> Self {
match tag {
ArchivedDeadPropertyTag::ElementStart(tag) => DeadPropertyTag::ElementStart(tag.into()),
ArchivedDeadPropertyTag::ElementEnd => DeadPropertyTag::ElementEnd,
ArchivedDeadPropertyTag::Text(tag) => DeadPropertyTag::Text(tag.to_string()),
}
}
}
impl From<&ArchivedDeadElementTag> for DeadElementTag {
fn from(tag: &ArchivedDeadElementTag) -> Self {
DeadElementTag {
name: tag.name.to_string(),
attrs: tag.attrs.as_ref().map(|s| s.to_string()),
}
}
}
impl ArchivedDeadProperty {
pub fn find_tag(&self, needle: &str) -> Option<DeadProperty> {
let mut depth: u32 = 0;
let mut tags = Vec::new();
let mut found_tag = false;
for tag in self.0.iter() {
match tag {
ArchivedDeadPropertyTag::ElementStart(start) => {
if depth == 0 && start.name == needle {
found_tag = true;
} else if found_tag {
tags.push(tag.into());
}
depth += 1;
}
ArchivedDeadPropertyTag::ElementEnd => {
if found_tag {
if depth == 1 {
break;
} else {
tags.push(tag.into());
}
}
depth = depth.saturating_sub(1);
}
ArchivedDeadPropertyTag::Text(_) => {
if found_tag {
tags.push(tag.into());
}
}
}
}
if found_tag {
Some(DeadProperty(tags))
} else {
None
}
}
}
impl DeadProperty {
pub fn remove_element(&mut self, element: &DeadElementTag) {
let mut depth = 0;
let mut remove = false;
self.0.retain(|item| match item {
DeadPropertyTag::ElementStart(tag) => {
if depth == 0 && !remove && tag.name == element.name {
remove = true;
}
depth += 1;
!remove
}
DeadPropertyTag::ElementEnd => {
depth -= 1;
if remove && depth == 0 {
remove = false;
false
} else {
!remove
}
}
_ => !remove,
});
}
pub fn add_element(&mut self, element: DeadElementTag, values: Vec<DeadPropertyTag>) {
self.0.push(DeadPropertyTag::ElementStart(element));
self.0.extend(values);
self.0.push(DeadPropertyTag::ElementEnd);
}
pub fn size(&self) -> usize {
let mut size = 0;
for item in &self.0 {
match item {
DeadPropertyTag::ElementStart(tag) => {
size += tag.size();
}
DeadPropertyTag::ElementEnd => {
size += 1;
}
DeadPropertyTag::Text(text) => {
size += text.len();
}
}
}
size
}
}
impl ArchivedDeadProperty {
pub fn size(&self) -> usize {
let mut size = 0;
for item in self.0.iter() {
match item {
ArchivedDeadPropertyTag::ElementStart(tag) => {
size += tag.size();
}
ArchivedDeadPropertyTag::ElementEnd => {
size += 1;
}
ArchivedDeadPropertyTag::Text(text) => {
size += text.len();
}
}
}
size
}
}
impl DeadElementTag {
pub fn new(name: String, attrs: Option<String>) -> Self {
DeadElementTag { name, attrs }
}
pub fn size(&self) -> usize {
self.name.len() + self.attrs.as_ref().map_or(0, |attrs| attrs.len())
}
}
impl ArchivedDeadElementTag {
pub fn size(&self) -> usize {
self.name.len() + self.attrs.as_ref().map_or(0, |attrs| attrs.len())
}
}
impl Default for DeadProperty {
fn default() -> Self {
DeadProperty(Vec::with_capacity(4))
}
}
+260
View File
@@ -0,0 +1,260 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
const ARCHIVE_FIELD: u8 = 50;
pub trait FieldType: Into<u8> + Copy + std::fmt::Debug + PartialEq + Eq {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Field(u8);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ContactField {
Uid,
Email,
Archive,
CreatedToUpdated,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CalendarEventField {
Uid,
Archive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CalendarNotificationField {
CreatedToId,
Archive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum EmailField {
Archive,
Metadata,
Threading,
DeletedAt,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum MailboxField {
UidCounter = 84,
Archive = ARCHIVE_FIELD,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum SieveField {
Name,
Ids,
Archive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum EmailSubmissionField {
Archive,
Metadata,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum IdentityField {
Archive,
DocumentId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum PrincipalField {
Archive = ARCHIVE_FIELD,
ParticipantIdentities = 45,
IdentityAddresses = 110,
DefaultCalendarId = 47,
DefaultAddressBookId = 48,
ActiveScriptId = 49,
PushSubscriptions = 44,
}
impl From<ContactField> for u8 {
fn from(value: ContactField) -> Self {
match value {
ContactField::Uid => 0,
ContactField::Email => 1,
ContactField::CreatedToUpdated => 2,
ContactField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<CalendarEventField> for u8 {
fn from(value: CalendarEventField) -> Self {
match value {
CalendarEventField::Uid => 0,
CalendarEventField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<CalendarNotificationField> for u8 {
fn from(value: CalendarNotificationField) -> Self {
match value {
CalendarNotificationField::CreatedToId => 0,
CalendarNotificationField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<EmailField> for u8 {
fn from(value: EmailField) -> Self {
match value {
EmailField::Metadata => 71,
EmailField::Threading => 90,
EmailField::DeletedAt => 91,
EmailField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<MailboxField> for u8 {
fn from(value: MailboxField) -> Self {
match value {
MailboxField::UidCounter => 84,
MailboxField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<SieveField> for u8 {
fn from(value: SieveField) -> Self {
match value {
SieveField::Name => 13,
SieveField::Ids => 84,
SieveField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<EmailSubmissionField> for u8 {
fn from(value: EmailSubmissionField) -> Self {
match value {
EmailSubmissionField::Metadata => 49,
EmailSubmissionField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<PrincipalField> for u8 {
fn from(value: PrincipalField) -> Self {
match value {
PrincipalField::ParticipantIdentities => 45,
PrincipalField::IdentityAddresses => 110,
PrincipalField::DefaultCalendarId => 47,
PrincipalField::DefaultAddressBookId => 48,
PrincipalField::ActiveScriptId => 49,
PrincipalField::PushSubscriptions => 44,
PrincipalField::Archive => ARCHIVE_FIELD,
}
}
}
impl From<IdentityField> for u8 {
fn from(value: IdentityField) -> Self {
match value {
IdentityField::Archive => ARCHIVE_FIELD,
IdentityField::DocumentId => 51,
}
}
}
impl From<Field> for u8 {
fn from(value: Field) -> Self {
value.0
}
}
impl From<ContactField> for Field {
fn from(value: ContactField) -> Self {
Field(u8::from(value))
}
}
impl From<CalendarEventField> for Field {
fn from(value: CalendarEventField) -> Self {
Field(u8::from(value))
}
}
impl From<CalendarNotificationField> for Field {
fn from(value: CalendarNotificationField) -> Self {
Field(u8::from(value))
}
}
impl From<EmailField> for Field {
fn from(value: EmailField) -> Self {
Field(u8::from(value))
}
}
impl From<MailboxField> for Field {
fn from(value: MailboxField) -> Self {
Field(u8::from(value))
}
}
impl From<PrincipalField> for Field {
fn from(value: PrincipalField) -> Self {
Field(u8::from(value))
}
}
impl From<SieveField> for Field {
fn from(value: SieveField) -> Self {
Field(u8::from(value))
}
}
impl From<EmailSubmissionField> for Field {
fn from(value: EmailSubmissionField) -> Self {
Field(u8::from(value))
}
}
impl From<IdentityField> for Field {
fn from(value: IdentityField) -> Self {
Field(u8::from(value))
}
}
impl Field {
pub const ARCHIVE: Field = Field(ARCHIVE_FIELD);
pub fn new(value: u8) -> Self {
Field(value)
}
pub fn inner(&self) -> u8 {
self.0
}
}
impl FieldType for Field {}
impl FieldType for ContactField {}
impl FieldType for CalendarEventField {}
impl FieldType for CalendarNotificationField {}
impl FieldType for EmailField {}
impl FieldType for MailboxField {}
impl FieldType for PrincipalField {}
impl FieldType for SieveField {}
impl FieldType for EmailSubmissionField {}
impl FieldType for IdentityField {}
+264
View File
@@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::DocumentId;
use jmap_tools::{Element, Property, Value};
use std::{ops::Deref, str::FromStr};
use utils::codec::base32_custom::{BASE32_ALPHABET, BASE32_INVERSE};
#[derive(
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
Debug,
Clone,
PartialEq,
Eq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[rkyv(derive(Debug), compare(PartialEq))]
#[repr(transparent)]
pub struct Id(u64);
impl Default for Id {
fn default() -> Self {
Id(u64::MAX)
}
}
impl FromStr for Id {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut id = 0;
for &ch in s.as_bytes() {
let i = BASE32_INVERSE[ch as usize];
if i != u8::MAX {
id = (id << 5) | i as u64;
} else {
return Err(());
}
}
Ok(Id(id))
}
}
impl From<&ArchivedId> for Id {
fn from(value: &ArchivedId) -> Self {
Id(value.0.to_native())
}
}
impl Id {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn singleton() -> Self {
Self::new(20080258862541)
}
// From https://github.com/archer884/crockford by J/A <[email protected]>
// License: MIT/Apache 2.0
pub fn as_string(&self) -> String {
match self.0 {
0 => "a".to_string(),
mut n => {
// Used for the initial shift.
const QUAD_SHIFT: usize = 60;
const QUAD_RESET: usize = 4;
// Used for all subsequent shifts.
const FIVE_SHIFT: usize = 59;
const FIVE_RESET: usize = 5;
// After we clear the four most significant bits, the four least significant bits will be
// replaced with 0001. We can then know to stop once the four most significant bits are,
// likewise, 0001.
const STOP_BIT: u64 = 1 << QUAD_SHIFT;
let mut buf = String::with_capacity(7);
// Start by getting the most significant four bits. We get four here because these would be
// leftovers when starting from the least significant bits. In either case, tag the four least
// significant bits with our stop bit.
match (n >> QUAD_SHIFT) as usize {
// Eat leading zero-bits. This should not be done if the first four bits were non-zero.
// Additionally, we *must* do this in increments of five bits.
0 => {
n <<= QUAD_RESET;
n |= 1;
n <<= n.leading_zeros() / 5 * 5;
}
// Write value of first four bytes.
i => {
n <<= QUAD_RESET;
n |= 1;
buf.push(char::from(BASE32_ALPHABET[i]));
}
}
// From now until we reach the stop bit, take the five most significant bits and then shift
// left by five bits.
while n != STOP_BIT {
buf.push(char::from(BASE32_ALPHABET[(n >> FIVE_SHIFT) as usize]));
n <<= FIVE_RESET;
}
buf
}
}
}
#[inline(always)]
pub fn from_parts(prefix_id: DocumentId, doc_id: DocumentId) -> Id {
Id(((prefix_id as u64) << 32) | doc_id as u64)
}
#[inline(always)]
pub fn id(&self) -> u64 {
self.0
}
#[inline(always)]
pub fn document_id(&self) -> DocumentId {
self.0 as DocumentId
}
#[inline(always)]
pub fn prefix_id(&self) -> DocumentId {
(self.0 >> 32) as DocumentId
}
#[inline(always)]
pub fn is_singleton(&self) -> bool {
self.0 == 20080258862541
}
#[inline(always)]
pub fn is_valid(&self) -> bool {
self.0 != u64::MAX
}
}
impl From<u64> for Id {
fn from(id: u64) -> Self {
Id(id)
}
}
impl From<u32> for Id {
fn from(id: u32) -> Self {
Id(id as u64)
}
}
impl From<Id> for u64 {
fn from(id: Id) -> Self {
id.0
}
}
impl From<&Id> for u64 {
fn from(id: &Id) -> Self {
id.0
}
}
impl From<(u32, u32)> for Id {
fn from(id: (u32, u32)) -> Self {
Id::from_parts(id.0, id.1)
}
}
impl Deref for Id {
type Target = u64;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<u64> for Id {
fn as_ref(&self) -> &u64 {
&self.0
}
}
impl From<Id> for u32 {
fn from(id: Id) -> Self {
id.document_id()
}
}
impl From<Id> for String {
fn from(id: Id) -> Self {
id.as_string()
}
}
impl serde::Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_string().as_str())
}
}
impl<'de> serde::Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Id::from_str(<&str>::deserialize(deserializer)?)
.map_err(|_| serde::de::Error::custom("invalid JMAP ID"))
}
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.as_string())
}
}
impl<'x, P: Property, E: Element + From<Id>> From<Id> for Value<'x, P, E> {
fn from(id: Id) -> Self {
Value::Element(E::from(id))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::id::Id;
#[test]
fn parse_jmap_id() {
for number in [
0,
1,
10,
1000,
Id::singleton().id(),
u64::MAX / 2,
u64::MAX - 1,
u64::MAX,
] {
let id = Id::from(number);
assert_eq!(Id::from_str(&id.to_string()).unwrap(), id);
}
Id::from_str("p333333333333p333333333333").unwrap();
}
}
+538
View File
@@ -0,0 +1,538 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Property, Value};
use std::{fmt::Display, str::FromStr};
pub const SEEN: usize = 0;
pub const DRAFT: usize = 1;
pub const FLAGGED: usize = 2;
pub const ANSWERED: usize = 3;
pub const RECENT: usize = 4;
pub const IMPORTANT: usize = 5;
pub const PHISHING: usize = 6;
pub const JUNK: usize = 7;
pub const NOTJUNK: usize = 8;
pub const DELETED: usize = 9;
pub const FORWARDED: usize = 10;
pub const MDN_SENT: usize = 11;
pub const AUTOSENT: usize = 12;
pub const CANUNSUBSCRIBE: usize = 13;
pub const FOLLOWED: usize = 14;
pub const HASATTACHMENT: usize = 15;
pub const HASMEMO: usize = 16;
pub const HASNOATTACHMENT: usize = 17;
pub const IMPORTED: usize = 18;
pub const ISTRUSTED: usize = 19;
pub const MAILFLAGBIT0: usize = 20;
pub const MAILFLAGBIT1: usize = 21;
pub const MAILFLAGBIT2: usize = 22;
pub const MASKEDEMAIL: usize = 23;
pub const MEMO: usize = 24;
pub const MUTED: usize = 25;
pub const NEW: usize = 26;
pub const NOTIFY: usize = 27;
pub const UNSUBSCRIBED: usize = 28;
pub const OTHER: usize = 29;
#[derive(
rkyv::Serialize,
rkyv::Deserialize,
rkyv::Archive,
Debug,
Clone,
PartialEq,
Eq,
Hash,
Default,
PartialOrd,
Ord,
serde::Serialize,
)]
#[serde(untagged)]
#[rkyv(derive(PartialEq), compare(PartialEq))]
pub enum Keyword {
Other(Box<str>),
#[serde(rename(serialize = "$seen"))]
Seen,
#[serde(rename(serialize = "$draft"))]
Draft,
#[serde(rename(serialize = "$flagged"))]
Flagged,
#[serde(rename(serialize = "$answered"))]
Answered,
#[default]
#[serde(rename(serialize = "$recent"))]
Recent,
#[serde(rename(serialize = "$important"))]
Important,
#[serde(rename(serialize = "$phishing"))]
Phishing,
#[serde(rename(serialize = "$junk"))]
Junk,
#[serde(rename(serialize = "$notjunk"))]
NotJunk,
#[serde(rename(serialize = "$deleted"))]
Deleted,
#[serde(rename(serialize = "$forwarded"))]
Forwarded,
#[serde(rename(serialize = "$mdnsent"))]
MdnSent,
#[serde(rename(serialize = "$autosent"))]
Autosent,
#[serde(rename(serialize = "$canunsubscribe"))]
CanUnsubscribe,
#[serde(rename(serialize = "$followed"))]
Followed,
#[serde(rename(serialize = "$hasattachment"))]
HasAttachment,
#[serde(rename(serialize = "$hasmemo"))]
HasMemo,
#[serde(rename(serialize = "$hasnoattachment"))]
HasNoAttachment,
#[serde(rename(serialize = "$imported"))]
Imported,
#[serde(rename(serialize = "$istrusted"))]
IsTrusted,
#[serde(rename(serialize = "$MailFlagBit0"))]
MailFlagBit0,
#[serde(rename(serialize = "$MailFlagBit1"))]
MailFlagBit1,
#[serde(rename(serialize = "$MailFlagBit2"))]
MailFlagBit2,
#[serde(rename(serialize = "$maskedemail"))]
MaskedEmail,
#[serde(rename(serialize = "$memo"))]
Memo,
#[serde(rename(serialize = "$muted"))]
Muted,
#[serde(rename(serialize = "$new"))]
New,
#[serde(rename(serialize = "$notify"))]
Notify,
#[serde(rename(serialize = "$unsubscribed"))]
Unsubscribed,
}
impl Keyword {
pub const MAX_LENGTH: usize = 128;
pub fn parse(value: &str) -> Self {
Self::try_parse(value)
.unwrap_or_else(|| Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect()))
}
pub fn from_other(value: String) -> Self {
if value.len() <= Keyword::MAX_LENGTH {
Keyword::Other(value.into_boxed_str())
} else {
Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect())
}
}
pub fn from_boxed_other(value: Box<str>) -> Self {
if value.len() <= Keyword::MAX_LENGTH {
Keyword::Other(value)
} else {
Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect())
}
}
pub fn try_parse(value: &str) -> Option<Self> {
value
.split_at_checked(1)
.filter(|(prefix, _)| matches!(*prefix, "$" | "\\"))
.and_then(|(_, rest)| {
hashify::tiny_map_ignore_case!(rest.as_bytes(),
"seen" => Keyword::Seen,
"draft" => Keyword::Draft,
"flagged" => Keyword::Flagged,
"answered" => Keyword::Answered,
"recent" => Keyword::Recent,
"important" => Keyword::Important,
"phishing" => Keyword::Phishing,
"junk" => Keyword::Junk,
"notjunk" => Keyword::NotJunk,
"deleted" => Keyword::Deleted,
"forwarded" => Keyword::Forwarded,
"mdnsent" => Keyword::MdnSent,
"autosent" => Keyword::Autosent,
"canunsubscribe" => Keyword::CanUnsubscribe,
"followed" => Keyword::Followed,
"hasattachment" => Keyword::HasAttachment,
"hasmemo" => Keyword::HasMemo,
"hasnoattachment" => Keyword::HasNoAttachment,
"imported" => Keyword::Imported,
"istrusted" => Keyword::IsTrusted,
"mailflagbit0" => Keyword::MailFlagBit0,
"mailflagbit1" => Keyword::MailFlagBit1,
"mailflagbit2" => Keyword::MailFlagBit2,
"maskedemail" => Keyword::MaskedEmail,
"memo" => Keyword::Memo,
"muted" => Keyword::Muted,
"new" => Keyword::New,
"notify" => Keyword::Notify,
"unsubscribed" => Keyword::Unsubscribed,
)
})
}
pub fn id(&self) -> Result<u32, &str> {
match self {
Keyword::Seen => Ok(SEEN as u32),
Keyword::Draft => Ok(DRAFT as u32),
Keyword::Flagged => Ok(FLAGGED as u32),
Keyword::Answered => Ok(ANSWERED as u32),
Keyword::Recent => Ok(RECENT as u32),
Keyword::Important => Ok(IMPORTANT as u32),
Keyword::Phishing => Ok(PHISHING as u32),
Keyword::Junk => Ok(JUNK as u32),
Keyword::NotJunk => Ok(NOTJUNK as u32),
Keyword::Deleted => Ok(DELETED as u32),
Keyword::Forwarded => Ok(FORWARDED as u32),
Keyword::MdnSent => Ok(MDN_SENT as u32),
Keyword::Autosent => Ok(AUTOSENT as u32),
Keyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
Keyword::Followed => Ok(FOLLOWED as u32),
Keyword::HasAttachment => Ok(HASATTACHMENT as u32),
Keyword::HasMemo => Ok(HASMEMO as u32),
Keyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
Keyword::Imported => Ok(IMPORTED as u32),
Keyword::IsTrusted => Ok(ISTRUSTED as u32),
Keyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
Keyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
Keyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
Keyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
Keyword::Memo => Ok(MEMO as u32),
Keyword::Muted => Ok(MUTED as u32),
Keyword::New => Ok(NEW as u32),
Keyword::Notify => Ok(NOTIFY as u32),
Keyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
Keyword::Other(string) => Err(string.as_ref()),
}
}
pub fn into_id(self) -> Result<u32, Box<str>> {
match self {
Keyword::Seen => Ok(SEEN as u32),
Keyword::Draft => Ok(DRAFT as u32),
Keyword::Flagged => Ok(FLAGGED as u32),
Keyword::Answered => Ok(ANSWERED as u32),
Keyword::Recent => Ok(RECENT as u32),
Keyword::Important => Ok(IMPORTANT as u32),
Keyword::Phishing => Ok(PHISHING as u32),
Keyword::Junk => Ok(JUNK as u32),
Keyword::NotJunk => Ok(NOTJUNK as u32),
Keyword::Deleted => Ok(DELETED as u32),
Keyword::Forwarded => Ok(FORWARDED as u32),
Keyword::MdnSent => Ok(MDN_SENT as u32),
Keyword::Autosent => Ok(AUTOSENT as u32),
Keyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
Keyword::Followed => Ok(FOLLOWED as u32),
Keyword::HasAttachment => Ok(HASATTACHMENT as u32),
Keyword::HasMemo => Ok(HASMEMO as u32),
Keyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
Keyword::Imported => Ok(IMPORTED as u32),
Keyword::IsTrusted => Ok(ISTRUSTED as u32),
Keyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
Keyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
Keyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
Keyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
Keyword::Memo => Ok(MEMO as u32),
Keyword::Muted => Ok(MUTED as u32),
Keyword::New => Ok(NEW as u32),
Keyword::Notify => Ok(NOTIFY as u32),
Keyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
Keyword::Other(string) => Err(string),
}
}
pub fn try_from_id(id: usize) -> Result<Self, usize> {
match id {
SEEN => Ok(Keyword::Seen),
DRAFT => Ok(Keyword::Draft),
FLAGGED => Ok(Keyword::Flagged),
ANSWERED => Ok(Keyword::Answered),
RECENT => Ok(Keyword::Recent),
IMPORTANT => Ok(Keyword::Important),
PHISHING => Ok(Keyword::Phishing),
JUNK => Ok(Keyword::Junk),
NOTJUNK => Ok(Keyword::NotJunk),
DELETED => Ok(Keyword::Deleted),
FORWARDED => Ok(Keyword::Forwarded),
MDN_SENT => Ok(Keyword::MdnSent),
AUTOSENT => Ok(Keyword::Autosent),
CANUNSUBSCRIBE => Ok(Keyword::CanUnsubscribe),
FOLLOWED => Ok(Keyword::Followed),
HASATTACHMENT => Ok(Keyword::HasAttachment),
HASMEMO => Ok(Keyword::HasMemo),
HASNOATTACHMENT => Ok(Keyword::HasNoAttachment),
IMPORTED => Ok(Keyword::Imported),
ISTRUSTED => Ok(Keyword::IsTrusted),
MAILFLAGBIT0 => Ok(Keyword::MailFlagBit0),
MAILFLAGBIT1 => Ok(Keyword::MailFlagBit1),
MAILFLAGBIT2 => Ok(Keyword::MailFlagBit2),
MASKEDEMAIL => Ok(Keyword::MaskedEmail),
MEMO => Ok(Keyword::Memo),
MUTED => Ok(Keyword::Muted),
NEW => Ok(Keyword::New),
NOTIFY => Ok(Keyword::Notify),
UNSUBSCRIBED => Ok(Keyword::Unsubscribed),
_ => Err(id),
}
}
}
impl From<String> for Keyword {
fn from(value: String) -> Self {
Keyword::try_parse(&value).unwrap_or_else(|| Keyword::from_other(value))
}
}
impl Display for Keyword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Keyword::Seen => write!(f, "$seen"),
Keyword::Draft => write!(f, "$draft"),
Keyword::Flagged => write!(f, "$flagged"),
Keyword::Answered => write!(f, "$answered"),
Keyword::Recent => write!(f, "$recent"),
Keyword::Important => write!(f, "$important"),
Keyword::Phishing => write!(f, "$phishing"),
Keyword::Junk => write!(f, "$junk"),
Keyword::NotJunk => write!(f, "$notjunk"),
Keyword::Deleted => write!(f, "$deleted"),
Keyword::Forwarded => write!(f, "$forwarded"),
Keyword::MdnSent => write!(f, "$mdnsent"),
Keyword::Autosent => write!(f, "$autosent"),
Keyword::CanUnsubscribe => write!(f, "$canunsubscribe"),
Keyword::Followed => write!(f, "$followed"),
Keyword::HasAttachment => write!(f, "$hasattachment"),
Keyword::HasMemo => write!(f, "$hasmemo"),
Keyword::HasNoAttachment => write!(f, "$hasnoattachment"),
Keyword::Imported => write!(f, "$imported"),
Keyword::IsTrusted => write!(f, "$istrusted"),
Keyword::MailFlagBit0 => write!(f, "$MailFlagBit0"),
Keyword::MailFlagBit1 => write!(f, "$MailFlagBit1"),
Keyword::MailFlagBit2 => write!(f, "$MailFlagBit2"),
Keyword::MaskedEmail => write!(f, "$maskedemail"),
Keyword::Memo => write!(f, "$memo"),
Keyword::Muted => write!(f, "$muted"),
Keyword::New => write!(f, "$new"),
Keyword::Notify => write!(f, "$notify"),
Keyword::Unsubscribed => write!(f, "$unsubscribed"),
Keyword::Other(s) => write!(f, "{}", s),
}
}
}
impl Display for ArchivedKeyword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArchivedKeyword::Seen => write!(f, "$seen"),
ArchivedKeyword::Draft => write!(f, "$draft"),
ArchivedKeyword::Flagged => write!(f, "$flagged"),
ArchivedKeyword::Answered => write!(f, "$answered"),
ArchivedKeyword::Recent => write!(f, "$recent"),
ArchivedKeyword::Important => write!(f, "$important"),
ArchivedKeyword::Phishing => write!(f, "$phishing"),
ArchivedKeyword::Junk => write!(f, "$junk"),
ArchivedKeyword::NotJunk => write!(f, "$notjunk"),
ArchivedKeyword::Deleted => write!(f, "$deleted"),
ArchivedKeyword::Forwarded => write!(f, "$forwarded"),
ArchivedKeyword::MdnSent => write!(f, "$mdnsent"),
ArchivedKeyword::Autosent => write!(f, "$autosent"),
ArchivedKeyword::CanUnsubscribe => write!(f, "$canunsubscribe"),
ArchivedKeyword::Followed => write!(f, "$followed"),
ArchivedKeyword::HasAttachment => write!(f, "$hasattachment"),
ArchivedKeyword::HasMemo => write!(f, "$hasmemo"),
ArchivedKeyword::HasNoAttachment => write!(f, "$hasnoattachment"),
ArchivedKeyword::Imported => write!(f, "$imported"),
ArchivedKeyword::IsTrusted => write!(f, "$istrusted"),
ArchivedKeyword::MailFlagBit0 => write!(f, "$MailFlagBit0"),
ArchivedKeyword::MailFlagBit1 => write!(f, "$MailFlagBit1"),
ArchivedKeyword::MailFlagBit2 => write!(f, "$MailFlagBit2"),
ArchivedKeyword::MaskedEmail => write!(f, "$maskedemail"),
ArchivedKeyword::Memo => write!(f, "$memo"),
ArchivedKeyword::Muted => write!(f, "$muted"),
ArchivedKeyword::New => write!(f, "$new"),
ArchivedKeyword::Notify => write!(f, "$notify"),
ArchivedKeyword::Unsubscribed => write!(f, "$unsubscribed"),
ArchivedKeyword::Other(s) => write!(f, "{}", s),
}
}
}
impl From<Keyword> for Vec<u8> {
fn from(keyword: Keyword) -> Self {
match keyword {
Keyword::Seen => vec![SEEN as u8],
Keyword::Draft => vec![DRAFT as u8],
Keyword::Flagged => vec![FLAGGED as u8],
Keyword::Answered => vec![ANSWERED as u8],
Keyword::Recent => vec![RECENT as u8],
Keyword::Important => vec![IMPORTANT as u8],
Keyword::Phishing => vec![PHISHING as u8],
Keyword::Junk => vec![JUNK as u8],
Keyword::NotJunk => vec![NOTJUNK as u8],
Keyword::Deleted => vec![DELETED as u8],
Keyword::Forwarded => vec![FORWARDED as u8],
Keyword::MdnSent => vec![MDN_SENT as u8],
Keyword::Autosent => vec![AUTOSENT as u8],
Keyword::CanUnsubscribe => vec![CANUNSUBSCRIBE as u8],
Keyword::Followed => vec![FOLLOWED as u8],
Keyword::HasAttachment => vec![HASATTACHMENT as u8],
Keyword::HasMemo => vec![HASMEMO as u8],
Keyword::HasNoAttachment => vec![HASNOATTACHMENT as u8],
Keyword::Imported => vec![IMPORTED as u8],
Keyword::IsTrusted => vec![ISTRUSTED as u8],
Keyword::MailFlagBit0 => vec![MAILFLAGBIT0 as u8],
Keyword::MailFlagBit1 => vec![MAILFLAGBIT1 as u8],
Keyword::MailFlagBit2 => vec![MAILFLAGBIT2 as u8],
Keyword::MaskedEmail => vec![MASKEDEMAIL as u8],
Keyword::Memo => vec![MEMO as u8],
Keyword::Muted => vec![MUTED as u8],
Keyword::New => vec![NEW as u8],
Keyword::Notify => vec![NOTIFY as u8],
Keyword::Unsubscribed => vec![UNSUBSCRIBED as u8],
Keyword::Other(string) => string.as_bytes().to_vec(),
}
}
}
impl FromStr for Keyword {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Keyword::parse(s))
}
}
impl ArchivedKeyword {
pub fn id(&self) -> Result<u32, &str> {
match self {
ArchivedKeyword::Seen => Ok(SEEN as u32),
ArchivedKeyword::Draft => Ok(DRAFT as u32),
ArchivedKeyword::Flagged => Ok(FLAGGED as u32),
ArchivedKeyword::Answered => Ok(ANSWERED as u32),
ArchivedKeyword::Recent => Ok(RECENT as u32),
ArchivedKeyword::Important => Ok(IMPORTANT as u32),
ArchivedKeyword::Phishing => Ok(PHISHING as u32),
ArchivedKeyword::Junk => Ok(JUNK as u32),
ArchivedKeyword::NotJunk => Ok(NOTJUNK as u32),
ArchivedKeyword::Deleted => Ok(DELETED as u32),
ArchivedKeyword::Forwarded => Ok(FORWARDED as u32),
ArchivedKeyword::MdnSent => Ok(MDN_SENT as u32),
ArchivedKeyword::Autosent => Ok(AUTOSENT as u32),
ArchivedKeyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
ArchivedKeyword::Followed => Ok(FOLLOWED as u32),
ArchivedKeyword::HasAttachment => Ok(HASATTACHMENT as u32),
ArchivedKeyword::HasMemo => Ok(HASMEMO as u32),
ArchivedKeyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
ArchivedKeyword::Imported => Ok(IMPORTED as u32),
ArchivedKeyword::IsTrusted => Ok(ISTRUSTED as u32),
ArchivedKeyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
ArchivedKeyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
ArchivedKeyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
ArchivedKeyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
ArchivedKeyword::Memo => Ok(MEMO as u32),
ArchivedKeyword::Muted => Ok(MUTED as u32),
ArchivedKeyword::New => Ok(NEW as u32),
ArchivedKeyword::Notify => Ok(NOTIFY as u32),
ArchivedKeyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
ArchivedKeyword::Other(string) => Err(string.as_ref()),
}
}
pub fn to_native(&self) -> Keyword {
match self {
ArchivedKeyword::Seen => Keyword::Seen,
ArchivedKeyword::Draft => Keyword::Draft,
ArchivedKeyword::Flagged => Keyword::Flagged,
ArchivedKeyword::Answered => Keyword::Answered,
ArchivedKeyword::Recent => Keyword::Recent,
ArchivedKeyword::Important => Keyword::Important,
ArchivedKeyword::Phishing => Keyword::Phishing,
ArchivedKeyword::Junk => Keyword::Junk,
ArchivedKeyword::NotJunk => Keyword::NotJunk,
ArchivedKeyword::Deleted => Keyword::Deleted,
ArchivedKeyword::Forwarded => Keyword::Forwarded,
ArchivedKeyword::MdnSent => Keyword::MdnSent,
ArchivedKeyword::Autosent => Keyword::Autosent,
ArchivedKeyword::CanUnsubscribe => Keyword::CanUnsubscribe,
ArchivedKeyword::Followed => Keyword::Followed,
ArchivedKeyword::HasAttachment => Keyword::HasAttachment,
ArchivedKeyword::HasMemo => Keyword::HasMemo,
ArchivedKeyword::HasNoAttachment => Keyword::HasNoAttachment,
ArchivedKeyword::Imported => Keyword::Imported,
ArchivedKeyword::IsTrusted => Keyword::IsTrusted,
ArchivedKeyword::MailFlagBit0 => Keyword::MailFlagBit0,
ArchivedKeyword::MailFlagBit1 => Keyword::MailFlagBit1,
ArchivedKeyword::MailFlagBit2 => Keyword::MailFlagBit2,
ArchivedKeyword::MaskedEmail => Keyword::MaskedEmail,
ArchivedKeyword::Memo => Keyword::Memo,
ArchivedKeyword::Muted => Keyword::Muted,
ArchivedKeyword::New => Keyword::New,
ArchivedKeyword::Notify => Keyword::Notify,
ArchivedKeyword::Unsubscribed => Keyword::Unsubscribed,
ArchivedKeyword::Other(other) => Keyword::Other(other.as_ref().into()),
}
}
}
impl From<&ArchivedKeyword> for Keyword {
fn from(value: &ArchivedKeyword) -> Self {
match value {
ArchivedKeyword::Seen => Keyword::Seen,
ArchivedKeyword::Draft => Keyword::Draft,
ArchivedKeyword::Flagged => Keyword::Flagged,
ArchivedKeyword::Answered => Keyword::Answered,
ArchivedKeyword::Recent => Keyword::Recent,
ArchivedKeyword::Important => Keyword::Important,
ArchivedKeyword::Phishing => Keyword::Phishing,
ArchivedKeyword::Junk => Keyword::Junk,
ArchivedKeyword::NotJunk => Keyword::NotJunk,
ArchivedKeyword::Deleted => Keyword::Deleted,
ArchivedKeyword::Forwarded => Keyword::Forwarded,
ArchivedKeyword::MdnSent => Keyword::MdnSent,
ArchivedKeyword::Autosent => Keyword::Autosent,
ArchivedKeyword::CanUnsubscribe => Keyword::CanUnsubscribe,
ArchivedKeyword::Followed => Keyword::Followed,
ArchivedKeyword::HasAttachment => Keyword::HasAttachment,
ArchivedKeyword::HasMemo => Keyword::HasMemo,
ArchivedKeyword::HasNoAttachment => Keyword::HasNoAttachment,
ArchivedKeyword::Imported => Keyword::Imported,
ArchivedKeyword::IsTrusted => Keyword::IsTrusted,
ArchivedKeyword::MailFlagBit0 => Keyword::MailFlagBit0,
ArchivedKeyword::MailFlagBit1 => Keyword::MailFlagBit1,
ArchivedKeyword::MailFlagBit2 => Keyword::MailFlagBit2,
ArchivedKeyword::MaskedEmail => Keyword::MaskedEmail,
ArchivedKeyword::Memo => Keyword::Memo,
ArchivedKeyword::Muted => Keyword::Muted,
ArchivedKeyword::New => Keyword::New,
ArchivedKeyword::Notify => Keyword::Notify,
ArchivedKeyword::Unsubscribed => Keyword::Unsubscribed,
ArchivedKeyword::Other(string) => Keyword::Other(string.as_ref().into()),
}
}
}
impl<'de> serde::Deserialize<'de> for Keyword {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Keyword::parse(
<std::borrow::Cow<'de, str>>::deserialize(deserializer)?.as_ref(),
))
}
}
impl<'x, P: Property, E: Element + From<Keyword>> From<Keyword> for Value<'x, P, E> {
fn from(id: Keyword) -> Self {
Value::Element(E::from(id))
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod acl;
pub mod blob;
pub mod blob_hash;
pub mod collection;
pub mod dead_property;
pub mod field;
pub mod id;
pub mod keyword;
pub mod semver;
pub mod special_use;
pub mod type_state;
pub type DocumentId = u32;
pub type ChangeId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
pub struct TimeRange {
pub start: i64,
pub end: i64,
}
impl TimeRange {
pub fn new(start: i64, end: i64) -> Self {
Self { start, end }
}
pub fn is_in_range(&self, match_overlap: bool, start: i64, end: i64) -> bool {
if !match_overlap {
// RFC4791#9.9: (start < DTEND AND end > DTSTART)
self.start < end && self.end > start
} else {
// RFC4791#9.9: ((start < DUE) OR (start <= DTSTART)) AND ((end > DTSTART) OR (end >= DUE))
((start < self.end) || (start <= self.start)) && (end > self.start || end >= self.end)
}
}
}
impl Default for TimeRange {
fn default() -> Self {
Self {
start: i64::MIN,
end: i64::MAX,
}
}
}
+80
View File
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Display;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Semver(u64);
impl Semver {
pub fn current() -> Self {
env!("CARGO_PKG_VERSION").try_into().unwrap()
}
pub fn new(major: u16, minor: u16, patch: u16) -> Self {
let mut version: u64 = 0;
version |= (major as u64) << 32;
version |= (minor as u64) << 16;
version |= patch as u64;
Semver(version)
}
pub fn unpack(&self) -> (u16, u16, u16) {
let version = self.0;
let major = ((version >> 32) & 0xFFFF) as u16;
let minor = ((version >> 16) & 0xFFFF) as u16;
let patch = (version & 0xFFFF) as u16;
(major, minor, patch)
}
pub fn major(&self) -> u16 {
(self.0 >> 32) as u16
}
pub fn minor(&self) -> u16 {
(self.0 >> 16) as u16
}
pub fn patch(&self) -> u16 {
self.0 as u16
}
pub fn is_valid(&self) -> bool {
self.0 > 0
}
}
impl AsRef<u64> for Semver {
fn as_ref(&self) -> &u64 {
&self.0
}
}
impl From<u64> for Semver {
fn from(value: u64) -> Self {
Semver(value)
}
}
impl TryFrom<&str> for Semver {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut parts = value.splitn(3, '.');
let major = parts.next().ok_or(())?.parse().map_err(|_| ())?;
let minor = parts.next().ok_or(())?.parse().map_err(|_| ())?;
let patch = parts.next().ok_or(())?.parse().map_err(|_| ())?;
Ok(Semver::new(major, minor, patch))
}
}
impl Display for Semver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (major, minor, patch) = self.unpack();
write!(f, "{major}.{minor}.{patch}")
}
}
+120
View File
@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Property, Value};
#[derive(
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
PartialOrd,
Ord,
)]
#[rkyv(derive(Debug))]
pub enum SpecialUse {
Inbox,
Trash,
Junk,
Drafts,
Archive,
Sent,
Shared,
Important,
None,
Memos,
Scheduled,
Snoozed,
}
impl SpecialUse {
pub fn parse(s: &str) -> Option<Self> {
hashify::tiny_map_ignore_case!(s.as_bytes(),
b"inbox" => SpecialUse::Inbox,
b"trash" => SpecialUse::Trash,
b"junk" => SpecialUse::Junk,
b"drafts" => SpecialUse::Drafts,
b"archive" => SpecialUse::Archive,
b"sent" => SpecialUse::Sent,
b"shared" => SpecialUse::Shared,
b"important" => SpecialUse::Important,
b"memos" => SpecialUse::Memos,
b"scheduled" => SpecialUse::Scheduled,
b"snoozed" => SpecialUse::Snoozed,
)
}
#[inline(always)]
pub fn parse_use_attr(s: &str) -> Option<Self> {
Self::parse(s.strip_prefix('\\').unwrap_or(s))
}
pub fn as_str(&self) -> Option<&'static str> {
match self {
SpecialUse::Inbox => Some("inbox"),
SpecialUse::Trash => Some("trash"),
SpecialUse::Junk => Some("junk"),
SpecialUse::Drafts => Some("drafts"),
SpecialUse::Archive => Some("archive"),
SpecialUse::Sent => Some("sent"),
SpecialUse::Shared => Some("shared"),
SpecialUse::Important => Some("important"),
SpecialUse::Memos => Some("memos"),
SpecialUse::Scheduled => Some("scheduled"),
SpecialUse::Snoozed => Some("snoozed"),
SpecialUse::None => None,
}
}
}
impl ArchivedSpecialUse {
pub fn as_str(&self) -> Option<&'static str> {
match self {
ArchivedSpecialUse::Inbox => Some("inbox"),
ArchivedSpecialUse::Trash => Some("trash"),
ArchivedSpecialUse::Junk => Some("junk"),
ArchivedSpecialUse::Drafts => Some("drafts"),
ArchivedSpecialUse::Archive => Some("archive"),
ArchivedSpecialUse::Sent => Some("sent"),
ArchivedSpecialUse::Shared => Some("shared"),
ArchivedSpecialUse::Important => Some("important"),
ArchivedSpecialUse::Memos => Some("memos"),
ArchivedSpecialUse::Scheduled => Some("scheduled"),
ArchivedSpecialUse::Snoozed => Some("snoozed"),
ArchivedSpecialUse::None => None,
}
}
}
impl From<&ArchivedSpecialUse> for SpecialUse {
fn from(value: &ArchivedSpecialUse) -> Self {
match value {
ArchivedSpecialUse::Inbox => SpecialUse::Inbox,
ArchivedSpecialUse::Trash => SpecialUse::Trash,
ArchivedSpecialUse::Junk => SpecialUse::Junk,
ArchivedSpecialUse::Drafts => SpecialUse::Drafts,
ArchivedSpecialUse::Archive => SpecialUse::Archive,
ArchivedSpecialUse::Sent => SpecialUse::Sent,
ArchivedSpecialUse::Shared => SpecialUse::Shared,
ArchivedSpecialUse::Important => SpecialUse::Important,
ArchivedSpecialUse::Memos => SpecialUse::Memos,
ArchivedSpecialUse::Scheduled => SpecialUse::Scheduled,
ArchivedSpecialUse::Snoozed => SpecialUse::Snoozed,
ArchivedSpecialUse::None => SpecialUse::None,
}
}
}
impl<'x, P: Property, E: Element + From<SpecialUse>> From<SpecialUse> for Value<'x, P, E> {
fn from(id: SpecialUse) -> Self {
Value::Element(E::from(id))
}
}
+256
View File
@@ -0,0 +1,256 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::collection::SyncCollection;
use jmap_tools::{Element, Property, Value};
use serde::Serialize;
use std::{fmt::Display, str::FromStr};
use utils::map::bitmap::{Bitmap, BitmapItem};
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, PartialOrd, Ord)]
#[repr(u8)]
pub enum DataType {
#[serde(rename = "Email")]
Email = 0,
#[serde(rename = "EmailDelivery")]
EmailDelivery = 1,
#[serde(rename = "EmailSubmission")]
EmailSubmission = 2,
#[serde(rename = "Mailbox")]
Mailbox = 3,
#[serde(rename = "Thread")]
Thread = 4,
#[serde(rename = "Identity")]
Identity = 5,
#[serde(rename = "Core")]
Core = 6,
#[serde(rename = "PushSubscription")]
PushSubscription = 7,
#[serde(rename = "SearchSnippet")]
SearchSnippet = 8,
#[serde(rename = "VacationResponse")]
VacationResponse = 9,
#[serde(rename = "MDN")]
Mdn = 10,
#[serde(rename = "Quota")]
Quota = 11,
#[serde(rename = "SieveScript")]
SieveScript = 12,
#[serde(rename = "Calendar")]
Calendar = 13,
#[serde(rename = "CalendarEvent")]
CalendarEvent = 14,
#[serde(rename = "CalendarEventNotification")]
CalendarEventNotification = 15,
#[serde(rename = "AddressBook")]
AddressBook = 16,
#[serde(rename = "ContactCard")]
ContactCard = 17,
#[serde(rename = "FileNode")]
FileNode = 18,
#[serde(rename = "Principal")]
Principal = 19,
#[serde(rename = "ShareNotification")]
ShareNotification = 20,
#[serde(rename = "ParticipantIdentity")]
ParticipantIdentity = 21,
#[serde(rename = "CalendarAlert")]
CalendarAlert = 22,
None = 23,
}
#[derive(Debug, Clone, Copy)]
pub struct StateChange {
pub account_id: u32,
pub change_id: u64,
pub types: Bitmap<DataType>,
}
impl StateChange {
pub fn new(account_id: u32) -> Self {
Self {
account_id,
change_id: 0,
types: Default::default(),
}
}
pub fn set_change(&mut self, type_state: DataType) {
self.types.insert(type_state);
}
pub fn with_change(mut self, type_state: DataType) -> Self {
self.set_change(type_state);
self
}
pub fn with_change_id(mut self, change_id: u64) -> Self {
self.change_id = change_id;
self
}
pub fn has_changes(&self) -> bool {
!self.types.is_empty()
}
}
impl BitmapItem for DataType {
fn max() -> u64 {
DataType::None as u64
}
fn is_valid(&self) -> bool {
!matches!(self, DataType::None)
}
}
impl From<u64> for DataType {
fn from(value: u64) -> Self {
match value {
0 => DataType::Email,
1 => DataType::EmailDelivery,
2 => DataType::EmailSubmission,
3 => DataType::Mailbox,
4 => DataType::Thread,
5 => DataType::Identity,
6 => DataType::Core,
7 => DataType::PushSubscription,
8 => DataType::SearchSnippet,
9 => DataType::VacationResponse,
10 => DataType::Mdn,
11 => DataType::Quota,
12 => DataType::SieveScript,
13 => DataType::Calendar,
14 => DataType::CalendarEvent,
15 => DataType::CalendarEventNotification,
16 => DataType::AddressBook,
17 => DataType::ContactCard,
18 => DataType::FileNode,
19 => DataType::Principal,
20 => DataType::ShareNotification,
21 => DataType::ParticipantIdentity,
22 => DataType::CalendarAlert,
_ => {
debug_assert!(false, "Invalid type_state value: {}", value);
DataType::None
}
}
}
}
impl From<DataType> for u64 {
fn from(type_state: DataType) -> u64 {
type_state as u64
}
}
impl DataType {
pub fn try_from_sync(value: SyncCollection, is_container: bool) -> Option<Self> {
match (value, is_container) {
(SyncCollection::Email, false) => DataType::Email.into(),
(SyncCollection::Email, true) => DataType::Mailbox.into(),
(SyncCollection::Thread, _) => DataType::Thread.into(),
(SyncCollection::Calendar, true) => DataType::Calendar.into(),
(SyncCollection::Calendar, false) => DataType::CalendarEvent.into(),
(SyncCollection::AddressBook, true) => DataType::AddressBook.into(),
(SyncCollection::AddressBook, false) => DataType::ContactCard.into(),
(SyncCollection::FileNode, _) => DataType::FileNode.into(),
(SyncCollection::Identity, _) => DataType::Identity.into(),
(SyncCollection::EmailSubmission, _) => DataType::EmailSubmission.into(),
(SyncCollection::SieveScript, _) => DataType::SieveScript.into(),
_ => None,
}
}
}
impl DataType {
pub fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"Email" => DataType::Email,
b"EmailDelivery" => DataType::EmailDelivery,
b"EmailSubmission" => DataType::EmailSubmission,
b"Mailbox" => DataType::Mailbox,
b"Thread" => DataType::Thread,
b"Identity" => DataType::Identity,
b"Core" => DataType::Core,
b"PushSubscription" => DataType::PushSubscription,
b"SearchSnippet" => DataType::SearchSnippet,
b"VacationResponse" => DataType::VacationResponse,
b"MDN" => DataType::Mdn,
b"Quota" => DataType::Quota,
b"SieveScript" => DataType::SieveScript,
b"Calendar" => DataType::Calendar,
b"CalendarEvent" => DataType::CalendarEvent,
b"CalendarEventNotification" => DataType::CalendarEventNotification,
b"AddressBook" => DataType::AddressBook,
b"ContactCard" => DataType::ContactCard,
b"FileNode" => DataType::FileNode,
b"Principal" => DataType::Principal,
b"ShareNotification" => DataType::ShareNotification,
b"ParticipantIdentity" => DataType::ParticipantIdentity,
b"CalendarAlert" => DataType::CalendarAlert,
)
}
pub fn as_str(&self) -> &'static str {
match self {
DataType::Email => "Email",
DataType::EmailDelivery => "EmailDelivery",
DataType::EmailSubmission => "EmailSubmission",
DataType::Mailbox => "Mailbox",
DataType::Thread => "Thread",
DataType::Identity => "Identity",
DataType::Core => "Core",
DataType::PushSubscription => "PushSubscription",
DataType::SearchSnippet => "SearchSnippet",
DataType::VacationResponse => "VacationResponse",
DataType::Mdn => "MDN",
DataType::Quota => "Quota",
DataType::SieveScript => "SieveScript",
DataType::Calendar => "Calendar",
DataType::CalendarEvent => "CalendarEvent",
DataType::CalendarEventNotification => "CalendarEventNotification",
DataType::AddressBook => "AddressBook",
DataType::ContactCard => "ContactCard",
DataType::FileNode => "FileNode",
DataType::Principal => "Principal",
DataType::ShareNotification => "ShareNotification",
DataType::ParticipantIdentity => "ParticipantIdentity",
DataType::CalendarAlert => "CalendarAlert",
DataType::None => "",
}
}
}
impl FromStr for DataType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
DataType::parse(s).ok_or(())
}
}
impl Display for DataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for DataType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
DataType::parse(<&str>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("invalid JMAP data type"))
}
}
impl<'x, P: Property, E: Element + From<DataType>> From<DataType> for Value<'x, P, E> {
fn from(id: DataType) -> Self {
Value::Element(E::from(id))
}
}