webdav sync core codec

This commit is contained in:
Quentin 2024-05-27 18:16:53 +02:00
parent 418adf92be
commit 5b1da2a33b
Signed by: quentin
GPG key ID: E9602264D639FF68
14 changed files with 628 additions and 13 deletions

View file

@ -25,7 +25,7 @@ impl<E: dav::Extension> QRead<MkCalendarResponse<E>> for MkCalendarResponse<E> {
}
}
impl<E: dav::Extension> QRead<Report<E>> for Report<E> {
impl<E: dav::Extension> QRead<ReportType<E>> for ReportType<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
match CalendarQuery::<E>::qread(xml).await {
Err(ParsingError::Recoverable) => (),

View file

@ -33,7 +33,7 @@ impl<E: Extension> QWrite for MkCalendarResponse<E> {
}
// ----------------------- REPORT METHOD -------------------------------------
impl<E: Extension> QWrite for Report<E> {
impl<E: Extension> QWrite for ReportType<E> {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
match self {
Self::Query(v) => v.qwrite(xml).await,

View file

@ -51,7 +51,7 @@ pub struct MkCalendarResponse<E: dav::Extension>(pub Vec<dav::PropStat<E>>);
// --- (REPORT PART) ---
#[derive(Debug, PartialEq, Clone)]
pub enum Report<E: dav::Extension> {
pub enum ReportType<E: dav::Extension> {
Query(CalendarQuery<E>),
Multiget(CalendarMultiget<E>),
FreeBusy(FreeBusyQuery),

View file

@ -16,13 +16,20 @@ pub mod caldecoder;
pub mod calencoder;
pub mod caltypes;
// acl (wip)
// acl (partial)
pub mod acldecoder;
pub mod aclencoder;
pub mod acltypes;
// versioning (wip)
mod versioningtypes;
// versioning (partial)
pub mod versioningdecoder;
pub mod versioningencoder;
pub mod versioningtypes;
// sync
pub mod syncdecoder;
pub mod syncencoder;
pub mod synctypes;
// final type
pub mod realization;

View file

@ -1,6 +1,7 @@
use super::acltypes as acl;
use super::caltypes as cal;
use super::error;
use super::synctypes as sync;
use super::types as dav;
use super::xml;
@ -31,6 +32,7 @@ impl dav::Extension for Core {
type Property = Disabled;
type PropertyRequest = Disabled;
type ResourceType = Disabled;
type ReportType = Disabled;
}
// WebDAV with the base Calendar implementation (RFC4791)
@ -41,6 +43,7 @@ impl dav::Extension for Calendar {
type Property = cal::Property;
type PropertyRequest = cal::PropertyRequest;
type ResourceType = cal::ResourceType;
type ReportType = cal::ReportType<Calendar>;
}
// ACL
@ -51,6 +54,7 @@ impl dav::Extension for Acl {
type Property = acl::Property;
type PropertyRequest = acl::PropertyRequest;
type ResourceType = acl::ResourceType;
type ReportType = Disabled;
}
// All merged
@ -61,6 +65,7 @@ impl dav::Extension for All {
type Property = Property;
type PropertyRequest = PropertyRequest;
type ResourceType = ResourceType;
type ReportType = ReportType<All>;
}
#[derive(Debug, PartialEq, Clone)]
@ -142,3 +147,31 @@ impl xml::QWrite for ResourceType {
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ReportType<E: dav::Extension> {
Cal(cal::ReportType<E>),
Sync(sync::SyncCollection<E>),
}
impl<E: dav::Extension> xml::QRead<ReportType<E>> for ReportType<E> {
async fn qread(
xml: &mut xml::Reader<impl xml::IRead>,
) -> Result<ReportType<E>, error::ParsingError> {
match cal::ReportType::qread(xml).await {
Err(error::ParsingError::Recoverable) => (),
otherwise => return otherwise.map(ReportType::Cal),
}
sync::SyncCollection::qread(xml).await.map(ReportType::Sync)
}
}
impl<E: dav::Extension> xml::QWrite for ReportType<E> {
async fn qwrite(
&self,
xml: &mut xml::Writer<impl xml::IWrite>,
) -> Result<(), quick_xml::Error> {
match self {
Self::Cal(c) => c.qwrite(xml).await,
Self::Sync(s) => s.qwrite(xml).await,
}
}
}

175
aero-dav/src/syncdecoder.rs Normal file
View file

@ -0,0 +1,175 @@
use quick_xml::events::Event;
use super::error::ParsingError;
use super::synctypes::*;
use super::types as dav;
use super::xml::{IRead, QRead, Reader, DAV_URN};
impl<E: dav::Extension> QRead<SyncCollection<E>> for SyncCollection<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "sync-collection").await?;
let (mut sync_token, mut sync_level, mut limit, mut prop) = (None, None, None, None);
loop {
let mut dirty = false;
xml.maybe_read(&mut sync_token, &mut dirty).await?;
xml.maybe_read(&mut sync_level, &mut dirty).await?;
xml.maybe_read(&mut limit, &mut dirty).await?;
xml.maybe_read(&mut prop, &mut dirty).await?;
if !dirty {
match xml.peek() {
Event::End(_) => break,
_ => xml.skip().await?,
};
}
}
xml.close().await?;
match (sync_token, sync_level, prop) {
(Some(sync_token), Some(sync_level), Some(prop)) => Ok(SyncCollection {
sync_token,
sync_level,
limit,
prop,
}),
_ => Err(ParsingError::MissingChild),
}
}
}
impl QRead<SyncTokenRequest> for SyncTokenRequest {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "sync-token").await?;
let token = match xml.tag_string().await {
Ok(v) => SyncTokenRequest::IncrementalSync(v),
Err(ParsingError::Recoverable) => SyncTokenRequest::InitialSync,
Err(e) => return Err(e),
};
xml.close().await?;
Ok(token)
}
}
impl QRead<SyncToken> for SyncToken {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "sync-token").await?;
let token = xml.tag_string().await?;
xml.close().await?;
Ok(SyncToken(token))
}
}
impl QRead<SyncLevel> for SyncLevel {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "sync-level").await?;
let lvl = match xml.tag_string().await?.to_lowercase().as_str() {
"1" => SyncLevel::One,
"infinite" => SyncLevel::Infinite,
_ => return Err(ParsingError::InvalidValue),
};
xml.close().await?;
Ok(lvl)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::realization::All;
use crate::types as dav;
use crate::versioningtypes as vers;
use crate::xml::Node;
async fn deserialize<T: Node<T>>(src: &str) -> T {
let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes()))
.await
.unwrap();
rdr.find().await.unwrap()
}
#[tokio::test]
async fn sync_level() {
{
let expected = SyncLevel::One;
let src = r#"<D:sync-level xmlns:D="DAV:">1</D:sync-level>"#;
let got = deserialize::<SyncLevel>(src).await;
assert_eq!(got, expected);
}
{
let expected = SyncLevel::Infinite;
let src = r#"<D:sync-level xmlns:D="DAV:">infinite</D:sync-level>"#;
let got = deserialize::<SyncLevel>(src).await;
assert_eq!(got, expected);
}
}
#[tokio::test]
async fn sync_token_request() {
{
let expected = SyncTokenRequest::InitialSync;
let src = r#"<D:sync-token xmlns:D="DAV:"/>"#;
let got = deserialize::<SyncTokenRequest>(src).await;
assert_eq!(got, expected);
}
{
let expected =
SyncTokenRequest::IncrementalSync("http://example.com/ns/sync/1232".into());
let src =
r#"<D:sync-token xmlns:D="DAV:">http://example.com/ns/sync/1232</D:sync-token>"#;
let got = deserialize::<SyncTokenRequest>(src).await;
assert_eq!(got, expected);
}
}
#[tokio::test]
async fn sync_token() {
let expected = SyncToken("http://example.com/ns/sync/1232".into());
let src = r#"<D:sync-token xmlns:D="DAV:">http://example.com/ns/sync/1232</D:sync-token>"#;
let got = deserialize::<SyncToken>(src).await;
assert_eq!(got, expected);
}
#[tokio::test]
async fn sync_collection() {
{
let expected = SyncCollection::<All> {
sync_token: SyncTokenRequest::IncrementalSync(
"http://example.com/ns/sync/1232".into(),
),
sync_level: SyncLevel::One,
limit: Some(vers::Limit(vers::NResults(100))),
prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]),
};
let src = r#"<D:sync-collection xmlns:D="DAV:">
<D:sync-token>http://example.com/ns/sync/1232</D:sync-token>
<D:sync-level>1</D:sync-level>
<D:limit>
<D:nresults>100</D:nresults>
</D:limit>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>"#;
let got = deserialize::<SyncCollection<All>>(src).await;
assert_eq!(got, expected);
}
{
let expected = SyncCollection::<All> {
sync_token: SyncTokenRequest::InitialSync,
sync_level: SyncLevel::Infinite,
limit: None,
prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]),
};
let src = r#"<D:sync-collection xmlns:D="DAV:">
<D:sync-token/>
<D:sync-level>infinite</D:sync-level>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>"#;
let got = deserialize::<SyncCollection<All>>(src).await;
assert_eq!(got, expected);
}
}
}

