diff options
Diffstat (limited to 'src/dav')
-rw-r--r-- | src/dav/acltypes.rs | 4 | ||||
-rw-r--r-- | src/dav/caldecoder.rs | 33 | ||||
-rw-r--r-- | src/dav/calencoder.rs | 886 | ||||
-rw-r--r-- | src/dav/caltypes.rs | 1440 | ||||
-rw-r--r-- | src/dav/decoder.rs | 948 | ||||
-rw-r--r-- | src/dav/encoder.rs | 1117 | ||||
-rw-r--r-- | src/dav/error.rs | 42 | ||||
-rw-r--r-- | src/dav/mod.rs | 167 | ||||
-rw-r--r-- | src/dav/realization.rs | 42 | ||||
-rw-r--r-- | src/dav/types.rs | 950 | ||||
-rw-r--r-- | src/dav/versioningtypes.rs | 3 | ||||
-rw-r--r-- | src/dav/xml.rs | 273 |
12 files changed, 0 insertions, 5905 deletions
diff --git a/src/dav/acltypes.rs b/src/dav/acltypes.rs deleted file mode 100644 index f356813..0000000 --- a/src/dav/acltypes.rs +++ /dev/null @@ -1,4 +0,0 @@ -//@FIXME required for a full DAV implementation -// See section 6. of the CalDAV RFC -// It seems mainly required for free-busy that I will not implement now. -// It can also be used for discovering main calendar, not sure it is used. diff --git a/src/dav/caldecoder.rs b/src/dav/caldecoder.rs deleted file mode 100644 index 5f40c4b..0000000 --- a/src/dav/caldecoder.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::types as dav; -use super::caltypes::*; -use super::xml; -use super::error; - -// ---- ROOT ELEMENTS --- - -// ---- EXTENSIONS --- -impl xml::QRead<Violation> for Violation { - async fn qread(xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> { - unreachable!(); - } -} - -impl xml::QRead<Property> for Property { - async fn qread(xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> { - unreachable!(); - } -} - -impl xml::QRead<PropertyRequest> for PropertyRequest { - async fn qread(xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> { - unreachable!(); - } -} - -impl xml::QRead<ResourceType> for ResourceType { - async fn qread(xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> { - unreachable!(); - } -} - -// ---- INNER XML ---- diff --git a/src/dav/calencoder.rs b/src/dav/calencoder.rs deleted file mode 100644 index 58b88c7..0000000 --- a/src/dav/calencoder.rs +++ /dev/null @@ -1,886 +0,0 @@ -use quick_xml::Error as QError; -use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; -use quick_xml::name::PrefixDeclaration; -use tokio::io::AsyncWrite; - -use super::caltypes::*; -use super::xml::{Node, QWrite, IWrite, Writer}; -use super::types::Extension; - -const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ"; - -// ==================== Calendar Types Serialization ========================= - -// -------------------- MKCALENDAR METHOD ------------------------------------ -impl<E: Extension> QWrite for MkCalendar<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("mkcalendar"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension, N: Node<N>> QWrite for MkCalendarResponse<E,N> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("mkcalendar-response"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for propstat in self.0.iter() { - propstat.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -// ----------------------- REPORT METHOD ------------------------------------- - -impl<E: Extension> QWrite for CalendarQuery<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("calendar-query"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - if let Some(selector) = &self.selector { - selector.qwrite(xml).await?; - } - self.filter.qwrite(xml).await?; - if let Some(tz) = &self.timezone { - tz.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension> QWrite for CalendarMultiget<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("calendar-multiget"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - if let Some(selector) = &self.selector { - selector.qwrite(xml).await?; - } - for href in self.href.iter() { - href.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for FreeBusyQuery { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("free-busy-query"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -// -------------------------- DAV::prop -------------------------------------- -impl QWrite for PropertyRequest { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut atom = async |c| { - let empty_tag = xml.create_cal_element(c); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }; - - match self { - Self::CalendarDescription => atom("calendar-description").await, - Self::CalendarTimezone => atom("calendar-timezone").await, - Self::SupportedCalendarComponentSet => atom("supported-calendar-component-set").await, - Self::SupportedCalendarData => atom("supported-calendar-data").await, - Self::MaxResourceSize => atom("max-resource-size").await, - Self::MinDateTime => atom("min-date-time").await, - Self::MaxDateTime => atom("max-date-time").await, - Self::MaxInstances => atom("max-instances").await, - Self::MaxAttendeesPerInstance => atom("max-attendees-per-instance").await, - Self::SupportedCollationSet => atom("supported-collation-set").await, - Self::CalendarData(req) => req.qwrite(xml).await, - } - } -} -impl QWrite for Property { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::CalendarDescription { lang, text } => { - let mut start = xml.create_cal_element("calendar-description"); - if let Some(the_lang) = lang { - start.push_attribute(("xml:lang", the_lang.as_str())); - } - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(text))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::CalendarTimezone(payload) => { - let start = xml.create_cal_element("calendar-timezone"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(payload))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::SupportedCalendarComponentSet(many_comp) => { - let start = xml.create_cal_element("supported-calendar-component-set"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for comp in many_comp.iter() { - comp.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Self::SupportedCalendarData(many_mime) => { - let start = xml.create_cal_element("supported-calendar-data"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for mime in many_mime.iter() { - mime.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Self::MaxResourceSize(bytes) => { - let start = xml.create_cal_element("max-resource-size"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(bytes.to_string().as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::MinDateTime(dt) => { - let start = xml.create_cal_element("min-date-time"); - let end = start.to_end(); - - let dtstr = format!("{}", dt.format(ICAL_DATETIME_FMT)); - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(dtstr.as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::MaxDateTime(dt) => { - let start = xml.create_cal_element("max-date-time"); - let end = start.to_end(); - - let dtstr = format!("{}", dt.format(ICAL_DATETIME_FMT)); - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(dtstr.as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::MaxInstances(count) => { - let start = xml.create_cal_element("max-instances"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(count.to_string().as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::MaxAttendeesPerInstance(count) => { - let start = xml.create_cal_element("max-attendees-per-instance"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(count.to_string().as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::SupportedCollationSet(many_collations) => { - let start = xml.create_cal_element("supported-collation-set"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for collation in many_collations.iter() { - collation.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Self::CalendarData(inner) => inner.qwrite(xml).await, - } - } -} - -// ---------------------- DAV::resourcetype ---------------------------------- -impl QWrite for ResourceType { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Calendar => { - let empty_tag = xml.create_dav_element("calendar"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - } - } -} - -// --------------------------- DAV::error ------------------------------------ -impl QWrite for Violation { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut atom = async |c| { - let empty_tag = xml.create_cal_element(c); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }; - - match self { - //@FIXME - // DAV elements, should not be here but in RFC3744 on ACLs - // (we do not use atom as this error is in the DAV namespace, not the caldav one) - Self::NeedPrivileges => { - let empty_tag = xml.create_dav_element("need-privileges"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - - // Regular CalDAV errors - Self::ResourceMustBeNull => atom("resource-must-be-null").await, - Self::CalendarCollectionLocationOk => atom("calendar-collection-location-ok").await, - Self::ValidCalendarData => atom("valid-calendar-data").await, - Self::InitializeCalendarCollection => atom("initialize-calendar-collection").await, - Self::SupportedCalendarData => atom("supported-calendar-data").await, - Self::ValidCalendarObjectResource => atom("valid-calendar-object-resource").await, - Self::SupportedCalendarComponent => atom("supported-calendar-component").await, - Self::NoUidConflict(href) => { - let start = xml.create_cal_element("no-uid-conflict"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - href.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - }, - Self::MaxResourceSize => atom("max-resource-size").await, - Self::MinDateTime => atom("min-date-time").await, - Self::MaxDateTime => atom("max-date-time").await, - Self::MaxInstances => atom("max-instances").await, - Self::MaxAttendeesPerInstance => atom("max-attendees-per-instance").await, - Self::ValidFilter => atom("valid-filter").await, - Self::SupportedFilter { comp, prop, param } => { - let start = xml.create_cal_element("supported-filter"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for comp_item in comp.iter() { - comp_item.qwrite(xml).await?; - } - for prop_item in prop.iter() { - prop_item.qwrite(xml).await?; - } - for param_item in param.iter() { - param_item.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Self::NumberOfMatchesWithinLimits => atom("number-of-matches-within-limits").await, - } - } -} - - -// ---------------------------- Inner XML ------------------------------------ -impl QWrite for SupportedCollation { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_cal_element("supported-collation"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - - } -} - -impl QWrite for Collation { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let col = match self { - Self::AsciiCaseMap => "i;ascii-casemap", - Self::Octet => "i;octet", - Self::Unknown(v) => v.as_str(), - }; - - xml.q.write_event_async(Event::Text(BytesText::new(col))).await - } -} - -impl QWrite for CalendarDataPayload { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("calendar-data"); - if let Some(mime) = &self.mime { - start.push_attribute(("content-type", mime.content_type.as_str())); - start.push_attribute(("version", mime.version.as_str())); - } - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(self.payload.as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for CalendarDataRequest { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("calendar-data"); - if let Some(mime) = &self.mime { - start.push_attribute(("content-type", mime.content_type.as_str())); - start.push_attribute(("version", mime.version.as_str())); - } - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - if let Some(comp) = &self.comp { - comp.qwrite(xml).await?; - } - if let Some(recurrence) = &self.recurrence { - recurrence.qwrite(xml).await?; - } - if let Some(freebusy) = &self.limit_freebusy_set { - freebusy.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for CalendarDataEmpty { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("calendar-data"); - if let Some(mime) = &self.0 { - empty.push_attribute(("content-type", mime.content_type.as_str())); - empty.push_attribute(("version", mime.version.as_str())); - } - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl QWrite for Comp { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("comp"); - start.push_attribute(("name", self.name.as_str())); - match &self.additional_rules { - None => xml.q.write_event_async(Event::Empty(start)).await, - Some(rules) => { - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - rules.prop_kind.qwrite(xml).await?; - rules.comp_kind.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - }, - } - } -} - -impl QWrite for CompSupport { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("comp"); - empty.push_attribute(("name", self.0.as_str())); - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl QWrite for CompKind { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::AllComp => { - let empty_tag = xml.create_cal_element("allcomp"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Comp(many_comp) => { - for comp in many_comp.iter() { - // Required: recursion in an async fn requires boxing - // rustc --explain E0733 - Box::pin(comp.qwrite(xml)).await?; - } - Ok(()) - } - } - } -} - -impl QWrite for PropKind { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::AllProp => { - let empty_tag = xml.create_cal_element("allprop"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Prop(many_prop) => { - for prop in many_prop.iter() { - prop.qwrite(xml).await?; - } - Ok(()) - } - } - } -} - -impl QWrite for CalProp { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("prop"); - empty.push_attribute(("name", self.name.0.as_str())); - match self.novalue { - None => (), - Some(true) => empty.push_attribute(("novalue", "yes")), - Some(false) => empty.push_attribute(("novalue", "no")), - } - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl QWrite for RecurrenceModifier { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Expand(exp) => exp.qwrite(xml).await, - Self::LimitRecurrenceSet(lrs) => lrs.qwrite(xml).await, - } - } -} - -impl QWrite for Expand { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("expand"); - empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str())); - empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str())); - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl QWrite for LimitRecurrenceSet { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("limit-recurrence-set"); - empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str())); - empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str())); - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl QWrite for LimitFreebusySet { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("limit-freebusy-set"); - empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str())); - empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str())); - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -impl<E: Extension> QWrite for CalendarSelector<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::AllProp => { - let empty_tag = xml.create_dav_element("allprop"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::PropName => { - let empty_tag = xml.create_dav_element("propname"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Prop(prop) => prop.qwrite(xml).await, - } - } -} - -impl QWrite for CompFilter { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("comp-filter"); - start.push_attribute(("name", self.name.as_str())); - - match &self.additional_rules { - None => xml.q.write_event_async(Event::Empty(start)).await, - Some(rules) => { - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - rules.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } - } - } -} - -impl QWrite for CompFilterRules { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::IsNotDefined => { - let empty_tag = xml.create_dav_element("is-not-defined"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Matches(cfm) => cfm.qwrite(xml).await, - } - } -} - -impl QWrite for CompFilterMatch { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - if let Some(time_range) = &self.time_range { - time_range.qwrite(xml).await?; - } - - for prop_item in self.prop_filter.iter() { - prop_item.qwrite(xml).await?; - } - for comp_item in self.comp_filter.iter() { - // Required: recursion in an async fn requires boxing - // rustc --explain E0733 - Box::pin(comp_item.qwrite(xml)).await?; - } - Ok(()) - } -} - -impl QWrite for PropFilter { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("prop-filter"); - start.push_attribute(("name", self.name.as_str())); - - match &self.additional_rules { - None => xml.q.write_event_async(Event::Empty(start.clone())).await, - Some(rules) => { - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - rules.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } - } - } -} - -impl QWrite for PropFilterRules { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::IsNotDefined => { - let empty_tag = xml.create_dav_element("is-not-defined"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Match(prop_match) => prop_match.qwrite(xml).await, - } - } -} - -impl QWrite for PropFilterMatch { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - if let Some(time_range) = &self.time_range { - time_range.qwrite(xml).await?; - } - if let Some(time_or_text) = &self.time_or_text { - time_or_text.qwrite(xml).await?; - } - for param_item in self.param_filter.iter() { - param_item.qwrite(xml).await?; - } - Ok(()) - } -} - -impl QWrite for TimeOrText { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Time(time) => time.qwrite(xml).await, - Self::Text(txt) => txt.qwrite(xml).await, - } - } -} - -impl QWrite for TextMatch { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("text-match"); - if let Some(collation) = &self.collation { - start.push_attribute(("collation", collation.as_str())); - } - match self.negate_condition { - None => (), - Some(true) => start.push_attribute(("negate-condition", "yes")), - Some(false) => start.push_attribute(("negate-condition", "no")), - } - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(self.text.as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for ParamFilter { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("param-filter"); - start.push_attribute(("name", self.name.as_str())); - - match &self.additional_rules { - None => xml.q.write_event_async(Event::Empty(start)).await, - Some(rules) => { - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - rules.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } - } - } -} - -impl QWrite for ParamFilterMatch { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::IsNotDefined => { - let empty_tag = xml.create_dav_element("is-not-defined"); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }, - Self::Match(tm) => tm.qwrite(xml).await, - } - } -} - -impl QWrite for TimeZone { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("timezone"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(self.0.as_str()))).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for Filter { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut start = xml.create_cal_element("filter"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for TimeRange { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut empty = xml.create_cal_element("time-range"); - match self { - Self::OnlyStart(start) => empty.push_attribute(("start", format!("{}", start.format(ICAL_DATETIME_FMT)).as_str())), - Self::OnlyEnd(end) => empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str())), - Self::FullRange(start, end) => { - empty.push_attribute(("start", format!("{}", start.format(ICAL_DATETIME_FMT)).as_str())); - empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str())); - } - } - xml.q.write_event_async(Event::Empty(empty)).await - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::dav::types as dav; - use crate::dav::realization::Calendar; - use tokio::io::AsyncWriteExt; - use chrono::{Utc,TimeZone,DateTime}; - - async fn serialize(elem: &impl QWrite) -> String { - let mut buffer = Vec::new(); - let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); - let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4); - let ns_to_apply = vec![ - ("xmlns:D".into(), "DAV:".into()), - ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()), - ]; - let mut writer = Writer { q, ns_to_apply }; - - elem.qwrite(&mut writer).await.expect("xml serialization"); - tokio_buffer.flush().await.expect("tokio buffer flush"); - let got = std::str::from_utf8(buffer.as_slice()).unwrap(); - - return got.into() - } - - #[tokio::test] - async fn basic_violation() { - let got = serialize( - &dav::Error::<Calendar>(vec![ - dav::Violation::Extension(Violation::ResourceMustBeNull), - ]) - ).await; - - let expected = r#"<D:error xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> - <C:resource-must-be-null/> -</D:error>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_calendar_query1_req() { - let got = serialize( - &CalendarQuery::<Calendar> { - selector: Some(CalendarSelector::Prop(dav::PropName(vec![ - dav::PropertyRequest::GetEtag, - dav::PropertyRequest::Extension(PropertyRequest::CalendarData(CalendarDataRequest { - mime: None, - comp: Some(Comp { - name: Component::VCalendar, - additional_rules: Some(CompInner { - prop_kind: PropKind::Prop(vec![ - CalProp { - name: ComponentProperty("VERSION".into()), - novalue: None, - } - ]), - comp_kind: CompKind::Comp(vec![ - Comp { - name: Component::VEvent, - additional_rules: Some(CompInner { - prop_kind: PropKind::Prop(vec![ - CalProp { name: ComponentProperty("SUMMARY".into()), novalue: None }, - CalProp { name: ComponentProperty("UID".into()), novalue: None }, - CalProp { name: ComponentProperty("DTSTART".into()), novalue: None }, - CalProp { name: ComponentProperty("DTEND".into()), novalue: None }, - CalProp { name: ComponentProperty("DURATION".into()), novalue: None }, - CalProp { name: ComponentProperty("RRULE".into()), novalue: None }, - CalProp { name: ComponentProperty("RDATE".into()), novalue: None }, - CalProp { name: ComponentProperty("EXRULE".into()), novalue: None }, - CalProp { name: ComponentProperty("EXDATE".into()), novalue: None }, - CalProp { name: ComponentProperty("RECURRENCE-ID".into()), novalue: None }, - ]), - comp_kind: CompKind::Comp(vec![]), - }), - }, - Comp { - name: Component::VTimeZone, - additional_rules: None, - } - ]), - }), - }), - recurrence: None, - limit_freebusy_set: None, - })), - ]))), - filter: Filter(CompFilter { - name: Component::VCalendar, - additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { - time_range: None, - prop_filter: vec![], - comp_filter: vec![ - CompFilter { - name: Component::VEvent, - additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { - time_range: Some(TimeRange::FullRange( - Utc.with_ymd_and_hms(2006,1,4,0,0,0).unwrap(), - Utc.with_ymd_and_hms(2006,1,5,0,0,0).unwrap(), - )), - prop_filter: vec![], - comp_filter: vec![], - })), - }, - ], - })), - }), - timezone: None, - } - ).await; - - let expected = r#"<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> - <D:prop> - <D:getetag/> - <C:calendar-data> - <C:comp name="VCALENDAR"> - <C:prop name="VERSION"/> - <C:comp name="VEVENT"> - <C:prop name="SUMMARY"/> - <C:prop name="UID"/> - <C:prop name="DTSTART"/> - <C:prop name="DTEND"/> - <C:prop name="DURATION"/> - <C:prop name="RRULE"/> - <C:prop name="RDATE"/> - <C:prop name="EXRULE"/> - <C:prop name="EXDATE"/> - <C:prop name="RECURRENCE-ID"/> - </C:comp> - <C:comp name="VTIMEZONE"/> - </C:comp> - </C:calendar-data> - </D:prop> - <C:filter> - <C:comp-filter name="VCALENDAR"> - <C:comp-filter name="VEVENT"> - <C:time-range start="20060104T000000Z" end="20060105T000000Z"/> - </C:comp-filter> - </C:comp-filter> - </C:filter> -</C:calendar-query>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_calendar_query1_res() { - let got = serialize( - &dav::Multistatus::<Calendar,dav::PropValue<Calendar>> { - responses: vec![ - dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat( - dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()), - vec![dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd2\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload { - mime: None, - payload: "PLACEHOLDER".into() - })), - ]), - status: dav::Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }] - ), - location: None, - error: None, - responsedescription: None, - }, - dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat( - dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()), - vec![dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd3\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload{ - mime: None, - payload: "PLACEHOLDER".into(), - })), - ]), - status: dav::Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }] - ), - location: None, - error: None, - responsedescription: None, - }, - ], - responsedescription: None, - }, - ).await; - - let expected = r#"<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> - <D:response> - <D:href>http://cal.example.com/bernard/work/abcd2.ics</D:href> - <D:propstat> - <D:prop> - <D:getetag>"fffff-abcd2"</D:getetag> - <C:calendar-data>PLACEHOLDER</C:calendar-data> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>http://cal.example.com/bernard/work/abcd3.ics</D:href> - <D:propstat> - <D:prop> - <D:getetag>"fffff-abcd3"</D:getetag> - <C:calendar-data>PLACEHOLDER</C:calendar-data> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> -</D:multistatus>"#; - - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } -} diff --git a/src/dav/caltypes.rs b/src/dav/caltypes.rs deleted file mode 100644 index befecef..0000000 --- a/src/dav/caltypes.rs +++ /dev/null @@ -1,1440 +0,0 @@ -#![allow(dead_code)] - -use chrono::{DateTime,Utc}; -use super::types as dav; -use super::xml; - -//@FIXME ACL (rfc3744) is missing, required -//@FIXME Versioning (rfc3253) is missing, required -//@FIXME WebDAV sync (rfc6578) is missing, optional -// For reference, SabreDAV guide gives high-level & real-world overview: -// https://sabre.io/dav/building-a-caldav-client/ -// For reference, non-official extensions documented by SabreDAV: -// https://github.com/apple/ccs-calendarserver/tree/master/doc/Extensions - - -// ----- Root elements ----- - -// --- (MKCALENDAR PART) --- - -/// If a request body is included, it MUST be a CALDAV:mkcalendar XML -/// element. Instruction processing MUST occur in the order -/// instructions are received (i.e., from top to bottom). -/// Instructions MUST either all be executed or none executed. Thus, -/// if any error occurs during processing, all executed instructions -/// MUST be undone and a proper error result returned. Instruction -/// processing details can be found in the definition of the DAV:set -/// instruction in Section 12.13.2 of [RFC2518]. -/// -/// <!ELEMENT mkcalendar (DAV:set)> -#[derive(Debug, PartialEq)] -pub struct MkCalendar<E: dav::Extension>(pub dav::Set<E>); - - -/// If a response body for a successful request is included, it MUST -/// be a CALDAV:mkcalendar-response XML element. -/// -/// <!ELEMENT mkcalendar-response ANY> -/// -/// ---- -/// -/// ANY is not satisfying, so looking at RFC5689 -/// https://www.rfc-editor.org/rfc/rfc5689.html#section-5.2 -/// -/// Definition: -/// -/// <!ELEMENT mkcol-response (propstat+)> -#[derive(Debug, PartialEq)] -pub struct MkCalendarResponse<E: dav::Extension, N: xml::Node<N>>(pub Vec<dav::PropStat<E,N>>); - -// --- (REPORT PART) --- - -/// Name: calendar-query -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Defines a report for querying calendar object resources. -/// -/// Description: See Section 7.8. -/// -/// Definition: -/// -/// <!ELEMENT calendar-query ((DAV:allprop | -/// DAV:propname | -/// DAV:prop)?, filter, timezone?)> -#[derive(Debug, PartialEq)] -pub struct CalendarQuery<E: dav::Extension> { - pub selector: Option<CalendarSelector<E>>, - pub filter: Filter, - pub timezone: Option<TimeZone>, -} - -/// Name: calendar-multiget -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: CalDAV report used to retrieve specific calendar object -/// resources. -/// -/// Description: See Section 7.9. -/// -/// Definition: -/// -/// <!ELEMENT calendar-multiget ((DAV:allprop | -/// DAV:propname | -/// DAV:prop)?, DAV:href+)> -#[derive(Debug, PartialEq)] -pub struct CalendarMultiget<E: dav::Extension> { - pub selector: Option<CalendarSelector<E>>, - pub href: Vec<dav::Href>, -} - -/// Name: free-busy-query -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: CalDAV report used to generate a VFREEBUSY to determine -/// busy time over a specific time range. -/// -/// Description: See Section 7.10. -/// -/// Definition: -/// <!ELEMENT free-busy-query (time-range)> -#[derive(Debug, PartialEq)] -pub struct FreeBusyQuery(pub TimeRange); - -// ----- Hooks ----- -#[derive(Debug, PartialEq)] -pub enum ResourceType { - Calendar, -} - -/// Check the matching Property object for documentation -#[derive(Debug, PartialEq)] -pub enum PropertyRequest { - CalendarDescription, - CalendarTimezone, - SupportedCalendarComponentSet, - SupportedCalendarData, - MaxResourceSize, - MinDateTime, - MaxDateTime, - MaxInstances, - MaxAttendeesPerInstance, - SupportedCollationSet, - CalendarData(CalendarDataRequest), -} - -#[derive(Debug, PartialEq)] -pub enum Property { - /// Name: calendar-description - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a human-readable description of the calendar - /// collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MAY be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). An xml:lang attribute indicating the human - /// language of the description SHOULD be set for this property by - /// clients or through server provisioning. Servers MUST return any - /// xml:lang attribute if set for the property. - /// - /// Description: If present, the property contains a description of the - /// calendar collection that is suitable for presentation to a user. - /// If not present, the client should assume no description for the - /// calendar collection. - /// - /// Definition: - /// - /// <!ELEMENT calendar-description (#PCDATA)> - /// PCDATA value: string - /// - /// Example: - /// - /// <C:calendar-description xml:lang="fr-CA" - /// xmlns:C="urn:ietf:params:xml:ns:caldav" - /// >Calendrier de Mathilde Desruisseaux</C:calendar-description> - CalendarDescription { - lang: Option<String>, - text: String, - }, - - /// 5.2.2. CALDAV:calendar-timezone Property - /// - /// Name: calendar-timezone - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Specifies a time zone on a calendar collection. - /// - /// Conformance: This property SHOULD be defined on all calendar - /// collections. If defined, it SHOULD NOT be returned by a PROPFIND - /// DAV:allprop request (as defined in Section 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:calendar-timezone property is used to - /// specify the time zone the server should rely on to resolve "date" - /// values and "date with local time" values (i.e., floating time) to - /// "date with UTC time" values. The server will require this - /// information to determine if a calendar component scheduled with - /// "date" values or "date with local time" values overlaps a CALDAV: - /// time-range specified in a CALDAV:calendar-query REPORT. The - /// server will also require this information to compute the proper - /// FREEBUSY time period as "date with UTC time" in the VFREEBUSY - /// component returned in a response to a CALDAV:free-busy-query - /// REPORT request that takes into account calendar components - /// scheduled with "date" values or "date with local time" values. In - /// the absence of this property, the server MAY rely on the time zone - /// of their choice. - /// - /// Note: The iCalendar data embedded within the CALDAV:calendar- - /// timezone XML element MUST follow the standard XML character data - /// encoding rules, including use of <, >, & etc. entity - /// encoding or the use of a <![CDATA[ ... ]]> construct. In the - /// later case, the iCalendar data cannot contain the character - /// sequence "]]>", which is the end delimiter for the CDATA section. - /// - /// Definition: - /// - /// <!ELEMENT calendar-timezone (#PCDATA)> - /// PCDATA value: an iCalendar object with exactly one VTIMEZONE component. - /// - /// Example: - /// - /// <C:calendar-timezone - /// xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR - /// PRODID:-//Example Corp.//CalDAV Client//EN - /// VERSION:2.0 - /// BEGIN:VTIMEZONE - /// TZID:US-Eastern - /// LAST-MODIFIED:19870101T000000Z - /// BEGIN:STANDARD - /// DTSTART:19671029T020000 - /// RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 - /// TZOFFSETFROM:-0400 - /// TZOFFSETTO:-0500 - /// TZNAME:Eastern Standard Time (US & Canada) - /// END:STANDARD - /// BEGIN:DAYLIGHT - /// DTSTART:19870405T020000 - /// RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 - /// TZOFFSETFROM:-0500 - /// TZOFFSETTO:-0400 - /// TZNAME:Eastern Daylight Time (US & Canada) - /// END:DAYLIGHT - /// END:VTIMEZONE - /// END:VCALENDAR - /// </C:calendar-timezone> - //@FIXME we might want to put a buffer here or an iCal parsed object - CalendarTimezone(String), - - /// Name: supported-calendar-component-set - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Specifies the calendar component types (e.g., VEVENT, - /// VTODO, etc.) that calendar object resources can contain in the - /// calendar collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:supported-calendar-component-set property is - /// used to specify restrictions on the calendar component types that - /// calendar object resources may contain in a calendar collection. - /// Any attempt by the client to store calendar object resources with - /// component types not listed in this property, if it exists, MUST - /// result in an error, with the CALDAV:supported-calendar-component - /// precondition (Section 5.3.2.1) being violated. Since this - /// property is protected, it cannot be changed by clients using a - /// PROPPATCH request. However, clients can initialize the value of - /// this property when creating a new calendar collection with - /// MKCALENDAR. The empty-element tag <C:comp name="VTIMEZONE"/> MUST - /// only be specified if support for calendar object resources that - /// only contain VTIMEZONE components is provided or desired. Support - /// for VTIMEZONE components in calendar object resources that contain - /// VEVENT or VTODO components is always assumed. In the absence of - /// this property, the server MUST accept all component types, and the - /// client can assume that all component types are accepted. - /// - /// Definition: - /// - /// <!ELEMENT supported-calendar-component-set (comp+)> - /// - /// Example: - /// - /// <C:supported-calendar-component-set - /// xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// <C:comp name="VEVENT"/> - /// <C:comp name="VTODO"/> - /// </C:supported-calendar-component-set> - SupportedCalendarComponentSet(Vec<CompSupport>), - - /// Name: supported-calendar-data - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Specifies what media types are allowed for calendar object - /// resources in a calendar collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:supported-calendar-data property is used to - /// specify the media type supported for the calendar object resources - /// contained in a given calendar collection (e.g., iCalendar version - /// 2.0). Any attempt by the client to store calendar object - /// resources with a media type not listed in this property MUST - /// result in an error, with the CALDAV:supported-calendar-data - /// precondition (Section 5.3.2.1) being violated. In the absence of - /// this property, the server MUST only accept data with the media - /// type "text/calendar" and iCalendar version 2.0, and clients can - /// assume that the server will only accept this data. - /// - /// Definition: - /// - /// <!ELEMENT supported-calendar-data (calendar-data+)> - /// - /// Example: - /// - /// <C:supported-calendar-data - /// xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// <C:calendar-data content-type="text/calendar" version="2.0"/> - /// </C:supported-calendar-data> - /// - /// ----- - /// - /// <!ELEMENT calendar-data EMPTY> - /// - /// when nested in the CALDAV:supported-calendar-data property - /// to specify a supported media type for calendar object - /// resources; - SupportedCalendarData(Vec<CalendarDataEmpty>), - - /// Name: max-resource-size - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a numeric value indicating the maximum size of a - /// resource in octets that the server is willing to accept when a - /// calendar object resource is stored in a calendar collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:max-resource-size is used to specify a - /// numeric value that represents the maximum size in octets that the - /// server is willing to accept when a calendar object resource is - /// stored in a calendar collection. Any attempt to store a calendar - /// object resource exceeding this size MUST result in an error, with - /// the CALDAV:max-resource-size precondition (Section 5.3.2.1) being - /// violated. In the absence of this property, the client can assume - /// that the server will allow storing a resource of any reasonable - /// size. - /// - /// Definition: - /// - /// <!ELEMENT max-resource-size (#PCDATA)> - /// PCDATA value: a numeric value (positive integer) - /// - /// Example: - /// - /// <C:max-resource-size xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// 102400 - /// </C:max-resource-size> - MaxResourceSize(u64), - - /// CALDAV:min-date-time Property - /// - /// Name: min-date-time - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a DATE-TIME value indicating the earliest date and - /// time (in UTC) that the server is willing to accept for any DATE or - /// DATE-TIME value in a calendar object resource stored in a calendar - /// collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:min-date-time is used to specify an - /// iCalendar DATE-TIME value in UTC that indicates the earliest - /// inclusive date that the server is willing to accept for any - /// explicit DATE or DATE-TIME value in a calendar object resource - /// stored in a calendar collection. Any attempt to store a calendar - /// object resource using a DATE or DATE-TIME value earlier than this - /// value MUST result in an error, with the CALDAV:min-date-time - /// precondition (Section 5.3.2.1) being violated. Note that servers - /// MUST accept recurring components that specify instances beyond - /// this limit, provided none of those instances have been overridden. - /// In that case, the server MAY simply ignore those instances outside - /// of the acceptable range when processing reports on the calendar - /// object resource. In the absence of this property, the client can - /// assume any valid iCalendar date may be used at least up to the - /// CALDAV:max-date-time value, if that is defined. - /// - /// Definition: - /// - /// <!ELEMENT min-date-time (#PCDATA)> - /// PCDATA value: an iCalendar format DATE-TIME value in UTC - /// - /// Example: - /// - /// <C:min-date-time xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// 19000101T000000Z - /// </C:min-date-time> - MinDateTime(DateTime<Utc>), - - /// CALDAV:max-date-time Property - /// - /// Name: max-date-time - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a DATE-TIME value indicating the latest date and - /// time (in UTC) that the server is willing to accept for any DATE or - /// DATE-TIME value in a calendar object resource stored in a calendar - /// collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:max-date-time is used to specify an - /// iCalendar DATE-TIME value in UTC that indicates the inclusive - /// latest date that the server is willing to accept for any date or - /// time value in a calendar object resource stored in a calendar - /// collection. Any attempt to store a calendar object resource using - /// a DATE or DATE-TIME value later than this value MUST result in an - /// error, with the CALDAV:max-date-time precondition - /// (Section 5.3.2.1) being violated. Note that servers MUST accept - /// recurring components that specify instances beyond this limit, - /// provided none of those instances have been overridden. In that - /// case, the server MAY simply ignore those instances outside of the - /// acceptable range when processing reports on the calendar object - /// resource. In the absence of this property, the client can assume - /// any valid iCalendar date may be used at least down to the CALDAV: - /// min-date-time value, if that is defined. - /// - /// Definition: - /// - /// <!ELEMENT max-date-time (#PCDATA)> - /// PCDATA value: an iCalendar format DATE-TIME value in UTC - /// - /// Example: - /// - /// <C:max-date-time xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// 20491231T235959Z - /// </C:max-date-time> - MaxDateTime(DateTime<Utc>), - - /// CALDAV:max-instances Property - /// - /// Name: max-instances - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a numeric value indicating the maximum number of - /// recurrence instances that a calendar object resource stored in a - /// calendar collection can generate. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:max-instances is used to specify a numeric - /// value that indicates the maximum number of recurrence instances - /// that a calendar object resource stored in a calendar collection - /// can generate. Any attempt to store a calendar object resource - /// with a recurrence pattern that generates more instances than this - /// value MUST result in an error, with the CALDAV:max-instances - /// precondition (Section 5.3.2.1) being violated. In the absence of - /// this property, the client can assume that the server has no limits - /// on the number of recurrence instances it can handle or expand. - /// - /// Definition: - /// - /// <!ELEMENT max-instances (#PCDATA)> - /// PCDATA value: a numeric value (integer greater than zero) - /// - /// Example: - /// - /// <C:max-instances xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// 100 - /// </C:max-instances> - MaxInstances(u64), - - /// CALDAV:max-attendees-per-instance Property - /// - /// Name: max-attendees-per-instance - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Provides a numeric value indicating the maximum number of - /// ATTENDEE properties in any instance of a calendar object resource - /// stored in a calendar collection. - /// - /// Conformance: This property MAY be defined on any calendar - /// collection. If defined, it MUST be protected and SHOULD NOT be - /// returned by a PROPFIND DAV:allprop request (as defined in Section - /// 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:max-attendees-per-instance is used to - /// specify a numeric value that indicates the maximum number of - /// iCalendar ATTENDEE properties on any one instance of a calendar - /// object resource stored in a calendar collection. Any attempt to - /// store a calendar object resource with more ATTENDEE properties per - /// instance than this value MUST result in an error, with the CALDAV: - /// max-attendees-per-instance precondition (Section 5.3.2.1) being - /// violated. In the absence of this property, the client can assume - /// that the server can handle any number of ATTENDEE properties in a - /// calendar component. - /// - /// Definition: - /// - /// <!ELEMENT max-attendees-per-instance (#PCDATA)> - /// PCDATA value: a numeric value (integer greater than zero) - /// - /// Example: - /// - /// <C:max-attendees-per-instance - /// xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// 25 - /// </C:max-attendees-per-instance> - MaxAttendeesPerInstance(u64), - - /// Name: supported-collation-set - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Identifies the set of collations supported by the server - /// for text matching operations. - /// - /// Conformance: This property MUST be defined on any resource that - /// supports a report that does text matching. If defined, it MUST be - /// protected and SHOULD NOT be returned by a PROPFIND DAV:allprop - /// request (as defined in Section 12.14.1 of [RFC2518]). - /// - /// Description: The CALDAV:supported-collation-set property contains - /// zero or more CALDAV:supported-collation elements, which specify - /// the collection identifiers of the collations supported by the - /// server. - /// - /// Definition: - /// - /// <!ELEMENT supported-collation-set (supported-collation*)> - /// <!ELEMENT supported-collation (#PCDATA)> - /// - /// Example: - /// - /// <C:supported-collation-set - /// xmlns:C="urn:ietf:params:xml:ns:caldav"> - /// <C:supported-collation>i;ascii-casemap</C:supported-collation> - /// <C:supported-collation>i;octet</C:supported-collation> - /// </C:supported-collation-set> - SupportedCollationSet(Vec<SupportedCollation>), - - /// Name: calendar-data - /// - /// Namespace: urn:ietf:params:xml:ns:caldav - /// - /// Purpose: Specified one of the following: - /// - /// 1. A supported media type for calendar object resources when - /// nested in the CALDAV:supported-calendar-data property; - /// - /// 2. The parts of a calendar object resource should be returned by - /// a calendaring report; - /// - /// 3. The content of a calendar object resource in a response to a - /// calendaring report. - /// - /// Description: When nested in the CALDAV:supported-calendar-data - /// property, the CALDAV:calendar-data XML element specifies a media - /// type supported by the CalDAV server for calendar object resources. - /// - /// When used in a calendaring REPORT request, the CALDAV:calendar- - /// data XML element specifies which parts of calendar object - /// resources need to be returned in the response. If the CALDAV: - /// calendar-data XML element doesn't contain any CALDAV:comp element, - /// calendar object resources will be returned in their entirety. - /// - /// Finally, when used in a calendaring REPORT response, the CALDAV: - /// calendar-data XML element specifies the content of a calendar - /// object resource. Given that XML parsers normalize the two- - /// character sequence CRLF (US-ASCII decimal 13 and US-ASCII decimal - /// 10) to a single LF character (US-ASCII decimal 10), the CR - /// character (US-ASCII decimal 13) MAY be omitted in calendar object - /// resources specified in the CALDAV:calendar-data XML element. - /// Furthermore, calendar object resources specified in the CALDAV: - /// calendar-data XML element MAY be invalid per their media type - /// specification if the CALDAV:calendar-data XML element part of the - /// calendaring REPORT request did not specify required properties - /// (e.g., UID, DTSTAMP, etc.), or specified a CALDAV:prop XML element - /// with the "novalue" attribute set to "yes". - /// - /// Note: The CALDAV:calendar-data XML element is specified in requests - /// and responses inside the DAV:prop XML element as if it were a - /// WebDAV property. However, the CALDAV:calendar-data XML element is - /// not a WebDAV property and, as such, is not returned in PROPFIND - /// responses, nor used in PROPPATCH requests. - /// - /// Note: The iCalendar data embedded within the CALDAV:calendar-data - /// XML element MUST follow the standard XML character data encoding - /// rules, including use of <, >, & etc. entity encoding or - /// the use of a <![CDATA[ ... ]]> construct. In the later case, the - /// iCalendar data cannot contain the character sequence "]]>", which - /// is the end delimiter for the CDATA section. - CalendarData(CalendarDataPayload), -} - -#[derive(Debug, PartialEq)] -pub enum Violation { - /// (DAV:resource-must-be-null): A resource MUST NOT exist at the - /// Request-URI; - ResourceMustBeNull, - - /// (CALDAV:calendar-collection-location-ok): The Request-URI MUST - /// identify a location where a calendar collection can be created; - CalendarCollectionLocationOk, - - /// (CALDAV:valid-calendar-data): The time zone specified in CALDAV: - /// calendar-timezone property MUST be a valid iCalendar object - /// containing a single valid VTIMEZONE component. - ValidCalendarData, - - ///@FIXME should not be here but in RFC3744 - /// !!! ERRATA 1002 !!! - /// (DAV:need-privileges): The DAV:bind privilege MUST be granted to - /// the current user on the parent collection of the Request-URI. - NeedPrivileges, - - /// (CALDAV:initialize-calendar-collection): A new calendar collection - /// exists at the Request-URI. The DAV:resourcetype of the calendar - /// collection MUST contain both DAV:collection and CALDAV:calendar - /// XML elements. - InitializeCalendarCollection, - - /// (CALDAV:supported-calendar-data): The resource submitted in the - /// PUT request, or targeted by a COPY or MOVE request, MUST be a - /// supported media type (i.e., iCalendar) for calendar object - /// resources; - SupportedCalendarData, - - /// (CALDAV:valid-calendar-object-resource): The resource submitted in - /// the PUT request, or targeted by a COPY or MOVE request, MUST obey - /// all restrictions specified in Section 4.1 (e.g., calendar object - /// resources MUST NOT contain more than one type of calendar - /// component, calendar object resources MUST NOT specify the - /// iCalendar METHOD property, etc.); - ValidCalendarObjectResource, - - /// (CALDAV:supported-calendar-component): The resource submitted in - /// the PUT request, or targeted by a COPY or MOVE request, MUST - /// contain a type of calendar component that is supported in the - /// targeted calendar collection; - SupportedCalendarComponent, - - /// (CALDAV:no-uid-conflict): The resource submitted in the PUT - /// request, or targeted by a COPY or MOVE request, MUST NOT specify - /// an iCalendar UID property value already in use in the targeted - /// calendar collection or overwrite an existing calendar object - /// resource with one that has a different UID property value. - /// Servers SHOULD report the URL of the resource that is already - /// making use of the same UID property value in the DAV:href element; - /// - /// <!ELEMENT no-uid-conflict (DAV:href)> - NoUidConflict(dav::Href), - - /// (CALDAV:max-resource-size): The resource submitted in the PUT - /// request, or targeted by a COPY or MOVE request, MUST have an octet - /// size less than or equal to the value of the CALDAV:max-resource- - /// size property value (Section 5.2.5) on the calendar collection - /// where the resource will be stored; - MaxResourceSize, - - /// (CALDAV:min-date-time): The resource submitted in the PUT request, - /// or targeted by a COPY or MOVE request, MUST have all of its - /// iCalendar DATE or DATE-TIME property values (for each recurring - /// instance) greater than or equal to the value of the CALDAV:min- - /// date-time property value (Section 5.2.6) on the calendar - /// collection where the resource will be stored; - MinDateTime, - - /// (CALDAV:max-date-time): The resource submitted in the PUT request, - /// or targeted by a COPY or MOVE request, MUST have all of its - /// iCalendar DATE or DATE-TIME property values (for each recurring - /// instance) less than the value of the CALDAV:max-date-time property - /// value (Section 5.2.7) on the calendar collection where the - /// resource will be stored; - MaxDateTime, - - /// (CALDAV:max-instances): The resource submitted in the PUT request, - /// or targeted by a COPY or MOVE request, MUST generate a number of - /// recurring instances less than or equal to the value of the CALDAV: - /// max-instances property value (Section 5.2.8) on the calendar - /// collection where the resource will be stored; - MaxInstances, - - /// (CALDAV:max-attendees-per-instance): The resource submitted in the - /// PUT request, or targeted by a COPY or MOVE request, MUST have a - /// number of ATTENDEE properties on any one instance less than or - /// equal to the value of the CALDAV:max-attendees-per-instance - /// property value (Section 5.2.9) on the calendar collection where - /// the resource will be stored; - MaxAttendeesPerInstance, - - /// (CALDAV:valid-filter): The CALDAV:filter XML element (see - /// Section 9.7) specified in the REPORT request MUST be valid. For - /// instance, a CALDAV:filter cannot nest a <C:comp name="VEVENT"> - /// element in a <C:comp name="VTODO"> element, and a CALDAV:filter - /// cannot nest a <C:time-range start="..." end="..."> element in a - /// <C:prop name="SUMMARY"> element. - ValidFilter, - - /// (CALDAV:supported-filter): The CALDAV:comp-filter (see - /// Section 9.7.1), CALDAV:prop-filter (see Section 9.7.2), and - /// CALDAV:param-filter (see Section 9.7.3) XML elements used in the - /// CALDAV:filter XML element (see Section 9.7) in the REPORT request - /// only make reference to components, properties, and parameters for - /// which queries are supported by the server, i.e., if the CALDAV: - /// filter element attempts to reference an unsupported component, - /// property, or parameter, this precondition is violated. Servers - /// SHOULD report the CALDAV:comp-filter, CALDAV:prop-filter, or - /// CALDAV:param-filter for which it does not provide support. - /// - /// <!ELEMENT supported-filter (comp-filter*, - /// prop-filter*, - /// param-filter*)> - SupportedFilter { - comp: Vec<CompFilter>, - prop: Vec<PropFilter>, - param: Vec<ParamFilter>, - }, - - /// (DAV:number-of-matches-within-limits): The number of matching - /// calendar object resources must fall within server-specific, - /// predefined limits. For example, this condition might be triggered - /// if a search specification would cause the return of an extremely - /// large number of responses. - NumberOfMatchesWithinLimits, -} - -// -------- Inner XML elements --------- - -/// Some of the reports defined in this section do text matches of -/// character strings provided by the client and are compared to stored -/// calendar data. Since iCalendar data is, by default, encoded in the -/// UTF-8 charset and may include characters outside the US-ASCII charset -/// range in some property and parameter values, there is a need to -/// ensure that text matching follows well-defined rules. -/// -/// To deal with this, this specification makes use of the IANA Collation -/// Registry defined in [RFC4790] to specify collations that may be used -/// to carry out the text comparison operations with a well-defined rule. -/// -/// The comparisons used in CalDAV are all "substring" matches, as per -/// [RFC4790], Section 4.2. Collations supported by the server MUST -/// support "substring" match operations. -/// -/// CalDAV servers are REQUIRED to support the "i;ascii-casemap" and -/// "i;octet" collations, as described in [RFC4790], and MAY support -/// other collations. -/// -/// Servers MUST advertise the set of collations that they support via -/// the CALDAV:supported-collation-set property defined on any resource -/// that supports reports that use collations. -/// -/// Clients MUST only use collations from the list advertised by the -/// server. -/// -/// In the absence of a collation explicitly specified by the client, or -/// if the client specifies the "default" collation identifier (as -/// defined in [RFC4790], Section 3.1), the server MUST default to using -/// "i;ascii-casemap" as the collation. -/// -/// Wildcards (as defined in [RFC4790], Section 3.2) MUST NOT be used in -/// the collation identifier. -/// -/// If the client chooses a collation not supported by the server, the -/// server MUST respond with a CALDAV:supported-collation precondition -/// error response. -#[derive(Debug, PartialEq)] -pub struct SupportedCollation(pub Collation); - -/// <!ELEMENT calendar-data (#PCDATA)> -/// PCDATA value: iCalendar object -/// -/// when nested in the DAV:prop XML element in a calendaring -/// REPORT response to specify the content of a returned -/// calendar object resource. -#[derive(Debug, PartialEq)] -pub struct CalendarDataPayload { - pub mime: Option<CalendarDataSupport>, - pub payload: String, -} - -/// <!ELEMENT calendar-data (comp?, -/// (expand | limit-recurrence-set)?, -/// limit-freebusy-set?)> -/// -/// when nested in the DAV:prop XML element in a calendaring -/// REPORT request to specify which parts of calendar object -/// resources should be returned in the response; -#[derive(Debug, PartialEq)] -pub struct CalendarDataRequest { - pub mime: Option<CalendarDataSupport>, - pub comp: Option<Comp>, - pub recurrence: Option<RecurrenceModifier>, - pub limit_freebusy_set: Option<LimitFreebusySet>, -} - -/// calendar-data specialization for Property -/// -/// <!ELEMENT calendar-data EMPTY> -/// -/// when nested in the CALDAV:supported-calendar-data property -/// to specify a supported media type for calendar object -/// resources; -#[derive(Debug, PartialEq)] -pub struct CalendarDataEmpty(pub Option<CalendarDataSupport>); - -/// <!ATTLIST calendar-data content-type CDATA "text/calendar" -/// version CDATA "2.0"> -/// content-type value: a MIME media type -/// version value: a version string -/// attributes can be used on all three variants of the -/// CALDAV:calendar-data XML element. -#[derive(Debug, PartialEq)] -pub struct CalendarDataSupport { - pub content_type: String, - pub version: String, -} - -/// Name: comp -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Defines which component types to return. -/// -/// Description: The name value is a calendar component name (e.g., -/// VEVENT). -/// -/// Definition: -/// -/// <!ELEMENT comp ((allprop | prop*), (allcomp | comp*))> -/// <!ATTLIST comp name CDATA #REQUIRED> -/// name value: a calendar component name -/// -/// Note: The CALDAV:prop and CALDAV:allprop elements have the same name -/// as the DAV:prop and DAV:allprop elements defined in [RFC2518]. -/// However, the CALDAV:prop and CALDAV:allprop elements are defined -/// in the "urn:ietf:params:xml:ns:caldav" namespace instead of the -/// "DAV:" namespace. -#[derive(Debug, PartialEq)] -pub struct Comp { - pub name: Component, - pub additional_rules: Option<CompInner>, -} - -#[derive(Debug, PartialEq)] -pub struct CompInner { - pub prop_kind: PropKind, - pub comp_kind: CompKind, -} - -/// For SupportedCalendarComponentSet -/// -/// Definition: -/// -/// <!ELEMENT supported-calendar-component-set (comp+)> -/// -/// Example: -/// -/// <C:supported-calendar-component-set -/// xmlns:C="urn:ietf:params:xml:ns:caldav"> -/// <C:comp name="VEVENT"/> -/// <C:comp name="VTODO"/> -/// </C:supported-calendar-component-set> -#[derive(Debug, PartialEq)] -pub struct CompSupport(pub Component); - -/// Name: allcomp -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies that all components shall be returned. -/// -/// Description: The CALDAV:allcomp XML element can be used when the -/// client wants all types of components returned by a calendaring -/// REPORT request. -/// -/// Definition: -/// -/// <!ELEMENT allcomp EMPTY> -#[derive(Debug, PartialEq)] -pub enum CompKind { - AllComp, - Comp(Vec<Comp>), -} - -/// Name: allprop -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies that all properties shall be returned. -/// -/// Description: The CALDAV:allprop XML element can be used when the -/// client wants all properties of components returned by a -/// calendaring REPORT request. -/// -/// Definition: -/// -/// <!ELEMENT allprop EMPTY> -/// -/// Note: The CALDAV:allprop element has the same name as the DAV: -/// allprop element defined in [RFC2518]. However, the CALDAV:allprop -/// element is defined in the "urn:ietf:params:xml:ns:caldav" -/// namespace instead of the "DAV:" namespace. -#[derive(Debug, PartialEq)] -pub enum PropKind { - AllProp, - Prop(Vec<CalProp>), -} - -/// Name: prop -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Defines which properties to return in the response. -/// -/// Description: The "name" attribute specifies the name of the calendar -/// property to return (e.g., ATTENDEE). The "novalue" attribute can -/// be used by clients to request that the actual value of the -/// property not be returned (if the "novalue" attribute is set to -/// "yes"). In that case, the server will return just the iCalendar -/// property name and any iCalendar parameters and a trailing ":" -/// without the subsequent value data. -/// -/// Definition: -/// <!ELEMENT prop EMPTY> -/// <!ATTLIST prop name CDATA #REQUIRED novalue (yes | no) "no"> -/// name value: a calendar property name -/// novalue value: "yes" or "no" -/// -/// Note: The CALDAV:prop element has the same name as the DAV:prop -/// element defined in [RFC2518]. However, the CALDAV:prop element is -/// defined in the "urn:ietf:params:xml:ns:caldav" namespace instead -/// of the "DAV:" namespace. -#[derive(Debug, PartialEq)] -pub struct CalProp { - pub name: ComponentProperty, - pub novalue: Option<bool>, -} - -#[derive(Debug, PartialEq)] -pub enum RecurrenceModifier { - Expand(Expand), - LimitRecurrenceSet(LimitRecurrenceSet), -} - -/// Name: expand -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Forces the server to expand recurring components into -/// individual recurrence instances. -/// -/// Description: The CALDAV:expand XML element specifies that for a -/// given calendaring REPORT request, the server MUST expand the -/// recurrence set into calendar components that define exactly one -/// recurrence instance, and MUST return only those whose scheduled -/// time intersect a specified time range. -/// -/// The "start" attribute specifies the inclusive start of the time -/// range, and the "end" attribute specifies the non-inclusive end of -/// the time range. Both attributes are specified as date with UTC -/// time value. The value of the "end" attribute MUST be greater than -/// the value of the "start" attribute. -/// -/// The server MUST use the same logic as defined for CALDAV:time- -/// range to determine if a recurrence instance intersects the -/// specified time range. -/// -/// Recurring components, other than the initial instance, MUST -/// include a RECURRENCE-ID property indicating which instance they -/// refer to. -/// -/// The returned calendar components MUST NOT use recurrence -/// properties (i.e., EXDATE, EXRULE, RDATE, and RRULE) and MUST NOT -/// have reference to or include VTIMEZONE components. Date and local -/// time with reference to time zone information MUST be converted -/// into date with UTC time. -/// -/// Definition: -/// -/// <!ELEMENT expand EMPTY> -/// <!ATTLIST expand start CDATA #REQUIRED -/// end CDATA #REQUIRED> -/// start value: an iCalendar "date with UTC time" -/// end value: an iCalendar "date with UTC time" -#[derive(Debug, PartialEq)] -pub struct Expand(pub DateTime<Utc>, pub DateTime<Utc>); - -/// CALDAV:limit-recurrence-set XML Element -/// -/// Name: limit-recurrence-set -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies a time range to limit the set of "overridden -/// components" returned by the server. -/// -/// Description: The CALDAV:limit-recurrence-set XML element specifies -/// that for a given calendaring REPORT request, the server MUST -/// return, in addition to the "master component", only the -/// "overridden components" that impact a specified time range. An -/// overridden component impacts a time range if its current start and -/// end times overlap the time range, or if the original start and end -/// times -- the ones that would have been used if the instance were -/// not overridden -- overlap the time range. -/// -/// The "start" attribute specifies the inclusive start of the time -/// range, and the "end" attribute specifies the non-inclusive end of -/// the time range. Both attributes are specified as date with UTC -/// time value. The value of the "end" attribute MUST be greater than -/// the value of the "start" attribute. -/// -/// The server MUST use the same logic as defined for CALDAV:time- -/// range to determine if the current or original scheduled time of an -/// "overridden" recurrence instance intersects the specified time -/// range. -/// -/// Overridden components that have a RANGE parameter on their -/// RECURRENCE-ID property may specify one or more instances in the -/// recurrence set, and some of those instances may fall within the -/// specified time range or may have originally fallen within the -/// specified time range prior to being overridden. If that is the -/// case, the overridden component MUST be included in the results, as -/// it has a direct impact on the interpretation of instances within -/// the specified time range. -/// -/// Definition: -/// -/// <!ELEMENT limit-recurrence-set EMPTY> -/// <!ATTLIST limit-recurrence-set start CDATA #REQUIRED -/// end CDATA #REQUIRED> -/// start value: an iCalendar "date with UTC time" -/// end value: an iCalendar "date with UTC time" -#[derive(Debug, PartialEq)] -pub struct LimitRecurrenceSet(pub DateTime<Utc>, pub DateTime<Utc>); - -/// Name: limit-freebusy-set -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies a time range to limit the set of FREEBUSY values -/// returned by the server. -/// -/// Description: The CALDAV:limit-freebusy-set XML element specifies -/// that for a given calendaring REPORT request, the server MUST only -/// return the FREEBUSY property values of a VFREEBUSY component that -/// intersects a specified time range. -/// -/// The "start" attribute specifies the inclusive start of the time -/// range, and the "end" attribute specifies the non-inclusive end of -/// the time range. Both attributes are specified as "date with UTC -/// time" value. The value of the "end" attribute MUST be greater -/// than the value of the "start" attribute. -/// -/// The server MUST use the same logic as defined for CALDAV:time- -/// range to determine if a FREEBUSY property value intersects the -/// specified time range. -/// -/// Definition: -/// <!ELEMENT limit-freebusy-set EMPTY> -/// <!ATTLIST limit-freebusy-set start CDATA #REQUIRED -/// end CDATA #REQUIRED> -/// start value: an iCalendar "date with UTC time" -/// end value: an iCalendar "date with UTC time" -#[derive(Debug, PartialEq)] -pub struct LimitFreebusySet(pub DateTime<Utc>, pub DateTime<Utc>); - -/// Used by CalendarQuery & CalendarMultiget -#[derive(Debug, PartialEq)] -pub enum CalendarSelector<E: dav::Extension> { - AllProp, - PropName, - Prop(dav::PropName<E>), -} - -/// Name: comp-filter -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies search criteria on calendar components. -/// -/// Description: The CALDAV:comp-filter XML element specifies a query -/// targeted at the calendar object (i.e., VCALENDAR) or at a specific -/// calendar component type (e.g., VEVENT). The scope of the -/// CALDAV:comp-filter XML element is the calendar object when used as -/// a child of the CALDAV:filter XML element. The scope of the -/// CALDAV:comp-filter XML element is the enclosing calendar component -/// when used as a child of another CALDAV:comp-filter XML element. A -/// CALDAV:comp-filter is said to match if: -/// -/// * The CALDAV:comp-filter XML element is empty and the calendar -/// object or calendar component type specified by the "name" -/// attribute exists in the current scope; -/// -/// or: -/// -/// * The CALDAV:comp-filter XML element contains a CALDAV:is-not- -/// defined XML element and the calendar object or calendar -/// component type specified by the "name" attribute does not exist -/// in the current scope; -/// -/// or: -/// -/// * The CALDAV:comp-filter XML element contains a CALDAV:time-range -/// XML element and at least one recurrence instance in the -/// targeted calendar component is scheduled to overlap the -/// specified time range, and all specified CALDAV:prop-filter and -/// CALDAV:comp-filter child XML elements also match the targeted -/// calendar component; -/// -/// or: -/// -/// * The CALDAV:comp-filter XML element only contains CALDAV:prop- -/// filter and CALDAV:comp-filter child XML elements that all match -/// the targeted calendar component. -/// -/// Definition: -/// <!ELEMENT comp-filter (is-not-defined | (time-range?, -/// prop-filter*, comp-filter*))> -/// -/// <!ATTLIST comp-filter name CDATA #REQUIRED> -/// name value: a calendar object or calendar component -/// type (e.g., VEVENT) -#[derive(Debug, PartialEq)] -pub struct CompFilter { - pub name: Component, - // Option 1 = None, Option 2, 3, 4 = Some - pub additional_rules: Option<CompFilterRules>, -} -#[derive(Debug, PartialEq)] -pub enum CompFilterRules { - // Option 2 - IsNotDefined, - // Options 3 & 4 - Matches(CompFilterMatch), -} -#[derive(Debug, PartialEq)] -pub struct CompFilterMatch { - pub time_range: Option<TimeRange>, - pub prop_filter: Vec<PropFilter>, - pub comp_filter: Vec<CompFilter>, -} - -/// Name: prop-filter -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies search criteria on calendar properties. -/// -/// Description: The CALDAV:prop-filter XML element specifies a query -/// targeted at a specific calendar property (e.g., CATEGORIES) in the -/// scope of the enclosing calendar component. A calendar property is -/// said to match a CALDAV:prop-filter if: -/// -/// * The CALDAV:prop-filter XML element is empty and a property of -/// the type specified by the "name" attribute exists in the -/// enclosing calendar component; -/// -/// or: -/// -/// * The CALDAV:prop-filter XML element contains a CALDAV:is-not- -/// defined XML element and no property of the type specified by -/// the "name" attribute exists in the enclosing calendar -/// component; -/// -/// or: -/// -/// * The CALDAV:prop-filter XML element contains a CALDAV:time-range -/// XML element and the property value overlaps the specified time -/// range, and all specified CALDAV:param-filter child XML elements -/// also match the targeted property; -/// -/// or: -/// -/// * The CALDAV:prop-filter XML element contains a CALDAV:text-match -/// XML element and the property value matches it, and all -/// specified CALDAV:param-filter child XML elements also match the -/// targeted property; -/// -/// Definition: -/// -/// <!ELEMENT prop-filter (is-not-defined | -/// ((time-range | text-match)?, -/// param-filter*))> -/// -/// <!ATTLIST prop-filter name CDATA #REQUIRED> -/// name value: a calendar property name (e.g., ATTENDEE) -#[derive(Debug, PartialEq)] -pub struct PropFilter { - pub name: Component, - // None = Option 1, Some() = Option 2, 3 & 4 - pub additional_rules: Option<PropFilterRules>, -} -#[derive(Debug, PartialEq)] -pub enum PropFilterRules { - // Option 2 - IsNotDefined, - // Options 3 & 4 - Match(PropFilterMatch), -} -#[derive(Debug, PartialEq)] -pub struct PropFilterMatch { - pub time_range: Option<TimeRange>, - pub time_or_text: Option<TimeOrText>, - pub param_filter: Vec<ParamFilter>, -} -#[derive(Debug, PartialEq)] -pub enum TimeOrText { - Time(TimeRange), - Text(TextMatch), -} - -/// Name: text-match -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies a substring match on a property or parameter -/// value. -/// -/// Description: The CALDAV:text-match XML element specifies text used -/// for a substring match against the property or parameter value -/// specified in a calendaring REPORT request. -/// -/// The "collation" attribute is used to select the collation that the -/// server MUST use for character string matching. In the absence of -/// this attribute, the server MUST use the "i;ascii-casemap" -/// collation. -/// -/// The "negate-condition" attribute is used to indicate that this -/// test returns a match if the text matches when the attribute value -/// is set to "no", or return a match if the text does not match, if -/// the attribute value is set to "yes". For example, this can be -/// used to match components with a STATUS property not set to -/// CANCELLED. -/// -/// Definition: -/// <!ELEMENT text-match (#PCDATA)> -/// PCDATA value: string -/// <!ATTLIST text-match collation CDATA "i;ascii-casemap" -/// negate-condition (yes | no) "no"> -#[derive(Debug, PartialEq)] -pub struct TextMatch { - pub collation: Option<Collation>, - pub negate_condition: Option<bool>, - pub text: String, -} - -/// Name: param-filter -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Limits the search to specific parameter values. -/// -/// Description: The CALDAV:param-filter XML element specifies a query -/// targeted at a specific calendar property parameter (e.g., -/// PARTSTAT) in the scope of the calendar property on which it is -/// defined. A calendar property parameter is said to match a CALDAV: -/// param-filter if: -/// -/// * The CALDAV:param-filter XML element is empty and a parameter of -/// the type specified by the "name" attribute exists on the -/// calendar property being examined; -/// -/// or: -/// -/// * The CALDAV:param-filter XML element contains a CALDAV:is-not- -/// defined XML element and no parameter of the type specified by -/// the "name" attribute exists on the calendar property being -/// examined; -/// -/// Definition: -/// -/// <!ELEMENT param-filter (is-not-defined | text-match?)> -/// -/// <!ATTLIST param-filter name CDATA #REQUIRED> -/// name value: a property parameter name (e.g., PARTSTAT) -#[derive(Debug, PartialEq)] -pub struct ParamFilter { - pub name: PropertyParameter, - pub additional_rules: Option<ParamFilterMatch>, -} -#[derive(Debug, PartialEq)] -pub enum ParamFilterMatch { - IsNotDefined, - Match(TextMatch), -} - -/// CALDAV:is-not-defined XML Element -/// -/// Name: is-not-defined -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies that a match should occur if the enclosing -/// component, property, or parameter does not exist. -/// -/// Description: The CALDAV:is-not-defined XML element specifies that a -/// match occurs if the enclosing component, property, or parameter -/// value specified in a calendaring REPORT request does not exist in -/// the calendar data being tested. -/// -/// Definition: -/// <!ELEMENT is-not-defined EMPTY> -/* CURRENTLY INLINED */ - - - -/// Name: timezone -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies the time zone component to use when determining -/// the results of a report. -/// -/// Description: The CALDAV:timezone XML element specifies that for a -/// given calendaring REPORT request, the server MUST rely on the -/// specified VTIMEZONE component instead of the CALDAV:calendar- -/// timezone property of the calendar collection, in which the -/// calendar object resource is contained to resolve "date" values and -/// "date with local time" values (i.e., floating time) to "date with -/// UTC time" values. The server will require this information to -/// determine if a calendar component scheduled with "date" values or -/// "date with local time" values intersects a CALDAV:time-range -/// specified in a CALDAV:calendar-query REPORT. -/// -/// Note: The iCalendar data embedded within the CALDAV:timezone XML -/// element MUST follow the standard XML character data encoding -/// rules, including use of <, >, & etc. entity encoding or -/// the use of a <![CDATA[ ... ]]> construct. In the later case, the -/// -/// iCalendar data cannot contain the character sequence "]]>", which -/// is the end delimiter for the CDATA section. -/// -/// Definition: -/// -/// <!ELEMENT timezone (#PCDATA)> -/// PCDATA value: an iCalendar object with exactly one VTIMEZONE -#[derive(Debug, PartialEq)] -pub struct TimeZone(pub String); - -/// Name: filter -/// -/// Namespace: urn:ietf:params:xml:ns:caldav -/// -/// Purpose: Specifies a filter to limit the set of calendar components -/// returned by the server. -/// -/// Description: The CALDAV:filter XML element specifies the search -/// filter used to limit the calendar components returned by a -/// calendaring REPORT request. -/// -/// Definition: -/// <!ELEMENT filter (comp-filter)> -#[derive(Debug, PartialEq)] -pub struct Filter(pub CompFilter); - -/// Name: time-range -/// -/// Definition: -/// -/// <!ELEMENT time-range EMPTY> -/// <!ATTLIST time-range start CDATA #IMPLIED -/// end CDATA #IMPLIED> -/// start value: an iCalendar "date with UTC time" -/// end value: an iCalendar "date with UTC time" -#[derive(Debug, PartialEq)] -pub enum TimeRange { - OnlyStart(DateTime<Utc>), - OnlyEnd(DateTime<Utc>), - FullRange(DateTime<Utc>, DateTime<Utc>), -} - -// ----------------------- ENUM ATTRIBUTES --------------------- - -/// Known components -#[derive(Debug, PartialEq)] -pub enum Component { - VCalendar, - VJournal, - VFreeBusy, - VEvent, - VTodo, - VAlarm, - VTimeZone, - Unknown(String), -} -impl Component { - pub fn as_str<'a>(&'a self) -> &'a str { - match self { - Self::VCalendar => "VCALENDAR", - Self::VJournal => "VJOURNAL", - Self::VFreeBusy => "VFREEBUSY", - Self::VEvent => "VEVENT", - Self::VTodo => "VTODO", - Self::VAlarm => "VALARM", - Self::VTimeZone => "VTIMEZONE", - Self::Unknown(c) => c, - } - } -} - -/// name="VERSION", name="SUMMARY", etc. -/// Can be set on different objects: VCalendar, VEvent, etc. -/// Might be replaced by an enum later -#[derive(Debug, PartialEq)] -pub struct ComponentProperty(pub String); - -/// like PARSTAT -#[derive(Debug, PartialEq)] -pub struct PropertyParameter(pub String); -impl PropertyParameter { - pub fn as_str<'a>(&'a self) -> &'a str { - self.0.as_str() - } -} - -#[derive(Default,Debug,PartialEq)] -pub enum Collation { - #[default] - AsciiCaseMap, - Octet, - Unknown(String), -} -impl Collation { - pub fn as_str<'a>(&'a self) -> &'a str { - match self { - Self::AsciiCaseMap => "i;ascii-casemap", - Self::Octet => "i;octet", - Self::Unknown(c) => c.as_str(), - } - } -} diff --git a/src/dav/decoder.rs b/src/dav/decoder.rs deleted file mode 100644 index aa3c7e5..0000000 --- a/src/dav/decoder.rs +++ /dev/null @@ -1,948 +0,0 @@ -use std::borrow::Cow; -use std::future::Future; - -use quick_xml::events::{Event, BytesStart, BytesDecl, BytesText}; -use quick_xml::events::attributes::AttrError; -use quick_xml::name::{Namespace, QName, PrefixDeclaration, ResolveResult, ResolveResult::*}; -use quick_xml::reader::NsReader; -use tokio::io::AsyncBufRead; - -use super::types::*; -use super::error::ParsingError; -use super::xml::{Node, QRead, Reader, IRead, DAV_URN, CAL_URN}; - -//@TODO (1) Rewrite all objects as Href, -// where we return Ok(None) instead of trying to find the object at any cost. -// Add a xml.find<E: Qread>() -> Result<Option<E>, ParsingError> or similar for the cases we -// really need the object -// (2) Rewrite QRead and replace Result<Option<_>, _> with Result<_, _>, not found being a possible -// error. -// (3) Rewrite vectors with xml.collect<E: QRead>() -> Result<Vec<E>, _> -// (4) Something for alternatives would be great but no idea yet - -// ---- ROOT ---- - -/// Propfind request -impl<E: Extension> QRead<PropFind<E>> for PropFind<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "propfind").await?; - let propfind: PropFind<E> = loop { - // allprop - if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? { - let includ = xml.maybe_find::<Include<E>>().await?; - xml.close().await?; - break PropFind::AllProp(includ) - } - - // propname - if let Some(_) = xml.maybe_open(DAV_URN, "propname").await? { - xml.close().await?; - break PropFind::PropName - } - - // prop - let (mut maybe_prop, mut dirty) = (None, false); - xml.maybe_read::<PropName<E>>(&mut maybe_prop, &mut dirty).await?; - if let Some(prop) = maybe_prop { - break PropFind::Prop(prop) - } - - // not found, skipping - xml.skip().await?; - }; - xml.close().await?; - - Ok(propfind) - } -} - -/// PROPPATCH request -impl<E: Extension> QRead<PropertyUpdate<E>> for PropertyUpdate<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "propertyupdate").await?; - let collected_items = xml.collect::<PropertyUpdateItem<E>>().await?; - xml.close().await?; - Ok(PropertyUpdate(collected_items)) - } -} - -/// Generic response -impl<E: Extension, N: Node<N>> QRead<Multistatus<E,N>> for Multistatus<E,N> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "multistatus").await?; - let mut responses = Vec::new(); - let mut responsedescription = None; - - loop { - let mut dirty = false; - xml.maybe_push(&mut responses, &mut dirty).await?; - xml.maybe_read(&mut responsedescription, &mut dirty).await?; - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => xml.skip().await?, - }; - } - } - - xml.close().await?; - Ok(Multistatus { responses, responsedescription }) - } -} - -// LOCK REQUEST -impl QRead<LockInfo> for LockInfo { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "lockinfo").await?; - let (mut m_scope, mut m_type, mut owner) = (None, None, None); - loop { - let mut dirty = false; - xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?; - xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?; - xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?; - - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => xml.skip().await?, - }; - } - } - xml.close().await?; - match (m_scope, m_type) { - (Some(lockscope), Some(locktype)) => Ok(LockInfo { lockscope, locktype, owner }), - _ => Err(ParsingError::MissingChild), - } - } -} - -// LOCK RESPONSE -impl<E: Extension> QRead<PropValue<E>> for PropValue<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "prop").await?; - let mut acc = xml.collect::<Property<E>>().await?; - xml.close().await?; - Ok(PropValue(acc)) - } -} - - -/// Error response -impl<E: Extension> QRead<Error<E>> for Error<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "error").await?; - let violations = xml.collect::<Violation<E>>().await?; - xml.close().await?; - Ok(Error(violations)) - } -} - - - -// ---- INNER XML -impl<E: Extension, N: Node<N>> QRead<Response<E,N>> for Response<E,N> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "response").await?; - let (mut status, mut error, mut responsedescription, mut location) = (None, None, None, None); - let mut href = Vec::new(); - let mut propstat = Vec::new(); - - loop { - let mut dirty = false; - xml.maybe_read::<Status>(&mut status, &mut dirty).await?; - xml.maybe_push::<Href>(&mut href, &mut dirty).await?; - xml.maybe_push::<PropStat<E,N>>(&mut propstat, &mut dirty).await?; - xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; - xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; - xml.maybe_read::<Location>(&mut location, &mut dirty).await?; - - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => { xml.skip().await? }, - }; - } - } - - xml.close().await?; - match (status, &propstat[..], &href[..]) { - (Some(status), &[], &[_, ..]) => Ok(Response { - status_or_propstat: StatusOrPropstat::Status(href, status), - error, responsedescription, location, - }), - (None, &[_, ..], &[_, ..]) => Ok(Response { - status_or_propstat: StatusOrPropstat::PropStat(href.into_iter().next().unwrap(), propstat), - error, responsedescription, location, - }), - (Some(_), &[_, ..], _) => Err(ParsingError::InvalidValue), - _ => Err(ParsingError::MissingChild), - } - } -} - -impl<E: Extension, N: Node<N>> QRead<PropStat<E,N>> for PropStat<E,N> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "propstat").await?; - - let (mut m_prop, mut m_status, mut error, mut responsedescription) = (None, None, None, None); - - loop { - let mut dirty = false; - xml.maybe_read::<N>(&mut m_prop, &mut dirty).await?; - xml.maybe_read::<Status>(&mut m_status, &mut dirty).await?; - xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; - xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; - - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => xml.skip().await?, - }; - } - } - - xml.close().await?; - match (m_prop, m_status) { - (Some(prop), Some(status)) => Ok(PropStat { prop, status, error, responsedescription }), - _ => Err(ParsingError::MissingChild), - } - } -} - -impl QRead<Status> for Status { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "status").await?; - let fullcode = xml.tag_string().await?; - let txtcode = fullcode.splitn(3, ' ').nth(1).ok_or(ParsingError::InvalidValue)?; - let code = http::status::StatusCode::from_bytes(txtcode.as_bytes()).or(Err(ParsingError::InvalidValue))?; - xml.close().await?; - Ok(Status(code)) - } -} - -impl QRead<ResponseDescription> for ResponseDescription { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "responsedescription").await?; - let cnt = xml.tag_string().await?; - xml.close().await?; - Ok(ResponseDescription(cnt)) - } -} - -impl QRead<Location> for Location { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "location").await?; - let href = xml.find::<Href>().await?; - xml.close().await?; - Ok(Location(href)) - } -} - -impl<E: Extension> QRead<PropertyUpdateItem<E>> for PropertyUpdateItem<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - match Remove::qread(xml).await { - Err(ParsingError::Recoverable) => (), - otherwise => return otherwise.map(PropertyUpdateItem::Remove), - } - Set::qread(xml).await.map(PropertyUpdateItem::Set) - } -} - -impl<E: Extension> QRead<Remove<E>> for Remove<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "remove").await?; - let propname = xml.find::<PropName<E>>().await?; - xml.close().await?; - Ok(Remove(propname)) - } -} - -impl<E: Extension> QRead<Set<E>> for Set<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "set").await?; - let propvalue = xml.find::<PropValue<E>>().await?; - xml.close().await?; - Ok(Set(propvalue)) - } -} - -impl<E: Extension> QRead<Violation<E>> for Violation<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - if xml.maybe_open(DAV_URN, "lock-token-matches-request-uri").await?.is_some() { - xml.close().await?; - Ok(Violation::LockTokenMatchesRequestUri) - } else if xml.maybe_open(DAV_URN, "lock-token-submitted").await?.is_some() { - let links = xml.collect::<Href>().await?; - xml.close().await?; - Ok(Violation::LockTokenSubmitted(links)) - } else if xml.maybe_open(DAV_URN, "no-conflicting-lock").await?.is_some() { - let links = xml.collect::<Href>().await?; - xml.close().await?; - Ok(Violation::NoConflictingLock(links)) - } else if xml.maybe_open(DAV_URN, "no-external-entities").await?.is_some() { - xml.close().await?; - Ok(Violation::NoExternalEntities) - } else if xml.maybe_open(DAV_URN, "preserved-live-properties").await?.is_some() { - xml.close().await?; - Ok(Violation::PreservedLiveProperties) - } else if xml.maybe_open(DAV_URN, "propfind-finite-depth").await?.is_some() { - xml.close().await?; - Ok(Violation::PropfindFiniteDepth) - } else if xml.maybe_open(DAV_URN, "cannot-modify-protected-property").await?.is_some() { - xml.close().await?; - Ok(Violation::CannotModifyProtectedProperty) - } else { - E::Error::qread(xml).await.map(Violation::Extension) - } - } -} - -impl<E: Extension> QRead<Include<E>> for Include<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "include").await?; - let acc = xml.collect::<PropertyRequest<E>>().await?; - xml.close().await?; - Ok(Include(acc)) - } -} - -impl<E: Extension> QRead<PropName<E>> for PropName<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "prop").await?; - let acc = xml.collect::<PropertyRequest<E>>().await?; - xml.close().await?; - Ok(PropName(acc)) - } -} - -impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - let maybe = if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { - Some(PropertyRequest::CreationDate) - } else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() { - Some(PropertyRequest::DisplayName) - } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() { - Some(PropertyRequest::GetContentLanguage) - } else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() { - Some(PropertyRequest::GetContentLength) - } else if xml.maybe_open(DAV_URN, "getcontenttype").await?.is_some() { - Some(PropertyRequest::GetContentType) - } else if xml.maybe_open(DAV_URN, "getetag").await?.is_some() { - Some(PropertyRequest::GetEtag) - } else if xml.maybe_open(DAV_URN, "getlastmodified").await?.is_some() { - Some(PropertyRequest::GetLastModified) - } else if xml.maybe_open(DAV_URN, "lockdiscovery").await?.is_some() { - Some(PropertyRequest::LockDiscovery) - } else if xml.maybe_open(DAV_URN, "resourcetype").await?.is_some() { - Some(PropertyRequest::ResourceType) - } else if xml.maybe_open(DAV_URN, "supportedlock").await?.is_some() { - Some(PropertyRequest::SupportedLock) - } else { - None - }; - - match maybe { - Some(pr) => { - xml.close().await?; - Ok(pr) - }, - None => E::PropertyRequest::qread(xml).await.map(PropertyRequest::Extension), - } - } -} - -impl<E: Extension> QRead<Property<E>> for Property<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - use chrono::{DateTime, FixedOffset, TimeZone}; - - // Core WebDAV properties - if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { - let datestr = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::CreationDate(DateTime::parse_from_rfc3339(datestr.as_str())?)) - } else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() { - let name = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::DisplayName(name)) - } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() { - let lang = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::GetContentLanguage(lang)) - } else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() { - let cl = xml.tag_string().await?.parse::<u64>()?; - xml.close().await?; - return Ok(Property::GetContentLength(cl)) - } else if xml.maybe_open(DAV_URN, "getcontenttype").await?.is_some() { - let ct = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::GetContentType(ct)) - } else if xml.maybe_open(DAV_URN, "getetag").await?.is_some() { - let etag = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::GetEtag(etag)) - } else if xml.maybe_open(DAV_URN, "getlastmodified").await?.is_some() { - let datestr = xml.tag_string().await?; - xml.close().await?; - return Ok(Property::GetLastModified(DateTime::parse_from_rfc2822(datestr.as_str())?)) - } else if xml.maybe_open(DAV_URN, "lockdiscovery").await?.is_some() { - let acc = xml.collect::<ActiveLock>().await?; - xml.close().await?; - return Ok(Property::LockDiscovery(acc)) - } else if xml.maybe_open(DAV_URN, "resourcetype").await?.is_some() { - let acc = xml.collect::<ResourceType<E>>().await?; - xml.close().await?; - return Ok(Property::ResourceType(acc)) - } else if xml.maybe_open(DAV_URN, "supportedlock").await?.is_some() { - let acc = xml.collect::<LockEntry>().await?; - xml.close().await?; - return Ok(Property::SupportedLock(acc)) - } - - // Option 2: an extension property, delegating - E::Property::qread(xml).await.map(Property::Extension) - } -} - -impl QRead<ActiveLock> for ActiveLock { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "activelock").await?; - let (mut m_scope, mut m_type, mut m_depth, mut owner, mut timeout, mut locktoken, mut m_root) = - (None, None, None, None, None, None, None); - - loop { - let mut dirty = false; - xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?; - xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?; - xml.maybe_read::<Depth>(&mut m_depth, &mut dirty).await?; - xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?; - xml.maybe_read::<Timeout>(&mut timeout, &mut dirty).await?; - xml.maybe_read::<LockToken>(&mut locktoken, &mut dirty).await?; - xml.maybe_read::<LockRoot>(&mut m_root, &mut dirty).await?; - - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => { xml.skip().await?; }, - } - } - } - - xml.close().await?; - match (m_scope, m_type, m_depth, m_root) { - (Some(lockscope), Some(locktype), Some(depth), Some(lockroot)) => - Ok(ActiveLock { lockscope, locktype, depth, owner, timeout, locktoken, lockroot }), - _ => Err(ParsingError::MissingChild), - } - } -} - -impl QRead<Depth> for Depth { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "depth").await?; - let depth_str = xml.tag_string().await?; - xml.close().await?; - match depth_str.as_str() { - "0" => Ok(Depth::Zero), - "1" => Ok(Depth::One), - "infinity" => Ok(Depth::Infinity), - _ => Err(ParsingError::WrongToken), - } - } -} - -impl QRead<Owner> for Owner { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "owner").await?; - - let mut owner = Owner::Unknown; - loop { - match xml.peek() { - Event::Text(_) | Event::CData(_) => { - let txt = xml.tag_string().await?; - if matches!(owner, Owner::Unknown) { - owner = Owner::Txt(txt); - } - } - Event::Start(_) | Event::Empty(_) => { - match Href::qread(xml).await { - Ok(href) => { owner = Owner::Href(href); }, - Err(ParsingError::Recoverable) => { xml.skip().await?; }, - Err(e) => return Err(e), - } - } - Event::End(_) => break, - _ => { xml.skip().await?; }, - } - }; - xml.close().await?; - Ok(owner) - } -} - -impl QRead<Timeout> for Timeout { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - const SEC_PFX: &str = "SEC_PFX"; - xml.open(DAV_URN, "timeout").await?; - - let timeout = match xml.tag_string().await?.as_str() { - "Infinite" => Timeout::Infinite, - seconds => match seconds.strip_prefix(SEC_PFX) { - Some(secs) => Timeout::Seconds(secs.parse::<u32>()?), - None => return Err(ParsingError::InvalidValue), - }, - }; - - xml.close().await?; - Ok(timeout) - } -} - -impl QRead<LockToken> for LockToken { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "locktoken").await?; - let href = Href::qread(xml).await?; - xml.close().await?; - Ok(LockToken(href)) - } -} - -impl QRead<LockRoot> for LockRoot { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "lockroot").await?; - let href = Href::qread(xml).await?; - xml.close().await?; - Ok(LockRoot(href)) - } -} - -impl<E: Extension> QRead<ResourceType<E>> for ResourceType<E> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - if xml.maybe_open(DAV_URN, "collection").await?.is_some() { - xml.close().await?; - return Ok(ResourceType::Collection) - } - - E::ResourceType::qread(xml).await.map(ResourceType::Extension) - } -} - -impl QRead<LockEntry> for LockEntry { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "lockentry").await?; - let (mut maybe_scope, mut maybe_type) = (None, None); - - loop { - let mut dirty = false; - xml.maybe_read::<LockScope>(&mut maybe_scope, &mut dirty).await?; - xml.maybe_read::<LockType>(&mut maybe_type, &mut dirty).await?; - if !dirty { - match xml.peek() { - Event::End(_) => break, - _ => xml.skip().await?, - }; - } - } - - xml.close().await?; - match (maybe_scope, maybe_type) { - (Some(lockscope), Some(locktype)) => Ok(LockEntry { lockscope, locktype }), - _ => Err(ParsingError::MissingChild), - } - } -} - -impl QRead<LockScope> for LockScope { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "lockscope").await?; - - let lockscope = loop { - if xml.maybe_open(DAV_URN, "exclusive").await?.is_some() { - xml.close().await?; - break LockScope::Exclusive - } else if xml.maybe_open(DAV_URN, "shared").await?.is_some() { - xml.close().await?; - break LockScope::Shared - } - - xml.skip().await?; - }; - - xml.close().await?; - Ok(lockscope) - } -} - -impl QRead<LockType> for LockType { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "locktype").await?; - - let locktype = loop { - if xml.maybe_open(DAV_URN, "write").await?.is_some() { - xml.close().await?; - break LockType::Write - } - - xml.skip().await?; - }; - - xml.close().await?; - Ok(locktype) - } -} - -impl QRead<Href> for Href { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - xml.open(DAV_URN, "href").await?; - let mut url = xml.tag_string().await?; - xml.close().await?; - Ok(Href(url)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use chrono::{FixedOffset, DateTime, TimeZone, Utc}; - use crate::dav::realization::Core; - - #[tokio::test] - async fn basic_propfind_propname() { - let src = r#"<?xml version="1.0" encoding="utf-8" ?> -<rando/> -<garbage><old/></garbage> -<D:propfind xmlns:D="DAV:"> - <D:propname/> -</D:propfind> -"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<PropFind::<Core>>().await.unwrap(); - - assert_eq!(got, PropFind::<Core>::PropName); - } - - #[tokio::test] - async fn basic_propfind_prop() { - let src = r#"<?xml version="1.0" encoding="utf-8" ?> -<rando/> -<garbage><old/></garbage> -<D:propfind xmlns:D="DAV:"> - <D:prop> - <D:displayname/> - <D:getcontentlength/> - <D:getcontenttype/> - <D:getetag/> - <D:getlastmodified/> - <D:resourcetype/> - <D:supportedlock/> - </D:prop> -</D:propfind> -"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<PropFind::<Core>>().await.unwrap(); - - assert_eq!(got, PropFind::Prop(PropName(vec![ - PropertyRequest::DisplayName, - PropertyRequest::GetContentLength, - PropertyRequest::GetContentType, - PropertyRequest::GetEtag, - PropertyRequest::GetLastModified, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, - ]))); - } - - #[tokio::test] - async fn rfc_lock_error() { - let src = r#"<?xml version="1.0" encoding="utf-8" ?> - <D:error xmlns:D="DAV:"> - <D:lock-token-submitted> - <D:href>/locked/</D:href> - </D:lock-token-submitted> - </D:error>"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<Error::<Core>>().await.unwrap(); - - assert_eq!(got, Error(vec![ - Violation::LockTokenSubmitted(vec![ - Href("/locked/".into()) - ]) - ])); - } - - - #[tokio::test] - async fn rfc_propertyupdate() { - let src = r#"<?xml version="1.0" encoding="utf-8" ?> - <D:propertyupdate xmlns:D="DAV:" - xmlns:Z="http://ns.example.com/standards/z39.50/"> - <D:set> - <D:prop> - <Z:Authors> - <Z:Author>Jim Whitehead</Z:Author> - <Z:Author>Roy Fielding</Z:Author> - </Z:Authors> - </D:prop> - </D:set> - <D:remove> - <D:prop><Z:Copyright-Owner/></D:prop> - </D:remove> - </D:propertyupdate>"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<PropertyUpdate::<Core>>().await.unwrap(); - - assert_eq!(got, PropertyUpdate(vec![ - PropertyUpdateItem::Set(Set(PropValue(vec![]))), - PropertyUpdateItem::Remove(Remove(PropName(vec![]))), - ])); - } - - #[tokio::test] - async fn rfc_lockinfo() { - let src = r#" -<?xml version="1.0" encoding="utf-8" ?> -<D:lockinfo xmlns:D='DAV:'> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - <D:owner> - <D:href>http://example.org/~ejw/contact.html</D:href> - </D:owner> -</D:lockinfo> -"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<LockInfo>().await.unwrap(); - - assert_eq!(got, LockInfo { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))), - }); - } - - #[tokio::test] - async fn rfc_multistatus_name() { - let src = r#" -<?xml version="1.0" encoding="utf-8" ?> - <multistatus xmlns="DAV:"> - <response> - <href>http://www.example.com/container/</href> - <propstat> - <prop xmlns:R="http://ns.example.com/boxschema/"> - <R:bigbox/> - <R:author/> - <creationdate/> - <displayname/> - <resourcetype/> - <supportedlock/> - </prop> - <status>HTTP/1.1 200 OK</status> - </propstat> - </response> - <response> - <href>http://www.example.com/container/front.html</href> - <propstat> - <prop xmlns:R="http://ns.example.com/boxschema/"> - <R:bigbox/> - <creationdate/> - <displayname/> - <getcontentlength/> - <getcontenttype/> - <getetag/> - <getlastmodified/> - <resourcetype/> - <supportedlock/> - </prop> - <status>HTTP/1.1 200 OK</status> - </propstat> - </response> - </multistatus> -"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<Multistatus::<Core, PropName<Core>>>().await.unwrap(); - - assert_eq!(got, Multistatus { - responses: vec![ - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("http://www.example.com/container/".into()), - vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - responsedescription: None, - location: None, - }, - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("http://www.example.com/container/front.html".into()), - vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::GetContentLength, - PropertyRequest::GetContentType, - PropertyRequest::GetEtag, - PropertyRequest::GetLastModified, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - responsedescription: None, - location: None, - }, - ], - responsedescription: None, - }); - } - - - #[tokio::test] - async fn rfc_multistatus_value() { - let src = r#" - <?xml version="1.0" encoding="utf-8" ?> - <D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>/container/</D:href> - <D:propstat> - <D:prop xmlns:R="http://ns.example.com/boxschema/"> - <R:bigbox><R:BoxType>Box type A</R:BoxType></R:bigbox> - <R:author><R:Name>Hadrian</R:Name></R:author> - <D:creationdate>1997-12-01T17:42:21-08:00</D:creationdate> - <D:displayname>Example collection</D:displayname> - <D:resourcetype><D:collection/></D:resourcetype> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - <D:lockentry> - <D:lockscope><D:shared/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/container/front.html</D:href> - <D:propstat> - <D:prop xmlns:R="http://ns.example.com/boxschema/"> - <R:bigbox><R:BoxType>Box type B</R:BoxType> - </R:bigbox> - <D:creationdate>1997-12-01T18:27:21-08:00</D:creationdate> - <D:displayname>Example HTML resource</D:displayname> - <D:getcontentlength>4525</D:getcontentlength> - <D:getcontenttype>text/html</D:getcontenttype> - <D:getetag>"zzyzx"</D:getetag> - <D:getlastmodified - >Mon, 12 Jan 1998 09:25:56 GMT</D:getlastmodified> - <D:resourcetype/> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - <D:lockentry> - <D:lockscope><D:shared/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - </D:multistatus>"#; - - let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<Multistatus::<Core, PropValue<Core>>>().await.unwrap(); - - assert_eq!(got, Multistatus { - responses: vec![ - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("/container/".into()), - vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 17, 42, 21).unwrap()), - Property::DisplayName("Example collection".into()), - Property::ResourceType(vec![ResourceType::Collection]), - Property::SupportedLock(vec![ - LockEntry { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - }, - LockEntry { - lockscope: LockScope::Shared, - locktype: LockType::Write, - }, - ]), - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - responsedescription: None, - location: None, - - }, - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("/container/front.html".into()), - vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 18, 27, 21).unwrap()), - Property::DisplayName("Example HTML resource".into()), - Property::GetContentLength(4525), - Property::GetContentType("text/html".into()), - Property::GetEtag(r#""zzyzx""#.into()), - Property::GetLastModified(FixedOffset::west_opt(0).unwrap().with_ymd_and_hms(1998, 01, 12, 09, 25, 56).unwrap()), - //Property::ResourceType(vec![]), - Property::SupportedLock(vec![ - LockEntry { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - }, - LockEntry { - lockscope: LockScope::Shared, - locktype: LockType::Write, - }, - ]), - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - responsedescription: None, - location: None, - - }, - ], - responsedescription: None, - }); - } - -} diff --git a/src/dav/encoder.rs b/src/dav/encoder.rs deleted file mode 100644 index 4de5440..0000000 --- a/src/dav/encoder.rs +++ /dev/null @@ -1,1117 +0,0 @@ -use std::io::Cursor; - -use quick_xml::Error as QError; -use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; -use quick_xml::writer::ElementWriter; -use quick_xml::name::PrefixDeclaration; -use tokio::io::AsyncWrite; -use super::types::*; -use super::xml::{Node, Writer,QWrite,IWrite}; - - -// --- XML ROOTS - -/// PROPFIND REQUEST -impl<E: Extension> QWrite for PropFind<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("propfind"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::PropName => { - let empty_propname = xml.create_dav_element("propname"); - xml.q.write_event_async(Event::Empty(empty_propname)).await? - }, - Self::AllProp(maybe_include) => { - let empty_allprop = xml.create_dav_element("allprop"); - xml.q.write_event_async(Event::Empty(empty_allprop)).await?; - if let Some(include) = maybe_include { - include.qwrite(xml).await?; - } - }, - Self::Prop(propname) => propname.qwrite(xml).await?, - } - xml.q.write_event_async(Event::End(end)).await - } -} - -/// PROPPATCH REQUEST -impl<E: Extension> QWrite for PropertyUpdate<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("propertyupdate"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for update in self.0.iter() { - update.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - - -/// PROPFIND RESPONSE, PROPPATCH RESPONSE, COPY RESPONSE, MOVE RESPONSE -/// DELETE RESPONSE, -impl<E: Extension, N: Node<N>> QWrite for Multistatus<E,N> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("multistatus"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for response in self.responses.iter() { - response.qwrite(xml).await?; - } - if let Some(description) = &self.responsedescription { - description.qwrite(xml).await?; - } - - xml.q.write_event_async(Event::End(end)).await?; - Ok(()) - } -} - -/// LOCK REQUEST -impl QWrite for LockInfo { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("lockinfo"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.lockscope.qwrite(xml).await?; - self.locktype.qwrite(xml).await?; - if let Some(owner) = &self.owner { - owner.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -/// SOME LOCK RESPONSES -impl<E: Extension> QWrite for PropValue<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("prop"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for propval in &self.0 { - propval.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -/// Error response -impl<E: Extension> QWrite for Error<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("error"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for violation in &self.0 { - violation.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -// --- XML inner elements -impl<E: Extension> QWrite for PropertyUpdateItem<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Set(set) => set.qwrite(xml).await, - Self::Remove(rm) => rm.qwrite(xml).await, - } - } -} - -impl<E: Extension> QWrite for Set<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("set"); - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension> QWrite for Remove<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("remove"); - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - - -impl<E: Extension> QWrite for PropName<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("prop"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for propname in &self.0 { - propname.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - - -impl QWrite for Href { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("href"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&self.0))).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension, N: Node<N>> QWrite for Response<E,N> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("response"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.status_or_propstat.qwrite(xml).await?; - if let Some(error) = &self.error { - error.qwrite(xml).await?; - } - if let Some(responsedescription) = &self.responsedescription { - responsedescription.qwrite(xml).await?; - } - if let Some(location) = &self.location { - location.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension, N: Node<N>> QWrite for StatusOrPropstat<E,N> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Status(many_href, status) => { - for href in many_href.iter() { - href.qwrite(xml).await?; - } - status.qwrite(xml).await - }, - Self::PropStat(href, propstat_list) => { - href.qwrite(xml).await?; - for propstat in propstat_list.iter() { - propstat.qwrite(xml).await?; - } - Ok(()) - } - } - } -} - -impl QWrite for Status { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("status"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - - let txt = format!("HTTP/1.1 {} {}", self.0.as_str(), self.0.canonical_reason().unwrap_or("No reason")); - xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await?; - - xml.q.write_event_async(Event::End(end)).await?; - - Ok(()) - } -} - -impl QWrite for ResponseDescription { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("responsedescription"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&self.0))).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for Location { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("location"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension, N: Node<N>> QWrite for PropStat<E,N> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("propstat"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.prop.qwrite(xml).await?; - self.status.qwrite(xml).await?; - if let Some(error) = &self.error { - error.qwrite(xml).await?; - } - if let Some(description) = &self.responsedescription { - description.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await?; - - Ok(()) - } -} - -impl<E: Extension> QWrite for Property<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - use Property::*; - match self { - CreationDate(date) => { - // <D:creationdate>1997-12-01T17:42:21-08:00</D:creationdate> - let start = xml.create_dav_element("creationdate"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&date.to_rfc3339()))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - DisplayName(name) => { - // <D:displayname>Example collection</D:displayname> - let start = xml.create_dav_element("displayname"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(name))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - GetContentLanguage(lang) => { - let start = xml.create_dav_element("getcontentlanguage"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(lang))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - GetContentLength(len) => { - // <D:getcontentlength>4525</D:getcontentlength> - let start = xml.create_dav_element("getcontentlength"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&len.to_string()))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - GetContentType(ct) => { - // <D:getcontenttype>text/html</D:getcontenttype> - let start = xml.create_dav_element("getcontenttype"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&ct))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - GetEtag(et) => { - // <D:getetag>"zzyzx"</D:getetag> - let start = xml.create_dav_element("getetag"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(et))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - GetLastModified(date) => { - // <D:getlastmodified>Mon, 12 Jan 1998 09:25:56 GMT</D:getlastmodified> - let start = xml.create_dav_element("getlastmodified"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - xml.q.write_event_async(Event::Text(BytesText::new(&date.to_rfc2822()))).await?; - xml.q.write_event_async(Event::End(end)).await?; - }, - LockDiscovery(many_locks) => { - // <D:lockdiscovery><D:activelock> ... </D:activelock></D:lockdiscovery> - let start = xml.create_dav_element("lockdiscovery"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for lock in many_locks.iter() { - lock.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await?; - }, - ResourceType(many_types) => { - // <D:resourcetype><D:collection/></D:resourcetype> - - // <D:resourcetype/> - - // <x:resourcetype xmlns:x="DAV:"> - // <x:collection/> - // <f:search-results xmlns:f="http://www.example.com/ns"/> - // </x:resourcetype> - - let start = xml.create_dav_element("resourcetype"); - if many_types.is_empty() { - xml.q.write_event_async(Event::Empty(start)).await?; - } else { - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - for restype in many_types.iter() { - restype.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await?; - } - }, - SupportedLock(many_entries) => { - // <D:supportedlock/> - - // <D:supportedlock> <D:lockentry> ... </D:lockentry> </D:supportedlock> - - let start = xml.create_dav_element("supportedlock"); - if many_entries.is_empty() { - xml.q.write_event_async(Event::Empty(start)).await?; - } else { - let end = start.to_end(); - xml.q.write_event_async(Event::Start(start.clone())).await?; - for entry in many_entries.iter() { - entry.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await?; - } - }, - Extension(inner) => inner.qwrite(xml).await?, - }; - Ok(()) - } -} - -impl<E: Extension> QWrite for ResourceType<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - match self { - Self::Collection => { - let empty_collection = xml.create_dav_element("collection"); - xml.q.write_event_async(Event::Empty(empty_collection)).await - }, - Self::Extension(inner) => inner.qwrite(xml).await, - } - } -} - -impl<E: Extension> QWrite for Include<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("include"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for prop in self.0.iter() { - prop.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension> QWrite for PropertyRequest<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - use PropertyRequest::*; - let mut atom = async |c| { - let empty_tag = xml.create_dav_element(c); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }; - - match self { - CreationDate => atom("creationdate").await, - DisplayName => atom("displayname").await, - GetContentLanguage => atom("getcontentlanguage").await, - GetContentLength => atom("getcontentlength").await, - GetContentType => atom("getcontenttype").await, - GetEtag => atom("getetag").await, - GetLastModified => atom("getlastmodified").await, - LockDiscovery => atom("lockdiscovery").await, - ResourceType => atom("resourcetype").await, - SupportedLock => atom("supportedlock").await, - Extension(inner) => inner.qwrite(xml).await, - } - } -} - -impl QWrite for ActiveLock { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - // <D:activelock> - // <D:locktype><D:write/></D:locktype> - // <D:lockscope><D:exclusive/></D:lockscope> - // <D:depth>infinity</D:depth> - // <D:owner> - // <D:href>http://example.org/~ejw/contact.html</D:href> - // </D:owner> - // <D:timeout>Second-604800</D:timeout> - // <D:locktoken> - // <D:href>urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4</D:href> - // </D:locktoken> - // <D:lockroot> - // <D:href>http://example.com/workspace/webdav/proposal.doc</D:href> - // </D:lockroot> - // </D:activelock> - let start = xml.create_dav_element("activelock"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.locktype.qwrite(xml).await?; - self.lockscope.qwrite(xml).await?; - self.depth.qwrite(xml).await?; - if let Some(owner) = &self.owner { - owner.qwrite(xml).await?; - } - if let Some(timeout) = &self.timeout { - timeout.qwrite(xml).await?; - } - if let Some(locktoken) = &self.locktoken { - locktoken.qwrite(xml).await?; - } - self.lockroot.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for LockType { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("locktype"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::Write => { - let empty_write = xml.create_dav_element("write"); - xml.q.write_event_async(Event::Empty(empty_write)).await? - }, - }; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for LockScope { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("lockscope"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::Exclusive => { - let empty_tag = xml.create_dav_element("exclusive"); - xml.q.write_event_async(Event::Empty(empty_tag)).await? - }, - Self::Shared => { - let empty_tag = xml.create_dav_element("shared"); - xml.q.write_event_async(Event::Empty(empty_tag)).await? - }, - }; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for Owner { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("owner"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::Txt(txt) => xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await?, - Self::Href(href) => href.qwrite(xml).await?, - Self::Unknown => (), - } - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for Depth { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("depth"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::Zero => xml.q.write_event_async(Event::Text(BytesText::new("0"))).await?, - Self::One => xml.q.write_event_async(Event::Text(BytesText::new("1"))).await?, - Self::Infinity => xml.q.write_event_async(Event::Text(BytesText::new("infinity"))).await?, - }; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for Timeout { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("timeout"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - match self { - Self::Seconds(count) => { - let txt = format!("Second-{}", count); - xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await? - }, - Self::Infinite => xml.q.write_event_async(Event::Text(BytesText::new("Infinite"))).await? - }; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for LockToken { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("locktoken"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for LockRoot { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("lockroot"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.0.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl QWrite for LockEntry { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let start = xml.create_dav_element("lockentry"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - self.lockscope.qwrite(xml).await?; - self.locktype.qwrite(xml).await?; - xml.q.write_event_async(Event::End(end)).await - } -} - -impl<E: Extension> QWrite for Violation<E> { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { - let mut atom = async |c| { - let empty_tag = xml.create_dav_element(c); - xml.q.write_event_async(Event::Empty(empty_tag)).await - }; - - match self { - Violation::LockTokenMatchesRequestUri => atom("lock-token-matches-request-uri").await, - Violation::LockTokenSubmitted(hrefs) if hrefs.is_empty() => atom("lock-token-submitted").await, - Violation::LockTokenSubmitted(hrefs) => { - let start = xml.create_dav_element("lock-token-submitted"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for href in hrefs { - href.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Violation::NoConflictingLock(hrefs) if hrefs.is_empty() => atom("no-conflicting-lock").await, - Violation::NoConflictingLock(hrefs) => { - let start = xml.create_dav_element("no-conflicting-lock"); - let end = start.to_end(); - - xml.q.write_event_async(Event::Start(start.clone())).await?; - for href in hrefs { - href.qwrite(xml).await?; - } - xml.q.write_event_async(Event::End(end)).await - }, - Violation::NoExternalEntities => atom("no-external-entities").await, - Violation::PreservedLiveProperties => atom("preserved-live-properties").await, - Violation::PropfindFiniteDepth => atom("propfind-finite-depth").await, - Violation::CannotModifyProtectedProperty => atom("cannot-modify-protected-property").await, - Violation::Extension(inner) => inner.qwrite(xml).await, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::dav::realization::Core; - use tokio::io::AsyncWriteExt; - - /// To run only the unit tests and avoid the behavior ones: - /// cargo test --bin aerogramme - - async fn serialize(elem: &impl QWrite) -> String { - let mut buffer = Vec::new(); - let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); - let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4); - let ns_to_apply = vec![ ("xmlns:D".into(), "DAV:".into()) ]; - let mut writer = Writer { q, ns_to_apply }; - - elem.qwrite(&mut writer).await.expect("xml serialization"); - tokio_buffer.flush().await.expect("tokio buffer flush"); - let got = std::str::from_utf8(buffer.as_slice()).unwrap(); - - return got.into() - } - - #[tokio::test] - async fn basic_href() { - - let got = serialize( - &Href("/SOGo/dav/so/".into()) - ).await; - let expected = r#"<D:href xmlns:D="DAV:">/SOGo/dav/so/</D:href>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn basic_multistatus() { - let got = serialize( - &Multistatus::<Core, PropName<Core>> { - responses: vec![], - responsedescription: Some(ResponseDescription("Hello world".into())) - }, - ).await; - - let expected = r#"<D:multistatus xmlns:D="DAV:"> - <D:responsedescription>Hello world</D:responsedescription> -</D:multistatus>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - - #[tokio::test] - async fn rfc_error_delete_locked() { - let got = serialize( - &Error::<Core>(vec![ - Violation::LockTokenSubmitted(vec![ - Href("/locked/".into()) - ]) - ]), - ).await; - - let expected = r#"<D:error xmlns:D="DAV:"> - <D:lock-token-submitted> - <D:href>/locked/</D:href> - </D:lock-token-submitted> -</D:error>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_propname_req() { - let got = serialize( - &PropFind::<Core>::PropName, - ).await; - - let expected = r#"<D:propfind xmlns:D="DAV:"> - <D:propname/> -</D:propfind>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_propname_res() { - let got = serialize( - &Multistatus::<Core, PropName<Core>> { - responses: vec![ - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("http://www.example.com/container/".into()), - vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }] - ), - error: None, - responsedescription: None, - location: None, - }, - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("http://www.example.com/container/front.html".into()), - vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::GetContentLength, - PropertyRequest::GetContentType, - PropertyRequest::GetEtag, - PropertyRequest::GetLastModified, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - } - ]), - error: None, - responsedescription: None, - location: None, - }, - ], - responsedescription: None, - }, - ).await; - - let expected = r#"<D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>http://www.example.com/container/</D:href> - <D:propstat> - <D:prop> - <D:creationdate/> - <D:displayname/> - <D:resourcetype/> - <D:supportedlock/> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>http://www.example.com/container/front.html</D:href> - <D:propstat> - <D:prop> - <D:creationdate/> - <D:displayname/> - <D:getcontentlength/> - <D:getcontenttype/> - <D:getetag/> - <D:getlastmodified/> - <D:resourcetype/> - <D:supportedlock/> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> -</D:multistatus>"#; - - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_allprop_req() { - let got = serialize( - &PropFind::<Core>::AllProp(None), - ).await; - - let expected = r#"<D:propfind xmlns:D="DAV:"> - <D:allprop/> -</D:propfind>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_allprop_res() { - use chrono::{DateTime,FixedOffset,TimeZone}; - let got = serialize( - &Multistatus::<Core, PropValue<Core>> { - responses: vec![ - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("/container/".into()), - vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600) - .unwrap() - .with_ymd_and_hms(1997, 12, 1, 17, 42, 21) - .unwrap()), - Property::DisplayName("Example collection".into()), - Property::ResourceType(vec![ResourceType::Collection]), - Property::SupportedLock(vec![ - LockEntry { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - }, - LockEntry { - lockscope: LockScope::Shared, - locktype: LockType::Write, - }, - ]), - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }] - ), - error: None, - responsedescription: None, - location: None, - }, - Response { - status_or_propstat: StatusOrPropstat::PropStat( - Href("/container/front.html".into()), - vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600) - .unwrap() - .with_ymd_and_hms(1997, 12, 1, 18, 27, 21) - .unwrap()), - Property::DisplayName("Example HTML resource".into()), - Property::GetContentLength(4525), - Property::GetContentType("text/html".into()), - Property::GetEtag(r#""zzyzx""#.into()), - Property::GetLastModified(FixedOffset::east_opt(0) - .unwrap() - .with_ymd_and_hms(1998, 1, 12, 9, 25, 56) - .unwrap()), - Property::ResourceType(vec![]), - Property::SupportedLock(vec![ - LockEntry { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - }, - LockEntry { - lockscope: LockScope::Shared, - locktype: LockType::Write, - }, - ]), - ]), - status: Status(http::status::StatusCode::OK), - error: None, - responsedescription: None, - }] - ), - error: None, - responsedescription: None, - location: None, - }, - ], - responsedescription: None, - } - ).await; - - let expected = r#"<D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>/container/</D:href> - <D:propstat> - <D:prop> - <D:creationdate>1997-12-01T17:42:21-08:00</D:creationdate> - <D:displayname>Example collection</D:displayname> - <D:resourcetype> - <D:collection/> - </D:resourcetype> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive/> - </D:lockscope> - <D:locktype> - <D:write/> - </D:locktype> - </D:lockentry> - <D:lockentry> - <D:lockscope> - <D:shared/> - </D:lockscope> - <D:locktype> - <D:write/> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/container/front.html</D:href> - <D:propstat> - <D:prop> - <D:creationdate>1997-12-01T18:27:21-08:00</D:creationdate> - <D:displayname>Example HTML resource</D:displayname> - <D:getcontentlength>4525</D:getcontentlength> - <D:getcontenttype>text/html</D:getcontenttype> - <D:getetag>"zzyzx"</D:getetag> - <D:getlastmodified>Mon, 12 Jan 1998 09:25:56 +0000</D:getlastmodified> - <D:resourcetype/> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive/> - </D:lockscope> - <D:locktype> - <D:write/> - </D:locktype> - </D:lockentry> - <D:lockentry> - <D:lockscope> - <D:shared/> - </D:lockscope> - <D:locktype> - <D:write/> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> -</D:multistatus>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_allprop_include() { - let got = serialize( - &PropFind::<Core>::AllProp(Some(Include(vec![ - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - ]))), - ).await; - - let expected = r#"<D:propfind xmlns:D="DAV:"> - <D:allprop/> - <D:include> - <D:displayname/> - <D:resourcetype/> - </D:include> -</D:propfind>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_propertyupdate() { - let got = serialize( - &PropertyUpdate::<Core>(vec![ - PropertyUpdateItem::Set(Set(PropValue(vec![ - Property::GetContentLanguage("fr-FR".into()), - ]))), - PropertyUpdateItem::Remove(Remove(PropName(vec![ - PropertyRequest::DisplayName, - ]))), - ]), - ).await; - - let expected = r#"<D:propertyupdate xmlns:D="DAV:"> - <D:set> - <D:prop> - <D:getcontentlanguage>fr-FR</D:getcontentlanguage> - </D:prop> - </D:set> - <D:remove> - <D:prop> - <D:displayname/> - </D:prop> - </D:remove> -</D:propertyupdate>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_delete_locked2() { - let got = serialize( - &Multistatus::<Core, PropValue<Core>> { - responses: vec![Response { - status_or_propstat: StatusOrPropstat::Status( - vec![Href("http://www.example.com/container/resource3".into())], - Status(http::status::StatusCode::from_u16(423).unwrap()) - ), - error: Some(Error(vec![Violation::LockTokenSubmitted(vec![])])), - responsedescription: None, - location: None, - }], - responsedescription: None, - }, - ).await; - - let expected = r#"<D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>http://www.example.com/container/resource3</D:href> - <D:status>HTTP/1.1 423 Locked</D:status> - <D:error> - <D:lock-token-submitted/> - </D:error> - </D:response> -</D:multistatus>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_simple_lock_request() { - let got = serialize( - &LockInfo { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))), - }, - ).await; - - let expected = r#"<D:lockinfo xmlns:D="DAV:"> - <D:lockscope> - <D:exclusive/> - </D:lockscope> - <D:locktype> - <D:write/> - </D:locktype> - <D:owner> - <D:href>http://example.org/~ejw/contact.html</D:href> - </D:owner> -</D:lockinfo>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } - - #[tokio::test] - async fn rfc_simple_lock_response() { - let got = serialize( - &PropValue::<Core>(vec![ - Property::LockDiscovery(vec![ActiveLock { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - depth: Depth::Infinity, - owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))), - timeout: Some(Timeout::Seconds(604800)), - locktoken: Some(LockToken(Href("urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4".into()))), - lockroot: LockRoot(Href("http://example.com/workspace/webdav/proposal.doc".into())), - }]), - ]), - ).await; - - let expected = r#"<D:prop xmlns:D="DAV:"> - <D:lockdiscovery> - <D:activelock> - <D:locktype> - <D:write/> - </D:locktype> - <D:lockscope> - <D:exclusive/> - </D:lockscope> - <D:depth>infinity</D:depth> - <D:owner> - <D:href>http://example.org/~ejw/contact.html</D:href> - </D:owner> - <D:timeout>Second-604800</D:timeout> - <D:locktoken> - <D:href>urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4</D:href> - </D:locktoken> - <D:lockroot> - <D:href>http://example.com/workspace/webdav/proposal.doc</D:href> - </D:lockroot> - </D:activelock> - </D:lockdiscovery> -</D:prop>"#; - - assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - } -} diff --git a/src/dav/error.rs b/src/dav/error.rs deleted file mode 100644 index 78c6d6b..0000000 --- a/src/dav/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use quick_xml::events::attributes::AttrError; - -#[derive(Debug)] -pub enum ParsingError { - Recoverable, - MissingChild, - NamespacePrefixAlreadyUsed, - WrongToken, - TagNotFound, - InvalidValue, - Utf8Error(std::str::Utf8Error), - QuickXml(quick_xml::Error), - Chrono(chrono::format::ParseError), - Int(std::num::ParseIntError), - Eof -} -impl From<AttrError> for ParsingError { - fn from(value: AttrError) -> Self { - Self::QuickXml(value.into()) - } -} -impl From<quick_xml::Error> for ParsingError { - fn from(value: quick_xml::Error) -> Self { - Self::QuickXml(value) - } -} -impl From<std::str::Utf8Error> for ParsingError { - fn from(value: std::str::Utf8Error) -> Self { - Self::Utf8Error(value) - } -} -impl From<chrono::format::ParseError> for ParsingError { - fn from(value: chrono::format::ParseError) -> Self { - Self::Chrono(value) - } -} - -impl From<std::num::ParseIntError> for ParsingError { - fn from(value: std::num::ParseIntError) -> Self { - Self::Int(value) - } -} diff --git a/src/dav/mod.rs b/src/dav/mod.rs deleted file mode 100644 index 906cfdd..0000000 --- a/src/dav/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -// utils -pub mod error; -pub mod xml; - -// webdav -pub mod types; -pub mod encoder; -pub mod decoder; - -// calendar -mod caltypes; -mod calencoder; -mod caldecoder; - -// wip -mod acltypes; -mod versioningtypes; - -// final type -pub mod realization; - - -use std::net::SocketAddr; - -use anyhow::{anyhow, Result}; -use base64::Engine; -use hyper::service::service_fn; -use hyper::{Request, Response, body::Bytes}; -use hyper::server::conn::http1 as http; -use hyper_util::rt::TokioIo; -use http_body_util::Full; -use futures::stream::{FuturesUnordered, StreamExt}; -use tokio::net::TcpListener; -use tokio::sync::watch; - -use crate::config::DavUnsecureConfig; -use crate::login::ArcLoginProvider; -use crate::user::User; - -pub struct Server { - bind_addr: SocketAddr, - login_provider: ArcLoginProvider, -} - -pub fn new_unsecure(config: DavUnsecureConfig, login: ArcLoginProvider) -> Server { - Server { - bind_addr: config.bind_addr, - login_provider: login, - } -} - -impl Server { - pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> { - let tcp = TcpListener::bind(self.bind_addr).await?; - tracing::info!("DAV server listening on {:#}", self.bind_addr); - - let mut connections = FuturesUnordered::new(); - while !*must_exit.borrow() { - let wait_conn_finished = async { - if connections.is_empty() { - futures::future::pending().await - } else { - connections.next().await - } - }; - let (socket, remote_addr) = tokio::select! { - a = tcp.accept() => a?, - _ = wait_conn_finished => continue, - _ = must_exit.changed() => continue, - }; - tracing::info!("Accepted connection from {}", remote_addr); - let stream = TokioIo::new(socket); - let login = self.login_provider.clone(); - let conn = tokio::spawn(async move { - //@FIXME should create a generic "public web" server on which "routers" could be - //abitrarily bound - //@FIXME replace with a handler supporting http2 and TLS - match http::Builder::new().serve_connection(stream, service_fn(|req: Request<hyper::body::Incoming>| { - let login = login.clone(); - async move { - auth(login, req).await - } - })).await { - Err(e) => tracing::warn!(err=?e, "connection failed"), - Ok(()) => tracing::trace!("connection terminated with success"), - } - }); - connections.push(conn); - } - drop(tcp); - - tracing::info!("Server shutting down, draining remaining connections..."); - while connections.next().await.is_some() {} - - Ok(()) - } -} - -//@FIXME We should not support only BasicAuth -async fn auth( - login: ArcLoginProvider, - req: Request<impl hyper::body::Body>, -) -> Result<Response<Full<Bytes>>> { - - let auth_val = match req.headers().get("Authorization") { - Some(hv) => hv.to_str()?, - None => return Ok(Response::builder() - .status(401) - .body(Full::new(Bytes::from("Missing Authorization field")))?), - }; - - let b64_creds_maybe_padded = match auth_val.split_once(" ") { - Some(("Basic", b64)) => b64, - _ => return Ok(Response::builder() - .status(400) - .body(Full::new(Bytes::from("Unsupported Authorization field")))?), - }; - - // base64urlencoded may have trailing equals, base64urlsafe has not - // theoretically authorization is padded but "be liberal in what you accept" - let b64_creds_clean = b64_creds_maybe_padded.trim_end_matches('='); - - // Decode base64 - let creds = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64_creds_clean)?; - let str_creds = std::str::from_utf8(&creds)?; - - // Split username and password - let (username, password) = str_creds - .split_once(':') - .ok_or(anyhow!("Missing colon in Authorization, can't split decoded value into a username/password pair"))?; - - // Call login provider - let creds = match login.login(username, password).await { - Ok(c) => c, - Err(e) => return Ok(Response::builder() - .status(401) - .body(Full::new(Bytes::from("Wrong credentials")))?), - }; - - // Build a user - let user = User::new(username.into(), creds).await?; - - // Call router with user - router(user, req).await -} - -async fn router(user: std::sync::Arc<User>, req: Request<impl hyper::body::Body>) -> Result<Response<Full<Bytes>>> { - let path_segments: Vec<_> = req.uri().path().split("/").filter(|s| *s != "").collect(); - match path_segments.as_slice() { - [] => tracing::info!("root"), - [ username, ..] if *username != user.username => return Ok(Response::builder() - .status(403) - .body(Full::new(Bytes::from("Accessing other user ressources is not allowed")))?), - [ _ ] => tracing::info!("user home"), - [ _, "calendar" ] => tracing::info!("user calendars"), - [ _, "calendar", colname ] => tracing::info!(name=colname, "selected calendar"), - [ _, "calendar", colname, member ] => tracing::info!(name=colname, obj=member, "selected event"), - _ => return Ok(Response::builder() - .status(404) - .body(Full::new(Bytes::from("Resource not found")))?), - } - Ok(Response::new(Full::new(Bytes::from("Hello World!")))) -} - -async fn collections(user: std::sync::Arc<User>, req: Request<impl hyper::body::Body>) -> Result<Response<Full<Bytes>>> { - unimplemented!(); -} diff --git a/src/dav/realization.rs b/src/dav/realization.rs deleted file mode 100644 index 33a556e..0000000 --- a/src/dav/realization.rs +++ /dev/null @@ -1,42 +0,0 @@ -use super::types as dav; -use super::caltypes as cal; -use super::xml; -use super::error; - -#[derive(Debug, PartialEq)] -pub struct Disabled(()); -impl xml::QRead<Disabled> for Disabled { - async fn qread(xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> { - Err(error::ParsingError::Recoverable) - } -} -impl xml::QWrite for Disabled { - async fn qwrite(&self, xml: &mut xml::Writer<impl xml::IWrite>) -> Result<(), quick_xml::Error> { - unreachable!(); - } -} - -/// The base WebDAV -/// -/// Any extension is kooh is disabled through an object we can't build -/// due to a private inner element. -#[derive(Debug, PartialEq)] -pub struct Core {} -impl dav::Extension for Core { - type Error = Disabled; - type Property = Disabled; - type PropertyRequest = Disabled; - type ResourceType = Disabled; -} - -// WebDAV with the base Calendar implementation (RFC4791) -#[derive(Debug, PartialEq)] -pub struct Calendar {} -impl dav::Extension for Calendar -{ - type Error = cal::Violation; - type Property = cal::Property; - type PropertyRequest = cal::PropertyRequest; - type ResourceType = cal::ResourceType; -} - diff --git a/src/dav/types.rs b/src/dav/types.rs deleted file mode 100644 index 5ea38d1..0000000 --- a/src/dav/types.rs +++ /dev/null @@ -1,950 +0,0 @@ -#![allow(dead_code)] -use std::fmt::Debug; - -use chrono::{DateTime,FixedOffset}; -use super::xml; -use super::error; - -/// It's how we implement a DAV extension -/// (That's the dark magic part...) -pub trait Extension: std::fmt::Debug + PartialEq { - type Error: xml::Node<Self::Error>; - type Property: xml::Node<Self::Property>; - type PropertyRequest: xml::Node<Self::PropertyRequest>; - type ResourceType: xml::Node<Self::ResourceType>; -} - -/// 14.1. activelock XML Element -/// -/// Name: activelock -/// -/// Purpose: Describes a lock on a resource. -/// <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?, -/// locktoken?, lockroot)> -#[derive(Debug, PartialEq)] -pub struct ActiveLock { - pub lockscope: LockScope, - pub locktype: LockType, - pub depth: Depth, - pub owner: Option<Owner>, - pub timeout: Option<Timeout>, - pub locktoken: Option<LockToken>, - pub lockroot: LockRoot, -} - -/// 14.3 collection XML Element -/// -/// Name: collection -/// -/// Purpose: Identifies the associated resource as a collection. The -/// DAV:resourcetype property of a collection resource MUST contain -/// this element. It is normally empty but extensions may add sub- -/// elements. -/// -/// <!ELEMENT collection EMPTY > -#[derive(Debug, PartialEq)] -pub struct Collection{} - -/// 14.4 depth XML Element -/// -/// Name: depth -/// -/// Purpose: Used for representing depth values in XML content (e.g., -/// in lock information). -/// -/// Value: "0" | "1" | "infinity" -/// -/// <!ELEMENT depth (#PCDATA) > -#[derive(Debug, PartialEq)] -pub enum Depth { - Zero, - One, - Infinity -} - -/// 14.5 error XML Element -/// -/// Name: error -/// -/// Purpose: Error responses, particularly 403 Forbidden and 409 -/// Conflict, sometimes need more information to indicate what went -/// wrong. In these cases, servers MAY return an XML response body -/// with a document element of 'error', containing child elements -/// identifying particular condition codes. -/// -/// Description: Contains at least one XML element, and MUST NOT -/// contain text or mixed content. Any element that is a child of the -/// 'error' element is considered to be a precondition or -/// postcondition code. Unrecognized elements MUST be ignored. -/// -/// <!ELEMENT error ANY > -#[derive(Debug, PartialEq)] -pub struct Error<E: Extension>(pub Vec<Violation<E>>); -#[derive(Debug, PartialEq)] -pub enum Violation<E: Extension> { - /// Name: lock-token-matches-request-uri - /// - /// Use with: 409 Conflict - /// - /// Purpose: (precondition) -- A request may include a Lock-Token header - /// to identify a lock for the UNLOCK method. However, if the - /// Request-URI does not fall within the scope of the lock identified - /// by the token, the server SHOULD use this error. The lock may have - /// a scope that does not include the Request-URI, or the lock could - /// have disappeared, or the token may be invalid. - LockTokenMatchesRequestUri, - - /// Name: lock-token-submitted (precondition) - /// - /// Use with: 423 Locked - /// - /// Purpose: The request could not succeed because a lock token should - /// have been submitted. This element, if present, MUST contain at - /// least one URL of a locked resource that prevented the request. In - /// cases of MOVE, COPY, and DELETE where collection locks are - /// involved, it can be difficult for the client to find out which - /// locked resource made the request fail -- but the server is only - /// responsible for returning one such locked resource. The server - /// MAY return every locked resource that prevented the request from - /// succeeding if it knows them all. - /// - /// <!ELEMENT lock-token-submitted (href+) > - LockTokenSubmitted(Vec<Href>), - - /// Name: no-conflicting-lock (precondition) - /// - /// Use with: Typically 423 Locked - /// - /// Purpose: A LOCK request failed due the presence of an already - /// existing conflicting lock. Note that a lock can be in conflict - /// although the resource to which the request was directed is only - /// indirectly locked. In this case, the precondition code can be - /// used to inform the client about the resource that is the root of - /// the conflicting lock, avoiding a separate lookup of the - /// "lockdiscovery" property. - /// - /// <!ELEMENT no-conflicting-lock (href)* > - NoConflictingLock(Vec<Href>), - - /// Name: no-external-entities - /// - /// Use with: 403 Forbidden - /// - /// Purpose: (precondition) -- If the server rejects a client request - /// because the request body contains an external entity, the server - /// SHOULD use this error. - NoExternalEntities, - - /// Name: preserved-live-properties - /// - /// Use with: 409 Conflict - /// - /// Purpose: (postcondition) -- The server received an otherwise-valid - /// MOVE or COPY request, but cannot maintain the live properties with - /// the same behavior at the destination. It may be that the server - /// only supports some live properties in some parts of the - /// repository, or simply has an internal error. - PreservedLiveProperties, - - /// Name: propfind-finite-depth - /// - /// Use with: 403 Forbidden - /// - /// Purpose: (precondition) -- This server does not allow infinite-depth - /// PROPFIND requests on collections. - PropfindFiniteDepth, - - - /// Name: cannot-modify-protected-property - /// - /// Use with: 403 Forbidden - /// - /// Purpose: (precondition) -- The client attempted to set a protected - /// property in a PROPPATCH (such as DAV:getetag). See also - /// [RFC3253], Section 3.12. - CannotModifyProtectedProperty, - - /// Specific errors - Extension(E::Error), -} - -/// 14.6. exclusive XML Element -/// -/// Name: exclusive -/// -/// Purpose: Specifies an exclusive lock. -/// -/// <!ELEMENT exclusive EMPTY > -#[derive(Debug, PartialEq)] -pub struct Exclusive {} - -/// 14.7. href XML Element -/// -/// Name: href -/// -/// Purpose: MUST contain a URI or a relative reference. -/// -/// Description: There may be limits on the value of 'href' depending -/// on the context of its use. Refer to the specification text where -/// 'href' is used to see what limitations apply in each case. -/// -/// Value: Simple-ref -/// -/// <!ELEMENT href (#PCDATA)> -#[derive(Debug, PartialEq)] -pub struct Href(pub String); - - -/// 14.8. include XML Element -/// -/// Name: include -/// -/// Purpose: Any child element represents the name of a property to be -/// included in the PROPFIND response. All elements inside an -/// 'include' XML element MUST define properties related to the -/// resource, although possible property names are in no way limited -/// to those property names defined in this document or other -/// standards. This element MUST NOT contain text or mixed content. -/// -/// <!ELEMENT include ANY > -#[derive(Debug, PartialEq)] -pub struct Include<E: Extension>(pub Vec<PropertyRequest<E>>); - -/// 14.9. location XML Element -/// -/// Name: location -/// -/// Purpose: HTTP defines the "Location" header (see [RFC2616], Section -/// 14.30) for use with some status codes (such as 201 and the 300 -/// series codes). When these codes are used inside a 'multistatus' -/// element, the 'location' element can be used to provide the -/// accompanying Location header value. -/// -/// Description: Contains a single href element with the same value -/// that would be used in a Location header. -/// -/// <!ELEMENT location (href)> -#[derive(Debug, PartialEq)] -pub struct Location(pub Href); - -/// 14.10. lockentry XML Element -/// -/// Name: lockentry -/// -/// Purpose: Defines the types of locks that can be used with the -/// resource. -/// -/// <!ELEMENT lockentry (lockscope, locktype) > -#[derive(Debug, PartialEq)] -pub struct LockEntry { - pub lockscope: LockScope, - pub locktype: LockType, -} - -/// 14.11. lockinfo XML Element -/// -/// Name: lockinfo -/// -/// Purpose: The 'lockinfo' XML element is used with a LOCK method to -/// specify the type of lock the client wishes to have created. -/// -/// <!ELEMENT lockinfo (lockscope, locktype, owner?) > -#[derive(Debug, PartialEq)] -pub struct LockInfo { - pub lockscope: LockScope, - pub locktype: LockType, - pub owner: Option<Owner>, -} - -/// 14.12. lockroot XML Element -/// -/// Name: lockroot -/// -/// Purpose: Contains the root URL of the lock, which is the URL -/// through which the resource was addressed in the LOCK request. -/// -/// Description: The href element contains the root of the lock. The -/// server SHOULD include this in all DAV:lockdiscovery property -/// values and the response to LOCK requests. -/// -/// <!ELEMENT lockroot (href) > -#[derive(Debug, PartialEq)] -pub struct LockRoot(pub Href); - -/// 14.13. lockscope XML Element -/// -/// Name: lockscope -/// -/// Purpose: Specifies whether a lock is an exclusive lock, or a shared -/// lock. -/// <!ELEMENT lockscope (exclusive | shared) > -#[derive(Debug, PartialEq)] -pub enum LockScope { - Exclusive, - Shared -} - -/// 14.14. locktoken XML Element -/// -/// Name: locktoken -/// -/// Purpose: The lock token associated with a lock. -/// -/// Description: The href contains a single lock token URI, which -/// refers to the lock. -/// -/// <!ELEMENT locktoken (href) > -#[derive(Debug, PartialEq)] -pub struct LockToken(pub Href); - -/// 14.15. locktype XML Element -/// -/// Name: locktype -/// -/// Purpose: Specifies the access type of a lock. At present, this -/// specification only defines one lock type, the write lock. -/// -/// <!ELEMENT locktype (write) > -#[derive(Debug, PartialEq)] -pub enum LockType { - /// 14.30. write XML Element - /// - /// Name: write - /// - /// Purpose: Specifies a write lock. - /// - /// - /// <!ELEMENT write EMPTY > - Write -} - -/// 14.16. multistatus XML Element -/// -/// Name: multistatus -/// -/// Purpose: Contains multiple response messages. -/// -/// Description: The 'responsedescription' element at the top level is -/// used to provide a general message describing the overarching -/// nature of the response. If this value is available, an -/// application may use it instead of presenting the individual -/// response descriptions contained within the responses. -/// -/// <!ELEMENT multistatus (response*, responsedescription?) > -#[derive(Debug, PartialEq)] -pub struct Multistatus<E: Extension, N: xml::Node<N>> { - pub responses: Vec<Response<E, N>>, - pub responsedescription: Option<ResponseDescription>, -} - -/// 14.17. owner XML Element -/// -/// Name: owner -/// -/// Purpose: Holds client-supplied information about the creator of a -/// lock. -/// -/// Description: Allows a client to provide information sufficient for -/// either directly contacting a principal (such as a telephone number -/// or Email URI), or for discovering the principal (such as the URL -/// of a homepage) who created a lock. The value provided MUST be -/// treated as a dead property in terms of XML Information Item -/// preservation. The server MUST NOT alter the value unless the -/// owner value provided by the client is empty. For a certain amount -/// of interoperability between different client implementations, if -/// clients have URI-formatted contact information for the lock -/// creator suitable for user display, then clients SHOULD put those -/// URIs in 'href' child elements of the 'owner' element. -/// -/// Extensibility: MAY be extended with child elements, mixed content, -/// text content or attributes. -/// -/// <!ELEMENT owner ANY > -//@FIXME might need support for an extension -#[derive(Debug, PartialEq)] -pub enum Owner { - Txt(String), - Href(Href), - Unknown, -} - -/// 14.18. prop XML Element -/// -/// Name: prop -/// -/// Purpose: Contains properties related to a resource. -/// -/// Description: A generic container for properties defined on -/// resources. All elements inside a 'prop' XML element MUST define -/// properties related to the resource, although possible property -/// names are in no way limited to those property names defined in -/// this document or other standards. This element MUST NOT contain -/// text or mixed content. -/// -/// <!ELEMENT prop ANY > -#[derive(Debug, PartialEq)] -pub struct PropName<E: Extension>(pub Vec<PropertyRequest<E>>); - -#[derive(Debug, PartialEq)] -pub struct PropValue<E: Extension>(pub Vec<Property<E>>); - -/// 14.19. propertyupdate XML Element -/// -/// Name: propertyupdate -/// -/// Purpose: Contains a request to alter the properties on a resource. -/// -/// Description: This XML element is a container for the information -/// required to modify the properties on the resource. -/// -/// <!ELEMENT propertyupdate (remove | set)+ > -#[derive(Debug, PartialEq)] -pub struct PropertyUpdate<E: Extension>(pub Vec<PropertyUpdateItem<E>>); - -#[derive(Debug, PartialEq)] -pub enum PropertyUpdateItem<E: Extension> { - Remove(Remove<E>), - Set(Set<E>), -} - -/// 14.2 allprop XML Element -/// -/// Name: allprop -/// -/// Purpose: Specifies that all names and values of dead properties and -/// the live properties defined by this document existing on the -/// resource are to be returned. -/// -/// <!ELEMENT allprop EMPTY > -/// -/// --- -/// -/// 14.21. propname XML Element -/// -/// Name: propname -/// -/// Purpose: Specifies that only a list of property names on the -/// resource is to be returned. -/// -/// <!ELEMENT propname EMPTY > -/// -/// --- -/// -/// 14.20. propfind XML Element -/// -/// Name: propfind -/// -/// Purpose: Specifies the properties to be returned from a PROPFIND -/// method. Four special elements are specified for use with -/// 'propfind': 'prop', 'allprop', 'include', and 'propname'. If -/// 'prop' is used inside 'propfind', it MUST NOT contain property -/// values. -/// -/// <!ELEMENT propfind ( propname | (allprop, include?) | prop ) > -#[derive(Debug, PartialEq)] -pub enum PropFind<E: Extension> { - PropName, - AllProp(Option<Include<E>>), - Prop(PropName<E>), -} - -/// 14.22 propstat XML Element -/// -/// Name: propstat -/// -/// Purpose: Groups together a prop and status element that is -/// associated with a particular 'href' element. -/// -/// Description: The propstat XML element MUST contain one prop XML -/// element and one status XML element. The contents of the prop XML -/// element MUST only list the names of properties to which the result -/// in the status element applies. The optional precondition/ -/// postcondition element and 'responsedescription' text also apply to -/// the properties named in 'prop'. -/// -/// <!ELEMENT propstat (prop, status, error?, responsedescription?) > -#[derive(Debug, PartialEq)] -pub struct PropStat<E: Extension, N: xml::Node<N>> { - pub prop: N, - pub status: Status, - pub error: Option<Error<E>>, - pub responsedescription: Option<ResponseDescription>, -} - -/// 14.23. remove XML Element -/// -/// Name: remove -/// -/// Purpose: Lists the properties to be removed from a resource. -/// -/// Description: Remove instructs that the properties specified in prop -/// should be removed. Specifying the removal of a property that does -/// not exist is not an error. All the XML elements in a 'prop' XML -/// element inside of a 'remove' XML element MUST be empty, as only -/// the names of properties to be removed are required. -/// -/// <!ELEMENT remove (prop) > -#[derive(Debug, PartialEq)] -pub struct Remove<E: Extension>(pub PropName<E>); - -/// 14.24. response XML Element -/// -/// Name: response -/// -/// Purpose: Holds a single response describing the effect of a method -/// on resource and/or its properties. -/// -/// Description: The 'href' element contains an HTTP URL pointing to a -/// WebDAV resource when used in the 'response' container. A -/// particular 'href' value MUST NOT appear more than once as the -/// child of a 'response' XML element under a 'multistatus' XML -/// element. This requirement is necessary in order to keep -/// processing costs for a response to linear time. Essentially, this -/// prevents having to search in order to group together all the -/// responses by 'href'. There are, however, no requirements -/// regarding ordering based on 'href' values. The optional -/// precondition/postcondition element and 'responsedescription' text -/// can provide additional information about this resource relative to -/// the request or result. -/// -/// <!ELEMENT response (href, ((href*, status)|(propstat+)), -/// error?, responsedescription? , location?) > -/// -/// --- rewritten as --- -/// <!ELEMENT response ((href+, status)|(href, propstat+), error?, responsedescription?, location?> -#[derive(Debug, PartialEq)] -pub enum StatusOrPropstat<E: Extension, N: xml::Node<N>> { - // One status, multiple hrefs... - Status(Vec<Href>, Status), - // A single href, multiple properties... - PropStat(Href, Vec<PropStat<E, N>>), -} - -#[derive(Debug, PartialEq)] -pub struct Response<E: Extension, N: xml::Node<N>> { - pub status_or_propstat: StatusOrPropstat<E, N>, - pub error: Option<Error<E>>, - pub responsedescription: Option<ResponseDescription>, - pub location: Option<Location>, -} - -/// 14.25. responsedescription XML Element -/// -/// Name: responsedescription -/// -/// Purpose: Contains information about a status response within a -/// Multi-Status. -/// -/// Description: Provides information suitable to be presented to a -/// user. -/// -/// <!ELEMENT responsedescription (#PCDATA) > -#[derive(Debug, PartialEq)] -pub struct ResponseDescription(pub String); - -/// 14.26. set XML Element -/// -/// Name: set -/// -/// Purpose: Lists the property values to be set for a resource. -/// -/// Description: The 'set' element MUST contain only a 'prop' element. -/// The elements contained by the 'prop' element inside the 'set' -/// element MUST specify the name and value of properties that are set -/// on the resource identified by Request-URI. If a property already -/// exists, then its value is replaced. Language tagging information -/// appearing in the scope of the 'prop' element (in the "xml:lang" -/// attribute, if present) MUST be persistently stored along with the -/// property, and MUST be subsequently retrievable using PROPFIND. -/// -/// <!ELEMENT set (prop) > -#[derive(Debug, PartialEq)] -pub struct Set<E: Extension>(pub PropValue<E>); - -/// 14.27. shared XML Element -/// -/// Name: shared -/// -/// Purpose: Specifies a shared lock. -/// -/// -/// <!ELEMENT shared EMPTY > -#[derive(Debug, PartialEq)] -pub struct Shared {} - - -/// 14.28. status XML Element -/// -/// Name: status -/// -/// Purpose: Holds a single HTTP status-line. -/// -/// Value: status-line (defined in Section 6.1 of [RFC2616]) -/// -/// <!ELEMENT status (#PCDATA) > -//@FIXME: Better typing is possible with an enum for example -#[derive(Debug, PartialEq)] -pub struct Status(pub http::status::StatusCode); - -/// 14.29. timeout XML Element -/// -/// Name: timeout -/// -/// Purpose: The number of seconds remaining before a lock expires. -/// -/// Value: TimeType (defined in Section 10.7) -/// -/// -/// <!ELEMENT timeout (#PCDATA) > -/// -/// TimeOut = "Timeout" ":" 1#TimeType -/// TimeType = ("Second-" DAVTimeOutVal | "Infinite") -/// ; No LWS allowed within TimeType -/// DAVTimeOutVal = 1*DIGIT -/// -/// Clients MAY include Timeout request headers in their LOCK requests. -/// However, the server is not required to honor or even consider these -/// requests. Clients MUST NOT submit a Timeout request header with any -/// method other than a LOCK method. -/// -/// The "Second" TimeType specifies the number of seconds that will -/// elapse between granting of the lock at the server, and the automatic -/// removal of the lock. The timeout value for TimeType "Second" MUST -/// NOT be greater than 2^32-1. -#[derive(Debug, PartialEq)] -pub enum Timeout { - Seconds(u32), - Infinite, -} - - -/// 15. DAV Properties -/// -/// For DAV properties, the name of the property is also the same as the -/// name of the XML element that contains its value. In the section -/// below, the final line of each section gives the element type -/// declaration using the format defined in [REC-XML]. The "Value" -/// field, where present, specifies further restrictions on the allowable -/// contents of the XML element using BNF (i.e., to further restrict the -/// values of a PCDATA element). -/// -/// A protected property is one that cannot be changed with a PROPPATCH -/// request. There may be other requests that would result in a change -/// to a protected property (as when a LOCK request affects the value of -/// DAV:lockdiscovery). Note that a given property could be protected on -/// one type of resource, but not protected on another type of resource. -/// -/// A computed property is one with a value defined in terms of a -/// computation (based on the content and other properties of that -/// resource, or even of some other resource). A computed property is -/// always a protected property. -/// -/// COPY and MOVE behavior refers to local COPY and MOVE operations. -/// -/// For properties defined based on HTTP GET response headers (DAV:get*), -/// the header value could include LWS as defined in [RFC2616], Section -/// 4.2. Server implementors SHOULD strip LWS from these values before -/// using as WebDAV property values. -#[derive(Debug, PartialEq)] -pub enum PropertyRequest<E: Extension> { - CreationDate, - DisplayName, - GetContentLanguage, - GetContentLength, - GetContentType, - GetEtag, - GetLastModified, - LockDiscovery, - ResourceType, - SupportedLock, - Extension(E::PropertyRequest), -} - -#[derive(Debug, PartialEq)] -pub enum Property<E: Extension> { - /// 15.1. creationdate Property - /// - /// Name: creationdate - /// - /// Purpose: Records the time and date the resource was created. - /// - /// Value: date-time (defined in [RFC3339], see the ABNF in Section - /// 5.6.) - /// - /// Protected: MAY be protected. Some servers allow DAV:creationdate - /// to be changed to reflect the time the document was created if that - /// is more meaningful to the user (rather than the time it was - /// uploaded). Thus, clients SHOULD NOT use this property in - /// synchronization logic (use DAV:getetag instead). - /// - /// COPY/MOVE behavior: This property value SHOULD be kept during a - /// MOVE operation, but is normally re-initialized when a resource is - /// created with a COPY. It should not be set in a COPY. - /// - /// Description: The DAV:creationdate property SHOULD be defined on all - /// DAV compliant resources. If present, it contains a timestamp of - /// the moment when the resource was created. Servers that are - /// incapable of persistently recording the creation date SHOULD - /// instead leave it undefined (i.e. report "Not Found"). - /// - /// <!ELEMENT creationdate (#PCDATA) > - CreationDate(DateTime<FixedOffset>), - - /// 15.2. displayname Property - /// - /// Name: displayname - /// - /// Purpose: Provides a name for the resource that is suitable for - /// presentation to a user. - /// - /// Value: Any text. - /// - /// Protected: SHOULD NOT be protected. Note that servers implementing - /// [RFC2518] might have made this a protected property as this is a - /// new requirement. - /// - /// COPY/MOVE behavior: This property value SHOULD be preserved in COPY - /// and MOVE operations. - /// - /// Description: Contains a description of the resource that is - /// suitable for presentation to a user. This property is defined on - /// the resource, and hence SHOULD have the same value independent of - /// the Request-URI used to retrieve it (thus, computing this property - /// based on the Request-URI is deprecated). While generic clients - /// might display the property value to end users, client UI designers - /// must understand that the method for identifying resources is still - /// the URL. Changes to DAV:displayname do not issue moves or copies - /// to the server, but simply change a piece of meta-data on the - /// individual resource. Two resources can have the same DAV: - /// displayname value even within the same collection. - /// - /// <!ELEMENT displayname (#PCDATA) > - DisplayName(String), - - - /// 15.3. getcontentlanguage Property - /// - /// Name: getcontentlanguage - /// - /// Purpose: Contains the Content-Language header value (from Section - /// 14.12 of [RFC2616]) as it would be returned by a GET without - /// accept headers. - /// - /// Value: language-tag (language-tag is defined in Section 3.10 of - /// [RFC2616]) - /// - /// Protected: SHOULD NOT be protected, so that clients can reset the - /// language. Note that servers implementing [RFC2518] might have - /// made this a protected property as this is a new requirement. - /// - /// COPY/MOVE behavior: This property value SHOULD be preserved in COPY - /// and MOVE operations. - /// - /// Description: The DAV:getcontentlanguage property MUST be defined on - /// any DAV-compliant resource that returns the Content-Language - /// header on a GET. - /// - /// <!ELEMENT getcontentlanguage (#PCDATA) > - GetContentLanguage(String), - - /// 15.4. getcontentlength Property - /// - /// Name: getcontentlength - /// - /// Purpose: Contains the Content-Length header returned by a GET - /// without accept headers. - /// - /// Value: See Section 14.13 of [RFC2616]. - /// - /// Protected: This property is computed, therefore protected. - /// - /// Description: The DAV:getcontentlength property MUST be defined on - /// any DAV-compliant resource that returns the Content-Length header - /// in response to a GET. - /// - /// COPY/MOVE behavior: This property value is dependent on the size of - /// the destination resource, not the value of the property on the - /// source resource. - /// - /// <!ELEMENT getcontentlength (#PCDATA) > - GetContentLength(u64), - - /// 15.5. getcontenttype Property - /// - /// Name: getcontenttype - /// - /// Purpose: Contains the Content-Type header value (from Section 14.17 - /// of [RFC2616]) as it would be returned by a GET without accept - /// headers. - /// - /// Value: media-type (defined in Section 3.7 of [RFC2616]) - /// - /// Protected: Potentially protected if the server prefers to assign - /// content types on its own (see also discussion in Section 9.7.1). - /// - /// COPY/MOVE behavior: This property value SHOULD be preserved in COPY - /// and MOVE operations. - /// - /// Description: This property MUST be defined on any DAV-compliant - /// resource that returns the Content-Type header in response to a - /// GET. - /// - /// <!ELEMENT getcontenttype (#PCDATA) > - GetContentType(String), - - /// 15.6. getetag Property - /// - /// Name: getetag - /// - /// Purpose: Contains the ETag header value (from Section 14.19 of - /// [RFC2616]) as it would be returned by a GET without accept - /// headers. - /// - /// Value: entity-tag (defined in Section 3.11 of [RFC2616]) - /// - /// Protected: MUST be protected because this value is created and - /// controlled by the server. - /// - /// COPY/MOVE behavior: This property value is dependent on the final - /// state of the destination resource, not the value of the property - /// on the source resource. Also note the considerations in - /// Section 8.8. - /// - /// Description: The getetag property MUST be defined on any DAV- - /// compliant resource that returns the Etag header. Refer to Section - /// 3.11 of RFC 2616 for a complete definition of the semantics of an - /// ETag, and to Section 8.6 for a discussion of ETags in WebDAV. - /// - /// <!ELEMENT getetag (#PCDATA) > - GetEtag(String), - - /// 15.7. getlastmodified Property - /// - /// Name: getlastmodified - /// - /// Purpose: Contains the Last-Modified header value (from Section - /// 14.29 of [RFC2616]) as it would be returned by a GET method - /// without accept headers. - /// - /// Value: rfc1123-date (defined in Section 3.3.1 of [RFC2616]) - /// - /// Protected: SHOULD be protected because some clients may rely on the - /// value for appropriate caching behavior, or on the value of the - /// Last-Modified header to which this property is linked. - /// - /// COPY/MOVE behavior: This property value is dependent on the last - /// modified date of the destination resource, not the value of the - /// property on the source resource. Note that some server - /// implementations use the file system date modified value for the - /// DAV:getlastmodified value, and this can be preserved in a MOVE - /// even when the HTTP Last-Modified value SHOULD change. Note that - /// since [RFC2616] requires clients to use ETags where provided, a - /// server implementing ETags can count on clients using a much better - /// mechanism than modification dates for offline synchronization or - /// cache control. Also note the considerations in Section 8.8. - /// - /// Description: The last-modified date on a resource SHOULD only - /// reflect changes in the body (the GET responses) of the resource. - /// A change in a property only SHOULD NOT cause the last-modified - /// date to change, because clients MAY rely on the last-modified date - /// to know when to overwrite the existing body. The DAV: - /// getlastmodified property MUST be defined on any DAV-compliant - /// resource that returns the Last-Modified header in response to a - /// GET. - /// - /// <!ELEMENT getlastmodified (#PCDATA) > - GetLastModified(DateTime<FixedOffset>), - - /// 15.8. lockdiscovery Property - /// - /// Name: lockdiscovery - /// - /// Purpose: Describes the active locks on a resource - /// - /// Protected: MUST be protected. Clients change the list of locks - /// through LOCK and UNLOCK, not through PROPPATCH. - /// - /// COPY/MOVE behavior: The value of this property depends on the lock - /// state of the destination, not on the locks of the source resource. - /// Recall that locks are not moved in a MOVE operation. - /// - /// Description: Returns a listing of who has a lock, what type of lock - /// he has, the timeout type and the time remaining on the timeout, - /// and the associated lock token. Owner information MAY be omitted - /// if it is considered sensitive. If there are no locks, but the - /// server supports locks, the property will be present but contain - /// zero 'activelock' elements. If there are one or more locks, an - /// 'activelock' element appears for each lock on the resource. This - /// property is NOT lockable with respect to write locks (Section 7). - /// - /// <!ELEMENT lockdiscovery (activelock)* > - LockDiscovery(Vec<ActiveLock>), - - - /// 15.9. resourcetype Property - /// - /// Name: resourcetype - /// - /// Purpose: Specifies the nature of the resource. - /// - /// Protected: SHOULD be protected. Resource type is generally decided - /// through the operation creating the resource (MKCOL vs PUT), not by - /// PROPPATCH. - /// - /// COPY/MOVE behavior: Generally a COPY/MOVE of a resource results in - /// the same type of resource at the destination. - /// - /// Description: MUST be defined on all DAV-compliant resources. Each - /// child element identifies a specific type the resource belongs to, - /// such as 'collection', which is the only resource type defined by - /// this specification (see Section 14.3). If the element contains - /// the 'collection' child element plus additional unrecognized - /// elements, it should generally be treated as a collection. If the - /// element contains no recognized child elements, it should be - /// treated as a non-collection resource. The default value is empty. - /// This element MUST NOT contain text or mixed content. Any custom - /// child element is considered to be an identifier for a resource - /// type. - /// - /// Example: (fictional example to show extensibility) - /// - /// <x:resourcetype xmlns:x="DAV:"> - /// <x:collection/> - /// <f:search-results xmlns:f="http://www.example.com/ns"/> - /// </x:resourcetype> - ResourceType(Vec<ResourceType<E>>), - - /// 15.10. supportedlock Property - /// - /// Name: supportedlock - /// - /// Purpose: To provide a listing of the lock capabilities supported by - /// the resource. - /// - /// Protected: MUST be protected. Servers, not clients, determine what - /// lock mechanisms are supported. - /// COPY/MOVE behavior: This property value is dependent on the kind of - /// locks supported at the destination, not on the value of the - /// property at the source resource. Servers attempting to COPY to a - /// destination should not attempt to set this property at the - /// destination. - /// - /// Description: Returns a listing of the combinations of scope and - /// access types that may be specified in a lock request on the - /// resource. Note that the actual contents are themselves controlled - /// by access controls, so a server is not required to provide - /// information the client is not authorized to see. This property is - /// NOT lockable with respect to write locks (Section 7). - /// - /// <!ELEMENT supportedlock (lockentry)* > - SupportedLock(Vec<LockEntry>), - - /// Any extension - Extension(E::Property), -} - -#[derive(Debug, PartialEq)] -pub enum ResourceType<E: Extension> { - Collection, - Extension(E::ResourceType), -} diff --git a/src/dav/versioningtypes.rs b/src/dav/versioningtypes.rs deleted file mode 100644 index 6c1c204..0000000 --- a/src/dav/versioningtypes.rs +++ /dev/null @@ -1,3 +0,0 @@ -//@FIXME required for a full DAV implementation -// See section 7.1 of the CalDAV RFC -// It seems it's mainly due to the fact that the REPORT method is re-used. diff --git a/src/dav/xml.rs b/src/dav/xml.rs deleted file mode 100644 index 02263fd..0000000 --- a/src/dav/xml.rs +++ /dev/null @@ -1,273 +0,0 @@ -use tokio::io::{AsyncWrite, AsyncBufRead}; -use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; -use quick_xml::name::{Namespace, QName, PrefixDeclaration, ResolveResult, ResolveResult::*}; -use quick_xml::reader::NsReader; - -use super::error::ParsingError; - -// Constants -pub const DAV_URN: &[u8] = b"DAV:"; -pub const CAL_URN: &[u8] = b"urn:ietf:params:xml:ns:caldav"; -pub const CARD_URN: &[u8] = b"urn:ietf:params:xml:ns:carddav"; - -// Async traits -pub trait IWrite = AsyncWrite + Unpin; -pub trait IRead = AsyncBufRead + Unpin; - -// Serialization/Deserialization traits -pub trait QWrite { - async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), quick_xml::Error>; -} -pub trait QRead<T> { - async fn qread(xml: &mut Reader<impl IRead>) -> Result<T, ParsingError>; -} - -// The representation of an XML node in Rust -pub trait Node<T> = QRead<T> + QWrite + std::fmt::Debug + PartialEq; - -// --------------- - -/// Transform a Rust object into an XML stream of characters -pub struct Writer<T: IWrite> { - pub q: quick_xml::writer::Writer<T>, - pub ns_to_apply: Vec<(String, String)>, -} -impl<T: IWrite> Writer<T> { - pub fn create_dav_element(&mut self, name: &str) -> BytesStart<'static> { - self.create_ns_element("D", name) - } - pub fn create_cal_element(&mut self, name: &str) -> BytesStart<'static> { - self.create_ns_element("C", name) - } - - fn create_ns_element(&mut self, ns: &str, name: &str) -> BytesStart<'static> { - let mut start = BytesStart::new(format!("{}:{}", ns, name)); - if !self.ns_to_apply.is_empty() { - start.extend_attributes(self.ns_to_apply.iter().map(|(k, n)| (k.as_str(), n.as_str()))); - self.ns_to_apply.clear() - } - start - } -} - -/// Transform an XML stream of characters into a Rust object -pub struct Reader<T: IRead> { - pub rdr: NsReader<T>, - cur: Event<'static>, - parents: Vec<Event<'static>>, - buf: Vec<u8>, -} -impl<T: IRead> Reader<T> { - pub async fn new(mut rdr: NsReader<T>) -> Result<Self, ParsingError> { - let mut buf: Vec<u8> = vec![]; - let cur = rdr.read_event_into_async(&mut buf).await?.into_owned(); - let parents = vec![]; - buf.clear(); - Ok(Self { cur, parents, rdr, buf }) - } - - /// read one more tag - /// do not expose it publicly - async fn next(&mut self) -> Result<Event<'static>, ParsingError> { - let evt = self.rdr.read_event_into_async(&mut self.buf).await?.into_owned(); - self.buf.clear(); - let old_evt = std::mem::replace(&mut self.cur, evt); - Ok(old_evt) - } - - /// skip a node at current level - /// I would like to make this one private but not ready - pub async fn skip(&mut self) -> Result<Event<'static>, ParsingError> { - //println!("skipping inside node {:?}", self.parents.last()); - match &self.cur { - Event::Start(b) => { - let _span = self.rdr.read_to_end_into_async(b.to_end().name(), &mut self.buf).await?; - self.next().await - }, - Event::End(_) => Err(ParsingError::WrongToken), - Event::Eof => Err(ParsingError::Eof), - _ => self.next().await, - } - } - - /// check if this is the desired tag - fn is_tag(&self, ns: &[u8], key: &str) -> bool { - let qname = match self.peek() { - Event::Start(bs) | Event::Empty(bs) => bs.name(), - Event::End(be) => be.name(), - _ => return false, - }; - - let (extr_ns, local) = self.rdr.resolve_element(qname); - - if local.into_inner() != key.as_bytes() { - return false - } - - match extr_ns { - ResolveResult::Bound(v) => v.into_inner() == ns, - _ => false, - } - } - - fn parent_has_child(&self) -> bool { - matches!(self.parents.last(), Some(Event::Start(_)) | None) - } - - fn ensure_parent_has_child(&self) -> Result<(), ParsingError> { - match self.parent_has_child() { - true => Ok(()), - false => Err(ParsingError::Recoverable), - } - } - - pub fn peek(&self) -> &Event<'static> { - &self.cur - } - - // NEW API - pub async fn tag_string(&mut self) -> Result<String, ParsingError> { - self.ensure_parent_has_child()?; - - let mut acc = String::new(); - loop { - match self.peek() { - Event::CData(unescaped) => { - acc.push_str(std::str::from_utf8(unescaped.as_ref())?); - self.next().await? - }, - Event::Text(escaped) => { - acc.push_str(escaped.unescape()?.as_ref()); - self.next().await? - } - Event::End(_) | Event::Start(_) | Event::Empty(_) => return Ok(acc), - _ => self.next().await?, - }; - } - } - - pub async fn maybe_read<N: Node<N>>(&mut self, t: &mut Option<N>, dirty: &mut bool) -> Result<(), ParsingError> { - if !self.parent_has_child() { - return Ok(()) - } - - match N::qread(self).await { - Ok(v) => { - *t = Some(v); - *dirty = true; - Ok(()) - }, - Err(ParsingError::Recoverable) => Ok(()), - Err(e) => Err(e), - } - } - - pub async fn maybe_push<N: Node<N>>(&mut self, t: &mut Vec<N>, dirty: &mut bool) -> Result<(), ParsingError> { - if !self.parent_has_child() { - return Ok(()) - } - - match N::qread(self).await { - Ok(v) => { - t.push(v); - *dirty = true; - Ok(()) - }, - Err(ParsingError::Recoverable) => Ok(()), - Err(e) => Err(e), - } - } - - pub async fn find<N: Node<N>>(&mut self) -> Result<N, ParsingError> { - self.ensure_parent_has_child()?; - - loop { - // Try parse - match N::qread(self).await { - Err(ParsingError::Recoverable) => (), - otherwise => return otherwise, - } - - // If recovered, skip the element - self.skip().await?; - } - } - - pub async fn maybe_find<N: Node<N>>(&mut self) -> Result<Option<N>, ParsingError> { - self.ensure_parent_has_child()?; - - loop { - // Try parse - match N::qread(self).await { - Err(ParsingError::Recoverable) => (), - otherwise => return otherwise.map(Some), - } - - match self.peek() { - Event::End(_) => return Ok(None), - _ => self.skip().await?, - }; - } - } - - pub async fn collect<N: Node<N>>(&mut self) -> Result<Vec<N>, ParsingError> { - self.ensure_parent_has_child()?; - let mut acc = Vec::new(); - - loop { - match N::qread(self).await { - Err(ParsingError::Recoverable) => match self.peek() { - Event::End(_) => return Ok(acc), - _ => { - self.skip().await?; - }, - }, - Ok(v) => acc.push(v), - Err(e) => return Err(e), - } - } - } - - pub async fn open(&mut self, ns: &[u8], key: &str) -> Result<Event<'static>, ParsingError> { - let evt = match self.peek() { - Event::Empty(_) if self.is_tag(ns, key) => self.cur.clone(), - Event::Start(_) if self.is_tag(ns, key) => self.next().await?, - _ => return Err(ParsingError::Recoverable), - }; - - //println!("open tag {:?}", evt); - self.parents.push(evt.clone()); - Ok(evt) - } - - pub async fn maybe_open(&mut self, ns: &[u8], key: &str) -> Result<Option<Event<'static>>, ParsingError> { - match self.open(ns, key).await { - Ok(v) => Ok(Some(v)), - Err(ParsingError::Recoverable) => Ok(None), - Err(e) => Err(e), - } - } - - // find stop tag - pub async fn close(&mut self) -> Result<Event<'static>, ParsingError> { - //println!("close tag {:?}", self.parents.last()); - - // Handle the empty case - if !self.parent_has_child() { - self.parents.pop(); - return self.next().await - } - - // Handle the start/end case - loop { - match self.peek() { - Event::End(_) => { - self.parents.pop(); - return self.next().await - }, - _ => self.skip().await?, - }; - } - } -} - |