From 80633c344101f02b5eaaa54cc390276e548cae93 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Tue, 26 Aug 2025 10:46:37 +0200 Subject: [PATCH] ITIP: Add organizer to iMIP replies if missing to deal with MS Exchange 2010 bug --- crates/email/src/message/ingest.rs | 1 + crates/groupware/src/calendar/itip.rs | 45 +++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/email/src/message/ingest.rs b/crates/email/src/message/ingest.rs index bb5d3efb..19f0b726 100644 --- a/crates/email/src/message/ingest.rs +++ b/crates/email/src/message/ingest.rs @@ -328,6 +328,7 @@ impl EmailIngest for Server { params.access_token, &resource_token, sender, + deliver_to, itip_message, ) .await diff --git a/crates/groupware/src/calendar/itip.rs b/crates/groupware/src/calendar/itip.rs index 4fcdc006..12134942 100644 --- a/crates/groupware/src/calendar/itip.rs +++ b/crates/groupware/src/calendar/itip.rs @@ -19,8 +19,8 @@ use crate::{ use calcard::{ common::timezone::Tz, icalendar::{ - ICalendar, ICalendarComponentType, ICalendarMethod, ICalendarParameter, - ICalendarParticipationStatus, ICalendarProperty, + ICalendar, ICalendarComponentType, ICalendarEntry, ICalendarMethod, ICalendarParameter, + ICalendarParticipationStatus, ICalendarProperty, ICalendarValue, }, }; use common::{ @@ -52,6 +52,7 @@ pub trait ItipIngest: Sync + Send { access_token: &AccessToken, resource_token: &ResourceToken, sender: &str, + recipient: &str, itip_message: &str, ) -> impl Future>, ItipIngestError>> + Send; @@ -75,10 +76,11 @@ impl ItipIngest for Server { access_token: &AccessToken, resource_token: &ResourceToken, sender: &str, + recipient: &str, itip_message: &str, ) -> Result>, ItipIngestError> { // Parse and validate the iTIP message - let itip = ICalendar::parse(itip_message) + let mut itip = ICalendar::parse(itip_message) .map_err(|_| ItipIngestError::Message(ItipError::ICalendarParseError)) .and_then(|ical| { if ical.components.len() > 1 @@ -89,6 +91,43 @@ impl ItipIngest for Server { Err(ItipIngestError::Message(ItipError::ICalendarParseError)) } })?; + + // Microsoft Exchange does not include the organizer in REPLY, assume it is the recipient. + // This will be validated against the stored event anyway. + if itip.components[0] + .property(&ICalendarProperty::Method) + .and_then(|v| v.values.first()) + .is_some_and(|v| { + matches!( + v, + ICalendarValue::Method(ICalendarMethod::Reply | ICalendarMethod::Request) + ) + }) + { + for comp in &mut itip.components { + if comp.component_type.is_scheduling_object() { + let mut has_organizer = false; + let mut has_attendee = false; + + for entry in &comp.entries { + match entry.name { + ICalendarProperty::Organizer => has_organizer = true, + ICalendarProperty::Attendee => has_attendee = true, + _ => {} + } + } + + if has_attendee && !has_organizer { + comp.entries.push(ICalendarEntry { + name: ICalendarProperty::Organizer, + params: vec![], + values: vec![ICalendarValue::Text(format!("mailto:{recipient}"))], + }); + } + } + } + } + let itip_snapshots = itip_snapshot(&itip, access_token.emails.as_slice(), false)?; if !itip_snapshots.sender_is_organizer_or_attendee(sender) { return Err(ItipIngestError::Message(