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