mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
Some preparation to parse CalDAV REPORT requests with calendar-data
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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>),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user