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
+342
View File
@@ -0,0 +1,342 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard};
use ahash::AHashSet;
use calcard::{
common::IanaString,
vcard::{ArchivedVCardProperty, ArchivedVCardValue, VCardProperty},
};
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use nlp::language::{
Language,
detect::{LanguageDetector, MIN_LANGUAGE_SCORE},
};
use store::{
search::{ContactSearchField, IndexDocument, SearchField},
write::{IndexPropertyClass, SearchIndex, ValueClass},
xxhash_rust::xxh3,
};
use types::{acl::AclGrant, collection::SyncCollection, field::ContactField};
use utils::sanitize_email;
impl IndexableObject for AddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Acl {
value: (&self.acls).into(),
},
IndexValue::Quota {
used: self.size() as u32,
},
IndexValue::LogContainer {
sync_collection: SyncCollection::AddressBook,
},
]
.into_iter()
}
}
impl IndexableObject for &ArchivedAddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Acl {
value: self
.acls
.iter()
.map(AclGrant::from)
.collect::<Vec<_>>()
.into(),
},
IndexValue::Quota {
used: self.size() as u32,
},
IndexValue::LogContainer {
sync_collection: SyncCollection::AddressBook,
},
]
.into_iter()
}
}
impl IndexableAndSerializableObject for AddressBook {
fn is_versioned() -> bool {
true
}
}
impl IndexableObject for ContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Index {
field: ContactField::Uid.into(),
value: self.card.uid().into(),
},
IndexValue::Index {
field: ContactField::Email.into(),
value: self.emails().next().into(),
},
IndexValue::Property {
field: ValueClass::IndexProperty(IndexPropertyClass::Integer {
property: ContactField::CreatedToUpdated.into(),
value: self.created as u64,
}),
value: self.modified.into(),
},
IndexValue::SearchIndex {
index: SearchIndex::Contacts,
hash: self.hashes().fold(0, |acc, hash| acc ^ hash),
},
IndexValue::Quota {
used: self.size() as u32,
},
IndexValue::LogItem {
sync_collection: SyncCollection::AddressBook,
prefix: None,
},
]
.into_iter()
}
}
impl IndexableObject for &ArchivedContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Index {
field: ContactField::Uid.into(),
value: self.card.uid().into(),
},
IndexValue::Index {
field: ContactField::Email.into(),
value: self.emails().next().into(),
},
IndexValue::Property {
field: ValueClass::IndexProperty(IndexPropertyClass::Integer {
property: ContactField::CreatedToUpdated.into(),
value: self.created.to_native() as u64,
}),
value: (self.modified.to_native() as u64).into(),
},
IndexValue::SearchIndex {
index: SearchIndex::Contacts,
hash: self.hashes().fold(0, |acc, hash| acc ^ hash),
},
IndexValue::Quota {
used: self.size() as u32,
},
IndexValue::LogItem {
sync_collection: SyncCollection::AddressBook,
prefix: None,
},
]
.into_iter()
}
}
impl IndexableAndSerializableObject for ContactCard {
fn is_versioned() -> bool {
true
}
}
impl AddressBook {
pub fn size(&self) -> usize {
self.dead_properties.size()
+ self
.preferences
.iter()
.map(|p| p.name.len() + p.description.as_ref().map_or(0, |n| n.len()))
.sum::<usize>()
+ self.name.len()
+ std::mem::size_of::<AddressBook>()
}
}
impl ArchivedAddressBook {
pub fn size(&self) -> usize {
self.dead_properties.size()
+ self
.preferences
.iter()
.map(|p| p.name.len() + p.description.as_ref().map_or(0, |n| n.len()))
.sum::<usize>()
+ self.name.len()
+ std::mem::size_of::<AddressBook>()
}
}
impl ContactCard {
pub fn size(&self) -> usize {
self.dead_properties.size()
+ self.display_name.as_ref().map_or(0, |n| n.len())
+ self.names.iter().map(|n| n.name.len()).sum::<usize>()
+ self.size as usize
+ std::mem::size_of::<ContactCard>()
}
pub fn hashes(&self) -> impl Iterator<Item = u64> {
self.card
.entries
.iter()
.filter(|e| {
matches!(
e.name,
VCardProperty::Adr
| VCardProperty::N
| VCardProperty::Fn
| VCardProperty::Title
| VCardProperty::Org
| VCardProperty::Note
| VCardProperty::Nickname
| VCardProperty::Email
| VCardProperty::Kind
| VCardProperty::Uid
| VCardProperty::Member
| VCardProperty::Impp
| VCardProperty::Socialprofile
| VCardProperty::Tel
)
})
.flat_map(|e| e.values.iter().filter_map(|v| v.as_text()))
.map(|v| xxh3::xxh3_64(v.as_bytes()))
}
pub fn emails(&self) -> impl Iterator<Item = String> {
self.card.properties(&VCardProperty::Email).flat_map(|e| {
e.values
.iter()
.filter_map(|v| v.as_text().and_then(sanitize_email))
})
}
}
impl ArchivedContactCard {
pub fn size(&self) -> usize {
self.dead_properties.size()
+ self.display_name.as_ref().map_or(0, |n| n.len())
+ self.names.iter().map(|n| n.name.len()).sum::<usize>()
+ self.size.to_native() as usize
+ std::mem::size_of::<ContactCard>()
}
pub fn hashes(&self) -> impl Iterator<Item = u64> {
self.card
.entries
.iter()
.filter(|e| {
matches!(
e.name,
ArchivedVCardProperty::Adr
| ArchivedVCardProperty::N
| ArchivedVCardProperty::Fn
| ArchivedVCardProperty::Title
| ArchivedVCardProperty::Org
| ArchivedVCardProperty::Note
| ArchivedVCardProperty::Nickname
| ArchivedVCardProperty::Email
| ArchivedVCardProperty::Kind
| ArchivedVCardProperty::Uid
| ArchivedVCardProperty::Member
| ArchivedVCardProperty::Impp
| ArchivedVCardProperty::Socialprofile
| ArchivedVCardProperty::Tel
)
})
.flat_map(|e| e.values.iter().filter_map(|v| v.as_text()))
.map(|v| xxh3::xxh3_64(v.as_bytes()))
}
pub fn emails(&self) -> impl Iterator<Item = String> {
self.card.properties(&VCardProperty::Email).flat_map(|e| {
e.values
.iter()
.filter_map(|v| v.as_text().and_then(sanitize_email))
})
}
pub fn index_document(
&self,
account_id: u32,
document_id: u32,
index_fields: &AHashSet<SearchField>,
default_language: Language,
) -> IndexDocument {
let mut document = IndexDocument::new(SearchIndex::Contacts)
.with_account_id(account_id)
.with_document_id(document_id);
let mut detector = LanguageDetector::new();
for entry in self.card.entries.iter() {
let (is_text, is_keyword, field) = match entry.name {
ArchivedVCardProperty::N => (false, false, ContactSearchField::Name),
ArchivedVCardProperty::Nickname => (false, false, ContactSearchField::Nickname),
ArchivedVCardProperty::Org => (false, false, ContactSearchField::Organization),
ArchivedVCardProperty::Email => (false, false, ContactSearchField::Email),
ArchivedVCardProperty::Tel => (false, false, ContactSearchField::Phone),
ArchivedVCardProperty::Impp | ArchivedVCardProperty::Socialprofile => {
(false, false, ContactSearchField::OnlineService)
}
ArchivedVCardProperty::Adr => (false, false, ContactSearchField::Address),
ArchivedVCardProperty::Note => (true, false, ContactSearchField::Note),
ArchivedVCardProperty::Kind => (false, true, ContactSearchField::Kind),
ArchivedVCardProperty::Uid => (false, true, ContactSearchField::Uid),
ArchivedVCardProperty::Member => (false, false, ContactSearchField::Member),
_ => continue,
};
let field = SearchField::Contact(field);
if index_fields.is_empty() || index_fields.contains(&field) {
for value in entry.values.iter() {
match value {
ArchivedVCardValue::Text(v) => {
if !is_keyword {
let lang = if is_text {
detector.detect(v.as_str().trim(), MIN_LANGUAGE_SCORE);
Language::Unknown
} else {
Language::None
};
document.index_text(field.clone(), v, lang);
} else {
document.index_keyword(field.clone(), v.as_str());
}
}
ArchivedVCardValue::Kind(v) => {
document.index_keyword(field.clone(), v.as_str());
}
ArchivedVCardValue::Component(v) => {
for item in v.iter() {
document.index_text(field.clone(), item.trim(), Language::None);
}
}
_ => (),
}
}
/*for param in entry.params.iter() {
if let ArchivedVCardParameterValue::Text(value) = &param.value {
let lang = if is_text {
detector.detect(value.as_str(), MIN_LANGUAGE_SCORE);
Language::Unknown
} else {
Language::None
};
document.index_text(field.clone(), value, lang);
}
}*/
}
}
document.set_unknown_language(
detector
.most_frequent_language()
.unwrap_or(default_language),
);
document
}
}
+128
View File
@@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod index;
pub mod storage;
use calcard::vcard::VCard;
use common::DavName;
use types::{acl::AclGrant, dead_property::DeadProperty};
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
#[rkyv(derive(Debug))]
pub struct AddressBook {
pub name: String,
pub preferences: Vec<AddressBookPreferences>,
pub subscribers: Vec<u32>,
pub dead_properties: DeadProperty,
pub acls: Vec<AclGrant>,
pub created: i64,
pub modified: i64,
}
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
#[rkyv(derive(Debug))]
pub struct AddressBookPreferences {
pub account_id: u32,
pub name: String,
pub description: Option<String>,
pub sort_order: u32,
}
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
pub struct ContactCard {
pub names: Vec<DavName>,
pub display_name: Option<String>,
pub card: VCard,
pub dead_properties: DeadProperty,
pub created: i64,
pub modified: i64,
pub size: u32,
}
impl AddressBook {
pub fn preferences(&self, account_id: u32) -> &AddressBookPreferences {
if self.preferences.len() == 1 {
&self.preferences[0]
} else {
self.preferences
.iter()
.find(|p| p.account_id == account_id)
.or_else(|| self.preferences.first())
.unwrap()
}
}
pub fn preferences_mut(&mut self, account_id: u32) -> &mut AddressBookPreferences {
let idx = if let Some(idx) = self
.preferences
.iter()
.position(|p| p.account_id == account_id)
{
idx
} else {
let mut preferences = self.preferences[0].clone();
preferences.account_id = account_id;
self.preferences.push(preferences);
self.preferences.len() - 1
};
&mut self.preferences[idx]
}
}
impl ArchivedAddressBook {
pub fn preferences(&self, account_id: u32) -> &ArchivedAddressBookPreferences {
if self.preferences.len() == 1 {
&self.preferences[0]
} else {
self.preferences
.iter()
.find(|p| p.account_id == account_id)
.or_else(|| self.preferences.first())
.unwrap()
}
}
}
impl ContactCard {
pub fn added_addressbook_ids(
&self,
prev_data: &ArchivedContactCard,
) -> impl Iterator<Item = u32> {
self.names
.iter()
.filter(|m| prev_data.names.iter().all(|pm| pm.parent_id != m.parent_id))
.map(|m| m.parent_id)
}
pub fn removed_addressbook_ids(
&self,
prev_data: &ArchivedContactCard,
) -> impl Iterator<Item = u32> {
prev_data
.names
.iter()
.filter(|m| self.names.iter().all(|pm| pm.parent_id != m.parent_id))
.map(|m| m.parent_id.to_native())
}
pub fn unchanged_addressbook_ids(
&self,
prev_data: &ArchivedContactCard,
) -> impl Iterator<Item = u32> {
self.names
.iter()
.filter(|m| prev_data.names.iter().any(|pm| pm.parent_id == m.parent_id))
.map(|m| m.parent_id)
}
}
+279
View File
@@ -0,0 +1,279 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard};
use crate::DestroyArchive;
use common::{Server, auth::AccountTenantIds, storage::index::ObjectIndexBuilder};
use store::{
ValueKey,
write::{AlignedBytes, Archive, BatchBuilder, now},
};
use trc::AddContext;
use types::collection::{Collection, VanishedCollection};
impl ContactCard {
pub fn update<'x>(
self,
changed_by: AccountTenantIds,
card: Archive<&ArchivedContactCard>,
account_id: u32,
document_id: u32,
batch: &'x mut BatchBuilder,
) -> trc::Result<&'x mut BatchBuilder> {
let mut new_card = self;
// Build card
new_card.modified = now() as i64;
// Prepare write batch
batch
.with_account_id(account_id)
.with_collection(Collection::ContactCard)
.with_document(document_id)
.custom(
ObjectIndexBuilder::new()
.with_current(card)
.with_changes(new_card)
.with_changed_by(changed_by),
)
.map(|b| b.commit_point())
}
pub fn insert(
self,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
batch: &mut BatchBuilder,
) -> trc::Result<&mut BatchBuilder> {
// Build card
let mut card = self;
let now = now() as i64;
card.modified = now;
card.created = now;
// Prepare write batch
batch
.with_account_id(account_id)
.with_collection(Collection::ContactCard)
.with_document(document_id)
.custom(
ObjectIndexBuilder::<(), _>::new()
.with_changes(card)
.with_changed_by(changed_by),
)
.map(|b| b.commit_point())
}
}
impl AddressBook {
pub fn insert(
self,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
batch: &mut BatchBuilder,
) -> trc::Result<&mut BatchBuilder> {
// Build address book
let mut book = self;
let now = now() as i64;
book.modified = now;
book.created = now;
// Prepare write batch
batch
.with_account_id(account_id)
.with_collection(Collection::AddressBook)
.with_document(document_id)
.custom(
ObjectIndexBuilder::<(), _>::new()
.with_changes(book)
.with_changed_by(changed_by),
)
.map(|b| b.commit_point())
}
pub fn update<'x>(
self,
changed_by: AccountTenantIds,
book: Archive<&ArchivedAddressBook>,
account_id: u32,
document_id: u32,
batch: &'x mut BatchBuilder,
) -> trc::Result<&'x mut BatchBuilder> {
// Build address book
let mut new_book = self;
new_book.modified = now() as i64;
// Prepare write batch
batch
.with_account_id(account_id)
.with_collection(Collection::AddressBook)
.with_document(document_id)
.custom(
ObjectIndexBuilder::new()
.with_current(book)
.with_changes(new_book)
.with_changed_by(changed_by),
)
.map(|b| b.commit_point())
}
}
impl DestroyArchive<Archive<&ArchivedAddressBook>> {
#[allow(clippy::too_many_arguments)]
pub async fn delete_with_cards(
self,
server: &Server,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
children_ids: Vec<u32>,
delete_path: Option<String>,
batch: &mut BatchBuilder,
) -> trc::Result<()> {
// Process deletions
let addressbook_id = document_id;
for document_id in children_ids {
if let Some(card_) = server
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::ContactCard,
document_id,
))
.await?
{
DestroyArchive(
card_
.to_unarchived::<ContactCard>()
.caused_by(trc::location!())?,
)
.delete(
changed_by,
account_id,
document_id,
addressbook_id,
None,
batch,
)?;
}
}
self.delete(changed_by, account_id, document_id, delete_path, batch)
}
pub fn delete(
self,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
delete_path: Option<String>,
batch: &mut BatchBuilder,
) -> trc::Result<()> {
let book = self.0;
// Delete addressbook
batch
.with_account_id(account_id)
.with_collection(Collection::AddressBook)
.with_document(document_id)
.custom(
ObjectIndexBuilder::<_, ()>::new()
.with_changed_by(changed_by)
.with_current(book),
)
.caused_by(trc::location!())?;
if let Some(delete_path) = delete_path {
batch.log_vanished_item(VanishedCollection::AddressBook, delete_path);
}
batch.commit_point();
Ok(())
}
}
impl DestroyArchive<Archive<&ArchivedContactCard>> {
pub fn delete(
self,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
addressbook_id: u32,
delete_path: Option<String>,
batch: &mut BatchBuilder,
) -> trc::Result<()> {
let card = self.0;
if let Some(delete_idx) = card
.inner
.names
.iter()
.position(|name| name.parent_id == addressbook_id)
{
batch
.with_account_id(account_id)
.with_collection(Collection::ContactCard);
if card.inner.names.len() > 1 {
// Unlink addressbook id from card
let mut new_card = card
.deserialize::<ContactCard>()
.caused_by(trc::location!())?;
new_card.names.swap_remove(delete_idx);
batch
.with_document(document_id)
.custom(
ObjectIndexBuilder::new()
.with_changed_by(changed_by)
.with_current(card)
.with_changes(new_card),
)
.caused_by(trc::location!())?;
} else {
// Delete card
batch
.with_document(document_id)
.custom(
ObjectIndexBuilder::<_, ()>::new()
.with_changed_by(changed_by)
.with_current(card),
)
.caused_by(trc::location!())?;
}
if let Some(delete_path) = delete_path {
batch.log_vanished_item(VanishedCollection::AddressBook, delete_path);
}
batch.commit_point();
}
Ok(())
}
pub fn delete_all(
self,
changed_by: AccountTenantIds,
account_id: u32,
document_id: u32,
batch: &mut BatchBuilder,
) -> trc::Result<()> {
batch
.with_account_id(account_id)
.with_collection(Collection::ContactCard)
.with_document(document_id)
.custom(
ObjectIndexBuilder::<_, ()>::new()
.with_changed_by(changed_by)
.with_current(self.0),
)
.caused_by(trc::location!())
.map(|b| {
b.commit_point();
})
}
}