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
+427
View File
@@ -0,0 +1,427 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{calendar::Availability, calendar_event::CalendarSyntheticId};
use calcard::{
common::timezone::Tz,
icalendar::{
ArchivedICalendarClassification, ArchivedICalendarParameterValue,
ArchivedICalendarParticipationStatus, ArchivedICalendarProperty, ArchivedICalendarStatus,
ArchivedICalendarTransparency, ArchivedICalendarValue, ICalendarParameterName,
},
jscalendar::{JSCalendar, JSCalendarProperty, JSCalendarValue},
};
use common::{
Server, TinyCalendarPreferences,
auth::{AccessToken, BuildAccessToken},
};
use groupware::{
cache::GroupwareCache,
calendar::{CALENDAR_SUBSCRIBED, CalendarEvent, expand::RecurrenceKey},
strip_mailto_scheme,
};
use jmap_proto::{
method::availability::{
BusyPeriod, BusyStatus, GetAvailabilityRequest, GetAvailabilityResponse,
},
object::calendar::IncludeInAvailability,
request::IntoValid,
types::date::UTCDate,
};
use jmap_tools::{Key, Map, Value};
use registry::schema::enums::Permission;
use std::{collections::hash_map::Entry, future::Future};
use store::{
ValueKey,
ahash::AHashMap,
write::{AlignedBytes, Archive},
};
use trc::AddContext;
use types::{
TimeRange,
acl::Acl,
collection::{Collection, SyncCollection},
id::Id,
};
use utils::sanitize_email;
pub trait PrincipalGetAvailability: Sync + Send {
fn principal_get_availability(
&self,
request: GetAvailabilityRequest,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<GetAvailabilityResponse>> + Send;
}
impl PrincipalGetAvailability for Server {
async fn principal_get_availability(
&self,
request: GetAvailabilityRequest,
access_token: &AccessToken,
) -> trc::Result<GetAvailabilityResponse> {
if !self.core.groupware.allow_directory_query
&& !access_token.has_permission(Permission::JmapPrincipalGetAvailability)
{
return Err(trc::JmapEvent::Forbidden
.into_err()
.details("The administrator has disabled directory queries.".to_string()));
}
// Process parameters
if !request.id.is_valid() {
return Err(trc::JmapEvent::InvalidArguments
.into_err()
.details("Missing principal id"));
}
let properties = request
.event_properties
.map(|props| props.into_valid().collect::<Vec<_>>())
.unwrap_or_default();
if properties
.iter()
.any(|p| !matches!(p, JSCalendarProperty::Id | JSCalendarProperty::BaseEventId))
{
return Err(trc::JmapEvent::InvalidArguments
.into_err()
.details("Only 'id' and 'baseEventId' properties are supported in results"));
}
let return_event_details = !properties.is_empty();
let max_instances = self.core.groupware.max_ical_instances;
let filter = TimeRange {
start: request.utc_start.timestamp(),
end: request.utc_end.timestamp(),
};
let principal_id = request.id.document_id();
let principal = self
.access_token(principal_id)
.await
.caused_by(trc::location!())?
.build();
let principal_account = self
.account_info(principal_id)
.await
.caused_by(trc::location!())?;
let mut periods = Vec::new();
for account_id in principal.all_ids_by_collection(Collection::Calendar) {
let resources = self
.fetch_dav_resources(
access_token.account_id(),
account_id,
SyncCollection::Calendar,
)
.await
.caused_by(trc::location!())?;
// Obtain shared ids
let is_account_owner = principal_id == account_id;
let shared_ids = if !access_token.is_member(account_id) {
// Condition: The user has the "mayReadFreeBusy" permission for the calendar.
let shared_ids = resources.shared_items(
access_token,
[Acl::ReadItems, Acl::SchedulingReadFreeBusy],
true,
);
if shared_ids.is_empty() {
continue;
}
shared_ids.into()
} else {
None
};
// Condition: The event finishes after the "utcStart" argument and starts before the "utcEnd" argument.
let mut preferences_cache: AHashMap<u32, Option<&TinyCalendarPreferences>> =
AHashMap::default();
'next_event: for resource in resources.resources.iter().filter(|r| {
r.event_time_range().is_some_and(|(start, end)| {
shared_ids
.as_ref()
.is_none_or(|ids| ids.contains(r.document_id))
&& filter.is_in_range(false, start, end)
})
}) {
// Obtain calendar settings
let mut include_in_availability = None;
let mut default_tz = Tz::UTC;
let mut is_subscribed = is_account_owner;
for calendar_id in resource
.child_names()
.unwrap_or_default()
.iter()
.map(|n| n.parent_id)
{
match preferences_cache.entry(calendar_id) {
Entry::Occupied(e) => {
if let Some(prefs) = e.get() {
default_tz = prefs.tz;
is_subscribed |= prefs.flags & CALENDAR_SUBSCRIBED != 0;
include_in_availability =
IncludeInAvailability::from_flags(prefs.flags);
}
}
Entry::Vacant(e) => {
if let Some(prefs) = resources
.container_resource_by_id(calendar_id)
.and_then(|r| r.calendar_preferences(principal_id))
{
default_tz = prefs.tz;
is_subscribed |= prefs.flags & CALENDAR_SUBSCRIBED != 0;
include_in_availability =
IncludeInAvailability::from_flags(prefs.flags);
e.insert(Some(prefs));
} else {
e.insert(None);
}
}
}
}
let include_in_availability = include_in_availability.unwrap_or({
if is_account_owner {
IncludeInAvailability::All
} else {
IncludeInAvailability::None
}
});
if !is_subscribed || include_in_availability == IncludeInAvailability::None {
continue 'next_event;
}
// Fetch event
let document_id = resource.document_id;
let Some(archive) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::CalendarEvent,
document_id,
))
.await
.caused_by(trc::location!())?
else {
continue;
};
let event = archive
.unarchive::<CalendarEvent>()
.caused_by(trc::location!())?;
// Find the component ids that match the criteria
let mut matching_component_ids = AHashMap::new();
'next_component: for (component_id, component) in
event.data.event.components.iter().enumerate()
{
if !component.component_type.is_event_or_todo() {
continue 'next_component;
}
let mut is_cancelled = false;
let mut is_main_event = true;
let mut busy_status = None;
for entry in component.entries.iter() {
match (&entry.name, entry.values.first()) {
(
ArchivedICalendarProperty::Status,
Some(ArchivedICalendarValue::Status(
ArchivedICalendarStatus::Cancelled,
)),
) => {
// The "status" property of the event is not "cancelled".
is_cancelled = true;
}
(ArchivedICalendarProperty::RecurrenceId, _) => {
is_main_event = false;
}
(
ArchivedICalendarProperty::Class,
Some(ArchivedICalendarValue::Classification(
ArchivedICalendarClassification::Confidential,
)),
) => {
// Condition: The event's "privacy" property is not "secret".
continue 'next_component;
}
(
ArchivedICalendarProperty::Transp,
Some(ArchivedICalendarValue::Transparency(
ArchivedICalendarTransparency::Transparent,
)),
) => {
// Condition: The "freeBusyStatus" property of the event is "busy" (or omitted, as this is the default).
continue 'next_component;
}
(ArchivedICalendarProperty::Attendee, Some(value))
if include_in_availability == IncludeInAvailability::Attending =>
{
if let Some(attendee) = value.as_text().and_then(|attendee| {
sanitize_email(strip_mailto_scheme(attendee))
}) {
// Condition: the Principal is a participant of the event, and has a "participationStatus" of "accepted" or "tentative".
if principal_account.addresses().contains(&attendee) {
busy_status = Some(
entry
.parameters(&ICalendarParameterName::Partstat)
.next()
.map(|v| {
match v {
ArchivedICalendarParameterValue::Partstat(
ArchivedICalendarParticipationStatus::Accepted,
) => BusyStatus::Confirmed,
ArchivedICalendarParameterValue::Partstat(
ArchivedICalendarParticipationStatus::Tentative,
) => BusyStatus::Tentative,
ArchivedICalendarParameterValue::Partstat(
ArchivedICalendarParticipationStatus::Declined,
) => {
is_cancelled = true;
BusyStatus::Unavailable
}
_ => BusyStatus::Unavailable,
}
})
.unwrap_or(BusyStatus::Unavailable),
);
}
}
}
_ => (),
}
}
if is_cancelled {
if is_main_event {
continue 'next_event;
} else {
continue 'next_component;
}
}
let busy_status = if let Some(busy_status) = busy_status {
busy_status
} else if include_in_availability == IncludeInAvailability::All {
BusyStatus::Confirmed
} else {
continue 'next_component;
};
matching_component_ids.insert(component_id as u32, busy_status);
}
if matching_component_ids.is_empty() {
// No events matched the criteria
continue 'next_event;
}
for expansion in event.data.expand(default_tz, filter).unwrap_or_default() {
let Some(busy_status) = matching_component_ids.get(&expansion.comp_id) else {
continue;
};
let Some(recurrence_key) = expansion.recurrence_key() else {
continue;
};
if periods.len() < max_instances {
periods.push(FreeBusyResult {
utc_start: expansion.start,
utc_end: expansion.end,
busy_status: *busy_status,
recurrence_key,
document_id,
});
} else {
return Err(trc::JmapEvent::RequestTooLarge
.into_err()
.details("The number of expanded instances exceeds the server limit"));
}
}
}
}
let mut result = GetAvailabilityResponse {
list: Vec::with_capacity(periods.len()),
};
if periods.is_empty() {
return Ok(result);
}
// Sort by busy status and start time
periods.sort_unstable_by(|a, b| {
a.busy_status
.cmp(&b.busy_status)
.then_with(|| a.utc_start.cmp(&b.utc_start))
});
if return_event_details {
for period in periods {
result.list.push(period.into());
}
} else {
// Merge intervals with same busy status
let mut start_time = periods[0].utc_start;
let mut end_time = periods[0].utc_end;
let mut current_status = periods[0].busy_status;
for curr in periods.iter().skip(1) {
if curr.utc_start <= end_time && curr.busy_status == current_status {
end_time = end_time.max(curr.utc_end);
} else {
result.list.push(BusyPeriod {
utc_start: UTCDate::from_timestamp(start_time),
utc_end: UTCDate::from_timestamp(end_time),
busy_status: Some(current_status),
event: None,
});
start_time = curr.utc_start;
end_time = curr.utc_end;
current_status = curr.busy_status;
}
}
result.list.push(BusyPeriod {
utc_start: UTCDate::from_timestamp(start_time),
utc_end: UTCDate::from_timestamp(end_time),
busy_status: Some(current_status),
event: None,
});
}
Ok(result)
}
}
struct FreeBusyResult {
utc_start: i64,
utc_end: i64,
busy_status: BusyStatus,
recurrence_key: RecurrenceKey,
document_id: u32,
}
impl From<FreeBusyResult> for BusyPeriod {
fn from(value: FreeBusyResult) -> Self {
BusyPeriod {
utc_start: UTCDate::from_timestamp(value.utc_start),
utc_end: UTCDate::from_timestamp(value.utc_end),
busy_status: Some(value.busy_status),
event: JSCalendar(Value::Object(Map::from(vec![
(
Key::Property(JSCalendarProperty::Id),
Value::Element(JSCalendarValue::Id(<Id as CalendarSyntheticId>::new(
value.recurrence_key,
value.document_id,
))),
),
(
Key::Property(JSCalendarProperty::BaseEventId),
Value::Element(JSCalendarValue::Id(Id::from(value.document_id))),
),
])))
.into(),
}
}
}
+186
View File
@@ -0,0 +1,186 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use jmap_proto::{
method::get::{GetRequest, GetResponse},
object::principal::{Principal, PrincipalProperty, PrincipalType, PrincipalValue},
request::capability::Capability,
types::state::State,
};
use jmap_tools::{Key, Map, Value};
use registry::schema::prelude::{ObjectType, Permission};
use std::future::Future;
use store::{registry::RegistryQuery, roaring::RoaringBitmap};
use trc::AddContext;
pub trait PrincipalGet: Sync + Send {
fn principal_get(
&self,
request: GetRequest<Principal>,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<GetResponse<Principal>>> + Send;
}
impl PrincipalGet for Server {
async fn principal_get(
&self,
mut request: GetRequest<Principal>,
access_token: &AccessToken,
) -> trc::Result<GetResponse<Principal>> {
if !self.core.groupware.allow_directory_query
&& !access_token.has_permission(Permission::JmapPrincipalGet)
{
return Err(trc::JmapEvent::Forbidden
.into_err()
.details("The administrator has disabled directory queries.".to_string()));
}
let (ids, not_found_ids) = request.unwrap_ids(self.core.jmap.get_max_objects)?;
let properties = request.unwrap_properties(&[
PrincipalProperty::Id,
PrincipalProperty::Type,
PrincipalProperty::Name,
PrincipalProperty::Description,
PrincipalProperty::Email,
]);
// Return all principals
let principal_ids = self
.registry()
.query::<RoaringBitmap>(
RegistryQuery::new(ObjectType::Account).with_tenant(access_token.tenant_id()),
)
.await
.caused_by(trc::location!())?;
let ids = if let Some(ids) = ids {
ids
} else {
principal_ids
.iter()
.take(self.core.jmap.get_max_objects)
.map(Into::into)
.collect::<Vec<_>>()
};
let mut response = GetResponse {
account_id: request.account_id.into(),
state: State::Initial.into(),
list: Vec::with_capacity(ids.len()),
not_found: not_found_ids,
};
for id in ids {
// Obtain the principal
let document_id = id.document_id();
if !principal_ids.contains(document_id) {
response.push_not_found(id);
continue;
};
let principal = self
.account(document_id)
.await
.caused_by(trc::location!())?;
let mut result = Map::with_capacity(properties.len());
for property in &properties {
let value = match property {
PrincipalProperty::Id => Value::Element(PrincipalValue::Id(id)),
PrincipalProperty::Type => {
Value::Element(PrincipalValue::Type(if principal.is_user_account() {
PrincipalType::Individual
} else {
PrincipalType::Group
}))
}
PrincipalProperty::Name => Value::Str(principal.name().to_string().into()),
PrincipalProperty::Description => principal
.description()
.map(|v| Value::Str(v.to_string().into()))
.unwrap_or(Value::Null),
PrincipalProperty::Email => Value::Str(principal.name().to_string().into()),
PrincipalProperty::Accounts => Value::Object(Map::from(vec![(
Key::Property(PrincipalProperty::IdValue(id)),
Value::Object(Map::from_iter(
[
Capability::Mail,
Capability::Contacts,
Capability::Calendars,
Capability::FileNode,
Capability::Principals,
]
.iter()
.map(|cap| {
(
Key::Property(PrincipalProperty::Capability(*cap)),
Value::Object(Map::new()),
)
})
.chain([
(
Key::Property(PrincipalProperty::Capability(
Capability::PrincipalsOwner,
)),
Value::Object(Map::from(vec![
(
Key::Borrowed("accountIdForPrincipal"),
Value::Element(PrincipalValue::Id(id)),
),
(
Key::Borrowed("principalId"),
Value::Element(PrincipalValue::Id(id)),
),
])),
),
(
Key::Property(PrincipalProperty::Capability(
Capability::Calendars,
)),
Value::Object(Map::from(vec![
(
Key::Borrowed("accountId"),
Value::Element(PrincipalValue::Id(id)),
),
(Key::Borrowed("mayGetAvailability"), Value::Bool(true)),
(Key::Borrowed("mayShareWith"), Value::Bool(true)),
(
Key::Borrowed("calendarAddress"),
Value::Str(
format!("mailto:{}", principal.name()).into(),
),
),
])),
),
]),
)),
)])),
PrincipalProperty::Capabilities => Value::Object(Map::from_iter(
[
Capability::Mail,
Capability::Contacts,
Capability::Calendars,
Capability::FileNode,
Capability::Principals,
]
.iter()
.map(|cap| {
(
Key::Property(PrincipalProperty::Capability(*cap)),
Value::Object(Map::new()),
)
}),
)),
_ => Value::Null,
};
result.insert_unchecked(property.clone(), value);
}
response.list.push(result.into());
}
Ok(response)
}
}
+9
View File
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod availability;
pub mod get;
pub mod query;
+163
View File
@@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::api::query::QueryResponseBuilder;
use common::{Server, auth::AccessToken};
use jmap_proto::{
method::query::{Filter, QueryRequest, QueryResponse},
object::principal::{Principal, PrincipalFilter, PrincipalType},
types::state::State,
};
use registry::{
schema::{
enums::AccountType,
prelude::{ObjectType, Permission, Property},
},
types::EnumImpl,
};
use std::future::Future;
use store::{
registry::RegistryQuery,
roaring::RoaringBitmap,
search::{SearchFilter, SearchQuery},
write::SearchIndex,
};
use trc::AddContext;
pub trait PrincipalQuery: Sync + Send {
fn principal_query(
&self,
request: QueryRequest<Principal>,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
}
impl PrincipalQuery for Server {
async fn principal_query(
&self,
mut request: QueryRequest<Principal>,
access_token: &AccessToken,
) -> trc::Result<QueryResponse> {
if !self.core.groupware.allow_directory_query
&& !access_token.has_permission(Permission::JmapPrincipalQuery)
{
return Err(trc::JmapEvent::Forbidden
.into_err()
.details("The administrator has disabled directory queries.".to_string()));
}
let principal_ids = self
.registry()
.query::<RoaringBitmap>(
RegistryQuery::new(ObjectType::Account).with_tenant(access_token.tenant_id()),
)
.await
.caused_by(trc::location!())?;
let mut filters = Vec::with_capacity(request.filter.len());
for cond in std::mem::take(&mut request.filter) {
match cond {
Filter::Property(cond) => match cond {
PrincipalFilter::Name(name) | PrincipalFilter::Email(name) => {
filters.push(SearchFilter::is_in_set(
match self.account_id_from_email(&name, false).await? {
Some(account_id) => {
RoaringBitmap::from_sorted_iter([account_id]).unwrap()
}
None => RoaringBitmap::new(),
},
));
}
PrincipalFilter::AccountIds(ids) => {
filters.push(SearchFilter::is_in_set(
ids.into_iter()
.filter_map(|id| {
let id = id.document_id();
if principal_ids.contains(id) {
Some(id)
} else {
None
}
})
.collect::<RoaringBitmap>(),
));
}
PrincipalFilter::Text(text) => {
filters.push(SearchFilter::is_in_set(
self.registry()
.query::<RoaringBitmap>(
RegistryQuery::new(ObjectType::Account)
.with_tenant(access_token.tenant_id())
.text(Property::Text, text),
)
.await
.caused_by(trc::location!())?,
));
}
PrincipalFilter::Type(principal_type) => {
let typ = match principal_type {
PrincipalType::Individual => AccountType::User,
PrincipalType::Group => AccountType::Group,
_ => {
filters.push(SearchFilter::is_in_set(Default::default()));
continue;
}
};
filters.push(SearchFilter::is_in_set(
self.registry()
.query::<RoaringBitmap>(
RegistryQuery::new(ObjectType::Account)
.equal(Property::Type, typ.to_id())
.with_tenant(access_token.tenant_id()),
)
.await
.caused_by(trc::location!())?,
));
}
other => {
return Err(trc::JmapEvent::UnsupportedFilter
.into_err()
.details(other.to_string()));
}
},
Filter::And => {
filters.push(SearchFilter::And);
}
Filter::Or => {
filters.push(SearchFilter::Or);
}
Filter::Not => {
filters.push(SearchFilter::Not);
}
Filter::Close => {
filters.push(SearchFilter::End);
}
}
}
let results = SearchQuery::new(SearchIndex::InMemory)
.with_filters(filters)
.with_mask(principal_ids)
.filter()
.into_bitmap();
let mut response = QueryResponseBuilder::new(
results.len() as usize,
self.core.jmap.query_max_results,
State::Initial,
&request,
);
for document_id in results {
if !response.add(0, document_id) {
break;
}
}
response.build()
}
}