/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::{decode_mailto_address, extract_addr_spec}; use ahash::{AHashMap, AHashSet}; use calcard::{ common::{IanaString, PartialDateTime}, icalendar::{ ICalendar, ICalendarComponent, ICalendarDuration, ICalendarEntry, ICalendarMethod, ICalendarParameter, ICalendarParticipationRole, ICalendarParticipationStatus, ICalendarPeriod, ICalendarProperty, ICalendarRecurrenceRule, ICalendarScheduleForceSendValue, ICalendarStatus, ICalendarUserTypes, ICalendarValue, Uri, }, }; use indexmap::IndexSet; use registry::schema::structs::TaskCalendarItipContents; use std::{fmt::Display, hash::Hash}; use utils::sanitize_email; pub mod attendee; pub mod event_cancel; pub mod event_create; pub mod event_update; pub mod format; pub mod inbound; pub mod itip; pub mod organizer; pub mod snapshot; #[derive(Debug)] pub struct ItipSnapshots<'x> { pub organizer: Organizer<'x>, pub uid: &'x str, pub components: AHashMap>, } #[derive(Debug)] pub struct ItipSnapshot<'x> { pub comp_id: u16, pub comp: &'x ICalendarComponent, pub attendees: AHashSet>, pub dtstamp: Option<&'x PartialDateTime>, pub entries: ItipEntries<'x>, pub sequence: Option, pub request_status: Vec<&'x str>, } pub type ItipEntries<'x> = IndexSet, ahash::RandomState>; #[derive(Debug, PartialEq, Eq, Hash)] pub struct ItipEntry<'x> { pub name: &'x ICalendarProperty, pub value: ItipEntryValue<'x>, } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ItipEntryValue<'x> { DateTime(ItipDateTime<'x>), Period(&'x ICalendarPeriod), Duration(&'x ICalendarDuration), Status(&'x ICalendarStatus), RRule(&'x ICalendarRecurrenceRule), Text(&'x str), Integer(i64), } #[derive(Debug)] pub struct ItipDateTime<'x> { pub date: &'x PartialDateTime, pub tz_id: Option<&'x str>, pub tz_code: u16, pub timestamp: i64, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InstanceId { Main, Recurrence(RecurrenceId), } #[derive(Debug, PartialOrd, Ord)] pub struct RecurrenceId { pub entry_id: u16, pub date: i64, pub this_and_future: bool, } #[derive(Debug)] pub struct Attendee<'x> { pub entry_id: u16, pub email: Email, pub name: Option<&'x str>, pub part_stat: Option<&'x ICalendarParticipationStatus>, pub delegated_from: Vec, pub delegated_to: Vec, pub role: Option<&'x ICalendarParticipationRole>, pub cu_type: Option<&'x ICalendarUserTypes>, pub sent_by: Option, pub rsvp: Option, pub is_server_scheduling: bool, pub force_send: Option<&'x ICalendarScheduleForceSendValue>, } #[derive(Debug)] pub struct Organizer<'x> { pub entry_id: u16, pub email: Email, pub name: Option<&'x str>, pub is_server_scheduling: bool, pub force_send: Option<&'x ICalendarScheduleForceSendValue>, } #[derive(Debug)] pub struct Email { pub email: String, pub is_local: bool, } #[derive(Debug)] pub enum ItipError { NoSchedulingInfo, OtherSchedulingAgent, NotOrganizer, NotOrganizerNorAttendee, NothingToSend, MissingUid, MultipleUid, MultipleOrganizer, MultipleObjectTypes, MultipleObjectInstances, CannotModifyProperty(ICalendarProperty), CannotModifyInstance, CannotModifyAddress, OrganizerMismatch, MissingMethod, InvalidComponentType, OutOfSequence, OrganizerIsLocalAddress, SenderIsNotOrganizerNorAttendee, SenderIsNotParticipant(String), UnknownParticipant(String), UnsupportedMethod(ICalendarMethod), ICalendarParseError, EventNotFound, EventTooLarge, QuotaExceeded, NoDefaultCalendar, AutoAddDisabled, } #[derive(Debug)] pub struct ItipMessage { pub from: String, pub from_organizer: bool, pub to: Vec, pub summary: ItipSummary, pub message: T, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum ItipSummary { Invite(Vec), Update { method: ICalendarMethod, current: Vec, previous: Vec, }, Cancel(Vec), Rsvp { part_stat: ICalendarParticipationStatus, current: Vec, }, } #[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ItipField { pub name: ICalendarProperty, pub value: ItipValue, } #[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(tag = "type", content = "value")] pub enum ItipValue { Text(String), Time(ItipTime), Rrule(Box), Participants(Vec), } #[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ItipTime { pub start: i64, pub tz_id: u16, } #[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ItipParticipant { pub email: String, pub name: Option, pub is_organizer: bool, } pub struct ItipMessages { pub messages: Vec, } impl Attendee<'_> { pub fn send_invite_messages(&self) -> bool { !self.email.is_local && self.is_server_scheduling && self.rsvp.is_none_or(|rsvp| rsvp) && (self.force_send.is_some() || self.part_stat.is_none_or(|part_stat| { part_stat == &ICalendarParticipationStatus::NeedsAction })) } pub fn send_update_messages(&self) -> bool { !self.email.is_local && self.is_server_scheduling && self.rsvp.is_none_or(|rsvp| rsvp) && (self.force_send.is_some() || self .part_stat .is_none_or(|part_stat| part_stat != &ICalendarParticipationStatus::Declined)) } pub fn is_delegated_from(&self, attendee: &Attendee<'_>) -> bool { self.delegated_from .iter() .any(|d| d.email == attendee.email.email) } pub fn is_delegated_to(&self, attendee: &Attendee<'_>) -> bool { self.delegated_to .iter() .any(|d| d.email == attendee.email.email) } } impl Email { pub fn new(email: &str, local_addresses: &[String]) -> Option { let decoded = decode_mailto_address(email.trim()); let email = sanitize_email(decoded.as_ref()) .or_else(|| extract_addr_spec(decoded.as_ref()).and_then(sanitize_email))?; let is_local = local_addresses.contains(&email); Some(Email { email, is_local }) } pub fn from_uri(uri: &Uri, local_addresses: &[String]) -> Option { if let Uri::Location(uri) = uri { Email::new(uri.as_str(), local_addresses) } else { None } } } pub fn ical_size(ical: &ICalendar) -> usize { struct SizeWriter(usize); impl std::fmt::Write for SizeWriter { fn write_str(&mut self, text: &str) -> std::fmt::Result { self.0 += text.len(); Ok(()) } } let mut writer = SizeWriter(0); let _ = ical.write_to(&mut writer); writer.0 } impl PartialEq for Attendee<'_> { fn eq(&self, other: &Self) -> bool { self.email == other.email && self.part_stat == other.part_stat && self.delegated_from == other.delegated_from && self.delegated_to == other.delegated_to && self.role == other.role && self.cu_type == other.cu_type && self.sent_by == other.sent_by } } impl Eq for Attendee<'_> {} impl Hash for Attendee<'_> { fn hash(&self, state: &mut H) { self.email.hash(state); self.part_stat.hash(state); self.delegated_from.hash(state); self.delegated_to.hash(state); self.role.hash(state); self.cu_type.hash(state); self.sent_by.hash(state); } } impl Display for Email { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "mailto:{}", self.email) } } impl Hash for Email { fn hash(&self, state: &mut H) { self.email.hash(state); } } impl PartialEq for Email { fn eq(&self, other: &Self) -> bool { self.email == other.email } } impl Eq for Email {} impl PartialEq for RecurrenceId { fn eq(&self, other: &Self) -> bool { self.date == other.date && self.this_and_future == other.this_and_future } } impl Eq for RecurrenceId {} impl Hash for RecurrenceId { fn hash(&self, state: &mut H) { self.date.hash(state); self.this_and_future.hash(state); } } impl PartialEq for ItipDateTime<'_> { fn eq(&self, other: &Self) -> bool { self.timestamp == other.timestamp } } impl Eq for ItipDateTime<'_> {} impl Hash for ItipDateTime<'_> { fn hash(&self, state: &mut H) { self.timestamp.hash(state); } } impl ItipDateTime<'_> { pub fn to_entry(&self, name: ICalendarProperty) -> ICalendarEntry { ICalendarEntry { name, params: self .tz_id .map(|tz_id| vec![ICalendarParameter::tzid(tz_id.to_string())]) .unwrap_or_default(), values: vec![ICalendarValue::PartialDateTime(Box::new(self.date.clone()))], } } } impl ItipError { pub fn is_jmap_error(&self) -> bool { matches!( self, ItipError::MultipleOrganizer | ItipError::OrganizerIsLocalAddress | ItipError::SenderIsNotParticipant(_) | ItipError::OrganizerMismatch | ItipError::CannotModifyProperty(_) | ItipError::CannotModifyInstance | ItipError::CannotModifyAddress //| ItipError::MissingUid | ItipError::MultipleUid | ItipError::MultipleObjectTypes | ItipError::MultipleObjectInstances | ItipError::MissingMethod | ItipError::InvalidComponentType | ItipError::OutOfSequence | ItipError::UnknownParticipant(_) | ItipError::UnsupportedMethod(_) ) } } impl Display for ItipError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ItipError::NoSchedulingInfo => write!(f, "No scheduling information found"), ItipError::OtherSchedulingAgent => write!(f, "Other scheduling agent"), ItipError::NotOrganizer => write!(f, "Not the organizer of the event"), ItipError::NotOrganizerNorAttendee => write!(f, "Not an organizer or attendee"), ItipError::NothingToSend => write!(f, "No iTIP messages to send"), ItipError::MissingUid => write!(f, "Missing UID in iCalendar object"), ItipError::MultipleUid => write!(f, "Multiple UIDs found in iCalendar object"), ItipError::MultipleOrganizer => { write!(f, "Multiple organizers found in iCalendar object") } ItipError::MultipleObjectTypes => { write!(f, "Multiple object types found in iCalendar object") } ItipError::MultipleObjectInstances => { write!(f, "Multiple object instances found in iCalendar object") } ItipError::CannotModifyProperty(prop) => { write!(f, "Cannot modify property {}", prop.as_str()) } ItipError::CannotModifyInstance => write!(f, "Cannot modify instance of the event"), ItipError::CannotModifyAddress => write!(f, "Cannot modify address of the event"), ItipError::OrganizerMismatch => write!(f, "Organizer mismatch in iCalendar object"), ItipError::MissingMethod => write!(f, "Missing method in the iTIP message"), ItipError::InvalidComponentType => { write!(f, "Invalid component type in iCalendar object") } ItipError::OutOfSequence => write!(f, "Old sequence number found"), ItipError::OrganizerIsLocalAddress => { write!( f, "Organizer matches one of the recipient's account addresses" ) } ItipError::SenderIsNotParticipant(participant) => { write!(f, "Sender {participant:?} is not a participant") } ItipError::SenderIsNotOrganizerNorAttendee => { write!(f, "Sender is neither organizer nor attendee") } ItipError::UnknownParticipant(participant) => { write!(f, "Unknown participant: {}", participant) } ItipError::UnsupportedMethod(method) => { write!(f, "Unsupported method: {}", method.as_str()) } ItipError::ICalendarParseError => write!(f, "Failed to parse iCalendar object"), ItipError::EventNotFound => write!(f, "Event found in index but not in database"), ItipError::EventTooLarge => write!( f, "Applying the iTIP message would exceed the maximum event size" ), ItipError::QuotaExceeded => write!(f, "Quota exceeded"), ItipError::NoDefaultCalendar => write!(f, "No default calendar found for the account"), ItipError::AutoAddDisabled => { write!(f, "Auto-adding events is disabled for this account") } } } }