diff --git a/src/dav/calencoder.rs b/src/dav/calencoder.rs new file mode 100644 index 0000000..918083d --- /dev/null +++ b/src/dav/calencoder.rs @@ -0,0 +1,100 @@ +use super::encoder::{QuickWritable, Context}; +use super::caltypes::*; +use super::types::Extension; + +use quick_xml::Error as QError; +use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; +use quick_xml::writer::{ElementWriter, Writer}; +use quick_xml::name::PrefixDeclaration; +use tokio::io::AsyncWrite; + +/*pub trait CalWriter: DavWriter { + fn create_cal_element(&mut self, name: &str) -> ElementWriter; +} + +impl<'a, W: AsyncWrite+Unpin> DavWriter for Writer<'a, W, CalExtension> { + fn create_dav_element(&mut self, name: &str) -> ElementWriter { + self.create_ns_element(name, Namespace::Dav) + } + fn child(w: &'a mut QWriter) -> impl DavWriter { + Self::child(w) + } + async fn error(&mut self, err: &Violation) -> Result<(), QError> { + err.write(self).await + } +} + +impl<'a, W: AsyncWrite+Unpin> CalWriter for Writer<'a, W, CalExtension> { + fn create_cal_element(&mut self, name: &str) -> ElementWriter { + self.create_ns_element(name, Namespace::CalDav) + } +}*/ + +pub struct CalCtx { + root: bool +} +impl Context for CalCtx { + fn child(&self) -> Self { + Self { root: false } + } + fn create_dav_element(&self, name: &str) -> BytesStart { + let mut start = BytesStart::new(format!("D:{}", name)); + if self.root { + start.push_attribute(("xmlns:D", "DAV:")); + start.push_attribute(("xmlns:C", "urn:ietf:params:xml:ns:caldav")); + } + start + } + + async fn hook_error(&self, err: &Violation, xml: &mut Writer) -> Result<(), QError> { + err.write(xml, self.child()).await + } +} + +impl QuickWritable for Violation { + async fn write(&self, xml: &mut Writer, _ctx: CalCtx) -> Result<(), QError> { + match self { + Self::SupportedFilter => xml + .create_element("supported-filter") + .write_empty_async().await?, + }; + Ok(()) + } +} + +/* + + + + + + +*/ + +#[cfg(test)] +mod tests { + use super::*; + use crate::dav::types::{Error, Violation as DavViolation}; + use tokio::io::AsyncWriteExt; + + #[tokio::test] + async fn test_violation() { + let mut buffer = Vec::new(); + let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); + let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); + + let res: Error = Error(vec![ + DavViolation::Extension(Violation::SupportedFilter), + ]); + + res.write(&mut writer, CalCtx{ root: true }).await.expect("xml serialization"); + tokio_buffer.flush().await.expect("tokio buffer flush"); + + let expected = r#" + +"#; + let got = std::str::from_utf8(buffer.as_slice()).unwrap(); + + assert_eq!(got, expected); + } +} diff --git a/src/dav/caltypes.rs b/src/dav/caltypes.rs new file mode 100644 index 0000000..ed8496a --- /dev/null +++ b/src/dav/caltypes.rs @@ -0,0 +1,41 @@ +use super::types::*; + +pub enum Namespace { + Dav, + CalDav, +} + +pub struct CalExtension {} +impl Extension for CalExtension { + type Error = Violation; + type Namespace = Namespace; + + fn namespaces() -> &'static [(&'static str, &'static str)] { + return &[ ("D", "DAV:"), ("C", "urn:ietf:params:xml:ns:caldav") ][..] + } + + fn short_ns(ns: Self::Namespace) -> &'static str { + match ns { + Namespace::Dav => "D", + Namespace::CalDav => "C", + } + } +} + +pub enum Violation { + /// (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. + /// + /// + SupportedFilter, +} diff --git a/src/dav/encoder.rs b/src/dav/encoder.rs index 552f183..ddef533 100644 --- a/src/dav/encoder.rs +++ b/src/dav/encoder.rs @@ -1,61 +1,205 @@ use std::io::Cursor; -use futures::stream::{StreamExt, TryStreamExt}; -use quick_xml::Error; +use quick_xml::Error as QError; use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; use quick_xml::writer::{ElementWriter, Writer}; use quick_xml::name::PrefixDeclaration; use tokio::io::AsyncWrite; use super::types::*; -//@FIXME a cleaner way to manager namespace would be great -//but at the same time, the quick-xml library is not cooperating. -//So instead of writing many cursed workarounds - I tried, I am just hardcoding the namespaces... -pub trait Encode { - async fn write(&self, xml: &mut Writer) -> Result<(), Error>; +//-------------- TRAITS ---------------------- +/*pub trait DavWriter { + fn create_dav_element(&mut self, name: &str) -> ElementWriter; + fn child(w: &mut QWriter) -> impl DavWriter; + async fn error(&mut self, err: &E::Error) -> Result<(), QError>; +}*/ + +/// Basic encode trait to make a type encodable +pub trait QuickWritable> { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError>; } -impl Encode for Href { - async fn write(&self, xml: &mut Writer) -> Result<(), Error> { - xml.create_element("D:href") +pub trait Context { + fn child(&self) -> Self; + fn create_dav_element(&self, name: &str) -> BytesStart; + async fn hook_error(&self, err: &E::Error, xml: &mut Writer) -> Result<(), QError>; +} + +pub struct NoExtCtx { + root: bool +} +impl Context for NoExtCtx { + fn child(&self) -> Self { + Self { root: false } + } + fn create_dav_element(&self, name: &str) -> BytesStart { + let mut start = BytesStart::new(format!("D:{}", name)); + if self.root { + start.push_attribute(("xmlns:D", "DAV:")); + } + start + } + async fn hook_error(&self, err: &Disabled, xml: &mut Writer) -> Result<(), QError> { + unreachable!(); + } +} + + +//--------------------- ENCODING -------------------- + +// --- XML ROOTS +impl> QuickWritable for Multistatus { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + let start = ctx.create_dav_element("multistatus"); + let end = start.to_end(); + + xml.write_event_async(Event::Start(start.clone())).await?; + for response in self.responses.iter() { + response.write(xml, ctx.child()).await?; + } + if let Some(description) = &self.responsedescription { + description.write(xml, ctx.child()).await?; + } + + xml.write_event_async(Event::End(end)).await?; + Ok(()) + } +} + + +// --- XML inner elements +impl> QuickWritable for Href { + async fn write(&self, xml: &mut Writer, _ctx: C) -> Result<(), QError> { + xml.create_element("href") .write_text_content_async(BytesText::new(&self.0)) .await?; Ok(()) } } -impl Encode for Multistatus { - async fn write(&self, xml: &mut Writer) -> Result<(), Error> { - xml.create_element("D:multistatus") - .with_attribute(("xmlns:D", "DAV:")) - .write_inner_content_async::<_, _, quick_xml::Error>(|inner_xml| async move { - for response in self.responses.iter() { - response.write(inner_xml).await?; +impl> QuickWritable for Response { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + xml.create_element("response") + .write_inner_content_async::<_, _, QError>(|inner_xml| async move { + self.href.write(inner_xml, ctx.child()).await?; + self.status_or_propstat.write(inner_xml, ctx.child()).await?; + if let Some(error) = &self.error { + error.write(inner_xml, ctx.child()).await?; } - - if let Some(description) = &self.responsedescription { - description.write(inner_xml).await?; + if let Some(responsedescription) = &self.responsedescription { + responsedescription.write(inner_xml, ctx.child()).await?; } - + if let Some(location) = &self.location { + location.write(inner_xml, ctx.child()).await?; + } + Ok(inner_xml) }) + .await?; + + Ok(()) + } +} + +impl> QuickWritable for StatusOrPropstat { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + match self { + Self::Status(status) => status.write(xml, ctx.child()).await, + Self::PropStat(propstat_list) => { + for propstat in propstat_list.iter() { + propstat.write(xml, ctx.child()).await?; + } + + Ok(()) + } + } + } +} + +impl> QuickWritable for Status { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + xml.create_element("status") + .write_text_content_async( + BytesText::new(&format!("HTTP/1.1 {} {}", self.0.as_str(), self.0.canonical_reason().unwrap_or("No reason"))) + ) .await?; Ok(()) } } -impl Encode for Response { - async fn write(&self, xml: &mut Writer) -> Result<(), Error> { +impl> QuickWritable for ResponseDescription { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + let start = ctx.create_dav_element("responsedescription"); + let end = start.to_end(); + + xml.write_event_async(Event::Start(start.clone())).await?; + xml.write_event_async(Event::Text(BytesText::new(&self.0))).await?; + xml.write_event_async(Event::End(end)).await?; + + Ok(()) + } +} + +impl> QuickWritable for Location { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { unimplemented!(); } } -impl Encode for ResponseDescription { - async fn write(&self, xml: &mut Writer) -> Result<(), Error> { - xml.create_element("D:responsedescription") - .write_text_content_async(BytesText::new(&self.0)) - .await?; +impl> QuickWritable for PropStat { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + unimplemented!(); + } +} + +impl> QuickWritable for Error { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + xml.create_element("error") + .write_inner_content_async::<_, _, QError>(|inner_xml| async move { + for violation in &self.0 { + violation.write(inner_xml, ctx.child()).await?; + } + + Ok(inner_xml) + }) + .await?; + + Ok(()) + } +} + +impl> QuickWritable for Violation { + async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { + match self { + Violation::LockTokenMatchesRequestUri => xml.create_element("lock-token-matches-request-uri").write_empty_async().await?, + Violation::LockTokenSubmitted(hrefs) => xml + .create_element("lock-token-submitted") + .write_inner_content_async::<_, _, QError>(|inner_xml| async move { + for href in hrefs { + href.write(inner_xml, ctx.child()).await?; + } + Ok(inner_xml) + } + ).await?, + Violation::NoConflictingLock(hrefs) => xml + .create_element("no-conflicting-lock") + .write_inner_content_async::<_, _, QError>(|inner_xml| async move { + for href in hrefs { + href.write(inner_xml, ctx.child()).await?; + } + Ok(inner_xml) + } + ).await?, + Violation::NoExternalEntities => xml.create_element("no-external-entities").write_empty_async().await?, + Violation::PreservedLiveProperties => xml.create_element("preserved-live-properties").write_empty_async().await?, + Violation::PropfindFiniteDepth => xml.create_element("propfind-finite-depth").write_empty_async().await?, + Violation::CannotModifyProtectedProperty => xml.create_element("cannot-modify-protected-property").write_empty_async().await?, + Violation::Extension(inner) => { + ctx.hook_error(inner, xml).await?; + xml + }, + }; Ok(()) } } @@ -74,7 +218,8 @@ mod tests { let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); - Href("/SOGo/dav/so/".into()).write(&mut writer).await.expect("xml serialization"); + let ctx = NoExtCtx{ root: true }; + Href("/SOGo/dav/so/".into()).write(&mut writer, ctx).await.expect("xml serialization"); tokio_buffer.flush().await.expect("tokio buffer flush"); assert_eq!(buffer.as_slice(), &b"/SOGo/dav/so/"[..]); @@ -87,8 +232,9 @@ mod tests { let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); - let xml: Multistatus = Multistatus { responses: vec![], responsedescription: Some(ResponseDescription("Hello world".into())) }; - xml.write(&mut writer).await.expect("xml serialization"); + let ctx = NoExtCtx{ root: true }; + let xml: Multistatus = Multistatus { responses: vec![], responsedescription: Some(ResponseDescription("Hello world".into())) }; + xml.write(&mut writer, ctx).await.expect("xml serialization"); tokio_buffer.flush().await.expect("tokio buffer flush"); let expected = r#" diff --git a/src/dav/mod.rs b/src/dav/mod.rs index a542bbb..98d6965 100644 --- a/src/dav/mod.rs +++ b/src/dav/mod.rs @@ -1,5 +1,7 @@ mod types; +mod caltypes; mod encoder; +mod calencoder; use std::net::SocketAddr; diff --git a/src/dav/types.rs b/src/dav/types.rs index 7bbea8e..69ddf52 100644 --- a/src/dav/types.rs +++ b/src/dav/types.rs @@ -2,6 +2,34 @@ use chrono::{DateTime,FixedOffset}; +/// Extension utilities +pub struct Disabled(()); +pub trait Extension { + type Error; + type Namespace; + + fn namespaces() -> &'static [(&'static str, &'static str)]; + fn short_ns(ns: Self::Namespace) -> &'static str; +} + +/// No extension +pub struct NoExtension {} +pub enum Namespace { + Dav +} +impl Extension for NoExtension { + type Error = Disabled; + type Namespace = Namespace; + + fn namespaces() -> &'static [(&'static str, &'static str)] { + return &[ ("D", "DAV:") ][..] + } + + fn short_ns(ns: Self::Namespace) -> &'static str { + "D" + } +} + /// 14.1. activelock XML Element /// /// Name: activelock @@ -10,11 +38,11 @@ use chrono::{DateTime,FixedOffset}; /// pub struct ActiveLock { - lockscope: u64, - locktype: u64, + lockscope: LockScope, + locktype: LockType, depth: Depth, - owner: Option, - timeout: Option, + owner: Option, + timeout: Option, } /// 14.2 allprop XML Element @@ -72,7 +100,8 @@ pub enum Depth { /// postcondition code. Unrecognized elements MUST be ignored. /// /// -pub enum Error { +pub struct Error(pub Vec>); +pub enum Violation { /// Name: lock-token-matches-request-uri /// /// Use with: 409 Conflict @@ -156,7 +185,7 @@ pub enum Error { CannotModifyProtectedProperty, /// Specific errors - Extensions(T), + Extension(T::Error), } /// 14.6. exclusive XML Element @@ -312,7 +341,7 @@ pub enum LockType { /// response descriptions contained within the responses. /// /// -pub struct Multistatus { +pub struct Multistatus { pub responses: Vec>, pub responsedescription: Option, } @@ -416,7 +445,7 @@ pub struct PropName {} /// the properties named in 'prop'. /// /// -pub struct PropStat { +pub struct PropStat { prop: Prop, status: Status, error: Option>, @@ -460,13 +489,16 @@ pub struct Remove(Prop); /// /// -pub struct Response { - href: Vec, - status: Status, - propstat: Vec>, - error: Option>, - responsedescription: Option, - location: Option, +pub enum StatusOrPropstat { + Status(Status), + PropStat(Vec>), +} +pub struct Response { + pub href: Href, // It's wrong according to the spec, but I don't understand why there is an href* + pub status_or_propstat: StatusOrPropstat, + pub error: Option>, + pub responsedescription: Option, + pub location: Option, } /// 14.25. responsedescription XML Element @@ -521,7 +553,7 @@ pub struct Shared {} /// /// //@FIXME: Better typing is possible with an enum for example -pub struct Status(http::status::StatusCode); +pub struct Status(pub http::status::StatusCode); /// 14.29. timeout XML Element /// diff --git a/src/main.rs b/src/main.rs index 5f5089f..4f874b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(type_alias_impl_trait)] #![feature(async_fn_in_trait)] mod auth;