aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-05-23 10:01:43 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-05-23 10:01:43 +0200
commitff823a10f049e06c711537560ba10f3dc826afcd (patch)
tree25000784fef0758b7aad1990d3d14ecbe7eae46d
parent7687065bfc824127fda657363894a30268e95385 (diff)
downloadaerogramme-ff823a10f049e06c711537560ba10f3dc826afcd.tar.gz
aerogramme-ff823a10f049e06c711537560ba10f3dc826afcd.zip
improve ical date parsing
-rw-r--r--aero-dav/src/caldecoder.rs18
-rw-r--r--aero-dav/src/calencoder.rs24
-rw-r--r--aero-dav/src/caltypes.rs4
-rw-r--r--aero-proto/src/dav/controller.rs30
-rw-r--r--aerogramme/tests/behavior.rs57
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),
),
)
});