144
aero-dav/src/syncencoder.rs Normal file
View file

@ -0,0 +1,144 @@
use quick_xml::events::{BytesText, Event};
use quick_xml::Error as QError;
use super::synctypes::*;
use super::types::Extension;
use super::xml::{IWrite, QWrite, Writer};
impl<E: Extension> QWrite for SyncCollection<E> {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("sync-collection");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
self.sync_token.qwrite(xml).await?;
self.sync_level.qwrite(xml).await?;
if let Some(limit) = &self.limit {
limit.qwrite(xml).await?;
}
self.prop.qwrite(xml).await?;
xml.q.write_event_async(Event::End(end)).await
}
}
impl QWrite for SyncTokenRequest {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("sync-token");
match self {
Self::InitialSync => xml.q.write_event_async(Event::Empty(start)).await,
Self::IncrementalSync(uri) => {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
xml.q
.write_event_async(Event::Text(BytesText::new(uri.as_str())))
.await?;
xml.q.write_event_async(Event::End(end)).await
}
}
}
}
impl QWrite for SyncToken {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("sync-token");
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 SyncLevel {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("sync-level");
let end = start.to_end();
let text = match self {
Self::One => "1",
Self::Infinite => "infinite",
};
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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::realization::All;
use crate::types as dav;
use crate::versioningtypes as vers;
use crate::xml::Node;
use crate::xml::Reader;
use tokio::io::AsyncWriteExt;
async fn serialize_deserialize<T: Node<T>>(src: &T) {
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 };
src.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();
// deserialize
let mut rdr = Reader::new(quick_xml::NsReader::from_reader(got.as_bytes()))
.await
.unwrap();
let res = rdr.find().await.unwrap();
// check
assert_eq!(src, &res);
}
#[tokio::test]
async fn sync_level() {
serialize_deserialize(&SyncLevel::One).await;
serialize_deserialize(&SyncLevel::Infinite).await;
}
#[tokio::test]
async fn sync_token_request() {
serialize_deserialize(&SyncTokenRequest::InitialSync).await;
serialize_deserialize(&SyncTokenRequest::IncrementalSync(
"http://example.com/ns/sync/1232".into(),
))
.await;
}
#[tokio::test]
async fn sync_token() {
serialize_deserialize(&SyncToken("http://example.com/ns/sync/1232".into())).await;
}
#[tokio::test]
async fn sync_collection() {
serialize_deserialize(&SyncCollection::<All> {
sync_token: SyncTokenRequest::IncrementalSync("http://example.com/ns/sync/1232".into()),
sync_level: SyncLevel::One,
limit: Some(vers::Limit(vers::NResults(100))),
prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]),
})
.await;
serialize_deserialize(&SyncCollection::<All> {
sync_token: SyncTokenRequest::InitialSync,
sync_level: SyncLevel::Infinite,
limit: None,
prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]),
})
.await;
}
}

