From 15762fba2ba335e560b8d25f71af085a8b6b6cf2 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Tue, 9 Sep 2025 21:27:15 +0200 Subject: [PATCH] CalDAV: Limit recurrence expansions in calendar reports --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- CHANGELOG.md | 1 + crates/dav/src/calendar/query.rs | 14 ++++- crates/dav/src/common/propfind.rs | 83 +++++++++++---------------- 4 files changed, 49 insertions(+), 53 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b992854d..c13cfba7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,4 +1,4 @@ -name: I am absolutely certain I found a bug +name: I found a bug description: Most reported issues turn out to be configuration problems rather than actual bugs. If you are not 100% certain this is a bug, please start a new discussion instead. title: "🐛: " labels: ["bug"] @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Thanks for taking the time to fill out this bug report! Use this form only for reporting bugs. If you have a question or problem, please use the [Q&A discussion](https://github.com/stalwartlabs/stalwart/discussions/new?category=q-a). + Thanks for taking the time to fill out this bug report! Most reported issues turn out to be configuration problems rather than actual bugs. If you are not 100% certain this is a bug, please start a new [discussion](https://github.com/stalwartlabs/stalwart/discussions/new?category=q-a) instead. - type: textarea id: what-happened attributes: diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b2b849..8644aa3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ If you are upgrading from v0.11.x or v0.12.x, this version includes **breaking c - IMAP: Add owner rights to ACL get responses. - IMAP: Do not auto-train Bayes when moving messages from Junk to Trash. - IMAP/ManageSieve: Increase maximum quoted argument size (fixes #2039). +- CalDAV: Limit recurrence expansions in calendar reports. - WebDAV: Do not fix percent encoding on WebDAV FS (closes #2036). ## [0.13.2] - 2025-07-28 diff --git a/crates/dav/src/calendar/query.rs b/crates/dav/src/calendar/query.rs index 970763e8..ffcfdd92 100644 --- a/crates/dav/src/calendar/query.rs +++ b/crates/dav/src/calendar/query.rs @@ -420,7 +420,12 @@ impl CalendarQueryHandler { is_all || matches_one } - pub fn serialize_ical(&mut self, event: &ArchivedCalendarEvent, data: &CalendarData) -> String { + pub fn serialize_ical( + &mut self, + event: &ArchivedCalendarEvent, + data: &CalendarData, + instances_limit: &mut usize, + ) -> Option { let mut out = String::with_capacity(event.size.to_native() as usize); let _v = [0.into()]; let mut component_iter: Iter<'_, rkyv::rend::u16_le> = _v.iter(); @@ -528,6 +533,11 @@ impl CalendarQueryHandler { && (!is_recurrent_or_override || expand.is_in_range(is_todo, event.start, event.end)) { + if *instances_limit > 0 { + *instances_limit -= 1; + } else { + return None; + } let _ = write!(&mut out, "BEGIN:{component_name}\r\n"); // Write DTSTART, DTEND and RECURRENCE-ID @@ -607,7 +617,7 @@ impl CalendarQueryHandler { } } - out + Some(out) } pub fn into_expanded_times(self) -> Vec> { diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 26301e24..ba564fa4 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -296,6 +296,7 @@ impl PropFindRequestHandler for Server { ); let mut is_sync_limited = false; let mut is_propfind = false; + let mut ical_instances_limit = self.core.groupware.max_ical_instances; let paths = match std::mem::take(&mut query.resource) { DavQueryResource::Uri(resource) => { @@ -351,38 +352,6 @@ impl PropFindRequestHandler for Server { items } - /*DavQueryResource::Discovery { - parent_collection, - account_ids, - } => { - collection_container = parent_collection; - collection_children = collection_container.child_collection().unwrap(); - sync_collection = SyncCollection::from(collection_container); - - // Add container info - if !query.depth_no_root { - add_base_collection_response( - self, - &query.propfind, - parent_collection, - access_token, - &mut response, - ) - .await?; - } - - discover_root_paths( - self, - access_token, - collection_container, - sync_collection, - &query, - &mut data, - &mut response, - account_ids, - ) - .await? - }*/ DavQueryResource::None => unreachable!(), }; response.set_namespace(collection_container.namespace()); @@ -441,7 +410,7 @@ impl PropFindRequestHandler for Server { let view_as_id = access_token.primary_id(); let is_scheduling = collection_container == Collection::CalendarScheduling; - for item in paths { + 'outer: for item in paths { let account_id = item.account_id; let document_id = item.document_id; let collection = if item.is_container { @@ -981,20 +950,27 @@ impl PropFindRequestHandler for Server { CalDavProperty::CalendarData(data), ArchivedResource::CalendarEvent(event), ) => { - let ical = if calendar_filter.is_some() || !data.properties.is_empty() { - calendar_filter + if calendar_filter.is_some() || !data.properties.is_empty() { + if let Some(ical) = calendar_filter .get_or_insert_with(|| { CalendarQueryHandler::new(event.inner, None, Tz::UTC) }) - .serialize_ical(event.inner, data) + .serialize_ical(event.inner, data, &mut ical_instances_limit) + { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::CData(ical), + )); + } else { + limit = 0; + break 'outer; + } } else { - event.inner.data.event.to_string() - }; - - fields.push(DavPropertyValue::new( - property.clone(), - DavValue::CData(ical), - )); + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::CData(event.inner.data.event.to_string()), + )); + } } ( CalDavProperty::CalendarData(_), @@ -1092,12 +1068,21 @@ impl PropFindRequestHandler for Server { response.add_response( Response::new_status([query.uri], StatusCode::INSUFFICIENT_STORAGE) .with_error(BaseCondition::NumberOfMatchesWithinLimit) - .with_response_description(format!( - "The number of matches exceeds the limit of {}", - query - .limit - .unwrap_or(self.core.groupware.max_results as u32) - )), + .with_response_description(if ical_instances_limit > 0 { + format!( + "The number of matches exceeds the limit of {}", + query + .limit + .unwrap_or(self.core.groupware.max_results as u32) + ) + } else { + format!( + "The number of recurrence instances exceeds the limit of {}", + query + .limit + .unwrap_or(self.core.groupware.max_ical_instances as u32) + ) + }), ); }