object.rs
⎇
Raw
1//! Validation of the calendar and address objects clients PUT.
2
3use calcard::icalendar::{ICalendarComponentType, ICalendarProperty};
4use calcard::{Entry, Parser};
5use xmltree::Element;
6
7use crate::xml::{CALDAV, CARDDAV, el};
8
9/// Why a PUT body is refused, as the precondition the RFCs name.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Invalid {
12 /// Not parseable as iCalendar.
13 CalendarData,
14 /// Parseable, but not one CalDAV object: several UIDs, mixed component
15 /// types, a METHOD, or no component at all.
16 CalendarResource,
17 /// A component type the collection does not take.
18 CalendarComponent,
19 /// Not parseable as one vCard.
20 AddressData,
21}
22
23impl Invalid {
24 pub fn condition(self) -> Element {
25 match self {
26 Invalid::CalendarData => el(CALDAV, "valid-calendar-data"),
27 Invalid::CalendarResource => el(CALDAV, "valid-calendar-object-resource"),
28 Invalid::CalendarComponent => el(CALDAV, "supported-calendar-component"),
29 Invalid::AddressData => el(CARDDAV, "valid-address-data"),
30 }
31 }
32}
33
34/// What the store needs to know about a valid calendar object.
35#[derive(Debug, PartialEq, Eq)]
36pub struct CalendarObject {
37 pub uid: String,
38 /// `VEVENT`, `VTODO` or `VJOURNAL`.
39 pub component: &'static str,
40}
41
42/// Checks a calendar object resource (RFC 4791, 4.1). `supported` lists the
43/// component types the collection takes.
44pub fn calendar(body: &[u8], supported: &[&str]) -> Result<CalendarObject, Invalid> {
45 let text = std::str::from_utf8(body).map_err(|_| Invalid::CalendarData)?;
46 let mut parser = Parser::new(text);
47 let Entry::ICalendar(cal) = parser.entry() else {
48 return Err(Invalid::CalendarData);
49 };
50 if !matches!(parser.entry(), Entry::Eof) {
51 return Err(Invalid::CalendarResource);
52 }
53 let root = cal.components.first().ok_or(Invalid::CalendarData)?;
54 if root.component_type != ICalendarComponentType::VCalendar {
55 return Err(Invalid::CalendarData);
56 }
57 if root.has_property(&ICalendarProperty::Method) {
58 return Err(Invalid::CalendarResource);
59 }
60 let mut found: Option<CalendarObject> = None;
61 for c in root
62 .component_ids
63 .iter()
64 .filter_map(|&id| cal.components.get(id as usize))
65 {
66 let component = match c.component_type {
67 ICalendarComponentType::VTimezone => continue,
68 ICalendarComponentType::VEvent => "VEVENT",
69 ICalendarComponentType::VTodo => "VTODO",
70 ICalendarComponentType::VJournal => "VJOURNAL",
71 _ => return Err(Invalid::CalendarComponent),
72 };
73 let uid = c.uid().ok_or(Invalid::CalendarResource)?;
74 match &found {
75 Some(f) if f.uid != uid || f.component != component => {
76 return Err(Invalid::CalendarResource);
77 }
78 Some(_) => {}
79 None => {
80 found = Some(CalendarObject {
81 uid: uid.to_string(),
82 component,
83 })
84 }
85 }
86 }
87 let found = found.ok_or(Invalid::CalendarResource)?;
88 if !supported.contains(&found.component) {
89 return Err(Invalid::CalendarComponent);
90 }
91 Ok(found)
92}
93
94/// Checks an address object resource (RFC 6352, 5.1) and returns its UID. A
95/// card without one is accepted: several clients omit it.
96pub fn vcard(body: &[u8]) -> Result<Option<String>, Invalid> {
97 let text = std::str::from_utf8(body).map_err(|_| Invalid::AddressData)?;
98 let mut parser = Parser::new(text);
99 let Entry::VCard(card) = parser.entry() else {
100 return Err(Invalid::AddressData);
101 };
102 if !matches!(parser.entry(), Entry::Eof) {
103 return Err(Invalid::AddressData);
104 }
105 Ok(card.uid().map(str::to_string))
106}
107