aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dav/calencoder.rs100
-rw-r--r--src/dav/caltypes.rs41
-rw-r--r--src/dav/encoder.rs208
-rw-r--r--src/dav/mod.rs2
-rw-r--r--src/dav/types.rs64
-rw-r--r--src/main.rs1
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;