68
aero-dav/src/synctypes.rs Normal file
View file

@ -0,0 +1,68 @@
use super::types as dav;
use super::versioningtypes as vers;
// RFC 6578
// https://datatracker.ietf.org/doc/html/rfc6578
//@FIXME add SyncTokenRequest to PropertyRequest
//@FIXME add SyncToken to Property
//@FIXME add SyncToken to Multistatus
/// Name: sync-collection
///
/// Namespace: DAV:
///
/// Purpose: WebDAV report used to synchronize data between client and
/// server.
///
/// Description: See Section 3.
///
/// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
///
/// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
/// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
#[derive(Debug, PartialEq, Clone)]
pub struct SyncCollection<E: dav::Extension> {
pub sync_token: SyncTokenRequest,
pub sync_level: SyncLevel,
pub limit: Option<vers::Limit>,
pub prop: dav::PropName<E>,
}
/// Name: sync-token
///
/// Namespace: DAV:
///
/// Purpose: The synchronization token provided by the server and
/// returned by the client.
///
/// Description: See Section 3.
///
/// <!ELEMENT sync-token CDATA>
///
/// <!-- Text MUST be a URI -->
/// Used by multistatus
#[derive(Debug, PartialEq, Clone)]
pub struct SyncToken(pub String);
/// Used by propfind and report sync-collection
#[derive(Debug, PartialEq, Clone)]
pub enum SyncTokenRequest {
InitialSync,
IncrementalSync(String),
}
/// Name: sync-level
///
/// Namespace: DAV:
///
/// Purpose: Indicates the "scope" of the synchronization report
/// request.
///
/// Description: See Section 3.3.
#[derive(Debug, PartialEq, Clone)]
pub enum SyncLevel {
One,
Infinite,
}

