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> {
|
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
|
||||||
match CalendarQuery::<E>::qread(xml).await {
|
match CalendarQuery::<E>::qread(xml).await {
|
||||||
Err(ParsingError::Recoverable) => (),
|
Err(ParsingError::Recoverable) => (),
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl<E: Extension> QWrite for MkCalendarResponse<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------- REPORT METHOD -------------------------------------
|
// ----------------------- 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> {
|
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Query(v) => v.qwrite(xml).await,
|
Self::Query(v) => v.qwrite(xml).await,
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub struct MkCalendarResponse<E: dav::Extension>(pub Vec<dav::PropStat<E>>);
|
||||||
|
|
||||||
// --- (REPORT PART) ---
|
// --- (REPORT PART) ---
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Report<E: dav::Extension> {
|
pub enum ReportType<E: dav::Extension> {
|
||||||
Query(CalendarQuery<E>),
|
Query(CalendarQuery<E>),
|
||||||
Multiget(CalendarMultiget<E>),
|
Multiget(CalendarMultiget<E>),
|
||||||
FreeBusy(FreeBusyQuery),
|
FreeBusy(FreeBusyQuery),
|
||||||
|
|
|
@ -16,13 +16,20 @@ pub mod caldecoder;
|
||||||
pub mod calencoder;
|
pub mod calencoder;
|
||||||
pub mod caltypes;
|
pub mod caltypes;
|
||||||
|
|
||||||
// acl (wip)
|
// acl (partial)
|
||||||
pub mod acldecoder;
|
pub mod acldecoder;
|
||||||
pub mod aclencoder;
|
pub mod aclencoder;
|
||||||
pub mod acltypes;
|
pub mod acltypes;
|
||||||
|
|
||||||
// versioning (wip)
|
// versioning (partial)
|
||||||
mod versioningtypes;
|
pub mod versioningdecoder;
|
||||||
|
pub mod versioningencoder;
|
||||||
|
pub mod versioningtypes;
|
||||||
|
|
||||||
|
// sync
|
||||||
|
pub mod syncdecoder;
|
||||||
|
pub mod syncencoder;
|
||||||
|
pub mod synctypes;
|
||||||
|
|
||||||
// final type
|
// final type
|
||||||
pub mod realization;
|
pub mod realization;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::acltypes as acl;
|
use super::acltypes as acl;
|
||||||
use super::caltypes as cal;
|
use super::caltypes as cal;
|
||||||
use super::error;
|
use super::error;
|
||||||
|
use super::synctypes as sync;
|
||||||
use super::types as dav;
|
use super::types as dav;
|
||||||
use super::xml;
|
use super::xml;
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ impl dav::Extension for Core {
|
||||||
type Property = Disabled;
|
type Property = Disabled;
|
||||||
type PropertyRequest = Disabled;
|
type PropertyRequest = Disabled;
|
||||||
type ResourceType = Disabled;
|
type ResourceType = Disabled;
|
||||||
|
type ReportType = Disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebDAV with the base Calendar implementation (RFC4791)
|
// WebDAV with the base Calendar implementation (RFC4791)
|
||||||
|
@ -41,6 +43,7 @@ impl dav::Extension for Calendar {
|
||||||
type Property = cal::Property;
|
type Property = cal::Property;
|
||||||
type PropertyRequest = cal::PropertyRequest;
|
type PropertyRequest = cal::PropertyRequest;
|
||||||
type ResourceType = cal::ResourceType;
|
type ResourceType = cal::ResourceType;
|
||||||
|
type ReportType = cal::ReportType<Calendar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL
|
// ACL
|
||||||
|
@ -51,6 +54,7 @@ impl dav::Extension for Acl {
|
||||||
type Property = acl::Property;
|
type Property = acl::Property;
|
||||||
type PropertyRequest = acl::PropertyRequest;
|
type PropertyRequest = acl::PropertyRequest;
|
||||||
type ResourceType = acl::ResourceType;
|
type ResourceType = acl::ResourceType;
|
||||||
|
type ReportType = Disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All merged
|
// All merged
|
||||||
|
@ -61,6 +65,7 @@ impl dav::Extension for All {
|
||||||
type Property = Property;
|
type Property = Property;
|
||||||
type PropertyRequest = PropertyRequest;
|
type PropertyRequest = PropertyRequest;
|
||||||
type ResourceType = ResourceType;
|
type ResourceType = ResourceType;
|
||||||
|
type ReportType = ReportType<All>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[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 Property: xml::Node<Self::Property>;
|
||||||
type PropertyRequest: xml::Node<Self::PropertyRequest>;
|
type PropertyRequest: xml::Node<Self::PropertyRequest>;
|
||||||
type ResourceType: xml::Node<Self::ResourceType>;
|
type ResourceType: xml::Node<Self::ResourceType>;
|
||||||
|
type ReportType: xml::Node<Self::ReportType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 14.1. activelock XML Element
|
/// 14.1. activelock XML Element
|
||||||
|
@ -328,6 +329,10 @@ pub enum LockType {
|
||||||
/// response descriptions contained within the responses.
|
/// response descriptions contained within the responses.
|
||||||
///
|
///
|
||||||
/// <!ELEMENT multistatus (response*, responsedescription?) >
|
/// <!ELEMENT multistatus (response*, responsedescription?) >
|
||||||
|
///
|
||||||
|
/// In WebDAV sync (rfc6578), multistatus is extended:
|
||||||
|
///
|
||||||
|
/// <!ELEMENT multistatus (response*, responsedescription?, sync-token?) >
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Multistatus<E: Extension> {
|
pub struct Multistatus<E: Extension> {
|
||||||
pub responses: Vec<Response<E>>,
|
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
|
//@FIXME required for a full DAV implementation
|
||||||
// See section 7.1 of the CalDAV RFC
|
// See section 7.1 of the CalDAV RFC
|
||||||
// It seems it's mainly due to the fact that the REPORT method is re-used.
|
// 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_collections::user::User;
|
||||||
use aero_dav::caltypes as cal;
|
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::types as dav;
|
||||||
|
use aero_dav::versioningtypes as vers;
|
||||||
use aero_ical::query::is_component_match;
|
use aero_ical::query::is_component_match;
|
||||||
|
|
||||||
use crate::dav::codec;
|
use crate::dav::codec;
|
||||||
|
@ -95,7 +96,7 @@ impl Controller {
|
||||||
async fn report(self) -> Result<HttpResponse> {
|
async fn report(self) -> Result<HttpResponse> {
|
||||||
let status = hyper::StatusCode::from_u16(207)?;
|
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,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(err=?e, "unable to decode REPORT body");
|
tracing::error!(err=?e, "unable to decode REPORT body");
|
||||||
|
@ -110,8 +111,8 @@ impl Controller {
|
||||||
let calprop: Option<cal::CalendarSelector<All>>;
|
let calprop: Option<cal::CalendarSelector<All>>;
|
||||||
|
|
||||||
// Extracting request information
|
// Extracting request information
|
||||||
match report {
|
match cal_report {
|
||||||
cal::Report::Multiget(m) => {
|
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
|
// Multiget is really like a propfind where Depth: 0|1|Infinity is replaced by an arbitrary
|
||||||
// list of URLs
|
// list of URLs
|
||||||
// Getting the list of nodes
|
// Getting the list of nodes
|
||||||
|
@ -136,13 +137,16 @@ impl Controller {
|
||||||
}
|
}
|
||||||
calprop = m.selector;
|
calprop = m.selector;
|
||||||
}
|
}
|
||||||
cal::Report::Query(q) => {
|
vers::Report::Extension(realization::ReportType::Cal(cal::ReportType::Query(q))) => {
|
||||||
calprop = q.selector;
|
calprop = q.selector;
|
||||||
ok_node = apply_filter(self.node.children(&self.user).await, &q.filter)
|
ok_node = apply_filter(self.node.children(&self.user).await, &q.filter)
|
||||||
.try_collect()
|
.try_collect()
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
cal::Report::FreeBusy(_) => {
|
vers::Report::Extension(realization::ReportType::Sync(_sync_col)) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
return Ok(Response::builder()
|
return Ok(Response::builder()
|
||||||
.status(501)
|
.status(501)
|
||||||
.body(text_body("Not implemented"))?)
|
.body(text_body("Not implemented"))?)
|
||||||
|
|
|
@ -691,7 +691,7 @@ fn rfc4791_webdav_caldav() {
|
||||||
.send()?;
|
.send()?;
|
||||||
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
|
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
|
||||||
// Not used by any client I know, so not implementing it now.
|
// Not used by any client I know, so not implementing it now.
|
||||||
|
|
||||||
// --- REPORT calendar-multiget ---
|
// --- REPORT calendar-multiget ---
|
||||||
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
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">
|
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
|
Loading…
Reference in a new issue