itip.rs
⎇
Raw
1//! Implicit scheduling (RFC 6638) as iTIP messages (RFC 5546): what a change
2//! to a scheduling object sends to whom, and how each copy changes.
3//!
4//! Calendar user addresses are compared through closures, so the caller maps
5//! them onto its principals.
6
7use std::cell::{Cell, OnceCell, RefCell};
8use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10use std::sync::Arc;
11
12use calcard::common::PartialDateTime;
13use calcard::icalendar::{
14 ICalendar, ICalendarComponent, ICalendarComponentType, ICalendarDuration, ICalendarEntry,
15 ICalendarFrequency, ICalendarMethod, ICalendarParameter, ICalendarParameterName,
16 ICalendarParameterValue, ICalendarParticipationStatus, ICalendarProperty,
17 ICalendarRecurrenceRule, ICalendarStatus, ICalendarValue, ICalendarWeekday, Uri,
18};
19use chrono::{DateTime, TimeDelta, Utc};
20use xmltree::Element;
21
22use crate::expand::{
23 Expansion, MAX_OCCURRENCES, expand, expand_in, is_item, prop_stamp, rule, stamp, until_utc,
24};
25use crate::filter::TimeRange;
26use crate::freebusy::{Busy, Period, merge};
27use crate::object::MAX_COMPONENTS;
28use crate::text::{fold, logical_lines, name, param_parts, unfold, value};
29use crate::xml::{CALDAV, el};
30use crate::zone::{Zone, Zones, add, add_local};
31
32/// Whether an address belongs to someone in particular.
33pub type Is<'a> = &'a dyn Fn(&str) -> bool;
34
35/// How the owner of a calendar takes part in one of its objects (RFC 6638,
36/// 3.1).
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Role {
39 Organizer,
40 Attendee,
41 /// Not a scheduling object for this owner.
42 None,
43}
44
45/// A scheduling precondition a PUT fails.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Refused {
48 SameOrganizer,
49 AttendeeChange,
50}
51
52impl Refused {
53 pub fn condition(self) -> Element {
54 match self {
55 Refused::SameOrganizer => el(CALDAV, "same-organizer-in-all-components"),
56 Refused::AttendeeChange => el(CALDAV, "allowed-attendee-scheduling-object-change"),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum Method {
63 Request,
64 Cancel,
65 Reply,
66}
67
68#[derive(Debug, Clone)]
69pub struct Message {
70 /// The recipient's address, as the object writes it.
71 pub to: String,
72 pub method: Method,
73 /// Only other attendees' answers or the SEQUENCE changed. It updates an
74 /// existing copy, keeps its Schedule-Tag and leaves no inbox entry.
75 pub quiet: bool,
76 /// With METHOD. Recipients who see the same thing share one.
77 pub cal: Arc<ICalendar>,
78}
79
80pub fn role(cal: &ICalendar, owner: Is) -> Result<Role, Refused> {
81 let obj = Obj::new(cal);
82 let mut organizers = obj
83 .comps()
84 .filter_map(|c| address(c.c.property(&ICalendarProperty::Organizer)?));
85 let Some(organizer) = organizers.next() else {
86 return Ok(Role::None);
87 };
88 if organizers.any(|o| !o.eq_ignore_ascii_case(organizer)) {
89 return Err(Refused::SameOrganizer);
90 }
91 if owner(organizer) {
92 return Ok(Role::Organizer);
93 }
94 let attends = obj
95 .comps()
96 .flat_map(|c| attendees(&c.c))
97 .any(|e| address(e).is_some_and(owner));
98 Ok(if attends { Role::Attendee } else { Role::None })
99}
100
101/// The ORGANIZER address of a scheduling object.
102pub fn organizer(cal: &ICalendar) -> Option<&str> {
103 cal.components
104 .iter()
105 .filter(|c| is_item(c))
106 .find_map(|c| address(c.property(&ICalendarProperty::Organizer)?))
107}
108
109/// Names who acted for the owner: `SENT-BY` on the owner's ORGANIZER and
110/// ATTENDEE properties when `sender` is someone else, none when it is the
111/// owner. Only for writes that send a message, so every message names its
112/// real sender.
113pub fn stamp_sender(cal: &mut ICalendar, owner: Is, sender: Option<&str>) {
114 for c in cal.components.iter_mut().filter(|c| is_item(c)) {
115 for e in &mut c.entries {
116 let theirs = matches!(
117 e.name,
118 ICalendarProperty::Organizer | ICalendarProperty::Attendee
119 ) && address(e).is_some_and(owner);
120 match (theirs, sender) {
121 (false, _) => {}
122 (true, Some(s)) => set_param(
123 e,
124 ICalendarParameterName::SentBy,
125 ICalendarParameterValue::Uri(Uri::Location(s.to_string())),
126 ),
127 (true, None) => remove_param(e, &ICalendarParameterName::SentBy),
128 }
129 }
130 }
131}
132
133/// How far ahead a room checks an invitation against its bookings. A
134/// series without end has infinite instances, so it is checked for two years
135/// and accepted beyond. Anything else is checked to its end, at most ten
136/// years ahead.
137pub fn answer_horizon(copy: &ICalendar) -> TimeDelta {
138 let endless = copy
139 .components
140 .iter()
141 .any(|c| is_item(c) && !c.has_property(&ICalendarProperty::RecurrenceId) && endless(c));
142 TimeDelta::days(if endless { 731 } else { 3653 })
143}
144
145/// New overrides and EXDATE values one attendee PUT may bring.
146const MAX_ATTENDEE_CHANGES: usize = 1000;
147
148/// Conflicting instances of a series a room declines one by one. Beyond
149/// that it declines the series.
150const MAX_DECLINED_INSTANCES: usize = 100;
151
152/// The answer of a room or resource to the invitation in its copy:
153/// ACCEPTED, and DECLINED where an instance in `window` overlaps `taken`. A
154/// declined instance of a series gets an override of its own.
155pub fn auto_answer(
156 copy: &ICalendar,
157 me: Is,
158 taken: &[Period],
159 window: &TimeRange,
160 floating: &Zone,
161) -> ICalendar {
162 let taken = merge(
163 taken
164 .iter()
165 .map(|p| Period {
166 kind: Busy::Busy,
167 start: p.start,
168 end: p.end,
169 })
170 .collect(),
171 );
172 let conflicts = |s: DateTime<Utc>, e: DateTime<Utc>| {
173 let i = taken.partition_point(|p| p.end <= s);
174 taken
175 .get(i)
176 .is_some_and(|p| p.start < e || (s == e && p.start <= s))
177 };
178 let mut obj = Obj::new(copy);
179 let form = obj
180 .master()
181 .and_then(|m| m.c.property(&ICalendarProperty::Dtstart))
182 .cloned();
183 let mut declined: HashSet<Option<i64>> = HashSet::new();
184 let mut slots: Vec<DateTime<Utc>> = Vec::new();
185 for x in expand(copy, window.clone(), floating.clone()).instances {
186 if !conflicts(x.start, x.end) {
187 continue;
188 }
189 match x.recurrence_id {
190 Some(rid) if form.is_some() => slots.push(rid),
191 _ => {
192 declined.insert(obj.key(&copy.components[x.component]));
193 }
194 }
195 }
196 // One override each would bloat the copy and the REPLY.
197 let all = slots.len() > MAX_DECLINED_INSTANCES;
198 if let (false, Some(form)) = (all, &form) {
199 // In order, so a range moved on past one instance can move again.
200 slots.sort_unstable();
201 for rid in slots {
202 let Some(key) = obj
203 .recurrence_id(form, rid, floating)
204 .and_then(|id| obj.instant(&id))
205 else {
206 continue;
207 };
208 if obj.find(Some(key)).is_some() {
209 obj.narrow(copy, rid, key, floating);
210 } else if let Some(inst) = obj.single(rid, floating) {
211 obj.push(inst);
212 } else {
213 continue;
214 }
215 declined.insert(Some(key));
216 }
217 }
218 let keys: Vec<Option<i64>> = obj.comps().map(|c| obj.key(&c.c)).collect();
219 for (c, key) in obj.comps_mut().zip(&keys) {
220 let answer = match all || declined.contains(key) {
221 true => ICalendarParticipationStatus::Declined,
222 false => ICalendarParticipationStatus::Accepted,
223 };
224 set_own_partstat(&mut c.c, me, answer);
225 }
226 obj.done()
227}
228
229/// The attendee's copy with `me` answering `answer`: for every component, or
230/// only for the instance at `instance`, which gets an override of its own
231/// when it has none. `floating` reads the instance of an all-day series.
232/// Storing the result through a PUT sends the REPLY. `None` if the series
233/// has no instance at `instance`.
234pub fn respond(
235 copy: &ICalendar,
236 me: Is,
237 answer: ICalendarParticipationStatus,
238 instance: Option<DateTime<Utc>>,
239 floating: &Zone,
240) -> Option<ICalendar> {
241 let mut obj = Obj::new(copy);
242 let target = match instance {
243 None => None,
244 Some(rid) => {
245 let form = obj
246 .master()
247 .and_then(|m| m.c.property(&ICalendarProperty::Dtstart))
248 .or_else(|| {
249 obj.comps()
250 .find_map(|c| c.c.property(&ICalendarProperty::RecurrenceId))
251 })?
252 .clone();
253 let key = obj.instant(&obj.recurrence_id(&form, rid, floating)?)?;
254 if obj.find(Some(key)).is_some() {
255 obj.narrow(copy, rid, key, floating);
256 } else {
257 if !obj.occurs(copy, rid, key, floating) {
258 return None;
259 }
260 let inst = obj.single(rid, floating)?;
261 obj.push(inst);
262 }
263 Some(Some(key))
264 }
265 };
266 let keys: Vec<Option<i64>> = obj.comps().map(|c| obj.key(&c.c)).collect();
267 for (c, key) in obj.comps_mut().zip(&keys) {
268 if target.is_some_and(|t| t != *key) {
269 continue;
270 }
271 for e in
272 c.c.entries
273 .iter_mut()
274 .filter(|e| e.name == ICalendarProperty::Attendee && address(e).is_some_and(me))
275 {
276 set_param(
277 e,
278 ICalendarParameterName::Partstat,
279 partstat(answer.clone()),
280 );
281 remove_param(e, &ICalendarParameterName::Rsvp);
282 }
283 }
284 Some(obj.done())
285}
286
287/// The messages a change of the organizer object sends, without touching
288/// the attendee state in it. `force` lists attendees who get a REQUEST even
289/// if nothing changed for them.
290pub fn messages(
291 old: Option<&ICalendar>,
292 new: Option<&ICalendar>,
293 organizer: Is,
294 force: &[String],
295 now: DateTime<Utc>,
296) -> Vec<Message> {
297 let old = old.map(Obj::new);
298 let new = new.map(Obj::new);
299 let mut who: Vec<&str> = Vec::new();
300 let mut seen: HashSet<String> = HashSet::new();
301 for obj in old.iter().chain(new.iter()) {
302 for e in obj.comps().flat_map(|c| attendees(&c.c)) {
303 if let Some(a) = address(e)
304 && server_agent(e)
305 && !organizer(a)
306 && seen.insert(a.to_ascii_lowercase())
307 {
308 who.push(a);
309 }
310 }
311 }
312 // Attendees invited to the same components share one view and one
313 // message body.
314 let old_inv = old.as_ref().map(Obj::invitations);
315 let new_inv = new.as_ref().map(Obj::invitations);
316 let client: HashSet<String> = new
317 .iter()
318 .flat_map(|n| n.comps().flat_map(|c| attendees(&c.c)))
319 .filter(|e| !server_agent(e))
320 .filter_map(address)
321 .map(str::to_ascii_lowercase)
322 .collect();
323 let mut old_views: HashMap<Vec<bool>, Option<Vec<Node>>> = HashMap::new();
324 let mut new_views: HashMap<Vec<bool>, Option<Vec<Node>>> = HashMap::new();
325 let mut changes: HashMap<(Vec<bool>, Vec<bool>), Change> = HashMap::new();
326 let mut requests: HashMap<Vec<bool>, Arc<ICalendar>> = HashMap::new();
327 let mut cancels: HashMap<(Vec<bool>, Vec<bool>), Arc<ICalendar>> = HashMap::new();
328 let mut out = Vec::new();
329 for a in who {
330 let lower = a.to_ascii_lowercase();
331 let sig = |inv: &Vec<HashSet<String>>| -> Vec<bool> {
332 inv.iter().map(|s| s.contains(&lower)).collect()
333 };
334 let sb = old_inv.as_ref().map(sig);
335 let sa = new_inv.as_ref().map(sig);
336 if let (Some(o), Some(s)) = (&old, &sb) {
337 old_views.entry(s.clone()).or_insert_with(|| o.view(s));
338 }
339 if let (Some(n), Some(s)) = (&new, &sa) {
340 new_views.entry(s.clone()).or_insert_with(|| n.view(s));
341 }
342 let before = old
343 .as_ref()
344 .zip(sb.as_ref())
345 .and_then(|(o, s)| Some((o, s, old_views[s].as_ref()?)));
346 let after = new
347 .as_ref()
348 .zip(sa.as_ref())
349 .and_then(|(n, s)| Some((n, s, new_views[s].as_ref()?)));
350 let request = |n: &Obj, sa: &Vec<bool>, comps: &Vec<Node>, requests: &mut HashMap<_, _>| {
351 Arc::clone(
352 requests
353 .entry(sa.clone())
354 .or_insert_with(|| Arc::new(n.envelope(comps.clone(), Method::Request, now))),
355 )
356 };
357 let (method, quiet, cal) = match (before, after) {
358 (Some((src, sb, b)), None) => {
359 // A component that lists them with SCHEDULE-AGENT=CLIENT
360 // now: the client tells them.
361 let lost: Vec<bool> = match client.contains(&lower) {
362 false => vec![true; b.len()],
363 true => b
364 .iter()
365 .map(|c| {
366 !new.as_ref()
367 .and_then(|n| n.find(src.key(&c.c)))
368 .is_some_and(|nc| client_scheduled(&nc.c, a))
369 })
370 .collect(),
371 };
372 if !lost.contains(&true) {
373 continue;
374 }
375 let key = (sb.clone(), lost);
376 let cal = cancels.entry(key).or_insert_with_key(|(_, lost)| {
377 let comps = b
378 .iter()
379 .zip(lost)
380 .filter(|(_, l)| **l)
381 .map(|(c, _)| c.clone());
382 Arc::new(src.envelope(cancelled(comps.collect()), Method::Cancel, now))
383 });
384 (Method::Cancel, false, Arc::clone(cal))
385 }
386 (None, Some((n, sa, comps))) => {
387 (Method::Request, false, request(n, sa, comps, &mut requests))
388 }
389 (Some((_, sb, b)), Some((n, sa, comps))) => {
390 let change = *changes
391 .entry((sb.clone(), sa.clone()))
392 .or_insert_with(|| change(b, comps));
393 let forced = force.iter().any(|f| f.eq_ignore_ascii_case(a));
394 let quiet = match change {
395 Change::Content => false,
396 Change::Answers => true,
397 _ if forced => false,
398 // Copies carry the new SEQUENCE, or their replies count
399 // as stale.
400 Change::Sequence => true,
401 Change::None => continue,
402 };
403 (Method::Request, quiet, request(n, sa, comps, &mut requests))
404 }
405 (None, None) => continue,
406 };
407 out.push(Message {
408 to: a.to_string(),
409 method,
410 quiet,
411 cal,
412 });
413 }
414 out
415}
416
417/// Records the delivery status for `to` on its ATTENDEE properties.
418pub fn set_attendee_status(cal: &mut ICalendar, to: &str, status: &str) {
419 for c in &mut cal.components {
420 for e in c
421 .entries
422 .iter_mut()
423 .filter(|e| e.name == ICalendarProperty::Attendee)
424 {
425 if address(e).is_some_and(|a| a.eq_ignore_ascii_case(to)) {
426 set_param(e, ICalendarParameterName::ScheduleStatus, text(status));
427 }
428 }
429 }
430}
431
432/// Records the delivery status of a REPLY on the ORGANIZER properties.
433pub fn set_organizer_status(cal: &mut ICalendar, status: &str) {
434 for c in &mut cal.components {
435 for e in c
436 .entries
437 .iter_mut()
438 .filter(|e| e.name == ICalendarProperty::Organizer)
439 {
440 set_param(e, ICalendarParameterName::ScheduleStatus, text(status));
441 }
442 }
443}
444
445/// An attendee's PUT over the stored copy. Returns what to store and the
446/// REPLY, if the attendee's participation changed.
447pub fn attend(
448 old: &ICalendar,
449 new: ICalendar,
450 me: Is,
451 now: DateTime<Utc>,
452) -> Result<(ICalendar, Option<Message>), Refused> {
453 let old_cal = old;
454 let old = Obj::new(old_cal);
455 if !old.organizer_schedules() {
456 return Ok((new, None));
457 }
458 let mut next = Obj::new(&new);
459 let master = old.master();
460 let added: Vec<i64> = next
461 .comps()
462 .filter_map(|c| next.key(&c.c))
463 .filter(|k| old.find(Some(*k)).is_none())
464 .collect();
465 let excluded = next.master().map_or(0, |m| {
466 let before = master.map_or_else(HashSet::new, |om| {
467 old.times(&om.c, &ICalendarProperty::Exdate)
468 });
469 next.times(&m.c, &ICalendarProperty::Exdate)
470 .difference(&before)
471 .count()
472 });
473 // Each becomes a part of the REPLY and an override in the organizer object.
474 if added.len() + excluded > MAX_ATTENDEE_CHANGES {
475 return Err(Refused::AttendeeChange);
476 }
477 // A key a cut-short expansion cannot confirm is refused, as the
478 // organizer's side would drop its reply.
479 let (instances, _) = old.instances(old_cal, &added);
480
481 for c in next.comps() {
482 let key = next.key(&c.c);
483 match old.find(key) {
484 Some(oc) => {
485 // The end, not its property: clients rewrite DURATION as DTEND.
486 let same_times = [
487 ICalendarProperty::Dtstart,
488 ICalendarProperty::Rdate,
489 ICalendarProperty::Exrule,
490 ]
491 .iter()
492 .all(|p| old.times(&oc.c, p) == next.times(&c.c, p))
493 && old.end(&oc.c) == next.end(&c.c)
494 && same_rules(&old, &oc.c, &next, &c.c);
495 let kept_exdates = old
496 .times(&oc.c, &ICalendarProperty::Exdate)
497 .is_subset(&next.times(&c.c, &ICalendarProperty::Exdate));
498 if !same_times
499 || !kept_exdates
500 || organizer_of(&oc.c) != organizer_of(&c.c)
501 || addresses(&oc.c) != addresses(&c.c)
502 {
503 return Err(Refused::AttendeeChange);
504 }
505 }
506 // An instance the attendee overrides, to set its own status.
507 None => {
508 if !key.is_some_and(|k| {
509 master.is_some() && instances.contains(&k) && old.plain(&next, &c.c, k, true)
510 }) {
511 return Err(Refused::AttendeeChange);
512 }
513 }
514 }
515 }
516 // Dropping an override is fine where it held no change of the
517 // organizer's, where the attendee deletes that instance with an EXDATE,
518 // and in a copy of single instances.
519 let exdated: HashSet<i64> = next
520 .master()
521 .into_iter()
522 .flat_map(|m| m.c.properties(&ICalendarProperty::Exdate))
523 .flat_map(|e| {
524 e.values
525 .iter()
526 .filter_map(|v| next.at(v.as_partial_date_time()?, e.tz_id()))
527 })
528 .collect();
529 let mut reverted: Vec<(i64, Option<String>)> = Vec::new();
530 for oc in old.comps() {
531 let key = old.key(&oc.c);
532 if next.find(key).is_some() {
533 continue;
534 }
535 let plain = |k: i64| old.plain(&old, &oc.c, k, false);
536 let allowed =
537 |k: i64| master.is_none() || !is_range(&oc.c) && (exdated.contains(&k) || plain(k));
538 if !key.is_some_and(allowed) {
539 return Err(Refused::AttendeeChange);
540 }
541 if let Some(k) = key.filter(|k| master.is_some() && !exdated.contains(k)) {
542 reverted.push((k, own_partstat(&oc.c, me)));
543 }
544 }
545
546 // The state of the others and of the organizer is the server's. A new
547 // override takes it from the THISANDFUTURE override that moves it.
548 let base = |key: Option<i64>| {
549 old.find(key)
550 .or_else(|| old.future(key?).map(|(n, _)| n))
551 .or(master)
552 };
553 let mut force = false;
554 let keys: Vec<Option<i64>> = next.comps().map(|c| next.key(&c.c)).collect();
555 for (c, key) in next.comps_mut().zip(&keys) {
556 let Some(base) = base(*key) else {
557 continue;
558 };
559 for e in &mut c.c.entries {
560 match e.name {
561 ICalendarProperty::Attendee if !address(e).is_some_and(me) => {
562 if let Some(b) = same_attendee(&base.c, e) {
563 e.params = b.params.clone();
564 }
565 }
566 ICalendarProperty::Organizer => {
567 force |= param(e, &ICalendarParameterName::ScheduleForceSend)
568 .is_some_and(|v| v.eq_ignore_ascii_case("REPLY"));
569 if let Some(b) = base.c.property(&ICalendarProperty::Organizer) {
570 e.params = b.params.clone();
571 }
572 }
573 _ => {}
574 }
575 }
576 }
577
578 let mut replied: Vec<Node> = Vec::new();
579 for (c, key) in next.comps().zip(&keys) {
580 let now_stat = own_partstat(&c.c, me);
581 let before = base(*key).and_then(|b| own_partstat(&b.c, me));
582 if now_stat.is_some() && (force || now_stat != before) {
583 replied.push(reply_part(c, me));
584 }
585 }
586 // A dropped override falls back to the series' answer for that instance.
587 for (k, held) in reverted {
588 let Some(inst) = DateTime::from_timestamp(k, 0).and_then(|t| next.single(t, &Zone::Utc))
589 else {
590 continue;
591 };
592 if own_partstat(&inst.c, me).is_some_and(|now| Some(now) != held) {
593 replied.push(reply_part(&inst, me));
594 }
595 }
596 if let Some(m) = next.master() {
597 let before = master.map_or_else(HashSet::new, |om| {
598 old.times(&om.c, &ICalendarProperty::Exdate)
599 });
600 for e in m.c.properties(&ICalendarProperty::Exdate) {
601 for value in &e.values {
602 if before.contains(&next.value_key(e, value)) {
603 continue;
604 }
605 let rid = ICalendarEntry {
606 name: ICalendarProperty::RecurrenceId,
607 params: e.params.clone(),
608 values: vec![value.clone()],
609 };
610 replied.push(declined_instance(m, rid, me));
611 }
612 }
613 }
614
615 let reply = match replied.is_empty() {
616 true => None,
617 false => next.organizer().map(|to| Message {
618 to,
619 method: Method::Reply,
620 quiet: false,
621 cal: Arc::new(next.envelope(replied, Method::Reply, now)),
622 }),
623 };
624 Ok((next.done(), reply))
625}
626
627/// An attendee's DELETE: the REPLY declining every instance, unless the
628/// organizer handles scheduling itself or already cancelled.
629pub fn decline(old: &ICalendar, me: Is, now: DateTime<Utc>) -> Option<Message> {
630 let old = Obj::new(old);
631 if !old.organizer_schedules() {
632 return None;
633 }
634 let cancelled = old.comps().all(|c| {
635 c.c.property(&ICalendarProperty::Status)
636 .and_then(|e| e.values.first()?.as_text())
637 .is_some_and(|s| s.eq_ignore_ascii_case("CANCELLED"))
638 });
639 if cancelled {
640 return None;
641 }
642 let comps: Vec<Node> = old
643 .comps()
644 .filter(|c| own_partstat(&c.c, me).is_some())
645 .map(|c| {
646 let mut part = reply_part(c, me);
647 set_own_partstat(&mut part.c, me, ICalendarParticipationStatus::Declined);
648 part
649 })
650 .collect();
651 if comps.is_empty() {
652 return None;
653 }
654 Some(Message {
655 to: old.organizer()?,
656 method: Method::Reply,
657 quiet: false,
658 cal: Arc::new(old.envelope(comps, Method::Reply, now)),
659 })
660}
661
662/// A REQUEST or CANCEL applied to the attendee's copy. `None`: nothing to
663/// store.
664pub fn receive(copy: Option<&ICalendar>, msg: &Message) -> Option<ICalendar> {
665 match msg.method {
666 Method::Request => {
667 if msg.quiet && copy.is_none() {
668 return None;
669 }
670 let copy = copy.map(Obj::new);
671 let mut next = Obj::new(&msg.cal);
672 next.root
673 .c
674 .entries
675 .retain(|e| e.name != ICalendarProperty::Method);
676 let keys: Vec<Option<i64>> = next.comps().map(|c| next.key(&c.c)).collect();
677 let Some(copy) = copy else {
678 return Some(next.done());
679 };
680 // What the attendee may keep for itself (RFC 6638, 3.2.2.1).
681 for (c, key) in next.comps_mut().zip(&keys) {
682 let Some(base) = copy.find(*key).or(copy.master()) else {
683 continue;
684 };
685 c.children
686 .retain(|n| n.c.component_type != ICalendarComponentType::VAlarm);
687 c.children.extend(
688 base.children
689 .iter()
690 .filter(|n| n.c.component_type == ICalendarComponentType::VAlarm)
691 .cloned(),
692 );
693 for p in [
694 ICalendarProperty::Transp,
695 ICalendarProperty::PercentComplete,
696 ICalendarProperty::Completed,
697 ] {
698 c.c.entries.retain(|e| e.name != p);
699 c.c.entries.extend(base.c.properties(&p).cloned());
700 }
701 let status = base
702 .c
703 .property(&ICalendarProperty::Organizer)
704 .and_then(|e| e.parameter(&ICalendarParameterName::ScheduleStatus))
705 .cloned();
706 if let Some(s) = status {
707 for e in
708 c.c.entries
709 .iter_mut()
710 .filter(|e| e.name == ICalendarProperty::Organizer)
711 {
712 set_param(e, ICalendarParameterName::ScheduleStatus, s.clone());
713 }
714 }
715 }
716 Some(next.done())
717 }
718 Method::Cancel => {
719 let msg_obj = Obj::new(&msg.cal);
720 let mut next = Obj::new(copy?);
721 let whole = msg_obj.master().is_some();
722 let gone: Vec<(i64, ICalendarEntry)> = msg_obj
723 .comps()
724 .filter_map(|c| {
725 let rid = c.c.property(&ICalendarProperty::RecurrenceId)?;
726 Some((msg_obj.instant(rid)?, rid.clone()))
727 })
728 .collect();
729 let range =
730 |rid: &ICalendarEntry| rid.parameter(&ICalendarParameterName::Range).cloned();
731 let hit = |k: i64| {
732 gone.iter()
733 .any(|(g, rid)| *g == k || (*g < k && range(rid).is_some()))
734 };
735 // A cancelled range stands for later instances too, which an
736 // EXDATE cannot cover: the override carries the range.
737 let mut missing = Vec::new();
738 for (k, rid) in gone.iter().filter(|_| !whole) {
739 let Some(r) = range(rid) else { continue };
740 let at = next.position(Some(*k)).or_else(|| {
741 let n = next.single(DateTime::from_timestamp(*k, 0)?, &Zone::Utc)?;
742 Some(next.push(n))
743 });
744 let entry = at.and_then(|at| {
745 next.children_mut()[at]
746 .c
747 .entries
748 .iter_mut()
749 .find(|e| e.name == ICalendarProperty::RecurrenceId)
750 });
751 match entry {
752 Some(e) => set_param(e, ICalendarParameterName::Range, r),
753 None => missing.push(rid.clone()),
754 }
755 }
756 let keys: Vec<Option<i64>> = next.comps().map(|c| next.key(&c.c)).collect();
757 for (c, key) in next.comps_mut().zip(&keys) {
758 if whole || key.is_some_and(hit) {
759 set_prop(
760 &mut c.c,
761 ICalendarProperty::Status,
762 ICalendarValue::Status(ICalendarStatus::Cancelled),
763 );
764 }
765 }
766 if !whole {
767 missing.extend(
768 gone.into_iter()
769 .filter(|(k, rid)| range(rid).is_none() && !keys.contains(&Some(*k)))
770 .map(|(_, rid)| rid),
771 );
772 let missing: Vec<ICalendarEntry> = missing
773 .into_iter()
774 .map(|rid| ICalendarEntry {
775 name: ICalendarProperty::Exdate,
776 params: without(rid.params, &ICalendarParameterName::Range),
777 values: rid.values,
778 })
779 .collect();
780 if let Some(m) = next
781 .comps_mut()
782 .find(|c| !c.c.has_property(&ICalendarProperty::RecurrenceId))
783 {
784 m.c.entries.extend(missing);
785 }
786 }
787 Some(next.done())
788 }
789 Method::Reply => None,
790 }
791}
792
793/// The RECURRENCE-IDs an expansion gives.
794fn rids(e: &Expansion) -> HashSet<i64> {
795 e.instances
796 .iter()
797 .filter_map(|i| Some(i.recurrence_id?.timestamp()))
798 .collect()
799}
800
801/// What a REPLY did to the organizer object.
802#[derive(Debug, Default)]
803pub struct Applied {
804 pub changed: bool,
805 /// Parts left out: the object would pass `MAX_COMPONENTS`, or a
806 /// cut-short expansion cannot confirm the instance.
807 pub dropped: usize,
808}
809
810/// A REPLY applied to the organizer object.
811pub fn apply_reply(org: &mut ICalendar, reply: &ICalendar, replier: Is) -> Applied {
812 let rep = Obj::new(reply);
813 let mut next = Obj::new(org);
814 let mut changed = false;
815 let mut dropped = 0;
816 let mut parts: Vec<&Node> = rep.comps().collect();
817 // In order: a range narrowed for one instance moves on to the next.
818 parts.sort_by_key(|c| rep.key(&c.c));
819 let missing: Vec<i64> = parts
820 .iter()
821 .filter_map(|c| rep.key(&c.c))
822 .filter(|k| next.position(Some(*k)).is_none())
823 .collect();
824 let (found, unknown) = next.instances(org, &missing);
825 // A reply may not grow the object past what a PUT of it may hold.
826 let mut size = org.components.len();
827 for rc in parts {
828 let key = rep.key(&rc.c);
829 let at = match next.position(key) {
830 Some(at) => {
831 if let Some(t) = key
832 && !is_range(&rc.c)
833 && let Some(rid) = DateTime::from_timestamp(t, 0)
834 {
835 // Narrowing copies the range to the next instance.
836 if is_range(&next.root.children[at].c)
837 && size + nodes(&next.root.children[at]) > MAX_COMPONENTS
838 {
839 dropped += 1;
840 continue;
841 }
842 let had = next.root.children.len();
843 next.narrow(org, rid, t, &Zone::Utc);
844 size += next.root.children[had..].iter().map(nodes).sum::<usize>();
845 }
846 at
847 }
848 // A reply for one instance of the series gets its own override.
849 // Obj reads floating times in UTC; expand must match.
850 None => {
851 let Some((t, at)) = key.and_then(|t| Some((t, DateTime::from_timestamp(t, 0)?)))
852 else {
853 continue;
854 };
855 if unknown.contains(&t) {
856 dropped += 1;
857 continue;
858 }
859 if !found.contains(&t) {
860 continue;
861 }
862 let Some(inst) = next.single(at, &Zone::Utc) else {
863 continue;
864 };
865 if size + nodes(&inst) > MAX_COMPONENTS {
866 dropped += 1;
867 continue;
868 }
869 size += nodes(&inst);
870 next.push(inst)
871 }
872 };
873 // Attendee parameters leave the index as it is.
874 let target = &mut next.root.children[at];
875 if sequence(&rc.c) < sequence(&target.c) {
876 continue;
877 }
878 let Some(stat) =
879 rc.c.properties(&ICalendarProperty::Attendee)
880 .find(|e| address(e).is_some_and(replier))
881 .map(|e| {
882 e.parameter(&ICalendarParameterName::Partstat)
883 .cloned()
884 .unwrap_or(partstat(ICalendarParticipationStatus::NeedsAction))
885 })
886 else {
887 continue;
888 };
889 // The attendee writes these; only digits and dots are a status code.
890 let codes: Vec<String> =
891 rc.c.properties(&ICalendarProperty::RequestStatus)
892 .filter_map(|e| e.values.first()?.as_text())
893 .map(|s| {
894 let code = s.split(';').next().unwrap_or(s);
895 code.chars()
896 .filter(|c| c.is_ascii_digit() || *c == '.')
897 .collect::<String>()
898 })
899 .filter(|c| !c.is_empty())
900 .collect();
901 let status = match codes.is_empty() {
902 true => "2.0".to_string(),
903 false => codes.join(","),
904 };
905 for e in
906 target.c.entries.iter_mut().filter(|e| {
907 e.name == ICalendarProperty::Attendee && address(e).is_some_and(replier)
908 })
909 {
910 changed |= e.parameter(&ICalendarParameterName::Partstat) != Some(&stat);
911 set_param(e, ICalendarParameterName::Partstat, stat.clone());
912 set_param(e, ICalendarParameterName::ScheduleStatus, text(&status));
913 }
914 }
915 if changed {
916 *org = next.done();
917 }
918 Applied { changed, dropped }
919}
920
921// ---------------------------------------------------------------------------
922// The organizer object
923// ---------------------------------------------------------------------------
924
925/// An organizer's PUT: the client's object with the attendee state the
926/// server owns, their PARTSTAT (reset on a reschedule, RFC 6638 3.2.8) and
927/// SCHEDULE-STATUS, and the attendees SCHEDULE-FORCE-SEND=REQUEST names.
928/// `old` is the stored organizer object, if any. [`messages`] tells what the
929/// PUT sends.
930pub fn prepare(
931 old: Option<&ICalendar>,
932 new: &ICalendar,
933 organizer: Is,
934) -> (ICalendar, Vec<String>) {
935 let mut force = Vec::new();
936 let old = old.map(Obj::new);
937 let mut next = Obj::new(new);
938 // A new override takes the state of the THISANDFUTURE override that moves
939 // its instance, else of the master.
940 let base_of = |key: Option<i64>| {
941 let o = old.as_ref()?;
942 o.find(key)
943 .or_else(|| o.future(key?).map(|(n, _)| n))
944 .or(o.master())
945 };
946 let keys: Vec<Option<i64>> = next.comps().map(|c| next.key(&c.c)).collect();
947 let moved: Vec<bool> = next
948 .comps()
949 .map(|c| match &old {
950 Some(o) => match o.find(next.key(&c.c)) {
951 Some(oc) => rescheduled(o, &oc.c, &next, &c.c),
952 // A new override: rescheduled if it moves its instance.
953 None => {
954 let start =
955 c.c.property(&ICalendarProperty::Dtstart)
956 .and_then(|e| next.instant(e));
957 let key = next.key(&c.c);
958 key.is_none()
959 || start != key.map(|k| o.moved(k))
960 || base_of(key).is_some_and(|b| {
961 next.end(&c.c) != start.and_then(|s| o.end_from(&b.c, s))
962 })
963 }
964 },
965 None => true,
966 })
967 .collect();
968 let bumps: Vec<Option<i64>> = keys
969 .iter()
970 .zip(next.comps())
971 .zip(&moved)
972 .map(|((key, c), moved)| {
973 let oc = base_of(*key)?;
974 (*moved && sequence(&c.c) <= sequence(&oc.c)).then(|| sequence(&oc.c).saturating_add(1))
975 })
976 .collect();
977
978 for ((c, key), (moved, bump)) in next.comps_mut().zip(&keys).zip(moved.iter().zip(&bumps)) {
979 let base = base_of(*key);
980 for e in &mut c.c.entries {
981 match e.name {
982 ICalendarProperty::Organizer => {
983 remove_param(e, &ICalendarParameterName::ScheduleForceSend);
984 }
985 ICalendarProperty::Attendee => {
986 let forced = param(e, &ICalendarParameterName::ScheduleForceSend)
987 .is_some_and(|v| v.eq_ignore_ascii_case("REQUEST"));
988 remove_param(e, &ICalendarParameterName::ScheduleForceSend);
989 let Some(a) = address(e).map(str::to_string) else {
990 continue;
991 };
992 if organizer(&a) || !server_agent(e) {
993 continue;
994 }
995 if forced {
996 force.push(a.clone());
997 }
998 let prev = base.and_then(|b| same_attendee(&b.c, e));
999 let stat = match (moved, prev) {
1000 (false, Some(p)) => p
1001 .parameter(&ICalendarParameterName::Partstat)
1002 .cloned()
1003 .unwrap_or(partstat(ICalendarParticipationStatus::NeedsAction)),
1004 _ => partstat(ICalendarParticipationStatus::NeedsAction),
1005 };
1006 set_param(e, ICalendarParameterName::Partstat, stat);
1007 match prev.and_then(|p| p.parameter(&ICalendarParameterName::ScheduleStatus)) {
1008 Some(s) => set_param(e, ICalendarParameterName::ScheduleStatus, s.clone()),
1009 None => remove_param(e, &ICalendarParameterName::ScheduleStatus),
1010 }
1011 }
1012 _ => {}
1013 }
1014 }
1015 if let Some(n) = bump {
1016 set_prop(
1017 &mut c.c,
1018 ICalendarProperty::Sequence,
1019 ICalendarValue::Integer(*n),
1020 );
1021 }
1022 }
1023 (next.done(), force)
1024}
1025
1026/// Whether a change moves instances in time (RFC 6638, 3.2.8). Shortening a
1027/// series or excluding instances does not.
1028fn rescheduled(old: &Obj, oc: &ICalendarComponent, new: &Obj, nc: &ICalendarComponent) -> bool {
1029 let moved = [ICalendarProperty::Dtstart, ICalendarProperty::Rdate]
1030 .iter()
1031 .any(|p| old.times(oc, p) != new.times(nc, p))
1032 || old.end(oc) != new.end(nc);
1033 let reinstated = !old
1034 .times(oc, &ICalendarProperty::Exdate)
1035 .is_subset(&new.times(nc, &ICalendarProperty::Exdate));
1036 // A changed or removed EXRULE can bring instances back; a new one cannot.
1037 let exrules = (
1038 rules(oc, &ICalendarProperty::Exrule),
1039 rules(nc, &ICalendarProperty::Exrule),
1040 );
1041 let exrule_changed = !exrules.0.is_empty() && exrules.0 != exrules.1;
1042 moved || reinstated || exrule_changed || rules_grew(old, oc, new, nc)
1043}
1044
1045/// A rule in one form: BY lists sorted, INTERVAL=1 as absent, and WKST
1046/// absent where it cannot change the instances.
1047fn canonical(r: &ICalendarRecurrenceRule) -> ICalendarRecurrenceRule {
1048 fn tidy<T: Ord>(v: &mut Vec<T>) {
1049 v.sort();
1050 v.dedup();
1051 }
1052 let mut r = r.clone();
1053 tidy(&mut r.bysecond);
1054 tidy(&mut r.byminute);
1055 tidy(&mut r.byhour);
1056 tidy(&mut r.byday);
1057 tidy(&mut r.bymonthday);
1058 tidy(&mut r.byyearday);
1059 tidy(&mut r.byweekno);
1060 tidy(&mut r.bymonth);
1061 tidy(&mut r.bysetpos);
1062 r.interval = r.interval.filter(|i| *i > 1);
1063 // WKST only bounds weeks of a WEEKLY rule that skips weeks or picks by
1064 // BYSETPOS, and the weeks of BYWEEKNO (RFC 5545, 3.3.10).
1065 let weeks =
1066 r.freq == ICalendarFrequency::Weekly && (r.interval.is_some() || !r.bysetpos.is_empty());
1067 if !weeks && r.byweekno.is_empty() {
1068 r.wkst = None;
1069 }
1070 r.wkst = r.wkst.filter(|w| *w != ICalendarWeekday::Monday);
1071 r
1072}
1073
1074fn rules(c: &ICalendarComponent, prop: &ICalendarProperty) -> Vec<ICalendarRecurrenceRule> {
1075 let mut v: Vec<_> = c
1076 .properties(prop)
1077 .filter_map(|e| Some(canonical(rule(e.values.first()?)?)))
1078 .collect();
1079 v.sort_by_key(|r| format!("{r:?}"));
1080 v
1081}
1082
1083fn rules_grew(old: &Obj, oc: &ICalendarComponent, new: &Obj, nc: &ICalendarComponent) -> bool {
1084 let rules = |c| rules(c, &ICalendarProperty::Rrule);
1085 let (o, n) = (rules(oc), rules(nc));
1086 if o == n {
1087 return false;
1088 }
1089 let (o, n) = match (&o[..], &n[..]) {
1090 // Dropping the rule leaves the first instance only.
1091 ([_, ..], []) => return false,
1092 ([o], [n]) => (o, n),
1093 _ => return true,
1094 };
1095 if !same_starts(old, oc, o, new, nc, n) {
1096 return true;
1097 }
1098 match (bounded(o), bounded(n)) {
1099 (true, false) => true,
1100 (false, _) => false,
1101 (true, true) => new
1102 .compare_ends(nc, o, n)
1103 .is_none_or(|c| c == Ordering::Greater),
1104 }
1105}
1106
1107/// Whether an attendee's copy keeps the organizer's rule. Clients rewrite
1108/// UNTIL, swap COUNT for UNTIL, or name the start's weekday, month or day,
1109/// so forms that may give the same instances compare by them.
1110fn same_rules(old: &Obj, oc: &ICalendarComponent, new: &Obj, nc: &ICalendarComponent) -> bool {
1111 let (o, n) = (
1112 rules(oc, &ICalendarProperty::Rrule),
1113 rules(nc, &ICalendarProperty::Rrule),
1114 );
1115 if o == n {
1116 return true;
1117 }
1118 let ([o], [n]) = (&o[..], &n[..]) else {
1119 return false;
1120 };
1121 bounded(o) == bounded(n)
1122 && same_starts(old, oc, o, new, nc, n)
1123 && (!bounded(o) || new.compare_ends(nc, o, n) == Some(Ordering::Equal))
1124}
1125
1126/// Whether rules `o` of `oc` and `n` of `nc`, without their ends, give the
1127/// same starts.
1128fn same_starts(
1129 old: &Obj,
1130 oc: &ICalendarComponent,
1131 o: &ICalendarRecurrenceRule,
1132 new: &Obj,
1133 nc: &ICalendarComponent,
1134 n: &ICalendarRecurrenceRule,
1135) -> bool {
1136 if unbounded(o) == unbounded(n) {
1137 return true;
1138 }
1139 let loose = |r: &ICalendarRecurrenceRule| {
1140 let mut r = unbounded(r);
1141 match r.freq {
1142 ICalendarFrequency::Weekly => r.byday.clear(),
1143 ICalendarFrequency::Monthly => r.bymonthday.clear(),
1144 ICalendarFrequency::Yearly => {
1145 r.bymonth.clear();
1146 r.bymonthday.clear();
1147 }
1148 _ => {}
1149 }
1150 r
1151 };
1152 if loose(o) != loose(n) {
1153 return false;
1154 }
1155 // Weekdays and month days show within a year of periods. Leap days
1156 // show within two leap cycles, also where BYMONTH leaves one month a year.
1157 let yearly =
1158 n.freq == ICalendarFrequency::Yearly || !o.bymonth.is_empty() || !n.bymonth.is_empty();
1159 let days = match yearly {
1160 true => 8 * 366,
1161 false => 400,
1162 } * i64::from(n.interval.unwrap_or(1).max(1));
1163 let starts = |obj: &Obj, c, r: &ICalendarRecurrenceRule| {
1164 let c = with_rule(c, unbounded(r));
1165 let start = obj.start(&c)?;
1166 obj.rule_starts(c, start..add(start, TimeDelta::days(days)))
1167 };
1168 let a = starts(old, oc, o);
1169 a.is_some() && a == starts(new, nc, n)
1170}
1171
1172/// `c` with `r` as its only RRULE.
1173fn with_rule(c: &ICalendarComponent, r: ICalendarRecurrenceRule) -> ICalendarComponent {
1174 let mut c = c.clone();
1175 let mut first = true;
1176 c.entries.retain_mut(|e| {
1177 if e.name != ICalendarProperty::Rrule {
1178 return true;
1179 }
1180 e.values = vec![ICalendarValue::RecurrenceRule(Box::new(r.clone()))];
1181 std::mem::take(&mut first)
1182 });
1183 c
1184}
1185
1186fn unbounded(r: &ICalendarRecurrenceRule) -> ICalendarRecurrenceRule {
1187 let mut r = r.clone();
1188 r.until = None;
1189 r.count = None;
1190 r
1191}
1192
1193fn bounded(r: &ICalendarRecurrenceRule) -> bool {
1194 r.count.is_some() || r.until.is_some()
1195}
1196
1197fn cancelled(comps: Vec<Node>) -> Vec<Node> {
1198 comps
1199 .into_iter()
1200 .map(|mut n| {
1201 set_prop(
1202 &mut n.c,
1203 ICalendarProperty::Status,
1204 ICalendarValue::Status(ICalendarStatus::Cancelled),
1205 );
1206 let seq = sequence(&n.c).saturating_add(1);
1207 set_prop(
1208 &mut n.c,
1209 ICalendarProperty::Sequence,
1210 ICalendarValue::Integer(seq),
1211 );
1212 n
1213 })
1214 .collect()
1215}
1216
1217/// How an attendee's view changed.
1218#[derive(Clone, Copy)]
1219enum Change {
1220 /// More than the participation of others.
1221 Content,
1222 /// Only the participation of others.
1223 Answers,
1224 /// Only the SEQUENCE.
1225 Sequence,
1226 None,
1227}
1228
1229fn change(before: &[Node], after: &[Node]) -> Change {
1230 let sequences = |comps: &[Node]| {
1231 let mut v: Vec<i64> = comps.iter().map(|n| sequence(&n.c)).collect();
1232 v.sort_unstable();
1233 v
1234 };
1235 if normalized(before, true) != normalized(after, true) {
1236 Change::Content
1237 } else if normalized(before, false) != normalized(after, false) {
1238 Change::Answers
1239 } else if sequences(before) != sequences(after) {
1240 Change::Sequence
1241 } else {
1242 Change::None
1243 }
1244}
1245
1246/// For comparing what an attendee would receive: without the stamps a
1247/// client rewrites on every save, and optionally without participation.
1248fn normalized(comps: &[Node], without_partstat: bool) -> Vec<Node> {
1249 let mut out = comps
1250 .iter()
1251 .map(|n| {
1252 let mut n = n.clone();
1253 n.c.entries.retain(|e| {
1254 !matches!(
1255 e.name,
1256 ICalendarProperty::Dtstamp
1257 | ICalendarProperty::LastModified
1258 | ICalendarProperty::Created
1259 | ICalendarProperty::Sequence
1260 ) && !matches!(&e.name, ICalendarProperty::Other(x) if x.to_ascii_uppercase().starts_with("X-"))
1261 });
1262 // A new SENT-BY alone is no change for an attendee.
1263 for e in n.c.entries.iter_mut().filter(|e| {
1264 matches!(
1265 e.name,
1266 ICalendarProperty::Organizer | ICalendarProperty::Attendee
1267 )
1268 }) {
1269 remove_param(e, &ICalendarParameterName::SentBy);
1270 }
1271 if without_partstat {
1272 for e in n.c.entries.iter_mut().filter(|e| e.name == ICalendarProperty::Attendee) {
1273 remove_param(e, &ICalendarParameterName::Partstat);
1274 }
1275 }
1276 sorted(&mut n);
1277 n
1278 })
1279 .collect::<Vec<_>>();
1280 out.sort_by_cached_key(|n| format!("{n:?}"));
1281 out
1282}
1283
1284/// In one order, so another client's order of properties is no change.
1285fn sorted(n: &mut Node) {
1286 for e in &mut n.c.entries {
1287 e.params.sort_by_cached_key(|p| format!("{p:?}"));
1288 }
1289 n.c.entries.sort_by_cached_key(|e| format!("{e:?}"));
1290 for ch in &mut n.children {
1291 sorted(ch);
1292 }
1293 n.children.sort_by_cached_key(|ch| format!("{ch:?}"));
1294}
1295
1296/// A component of the attendee's REPLY: only its own ATTENDEE, no alarms.
1297fn reply_part(c: &Node, me: Is) -> Node {
1298 let mut n = Node {
1299 c: c.c.clone(),
1300 children: Vec::new(),
1301 };
1302 n.c.entries
1303 .retain(|e| e.name != ICalendarProperty::Attendee || address(e).is_some_and(me));
1304 strip_scheduling_params(&mut n.c);
1305 n
1306}
1307
1308/// The REPLY component for an instance the attendee excluded with EXDATE.
1309fn declined_instance(master: &Node, rid: ICalendarEntry, me: Is) -> Node {
1310 let mut c = ICalendarComponent {
1311 component_type: master.c.component_type.clone(),
1312 entries: Vec::new(),
1313 component_ids: Vec::new(),
1314 };
1315 for p in [
1316 ICalendarProperty::Uid,
1317 ICalendarProperty::Sequence,
1318 ICalendarProperty::Organizer,
1319 ICalendarProperty::Summary,
1320 ] {
1321 c.entries.extend(master.c.properties(&p).cloned());
1322 }
1323 c.entries.push(ICalendarEntry {
1324 name: ICalendarProperty::Dtstart,
1325 ..rid.clone()
1326 });
1327 c.entries.push(rid);
1328 for e in master
1329 .c
1330 .properties(&ICalendarProperty::Attendee)
1331 .filter(|e| address(e).is_some_and(me))
1332 {
1333 let mut e = e.clone();
1334 set_param(
1335 &mut e,
1336 ICalendarParameterName::Partstat,
1337 partstat(ICalendarParticipationStatus::Declined),
1338 );
1339 c.entries.push(e);
1340 }
1341 strip_scheduling_params(&mut c);
1342 Node {
1343 c,
1344 children: Vec::new(),
1345 }
1346}
1347
1348// ---------------------------------------------------------------------------
1349// The object as a tree
1350// ---------------------------------------------------------------------------
1351
1352/// A component with its sub-components, detached from the flat list calcard
1353/// keeps.
1354#[derive(Debug, Clone, PartialEq)]
1355struct Node {
1356 /// `component_ids` is empty; `children` replaces it.
1357 c: ICalendarComponent,
1358 children: Vec<Node>,
1359}
1360
1361fn node(cal: &ICalendar, i: usize) -> Node {
1362 let mut c = cal.components[i].clone();
1363 let ids = std::mem::take(&mut c.component_ids);
1364 let children = ids
1365 .iter()
1366 .map(|&id| id as usize)
1367 .filter(|&id| id > i && id < cal.components.len())
1368 .map(|id| node(cal, id))
1369 .collect();
1370 Node { c, children }
1371}
1372
1373/// How many components `n` flattens to.
1374fn nodes(n: &Node) -> usize {
1375 1 + n.children.iter().map(nodes).sum::<usize>()
1376}
1377
1378fn flatten(n: &Node, out: &mut Vec<ICalendarComponent>) -> u32 {
1379 let at = out.len();
1380 out.push(n.c.clone());
1381 for ch in &n.children {
1382 let id = flatten(ch, out);
1383 out[at].component_ids.push(id);
1384 }
1385 at as u32
1386}
1387
1388/// A calendar object: the VCALENDAR with its time zones and components.
1389struct Obj {
1390 /// Change its children only through `children_mut` or `comps_mut`, which
1391 /// drop `index`, or `push`, which keeps it.
1392 root: Node,
1393 zones: Zones,
1394 index: OnceCell<Index>,
1395 /// Occurrences left for comparing rules. Spent, a rule change counts as
1396 /// growth for the organizer and is refused from an attendee.
1397 budget: Cell<usize>,
1398}
1399
1400const RULE_BUDGET: usize = 100_000;
1401
1402/// Lookups over `Obj::root`, built on first use.
1403struct Index {
1404 /// The first scheduled component with each key.
1405 by_key: HashMap<Option<i64>, usize>,
1406 /// THISANDFUTURE overrides by key, ascending.
1407 ranges: Vec<(i64, usize)>,
1408 /// Answers of `Obj::in_series`, which expands.
1409 in_series: RefCell<HashMap<i64, bool>>,
1410 /// The expansion for all ranges at once was cut short.
1411 ranges_cut: Cell<bool>,
1412}
1413
1414impl Obj {
1415 fn new(cal: &ICalendar) -> Self {
1416 let root = match cal.components.is_empty() {
1417 true => Node {
1418 c: ICalendarComponent {
1419 component_type: ICalendarComponentType::VCalendar,
1420 entries: Vec::new(),
1421 component_ids: Vec::new(),
1422 },
1423 children: Vec::new(),
1424 },
1425 false => node(cal, 0),
1426 };
1427 Obj {
1428 root,
1429 zones: Zones::new(cal, Zone::Utc),
1430 index: OnceCell::new(),
1431 budget: Cell::new(RULE_BUDGET),
1432 }
1433 }
1434
1435 fn index(&self) -> &Index {
1436 self.index.get_or_init(|| {
1437 let mut by_key = HashMap::new();
1438 let mut ranges = Vec::new();
1439 for (i, n) in self.root.children.iter().enumerate() {
1440 if !is_item(&n.c) {
1441 continue;
1442 }
1443 let key = self.key(&n.c);
1444 by_key.entry(key).or_insert(i);
1445 if let Some(k) = key.filter(|_| is_range(&n.c)) {
1446 ranges.push((k, i));
1447 }
1448 }
1449 ranges.sort_by_key(|(k, _)| *k);
1450 Index {
1451 by_key,
1452 ranges,
1453 in_series: RefCell::default(),
1454 ranges_cut: Cell::default(),
1455 }
1456 })
1457 }
1458
1459 fn children_mut(&mut self) -> &mut Vec<Node> {
1460 self.index.take();
1461 &mut self.root.children
1462 }
1463
1464 /// Appends a child and returns where it is.
1465 fn push(&mut self, n: Node) -> usize {
1466 let at = self.root.children.len();
1467 let key = is_item(&n.c).then(|| self.key(&n.c));
1468 let range = is_range(&n.c);
1469 self.root.children.push(n);
1470 if let (Some(ix), Some(key)) = (self.index.get_mut(), key) {
1471 ix.by_key.entry(key).or_insert(at);
1472 if let Some(k) = key.filter(|_| range) {
1473 let p = ix.ranges.partition_point(|(r, _)| *r <= k);
1474 ix.ranges.insert(p, (k, at));
1475 }
1476 }
1477 at
1478 }
1479
1480 fn done(self) -> ICalendar {
1481 let mut components = Vec::new();
1482 flatten(&self.root, &mut components);
1483 ICalendar { components }
1484 }
1485
1486 fn comps(&self) -> impl Iterator<Item = &Node> {
1487 self.root.children.iter().filter(|n| is_item(&n.c))
1488 }
1489
1490 fn comps_mut(&mut self) -> impl Iterator<Item = &mut Node> {
1491 self.children_mut().iter_mut().filter(|n| is_item(&n.c))
1492 }
1493
1494 fn master(&self) -> Option<&Node> {
1495 self.find(None)
1496 }
1497
1498 fn find(&self, key: Option<i64>) -> Option<&Node> {
1499 Some(&self.root.children[self.position(key)?])
1500 }
1501
1502 fn position(&self, key: Option<i64>) -> Option<usize> {
1503 self.index().by_key.get(&key).copied()
1504 }
1505
1506 /// The RECURRENCE-ID as an instant; `None` for the master.
1507 fn key(&self, c: &ICalendarComponent) -> Option<i64> {
1508 self.instant(c.property(&ICalendarProperty::RecurrenceId)?)
1509 }
1510
1511 /// The first value of a date or date-time property, in UTC seconds.
1512 /// Dates count from midnight, whatever the zone.
1513 fn instant(&self, e: &ICalendarEntry) -> Option<i64> {
1514 let v = e.values.first()?.as_partial_date_time()?;
1515 self.at(v, e.tz_id())
1516 }
1517
1518 fn at(&self, v: &PartialDateTime, tzid: Option<&str>) -> Option<i64> {
1519 let dt = v.to_date_time()?;
1520 Some(match dt.offset {
1521 _ if v.hour.is_none() => dt.date_time.and_utc().timestamp(),
1522 Some(o) => dt.date_time.and_utc().timestamp() - i64::from(o.local_minus_utc()),
1523 None => self.zones.get(tzid).to_utc(dt.date_time).timestamp(),
1524 })
1525 }
1526
1527 /// Every value of a property, comparable across encodings: instants for
1528 /// dates and date-times, the text otherwise.
1529 fn times(&self, c: &ICalendarComponent, prop: &ICalendarProperty) -> HashSet<String> {
1530 c.properties(prop)
1531 .flat_map(|e| e.values.iter().map(move |v| self.value_key(e, v)))
1532 .collect()
1533 }
1534
1535 fn value_key(&self, e: &ICalendarEntry, v: &ICalendarValue) -> String {
1536 match v.as_partial_date_time() {
1537 Some(p) => match self.at(p, e.tz_id()) {
1538 Some(t) if p.hour.is_none() => format!("{t}d"),
1539 Some(t) => t.to_string(),
1540 None => format!("{v:?}"),
1541 },
1542 None => match v {
1543 ICalendarValue::RecurrenceRule(r) => format!("{:?}", canonical(r)),
1544 _ => format!("{v:?}"),
1545 },
1546 }
1547 }
1548
1549 fn organizer(&self) -> Option<String> {
1550 self.comps()
1551 .find_map(|c| address(c.c.property(&ICalendarProperty::Organizer)?))
1552 .map(str::to_string)
1553 }
1554
1555 /// Whether the server replies for the attendee (RFC 6638, 3.2.2).
1556 fn organizer_schedules(&self) -> bool {
1557 self.comps()
1558 .filter_map(|c| c.c.property(&ICalendarProperty::Organizer))
1559 .all(server_agent)
1560 }
1561
1562 /// Per child of `root`, the addresses it invites through the server,
1563 /// lowercase.
1564 fn invitations(&self) -> Vec<HashSet<String>> {
1565 self.root
1566 .children
1567 .iter()
1568 .map(|n| match is_item(&n.c) {
1569 true => attendees(&n.c)
1570 .filter(|e| server_agent(e))
1571 .filter_map(address)
1572 .map(str::to_ascii_lowercase)
1573 .collect(),
1574 false => HashSet::new(),
1575 })
1576 .collect()
1577 }
1578
1579 /// What an attendee sees (RFC 6638, 3.2.6), given which children invite
1580 /// them. Overrides they are not in become EXDATEs. A THISANDFUTURE
1581 /// override they are not in ends their series.
1582 fn view(&self, invited: &[bool]) -> Option<Vec<Node>> {
1583 let overrides = || {
1584 self.root.children.iter().enumerate().filter(|(_, c)| {
1585 is_item(&c.c) && c.c.has_property(&ICalendarProperty::RecurrenceId)
1586 })
1587 };
1588 let mut out = Vec::new();
1589 let master = self
1590 .position(None)
1591 .filter(|i| invited[*i])
1592 .map(|i| &self.root.children[i]);
1593 if let Some(m) = master {
1594 let mut m = m.clone();
1595 let mut end: Option<i64> = None;
1596 for (i, o) in overrides() {
1597 let Some(rid) = o.c.property(&ICalendarProperty::RecurrenceId) else {
1598 continue;
1599 };
1600 if invited[i] {
1601 continue;
1602 }
1603 if is_range(&o.c) {
1604 // ponytail: a later range that invites them again is lost too.
1605 // A stale range past the series' end cuts nothing.
1606 if let Some(t) = self.instant(rid).filter(|t| self.in_series(*t)) {
1607 end = Some(end.map_or(t, |e| e.min(t)));
1608 }
1609 continue;
1610 }
1611 m.c.entries.push(ICalendarEntry {
1612 name: ICalendarProperty::Exdate,
1613 params: without(rid.params.clone(), &ICalendarParameterName::Range),
1614 values: rid.values.clone(),
1615 });
1616 }
1617 let first =
1618 m.c.property(&ICalendarProperty::Dtstart)
1619 .and_then(|e| self.instant(e));
1620 match end {
1621 // The range starts with the first instance: none is left.
1622 Some(t) if first.is_some_and(|f| t <= f) => {}
1623 Some(t) => {
1624 self.end_series(&mut m.c, t);
1625 out.push(m);
1626 }
1627 None => out.push(m),
1628 }
1629 }
1630 out.extend(
1631 overrides()
1632 .filter(|(i, _)| invited[*i])
1633 .map(|(_, c)| c.clone()),
1634 );
1635 for n in &mut out {
1636 n.children
1637 .retain(|ch| ch.c.component_type != ICalendarComponentType::VAlarm);
1638 strip_scheduling_params(&mut n.c);
1639 }
1640 (!out.is_empty()).then_some(out)
1641 }
1642
1643 /// Whether override `c`, read through `obj`, is instance `key` of this
1644 /// series unchanged: its start, length and people, no rules of its own.
1645 fn plain(&self, obj: &Obj, c: &ICalendarComponent, key: i64, added: bool) -> bool {
1646 let Some(base) = self.base(key) else {
1647 return false;
1648 };
1649 let start = c
1650 .property(&ICalendarProperty::Dtstart)
1651 .and_then(|e| obj.instant(e));
1652 start == Some(self.moved(key))
1653 && [
1654 ICalendarProperty::Rrule,
1655 ICalendarProperty::Rdate,
1656 ICalendarProperty::Exrule,
1657 ]
1658 .iter()
1659 .all(|p| !c.has_property(p))
1660 && obj.end(c) == self.end_from(&base.c, self.moved(key))
1661 && organizer_of(c) == organizer_of(&base.c)
1662 && addresses(c) == addresses(&base.c)
1663 && (c.status() == base.c.status()
1664 || own_progress(c, &base.c)
1665 // Some clients mark the instance they answer as confirmed.
1666 || added
1667 && base.c.status().is_none()
1668 && c.status() == Some(&ICalendarStatus::Confirmed))
1669 }
1670
1671 /// Whether the master's own rules have an instance at `key`.
1672 fn in_series(&self, key: i64) -> bool {
1673 let ix = self.index();
1674 if let Some(&k) = ix.in_series.borrow().get(&key) {
1675 return k;
1676 }
1677 // One expansion answers every range override at once.
1678 let mut keys = vec![key];
1679 if !ix.ranges_cut.get() {
1680 keys.extend(ix.ranges.iter().map(|(k, _)| *k));
1681 }
1682 // Cut short, the batch falls back to the key alone with the full cap.
1683 let cap = match keys.len() {
1684 1 => MAX_OCCURRENCES,
1685 _ => RULE_BUDGET,
1686 };
1687 let (mut found, cut) = self.expands_to(&keys, cap);
1688 if cut && keys.len() > 1 {
1689 ix.ranges_cut.set(true);
1690 keys = vec![key];
1691 found = self.expands_to(&keys, MAX_OCCURRENCES).0;
1692 }
1693 let mut memo = ix.in_series.borrow_mut();
1694 for k in keys {
1695 memo.insert(k, found.contains(&k));
1696 }
1697 memo[&key]
1698 }
1699
1700 /// Which of `keys` the master's own rules reach, and whether the
1701 /// expansion was cut short.
1702 fn expands_to(&self, keys: &[i64], cap: usize) -> (HashSet<i64>, bool) {
1703 let at = |t: Option<&i64>| DateTime::from_timestamp(*t?, 0);
1704 let (Some(m), Some(from), Some(to)) =
1705 (self.master(), at(keys.iter().min()), at(keys.iter().max()))
1706 else {
1707 return (HashSet::new(), false);
1708 };
1709 let window = from - TimeDelta::days(1)..to + TimeDelta::days(1);
1710 let e = expand_in(&self.alone(m.c.clone()), &self.zones, window, cap);
1711 (rids(&e), e.truncated)
1712 }
1713
1714 /// The DTSTART of `c` as an instant.
1715 fn start(&self, c: &ICalendarComponent) -> Option<DateTime<Utc>> {
1716 DateTime::from_timestamp(self.instant(c.property(&ICalendarProperty::Dtstart)?)?, 0)
1717 }
1718
1719 /// The starts the RRULE of `c` gives in `window`, without RDATE, EXDATE
1720 /// and EXRULE. `None` if the expansion was cut short.
1721 fn rule_starts(
1722 &self,
1723 mut c: ICalendarComponent,
1724 window: std::ops::Range<DateTime<Utc>>,
1725 ) -> Option<Vec<i64>> {
1726 c.entries.retain(|e| {
1727 !matches!(
1728 e.name,
1729 ICalendarProperty::Rdate
1730 | ICalendarProperty::Exdate
1731 | ICalendarProperty::Exrule
1732 | ICalendarProperty::RecurrenceId
1733 )
1734 });
1735 let left = self.budget.get();
1736 let e = expand_in(&self.alone(c), &self.zones, window, left);
1737 if e.truncated {
1738 self.budget.set(0);
1739 return None;
1740 }
1741 self.budget.set(left.saturating_sub(e.generated));
1742 let mut v: Vec<i64> = e
1743 .instances
1744 .iter()
1745 .map(|i| i.recurrence_id.unwrap_or(i.start).timestamp())
1746 .collect();
1747 v.sort_unstable();
1748 Some(v)
1749 }
1750
1751 /// How the instances `n` keeps compare with those `o` keeps, for rules of
1752 /// `c` that differ only in COUNT or UNTIL. `Greater` if `n` keeps more.
1753 /// Only COUNT, or the gap between two UNTILs, is expanded.
1754 fn compare_ends(
1755 &self,
1756 c: &ICalendarComponent,
1757 o: &ICalendarRecurrenceRule,
1758 n: &ICalendarRecurrenceRule,
1759 ) -> Option<Ordering> {
1760 let s = prop_stamp(&self.zones, c, &ICalendarProperty::Dtstart)?;
1761 let end = |u: &PartialDateTime| Some(until_utc(u, &s)?.timestamp());
1762 let start = self.start(c)?;
1763 // The last start COUNT keeps and the one after it.
1764 let counted = |k: u32| {
1765 let mut r = o.clone();
1766 r.until = None;
1767 r.count = Some(k + 1);
1768 let v = self.rule_starts(with_rule(c, r), start..DateTime::<Utc>::MAX_UTC)?;
1769 Some((
1770 v.get((k as usize).checked_sub(1)?).copied(),
1771 v.get(k as usize).copied(),
1772 ))
1773 };
1774 // Whether COUNT keeps more (`Greater`) than an UNTIL at `u`.
1775 let count_vs_until = |k: u32, u: i64| {
1776 let (last, next) = counted(k)?;
1777 Some(if last.is_some_and(|l| l > u) {
1778 Ordering::Greater
1779 } else if next.is_some_and(|x| x <= u) {
1780 Ordering::Less
1781 } else {
1782 Ordering::Equal
1783 })
1784 };
1785 match (o.count, &o.until, n.count, &n.until) {
1786 (Some(a), None, Some(b), None) => Some(b.cmp(&a)),
1787 (None, Some(a), None, Some(b)) => {
1788 let (a, b) = (end(a)?, end(b)?);
1789 if a == b {
1790 return Some(Ordering::Equal);
1791 }
1792 let (lo, hi) = (a.min(b), a.max(b));
1793 let at = |t: i64| DateTime::from_timestamp(t + 1, 0);
1794 let mut r = o.clone();
1795 r.until = None;
1796 let between = self.rule_starts(with_rule(c, r), at(lo)?..at(hi)?);
1797 // The window also holds instances that only overlap it.
1798 Some(match between {
1799 Some(v) if v.iter().all(|t| *t <= lo || *t > hi) => Ordering::Equal,
1800 _ => b.cmp(&a),
1801 })
1802 }
1803 (Some(k), None, None, Some(u)) => count_vs_until(k, end(u)?).map(Ordering::reverse),
1804 (None, Some(u), Some(k), None) => count_vs_until(k, end(u)?),
1805 _ => None,
1806 }
1807 }
1808
1809 /// A calendar of `c` alone, to expand with this object's zones.
1810 fn alone(&self, c: ICalendarComponent) -> ICalendar {
1811 let root = Node {
1812 c: self.root.c.clone(),
1813 children: vec![Node {
1814 c,
1815 children: Vec::new(),
1816 }],
1817 };
1818 let mut components = Vec::new();
1819 flatten(&root, &mut components);
1820 ICalendar { components }
1821 }
1822
1823 /// What instance `key` copies: the THISANDFUTURE override that moves it,
1824 /// else the master.
1825 fn base(&self, key: i64) -> Option<&Node> {
1826 self.future(key).map(|(n, _)| n).or(self.master())
1827 }
1828
1829 /// When the component ends: DTSTART plus its length, else its DUE.
1830 fn end(&self, c: &ICalendarComponent) -> Option<i64> {
1831 match c.property(&ICalendarProperty::Dtstart) {
1832 Some(s) => self.end_from(c, self.instant(s)?),
1833 None => self.instant(c.property(&ICalendarProperty::Due)?),
1834 }
1835 }
1836
1837 /// When `c` would end if it started at `start`. The days and weeks of a
1838 /// DURATION count in wall-clock time (RFC 5545, 3.3.6).
1839 fn end_from(&self, c: &ICalendarComponent, start: i64) -> Option<i64> {
1840 let duration = c.property(&ICalendarProperty::Duration);
1841 let d = match duration.and_then(|e| e.values.first()) {
1842 Some(ICalendarValue::Duration(d))
1843 if !d.neg
1844 && !c.has_property(&ICalendarProperty::Dtend)
1845 && !c.has_property(&ICalendarProperty::Due) =>
1846 {
1847 d
1848 }
1849 _ => return Some(start + self.length(c)?),
1850 };
1851 let zone = self
1852 .zones
1853 .get(c.property(&ICalendarProperty::Dtstart)?.tz_id());
1854 let days = TimeDelta::try_days(i64::from(d.weeks) * 7 + i64::from(d.days))?;
1855 let local = add_local(zone.to_local(DateTime::from_timestamp(start, 0)?), days);
1856 let exact = i64::from(d.hours) * 3600 + i64::from(d.minutes) * 60 + i64::from(d.seconds);
1857 Some(zone.to_utc(local).timestamp() + exact)
1858 }
1859
1860 /// Which of `keys` are instances of the series in `cal`, and which a
1861 /// cut-short expansion cannot tell.
1862 fn instances(&self, cal: &ICalendar, keys: &[i64]) -> (HashSet<i64>, HashSet<i64>) {
1863 let zones = Zones::new(cal, Zone::Utc);
1864 let at = |t: i64| DateTime::from_timestamp(t, 0);
1865 let starts: Vec<(i64, DateTime<Utc>)> = keys
1866 .iter()
1867 .filter_map(|k| Some((*k, at(self.moved(*k))?)))
1868 .collect();
1869 let (Some(lo), Some(hi)) = (
1870 starts.iter().map(|(_, s)| *s).min(),
1871 starts.iter().map(|(_, s)| *s).max(),
1872 ) else {
1873 return Default::default();
1874 };
1875 // A day either side: an all-day shift is whole days, not 24 hours.
1876 let day = TimeDelta::days(1);
1877 // Cut short, the keys get checked one by one, so a small cap is safe.
1878 let e = expand_in(cal, &zones, lo - day..hi + day, RULE_BUDGET);
1879 if !e.truncated {
1880 return (rids(&e), HashSet::new());
1881 }
1882 // The sender picks the keys and so the span.
1883 let (mut found, mut unknown) = (HashSet::new(), HashSet::new());
1884 let mut left = MAX_OCCURRENCES;
1885 for (k, s) in starts {
1886 let slack = match self.future(k) {
1887 Some(_) => day,
1888 None => TimeDelta::seconds(1),
1889 };
1890 let e = expand_in(cal, &zones, s - slack..s + slack, left);
1891 left = left.saturating_sub(e.generated);
1892 match e.truncated {
1893 true => unknown.insert(k),
1894 false => rids(&e).contains(&k) && found.insert(k),
1895 };
1896 }
1897 (found, unknown)
1898 }
1899
1900 /// Seconds from DTSTART to DTEND or DUE, or the DURATION. Without
1901 /// either, a day for a date and nothing for a date-time (RFC 5545).
1902 fn length(&self, c: &ICalendarComponent) -> Option<i64> {
1903 let start = c.property(&ICalendarProperty::Dtstart)?;
1904 let instant = |p: &ICalendarProperty| c.property(p).and_then(|e| self.instant(e));
1905 match instant(&ICalendarProperty::Dtend).or_else(|| instant(&ICalendarProperty::Due)) {
1906 Some(end) => Some(end - self.instant(start)?),
1907 None => match c.property(&ICalendarProperty::Duration) {
1908 Some(d) => match d.values.first()? {
1909 ICalendarValue::Duration(d) => Some(d.as_seconds()),
1910 _ => None,
1911 },
1912 None => {
1913 let date = start.values.first()?.as_partial_date_time()?.hour.is_none();
1914 Some(if date { 86400 } else { 0 })
1915 }
1916 },
1917 }
1918 }
1919
1920 /// Ends the series of master `m` before instant `at`: UNTIL in the form
1921 /// RFC 5545 asks for, COUNT dropped, later RDATEs removed. `at` is an
1922 /// instance, so COUNT had not ended before it.
1923 fn end_series(&self, m: &mut ICalendarComponent, at: i64) {
1924 let Some(start) = m.property(&ICalendarProperty::Dtstart).cloned() else {
1925 return;
1926 };
1927 let Some(form) = start.values.first().and_then(|v| v.as_partial_date_time()) else {
1928 return;
1929 };
1930 let s = stamp(&self.zones, form, start.tz_id());
1931 // Obj reads floating times in UTC, so `at` is their wall-clock time.
1932 let until = if form.hour.is_none() {
1933 PartialDateTime::from_date_timestamp(at - 86400)
1934 } else if form.tz_hour.is_none() && start.tz_id().is_none() {
1935 PartialDateTime::from_naive_timestamp(at - 1)
1936 } else {
1937 PartialDateTime::from_utc_timestamp(at - 1)
1938 };
1939 for e in m.entries.iter_mut() {
1940 match e.name {
1941 ICalendarProperty::Rrule => {
1942 for v in &mut e.values {
1943 if let ICalendarValue::RecurrenceRule(r) = v
1944 && !r
1945 .until
1946 .as_ref()
1947 .and_then(|u| until_utc(u, s.as_ref()?))
1948 .is_some_and(|u| u.timestamp() < at)
1949 {
1950 r.until = Some(until.clone());
1951 r.count = None;
1952 }
1953 }
1954 }
1955 ICalendarProperty::Rdate => {
1956 let tz = e.tz_id().map(str::to_string);
1957 e.values.retain(|v| {
1958 v.as_partial_date_time()
1959 .and_then(|p| self.at(p, tz.as_deref()))
1960 .is_none_or(|t| t < at)
1961 });
1962 }
1963 _ => {}
1964 }
1965 }
1966 m.entries
1967 .retain(|e| e.name != ICalendarProperty::Rdate || !e.values.is_empty());
1968 }
1969
1970 /// A message: this object's VCALENDAR and time zones around `comps`.
1971 fn envelope(&self, comps: Vec<Node>, method: Method, now: DateTime<Utc>) -> ICalendar {
1972 let mut root = Node {
1973 c: self.root.c.clone(),
1974 children: self
1975 .root
1976 .children
1977 .iter()
1978 .filter(|n| n.c.component_type == ICalendarComponentType::VTimezone)
1979 .cloned()
1980 .collect(),
1981 };
1982 let method = match method {
1983 Method::Request => ICalendarMethod::Request,
1984 Method::Cancel => ICalendarMethod::Cancel,
1985 Method::Reply => ICalendarMethod::Reply,
1986 };
1987 set_prop(
1988 &mut root.c,
1989 ICalendarProperty::Method,
1990 ICalendarValue::Method(method),
1991 );
1992 let stamp = PartialDateTime::from_utc_timestamp(now.timestamp());
1993 for mut n in comps {
1994 set_prop(
1995 &mut n.c,
1996 ICalendarProperty::Dtstamp,
1997 ICalendarValue::PartialDateTime(Box::new(stamp.clone())),
1998 );
1999 root.children.push(n);
2000 }
2001 let mut components = Vec::new();
2002 flatten(&root, &mut components);
2003 ICalendar { components }
2004 }
2005
2006 /// A RECURRENCE-ID for the instance at `rid`, in the form of `start`.
2007 fn recurrence_id(
2008 &self,
2009 start: &ICalendarEntry,
2010 rid: DateTime<Utc>,
2011 floating: &Zone,
2012 ) -> Option<ICalendarEntry> {
2013 let v = start.values.first()?.as_partial_date_time()?;
2014 let local = |zone: &Zone| zone.to_local(rid).and_utc().timestamp();
2015 let value = if v.hour.is_none() {
2016 PartialDateTime::from_date_timestamp(local(floating))
2017 } else if v.tz_hour.is_some() {
2018 PartialDateTime::from_utc_timestamp(rid.timestamp())
2019 } else if start.tz_id().is_some() {
2020 PartialDateTime::from_naive_timestamp(local(&self.zones.get(start.tz_id())))
2021 } else {
2022 PartialDateTime::from_naive_timestamp(local(floating))
2023 };
2024 Some(ICalendarEntry {
2025 name: ICalendarProperty::RecurrenceId,
2026 params: without(start.params.clone(), &ICalendarParameterName::Range),
2027 values: vec![ICalendarValue::PartialDateTime(Box::new(value))],
2028 })
2029 }
2030
2031 /// The latest THISANDFUTURE override at or before instance `key`, and
2032 /// its key.
2033 fn future(&self, key: i64) -> Option<(&Node, i64)> {
2034 let ranges = &self.index().ranges;
2035 let (k, at) = ranges[..ranges.partition_point(|(k, _)| *k <= key)].last()?;
2036 Some((&self.root.children[*at], *k))
2037 }
2038
2039 /// Where instance `key` starts once a THISANDFUTURE override moves it.
2040 /// The move is wall-clock time in the zone of the series, as in expand.
2041 fn moved(&self, key: i64) -> i64 {
2042 let shifted = || {
2043 let (n, k) = self.future(key)?;
2044 let dtstart = n.c.property(&ICalendarProperty::Dtstart)?;
2045 let start = self.instant(dtstart)?;
2046 // A copy without its master still has the override's zone.
2047 let series = self
2048 .master()
2049 .and_then(|m| m.c.property(&ICalendarProperty::Dtstart));
2050 let zone = self.zones.get(series.unwrap_or(dtstart).tz_id());
2051 let local = |t: i64| Some(zone.to_local(DateTime::from_timestamp(t, 0)?));
2052 let shift = local(start)? - local(k)?;
2053 Some(zone.to_utc(add_local(local(key)?, shift)).timestamp())
2054 };
2055 shifted().unwrap_or(key)
2056 }
2057
2058 /// Whether the series in `cal` has an instance at `rid`, whose key is
2059 /// `key`.
2060 fn occurs(&self, cal: &ICalendar, rid: DateTime<Utc>, key: i64, floating: &Zone) -> bool {
2061 let Some(at) = DateTime::from_timestamp(self.moved(key), 0) else {
2062 return false;
2063 };
2064 // A day either side: an all-day shift is whole days, not 24 hours.
2065 let window = at - TimeDelta::days(1)..at + TimeDelta::days(1);
2066 expand(cal, window, floating.clone())
2067 .instances
2068 .iter()
2069 .any(|i| i.recurrence_id == Some(rid))
2070 }
2071
2072 /// An override for the instance at `rid`, from the master or from the
2073 /// THISANDFUTURE override that moves it.
2074 fn single(&self, rid: DateTime<Utc>, floating: &Zone) -> Option<Node> {
2075 let master = self.master()?;
2076 let form = master.c.property(&ICalendarProperty::Dtstart)?;
2077 let id = self.recurrence_id(form, rid, floating)?;
2078 let key = self.instant(&id)?;
2079 let Some((base, _)) = self.future(key) else {
2080 return Some(self.instance(master, &id, &id));
2081 };
2082 let at = self.moved(key);
2083 let start = match form.values.first()?.as_partial_date_time()?.hour {
2084 None => ICalendarEntry {
2085 values: vec![ICalendarValue::PartialDateTime(Box::new(
2086 PartialDateTime::from_date_timestamp(at),
2087 ))],
2088 ..id.clone()
2089 },
2090 Some(_) => self.recurrence_id(form, DateTime::from_timestamp(at, 0)?, floating)?,
2091 };
2092 Some(self.instance(base, &id, &start))
2093 }
2094
2095 /// Narrows the THISANDFUTURE override at instance `rid` (key `key`) to
2096 /// that instance. A copy of it carries the range on from the next instance
2097 /// without an override of its own, found by expanding `cal`.
2098 fn narrow(&mut self, cal: &ICalendar, rid: DateTime<Utc>, key: i64, floating: &Zone) {
2099 let Some(at) = self.position(Some(key)) else {
2100 return;
2101 };
2102 let Some(range) = self.root.children[at]
2103 .c
2104 .property(&ICalendarProperty::RecurrenceId)
2105 .and_then(|e| e.parameter(&ICalendarParameterName::Range))
2106 .cloned()
2107 else {
2108 return;
2109 };
2110 let later = self
2111 .comps()
2112 .filter(|c| is_range(&c.c))
2113 .filter_map(|c| self.key(&c.c))
2114 .filter(|k| *k > key)
2115 .min();
2116 let next = self
2117 .next_free(cal, rid, key, floating)
2118 .filter(|(_, k)| later.is_none_or(|l| *k < l))
2119 .and_then(|(r, _)| self.single(r, floating));
2120 if let Some(mut n) = next {
2121 if let Some(e) =
2122 n.c.entries
2123 .iter_mut()
2124 .find(|e| e.name == ICalendarProperty::RecurrenceId)
2125 {
2126 set_param(e, ICalendarParameterName::Range, range);
2127 }
2128 self.push(n);
2129 }
2130 if let Some(e) = self.children_mut()[at]
2131 .c
2132 .entries
2133 .iter_mut()
2134 .find(|e| e.name == ICalendarProperty::RecurrenceId)
2135 {
2136 remove_param(e, &ICalendarParameterName::Range);
2137 }
2138 }
2139
2140 /// The first instance after `rid` that has no override, and its key.
2141 // ponytail: looks ten years ahead; a range beyond that falls back to the master.
2142 fn next_free(
2143 &self,
2144 cal: &ICalendar,
2145 rid: DateTime<Utc>,
2146 key: i64,
2147 floating: &Zone,
2148 ) -> Option<(DateTime<Utc>, i64)> {
2149 let form = self.master()?.c.property(&ICalendarProperty::Dtstart)?;
2150 let from = DateTime::from_timestamp(self.moved(key), 0)?;
2151 [1, 32, 400, 3700].into_iter().find_map(|days| {
2152 expand(cal, from..from + TimeDelta::days(days), floating.clone())
2153 .instances
2154 .iter()
2155 .filter_map(|i| i.recurrence_id)
2156 .filter(|r| *r > rid)
2157 .filter_map(|r| Some((r, self.instant(&self.recurrence_id(form, r, floating)?)?)))
2158 .filter(|(_, k)| self.find(Some(*k)).is_none())
2159 .min()
2160 })
2161 }
2162
2163 /// An override of `base` for the instance `rid` starting at `start`, so
2164 /// it can hold a status of its own. Its length becomes a DURATION.
2165 fn instance(&self, base: &Node, rid: &ICalendarEntry, start: &ICalendarEntry) -> Node {
2166 let mut n = base.clone();
2167 let begin =
2168 n.c.property(&ICalendarProperty::Dtstart)
2169 .and_then(|e| self.instant(e));
2170 let end_prop = match n.c.component_type {
2171 ICalendarComponentType::VTodo => ICalendarProperty::Due,
2172 _ => ICalendarProperty::Dtend,
2173 };
2174 let end = n.c.property(&end_prop).and_then(|e| self.instant(e));
2175 if let (Some(s), Some(e)) = (begin, end) {
2176 n.c.entries.retain(|x| x.name != end_prop);
2177 n.c.entries.push(ICalendarEntry {
2178 name: ICalendarProperty::Duration,
2179 params: Vec::new(),
2180 values: vec![ICalendarValue::Duration(ICalendarDuration::from_seconds(
2181 (e - s).max(0),
2182 ))],
2183 });
2184 }
2185 n.c.entries.retain(|e| {
2186 !matches!(
2187 e.name,
2188 ICalendarProperty::Rrule
2189 | ICalendarProperty::Rdate
2190 | ICalendarProperty::Exdate
2191 | ICalendarProperty::Exrule
2192 | ICalendarProperty::Dtstart
2193 | ICalendarProperty::RecurrenceId
2194 )
2195 });
2196 n.c.entries.push(ICalendarEntry {
2197 name: ICalendarProperty::Dtstart,
2198 params: without(start.params.clone(), &ICalendarParameterName::Range),
2199 values: start.values.clone(),
2200 });
2201 n.c.entries.push(ICalendarEntry {
2202 name: ICalendarProperty::RecurrenceId,
2203 params: without(rid.params.clone(), &ICalendarParameterName::Range),
2204 values: rid.values.clone(),
2205 });
2206 n
2207 }
2208}
2209
2210// ---------------------------------------------------------------------------
2211// Properties and parameters
2212// ---------------------------------------------------------------------------
2213
2214fn address(e: &ICalendarEntry) -> Option<&str> {
2215 e.values.first()?.as_text().map(str::trim)
2216}
2217
2218fn attendees(c: &ICalendarComponent) -> impl Iterator<Item = &ICalendarEntry> {
2219 c.properties(&ICalendarProperty::Attendee)
2220}
2221
2222/// A task's progress is the assignee's to set per instance, unless the
2223/// organizer cancelled it.
2224fn own_progress(c: &ICalendarComponent, base: &ICalendarComponent) -> bool {
2225 use ICalendarStatus::*;
2226 c.component_type == ICalendarComponentType::VTodo
2227 && matches!(c.status(), None | Some(NeedsAction | InProcess | Completed))
2228 && base.status() != Some(&Cancelled)
2229}
2230
2231fn organizer_of(c: &ICalendarComponent) -> Option<String> {
2232 c.property(&ICalendarProperty::Organizer)
2233 .and_then(address)
2234 .map(str::to_ascii_lowercase)
2235}
2236
2237/// Whether `c` repeats without end.
2238pub(crate) fn endless(c: &ICalendarComponent) -> bool {
2239 c.properties(&ICalendarProperty::Rrule)
2240 .filter_map(|e| rule(e.values.first()?))
2241 .any(|r| r.count.is_none() && r.until.is_none())
2242}
2243
2244fn addresses(c: &ICalendarComponent) -> Vec<String> {
2245 let mut v: Vec<String> = attendees(c)
2246 .filter_map(address)
2247 .map(str::to_ascii_lowercase)
2248 .collect();
2249 v.sort();
2250 v.dedup();
2251 v
2252}
2253
2254fn is_range(c: &ICalendarComponent) -> bool {
2255 c.property(&ICalendarProperty::RecurrenceId)
2256 .is_some_and(|e| e.parameter(&ICalendarParameterName::Range).is_some())
2257}
2258
2259fn client_scheduled(c: &ICalendarComponent, a: &str) -> bool {
2260 attendees(c).any(|e| address(e).is_some_and(|x| x.eq_ignore_ascii_case(a)) && !server_agent(e))
2261}
2262
2263fn same_attendee<'a>(c: &'a ICalendarComponent, e: &ICalendarEntry) -> Option<&'a ICalendarEntry> {
2264 let a = address(e)?;
2265 attendees(c).find(|x| address(x).is_some_and(|b| b.eq_ignore_ascii_case(a)))
2266}
2267
2268pub(crate) fn own_partstat(c: &ICalendarComponent, me: Is) -> Option<String> {
2269 attendees(c).find(|e| address(e).is_some_and(me)).map(|e| {
2270 param(e, &ICalendarParameterName::Partstat)
2271 .unwrap_or("NEEDS-ACTION")
2272 .to_ascii_uppercase()
2273 })
2274}
2275
2276fn set_own_partstat(c: &mut ICalendarComponent, me: Is, stat: ICalendarParticipationStatus) {
2277 for e in c
2278 .entries
2279 .iter_mut()
2280 .filter(|e| e.name == ICalendarProperty::Attendee && address(e).is_some_and(me))
2281 {
2282 set_param(e, ICalendarParameterName::Partstat, partstat(stat.clone()));
2283 }
2284}
2285
2286/// `SCHEDULE-AGENT` absent or `SERVER`. Unknown values count as `NONE`.
2287fn server_agent(e: &ICalendarEntry) -> bool {
2288 param(e, &ICalendarParameterName::ScheduleAgent)
2289 .is_none_or(|v| v.eq_ignore_ascii_case("SERVER"))
2290}
2291
2292fn sequence(c: &ICalendarComponent) -> i64 {
2293 c.property(&ICalendarProperty::Sequence)
2294 .and_then(|e| e.values.first()?.as_integer())
2295 .unwrap_or(0)
2296}
2297
2298fn param<'a>(e: &'a ICalendarEntry, name: &ICalendarParameterName) -> Option<&'a str> {
2299 e.parameter(name)?.as_text()
2300}
2301
2302fn set_param(e: &mut ICalendarEntry, name: ICalendarParameterName, value: ICalendarParameterValue) {
2303 remove_param(e, &name);
2304 e.params.push(ICalendarParameter::new(name, value));
2305}
2306
2307fn remove_param(e: &mut ICalendarEntry, name: &ICalendarParameterName) {
2308 e.params.retain(|p| &p.name != name);
2309}
2310
2311fn without(
2312 mut params: Vec<ICalendarParameter>,
2313 name: &ICalendarParameterName,
2314) -> Vec<ICalendarParameter> {
2315 params.retain(|p| &p.name != name);
2316 params
2317}
2318
2319fn set_prop(c: &mut ICalendarComponent, name: ICalendarProperty, value: ICalendarValue) {
2320 c.entries.retain(|e| e.name != name);
2321 c.entries.push(ICalendarEntry {
2322 name,
2323 params: Vec::new(),
2324 values: vec![value],
2325 });
2326}
2327
2328/// Messages carry none of the scheduling parameters (RFC 6638, 7).
2329fn strip_scheduling_params(c: &mut ICalendarComponent) {
2330 for e in c.entries.iter_mut().filter(|e| {
2331 matches!(
2332 e.name,
2333 ICalendarProperty::Attendee | ICalendarProperty::Organizer
2334 )
2335 }) {
2336 for p in [
2337 ICalendarParameterName::ScheduleAgent,
2338 ICalendarParameterName::ScheduleStatus,
2339 ICalendarParameterName::ScheduleForceSend,
2340 ] {
2341 remove_param(e, &p);
2342 }
2343 }
2344}
2345
2346fn text(s: &str) -> ICalendarParameterValue {
2347 ICalendarParameterValue::Text(s.to_string())
2348}
2349
2350fn partstat(p: ICalendarParticipationStatus) -> ICalendarParameterValue {
2351 ICalendarParameterValue::Partstat(p)
2352}
2353
2354/// Makes an object forget a principal that is about to be deleted, so that
2355/// a later principal of the same name is not taken for it. Every ORGANIZER
2356/// and ATTENDEE that `gone` names gets the address `tombstone`, and such an
2357/// ATTENDEE gets SCHEDULE-STATUS 3.7. A component the gone principal
2358/// organized is cancelled. All other lines keep their bytes. `None` when
2359/// nothing names the principal.
2360pub fn forget(text: &str, gone: Is, tombstone: &str) -> Option<String> {
2361 let mut out: Vec<String> = Vec::new();
2362 // Per open component: name, index of its BEGIN line in `out`, index of
2363 // its STATUS line, whether the gone principal organized it.
2364 let mut open: Vec<(String, usize, Option<usize>, bool)> = Vec::new();
2365 let mut changed = false;
2366 for raw in logical_lines(text) {
2367 let eol = match raw.ends_with('\n') {
2368 true if raw.ends_with("\r\n") => "\r\n",
2369 true => "\n",
2370 false => "\r\n",
2371 };
2372 let line = unfold(raw);
2373 let n = name(&line);
2374 match n.as_str() {
2375 "BEGIN" => open.push((
2376 value(&line).trim().to_ascii_uppercase(),
2377 out.len(),
2378 None,
2379 false,
2380 )),
2381 "END" => {
2382 if let Some((comp, begin, status, true)) = open.pop()
2383 && ["VEVENT", "VTODO", "VJOURNAL"].contains(&comp.as_str())
2384 {
2385 let cancelled = format!("STATUS:CANCELLED{eol}");
2386 match status {
2387 Some(i) => out[i] = cancelled,
2388 None => out.insert(begin + 1, cancelled),
2389 }
2390 }
2391 }
2392 "STATUS" => {
2393 if let Some(top) = open.last_mut() {
2394 top.2 = Some(out.len());
2395 }
2396 }
2397 "ORGANIZER" | "ATTENDEE" if gone(value(&line).trim()) => {
2398 let attendee = n == "ATTENDEE";
2399 let mut new = n.clone();
2400 for p in param_parts(&line) {
2401 if !(attendee
2402 && p.trim()
2403 .to_ascii_uppercase()
2404 .starts_with("SCHEDULE-STATUS="))
2405 {
2406 new.push(';');
2407 new.push_str(p);
2408 }
2409 }
2410 if attendee {
2411 new.push_str(";SCHEDULE-STATUS=3.7");
2412 } else if let Some(top) = open.last_mut() {
2413 top.3 = true;
2414 }
2415 new.push(':');
2416 new.push_str(tombstone);
2417 out.push(fold(&new, eol));
2418 changed = true;
2419 continue;
2420 }
2421 _ => {}
2422 }
2423 out.push(raw.to_string());
2424 }
2425 changed.then(|| out.concat())
2426}
2427