Some preparation to parse CalDAV REPORT requests with calendar-data

This commit is contained in:
Lennart
2025-04-27 15:09:28 +02:00
parent 19708b7951
commit 3b58d73b58
6 changed files with 128 additions and 44 deletions

View File

@@ -1,17 +1,18 @@
use super::ReportPropName;
use crate::{ use crate::{
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
Error, Error,
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
}; };
use actix_web::{ use actix_web::{
HttpRequest,
dev::{Path, ResourceDef}, dev::{Path, ResourceDef},
http::StatusCode, http::StatusCode,
HttpRequest,
}; };
use rustical_dav::{ use rustical_dav::{
resource::Resource, resource::Resource,
xml::{multistatus::ResponseElement, MultistatusElement, PropElement, PropfindType}, xml::{MultistatusElement, PropElement, PropfindType, multistatus::ResponseElement},
}; };
use rustical_store::{auth::User, CalendarObject, CalendarStore}; use rustical_store::{CalendarObject, CalendarStore, auth::User};
use rustical_xml::XmlDeserialize; use rustical_xml::XmlDeserialize;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[derive(XmlDeserialize, Clone, Debug, PartialEq)]
@@ -19,7 +20,7 @@ use rustical_xml::XmlDeserialize;
// <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, href+)> // <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, href+)>
pub(crate) struct CalendarMultigetRequest { pub(crate) struct CalendarMultigetRequest {
#[xml(ty = "untagged")] #[xml(ty = "untagged")]
pub(crate) prop: PropfindType, pub(crate) prop: PropfindType<ReportPropName>,
#[xml(flatten)] #[xml(flatten)]
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(ns = "rustical_dav::namespace::NS_DAV")]
pub(crate) href: Vec<String>, pub(crate) href: Vec<String>,
@@ -72,9 +73,16 @@ pub async fn handle_calendar_multiget<C: CalendarStore>(
PropfindType::Propname => { PropfindType::Propname => {
vec!["propname".to_owned()] vec!["propname".to_owned()]
} }
PropfindType::Prop(PropElement(prop_tags)) => { PropfindType::Prop(PropElement(prop_tags)) => prop_tags
prop_tags.into_iter().map(|propname| propname.0).collect() .into_iter()
} .filter_map(|propname| {
if let ReportPropName::Propname(propname) = propname {
Some(propname.0)
} else {
None
}
})
.collect(),
}; };
let props: Vec<&str> = props.iter().map(String::as_str).collect(); let props: Vec<&str> = props.iter().map(String::as_str).collect();

View File

