From 52f870633c2cab8a4aeeec74792774931139b8b5 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sat, 25 May 2024 19:30:59 +0200 Subject: add a new aero-ical module --- aero-proto/Cargo.toml | 1 + aero-proto/src/dav/controller.rs | 183 +-------------------------------------- 2 files changed, 5 insertions(+), 179 deletions(-) (limited to 'aero-proto') diff --git a/aero-proto/Cargo.toml b/aero-proto/Cargo.toml index b6f6336..e8d6b8f 100644 --- a/aero-proto/Cargo.toml +++ b/aero-proto/Cargo.toml @@ -7,6 +7,7 @@ license = "EUPL-1.2" description = "Binding between Aerogramme's internal components and well-known protocols" [dependencies] +aero-ical.workspace = true aero-sasl.workspace = true aero-dav.workspace = true aero-user.workspace = true diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs index 4cf520e..873f768 100644 --- a/aero-proto/src/dav/controller.rs +++ b/aero-proto/src/dav/controller.rs @@ -1,6 +1,6 @@ use anyhow::Result; use futures::stream::{StreamExt, TryStreamExt}; -use http_body_util::combinators::{BoxBody, UnsyncBoxBody}; +use http_body_util::combinators::UnsyncBoxBody; use http_body_util::BodyStream; use http_body_util::StreamBody; use hyper::body::Frame; @@ -11,10 +11,11 @@ use aero_collections::user::User; use aero_dav::caltypes as cal; use aero_dav::realization::All; use aero_dav::types as dav; +use aero_ical::query::is_component_match; use crate::dav::codec; use crate::dav::codec::{depth, deserialize, serialize, text_body}; -use crate::dav::node::{DavNode, PutPolicy}; +use crate::dav::node::DavNode; use crate::dav::resource::RootNode; pub(super) type ArcUser = std::sync::Arc; @@ -373,185 +374,9 @@ fn apply_filter<'a>( tracing::debug!(filter=?root_filter, "calendar-query filter"); // Adjust return value according to filter - match is_component_match(&[fake_vcal_component], root_filter) { + match is_component_match(&fake_vcal_component, &[fake_vcal_component.clone()], root_filter) { true => Some(Ok(single_node)), _ => None, } }) } - -fn ical_parse_date(dt: &str) -> Option> { - 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, -) -> Option> { - properties - .iter() - .find(|candidate| candidate.name.as_str() == name) - .map(|p| p.val.as_str()) - .map(ical_parse_date) - .flatten() -} - -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(_)) - | (Some(cal::PropFilterRules::Match(_)), None) => false, - (Some(cal::PropFilterRules::Match(pattern)), Some(prop)) => { - // check value - match &pattern.time_or_text { - Some(cal::TimeOrText::Time(time_range)) => { - let maybe_parsed_date = ical_parse_date(prop.val.as_str()); - - 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 { - cal::TimeRange::OnlyStart(after) => &parsed_date >= after, - cal::TimeRange::OnlyEnd(before) => &parsed_date <= before, - cal::TimeRange::FullRange(after, before) => { - &parsed_date >= after && &parsed_date <= before - } - }; - if !is_in_range { - return false; - } - - // if you are here, this subcondition is valid - } - Some(cal::TimeOrText::Text(txt_match)) => { - //@FIXME ignoring collation - let is_match = match txt_match.negate_condition { - None | Some(false) => { - prop.val.as_str().contains(txt_match.text.as_str()) - } - Some(true) => !prop.val.as_str().contains(txt_match.text.as_str()), - }; - if !is_match { - return false; - } - } - None => (), // if not filter on value is set, continue - }; - - // check parameters - pattern.param_filter.iter().all(|single_param_filter| { - let maybe_param = prop.params.iter().find(|candidate| { - candidate.key.as_str() == single_param_filter.name.as_str() - }); - - match (maybe_param, &single_param_filter.additional_rules) { - (Some(_), None) => true, - (None, None) => false, - (Some(_), Some(cal::ParamFilterMatch::IsNotDefined)) => false, - (None, Some(cal::ParamFilterMatch::IsNotDefined)) => true, - (None, Some(cal::ParamFilterMatch::Match(_))) => false, - (Some(param), Some(cal::ParamFilterMatch::Match(txt_match))) => { - let param_val = match ¶m.val { - Some(v) => v, - None => return false, - }; - - match txt_match.negate_condition { - None | Some(false) => { - param_val.as_str().contains(txt_match.text.as_str()) - } - Some(true) => !param_val.as_str().contains(txt_match.text.as_str()), - } - } - } - }) - } - } - }) -} - -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, - } -} - -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) - }) - } - } -} -- cgit v1.2.3