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
+237
View File
@@ -0,0 +1,237 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::*;
use crate::{
cache::{MessageCacheFetch, email::MessageCacheAccess},
message::{delete::EmailDeletion, metadata::MessageData},
};
use common::{
Server, auth::AccessToken, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder,
};
use registry::schema::{
enums::IndexDocumentType,
structs::{Task, TaskIndexDocument, TaskStatus},
};
use store::{
ValueKey,
write::{AlignedBytes, Archive},
};
use store::{roaring::RoaringBitmap, write::BatchBuilder};
use trc::AddContext;
use types::{
acl::Acl,
collection::{Collection, VanishedCollection},
field::MailboxField,
};
pub trait MailboxDestroy: Sync + Send {
fn mailbox_destroy(
&self,
account_id: u32,
document_id: u32,
access_token: &AccessToken,
remove_emails: bool,
) -> impl Future<Output = trc::Result<Result<Option<u64>, MailboxDestroyError>>> + Send;
}
pub enum MailboxDestroyError {
CannotDestroy,
Forbidden,
HasChildren,
HasEmails,
NotFound,
AssertionFailed,
}
impl MailboxDestroy for Server {
async fn mailbox_destroy(
&self,
account_id: u32,
document_id: u32,
access_token: &AccessToken,
remove_emails: bool,
) -> trc::Result<Result<Option<u64>, MailboxDestroyError>> {
// Internal folders cannot be deleted
#[cfg(not(feature = "test_mode"))]
if [INBOX_ID, TRASH_ID, JUNK_ID].contains(&document_id) {
return Ok(Err(MailboxDestroyError::CannotDestroy));
}
// Verify that this mailbox does not have sub-mailboxes
let cache = self
.get_cached_messages(account_id)
.await
.caused_by(trc::location!())?;
if cache
.mailboxes
.items
.iter()
.any(|item| item.parent_id == document_id)
{
return Ok(Err(MailboxDestroyError::HasChildren));
}
// Verify that the mailbox is empty
let mut batch = BatchBuilder::new();
batch.with_account_id(account_id);
let message_ids =
RoaringBitmap::from_iter(cache.in_mailbox(document_id).map(|m| m.document_id));
if !message_ids.is_empty() {
if remove_emails {
// If the message is in multiple mailboxes, untag it from the current mailbox,
// otherwise delete it.
let mut deleted_ids = RoaringBitmap::new();
let mut thread_ids = RoaringBitmap::new();
self.archives(
account_id,
Collection::Email,
&message_ids,
|message_id, message_data_| {
// Remove mailbox from list
let prev_message_data = message_data_
.to_unarchived::<MessageData>()
.caused_by(trc::location!())?;
if !prev_message_data
.inner
.mailboxes
.iter()
.any(|id| id.mailbox_id == document_id)
{
return Ok(true);
}
if prev_message_data.inner.mailboxes.len() == 1 {
// Delete message
for mailbox in prev_message_data.inner.mailboxes.iter() {
batch.log_vanished_item(
VanishedCollection::Email,
(mailbox.mailbox_id.to_native(), mailbox.uid.to_native()),
);
}
deleted_ids.insert(message_id);
thread_ids.insert(prev_message_data.inner.thread_id.to_native());
batch
.with_collection(Collection::Email)
.with_document(message_id)
.custom(
ObjectIndexBuilder::<_, ()>::new()
.with_changed_by(access_token.account_tenant_ids())
.with_current(prev_message_data),
)
.caused_by(trc::location!())?
.schedule_task(Task::UnindexDocument(TaskIndexDocument {
account_id: account_id.into(),
document_id: message_id.into(),
document_type: IndexDocumentType::Email,
status: TaskStatus::now(),
}))
.commit_point();
} else {
let new_message_data = MessageData {
mailboxes: prev_message_data
.inner
.mailboxes
.iter()
.filter(|m| m.mailbox_id != document_id)
.map(|m| m.to_native())
.collect(),
keywords: prev_message_data
.inner
.keywords
.iter()
.map(|k| k.to_native())
.collect(),
thread_id: prev_message_data.inner.thread_id.to_native(),
size: prev_message_data.inner.size.to_native(),
};
// Untag message from mailbox
batch
.with_collection(Collection::Email)
.with_document(message_id)
.custom(
ObjectIndexBuilder::new()
.with_changed_by(access_token.account_tenant_ids())
.with_changes(new_message_data)
.with_current(prev_message_data),
)
.caused_by(trc::location!())?
.commit_point();
}
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
self.log_emptied_threads(account_id, &mut batch, thread_ids, &deleted_ids)
.await
.caused_by(trc::location!())?;
} else {
return Ok(Err(MailboxDestroyError::HasEmails));
}
}
// Obtain mailbox
if let Some(mailbox_) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::Mailbox,
document_id,
))
.await
.caused_by(trc::location!())?
{
let mailbox = mailbox_
.to_unarchived::<Mailbox>()
.caused_by(trc::location!())?;
// Validate ACLs
if access_token.is_shared(account_id) {
let acl = mailbox.inner.acls.effective_acl(access_token);
if !acl.contains(Acl::Delete) || (remove_emails && !acl.contains(Acl::RemoveItems))
{
return Ok(Err(MailboxDestroyError::Forbidden));
}
}
batch
.with_account_id(account_id)
.with_collection(Collection::Mailbox)
.with_document(document_id)
.clear(MailboxField::UidCounter)
.custom(ObjectIndexBuilder::<_, ()>::new().with_current(mailbox))
.caused_by(trc::location!())?;
} else {
return Ok(Err(MailboxDestroyError::NotFound));
};
if !batch.is_empty() {
match self
.commit_batch(batch)
.await
.and_then(|ids| ids.last_change_id(account_id))
{
Ok(change_id) => {
self.notify_task_queue();
Ok(Ok(Some(change_id)))
}
Err(err) if err.is_assertion_failure() => {
Ok(Err(MailboxDestroyError::AssertionFailed))
}
Err(err) => Err(err.caused_by(trc::location!())),
}
} else {
Ok(Ok(None))
}
}
}
+48
View File
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{ArchivedMailbox, Mailbox};
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use types::{acl::AclGrant, collection::SyncCollection};
impl IndexableObject for Mailbox {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::LogContainer {
sync_collection: SyncCollection::Email,
},
IndexValue::Acl {
value: (&self.acls).into(),
},
]
.into_iter()
}
}
impl IndexableObject for &ArchivedMailbox {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::LogContainer {
sync_collection: SyncCollection::Email,
},
IndexValue::Acl {
value: self
.acls
.iter()
.map(AclGrant::from)
.collect::<Vec<_>>()
.into(),
},
]
.into_iter()
}
}
impl IndexableAndSerializableObject for Mailbox {
fn is_versioned() -> bool {
false
}
}
+166
View File
@@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::*;
use crate::cache::MessageCacheFetch;
use common::{Server, storage::index::ObjectIndexBuilder};
use registry::schema::enums::StorageQuota;
use std::future::Future;
use store::write::BatchBuilder;
use trc::AddContext;
use types::collection::Collection;
pub trait MailboxFnc: Sync + Send {
fn create_system_folders(
&self,
account_id: u32,
) -> impl Future<Output = trc::Result<()>> + Send;
fn mailbox_create_path(
&self,
account_id: u32,
path: &str,
) -> impl Future<Output = trc::Result<Option<u32>>> + Send;
}
impl MailboxFnc for Server {
async fn create_system_folders(&self, account_id: u32) -> trc::Result<()> {
#[cfg(feature = "test_mode")]
if account_id == 0 {
return Ok(());
}
let mut batch = BatchBuilder::new();
batch
.with_account_id(account_id)
.with_collection(Collection::Mailbox);
// Create mailboxes
let mut last_document_id = ARCHIVE_ID;
for folder in &self.core.email.default_folders {
let document_id = match folder.special_use {
SpecialUse::Inbox => INBOX_ID,
SpecialUse::Trash => TRASH_ID,
SpecialUse::Junk => JUNK_ID,
SpecialUse::Drafts => DRAFTS_ID,
SpecialUse::Sent => SENT_ID,
SpecialUse::Archive => ARCHIVE_ID,
SpecialUse::None
| SpecialUse::Important
| SpecialUse::Memos
| SpecialUse::Scheduled
| SpecialUse::Snoozed => {
last_document_id += 1;
last_document_id
}
SpecialUse::Shared => unreachable!(),
};
let mut object = Mailbox::new(folder.name.clone()).with_role(folder.special_use);
if folder.subscribe {
object.add_subscriber(account_id);
}
batch
.with_document(document_id)
.custom(ObjectIndexBuilder::<(), _>::new().with_changes(object))
.caused_by(trc::location!())?;
}
self.store()
.assign_document_ids(account_id, Collection::Mailbox, (ARCHIVE_ID + 1) as u64)
.await
.caused_by(trc::location!())?;
self.core
.storage
.data
.write(batch.build_all())
.await
.caused_by(trc::location!())?;
Ok(())
}
async fn mailbox_create_path(&self, account_id: u32, path: &str) -> trc::Result<Option<u32>> {
let cache = self
.get_cached_messages(account_id)
.await
.caused_by(trc::location!())?;
let mut next_parent_id = 0;
let mut create_paths = Vec::with_capacity(2);
let mut path = path.split('/').map(|v| v.trim());
let mut found_path = String::with_capacity(16);
{
while let Some(name) = path.next() {
if !found_path.is_empty() {
found_path.push('/');
}
for ch in name.chars() {
for ch in ch.to_lowercase() {
found_path.push(ch);
}
}
if let Some(item) = cache
.mailboxes
.items
.iter()
.find(|item| item.path.to_lowercase() == found_path)
{
next_parent_id = item.document_id + 1;
} else {
create_paths.push(name.to_string());
create_paths.extend(path.map(|v| v.to_string()));
break;
}
}
}
// Create missing folders
if !create_paths.is_empty() {
if create_paths
.iter()
.any(|name| name.len() > self.core.email.mailbox_name_max_len)
{
return Ok(None);
}
let account = self.account(account_id).await.caused_by(trc::location!())?;
if cache.mailboxes.items.len() + create_paths.len()
> self.object_quota(account.object_quotas(), StorageQuota::MaxMailboxes) as usize
{
return Ok(None);
}
let mut next_document_id = self
.store()
.assign_document_ids(account_id, Collection::Mailbox, create_paths.len() as u64)
.await
.caused_by(trc::location!())?;
let mut batch = BatchBuilder::new();
for name in create_paths {
let document_id = next_document_id;
next_document_id -= 1;
batch
.with_account_id(account_id)
.with_collection(Collection::Mailbox)
.with_document(document_id)
.custom(
ObjectIndexBuilder::<(), _>::new()
.with_changes(Mailbox::new(name).with_parent_id(next_parent_id)),
)
.caused_by(trc::location!())?;
next_parent_id = document_id + 1;
}
self.commit_batch(batch).await.caused_by(trc::location!())?;
}
Ok(Some(next_parent_id - 1))
}
}
+112
View File
@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use types::{acl::AclGrant, special_use::SpecialUse};
pub mod destroy;
pub mod index;
pub mod manage;
pub const INBOX_ID: u32 = 0;
pub const TRASH_ID: u32 = 1;
pub const JUNK_ID: u32 = 2;
pub const DRAFTS_ID: u32 = 3;
pub const SENT_ID: u32 = 4;
pub const ARCHIVE_ID: u32 = 5;
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
#[rkyv(derive(Debug))]
pub struct Mailbox {
pub name: String,
pub role: SpecialUse,
pub parent_id: u32,
pub sort_order: Option<u32>,
pub uid_validity: u32,
pub subscribers: Vec<u32>,
pub acls: Vec<AclGrant>,
}
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, Copy)]
#[rkyv(derive(Debug, Clone, Copy))]
pub struct UidMailbox {
pub mailbox_id: u32,
pub uid: u32,
}
impl Mailbox {
pub fn new(name: impl Into<String>) -> Self {
Mailbox {
name: name.into(),
role: SpecialUse::None,
parent_id: 0,
sort_order: None,
uid_validity: rand::random::<u32>(),
subscribers: vec![],
acls: vec![],
}
}
pub fn with_role(mut self, role: SpecialUse) -> Self {
self.role = role;
self
}
pub fn with_parent_id(mut self, parent_id: u32) -> Self {
self.parent_id = parent_id;
self
}
pub fn with_sort_order(mut self, sort_order: u32) -> Self {
self.sort_order = Some(sort_order);
self
}
pub fn with_subscriber(mut self, subscriber: u32) -> Self {
self.subscribers.push(subscriber);
self
}
pub fn add_subscriber(&mut self, subscriber: u32) -> bool {
if !self.subscribers.contains(&subscriber) {
self.subscribers.push(subscriber);
true
} else {
false
}
}
pub fn remove_subscriber(&mut self, subscriber: u32) {
self.subscribers.retain(|&x| x != subscriber);
}
pub fn is_subscribed(&self, subscriber: u32) -> bool {
self.subscribers.contains(&subscriber)
}
}
impl ArchivedMailbox {
pub fn is_subscribed(&self, subscriber: u32) -> bool {
self.subscribers.iter().any(|x| u32::from(x) == subscriber)
}
}
impl PartialEq for UidMailbox {
fn eq(&self, other: &Self) -> bool {
self.mailbox_id == other.mailbox_id
}
}
impl Eq for UidMailbox {}
impl UidMailbox {
pub fn new(mailbox_id: u32, uid: u32) -> Self {
UidMailbox { mailbox_id, uid }
}
pub fn new_unassigned(mailbox_id: u32) -> Self {
UidMailbox { mailbox_id, uid: 0 }
}
}