@@ -4,16 +4,18 @@ use rustical_dav::{
xml::{MultistatusElement, PropElement, PropfindType}, xml::{MultistatusElement, PropElement, PropfindType},
}; };
use rustical_store::{ use rustical_store::{
auth::User, calendar::UtcDateTime, calendar_store::CalendarQuery, CalendarObject, CalendarStore, CalendarObject, CalendarStore, auth::User, calendar::UtcDateTime, calendar_store::CalendarQuery,
}; };
use rustical_xml::XmlDeserialize; use rustical_xml::XmlDeserialize;
use std::ops::Deref; use std::ops::Deref;
use crate::{ use crate::{
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
Error, Error,
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
}; };
use super::ReportPropName;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct TimeRangeElement { pub(crate) struct TimeRangeElement {
@@ -179,7 +181,7 @@ impl From<&FilterElement> for CalendarQuery {
// <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, filter, timezone?)> // <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, filter, timezone?)>
pub struct CalendarQueryRequest { pub struct CalendarQueryRequest {
#[xml(ty = "untagged")] #[xml(ty = "untagged")]
pub prop: PropfindType, pub prop: PropfindType<ReportPropName>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) filter: Option<FilterElement>, pub(crate) filter: Option<FilterElement>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
@@ -230,9 +232,16 @@ pub async fn handle_calendar_query<C: CalendarStore>(
PropfindType::Propname => { PropfindType::Propname => {
vec!["propname".to_owned()] vec!["propname".to_owned()]
} }
PropfindType::Prop(PropElement(prop_tags)) => { PropfindType::Prop(PropElement(prop_tags)) => prop_tags
prop_tags.into_iter().map(|propname| propname.0).collect() .into_iter()
} .filter_map(|propname| {
if let ReportPropName::Propname(propname) = propname {
Some(propname.0)
} else {
None
}
})
.collect(),
}; };
let props: Vec<&str> = props.iter().map(String::as_str).collect(); let props: Vec<&str> = props.iter().map(String::as_str).collect();

View File

@@ -1,12 +1,12 @@
use crate::Error; use crate::Error;
use actix_web::{ use actix_web::{
web::{Data, Path},
HttpRequest, Responder, HttpRequest, Responder,
web::{Data, Path},
}; };
use calendar_multiget::{handle_calendar_multiget, CalendarMultigetRequest}; use calendar_multiget::{CalendarMultigetRequest, handle_calendar_multiget};
use calendar_query::{handle_calendar_query, CalendarQueryRequest}; use calendar_query::{CalendarQueryRequest, handle_calendar_query};
use rustical_dav::xml::sync_collection::SyncCollectionRequest; use rustical_dav::xml::{Propname, sync_collection::SyncCollectionRequest};
use rustical_store::{auth::User, CalendarStore}; use rustical_store::{CalendarStore, auth::User};
use rustical_xml::{XmlDeserialize, XmlDocument}; use rustical_xml::{XmlDeserialize, XmlDocument};
use sync_collection::handle_sync_collection; use sync_collection::handle_sync_collection;
use tracing::instrument; use tracing::instrument;
@@ -15,6 +15,34 @@ mod calendar_multiget;
mod calendar_query; mod calendar_query;
mod sync_collection; mod sync_collection;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub(crate) struct ExpandElement {
#[xml(ty = "attr")]
start: String,
#[xml(ty = "attr")]
end: String,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct CalendarData {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
comp: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
expand: Option<ExpandElement>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
limit_recurrence_set: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
limit_freebusy_set: Option<()>,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub enum ReportPropName {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarData(CalendarData),
#[xml(other)]
Propname(Propname),
}
#[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)] #[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)]
pub(crate) enum ReportRequest { pub(crate) enum ReportRequest {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
@@ -22,7 +50,7 @@ pub(crate) enum ReportRequest {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarQuery(CalendarQueryRequest), CalendarQuery(CalendarQueryRequest),
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(ns = "rustical_dav::namespace::NS_DAV")]
SyncCollection(SyncCollectionRequest), SyncCollection(SyncCollectionRequest<ReportPropName>),
} }
#[instrument(skip(req, cal_store))] #[instrument(skip(req, cal_store))]
@@ -79,12 +107,42 @@ pub async fn route_report_calendar<C: CalendarStore>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use calendar_query::{CompFilterElement, FilterElement, TimeRangeElement}; use calendar_query::{CompFilterElement, FilterElement, TimeRangeElement};
use rustical_dav::xml::{PropElement, PropfindType, Propname}; use rustical_dav::xml::{PropElement, PropfindType, Propname};
use rustical_store::calendar::UtcDateTime; use rustical_store::calendar::UtcDateTime;
use rustical_xml::ValueDeserialize; use rustical_xml::ValueDeserialize;
use super::*; #[test]
fn test_xml_calendar_data() {
let report_request = ReportRequest::parse_str(r#"
<?xml version="1.0" encoding="UTF-8"?>
<calendar-multiget xmlns="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<D:displayname/>
<calendar-data>
<expand start="20250426T220000Z" end="20250503T220000Z"/>
</calendar-data>
</D:prop>
<D:href>/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b</D:href>
</calendar-multiget>
"#).unwrap();
assert_eq!(
report_request,
ReportRequest::CalendarMultiget(CalendarMultigetRequest {
prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![
ReportPropName::Propname(Propname("getetag".to_owned())),
ReportPropName::Propname(Propname("displayname".to_owned())),
ReportPropName::CalendarData(CalendarData { comp: None, expand: Some(ExpandElement { start: "20250426T220000Z".to_owned(), end: "20250503T220000Z".to_owned() }), limit_recurrence_set: None, limit_freebusy_set: None })
])),
href: vec![
"/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned()
]
})
)
}
#[test] #[test]
fn test_xml_calendar_query() { fn test_xml_calendar_query() {
@@ -108,7 +166,9 @@ mod tests {
assert_eq!( assert_eq!(
report_request, report_request,
ReportRequest::CalendarQuery(CalendarQueryRequest { ReportRequest::CalendarQuery(CalendarQueryRequest {
prop: PropfindType::Prop(PropElement(vec![Propname("getetag".to_owned())])), prop: PropfindType::Prop(PropElement(vec![ReportPropName::Propname(Propname(
"getetag".to_owned()
))])),
filter: Some(FilterElement { filter: Some(FilterElement {
comp_filter: CompFilterElement { comp_filter: CompFilterElement {
is_not_defined: None, is_not_defined: None,
@@ -155,8 +215,8 @@ mod tests {
report_request, report_request,
ReportRequest::CalendarMultiget(CalendarMultigetRequest { ReportRequest::CalendarMultiget(CalendarMultigetRequest {
prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![ prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![
Propname("getetag".to_owned()), ReportPropName::Propname(Propname("getetag".to_owned())),
Propname("displayname".to_owned()) ReportPropName::Propname(Propname("displayname".to_owned()))
])), ])),
href: vec![ href: vec![
"/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned() "/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned()

View File

@@ -1,24 +1,24 @@
use actix_web::{http::StatusCode, HttpRequest}; use super::ReportPropName;
use crate::{
Error,
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
};
use actix_web::{HttpRequest, http::StatusCode};
use rustical_dav::{ use rustical_dav::{
resource::Resource, resource::Resource,
xml::{ xml::{
multistatus::ResponseElement, sync_collection::SyncCollectionRequest, MultistatusElement, MultistatusElement, PropElement, PropfindType, multistatus::ResponseElement,
PropElement, PropfindType, sync_collection::SyncCollectionRequest,
}, },
}; };
use rustical_store::{ use rustical_store::{
CalendarStore,
auth::User, auth::User,
synctoken::{format_synctoken, parse_synctoken}, synctoken::{format_synctoken, parse_synctoken},
CalendarStore,
};
use crate::{
calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource},
Error,
}; };
pub async fn handle_sync_collection<C: CalendarStore>( pub async fn handle_sync_collection<C: CalendarStore>(
sync_collection: SyncCollectionRequest, sync_collection: SyncCollectionRequest<ReportPropName>,
req: HttpRequest, req: HttpRequest,
user: &User, user: &User,
principal: &str, principal: &str,
@@ -32,9 +32,16 @@ pub async fn handle_sync_collection<C: CalendarStore>(
PropfindType::Propname => { PropfindType::Propname => {
vec!["propname".to_owned()] vec!["propname".to_owned()]
} }
PropfindType::Prop(PropElement(prop_tags)) => { PropfindType::Prop(PropElement(prop_tags)) => prop_tags
prop_tags.into_iter().map(|propname| propname.0).collect() .into_iter()
} .filter_map(|propname| {
if let ReportPropName::Propname(propname) = propname {
Some(propname.0)
} else {
None
}
})
.collect(),
}; };
let props: Vec<&str> = props.iter().map(String::as_str).collect(); let props: Vec<&str> = props.iter().map(String::as_str).collect();

View File

@@ -9,17 +9,17 @@ pub struct PropfindElement {
} }
#[derive(Debug, Clone, XmlDeserialize, PartialEq)] #[derive(Debug, Clone, XmlDeserialize, PartialEq)]
pub struct PropElement(#[xml(ty = "untagged", flatten)] pub Vec<Propname>); pub struct PropElement<PN: XmlDeserialize = Propname>(#[xml(ty = "untagged", flatten)] pub Vec<PN>);
#[derive(Debug, Clone, XmlDeserialize, PartialEq)] #[derive(Debug, Clone, XmlDeserialize, PartialEq)]
pub struct Propname(#[xml(ty = "tag_name")] pub String); pub struct Propname(#[xml(ty = "tag_name")] pub String);
#[derive(Debug, Clone, XmlDeserialize, PartialEq)] #[derive(Debug, Clone, XmlDeserialize, PartialEq)]
pub enum PropfindType { pub enum PropfindType<PN: XmlDeserialize = Propname> {
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
Propname, Propname,
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
Allprop, Allprop,
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
Prop(PropElement), Prop(PropElement<PN>),
} }

View File

@@ -1,6 +1,6 @@
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize}; use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize};
use super::PropfindType; use super::{PropfindType, Propname};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SyncLevel { pub enum SyncLevel {
@@ -16,7 +16,7 @@ impl ValueDeserialize for SyncLevel {
_ => { _ => {
return Err(rustical_xml::XmlError::InvalidValue( return Err(rustical_xml::XmlError::InvalidValue(
rustical_xml::ParseValueError::Other("Invalid sync-level".to_owned()), rustical_xml::ParseValueError::Other("Invalid sync-level".to_owned()),
)) ));
} }
}) })
} }
@@ -37,13 +37,13 @@ impl ValueSerialize for SyncLevel {
// <!-- DAV:limit defined in RFC 5323, Section 5.17 --> // <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 --> // <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
pub struct SyncCollectionRequest { pub struct SyncCollectionRequest<PN: XmlDeserialize = Propname> {
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
pub sync_token: String, pub sync_token: String,
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
pub sync_level: SyncLevel, pub sync_level: SyncLevel,
#[xml(ns = "crate::namespace::NS_DAV", ty = "untagged")] #[xml(ns = "crate::namespace::NS_DAV", ty = "untagged")]
pub prop: PropfindType, pub prop: PropfindType<PN>,
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
pub limit: Option<u64>, pub limit: Option<u64>,
} }