aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-05-22 15:02:53 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-05-22 15:02:53 +0200
commit6ca7082197aa60288c3295387bfdf47d8adbed2d (patch)
tree4f6e47bae54106f4144620839a41aa91a91f597e
parent194e34d4e1b28957d8310ea1205989fadb1b44c7 (diff)
downloadaerogramme-6ca7082197aa60288c3295387bfdf47d8adbed2d.tar.gz
aerogramme-6ca7082197aa60288c3295387bfdf47d8adbed2d.zip
fix: parsing components & times
-rw-r--r--aero-dav/src/caldecoder.rs18
-rw-r--r--aero-dav/src/calencoder.rs33
-rw-r--r--aero-dav/src/caltypes.rs3
-rw-r--r--aero-proto/src/dav/controller.rs180
4 files changed, 127 insertions, 107 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs
index b4391a4..1de4552 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(), ICAL_DATETIME_FMT)?.and_utc();
+ let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), CALDAV_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(), ICAL_DATETIME_FMT)?.and_utc();
- let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
+ 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();
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(), ICAL_DATETIME_FMT)?.and_utc();
- let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
+ 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();
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(), ICAL_DATETIME_FMT)?.and_utc();
- let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
+ 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();
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(), ICAL_DATETIME_FMT)?.and_utc())
+ Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc())
}
_ => None,
};
let end = match xml.prev_attr("end") {
Some(r) => {
- Some(NaiveDateTime::parse_from_str(r.as_str(), ICAL_DATETIME_FMT)?.and_utc())
+ Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc())
}
_ => None,
};
diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs
index 4467f7c..f145628 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(ICAL_DATETIME_FMT));
+ let dtstr = format!("{}", dt.format(CALDAV_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(ICAL_DATETIME_FMT));
+ let dtstr = format!("{}", dt.format(CALDAV_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(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
));
empty.push_attribute((
"end",
- format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.1.format(CALDAV_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(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
));
empty.push_attribute((
"end",
- format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.1.format(CALDAV_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(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
));
empty.push_attribute((
"end",
- format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(),
));
xml.q.write_event_async(Event::Empty(empty)).await
}
@@ -737,18 +737,21 @@ impl QWrite for TimeRange {
match self {
Self::OnlyStart(start) => empty.push_attribute((
"start",
- format!("{}", start.format(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(),
+ )),
+ Self::OnlyEnd(end) => empty.push_attribute((
+ "end",
+ format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(),
)),
- Self::OnlyEnd(end) => {
- empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str()))
- }
Self::FullRange(start, end) => {
empty.push_attribute((
"start",
- format!("{}", start.format(ICAL_DATETIME_FMT)).as_str(),
+ format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(),
+ ));
+ empty.push_attribute((
+ "end",
+ format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(),
));
- empty
- .push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str()));
}
}
xml.q.write_event_async(Event::Empty(empty)).await
diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs
index 717086b..924b651 100644
--- a/aero-dav/src/caltypes.rs
+++ b/aero-dav/src/caltypes.rs
@@ -3,7 +3,8 @@
use super::types as dav;
use chrono::{DateTime, Utc};
-pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
+pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%S";
+pub const CALDAV_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 e5a1cff..306b035 100644
--- a/aero-proto/src/dav/controller.rs
+++ b/aero-proto/src/dav/controller.rs
@@ -353,81 +353,49 @@ fn apply_filter<'a>(
};
// Do checks
+ // @FIXME: icalendar does not consider VCALENDAR as a component
+ // but WebDAV does...
+ // Build a fake VCALENDAR component for icalendar compatibility, it's a hack
let root_filter = &filter.0;
-
- // Find the component in the filter
- let maybe_comp = ics
- .components
- .iter()
- .find(|candidate| candidate.name.as_str() == root_filter.name.as_str());
-
- // Apply additional rules
- let is_keep = match (maybe_comp, &root_filter.additional_rules) {
- (Some(_), None) => true,
- (None, Some(cal::CompFilterRules::IsNotDefined)) => true,
- (None, None) => false,
- (None, Some(cal::CompFilterRules::Matches(_))) => false,
- (Some(_), Some(cal::CompFilterRules::IsNotDefined)) => false,
- (Some(inner_comp), Some(cal::CompFilterRules::Matches(filter))) => {
- is_component_match(inner_comp, filter)
- }
+ let fake_vcal_component = icalendar::parser::Component {
+ name: cal::Component::VCalendar.as_str().into(),
+ properties: ics.properties,
+ components: ics.components,
};
+ tracing::debug!(filter=?root_filter, "calendar-query filter");
// Adjust return value according to filter
- match is_keep {
+ match is_component_match(&[fake_vcal_component], root_filter) {
true => Some(Ok(single_node)),
_ => None,
}
})
}
-fn component_date(
- component: &icalendar::parser::Component,
- prop: &str,
+fn prop_date(
+ properties: &[icalendar::parser::Property],
+ name: &str,
) -> Option<chrono::DateTime<chrono::Utc>> {
- component
- .find_prop(prop)
+ properties
+ .iter()
+ .find(|candidate| candidate.name.as_str() == name)
.map(|p| p.val.as_str())
- .map(|raw_dtstart| {
- NaiveDateTime::parse_from_str(raw_dtstart, cal::ICAL_DATETIME_FMT)
+ .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())
})
.flatten()
}
-use chrono::NaiveDateTime;
-fn is_component_match(
- component: &icalendar::parser::Component,
- matcher: &cal::CompFilterMatch,
-) -> bool {
- if let Some(time_range) = &matcher.time_range {
- let (dtstart, dtend) = match (
- component_date(component, "DTSTART"),
- component_date(component, "DTEND"),
- ) {
- (Some(start), None) => (start, start),
- (None, Some(end)) => (end, end),
- (Some(start), Some(end)) => (start, end),
- _ => return false,
- };
-
- let is_in_range = match time_range {
- cal::TimeRange::OnlyStart(after) => &dtend >= after,
- cal::TimeRange::OnlyEnd(before) => &dtstart <= before,
- cal::TimeRange::FullRange(after, before) => &dtend >= after && &dtstart <= before,
- };
-
- if !is_in_range {
- return false;
- }
- }
-
- if !matcher.prop_filter.iter().all(|single_prop_filter| {
- match (
- &single_prop_filter.additional_rules,
- component.find_prop(single_prop_filter.name.0.as_str()),
- ) {
+fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::PropFilter]) -> bool {
+ filters.iter().all(|single_filter| {
+ // Find the property
+ let single_prop = props
+ .iter()
+ .find(|candidate| candidate.name.as_str() == single_filter.name.0.as_str());
+ match (&single_filter.additional_rules, single_prop) {
(None, Some(_)) | (Some(cal::PropFilterRules::IsNotDefined), None) => true,
(None, None)
| (Some(cal::PropFilterRules::IsNotDefined), Some(_))
@@ -436,12 +404,17 @@ fn is_component_match(
// check value
match &pattern.time_or_text {
Some(cal::TimeOrText::Time(time_range)) => {
- // try parse entry as date
- let parsed_date =
- match component_date(component, single_prop_filter.name.0.as_str()) {
- Some(v) => v,
- None => return false,
- };
+ let maybe_parsed_date = NaiveDateTime::parse_from_str(
+ prop.val.as_str(),
+ cal::ICAL_DATETIME_FMT,
+ )
+ .ok()
+ .map(|v| v.and_utc());
+
+ let parsed_date = match maybe_parsed_date {
+ None => return false,
+ Some(v) => v,
+ };
// see if entry is in range
let is_in_range = match time_range {
@@ -501,27 +474,70 @@ fn is_component_match(
})
}
}
- }) {
- return false;
+ })
+}
+
+fn is_in_time_range(
+ properties: &[icalendar::parser::Property],
+ time_range: &cal::TimeRange,
+) -> bool {
+ //@FIXME too naive: https://datatracker.ietf.org/doc/html/rfc4791#section-9.9
+
+ let (dtstart, dtend) = match (
+ prop_date(properties, "DTSTART"),
+ prop_date(properties, "DTEND"),
+ ) {
+ (Some(start), None) => (start, start),
+ (None, Some(end)) => (end, end),
+ (Some(start), Some(end)) => (start, end),
+ _ => {
+ tracing::warn!("unable to extract DTSTART and DTEND from VEVENT");
+ return false;
+ }
+ };
+
+ tracing::trace!(event_start=?dtstart, event_end=?dtend, filter=?time_range, "apply filter on VEVENT");
+ match time_range {
+ cal::TimeRange::OnlyStart(after) => &dtend >= after,
+ cal::TimeRange::OnlyEnd(before) => &dtstart <= before,
+ cal::TimeRange::FullRange(after, before) => &dtend >= after && &dtstart <= before,
}
+}
- matcher.comp_filter.iter().all(|single_comp_filter| {
- // Find the component
- let maybe_comp = component
- .components
- .iter()
- .find(|candidate| candidate.name.as_str() == single_comp_filter.name.as_str());
-
- // Filter according to rules
- match (maybe_comp, &single_comp_filter.additional_rules) {
- (Some(_), None) => true,
- (None, Some(cal::CompFilterRules::IsNotDefined)) => true,
- (None, None) => false,
- (Some(_), Some(cal::CompFilterRules::IsNotDefined)) => false,
- (None, Some(cal::CompFilterRules::Matches(_))) => false,
- (Some(inner_comp), Some(cal::CompFilterRules::Matches(comp_match))) => {
- is_component_match(inner_comp, comp_match)
+use chrono::NaiveDateTime;
+fn is_component_match(
+ components: &[icalendar::parser::Component],
+ filter: &cal::CompFilter,
+) -> bool {
+ // Find the component among the list
+ let maybe_comp = components
+ .iter()
+ .find(|candidate| candidate.name.as_str() == filter.name.as_str());
+
+ // Filter according to rules
+ match (maybe_comp, &filter.additional_rules) {
+ (Some(_), None) => true,
+ (None, Some(cal::CompFilterRules::IsNotDefined)) => true,
+ (None, None) => false,
+ (Some(_), Some(cal::CompFilterRules::IsNotDefined)) => false,
+ (None, Some(cal::CompFilterRules::Matches(_))) => false,
+ (Some(component), Some(cal::CompFilterRules::Matches(matcher))) => {
+ // check time range
+ if let Some(time_range) = &matcher.time_range {
+ if !is_in_time_range(component.properties.as_ref(), time_range) {
+ return false;
+ }
+ }
+
+ // check properties
+ if !is_properties_match(component.properties.as_ref(), matcher.prop_filter.as_ref()) {
+ return false;
}
+
+ // check inner components
+ matcher.comp_filter.iter().all(|inner_filter| {
+ is_component_match(component.components.as_ref(), &inner_filter)
+ })
}
- })
+ }
}