support multiple same name components, properties & parameters
This commit is contained in:
parent
6b9720844a
commit
d5a222967d
3 changed files with 244 additions and 110 deletions
|
@ -7,19 +7,18 @@ pub fn is_component_match(
|
||||||
filter: &cal::CompFilter,
|
filter: &cal::CompFilter,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Find the component among the list
|
// Find the component among the list
|
||||||
//@FIXME do not handle correctly multiple entities (eg. 3 VEVENT)
|
let maybe_comps = components
|
||||||
let maybe_comp = components
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|candidate| candidate.name.as_str() == filter.name.as_str());
|
.filter(|candidate| candidate.name.as_str() == filter.name.as_str())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Filter according to rules
|
// Filter according to rules
|
||||||
match (maybe_comp, &filter.additional_rules) {
|
match (&maybe_comps[..], &filter.additional_rules) {
|
||||||
(Some(_), None) => true,
|
([_, ..], None) => true,
|
||||||
(None, Some(cal::CompFilterRules::IsNotDefined)) => true,
|
([], Some(cal::CompFilterRules::IsNotDefined)) => true,
|
||||||
(None, None) => false,
|
([], None) => false,
|
||||||
(Some(_), Some(cal::CompFilterRules::IsNotDefined)) => false,
|
([_, ..], Some(cal::CompFilterRules::IsNotDefined)) => false,
|
||||||
(None, Some(cal::CompFilterRules::Matches(_))) => false,
|
(comps, Some(cal::CompFilterRules::Matches(matcher))) => comps.iter().any(|component| {
|
||||||
(Some(component), Some(cal::CompFilterRules::Matches(matcher))) => {
|
|
||||||
// check time range
|
// check time range
|
||||||
if let Some(time_range) = &matcher.time_range {
|
if let Some(time_range) = &matcher.time_range {
|
||||||
if !is_in_time_range(
|
if !is_in_time_range(
|
||||||
|
@ -41,7 +40,7 @@ pub fn is_component_match(
|
||||||
matcher.comp_filter.iter().all(|inner_filter| {
|
matcher.comp_filter.iter().all(|inner_filter| {
|
||||||
is_component_match(component, component.components.as_ref(), &inner_filter)
|
is_component_match(component, component.components.as_ref(), &inner_filter)
|
||||||
})
|
})
|
||||||
}
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,15 +70,16 @@ fn prop_parse<T: std::str::FromStr>(
|
||||||
fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::PropFilter]) -> bool {
|
fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::PropFilter]) -> bool {
|
||||||
filters.iter().all(|single_filter| {
|
filters.iter().all(|single_filter| {
|
||||||
// Find the property
|
// Find the property
|
||||||
let single_prop = props
|
let candidate_props = props
|
||||||
.iter()
|
.iter()
|
||||||
.find(|candidate| candidate.name.as_str() == single_filter.name.0.as_str());
|
.filter(|candidate| candidate.name.as_str() == single_filter.name.0.as_str())
|
||||||
match (&single_filter.additional_rules, single_prop) {
|
.collect::<Vec<_>>();
|
||||||
(None, Some(_)) | (Some(cal::PropFilterRules::IsNotDefined), None) => true,
|
|
||||||
(None, None)
|
match (&single_filter.additional_rules, &candidate_props[..]) {
|
||||||
| (Some(cal::PropFilterRules::IsNotDefined), Some(_))
|
(None, [_, ..]) | (Some(cal::PropFilterRules::IsNotDefined), []) => true,
|
||||||
| (Some(cal::PropFilterRules::Match(_)), None) => false,
|
(None, []) | (Some(cal::PropFilterRules::IsNotDefined), [_, ..]) => false,
|
||||||
(Some(cal::PropFilterRules::Match(pattern)), Some(prop)) => {
|
(Some(cal::PropFilterRules::Match(pattern)), multi_props) => {
|
||||||
|
multi_props.iter().any(|prop| {
|
||||||
// check value
|
// check value
|
||||||
match &pattern.time_or_text {
|
match &pattern.time_or_text {
|
||||||
Some(cal::TimeOrText::Time(time_range)) => {
|
Some(cal::TimeOrText::Time(time_range)) => {
|
||||||
|
@ -121,17 +121,21 @@ fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::Pr
|
||||||
|
|
||||||
// check parameters
|
// check parameters
|
||||||
pattern.param_filter.iter().all(|single_param_filter| {
|
pattern.param_filter.iter().all(|single_param_filter| {
|
||||||
let maybe_param = prop.params.iter().find(|candidate| {
|
let multi_param = prop
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.filter(|candidate| {
|
||||||
candidate.key.as_str() == single_param_filter.name.as_str()
|
candidate.key.as_str() == single_param_filter.name.as_str()
|
||||||
});
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
match (maybe_param, &single_param_filter.additional_rules) {
|
match (&multi_param[..], &single_param_filter.additional_rules) {
|
||||||
(Some(_), None) => true,
|
([.., _], None) => true,
|
||||||
(None, None) => false,
|
([], None) => false,
|
||||||
(Some(_), Some(cal::ParamFilterMatch::IsNotDefined)) => false,
|
([.., _], Some(cal::ParamFilterMatch::IsNotDefined)) => false,
|
||||||
(None, Some(cal::ParamFilterMatch::IsNotDefined)) => true,
|
([], Some(cal::ParamFilterMatch::IsNotDefined)) => true,
|
||||||
(None, Some(cal::ParamFilterMatch::Match(_))) => false,
|
(many_params, Some(cal::ParamFilterMatch::Match(txt_match))) => {
|
||||||
(Some(param), Some(cal::ParamFilterMatch::Match(txt_match))) => {
|
many_params.iter().any(|param| {
|
||||||
let param_val = match ¶m.val {
|
let param_val = match ¶m.val {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return false,
|
None => return false,
|
||||||
|
@ -141,10 +145,14 @@ fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::Pr
|
||||||
None | Some(false) => {
|
None | Some(false) => {
|
||||||
param_val.as_str().contains(txt_match.text.as_str())
|
param_val.as_str().contains(txt_match.text.as_str())
|
||||||
}
|
}
|
||||||
Some(true) => !param_val.as_str().contains(txt_match.text.as_str()),
|
Some(true) => {
|
||||||
|
!param_val.as_str().contains(txt_match.text.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ fn rfc4791_webdav_caldav() {
|
||||||
println!("🧪 rfc4791_webdav_caldav");
|
println!("🧪 rfc4791_webdav_caldav");
|
||||||
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
||||||
// --- INITIAL TEST SETUP ---
|
// --- INITIAL TEST SETUP ---
|
||||||
// Add entries (3 VEVENT, 1 FREEBUSY, 1 VTODO)
|
// Add entries
|
||||||
let resp = http
|
let resp = http
|
||||||
.put("http://localhost:8087/alice/calendar/Personal/rfc1.ics")
|
.put("http://localhost:8087/alice/calendar/Personal/rfc1.ics")
|
||||||
.header("If-None-Match", "*")
|
.header("If-None-Match", "*")
|
||||||
|
@ -595,7 +595,14 @@ fn rfc4791_webdav_caldav() {
|
||||||
.header("If-None-Match", "*")
|
.header("If-None-Match", "*")
|
||||||
.body(ICAL_RFC6)
|
.body(ICAL_RFC6)
|
||||||
.send()?;
|
.send()?;
|
||||||
let _obj6_etag = resp.headers().get("etag").expect("etag must be set");
|
let obj6_etag = resp.headers().get("etag").expect("etag must be set");
|
||||||
|
assert_eq!(resp.status(), 201);
|
||||||
|
let resp = http
|
||||||
|
.put("http://localhost:8087/alice/calendar/Personal/rfc7.ics")
|
||||||
|
.header("If-None-Match", "*")
|
||||||
|
.body(ICAL_RFC7)
|
||||||
|
.send()?;
|
||||||
|
let obj7_etag = resp.headers().get("etag").expect("etag must be set");
|
||||||
assert_eq!(resp.status(), 201);
|
assert_eq!(resp.status(), 201);
|
||||||
|
|
||||||
// A generic function to check a <calendar-data/> query result
|
// A generic function to check a <calendar-data/> query result
|
||||||
|
@ -685,8 +692,43 @@ fn rfc4791_webdav_caldav() {
|
||||||
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
|
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
|
||||||
// Not used by any client I know, so not implementing it now.
|
// Not used by any client I know, so not implementing it now.
|
||||||
|
|
||||||
// --- REPORT calendar-query ---
|
// --- REPORT calendar-multiget ---
|
||||||
//@FIXME missing support for calendar-data...
|
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">
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<C:calendar-data/>
|
||||||
|
</D:prop>
|
||||||
|
<D:href>/alice/calendar/Personal/rfc1.ics</D:href>
|
||||||
|
<D:href>/alice/calendar/Personal/rfc3.ics</D:href>
|
||||||
|
</C:calendar-multiget>"#;
|
||||||
|
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(), 2);
|
||||||
|
[
|
||||||
|
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
||||||
|
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
||||||
|
check_cal(
|
||||||
|
&multistatus,
|
||||||
|
(
|
||||||
|
ref_path,
|
||||||
|
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
||||||
|
Some(ref_ical),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- REPORT calendar-query, only filtering ---
|
||||||
// 7.8.8. Example: Retrieval of Events Only
|
// 7.8.8. Example: Retrieval of Events Only
|
||||||
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
@ -709,12 +751,13 @@ fn rfc4791_webdav_caldav() {
|
||||||
.send()?;
|
.send()?;
|
||||||
assert_eq!(resp.status(), 207);
|
assert_eq!(resp.status(), 207);
|
||||||
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
||||||
assert_eq!(multistatus.responses.len(), 3);
|
assert_eq!(multistatus.responses.len(), 4);
|
||||||
|
|
||||||
[
|
[
|
||||||
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
||||||
("/alice/calendar/Personal/rfc2.ics", obj2_etag, ICAL_RFC2),
|
("/alice/calendar/Personal/rfc2.ics", obj2_etag, ICAL_RFC2),
|
||||||
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
||||||
|
("/alice/calendar/Personal/rfc7.ics", obj7_etag, ICAL_RFC7),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
||||||
|
@ -788,26 +831,32 @@ fn rfc4791_webdav_caldav() {
|
||||||
assert_eq!(resp.status(), 207);
|
assert_eq!(resp.status(), 207);
|
||||||
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
||||||
assert_eq!(multistatus.responses.len(), 1);
|
assert_eq!(multistatus.responses.len(), 1);
|
||||||
|
check_cal(
|
||||||
|
&multistatus,
|
||||||
|
(
|
||||||
|
"/alice/calendar/Personal/rfc6.ics",
|
||||||
|
Some(obj6_etag.to_str().expect("etag header convertible to str")),
|
||||||
|
Some(ICAL_RFC6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// 7.8.6. Example: Retrieval of Event by UID
|
// 7.8.6. Example: Retrieval of Event by UID
|
||||||
// @TODO
|
|
||||||
|
|
||||||
// 7.8.7. Example: Retrieval of Events by PARTSTAT
|
|
||||||
// @TODO
|
|
||||||
|
|
||||||
// 7.8.9. Example: Retrieval of All Pending To-Dos
|
|
||||||
// @TODO
|
|
||||||
|
|
||||||
// --- REPORT calendar-multiget ---
|
|
||||||
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
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">
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
<D:prop>
|
<D:prop xmlns:D="DAV:">
|
||||||
<D:getetag/>
|
<D:getetag/>
|
||||||
<C:calendar-data/>
|
<C:calendar-data/>
|
||||||
</D:prop>
|
</D:prop>
|
||||||
<D:href>/alice/calendar/Personal/rfc1.ics</D:href>
|
<C:filter>
|
||||||
<D:href>/alice/calendar/Personal/rfc3.ics</D:href>
|
<C:comp-filter name="VCALENDAR">
|
||||||
</C:calendar-multiget>"#;
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:prop-filter name="UID">
|
||||||
|
<C:text-match collation="i;octet">DC6C50A017428C5216A2F1CD@example.com</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>"#;
|
||||||
let resp = http
|
let resp = http
|
||||||
.request(
|
.request(
|
||||||
reqwest::Method::from_bytes(b"REPORT")?,
|
reqwest::Method::from_bytes(b"REPORT")?,
|
||||||
|
@ -817,22 +866,61 @@ fn rfc4791_webdav_caldav() {
|
||||||
.send()?;
|
.send()?;
|
||||||
assert_eq!(resp.status(), 207);
|
assert_eq!(resp.status(), 207);
|
||||||
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
||||||
assert_eq!(multistatus.responses.len(), 2);
|
assert_eq!(multistatus.responses.len(), 1);
|
||||||
[
|
|
||||||
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
|
||||||
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
|
||||||
check_cal(
|
check_cal(
|
||||||
&multistatus,
|
&multistatus,
|
||||||
(
|
(
|
||||||
ref_path,
|
"/alice/calendar/Personal/rfc3.ics",
|
||||||
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
Some(obj3_etag.to_str().expect("etag header convertible to str")),
|
||||||
Some(ref_ical),
|
Some(ICAL_RFC3),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// 7.8.7. Example: Retrieval of Events by PARTSTAT
|
||||||
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop xmlns:D="DAV:">
|
||||||
|
<D:getetag/>
|
||||||
|
<C:calendar-data/>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:prop-filter name="ATTENDEE">
|
||||||
|
<C:text-match collation="i;ascii-casemap">mailto:lisa@example.com</C:text-match>
|
||||||
|
<C:param-filter name="PARTSTAT">
|
||||||
|
<C:text-match collation="i;ascii-casemap">NEEDS-ACTION</C:text-match>
|
||||||
|
</C:param-filter>
|
||||||
|
</C:prop-filter>
|
||||||
|
</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/rfc7.ics",
|
||||||
|
Some(obj7_etag.to_str().expect("etag header convertible to str")),
|
||||||
|
Some(ICAL_RFC7),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7.8.9. Example: Retrieval of All Pending To-Dos
|
||||||
|
// @TODO
|
||||||
|
|
||||||
|
// --- REPORT calendar-query, with calendar-data tx ---
|
||||||
|
//@FIXME add support for calendar-data...
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
|
@ -175,3 +175,41 @@ END:VALARM
|
||||||
END:VTODO
|
END:VTODO
|
||||||
END:VCALENDAR
|
END:VCALENDAR
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
pub static ICAL_RFC7: &[u8] = br#"BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
LAST-MODIFIED:20040110T032845Z
|
||||||
|
TZID:US/Eastern
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20000404T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||||
|
TZNAME:EDT
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20001026T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZNAME:EST
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
|
DTSTAMP:20090206T001220Z
|
||||||
|
DTSTART;TZID=US/Eastern:20090104T100000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20090206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:DC6C50A017428C5216A2F1CA@example.com
|
||||||
|
X-ABC-GUID:E1CX5Dr-0007ym-Hz@example.com
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
"#;
|
||||||
|
|
Loading…
Reference in a new issue