From 418adf92be86ea83008a145180837f1e0ad3018a Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 27 May 2024 08:03:21 +0200 Subject: debug support of calendar-data pruning --- aero-ical/src/lib.rs | 2 +- aero-ical/src/prune.rs | 63 ++++++++++++++++++++++-------------- aero-proto/src/dav/controller.rs | 1 - aero-proto/src/dav/resource.rs | 53 ++++++++++++++++++++++++------ aerogramme/tests/behavior.rs | 50 +++++++++++++++++++++++++++- aerogramme/tests/common/constants.rs | 28 ++++++++++++++++ 6 files changed, 160 insertions(+), 37 deletions(-) diff --git a/aero-ical/src/lib.rs b/aero-ical/src/lib.rs index 696010a..3f6f633 100644 --- a/aero-ical/src/lib.rs +++ b/aero-ical/src/lib.rs @@ -4,5 +4,5 @@ /// the goal will be to rewrite it in the end so it better /// integrates into Aerogramme pub mod parser; -pub mod query; pub mod prune; +pub mod query; diff --git a/aero-ical/src/prune.rs b/aero-ical/src/prune.rs index d04f700..3eb50ca 100644 --- a/aero-ical/src/prune.rs +++ b/aero-ical/src/prune.rs @@ -1,40 +1,55 @@ -use icalendar::parser::{Component, Property}; use aero_dav::caltypes as cal; +use icalendar::parser::{Component, Property}; pub fn component<'a>(src: &'a Component<'a>, prune: &cal::Comp) -> Option> { if src.name.as_str() != prune.name.as_str() { - return None + return None; } let name = src.name.clone(); let properties = match &prune.prop_kind { - None => vec![], - Some(cal::PropKind::AllProp) => src.properties.clone(), - Some(cal::PropKind::Prop(l)) => src.properties.iter().filter_map(|prop| { - let sel_filt = match l.iter().find(|filt| filt.name.0.as_str() == prop.name.as_str()) { - Some(v) => v, - None => return None - }; + Some(cal::PropKind::AllProp) | None => src.properties.clone(), + Some(cal::PropKind::Prop(l)) => src + .properties + .iter() + .filter_map(|prop| { + let sel_filt = match l + .iter() + .find(|filt| filt.name.0.as_str() == prop.name.as_str()) + { + Some(v) => v, + None => return None, + }; - match sel_filt.novalue { - None | Some(false) => Some(prop.clone()), - Some(true) => Some(Property { - name: prop.name.clone(), - params: prop.params.clone(), - val: "".into() - }), - } - }).collect::>(), + match sel_filt.novalue { + None | Some(false) => Some(prop.clone()), + Some(true) => Some(Property { + name: prop.name.clone(), + params: prop.params.clone(), + val: "".into(), + }), + } + }) + .collect::>(), }; let components = match &prune.comp_kind { - None => vec![], - Some(cal::CompKind::AllComp) => src.components.clone(), - Some(cal::CompKind::Comp(many_inner_prune)) => src.components.iter().filter_map(|src_component| { - many_inner_prune.iter().find_map(|inner_prune| component(src_component, inner_prune)) - }).collect::>(), + Some(cal::CompKind::AllComp) | None => src.components.clone(), + Some(cal::CompKind::Comp(many_inner_prune)) => src + .components + .iter() + .filter_map(|src_component| { + many_inner_prune + .iter() + .find_map(|inner_prune| component(src_component, inner_prune)) + }) + .collect::>(), }; - Some(Component { name, properties, components }) + Some(Component { + name, + properties, + components, + }) } diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs index abf6a97..eeb6d43 100644 --- a/aero-proto/src/dav/controller.rs +++ b/aero-proto/src/dav/controller.rs @@ -333,7 +333,6 @@ impl<'a> Path<'a> { } } -//@FIXME move somewhere else //@FIXME naive implementation, must be refactored later use futures::stream::Stream; fn apply_filter<'a>( diff --git a/aero-proto/src/dav/resource.rs b/aero-proto/src/dav/resource.rs index d65ce38..04bae4f 100644 --- a/aero-proto/src/dav/resource.rs +++ b/aero-proto/src/dav/resource.rs @@ -565,17 +565,49 @@ impl DavNode for EventNode { dav::Property::GetEtag(etag) } dav::PropertyRequest::Extension(all::PropertyRequest::Cal( - cal::PropertyRequest::CalendarData(_req), - )) => { + cal::PropertyRequest::CalendarData(req), + )) => { let ics = String::from_utf8( this.col.get(this.blob_id).await.or(Err(n.clone()))?, - ) - .or(Err(n.clone()))?; + ) + .or(Err(n.clone()))?; + + let new_ics = match &req.comp { + None => ics, + Some(prune_comp) => { + // parse content + let ics = match icalendar::parser::read_calendar(&ics) { + Ok(v) => v, + Err(e) => { + tracing::warn!(err=?e, "Unable to parse ICS in calendar-query"); + return Err(n.clone()) + } + }; + + // build a fake vcal component for caldav compat + let fake_vcal_component = icalendar::parser::Component { + name: cal::Component::VCalendar.as_str().into(), + properties: ics.properties, + components: ics.components, + }; + + // rebuild component + let new_comp = match aero_ical::prune::component(&fake_vcal_component, prune_comp) { + Some(v) => v, + None => return Err(n.clone()), + }; + + // reserialize + format!("{}", icalendar::parser::Calendar { properties: new_comp.properties, components: new_comp.components }) + }, + }; + + dav::Property::Extension(all::Property::Cal( cal::Property::CalendarData(cal::CalendarDataPayload { mime: None, - payload: ics, + payload: new_ics, }), )) } @@ -634,14 +666,15 @@ impl DavNode for EventNode { // so we load everything in memory let calendar = self.col.clone(); let blob_id = self.blob_id.clone(); - let r = async move { - let content = calendar + let calblob = async move { + let raw_ics = calendar .get(blob_id) .await - .or(Err(std::io::Error::from(std::io::ErrorKind::Interrupted))); - Ok(hyper::body::Bytes::from(content?)) + .or(Err(std::io::Error::from(std::io::ErrorKind::Interrupted)))?; + + Ok(hyper::body::Bytes::from(raw_ics)) }; - futures::stream::once(Box::pin(r)).boxed() + futures::stream::once(Box::pin(calblob)).boxed() } fn content_type(&self) -> &str { diff --git a/aerogramme/tests/behavior.rs b/aerogramme/tests/behavior.rs index 7b93d51..c88583f 100644 --- a/aerogramme/tests/behavior.rs +++ b/aerogramme/tests/behavior.rs @@ -956,7 +956,55 @@ fn rfc4791_webdav_caldav() { ); // --- REPORT calendar-query, with calendar-data tx --- - //@FIXME add support for calendar-data... + let cal_query = r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "#; + + let resp = http + .request( + reqwest::Method::from_bytes(b"REPORT")?, + "http://localhost:8087/alice/calendar/Personal/", + ) + .body(cal_query) + .send()?; + assert_eq!(resp.status(), 207); + let multistatus = dav_deserialize::>(&resp.text()?); + assert_eq!(multistatus.responses.len(), 1); + check_cal( + &multistatus, + ( + "/alice/calendar/Personal/rfc3.ics", + Some(obj3_etag.to_str().expect("etag header convertible to str")), + Some(ICAL_RFC3_STRIPPED), + ), + ); Ok(()) }) diff --git a/aerogramme/tests/common/constants.rs b/aerogramme/tests/common/constants.rs index c04bae0..16daec6 100644 --- a/aerogramme/tests/common/constants.rs +++ b/aerogramme/tests/common/constants.rs @@ -125,6 +125,34 @@ END:VEVENT END:VCALENDAR "; +pub static ICAL_RFC3_STRIPPED: &[u8] = b"BEGIN:VCALENDAR\r +VERSION:2.0\r +BEGIN:VTIMEZONE\r +LAST-MODIFIED:20040110T032845Z\r +TZID:US/Eastern\r +BEGIN:DAYLIGHT\r +DTSTART:20000404T020000\r +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\r +TZNAME:EDT\r +TZOFFSETFROM:-0500\r +TZOFFSETTO:-0400\r +END:DAYLIGHT\r +BEGIN:STANDARD\r +DTSTART:20001026T020000\r +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r +TZNAME:EST\r +TZOFFSETFROM:-0400\r +TZOFFSETTO:-0500\r +END:STANDARD\r +END:VTIMEZONE\r +BEGIN:VEVENT\r +DTSTART;TZID=US/Eastern:20060104T100000\r +DURATION:PT1H\r +UID:DC6C50A017428C5216A2F1CD@example.com\r +END:VEVENT\r +END:VCALENDAR\r +"; + pub static ICAL_RFC4: &[u8] = br#"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN -- cgit v1.2.3