object.rs
| 1 | //! Validation of the calendar and address objects clients PUT. |
| 2 | |
| 3 | use calcard::icalendar::{ICalendarComponentType, ICalendarProperty}; |
| 4 | use calcard::{Entry, Parser}; |
| 5 | use xmltree::Element; |
| 6 | |
| 7 | use 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)] |
| 11 | pub 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 | |
| 23 | impl 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)] |
| 36 | pub 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. |
| 44 | pub 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. |
| 96 | pub 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 |