View file

@ -11,6 +11,7 @@ pub trait Extension: std::fmt::Debug + PartialEq + Clone {
type Property: xml::Node<Self::Property>;
type PropertyRequest: xml::Node<Self::PropertyRequest>;
type ResourceType: xml::Node<Self::ResourceType>;
type ReportType: xml::Node<Self::ReportType>;
}
/// 14.1. activelock XML Element
@ -328,6 +329,10 @@ pub enum LockType {
/// response descriptions contained within the responses.
///
/// <!ELEMENT multistatus (response*, responsedescription?) >
///
/// In WebDAV sync (rfc6578), multistatus is extended:
///
/// <!ELEMENT multistatus (response*, responsedescription?, sync-token?) >
#[derive(Debug, PartialEq, Clone)]
pub struct Multistatus<E: Extension> {
pub responses: Vec<Response<E>>,

View file

@ -0,0 +1,62 @@
use super::error::ParsingError;
use super::types as dav;
use super::versioningtypes::*;
use super::xml::{IRead, QRead, Reader, DAV_URN};
impl<E: dav::Extension> QRead<Report<E>> for Report<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
//@FIXME VersionTree not implemented
//@FIXME ExpandTree not implemented
E::ReportType::qread(xml).await.map(Report::Extension)
}
}
impl QRead<Limit> for Limit {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "limit").await?;
let nres = xml.find().await?;
xml.close().await?;
Ok(Limit(nres))
}
}
impl QRead<NResults> for NResults {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "nresults").await?;
let sz = xml.tag_string().await?.parse::<u64>()?;
xml.close().await?;
Ok(NResults(sz))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xml::Node;
async fn deserialize<T: Node<T>>(src: &str) -> T {
let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes()))
.await
.unwrap();
rdr.find().await.unwrap()
}
#[tokio::test]
async fn nresults() {
let expected = NResults(100);
let src = r#"<D:nresults xmlns:D="DAV:">100</D:nresults>"#;
let got = deserialize::<NResults>(src).await;
assert_eq!(got, expected);
}
#[tokio::test]
async fn limit() {
let expected = Limit(NResults(1024));
let src = r#"<D:limit xmlns:D="DAV:">
<D:nresults>1024</D:nresults>
</D:limit>"#;
let got = deserialize::<Limit>(src).await;
assert_eq!(got, expected);
}
}

View file

