diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-05-23 10:01:43 +0200 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-05-23 10:01:43 +0200 |
commit | ff823a10f049e06c711537560ba10f3dc826afcd (patch) | |
tree | 25000784fef0758b7aad1990d3d14ecbe7eae46d | |
parent | 7687065bfc824127fda657363894a30268e95385 (diff) | |
download | aerogramme-ff823a10f049e06c711537560ba10f3dc826afcd.tar.gz aerogramme-ff823a10f049e06c711537560ba10f3dc826afcd.zip |
improve ical date parsing
-rw-r--r-- | aero-dav/src/caldecoder.rs | 18 | ||||
-rw-r--r-- | aero-dav/src/calencoder.rs | 24 | ||||
-rw-r--r-- | aero-dav/src/caltypes.rs | 4 | ||||
-rw-r--r-- | aero-proto/src/dav/controller.rs | 30 | ||||
-rw-r--r-- | aerogramme/tests/behavior.rs | 57 |
5 files changed, 82 insertions, 51 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index 6bc911f..7de5e2a 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -287,7 +287,7 @@ impl QRead<Property> for Property { .is_some() { let dtstr = xml.tag_string().await?; - let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); + let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), UTC_DATETIME_FMT)?.and_utc(); xml.close().await?; return Ok(Property::MaxDateTime(dt)); } @@ -653,8 +653,8 @@ impl QRead<Expand> for Expand { _ => return Err(ParsingError::MissingAttribute), }; - let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); - let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); + let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc(); + let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } @@ -672,8 +672,8 @@ impl QRead<LimitRecurrenceSet> for LimitRecurrenceSet { _ => return Err(ParsingError::MissingAttribute), }; - let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); - let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); + let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc(); + let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } @@ -691,8 +691,8 @@ impl QRead<LimitFreebusySet> for LimitFreebusySet { _ => return Err(ParsingError::MissingAttribute), }; - let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); - let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); + let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc(); + let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } @@ -918,13 +918,13 @@ impl QRead<TimeRange> for TimeRange { let start = match xml.prev_attr("start") { Some(r) => { - Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc()) + Some(NaiveDateTime::parse_from_str(r.as_str(), UTC_DATETIME_FMT)?.and_utc()) } _ => None, }; let end = match xml.prev_attr("end") { Some(r) => { - Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc()) + Some(NaiveDateTime::parse_from_str(r.as_str(), UTC_DATETIME_FMT)?.and_utc()) } _ => None, }; diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index f145628..d5d4305 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -178,7 +178,7 @@ impl QWrite for Property { let start = xml.create_cal_element("min-date-time"); let end = start.to_end(); - let dtstr = format!("{}", dt.format(CALDAV_DATETIME_FMT)); + let dtstr = format!("{}", dt.format(UTC_DATETIME_FMT)); xml.q.write_event_async(Event::Start(start.clone())).await?; xml.q .write_event_async(Event::Text(BytesText::new(dtstr.as_str()))) @@ -189,7 +189,7 @@ impl QWrite for Property { let start = xml.create_cal_element("max-date-time"); let end = start.to_end(); - let dtstr = format!("{}", dt.format(CALDAV_DATETIME_FMT)); + let dtstr = format!("{}", dt.format(UTC_DATETIME_FMT)); xml.q.write_event_async(Event::Start(start.clone())).await?; xml.q .write_event_async(Event::Text(BytesText::new(dtstr.as_str()))) @@ -493,11 +493,11 @@ impl QWrite for Expand { let mut empty = xml.create_cal_element("expand"); empty.push_attribute(( "start", - format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(), )); empty.push_attribute(( "end", - format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(), )); xml.q.write_event_async(Event::Empty(empty)).await } @@ -508,11 +508,11 @@ impl QWrite for LimitRecurrenceSet { let mut empty = xml.create_cal_element("limit-recurrence-set"); empty.push_attribute(( "start", - format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(), )); empty.push_attribute(( "end", - format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(), )); xml.q.write_event_async(Event::Empty(empty)).await } @@ -523,11 +523,11 @@ impl QWrite for LimitFreebusySet { let mut empty = xml.create_cal_element("limit-freebusy-set"); empty.push_attribute(( "start", - format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(), )); empty.push_attribute(( "end", - format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(), )); xml.q.write_event_async(Event::Empty(empty)).await } @@ -737,20 +737,20 @@ impl QWrite for TimeRange { match self { Self::OnlyStart(start) => empty.push_attribute(( "start", - format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", start.format(UTC_DATETIME_FMT)).as_str(), )), Self::OnlyEnd(end) => empty.push_attribute(( "end", - format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", end.format(UTC_DATETIME_FMT)).as_str(), )), Self::FullRange(start, end) => { empty.push_attribute(( "start", - format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", start.format(UTC_DATETIME_FMT)).as_str(), )); empty.push_attribute(( "end", - format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(), + format!("{}", end.format(UTC_DATETIME_FMT)).as_str(), )); } } diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs index 924b651..50cdb92 100644 --- a/aero-dav/src/caltypes.rs +++ b/aero-dav/src/caltypes.rs @@ -3,8 +3,8 @@ use super::types as dav; use chrono::{DateTime, Utc}; -pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%S"; -pub const CALDAV_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ"; +pub const FLOATING_DATETIME_FMT: &str = "%Y%m%dT%H%M%S"; +pub const UTC_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ"; //@FIXME ACL (rfc3744) is missing, required //@FIXME Versioning (rfc3253) is missing, required diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs index 0a47cf4..4cf520e 100644 --- a/aero-proto/src/dav/controller.rs +++ b/aero-proto/src/dav/controller.rs @@ -380,6 +380,22 @@ fn apply_filter<'a>( }) } +fn ical_parse_date(dt: &str) -> Option<chrono::DateTime<chrono::Utc>> { + tracing::trace!(raw_time = dt, "VEVENT raw time"); + let tmpl = match dt.chars().last() { + Some('Z') => cal::UTC_DATETIME_FMT, + Some(_) => { + tracing::warn!(raw_time=dt, "floating datetime is not properly supported yet"); + cal::FLOATING_DATETIME_FMT + }, + None => return None + }; + + NaiveDateTime::parse_from_str(dt, tmpl) + .ok() + .map(|v| v.and_utc()) +} + fn prop_date( properties: &[icalendar::parser::Property], name: &str, @@ -388,12 +404,7 @@ fn prop_date( .iter() .find(|candidate| candidate.name.as_str() == name) .map(|p| p.val.as_str()) - .map(|raw_time| { - tracing::trace!(raw_time = raw_time, "VEVENT raw time"); - NaiveDateTime::parse_from_str(raw_time, cal::ICAL_DATETIME_FMT) - .ok() - .map(|v| v.and_utc()) - }) + .map(ical_parse_date) .flatten() } @@ -412,12 +423,7 @@ fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::Pr // check value match &pattern.time_or_text { Some(cal::TimeOrText::Time(time_range)) => { - let maybe_parsed_date = NaiveDateTime::parse_from_str( - prop.val.as_str(), - cal::ICAL_DATETIME_FMT, - ) - .ok() - .map(|v| v.and_utc()); + let maybe_parsed_date = ical_parse_date(prop.val.as_str()); let parsed_date = match maybe_parsed_date { None => return false, diff --git a/aerogramme/tests/behavior.rs b/aerogramme/tests/behavior.rs index a83f1a7..b6c1c6e 100644 --- a/aerogramme/tests/behavior.rs +++ b/aerogramme/tests/behavior.rs @@ -592,9 +592,9 @@ fn rfc4791_webdav_caldav() { assert_eq!(resp.status(), 201); // A generic function to check a <calendar-data/> query result - let check_full_cal = + let check_cal = |multistatus: &dav::Multistatus<All>, - (ref_path, ref_etag, ref_ical): (&str, &str, &[u8])| { + (ref_path, ref_etag, ref_ical): (&str, Option<&str>, Option<&[u8]>)| { let obj_stats = multistatus .responses .iter() @@ -616,11 +616,10 @@ fn rfc4791_webdav_caldav() { .0 .iter() .find_map(|p| match p { - dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x), + dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x.as_str()), _ => None, - }) - .expect("etag is return in propstats"); - assert_eq!(etag.as_str(), ref_etag); + }); + assert_eq!(etag, ref_etag); let calendar_data = obj_success .prop .0 @@ -628,11 +627,10 @@ fn rfc4791_webdav_caldav() { .find_map(|p| match p { dav::AnyProperty::Value(dav::Property::Extension( realization::Property::Cal(cal::Property::CalendarData(x)), - )) => Some(x), + )) => Some(x.payload.as_bytes()), _ => None, - }) - .expect("calendar data is returned in propstats"); - assert_eq!(calendar_data.payload.as_bytes(), ref_ical); + }); + assert_eq!(calendar_data, ref_ical); }; // --- AUTODISCOVERY --- @@ -720,16 +718,43 @@ fn rfc4791_webdav_caldav() { ] .iter() .for_each(|(ref_path, ref_etag, ref_ical)| { - check_full_cal( + check_cal( &multistatus, ( ref_path, - ref_etag.to_str().expect("etag header convertible to str"), - ref_ical, + Some(ref_etag.to_str().expect("etag header convertible to str")), + Some(ref_ical), ), ) }); + // 8.2.1.2. Synchronize by Time Range (here: July 2006) + let cal_query = r#" <?xml version="1.0" encoding="utf-8" ?> + <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> + <D:prop> + <D:getetag/> + </D:prop> + <C:filter> + <C:comp-filter name="VCALENDAR"> + <C:comp-filter name="VEVENT"> + <C:time-range start="20060701T000000Z" end="20060801T000000Z"/> + </C:comp-filter> + </C:comp-filter> + </C:filter> + </C:calendar-query>"#; + 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::<dav::Multistatus<All>>(&resp.text()?); + assert_eq!(multistatus.responses.len(), 1); + check_cal(&multistatus, ("/alice/calendar/Personal/rfc2.ics", Some(obj2_etag.to_str().expect("etag header convertible to str")), None)); + + // --- REPORT calendar-multiget --- 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"> @@ -756,12 +781,12 @@ fn rfc4791_webdav_caldav() { ] .iter() .for_each(|(ref_path, ref_etag, ref_ical)| { - check_full_cal( + check_cal( &multistatus, ( ref_path, - ref_etag.to_str().expect("etag header convertible to str"), - ref_ical, + Some(ref_etag.to_str().expect("etag header convertible to str")), + Some(ref_ical), ), ) }); |