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
+100
View File
@@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::propfind::PrincipalPropFind;
use crate::{
DavError,
common::{
DavQuery, DavQueryResource,
propfind::PropFindRequestHandler,
uri::{DavUriResource, UriResource},
},
};
use common::{Server, auth::AccessToken};
use dav_proto::{
RequestHeaders,
schema::{
property::{DavProperty, WebDavProperty},
request::{PrincipalMatch, PropFind},
response::MultiStatus,
},
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use store::roaring::RoaringBitmap;
use types::collection::Collection;
pub(crate) trait PrincipalMatching: Sync + Send {
fn handle_principal_match(
&self,
access_token: &AccessToken,
headers: &RequestHeaders<'_>,
request: PrincipalMatch,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl PrincipalMatching for Server {
async fn handle_principal_match(
&self,
access_token: &AccessToken,
headers: &RequestHeaders<'_>,
mut request: PrincipalMatch,
) -> crate::Result<HttpResponse> {
let resource = self.validate_uri(access_token, headers.uri).await?;
match resource.collection {
Collection::AddressBook | Collection::Calendar | Collection::FileNode => {
if request.properties.is_empty() {
request
.properties
.push(DavProperty::WebDav(WebDavProperty::Owner));
}
if let Some(account_id) = resource.account_id {
return self
.handle_dav_query(
access_token,
DavQuery {
resource: DavQueryResource::Uri(UriResource {
collection: resource.collection,
account_id,
resource: resource.resource,
}),
propfind: PropFind::Prop(request.properties),
depth: usize::MAX,
ret: headers.ret,
depth_no_root: headers.depth_no_root,
uri: headers.uri,
sync_type: Default::default(),
limit: Default::default(),
vcard_version: Default::default(),
expand: Default::default(),
},
)
.await;
}
}
Collection::Principal => {}
_ => return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
}
let mut response = MultiStatus::new(Vec::with_capacity(16));
if request.properties.is_empty() {
request
.properties
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
}
let request = PropFind::Prop(request.properties);
self.prepare_principal_propfind_response(
access_token,
resource.collection,
RoaringBitmap::from_iter(access_token.all_ids()).into_iter(),
&request,
&mut response,
)
.await?;
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::auth::AccountCache;
use dav_proto::schema::response::Href;
use groupware::RFC_3986;
use crate::DavResourceName;
pub mod matching;
pub mod propfind;
pub mod propsearch;
pub trait CurrentUserPrincipal {
fn current_user_principal(&self) -> Href;
}
impl CurrentUserPrincipal for AccountCache {
fn current_user_principal(&self) -> Href {
Href(format!(
"{}/{}/",
DavResourceName::Principal.base_path(),
percent_encoding::utf8_percent_encode(self.name(), RFC_3986)
))
}
}
+464
View File
@@ -0,0 +1,464 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::CurrentUserPrincipal;
use crate::{
DavResourceName,
common::propfind::{PropFindRequestHandler, SyncTokenUrn},
};
use common::{
Server,
auth::{AccessToken, AccountCache},
};
use dav_proto::schema::{
Namespace,
property::{
DavProperty, DavValue, PrincipalProperty, Privilege, ReportSet, ResourceType,
WebDavProperty,
},
request::{DavPropertyValue, PropFind},
response::{Href, MultiStatus, PropStat, Response},
};
use groupware::RFC_3986;
use groupware::cache::GroupwareCache;
use hyper::StatusCode;
use std::borrow::Cow;
use trc::AddContext;
use types::collection::Collection;
pub(crate) trait PrincipalPropFind: Sync + Send {
fn prepare_principal_propfind_response(
&self,
access_token: &AccessToken,
collection: Collection,
documents: impl Iterator<Item = u32> + Sync + Send,
request: &PropFind,
response: &mut MultiStatus,
) -> impl Future<Output = crate::Result<()>> + Send;
fn expand_principal(
&self,
access_token: &AccessToken,
account_id: u32,
propfind: &PropFind,
) -> impl Future<Output = crate::Result<Option<Response>>> + Send;
fn owner_href(
&self,
account_info: &AccountCache,
account_id: u32,
) -> impl Future<Output = trc::Result<Href>> + Send;
}
impl PrincipalPropFind for Server {
async fn prepare_principal_propfind_response(
&self,
access_token: &AccessToken,
collection: Collection,
account_ids: impl Iterator<Item = u32> + Sync + Send,
request: &PropFind,
response: &mut MultiStatus,
) -> crate::Result<()> {
let access_account_info = self
.account(access_token.account_id())
.await
.caused_by(trc::location!())?;
let properties = match request {
PropFind::PropName => {
let props = all_props(collection, None);
for property in &props {
response.set_namespace(property.namespace());
}
for account_id in account_ids {
response.add_response(Response::new_propstat(
self.owner_href(&access_account_info, account_id)
.await
.caused_by(trc::location!())?,
vec![PropStat::new_list(
props.iter().cloned().map(DavPropertyValue::empty).collect(),
)],
));
}
return Ok(());
}
PropFind::AllProp(items) => Cow::Owned(all_props(collection, items.as_slice().into())),
PropFind::Prop(items) => Cow::Borrowed(items),
};
for property in properties.as_slice() {
response.set_namespace(property.namespace());
}
let is_principal = match collection {
Collection::AddressBook | Collection::ContactCard => {
response.set_namespace(Namespace::CardDav);
false
}
Collection::Calendar
| Collection::CalendarEvent
| Collection::CalendarEventNotification => {
response.set_namespace(Namespace::CalDav);
false
}
Collection::Principal => true,
_ => false,
};
let base_path = DavResourceName::from(collection).base_path();
let needs_quota = properties.iter().any(|property| {
matches!(
property,
DavProperty::WebDav(
WebDavProperty::QuotaAvailableBytes | WebDavProperty::QuotaUsedBytes
)
)
});
for account_id in account_ids {
let mut fields = Vec::with_capacity(properties.len());
let mut fields_not_found = Vec::new();
let account = self.account(account_id).await.caused_by(trc::location!())?;
// Fetch quota
let quota = if needs_quota {
self.dav_quota(account_id)
.await
.caused_by(trc::location!())?
} else {
Default::default()
};
for property in properties.as_slice() {
match property {
DavProperty::WebDav(dav_property) => match dav_property {
WebDavProperty::DisplayName => {
fields.push(DavPropertyValue::new(
property.clone(),
account.description().unwrap_or(account.name()).to_string(),
));
}
WebDavProperty::ResourceType => {
let resource_type = if !is_principal {
vec![ResourceType::Collection]
} else {
vec![ResourceType::Principal, ResourceType::Collection]
};
fields.push(DavPropertyValue::new(property.clone(), resource_type));
}
WebDavProperty::SupportedReportSet => {
let reports = match collection {
Collection::Principal => ReportSet::principal(),
Collection::Calendar | Collection::CalendarEvent => {
ReportSet::calendar()
}
Collection::AddressBook | Collection::ContactCard => {
ReportSet::addressbook()
}
_ => ReportSet::file(),
};
fields.push(DavPropertyValue::new(property.clone(), reports));
}
WebDavProperty::CurrentUserPrincipal => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![access_account_info.current_user_principal()],
));
}
WebDavProperty::QuotaAvailableBytes if !is_principal => {
if let Some(available) = quota.available {
fields.push(DavPropertyValue::new(property.clone(), available));
} else {
fields_not_found.push(DavPropertyValue::empty(property.clone()));
}
}
WebDavProperty::QuotaUsedBytes if !is_principal => {
fields.push(DavPropertyValue::new(property.clone(), quota.used));
}
WebDavProperty::SyncToken if !is_principal => {
let sync_token = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
collection.into(),
)
.await
.caused_by(trc::location!())?
.sync_token();
fields.push(DavPropertyValue::new(property.clone(), sync_token));
}
WebDavProperty::GetCTag if !is_principal => {
let ctag = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
collection.into(),
)
.await
.caused_by(trc::location!())?
.highest_change_id;
fields.push(DavPropertyValue::new(
property.clone(),
DavValue::String(format!("\"{ctag}\"")),
));
}
WebDavProperty::Owner => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(format!(
"{}/{}/",
DavResourceName::Principal.base_path(),
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
))],
));
}
WebDavProperty::Group if !is_principal => {
fields.push(DavPropertyValue::empty(property.clone()));
}
WebDavProperty::CurrentUserPrivilegeSet if !is_principal => {
fields.push(DavPropertyValue::new(
property.clone(),
if access_token.is_member(account_id) {
Privilege::all(matches!(
collection,
Collection::Calendar | Collection::CalendarEvent
))
} else {
vec![Privilege::Read]
},
));
}
WebDavProperty::PrincipalCollectionSet => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(
DavResourceName::Principal.collection_path().to_string(),
)],
));
}
_ => {
fields_not_found.push(DavPropertyValue::empty(property.clone()));
}
},
DavProperty::Principal(principal_property) => match principal_property {
PrincipalProperty::AlternateURISet
| PrincipalProperty::GroupMemberSet
| PrincipalProperty::GroupMembership => {
fields.push(DavPropertyValue::empty(property.clone()));
}
PrincipalProperty::PrincipalURL => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(format!(
"{}/{}/",
DavResourceName::Principal.base_path(),
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
))],
));
}
PrincipalProperty::CalendarHomeSet => {
let hrefs = build_home_set(
self,
access_token,
account.name(),
account_id,
true,
)
.await
.caused_by(trc::location!())?;
fields.push(DavPropertyValue::new(property.clone(), hrefs));
}
PrincipalProperty::AddressbookHomeSet => {
let hrefs = build_home_set(
self,
access_token,
account.name(),
account_id,
false,
)
.await
.caused_by(trc::location!())?;
fields.push(DavPropertyValue::new(property.clone(), hrefs));
}
PrincipalProperty::PrincipalAddress => {
fields_not_found.push(DavPropertyValue::empty(property.clone()));
}
PrincipalProperty::CalendarUserAddressSet => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(format!("mailto:{}", account.name()))],
));
}
PrincipalProperty::CalendarUserType => {
fields.push(DavPropertyValue::new(
property.clone(),
if account.is_user_account() {
DavValue::String("INDIVIDUAL".to_string())
} else {
DavValue::String("GROUP".to_string())
},
));
}
PrincipalProperty::ScheduleInboxURL => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(format!(
"{}/{}/inbox/",
DavResourceName::Scheduling.base_path(),
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
))],
));
}
PrincipalProperty::ScheduleOutboxURL => {
fields.push(DavPropertyValue::new(
property.clone(),
vec![Href(format!(
"{}/{}/outbox/",
DavResourceName::Scheduling.base_path(),
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
))],
));
}
},
_ => {
fields_not_found.push(DavPropertyValue::empty(property.clone()));
}
}
}
let mut prop_stats = Vec::with_capacity(2);
if !fields_not_found.is_empty() {
prop_stats
.push(PropStat::new_list(fields_not_found).with_status(StatusCode::NOT_FOUND));
}
if !fields.is_empty() || prop_stats.is_empty() {
prop_stats.push(PropStat::new_list(fields));
}
response.add_response(Response::new_propstat(
Href(format!(
"{}/{}/",
base_path,
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
)),
prop_stats,
));
}
Ok(())
}
async fn expand_principal(
&self,
access_token: &AccessToken,
account_id: u32,
propfind: &PropFind,
) -> crate::Result<Option<Response>> {
let mut status = MultiStatus::new(vec![]);
self.prepare_principal_propfind_response(
access_token,
Collection::Principal,
[account_id].into_iter(),
propfind,
&mut status,
)
.await?;
Ok(status.response.0.into_iter().next())
}
async fn owner_href(&self, account_info: &AccountCache, account_id: u32) -> trc::Result<Href> {
if account_info.account_id() == account_id {
Ok(account_info.current_user_principal())
} else {
let account_info = self.account(account_id).await.caused_by(trc::location!())?;
Ok(Href(format!(
"{}/{}/",
DavResourceName::Principal.base_path(),
percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986),
)))
}
}
}
pub(crate) async fn build_home_set(
server: &Server,
access_token: &AccessToken,
name: &str,
account_id: u32,
is_calendar: bool,
) -> trc::Result<Vec<Href>> {
let (collection, resource_name) = if is_calendar {
(Collection::Calendar, DavResourceName::Cal)
} else {
(Collection::AddressBook, DavResourceName::Card)
};
let mut hrefs = Vec::new();
hrefs.push(Href(format!(
"{}/{}/",
resource_name.base_path(),
percent_encoding::utf8_percent_encode(name, RFC_3986),
)));
if !server.core.groupware.assisted_discovery && account_id == access_token.account_id() {
for account_id in access_token.all_ids_by_collection(collection) {
if account_id != access_token.account_id() {
let other = server
.account(account_id)
.await
.caused_by(trc::location!())?;
hrefs.push(Href(format!(
"{}/{}/",
resource_name.base_path(),
percent_encoding::utf8_percent_encode(other.name(), RFC_3986),
)));
}
}
}
Ok(hrefs)
}
fn all_props(collection: Collection, all_props: Option<&[DavProperty]>) -> Vec<DavProperty> {
if collection == Collection::Principal {
vec![
DavProperty::WebDav(WebDavProperty::DisplayName),
DavProperty::WebDav(WebDavProperty::ResourceType),
DavProperty::WebDav(WebDavProperty::SupportedReportSet),
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet),
DavProperty::Principal(PrincipalProperty::AlternateURISet),
DavProperty::Principal(PrincipalProperty::PrincipalURL),
DavProperty::Principal(PrincipalProperty::GroupMemberSet),
DavProperty::Principal(PrincipalProperty::GroupMembership),
]
} else {
let mut props = vec![
DavProperty::WebDav(WebDavProperty::DisplayName),
DavProperty::WebDav(WebDavProperty::ResourceType),
DavProperty::WebDav(WebDavProperty::SupportedReportSet),
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
DavProperty::WebDav(WebDavProperty::SyncToken),
DavProperty::WebDav(WebDavProperty::Owner),
DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet),
];
if let Some(all_props) = all_props {
props.extend(all_props.iter().filter(|p| !p.is_all_prop()).cloned());
props
} else {
props
}
}
}
+79
View File
@@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::propfind::PrincipalPropFind;
use common::{Server, auth::AccessToken};
use dav_proto::schema::{
property::{DavProperty, WebDavProperty},
request::{PrincipalPropertySearch, PropFind},
response::MultiStatus,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use registry::schema::prelude::{ObjectType, Property};
use store::{registry::RegistryQuery, roaring::RoaringBitmap};
use trc::AddContext;
use types::collection::Collection;
pub(crate) trait PrincipalPropSearch: Sync + Send {
fn handle_principal_property_search(
&self,
access_token: &AccessToken,
request: PrincipalPropertySearch,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl PrincipalPropSearch for Server {
async fn handle_principal_property_search(
&self,
access_token: &AccessToken,
mut request: PrincipalPropertySearch,
) -> crate::Result<HttpResponse> {
let mut search_for = None;
for prop_search in request.property_search {
if matches!(
prop_search.property,
DavProperty::WebDav(WebDavProperty::DisplayName)
) && !prop_search.match_.is_empty()
{
search_for = Some(prop_search.match_);
}
}
let mut response = MultiStatus::new(Vec::with_capacity(16));
if let Some(search_for) = search_for {
let ids = self
.registry()
.query::<RoaringBitmap>(
RegistryQuery::new(ObjectType::Account)
.with_tenant(access_token.tenant_id())
.text(Property::Text, search_for),
)
.await
.caused_by(trc::location!())?;
if !ids.is_empty() {
if request.properties.is_empty() {
request
.properties
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
}
let request = PropFind::Prop(request.properties);
self.prepare_principal_propfind_response(
access_token,
Collection::Principal,
ids.into_iter(),
&request,
&mut response,
)
.await?;
}
}
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
}
}