At least it compiles

This commit is contained in:
Quentin 2024-02-29 20:40:40 +01:00
parent 9146537aaf
commit 1e3737a590
Signed by: quentin
GPG key ID: E9602264D639FF68
6 changed files with 369 additions and 47 deletions

100
src/dav/calencoder.rs Normal file
View file

@ -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);
}
}

41
src/dav/caltypes.rs Normal file
View file

@ -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,
}

View file

@ -1,61 +1,205 @@
use std::io::Cursor; use std::io::Cursor;
use futures::stream::{StreamExt, TryStreamExt}; use quick_xml::Error as QError;
use quick_xml::Error;
use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText};
use quick_xml::writer::{ElementWriter, Writer}; use quick_xml::writer::{ElementWriter, Writer};
use quick_xml::name::PrefixDeclaration; use quick_xml::name::PrefixDeclaration;
use tokio::io::AsyncWrite; use tokio::io::AsyncWrite;
use super::types::*; 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 { //-------------- TRAITS ----------------------
async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error>; /*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>;
} }
impl Encode for Href { pub trait Context<E: Extension> {
async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { fn child(&self) -> Self;
xml.create_element("D:href") 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!();
}
}
//--------------------- 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)) .write_text_content_async(BytesText::new(&self.0))
.await?; .await?;
Ok(()) Ok(())
} }
} }
impl<T> Encode for Multistatus<T> { impl<E: Extension, C: Context<E>> QuickWritable<E,C> for Response<E> {
async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, ctx: C) -> Result<(), QError> {
xml.create_element("D:multistatus") xml.create_element("response")
.with_attribute(("xmlns:D", "DAV:")) .write_inner_content_async::<_, _, QError>(|inner_xml| async move {
.write_inner_content_async::<_, _, quick_xml::Error>(|inner_xml| async move { self.href.write(inner_xml, ctx.child()).await?;
for response in self.responses.iter() { self.status_or_propstat.write(inner_xml, ctx.child()).await?;
response.write(inner_xml).await?; if let Some(error) = &self.error {
error.write(inner_xml, ctx.child()).await?;
} }
if let Some(responsedescription) = &self.responsedescription {
if let Some(description) = &self.responsedescription { responsedescription.write(inner_xml, ctx.child()).await?;
description.write(inner_xml).await?; }
if let Some(location) = &self.location {
location.write(inner_xml, ctx.child()).await?;
} }
Ok(inner_xml) 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?; .await?;
Ok(()) Ok(())
} }
} }
impl<T> Encode for Response<T> { impl<E: Extension, C: Context<E>> QuickWritable<E,C> for ResponseDescription {
async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { 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!(); unimplemented!();
} }
} }
impl Encode for ResponseDescription { impl<E: Extension, C: Context<E>> QuickWritable<E,C> for PropStat<E> {
async fn write(&self, xml: &mut Writer<impl AsyncWrite + Unpin>) -> Result<(), Error> { async fn write(&self, xml: &mut Writer<impl AsyncWrite+Unpin>, ctx: C) -> Result<(), QError> {
xml.create_element("D:responsedescription") unimplemented!();
.write_text_content_async(BytesText::new(&self.0)) }
.await?; }
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(()) Ok(())
} }
} }
@ -74,7 +218,8 @@ mod tests {
let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); 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"); tokio_buffer.flush().await.expect("tokio buffer flush");
assert_eq!(buffer.as_slice(), &b"<D:href>/SOGo/dav/so/</D:href>"[..]); 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 tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); 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())) }; let ctx = NoExtCtx{ root: true };
xml.write(&mut writer).await.expect("xml serialization"); 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"); tokio_buffer.flush().await.expect("tokio buffer flush");
let expected = r#"<D:multistatus xmlns:D="DAV:"> let expected = r#"<D:multistatus xmlns:D="DAV:">

View file

@ -1,5 +1,7 @@
mod types; mod types;
mod caltypes;
mod encoder; mod encoder;
mod calencoder;
use std::net::SocketAddr; use std::net::SocketAddr;

View file

@ -2,6 +2,34 @@
use chrono::{DateTime,FixedOffset}; 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 /// 14.1. activelock XML Element
/// ///
/// Name: activelock /// Name: activelock
@ -10,11 +38,11 @@ use chrono::{DateTime,FixedOffset};
/// <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?, /// <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?,
/// locktoken?, lockroot)> /// locktoken?, lockroot)>
pub struct ActiveLock { pub struct ActiveLock {
lockscope: u64, lockscope: LockScope,
locktype: u64, locktype: LockType,
depth: Depth, depth: Depth,
owner: Option<u64>, owner: Option<Owner>,
timeout: Option<u64>, timeout: Option<Timeout>,
} }
/// 14.2 allprop XML Element /// 14.2 allprop XML Element
@ -72,7 +100,8 @@ pub enum Depth {
/// postcondition code. Unrecognized elements MUST be ignored. /// postcondition code. Unrecognized elements MUST be ignored.
/// ///
/// <!ELEMENT error ANY > /// <!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 /// Name: lock-token-matches-request-uri
/// ///
/// Use with: 409 Conflict /// Use with: 409 Conflict
@ -156,7 +185,7 @@ pub enum Error<T> {
CannotModifyProtectedProperty, CannotModifyProtectedProperty,
/// Specific errors /// Specific errors
Extensions(T), Extension(T::Error),
} }
/// 14.6. exclusive XML Element /// 14.6. exclusive XML Element
@ -312,7 +341,7 @@ pub enum LockType {
/// response descriptions contained within the responses. /// response descriptions contained within the responses.
/// ///
/// <!ELEMENT multistatus (response*, responsedescription?) > /// <!ELEMENT multistatus (response*, responsedescription?) >
pub struct Multistatus<T> { pub struct Multistatus<T: Extension> {
pub responses: Vec<Response<T>>, pub responses: Vec<Response<T>>,
pub responsedescription: Option<ResponseDescription>, pub responsedescription: Option<ResponseDescription>,
} }
@ -416,7 +445,7 @@ pub struct PropName {}
/// the properties named in 'prop'. /// the properties named in 'prop'.
/// ///
/// <!ELEMENT propstat (prop, status, error?, responsedescription?) > /// <!ELEMENT propstat (prop, status, error?, responsedescription?) >
pub struct PropStat<T> { pub struct PropStat<T: Extension> {
prop: Prop, prop: Prop,
status: Status, status: Status,
error: Option<Error<T>>, error: Option<Error<T>>,
@ -460,13 +489,16 @@ pub struct Remove(Prop);
/// ///
/// <!ELEMENT response (href, ((href*, status)|(propstat+)), /// <!ELEMENT response (href, ((href*, status)|(propstat+)),
/// error?, responsedescription? , location?) > /// error?, responsedescription? , location?) >
pub struct Response<T> { pub enum StatusOrPropstat<T: Extension> {
href: Vec<Href>, Status(Status),
status: Status, PropStat(Vec<PropStat<T>>),
propstat: Vec<PropStat<T>>, }
error: Option<Error<T>>, pub struct Response<T: Extension> {
responsedescription: Option<ResponseDescription>, pub href: Href, // It's wrong according to the spec, but I don't understand why there is an href*
location: Option<u64>, pub status_or_propstat: StatusOrPropstat<T>,
pub error: Option<Error<T>>,
pub responsedescription: Option<ResponseDescription>,
pub location: Option<Location>,
} }
/// 14.25. responsedescription XML Element /// 14.25. responsedescription XML Element
@ -521,7 +553,7 @@ pub struct Shared {}
/// ///
/// <!ELEMENT status (#PCDATA) > /// <!ELEMENT status (#PCDATA) >
//@FIXME: Better typing is possible with an enum for example //@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 /// 14.29. timeout XML Element
/// ///

View file

@ -1,3 +1,4 @@
#![feature(type_alias_impl_trait)]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
mod auth; mod auth;