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::{MAX_OCCURRENCES, expand, expand_in, stamp, until_utc};
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 /// Occurrences left for comparing rules. Spent, a rule change counts as
1407 /// growth for the organizer and is refused from an attendee.
1408 budget: Cell<usize>,
1409}
1410
1411const RULE_BUDGET: usize = 100_000;
1412
1413/// Lookups over `Obj::root`, built on first use.
1414struct Index {
1415 /// The first scheduled component with each key.
1416 by_key: HashMap<Option<i64>, usize>,
1417 /// THISANDFUTURE overrides by key, ascending.
1418 ranges: Vec<(i64, usize)>,
1419 /// Answers of `Obj::in_series`, which expands.
1420 in_series: RefCell<HashMap<i64, bool>>,
1421}
1422
1423impl Obj {
1424 fn new(cal: &ICalendar) -> Self {
1425 let root = match cal.components.is_empty() {
1426 true => Node {
1427 c: ICalendarComponent {
1428 component_type: ICalendarComponentType::VCalendar,
1429 entries: Vec::new(),
1430 component_ids: Vec::new(),
1431 },
1432 children: Vec::new(),
1433 },
1434 false => node(cal, 0),
1435 };
1436 Obj {
1437 root,
1438 zones: Zones::new(cal, Zone::Utc),
1439 index: OnceCell::new(),
1440 budget: Cell::new(RULE_BUDGET),
1441 }
1442 }
1443
1444 fn index(&self) -> &Index {
1445 self.index.get_or_init(|| {
1446 let mut by_key = HashMap::new();
1447 let mut ranges = Vec::new();
1448 for (i, n) in self.root.children.iter().enumerate() {
1449 if !is_scheduled(&n.c) {
1450 continue;
1451 }
1452 let key = self.key(&n.c);
1453 by_key.entry(key).or_insert(i);
1454 if let Some(k) = key.filter(|_| is_range(&n.c)) {
1455 ranges.push((k, i));
1456 }
1457 }
1458 ranges.sort_by_key(|(k, _)| *k);
1459 Index {
1460 by_key,
1461 ranges,
1462 in_series: RefCell::default(),
1463 }
1464 })
1465 }
1466
1467 fn children_mut(&mut self) -> &mut Vec<Node> {
1468 self.index.take();
1469 &mut self.root.children
1470 }
1471
1472 /// Appends a child and returns where it is.
1473 fn push(&mut self, n: Node) -> usize {
1474 let at = self.root.children.len();
1475 let key = is_scheduled(&n.c).then(|| self.key(&n.c));
1476 let range = is_range(&n.c);
1477 self.root.children.push(n);
1478 if let (Some(ix), Some(key)) = (self.index.get_mut(), key) {
1479 ix.by_key.entry(key).or_insert(at);
1480 if let Some(k) = key.filter(|_| range) {
1481 let p = ix.ranges.partition_point(|(r, _)| *r <= k);
1482 ix.ranges.insert(p, (k, at));
1483 }
1484 }
1485 at
1486 }
1487
1488 fn done(self) -> ICalendar {
1489 let mut components = Vec::new();
1490 flatten(&self.root, &mut components);
1491 ICalendar { components }
1492 }
1493
1494 fn comps(&self) -> impl Iterator<Item = &Node> {
1495 self.root.children.iter().filter(|n| is_scheduled(&n.c))
1496 }
1497
1498 fn comps_mut(&mut self) -> impl Iterator<Item = &mut Node> {
1499 self.children_mut()
1500 .iter_mut()
1501 .filter(|n| is_scheduled(&n.c))
1502 }
1503
1504 fn master(&self) -> Option<&Node> {
1505 self.find(None)
1506 }
1507
1508 fn find(&self, key: Option<i64>) -> Option<&Node> {
1509 Some(&self.root.children[self.position(key)?])
1510 }
1511
1512 fn position(&self, key: Option<i64>) -> Option<usize> {
1513 self.index().by_key.get(&key).copied()
1514 }
1515
1516 /// The RECURRENCE-ID as an instant; `None` for the master.
1517 fn key(&self, c: &ICalendarComponent) -> Option<i64> {
1518 self.instant(c.property(&ICalendarProperty::RecurrenceId)?)
1519 }
1520
1521 /// The first value of a date or date-time property, in UTC seconds.
1522 /// Dates count from midnight, whatever the zone.
1523 fn instant(&self, e: &ICalendarEntry) -> Option<i64> {
1524 let v = e.values.first()?.as_partial_date_time()?;
1525 self.at(v, e.tz_id())
1526 }
1527
1528 fn at(&self, v: &PartialDateTime, tzid: Option<&str>) -> Option<i64> {
1529 let dt = v.to_date_time()?;
1530 Some(match dt.offset {
1531 _ if v.hour.is_none() => dt.date_time.and_utc().timestamp(),
1532 Some(o) => dt.date_time.and_utc().timestamp() - i64::from(o.local_minus_utc()),
1533 None => self.zones.get(tzid).to_utc(dt.date_time).timestamp(),
1534 })
1535 }
1536
1537 /// Every value of a property, comparable across encodings: instants for
1538 /// dates and date-times, the text otherwise.
1539 fn times(&self, c: &ICalendarComponent, prop: &ICalendarProperty) -> HashSet<String> {
1540 c.properties(prop)
1541 .flat_map(|e| e.values.iter().map(move |v| self.value_key(e, v)))
1542 .collect()
1543 }
1544
1545 fn value_key(&self, e: &ICalendarEntry, v: &ICalendarValue) -> String {
1546 match v.as_partial_date_time() {
1547 Some(p) => match self.at(p, e.tz_id()) {
1548 Some(t) if p.hour.is_none() => format!("{t}d"),
1549 Some(t) => t.to_string(),
1550 None => format!("{v:?}"),
1551 },
1552 None => match v {
1553 ICalendarValue::RecurrenceRule(r) => format!("{:?}", canonical(r)),
1554 _ => format!("{v:?}"),
1555 },
1556 }
1557 }
1558
1559 fn organizer(&self) -> Option<String> {
1560 self.comps()
1561 .find_map(|c| address(c.c.property(&ICalendarProperty::Organizer)?))
1562 .map(str::to_string)
1563 }
1564
1565 /// Whether the server replies for the attendee (RFC 6638, 3.2.2).
1566 fn organizer_schedules(&self) -> bool {
1567 self.comps()
1568 .filter_map(|c| c.c.property(&ICalendarProperty::Organizer))
1569 .all(server_agent)
1570 }
1571
1572 /// Per child of `root`, the addresses it invites through the server,
1573 /// lowercase.
1574 fn invitations(&self) -> Vec<HashSet<String>> {
1575 self.root
1576 .children
1577 .iter()
1578 .map(|n| match is_scheduled(&n.c) {
1579 true => attendees(&n.c)
1580 .filter(|e| server_agent(e))
1581 .filter_map(address)
1582 .map(str::to_ascii_lowercase)
1583 .collect(),
1584 false => HashSet::new(),
1585 })
1586 .collect()
1587 }
1588
1589 /// What an attendee sees (RFC 6638, 3.2.6), given which children invite
1590 /// them. Overrides they are not in become EXDATEs. A THISANDFUTURE
1591 /// override they are not in ends their series.
1592 fn view(&self, invited: &[bool]) -> Option<Vec<Node>> {
1593 let overrides = || {
1594 self.root.children.iter().enumerate().filter(|(_, c)| {
1595 is_scheduled(&c.c) && c.c.has_property(&ICalendarProperty::RecurrenceId)
1596 })
1597 };
1598 let mut out = Vec::new();
1599 let master = self
1600 .position(None)
1601 .filter(|i| invited[*i])
1602 .map(|i| &self.root.children[i]);
1603 if let Some(m) = master {
1604 let mut m = m.clone();
1605 let mut end: Option<i64> = None;
1606 for (i, o) in overrides() {
1607 let Some(rid) = o.c.property(&ICalendarProperty::RecurrenceId) else {
1608 continue;
1609 };
1610 if invited[i] {
1611 continue;
1612 }
1613 if is_range(&o.c) {
1614 // ponytail: a later range that invites them again is lost too.
1615 // A stale range past the series' end cuts nothing.
1616 if let Some(t) = self.instant(rid).filter(|t| self.in_series(*t)) {
1617 end = Some(end.map_or(t, |e| e.min(t)));
1618 }
1619 continue;
1620 }
1621 m.c.entries.push(ICalendarEntry {
1622 name: ICalendarProperty::Exdate,
1623 params: without(rid.params.clone(), &ICalendarParameterName::Range),
1624 values: rid.values.clone(),
1625 });
1626 }
1627 let first =
1628 m.c.property(&ICalendarProperty::Dtstart)
1629 .and_then(|e| self.instant(e));
1630 match end {
1631 // The range starts with the first instance: none is left.
1632 Some(t) if first.is_some_and(|f| t <= f) => {}
1633 Some(t) => {
1634 self.end_series(&mut m.c, t);
1635 out.push(m);
1636 }
1637 None => out.push(m),
1638 }
1639 }
1640 out.extend(
1641 overrides()
1642 .filter(|(i, _)| invited[*i])
1643 .map(|(_, c)| c.clone()),
1644 );
1645 for n in &mut out {
1646 n.children
1647 .retain(|ch| ch.c.component_type != ICalendarComponentType::VAlarm);
1648 strip_scheduling_params(&mut n.c);
1649 }
1650 (!out.is_empty()).then_some(out)
1651 }
1652
1653 /// Whether override `c`, read through `obj`, is instance `key` of this
1654 /// series unchanged: its start, length and people, no rules of its own.
1655 fn plain(&self, obj: &Obj, c: &ICalendarComponent, key: i64, added: bool) -> bool {
1656 let Some(base) = self.base(key) else {
1657 return false;
1658 };
1659 let start = c
1660 .property(&ICalendarProperty::Dtstart)
1661 .and_then(|e| obj.instant(e));
1662 start == Some(self.moved(key))
1663 && [
1664 ICalendarProperty::Rrule,
1665 ICalendarProperty::Rdate,
1666 ICalendarProperty::Exrule,
1667 ]
1668 .iter()
1669 .all(|p| !c.has_property(p))
1670 && obj.end(c) == self.end_from(&base.c, self.moved(key))
1671 && organizer_of(c) == organizer_of(&base.c)
1672 && addresses(c) == addresses(&base.c)
1673 && (c.status() == base.c.status()
1674 || own_progress(c, &base.c)
1675 // Some clients mark the instance they answer as confirmed.
1676 || added
1677 && base.c.status().is_none()
1678 && c.status() == Some(&ICalendarStatus::Confirmed))
1679 }
1680
1681 /// Whether the master's own rules have an instance at `key`.
1682 fn in_series(&self, key: i64) -> bool {
1683 *self
1684 .index()
1685 .in_series
1686 .borrow_mut()
1687 .entry(key)
1688 .or_insert_with(|| self.expands_to(key))
1689 }
1690
1691 fn expands_to(&self, key: i64) -> bool {
1692 let (Some(m), Some(at)) = (self.master(), DateTime::from_timestamp(key, 0)) else {
1693 return false;
1694 };
1695 let window = at - TimeDelta::days(1)..at + TimeDelta::days(1);
1696 expand_in(
1697 &self.alone(m.c.clone()),
1698 &self.zones,
1699 window,
1700 MAX_OCCURRENCES,
1701 )
1702 .instances
1703 .iter()
1704 .any(|i| i.recurrence_id == Some(at))
1705 }
1706
1707 /// The DTSTART of `c` as an instant.
1708 fn start(&self, c: &ICalendarComponent) -> Option<DateTime<Utc>> {
1709 DateTime::from_timestamp(self.instant(c.property(&ICalendarProperty::Dtstart)?)?, 0)
1710 }
1711
1712 /// The starts the RRULE of `c` gives in `window`, without RDATE, EXDATE
1713 /// and EXRULE. `None` if the expansion was cut short.
1714 fn rule_starts(
1715 &self,
1716 mut c: ICalendarComponent,
1717 window: std::ops::Range<DateTime<Utc>>,
1718 ) -> Option<Vec<i64>> {
1719 c.entries.retain(|e| {
1720 !matches!(
1721 e.name,
1722 ICalendarProperty::Rdate
1723 | ICalendarProperty::Exdate
1724 | ICalendarProperty::Exrule
1725 | ICalendarProperty::RecurrenceId
1726 )
1727 });
1728 let left = self.budget.get();
1729 let e = expand_in(&self.alone(c), &self.zones, window, left);
1730 if e.truncated {
1731 self.budget.set(0);
1732 return None;
1733 }
1734 self.budget.set(left.saturating_sub(e.generated));
1735 let mut v: Vec<i64> = e
1736 .instances
1737 .iter()
1738 .map(|i| i.recurrence_id.unwrap_or(i.start).timestamp())
1739 .collect();
1740 v.sort_unstable();
1741 Some(v)
1742 }
1743
1744 /// How the instances `n` keeps compare with those `o` keeps, for rules of
1745 /// `c` that differ only in COUNT or UNTIL. `Greater` if `n` keeps more.
1746 /// Only COUNT, or the gap between two UNTILs, is expanded.
1747 fn compare_ends(
1748 &self,
1749 c: &ICalendarComponent,
1750 o: &ICalendarRecurrenceRule,
1751 n: &ICalendarRecurrenceRule,
1752 ) -> Option<Ordering> {
1753 let dtstart = c.property(&ICalendarProperty::Dtstart)?;
1754 let s = stamp(
1755 &self.zones,
1756 dtstart.values.first()?.as_partial_date_time()?,
1757 dtstart.tz_id(),
1758 )?;
1759 let end = |u: &PartialDateTime| Some(until_utc(u, &s)?.timestamp());
1760 let start = self.start(c)?;
1761 // The last start COUNT keeps and the one after it.
1762 let counted = |k: u32| {
1763 let mut r = o.clone();
1764 r.until = None;
1765 r.count = Some(k + 1);
1766 let v = self.rule_starts(with_rule(c, r), start..DateTime::<Utc>::MAX_UTC)?;
1767 Some((
1768 v.get((k as usize).checked_sub(1)?).copied(),
1769 v.get(k as usize).copied(),
1770 ))
1771 };
1772 // Whether COUNT keeps more (`Greater`) than an UNTIL at `u`.
1773 let count_vs_until = |k: u32, u: i64| {
1774 let (last, next) = counted(k)?;
1775 Some(if last.is_some_and(|l| l > u) {
1776 Ordering::Greater
1777 } else if next.is_some_and(|x| x <= u) {
1778 Ordering::Less
1779 } else {
1780 Ordering::Equal
1781 })
1782 };
1783 match (o.count, &o.until, n.count, &n.until) {
1784 (Some(a), None, Some(b), None) => Some(b.cmp(&a)),
1785 (None, Some(a), None, Some(b)) => {
1786 let (a, b) = (end(a)?, end(b)?);
1787 if a == b {
1788 return Some(Ordering::Equal);
1789 }
1790 let (lo, hi) = (a.min(b), a.max(b));
1791 let at = |t: i64| DateTime::from_timestamp(t + 1, 0);
1792 let mut r = o.clone();
1793 r.until = None;
1794 let between = self.rule_starts(with_rule(c, r), at(lo)?..at(hi)?);
1795 // The window also holds instances that only overlap it.
1796 Some(match between {
1797 Some(v) if v.iter().all(|t| *t <= lo || *t > hi) => Ordering::Equal,
1798 _ => b.cmp(&a),
1799 })
1800 }
1801 (Some(k), None, None, Some(u)) => count_vs_until(k, end(u)?).map(Ordering::reverse),
1802 (None, Some(u), Some(k), None) => count_vs_until(k, end(u)?),
1803 _ => None,
1804 }
1805 }
1806
1807 /// A calendar of `c` alone, to expand with this object's zones.
1808 fn alone(&self, c: ICalendarComponent) -> ICalendar {
1809 let root = Node {
1810 c: self.root.c.clone(),
1811 children: vec![Node {
1812 c,
1813 children: Vec::new(),
1814 }],
1815 };
1816 let mut components = Vec::new();
1817 flatten(&root, &mut components);
1818 ICalendar { components }
1819 }
1820
1821 /// What instance `key` copies: the THISANDFUTURE override that moves it,
1822 /// else the master.
1823 fn base(&self, key: i64) -> Option<&Node> {
1824 self.future(key).map(|(n, _)| n).or(self.master())
1825 }
1826
1827 /// When the component ends: DTSTART plus its length, else its DUE.
1828 fn end(&self, c: &ICalendarComponent) -> Option<i64> {
1829 match c.property(&ICalendarProperty::Dtstart) {
1830 Some(s) => self.end_from(c, self.instant(s)?),
1831 None => self.instant(c.property(&ICalendarProperty::Due)?),
1832 }
1833 }
1834
1835 /// When `c` would end if it started at `start`. The days and weeks of a
1836 /// DURATION count in wall-clock time (RFC 5545, 3.3.6).
1837 fn end_from(&self, c: &ICalendarComponent, start: i64) -> Option<i64> {
1838 let duration = c.property(&ICalendarProperty::Duration);
1839 let d = match duration.and_then(|e| e.values.first()) {
1840 Some(ICalendarValue::Duration(d))
1841 if !d.neg
1842 && !c.has_property(&ICalendarProperty::Dtend)
1843 && !c.has_property(&ICalendarProperty::Due) =>
1844 {
1845 d
1846 }
1847 _ => return Some(start + self.length(c)?),
1848 };
1849 let zone = self
1850 .zones
1851 .get(c.property(&ICalendarProperty::Dtstart)?.tz_id());
1852 let days = TimeDelta::try_days(i64::from(d.weeks) * 7 + i64::from(d.days))?;
1853 let local = add_local(zone.to_local(DateTime::from_timestamp(start, 0)?), days);
1854 let exact = i64::from(d.hours) * 3600 + i64::from(d.minutes) * 60 + i64::from(d.seconds);
1855 Some(zone.to_utc(local).timestamp() + exact)
1856 }
1857
1858 /// Which of `keys` are instances of the series in `cal`, from one
1859 /// expansion. `None` if the expansion was cut short and cannot tell.
1860 fn instances(&self, cal: &ICalendar, keys: &[i64]) -> Option<HashSet<i64>> {
1861 let starts: Vec<i64> = keys.iter().map(|k| self.moved(*k)).collect();
1862 let at = |t: i64| DateTime::from_timestamp(t, 0);
1863 // A day either side: an all-day shift is whole days, not 24 hours.
1864 let from = at(*starts.iter().min()?)? - TimeDelta::days(1);
1865 let to = at(*starts.iter().max()?)? + TimeDelta::days(1);
1866 let e = expand(cal, from..to, Zone::Utc);
1867 (!e.truncated).then(|| {
1868 e.instances
1869 .iter()
1870 .filter_map(|i| Some(i.recurrence_id?.timestamp()))
1871 .collect()
1872 })
1873 }
1874
1875 /// Seconds from DTSTART to DTEND or DUE, or the DURATION. Without
1876 /// either, a day for a date and nothing for a date-time (RFC 5545).
1877 fn length(&self, c: &ICalendarComponent) -> Option<i64> {
1878 let start = c.property(&ICalendarProperty::Dtstart)?;
1879 let instant = |p: &ICalendarProperty| c.property(p).and_then(|e| self.instant(e));
1880 match instant(&ICalendarProperty::Dtend).or_else(|| instant(&ICalendarProperty::Due)) {
1881 Some(end) => Some(end - self.instant(start)?),
1882 None => match c.property(&ICalendarProperty::Duration) {
1883 Some(d) => match d.values.first()? {
1884 ICalendarValue::Duration(d) => Some(d.as_seconds()),
1885 _ => None,
1886 },
1887 None => {
1888 let date = start.values.first()?.as_partial_date_time()?.hour.is_none();
1889 Some(if date { 86400 } else { 0 })
1890 }
1891 },
1892 }
1893 }
1894
1895 /// Ends the series of master `m` before instant `at`: UNTIL in the form
1896 /// RFC 5545 asks for, COUNT dropped, later RDATEs removed. `at` is an
1897 /// instance, so COUNT had not ended before it.
1898 fn end_series(&self, m: &mut ICalendarComponent, at: i64) {
1899 let Some(start) = m.property(&ICalendarProperty::Dtstart).cloned() else {
1900 return;
1901 };
1902 let Some(form) = start.values.first().and_then(|v| v.as_partial_date_time()) else {
1903 return;
1904 };
1905 let s = stamp(&self.zones, form, start.tz_id());
1906 // Obj reads floating times in UTC, so `at` is their wall-clock time.
1907 let until = if form.hour.is_none() {
1908 PartialDateTime::from_date_timestamp(at - 86400)
1909 } else if form.tz_hour.is_none() && start.tz_id().is_none() {
1910 PartialDateTime::from_naive_timestamp(at - 1)
1911 } else {
1912 PartialDateTime::from_utc_timestamp(at - 1)
1913 };
1914 for e in m.entries.iter_mut() {
1915 match e.name {
1916 ICalendarProperty::Rrule => {
1917 for v in &mut e.values {
1918 if let ICalendarValue::RecurrenceRule(r) = v
1919 && !r
1920 .until
1921 .as_ref()
1922 .and_then(|u| until_utc(u, s.as_ref()?))
1923 .is_some_and(|u| u.timestamp() < at)
1924 {
1925 r.until = Some(until.clone());
1926 r.count = None;
1927 }
1928 }
1929 }
1930 ICalendarProperty::Rdate => {
1931 let tz = e.tz_id().map(str::to_string);
1932 e.values.retain(|v| {
1933 v.as_partial_date_time()
1934 .and_then(|p| self.at(p, tz.as_deref()))
1935 .is_none_or(|t| t < at)
1936 });
1937 }
1938 _ => {}
1939 }
1940 }
1941 m.entries
1942 .retain(|e| e.name != ICalendarProperty::Rdate || !e.values.is_empty());
1943 }
1944
1945 /// A message: this object's VCALENDAR and time zones around `comps`.
1946 fn envelope(&self, comps: Vec<Node>, method: Method, now: DateTime<Utc>) -> ICalendar {
1947 let mut root = Node {
1948 c: self.root.c.clone(),
1949 children: self
1950 .root
1951 .children
1952 .iter()
1953 .filter(|n| n.c.component_type == ICalendarComponentType::VTimezone)
1954 .cloned()
1955 .collect(),
1956 };
1957 let method = match method {
1958 Method::Request => ICalendarMethod::Request,
1959 Method::Cancel => ICalendarMethod::Cancel,
1960 Method::Reply => ICalendarMethod::Reply,
1961 };
1962 set_prop(
1963 &mut root.c,
1964 ICalendarProperty::Method,
1965 ICalendarValue::Method(method),
1966 );
1967 let stamp = PartialDateTime::from_utc_timestamp(now.timestamp());
1968 for mut n in comps {
1969 set_prop(
1970 &mut n.c,
1971 ICalendarProperty::Dtstamp,
1972 ICalendarValue::PartialDateTime(Box::new(stamp.clone())),
1973 );
1974 root.children.push(n);
1975 }
1976 let mut components = Vec::new();
1977 flatten(&root, &mut components);
1978 ICalendar { components }
1979 }
1980
1981 /// A RECURRENCE-ID for the instance at `rid`, in the form of `start`.
1982 fn recurrence_id(
1983 &self,
1984 start: &ICalendarEntry,
1985 rid: DateTime<Utc>,
1986 floating: &Zone,
1987 ) -> Option<ICalendarEntry> {
1988 let v = start.values.first()?.as_partial_date_time()?;
1989 let local = |zone: &Zone| zone.to_local(rid).and_utc().timestamp();
1990 let value = if v.hour.is_none() {
1991 PartialDateTime::from_date_timestamp(local(floating))
1992 } else if v.tz_hour.is_some() {
1993 PartialDateTime::from_utc_timestamp(rid.timestamp())
1994 } else if start.tz_id().is_some() {
1995 PartialDateTime::from_naive_timestamp(local(&self.zones.get(start.tz_id())))
1996 } else {
1997 PartialDateTime::from_naive_timestamp(local(floating))
1998 };
1999 Some(ICalendarEntry {
2000 name: ICalendarProperty::RecurrenceId,
2001 params: without(start.params.clone(), &ICalendarParameterName::Range),
2002 values: vec![ICalendarValue::PartialDateTime(Box::new(value))],
2003 })
2004 }
2005
2006 /// The latest THISANDFUTURE override at or before instance `key`, and
2007 /// its key.
2008 fn future(&self, key: i64) -> Option<(&Node, i64)> {
2009 let ranges = &self.index().ranges;
2010 let (k, at) = ranges[..ranges.partition_point(|(k, _)| *k <= key)].last()?;
2011 Some((&self.root.children[*at], *k))
2012 }
2013
2014 /// Where instance `key` starts once a THISANDFUTURE override moves it.
2015 /// The move is wall-clock time in the zone of the series, as in expand.
2016 fn moved(&self, key: i64) -> i64 {
2017 let shifted = || {
2018 let (n, k) = self.future(key)?;
2019 let dtstart = n.c.property(&ICalendarProperty::Dtstart)?;
2020 let start = self.instant(dtstart)?;
2021 // A copy without its master still has the override's zone.
2022 let series = self
2023 .master()
2024 .and_then(|m| m.c.property(&ICalendarProperty::Dtstart));
2025 let zone = self.zones.get(series.unwrap_or(dtstart).tz_id());
2026 let local = |t: i64| Some(zone.to_local(DateTime::from_timestamp(t, 0)?));
2027 let shift = local(start)? - local(k)?;
2028 Some(zone.to_utc(add_local(local(key)?, shift)).timestamp())
2029 };
2030 shifted().unwrap_or(key)
2031 }
2032
2033 /// Whether the series in `cal` has an instance at `rid`, whose key is
2034 /// `key`.
2035 fn occurs(&self, cal: &ICalendar, rid: DateTime<Utc>, key: i64, floating: &Zone) -> bool {
2036 let Some(at) = DateTime::from_timestamp(self.moved(key), 0) else {
2037 return false;
2038 };
2039 // A day either side: an all-day shift is whole days, not 24 hours.
2040 let window = at - TimeDelta::days(1)..at + TimeDelta::days(1);
2041 expand(cal, window, floating.clone())
2042 .instances
2043 .iter()
2044 .any(|i| i.recurrence_id == Some(rid))
2045 }
2046
2047 /// An override for the instance at `rid`, from the master or from the
2048 /// THISANDFUTURE override that moves it.
2049 fn single(&self, rid: DateTime<Utc>, floating: &Zone) -> Option<Node> {
2050 let master = self.master()?;
2051 let form = master.c.property(&ICalendarProperty::Dtstart)?;
2052 let id = self.recurrence_id(form, rid, floating)?;
2053 let key = self.instant(&id)?;
2054 let Some((base, _)) = self.future(key) else {
2055 return Some(self.instance(master, &id, &id));
2056 };
2057 let at = self.moved(key);
2058 let start = match form.values.first()?.as_partial_date_time()?.hour {
2059 None => ICalendarEntry {
2060 values: vec![ICalendarValue::PartialDateTime(Box::new(
2061 PartialDateTime::from_date_timestamp(at),
2062 ))],
2063 ..id.clone()
2064 },
2065 Some(_) => self.recurrence_id(form, DateTime::from_timestamp(at, 0)?, floating)?,
2066 };
2067 Some(self.instance(base, &id, &start))
2068 }
2069
2070 /// Narrows the THISANDFUTURE override at instance `rid` (key `key`) to
2071 /// that instance. A copy of it carries the range on from the next instance
2072 /// without an override of its own, found by expanding `cal`.
2073 fn narrow(&mut self, cal: &ICalendar, rid: DateTime<Utc>, key: i64, floating: &Zone) {
2074 let Some(at) = self.position(Some(key)) else {
2075 return;
2076 };
2077 let Some(range) = self.root.children[at]
2078 .c
2079 .property(&ICalendarProperty::RecurrenceId)
2080 .and_then(|e| e.parameter(&ICalendarParameterName::Range))
2081 .cloned()
2082 else {
2083 return;
2084 };
2085 let later = self
2086 .comps()
2087 .filter(|c| is_range(&c.c))
2088 .filter_map(|c| self.key(&c.c))
2089 .filter(|k| *k > key)
2090 .min();
2091 let next = self
2092 .next_free(cal, rid, key, floating)
2093 .filter(|(_, k)| later.is_none_or(|l| *k < l))
2094 .and_then(|(r, _)| self.single(r, floating));
2095 if let Some(mut n) = next {
2096 if let Some(e) =
2097 n.c.entries
2098 .iter_mut()
2099 .find(|e| e.name == ICalendarProperty::RecurrenceId)
2100 {
2101 set_param(e, ICalendarParameterName::Range, range);
2102 }
2103 self.push(n);
2104 }
2105 if let Some(e) = self.children_mut()[at]
2106 .c
2107 .entries
2108 .iter_mut()
2109 .find(|e| e.name == ICalendarProperty::RecurrenceId)
2110 {
2111 remove_param(e, &ICalendarParameterName::Range);
2112 }
2113 }
2114
2115 /// The first instance after `rid` that has no override, and its key.
2116 // ponytail: looks ten years ahead; a range beyond that falls back to the master.
2117 fn next_free(
2118 &self,
2119 cal: &ICalendar,
2120 rid: DateTime<Utc>,
2121 key: i64,
2122 floating: &Zone,
2123 ) -> Option<(DateTime<Utc>, i64)> {
2124 let form = self.master()?.c.property(&ICalendarProperty::Dtstart)?;
2125 let from = DateTime::from_timestamp(self.moved(key), 0)?;
2126 [1, 32, 400, 3700].into_iter().find_map(|days| {
2127 expand(cal, from..from + TimeDelta::days(days), floating.clone())
2128 .instances
2129 .iter()
2130 .filter_map(|i| i.recurrence_id)
2131 .filter(|r| *r > rid)
2132 .filter_map(|r| Some((r, self.instant(&self.recurrence_id(form, r, floating)?)?)))
2133 .filter(|(_, k)| self.find(Some(*k)).is_none())
2134 .min()
2135 })
2136 }
2137
2138 /// An override of `base` for the instance `rid` starting at `start`, so
2139 /// it can hold a status of its own. Its length becomes a DURATION.
2140 fn instance(&self, base: &Node, rid: &ICalendarEntry, start: &ICalendarEntry) -> Node {
2141 let mut n = base.clone();
2142 let begin =
2143 n.c.property(&ICalendarProperty::Dtstart)
2144 .and_then(|e| self.instant(e));
2145 let end_prop = match n.c.component_type {
2146 ICalendarComponentType::VTodo => ICalendarProperty::Due,
2147 _ => ICalendarProperty::Dtend,
2148 };
2149 let end = n.c.property(&end_prop).and_then(|e| self.instant(e));
2150 if let (Some(s), Some(e)) = (begin, end) {
2151 n.c.entries.retain(|x| x.name != end_prop);
2152 n.c.entries.push(ICalendarEntry {
2153 name: ICalendarProperty::Duration,
2154 params: Vec::new(),
2155 values: vec![ICalendarValue::Duration(ICalendarDuration::from_seconds(
2156 (e - s).max(0),
2157 ))],
2158 });
2159 }
2160 n.c.entries.retain(|e| {
2161 !matches!(
2162 e.name,
2163 ICalendarProperty::Rrule
2164 | ICalendarProperty::Rdate
2165 | ICalendarProperty::Exdate
2166 | ICalendarProperty::Exrule
2167 | ICalendarProperty::Dtstart
2168 | ICalendarProperty::RecurrenceId
2169 )
2170 });
2171 n.c.entries.push(ICalendarEntry {
2172 name: ICalendarProperty::Dtstart,
2173 params: without(start.params.clone(), &ICalendarParameterName::Range),
2174 values: start.values.clone(),
2175 });
2176 n.c.entries.push(ICalendarEntry {
2177 name: ICalendarProperty::RecurrenceId,
2178 params: without(rid.params.clone(), &ICalendarParameterName::Range),
2179 values: rid.values.clone(),
2180 });
2181 n
2182 }
2183}
2184
2185// ---------------------------------------------------------------------------
2186// Properties and parameters
2187// ---------------------------------------------------------------------------
2188
2189fn is_scheduled(c: &ICalendarComponent) -> bool {
2190 matches!(
2191 c.component_type,
2192 ICalendarComponentType::VEvent
2193 | ICalendarComponentType::VTodo
2194 | ICalendarComponentType::VJournal
2195 )
2196}
2197
2198fn address(e: &ICalendarEntry) -> Option<&str> {
2199 e.values.first()?.as_text().map(str::trim)
2200}
2201
2202fn attendees(c: &ICalendarComponent) -> impl Iterator<Item = &ICalendarEntry> {
2203 c.properties(&ICalendarProperty::Attendee)
2204}
2205
2206/// A task's progress is the assignee's to set per instance, unless the
2207/// organizer cancelled it.
2208fn own_progress(c: &ICalendarComponent, base: &ICalendarComponent) -> bool {
2209 use ICalendarStatus::*;
2210 c.component_type == ICalendarComponentType::VTodo
2211 && matches!(c.status(), None | Some(NeedsAction | InProcess | Completed))
2212 && base.status() != Some(&Cancelled)
2213}
2214
2215fn organizer_of(c: &ICalendarComponent) -> Option<String> {
2216 c.property(&ICalendarProperty::Organizer)
2217 .and_then(address)
2218 .map(str::to_ascii_lowercase)
2219}
2220
2221/// Whether `c` repeats without end.
2222pub(crate) fn endless(c: &ICalendarComponent) -> bool {
2223 c.properties(&ICalendarProperty::Rrule).any(|e| {
2224 matches!(e.values.first(), Some(ICalendarValue::RecurrenceRule(r))
2225 if r.count.is_none() && r.until.is_none())
2226 })
2227}
2228
2229fn addresses(c: &ICalendarComponent) -> Vec<String> {
2230 let mut v: Vec<String> = attendees(c)
2231 .filter_map(address)
2232 .map(str::to_ascii_lowercase)
2233 .collect();
2234 v.sort();
2235 v.dedup();
2236 v
2237}
2238
2239fn is_range(c: &ICalendarComponent) -> bool {
2240 c.property(&ICalendarProperty::RecurrenceId)
2241 .is_some_and(|e| e.parameter(&ICalendarParameterName::Range).is_some())
2242}
2243
2244fn client_scheduled(c: &ICalendarComponent, a: &str) -> bool {
2245 attendees(c).any(|e| address(e).is_some_and(|x| x.eq_ignore_ascii_case(a)) && !server_agent(e))
2246}
2247
2248fn same_attendee<'a>(c: &'a ICalendarComponent, e: &ICalendarEntry) -> Option<&'a ICalendarEntry> {
2249 let a = address(e)?;
2250 attendees(c).find(|x| address(x).is_some_and(|b| b.eq_ignore_ascii_case(a)))
2251}
2252
2253fn own_partstat(c: &ICalendarComponent, me: Is) -> Option<String> {
2254 attendees(c).find(|e| address(e).is_some_and(me)).map(|e| {
2255 param(e, &ICalendarParameterName::Partstat)
2256 .unwrap_or("NEEDS-ACTION")
2257 .to_ascii_uppercase()
2258 })
2259}
2260
2261/// `SCHEDULE-AGENT` absent or `SERVER`. Unknown values count as `NONE`.
2262fn server_agent(e: &ICalendarEntry) -> bool {
2263 param(e, &ICalendarParameterName::ScheduleAgent)
2264 .is_none_or(|v| v.eq_ignore_ascii_case("SERVER"))
2265}
2266
2267fn sequence(c: &ICalendarComponent) -> i64 {
2268 c.property(&ICalendarProperty::Sequence)
2269 .and_then(|e| e.values.first()?.as_integer())
2270 .unwrap_or(0)
2271}
2272
2273fn param<'a>(e: &'a ICalendarEntry, name: &ICalendarParameterName) -> Option<&'a str> {
2274 e.parameter(name)?.as_text()
2275}
2276
2277fn set_param(e: &mut ICalendarEntry, name: ICalendarParameterName, value: ICalendarParameterValue) {
2278 remove_param(e, &name);
2279 e.params.push(ICalendarParameter::new(name, value));
2280}
2281
2282fn remove_param(e: &mut ICalendarEntry, name: &ICalendarParameterName) {
2283 e.params.retain(|p| &p.name != name);
2284}
2285
2286fn without(
2287 mut params: Vec<ICalendarParameter>,
2288 name: &ICalendarParameterName,
2289) -> Vec<ICalendarParameter> {
2290 params.retain(|p| &p.name != name);
2291 params
2292}
2293
2294fn set_prop(c: &mut ICalendarComponent, name: ICalendarProperty, value: ICalendarValue) {
2295 c.entries.retain(|e| e.name != name);
2296 c.entries.push(ICalendarEntry {
2297 name,
2298 params: Vec::new(),
2299 values: vec![value],
2300 });
2301}
2302
2303/// Messages carry none of the scheduling parameters (RFC 6638, 7).
2304fn strip_scheduling_params(c: &mut ICalendarComponent) {
2305 for e in c.entries.iter_mut().filter(|e| {
2306 matches!(
2307 e.name,
2308 ICalendarProperty::Attendee | ICalendarProperty::Organizer
2309 )
2310 }) {
2311 for p in [
2312 ICalendarParameterName::ScheduleAgent,
2313 ICalendarParameterName::ScheduleStatus,
2314 ICalendarParameterName::ScheduleForceSend,
2315 ] {
2316 remove_param(e, &p);
2317 }
2318 }
2319}
2320
2321fn text(s: &str) -> ICalendarParameterValue {
2322 ICalendarParameterValue::Text(s.to_string())
2323}
2324
2325fn partstat(p: ICalendarParticipationStatus) -> ICalendarParameterValue {
2326 ICalendarParameterValue::Partstat(p)
2327}
2328
2329/// Makes an object forget a principal that is about to be deleted, so that
2330/// a later principal of the same name is not taken for it. Every ORGANIZER
2331/// and ATTENDEE that `gone` names gets the address `tombstone`, and such an
2332/// ATTENDEE gets SCHEDULE-STATUS 3.7. A component the gone principal
2333/// organized is cancelled. All other lines keep their bytes. `None` when
2334/// nothing names the principal.
2335pub fn forget(text: &str, gone: Is, tombstone: &str) -> Option<String> {
2336 let mut out: Vec<String> = Vec::new();
2337 // Per open component: name, index of its BEGIN line in `out`, index of
2338 // its STATUS line, whether the gone principal organized it.
2339 let mut open: Vec<(String, usize, Option<usize>, bool)> = Vec::new();
2340 let mut changed = false;
2341 for raw in logical_lines(text) {
2342 let eol = match raw.ends_with('\n') {
2343 true if raw.ends_with("\r\n") => "\r\n",
2344 true => "\n",
2345 false => "\r\n",
2346 };
2347 let line = unfold(raw);
2348 let n = name(&line);
2349 match n.as_str() {
2350 "BEGIN" => open.push((
2351 value(&line).trim().to_ascii_uppercase(),
2352 out.len(),
2353 None,
2354 false,
2355 )),
2356 "END" => {
2357 if let Some((comp, begin, status, true)) = open.pop()
2358 && ["VEVENT", "VTODO", "VJOURNAL"].contains(&comp.as_str())
2359 {
2360 let cancelled = format!("STATUS:CANCELLED{eol}");
2361 match status {
2362 Some(i) => out[i] = cancelled,
2363 None => out.insert(begin + 1, cancelled),
2364 }
2365 }
2366 }
2367 "STATUS" => {
2368 if let Some(top) = open.last_mut() {
2369 top.2 = Some(out.len());
2370 }
2371 }
2372 "ORGANIZER" | "ATTENDEE" if gone(value(&line).trim()) => {
2373 let attendee = n == "ATTENDEE";
2374 let mut new = n.clone();
2375 for p in param_parts(&line) {
2376 if !(attendee
2377 && p.trim()
2378 .to_ascii_uppercase()
2379 .starts_with("SCHEDULE-STATUS="))
2380 {
2381 new.push(';');
2382 new.push_str(p);
2383 }
2384 }
2385 if attendee {
2386 new.push_str(";SCHEDULE-STATUS=3.7");
2387 } else if let Some(top) = open.last_mut() {
2388 top.3 = true;
2389 }
2390 new.push(':');
2391 new.push_str(tombstone);
2392 out.push(fold(&new, eol));
2393 changed = true;
2394 continue;
2395 }
2396 _ => {}
2397 }
2398 out.push(raw.to_string());
2399 }
2400 changed.then(|| out.concat())
2401}
2402