@ -0,0 +1,81 @@
use quick_xml::events::{BytesText, Event};
use quick_xml::Error as QError;
use super::types::Extension;
use super::versioningtypes::*;
use super::xml::{IWrite, QWrite, Writer};
impl<E: Extension> QWrite for Report<E> {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
match self {
Report::VersionTree => unimplemented!(),
Report::ExpandProperty => unimplemented!(),
Report::Extension(inner) => inner.qwrite(xml).await,
}
}
}
impl QWrite for Limit {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("limit");
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 NResults {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("nresults");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
xml.q
.write_event_async(Event::Text(BytesText::new(&format!("{}", self.0))))
.await?;
xml.q.write_event_async(Event::End(end)).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xml::Node;
use crate::xml::Reader;
use tokio::io::AsyncWriteExt;
async fn serialize_deserialize<T: Node<T>>(src: &T) -> T {
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 };
src.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();
// deserialize
let mut rdr = Reader::new(quick_xml::NsReader::from_reader(got.as_bytes()))
.await
.unwrap();
rdr.find().await.unwrap()
}
#[tokio::test]
async fn nresults() {
let orig = NResults(100);
assert_eq!(orig, serialize_deserialize(&orig).await);
}
#[tokio::test]
async fn limit() {
let orig = Limit(NResults(1024));
assert_eq!(orig, serialize_deserialize(&orig).await);
}
}

View file

@ -1,3 +1,39 @@
use super::types as dav;
//@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.
// https://datatracker.ietf.org/doc/html/rfc4791#section-7.1
//
// Defines (required by CalDAV):
// - REPORT method
// - expand-property root report method
//
// Defines (required by Sync):
// - limit, nresults
// - supported-report-set
// This property identifies the reports that are supported by the
// resource.
//
// <!ELEMENT supported-report-set (supported-report*)>
// <!ELEMENT supported-report report>
// <!ELEMENT report ANY>
// ANY value: a report element type
#[derive(Debug, PartialEq, Clone)]
pub enum Report<E: dav::Extension> {
VersionTree, // Not yet implemented
ExpandProperty, // Not yet implemented
Extension(E::ReportType),
}
/// Limit
/// <!ELEMENT limit (nresults) >
#[derive(Debug, PartialEq, Clone)]
pub struct Limit(pub NResults);
/// NResults
/// <!ELEMENT nresults (#PCDATA) >
#[derive(Debug, PartialEq, Clone)]
pub struct NResults(pub u64);

View file

@ -9,8 +9,9 @@ use hyper::{body::Bytes, Request, Response};
use aero_collections::user::User;
use aero_dav::caltypes as cal;
use aero_dav::realization::All;
use aero_dav::realization::{self, All};
use aero_dav::types as dav;
use aero_dav::versioningtypes as vers;
use aero_ical::query::is_component_match;
use crate::dav::codec;
@ -95,7 +96,7 @@ impl Controller {
async fn report(self) -> Result<HttpResponse> {
let status = hyper::StatusCode::from_u16(207)?;
let report = match deserialize::<cal::Report<All>>(self.req).await {
let cal_report = match deserialize::<vers::Report<All>>(self.req).await {
Ok(v) => v,
Err(e) => {
tracing::error!(err=?e, "unable to decode REPORT body");
@ -110,8 +111,8 @@ impl Controller {
let calprop: Option<cal::CalendarSelector<All>>;
// Extracting request information
match report {
cal::Report::Multiget(m) => {
match cal_report {
vers::Report::Extension(realization::ReportType::Cal(cal::ReportType::Multiget(m))) => {
// Multiget is really like a propfind where Depth: 0|1|Infinity is replaced by an arbitrary
// list of URLs
// Getting the list of nodes
@ -136,13 +137,16 @@ impl Controller {
}
calprop = m.selector;
}
cal::Report::Query(q) => {
vers::Report::Extension(realization::ReportType::Cal(cal::ReportType::Query(q))) => {
calprop = q.selector;
ok_node = apply_filter(self.node.children(&self.user).await, &q.filter)
.try_collect()
.await?;
}
cal::Report::FreeBusy(_) => {
vers::Report::Extension(realization::ReportType::Sync(_sync_col)) => {
todo!()
}
_ => {
return Ok(Response::builder()
.status(501)
.body(text_body("Not implemented"))?)

View file

@ -691,7 +691,7 @@ fn rfc4791_webdav_caldav() {
.send()?;
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
// Not used by any client I know, so not implementing it now.
// --- REPORT calendar-multiget ---
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">