diff --git a/crates/groupware/src/calendar/storage.rs b/crates/groupware/src/calendar/storage.rs index 3a63457d..404af7f7 100644 --- a/crates/groupware/src/calendar/storage.rs +++ b/crates/groupware/src/calendar/storage.rs @@ -391,7 +391,7 @@ impl DestroyArchive> { .caused_by(trc::location!())?; if let Ok(messages) = - itip_cancel(&event.data.event, access_token.emails.as_slice()) + itip_cancel(&event.data.event, access_token.emails.as_slice(), true) { ItipMessages::new(vec![messages]) .queue(batch) diff --git a/crates/groupware/src/scheduling/attendee.rs b/crates/groupware/src/scheduling/attendee.rs index 5339cad1..af5564ff 100644 --- a/crates/groupware/src/scheduling/attendee.rs +++ b/crates/groupware/src/scheduling/attendee.rs @@ -59,6 +59,7 @@ pub(crate) fn attendee_handle_update( old_instance, &dt_stamp, &mut email_rcpt, + false, ) { // Add EXDATE as RECURRENCE-ID cancel_comp @@ -207,6 +208,7 @@ pub(crate) fn attendee_handle_update( old_instance, &dt_stamp, &mut email_rcpt, + false, ) { // Add cancel component let comp_id = message.components.len() as u16; @@ -269,6 +271,7 @@ pub(crate) fn attendee_decline<'x>( comp: &'x ItipSnapshot<'x>, dt_stamp: &'x PartialDateTime, email_rcpt: &mut AHashSet<&'x str>, + skip_needs_action: bool, ) -> Option<(ICalendarComponent, &'x Email)> { let component = comp.comp; let mut cancel_comp = ICalendarComponent { @@ -284,14 +287,14 @@ pub(crate) fn attendee_decline<'x>( if attendee.email.is_local { if attendee.is_server_scheduling && attendee.rsvp.is_none_or(|rsvp| rsvp) - && (attendee.force_send.is_some() - || !matches!( - attendee.part_stat, - Some( - ICalendarParticipationStatus::Declined - | ICalendarParticipationStatus::Delegated - ) - )) + && match attendee.part_stat { + Some( + ICalendarParticipationStatus::Declined + | ICalendarParticipationStatus::Delegated, + ) => attendee.force_send.is_some(), + Some(ICalendarParticipationStatus::NeedsAction) => !skip_needs_action, + _ => true, + } { local_attendee = Some(attendee); } diff --git a/crates/groupware/src/scheduling/event_cancel.rs b/crates/groupware/src/scheduling/event_cancel.rs index 875705b1..a047e8f3 100644 --- a/crates/groupware/src/scheduling/event_cancel.rs +++ b/crates/groupware/src/scheduling/event_cancel.rs @@ -22,6 +22,7 @@ use calcard::{ pub fn itip_cancel( ical: &ICalendar, account_emails: &[String], + is_deletion: bool, ) -> Result, ItipError> { // Prepare iTIP message let itip = itip_snapshot(ical, account_emails, false)?; @@ -88,9 +89,14 @@ pub fn itip_cancel( let mut mail_from = None; let mut email_rcpt = AHashSet::new(); for (instance_id, comp) in &itip.components { - if let Some((cancel_comp, attendee_email)) = - attendee_decline(instance_id, &itip, comp, &dt_stamp, &mut email_rcpt) - { + if let Some((cancel_comp, attendee_email)) = attendee_decline( + instance_id, + &itip, + comp, + &dt_stamp, + &mut email_rcpt, + is_deletion, + ) { // Add cancel component let comp_id = message.components.len() as u16; message.components[0].component_ids.push(comp_id); diff --git a/crates/groupware/src/scheduling/event_update.rs b/crates/groupware/src/scheduling/event_update.rs index cfa636d5..5e97950f 100644 --- a/crates/groupware/src/scheduling/event_update.rs +++ b/crates/groupware/src/scheduling/event_update.rs @@ -39,7 +39,7 @@ pub fn itip_update( | ItipError::OtherSchedulingAgent => { if old_itip.organizer.email.is_local { // RFC 6638 does not support replacing the organizer, so we cancel the event - itip_cancel(old_ical, account_emails).map(|message| vec![message]) + itip_cancel(old_ical, account_emails, false).map(|message| vec![message]) } else { Err(ItipError::CannotModifyAddress) } diff --git a/tests/src/webdav/cal_itip.rs b/tests/src/webdav/cal_itip.rs index c78638c3..d57b15ce 100644 --- a/tests/src/webdav/cal_itip.rs +++ b/tests/src/webdav/cal_itip.rs @@ -31,7 +31,7 @@ struct Test { enum Command { Put, Get, - Delete, + Delete(bool), Expect, Send, Reset, @@ -81,7 +81,8 @@ pub fn test() { "get" => Command::Get, "expect" => Command::Expect, "send" => Command::Send, - "delete" => Command::Delete, + "delete" => Command::Delete(false), + "delete-force-send" => Command::Delete(true), "reset" => Command::Reset, "itip" => Command::Itip, _ => panic!("Unknown command: {}", last_command), @@ -185,7 +186,7 @@ pub fn test() { ); }); } - Command::Delete => { + Command::Delete(force_send) => { let account = command .parameters .first() @@ -200,7 +201,8 @@ pub fn test() { if let Some(ical) = store.remove(name) { last_itip = Some( - itip_cancel(&ical, &[account.to_string()]).map(|message| vec![message]), + itip_cancel(&ical, &[account.to_string()], force_send) + .map(|message| vec![message]), ); } else { panic!(