webdav sync core codec
This commit is contained in:
parent
418adf92be
commit
5b1da2a33b
14 changed files with 628 additions and 13 deletions
|
@ -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) => (),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
175
aero-dav/src/syncdecoder.rs
Normal 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
144
aero-dav/src/syncencoder.rs
Normal 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
68
aero-dav/src/synctypes.rs
Normal 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,
|
||||
}
|
|
@ -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>>,
|
||||
|
|
62
aero-dav/src/versioningdecoder.rs
Normal file
62
aero-dav/src/versioningdecoder.rs
Normal 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);
|
||||
}
|
||||
}
|
81
aero-dav/src/versioningencoder.rs
Normal file
81
aero-dav/src/versioningencoder.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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"))?)
|
||||
|
|
Loading…
Reference in a new issue