//! Validation of the calendar and address objects clients PUT. use calcard::icalendar::{ICalendarComponentType, ICalendarProperty}; use calcard::{Entry, Parser}; use xmltree::Element; use crate::xml::{CALDAV, CARDDAV, el}; /// Why a PUT body is refused, as the precondition the RFCs name. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Invalid { /// Not parseable as iCalendar. CalendarData, /// Parseable, but not one CalDAV object: several UIDs, mixed component /// types, a METHOD, or no component at all. CalendarResource, /// A component type the collection does not take. CalendarComponent, /// Not parseable as one vCard. AddressData, } impl Invalid { pub fn condition(self) -> Element { match self { Invalid::CalendarData => el(CALDAV, "valid-calendar-data"), Invalid::CalendarResource => el(CALDAV, "valid-calendar-object-resource"), Invalid::CalendarComponent => el(CALDAV, "supported-calendar-component"), Invalid::AddressData => el(CARDDAV, "valid-address-data"), } } } /// What the store needs to know about a valid calendar object. #[derive(Debug, PartialEq, Eq)] pub struct CalendarObject { pub uid: String, /// `VEVENT`, `VTODO` or `VJOURNAL`. pub component: &'static str, } /// Checks a calendar object resource (RFC 4791, 4.1). `supported` lists the /// component types the collection takes. pub fn calendar(body: &[u8], supported: &[&str]) -> Result { let text = std::str::from_utf8(body).map_err(|_| Invalid::CalendarData)?; let mut parser = Parser::new(text); let Entry::ICalendar(cal) = parser.entry() else { return Err(Invalid::CalendarData); }; if !matches!(parser.entry(), Entry::Eof) { return Err(Invalid::CalendarResource); } let root = cal.components.first().ok_or(Invalid::CalendarData)?; if root.component_type != ICalendarComponentType::VCalendar { return Err(Invalid::CalendarData); } if root.has_property(&ICalendarProperty::Method) { return Err(Invalid::CalendarResource); } let mut found: Option = None; for c in root .component_ids .iter() .filter_map(|&id| cal.components.get(id as usize)) { let component = match c.component_type { ICalendarComponentType::VTimezone => continue, ICalendarComponentType::VEvent => "VEVENT", ICalendarComponentType::VTodo => "VTODO", ICalendarComponentType::VJournal => "VJOURNAL", _ => return Err(Invalid::CalendarComponent), }; let uid = c.uid().ok_or(Invalid::CalendarResource)?; match &found { Some(f) if f.uid != uid || f.component != component => { return Err(Invalid::CalendarResource); } Some(_) => {} None => { found = Some(CalendarObject { uid: uid.to_string(), component, }) } } } let found = found.ok_or(Invalid::CalendarResource)?; if !supported.contains(&found.component) { return Err(Invalid::CalendarComponent); } Ok(found) } /// Checks an address object resource (RFC 6352, 5.1) and returns its UID. A /// card without one is accepted: several clients omit it. pub fn vcard(body: &[u8]) -> Result, Invalid> { let text = std::str::from_utf8(body).map_err(|_| Invalid::AddressData)?; let mut parser = Parser::new(text); let Entry::VCard(card) = parser.entry() else { return Err(Invalid::AddressData); }; if !matches!(parser.entry(), Entry::Eof) { return Err(Invalid::AddressData); } Ok(card.uid().map(str::to_string)) }