diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-02-29 20:40:40 +0100 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-02-29 20:40:40 +0100 |
commit | 1e3737a590e2b329afc2b5531cf4ae67fb48a571 (patch) | |
tree | 9a496bbf16b1bef524bfe82d50b7123e702289d7 | |
parent | 9146537aaf9c8aef504dc3ed050992e97d907edd (diff) | |
download | aerogramme-1e3737a590e2b329afc2b5531cf4ae67fb48a571.tar.gz aerogramme-1e3737a590e2b329afc2b5531cf4ae67fb48a571.zip |
At least it compiles
-rw-r--r-- | src/dav/calencoder.rs | 100 | ||||
-rw-r--r-- | src/dav/caltypes.rs | 41 | ||||
-rw-r--r-- | src/dav/encoder.rs | 208 | ||||
-rw-r--r-- | src/dav/mod.rs | 2 | ||||
-rw-r--r-- | src/dav/types.rs | 64 | ||||
-rw-r--r-- | src/main.rs | 1 |
6 files changed, 369 insertions, 47 deletions
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<E: Extension>: DavWriter<E> { + fn create_cal_element(&mut self, name: &str) -> ElementWriter<impl AsyncWrite + Unpin>; +} + +impl<'a, W: AsyncWrite+Unpin> DavWriter<CalExtension> for Writer<'a, W, CalExtension> { + fn create_dav_element(&mut self, name: &str) -> ElementWriter<impl AsyncWrite + Unpin> { + self.create_ns_element(name, Namespace::Dav) + } + fn child(w: &'a mut QWriter<W>) -> impl DavWriter<CalExtension> { + Self::child(w) + } + async fn error(&mut self, err: &Violation) -> Result<(), QError> { + err.write(self).await + } +} + +impl<'a, W: AsyncWrite+Unpin> CalWriter<CalExtension> for Writer<'a, W, CalExtension> { + fn create_cal_element(&mut self, name: &str) -> ElementWriter<impl AsyncWrite + Unpin> { + self.create_ns_element(name, Namespace::CalDav) + } +}*/ + +pub struct CalCtx { + root: bool +} +impl Context<CalExtension> 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<impl AsyncWrite+Unpin>) -> Result<(), QError> { + err.write(xml, self.child()).await + } +} + +impl QuickWritable<CalExtension, CalCtx> for Violation { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, _ctx: CalCtx) -> Result<(), QError> { + match self { + Self::SupportedFilter => xml + .create_element("supported-filter") + .write_empty_async().await?, + }; + Ok(()) + } +} + +/* + <?xml version="1.0" encoding="utf-8" ?> + <D:error> + <C:supported-filter> + <C:prop-filter name="X-ABC-GUID"/> + </C:supported-filter> + </D:error> +*/ + +#[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<CalExtension> = 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#"<D:error xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> + <C:supported-filter/> +</D:error>"#; + 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. + /// + /// <!ELEMENT supported-filter (comp-filter*, + /// prop-filter*, + /// param-filter*)> + 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<impl AsyncWrite + Unpin>) -> Result<(), Error>; +//-------------- TRAITS ---------------------- +/*pub trait DavWriter<E: Extension> { + fn create_dav_element(&mut self, name: &str) -> ElementWriter<impl AsyncWrite + Unpin>; + fn child(w: &mut QWriter<impl AsyncWrite + Unpin>) -> impl DavWriter<E>; + async fn error(&mut self, err: &E::Error) -> Result<(), QError>; +}*/ + +/// Basic encode trait to make a type encodable +pub trait QuickWritable<E: Extension, C: Context<E>> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, ctx: C) -> Result<(), QError>; +} + +pub trait Context<E: Extension> { + fn child(&self) -> Self; + fn create_dav_element(&self, name: &str) -> BytesStart; + async fn hook_error(&self, err: &E::Error, xml: &mut Writer<impl AsyncWrite+Unpin>) -> Result<(), QError>; +} + +pub struct NoExtCtx { + root: bool +} +impl Context<NoExtension> 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<impl AsyncWrite+Unpin>) -> Result<(), QError> { + unreachable!(); + } } -impl Encode for Href { - async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { - xml.create_element("D:href") + +//--------------------- ENCODING -------------------- + +// --- XML ROOTS +impl<E: Extension, C: Context<E>> QuickWritable<E,C> for Multistatus<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<E: Extension, C: Context<E>> QuickWritable<E,C> for Href { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, _ctx: C) -> Result<(), QError> { + xml.create_element("href") .write_text_content_async(BytesText::new(&self.0)) .await?; Ok(()) } } -impl<T> Encode for Multistatus<T> { - async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> 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<E: Extension, C: Context<E>> QuickWritable<E,C> for Response<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<E: Extension, C: Context<E>> QuickWritable<E,C> for StatusOrPropstat<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<E: Extension, C: Context<E>> QuickWritable<E,C> for Status { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<T> Encode for Response<T> { - async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { +impl<E: Extension, C: Context<E>> QuickWritable<E,C> for ResponseDescription { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<E: Extension, C: Context<E>> QuickWritable<E,C> for Location { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, ctx: C) -> Result<(), QError> { unimplemented!(); } } -impl Encode for ResponseDescription { - async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { - xml.create_element("D:responsedescription") - .write_text_content_async(BytesText::new(&self.0)) - .await?; +impl<E: Extension, C: Context<E>> QuickWritable<E,C> for PropStat<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, ctx: C) -> Result<(), QError> { + unimplemented!(); + } +} + +impl<E: Extension, C: Context<E>> QuickWritable<E,C> for Error<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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<E: Extension, C: Context<E>> QuickWritable<E,C> for Violation<E> { + async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, 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"<D:href>/SOGo/dav/so/</D:href>"[..]); @@ -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<u64> = 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<NoExtension> = 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#"<D:multistatus xmlns:D="DAV:"> 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}; /// <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?, /// locktoken?, lockroot)> pub struct ActiveLock { - lockscope: u64, - locktype: u64, + lockscope: LockScope, + locktype: LockType, depth: Depth, - owner: Option<u64>, - timeout: Option<u64>, + owner: Option<Owner>, + timeout: Option<Timeout>, } /// 14.2 allprop XML Element @@ -72,7 +100,8 @@ pub enum Depth { /// postcondition code. Unrecognized elements MUST be ignored. /// /// <!ELEMENT error ANY > -pub enum Error<T> { +pub struct Error<T: Extension>(pub Vec<Violation<T>>); +pub enum Violation<T: Extension> { /// Name: lock-token-matches-request-uri /// /// Use with: 409 Conflict @@ -156,7 +185,7 @@ pub enum Error<T> { 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. /// /// <!ELEMENT multistatus (response*, responsedescription?) > -pub struct Multistatus<T> { +pub struct Multistatus<T: Extension> { pub responses: Vec<Response<T>>, pub responsedescription: Option<ResponseDescription>, } @@ -416,7 +445,7 @@ pub struct PropName {} /// the properties named in 'prop'. /// /// <!ELEMENT propstat (prop, status, error?, responsedescription?) > -pub struct PropStat<T> { +pub struct PropStat<T: Extension> { prop: Prop, status: Status, error: Option<Error<T>>, @@ -460,13 +489,16 @@ pub struct Remove(Prop); /// /// <!ELEMENT response (href, ((href*, status)|(propstat+)), /// error?, responsedescription? , location?) > -pub struct Response<T> { - href: Vec<Href>, - status: Status, - propstat: Vec<PropStat<T>>, - error: Option<Error<T>>, - responsedescription: Option<ResponseDescription>, - location: Option<u64>, +pub enum StatusOrPropstat<T: Extension> { + Status(Status), + PropStat(Vec<PropStat<T>>), +} +pub struct Response<T: Extension> { + 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<T>, + pub error: Option<Error<T>>, + pub responsedescription: Option<ResponseDescription>, + pub location: Option<Location>, } /// 14.25. responsedescription XML Element @@ -521,7 +553,7 @@ pub struct Shared {} /// /// <!ELEMENT status (#PCDATA) > //@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; |