mirror of
https://github.com/lennart-k/rustical.git
synced 2026-01-30 06:58:26 +00:00
Some small fixes on recurrence expansion
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3490,9 +3490,11 @@ dependencies = [
|
|||||||
"ical",
|
"ical",
|
||||||
"regex",
|
"regex",
|
||||||
"rrule",
|
"rrule",
|
||||||
|
"rstest",
|
||||||
"rustical_xml",
|
"rustical_xml",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"similar-asserts",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ rrule.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
similar-asserts.workspace = true
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ impl EventObject {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut rrule_set = RRuleSet::new(dtstart);
|
let mut rrule_set = RRuleSet::new(dtstart);
|
||||||
|
// TODO: Make nice, this is just a bodge to get correct behaviour
|
||||||
|
let mut empty = true;
|
||||||
|
|
||||||
for prop in &self.event.properties {
|
for prop in &self.event.properties {
|
||||||
rrule_set = match prop.name.as_str() {
|
rrule_set = match prop.name.as_str() {
|
||||||
@@ -76,49 +78,63 @@ impl EventObject {
|
|||||||
})?)?
|
})?)?
|
||||||
.validate(dtstart)
|
.validate(dtstart)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
empty = false;
|
||||||
rrule_set.rrule(rrule)
|
rrule_set.rrule(rrule)
|
||||||
}
|
}
|
||||||
"RDATE" => {
|
"RDATE" => {
|
||||||
let rdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
let rdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||||
|
empty = false;
|
||||||
rrule_set.rdate(rdate)
|
rrule_set.rdate(rdate)
|
||||||
}
|
}
|
||||||
"EXDATE" => {
|
"EXDATE" => {
|
||||||
let exdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
let exdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||||
|
empty = false;
|
||||||
rrule_set.exdate(exdate)
|
rrule_set.exdate(exdate)
|
||||||
}
|
}
|
||||||
_ => rrule_set,
|
_ => rrule_set,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if empty {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(rrule_set))
|
Ok(Some(rrule_set))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The returned calendar components MUST NOT use recurrence
|
||||||
|
// properties (i.e., EXDATE, EXRULE, RDATE, and RRULE) and MUST NOT
|
||||||
|
// have reference to or include VTIMEZONE components. Date and local
|
||||||
|
// time with reference to time zone information MUST be converted
|
||||||
|
// into date with UTC time.
|
||||||
pub fn expand_recurrence(
|
pub fn expand_recurrence(
|
||||||
&self,
|
&self,
|
||||||
start: Option<DateTime<Utc>>,
|
start: Option<DateTime<Utc>>,
|
||||||
end: Option<DateTime<Utc>>,
|
end: Option<DateTime<Utc>>,
|
||||||
overrides: &[Self],
|
overrides: &[Self],
|
||||||
) -> Result<Vec<IcalEvent>, Error> {
|
) -> Result<Vec<IcalEvent>, Error> {
|
||||||
let Some(mut rrule_set) = self.recurrence_ruleset()? else {
|
let mut events = vec![];
|
||||||
return Ok(vec![self.event.clone()]);
|
let dtstart = self.get_dtstart()?.expect("We must have a DTSTART here");
|
||||||
};
|
let computed_duration = self
|
||||||
|
.get_dtend()?
|
||||||
|
.map(|dtend| dtend.as_datetime().into_owned() - dtstart.as_datetime().as_ref());
|
||||||
|
|
||||||
|
let Some(mut rrule_set) = self.recurrence_ruleset()? else {
|
||||||
|
// If ruleset empty simply return main event AND all overrides
|
||||||
|
return Ok(std::iter::once(self.clone())
|
||||||
|
.chain(overrides.iter().cloned())
|
||||||
|
.map(|event| event.event)
|
||||||
|
.collect());
|
||||||
|
};
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC));
|
rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC));
|
||||||
}
|
}
|
||||||
if let Some(end) = end {
|
if let Some(end) = end {
|
||||||
rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC));
|
rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC));
|
||||||
}
|
}
|
||||||
let mut events = vec![];
|
|
||||||
let dates = rrule_set.all(2048).dates;
|
let dates = rrule_set.all(2048).dates;
|
||||||
let dtstart = self.get_dtstart()?.expect("We must have a DTSTART here");
|
|
||||||
let computed_duration = self
|
|
||||||
.get_dtend()?
|
|
||||||
.map(|dtend| dtend.as_datetime().into_owned() - dtstart.as_datetime().as_ref());
|
|
||||||
|
|
||||||
'recurrence: for date in dates {
|
'recurrence: for date in dates {
|
||||||
let date = CalDateTime::from(date);
|
let date = CalDateTime::from(date.to_utc());
|
||||||
let dateformat = if dtstart.is_date() {
|
let recurrence_id = if dtstart.is_date() {
|
||||||
date.format_date()
|
date.format_date()
|
||||||
} else {
|
} else {
|
||||||
date.format()
|
date.format()
|
||||||
@@ -131,7 +147,7 @@ impl EventObject {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("overrides have a recurrence id")
|
.expect("overrides have a recurrence id")
|
||||||
.value
|
.value
|
||||||
&& override_id == &dateformat
|
&& override_id == &recurrence_id
|
||||||
{
|
{
|
||||||
// We have an override for this occurence
|
// We have an override for this occurence
|
||||||
//
|
//
|
||||||
@@ -154,13 +170,13 @@ impl EventObject {
|
|||||||
|
|
||||||
ev.set_property(Property {
|
ev.set_property(Property {
|
||||||
name: "RECURRENCE-ID".to_string(),
|
name: "RECURRENCE-ID".to_string(),
|
||||||
value: Some(dateformat.clone()),
|
value: Some(recurrence_id.clone()),
|
||||||
params: vec![],
|
params: vec![],
|
||||||
});
|
});
|
||||||
ev.set_property(Property {
|
ev.set_property(Property {
|
||||||
name: "DTSTART".to_string(),
|
name: "DTSTART".to_string(),
|
||||||
value: Some(dateformat),
|
value: Some(recurrence_id),
|
||||||
params: dtstart_prop.params.clone(),
|
params: vec![],
|
||||||
});
|
});
|
||||||
if let Some(duration) = computed_duration {
|
if let Some(duration) = computed_duration {
|
||||||
let dtend = date + duration;
|
let dtend = date + duration;
|
||||||
@@ -183,10 +199,12 @@ impl EventObject {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::CalendarObject;
|
use crate::{CalDateTime, CalendarObject};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use ical::generator::Emitter;
|
use ical::generator::Emitter;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
const ICS: &str = r"BEGIN:VCALENDAR
|
const ICS_1: &str = r"BEGIN:VCALENDAR
|
||||||
CALSCALE:GREGORIAN
|
CALSCALE:GREGORIAN
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
BEGIN:VTIMEZONE
|
BEGIN:VTIMEZONE
|
||||||
@@ -206,16 +224,16 @@ RRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;BYDAY=TU,TH,SU
|
|||||||
END:VEVENT
|
END:VEVENT
|
||||||
END:VCALENDAR";
|
END:VCALENDAR";
|
||||||
|
|
||||||
const EXPANDED: [&str; 4] = [
|
const EXPANDED_1: &[&str] = &[
|
||||||
"BEGIN:VEVENT\r
|
"BEGIN:VEVENT\r
|
||||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||||
DTSTAMP:20250502T132758Z\r
|
DTSTAMP:20250502T132758Z\r
|
||||||
SEQUENCE:2\r
|
SEQUENCE:2\r
|
||||||
SUMMARY:weekly stuff\r
|
SUMMARY:weekly stuff\r
|
||||||
TRANSP:OPAQUE\r
|
TRANSP:OPAQUE\r
|
||||||
RECURRENCE-ID:20250506T090000\r
|
RECURRENCE-ID:20250506T070000Z\r
|
||||||
DTSTART;TZID=Europe/Berlin:20250506T090000\r
|
DTSTART:20250506T070000Z\r
|
||||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
DTEND:20250506T072500Z\r
|
||||||
END:VEVENT\r\n",
|
END:VEVENT\r\n",
|
||||||
"BEGIN:VEVENT\r
|
"BEGIN:VEVENT\r
|
||||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||||
@@ -223,9 +241,9 @@ DTSTAMP:20250502T132758Z\r
|
|||||||
SEQUENCE:2\r
|
SEQUENCE:2\r
|
||||||
SUMMARY:weekly stuff\r
|
SUMMARY:weekly stuff\r
|
||||||
TRANSP:OPAQUE\r
|
TRANSP:OPAQUE\r
|
||||||
RECURRENCE-ID:20250508T090000\r
|
RECURRENCE-ID:20250508T070000Z\r
|
||||||
DTSTART;TZID=Europe/Berlin:20250508T090000\r
|
DTSTART:20250508T070000Z\r
|
||||||
DTEND;TZID=Europe/Berlin:20250508T092500\r
|
DTEND:20250508T072500Z\r
|
||||||
END:VEVENT\r\n",
|
END:VEVENT\r\n",
|
||||||
"BEGIN:VEVENT\r
|
"BEGIN:VEVENT\r
|
||||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||||
@@ -234,8 +252,8 @@ SEQUENCE:2\r
|
|||||||
SUMMARY:weekly stuff\r
|
SUMMARY:weekly stuff\r
|
||||||
TRANSP:OPAQUE\r
|
TRANSP:OPAQUE\r
|
||||||
RECURRENCE-ID:20250511T090000\r
|
RECURRENCE-ID:20250511T090000\r
|
||||||
DTSTART;TZID=Europe/Berlin:20250511T090000\r
|
DTSTART:20250511T070000Z\r
|
||||||
DTEND;TZID=Europe/Berlin:20250511T092500\r
|
DTEND:20250511T072500Z\r
|
||||||
END:VEVENT\r\n",
|
END:VEVENT\r\n",
|
||||||
"BEGIN:VEVENT\r
|
"BEGIN:VEVENT\r
|
||||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||||
@@ -244,25 +262,124 @@ SEQUENCE:2\r
|
|||||||
SUMMARY:weekly stuff\r
|
SUMMARY:weekly stuff\r
|
||||||
TRANSP:OPAQUE\r
|
TRANSP:OPAQUE\r
|
||||||
RECURRENCE-ID:20250520T090000\r
|
RECURRENCE-ID:20250520T090000\r
|
||||||
DTSTART;TZID=Europe/Berlin:20250520T090000\r
|
DTSTA:20250520T070000Z\r
|
||||||
DTEND;TZID=Europe/Berlin:20250520T092500\r
|
DTEND:20250520T072500Z\r
|
||||||
END:VEVENT\r\n",
|
END:VEVENT\r\n",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[test]
|
const ICS_2: &str = r"BEGIN:VCALENDAR
|
||||||
fn test_expand_recurrence() {
|
CALSCALE:GREGORIAN
|
||||||
let event = CalendarObject::from_ics(ICS.to_string(), None).unwrap();
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Eastern
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060102T120000
|
||||||
|
DURATION:PT1H
|
||||||
|
RRULE:FREQ=DAILY;COUNT=5
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T140000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
||||||
|
SUMMARY:Event #2 bis
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
";
|
||||||
|
|
||||||
|
const EXPANDED_2: &[&str] = &[
|
||||||
|
"BEGIN:VEVENT\r
|
||||||
|
DTSTAMP:20060206T001121Z\r
|
||||||
|
DURATION:PT1H\r
|
||||||
|
SUMMARY:Event #2\r
|
||||||
|
UID:abcd2\r
|
||||||
|
RECURRENCE-ID:20060103T170000\r
|
||||||
|
DTSTART:20060103T170000\r
|
||||||
|
END:VEVENT\r\n",
|
||||||
|
"BEGIN:VEVENT\r
|
||||||
|
DTSTAMP:20060206T001121Z\r
|
||||||
|
DURATION:PT1H\r
|
||||||
|
SUMMARY:Event #2 bis\r
|
||||||
|
UID:abcd2\r
|
||||||
|
RECURRENCE-ID:20060104T170000\r
|
||||||
|
DTSTART:20060104T190000\r
|
||||||
|
END:VEVENT\r
|
||||||
|
END:VCALENDAR\r\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
const ICS_3: &str = r"BEGIN:VCALENDAR
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Eastern
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
|
DTSTAMP:20060206T001220Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T100000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:abcd3
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
";
|
||||||
|
|
||||||
|
const EXPANDED_3: &[&str] = &["BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
|
DTSTAMP:20060206T001220Z
|
||||||
|
DTSTART:20060104T150000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:abcd3
|
||||||
|
X-ABC-GUID:E1CX5Dr-0007ym-Hz@example.com
|
||||||
|
END:VEVENT"];
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ICS_1, EXPANDED_1, None, None)]
|
||||||
|
// from https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.3
|
||||||
|
#[case(ICS_2, EXPANDED_2,
|
||||||
|
Some(CalDateTime::parse("20060103T000000Z", Some(chrono_tz::US::Eastern)).unwrap().utc()),
|
||||||
|
Some(CalDateTime::parse("20060105T000000Z", Some(chrono_tz::US::Eastern)).unwrap().utc())
|
||||||
|
)]
|
||||||
|
#[case(ICS_3, EXPANDED_3,
|
||||||
|
Some(CalDateTime::parse("20060103T000000Z", Some(chrono_tz::US::Eastern)).unwrap().utc()),
|
||||||
|
Some(CalDateTime::parse("20060105T000000Z", Some(chrono_tz::US::Eastern)).unwrap().utc())
|
||||||
|
)]
|
||||||
|
fn test_expand_recurrence(
|
||||||
|
#[case] ics: &'static str,
|
||||||
|
#[case] expanded: &[&str],
|
||||||
|
#[case] from: Option<DateTime<Utc>>,
|
||||||
|
#[case] to: Option<DateTime<Utc>>,
|
||||||
|
) {
|
||||||
|
let event = CalendarObject::from_ics(ics.to_string(), None).unwrap();
|
||||||
let crate::CalendarObjectComponent::Event(event, overrides) = event.get_data() else {
|
let crate::CalendarObjectComponent::Event(event, overrides) = event.get_data() else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let events: Vec<String> = event
|
let events: Vec<String> = event
|
||||||
.expand_recurrence(None, None, overrides)
|
.expand_recurrence(from, to, overrides)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|event| Emitter::generate(&event))
|
.map(|event| Emitter::generate(&event))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(events.as_slice()[0], EXPANDED[0]);
|
assert_eq!(events.len(), expanded.len());
|
||||||
assert_eq!(events.as_slice(), &EXPANDED);
|
for (output, reference) in events.iter().zip(expanded) {
|
||||||
|
similar_asserts::assert_eq!(output, reference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,14 @@ impl CalDateTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_timezone(&self, tz: &ICalTimezone) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::DateTime(datetime) => Self::DateTime(datetime.with_timezone(tz)),
|
||||||
|
Self::Date(date, _) => Self::Date(date.to_owned(), tz.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(value: &str, timezone: Option<Tz>) -> Result<Self, CalDateTimeError> {
|
pub fn parse(value: &str, timezone: Option<Tz>) -> Result<Self, CalDateTimeError> {
|
||||||
if let Ok(datetime) = NaiveDateTime::parse_from_str(value, LOCAL_DATE_TIME) {
|
if let Ok(datetime) = NaiveDateTime::parse_from_str(value, LOCAL_DATE_TIME) {
|
||||||
if let Some(timezone) = timezone {
|
if let Some(timezone) = timezone {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use quick_xml::events::BytesStart;
|
|||||||
use crate::{XmlDeserialize, XmlError};
|
use crate::{XmlDeserialize, XmlError};
|
||||||
|
|
||||||
// TODO: actually implement
|
// TODO: actually implement
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Unparsed(BytesStart<'static>);
|
pub struct Unparsed(String);
|
||||||
|
|
||||||
impl Unparsed {
|
impl Unparsed {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn tag_name(&self) -> String {
|
pub fn tag_name(&self) -> String {
|
||||||
// TODO: respect namespace?
|
// TODO: respect namespace?
|
||||||
String::from_utf8_lossy(self.0.local_name().as_ref()).to_string()
|
self.0.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ impl XmlDeserialize for Unparsed {
|
|||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
reader.read_to_end_into(start.name(), &mut buf)?;
|
reader.read_to_end_into(start.name(), &mut buf)?;
|
||||||
}
|
}
|
||||||
Ok(Self(start.to_owned()))
|
let tag_name = String::from_utf8_lossy(start.local_name().as_ref()).to_string();
|
||||||
|
Ok(Self(tag_name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
253
docs/developers/rfcs/rfc4791.md
Normal file
253
docs/developers/rfcs/rfc4791.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# RFC 4791 (CalDAV)
|
||||||
|
|
||||||
|
## ☑ 1. Introduction
|
||||||
|
|
||||||
|
### ☑ 1.1 Notational Conventions
|
||||||
|
|
||||||
|
### ☑ 1.2 XML Namespaces and Processing
|
||||||
|
|
||||||
|
### ☐ 1.3 Method Preconditions and Postconditions
|
||||||
|
|
||||||
|
## ☐ 2. Requirements Overview
|
||||||
|
|
||||||
|
- [x] MUST support iCalendar [RFC2445] as a media type for the calendar
|
||||||
|
object resource format;
|
||||||
|
|
||||||
|
- [ ] MUST support WebDAV Class 1 [RFC2518] (note that [rfc2518bis]
|
||||||
|
describes clarifications to [RFC2518] that aid interoperability);
|
||||||
|
|
||||||
|
- [x] MUST support WebDAV ACL [RFC3744] with the additional privilege
|
||||||
|
defined in Section 6.1 of this document;
|
||||||
|
|
||||||
|
- [x] MUST support transport over TLS [RFC2246] as defined in [RFC2818]
|
||||||
|
(note that [RFC2246] has been obsoleted by [RFC4346]);
|
||||||
|
|
||||||
|
- [x] MUST support ETags [RFC2616] with additional requirements
|
||||||
|
specified in Section 5.3.4 of this document;
|
||||||
|
|
||||||
|
- [ ] MUST support all calendaring reports defined in Section 7 of this
|
||||||
|
document; and
|
||||||
|
|
||||||
|
- [x] MUST advertise support on all calendar collections and calendar
|
||||||
|
object resources for the calendaring reports in the DAV:supported-
|
||||||
|
report-set property, as defined in Versioning Extensions to WebDAV
|
||||||
|
[RFC3253].
|
||||||
|
|
||||||
|
In addition, a server:
|
||||||
|
|
||||||
|
- [x] SHOULD support the MKCALENDAR method defined in Section 5.3.1 of
|
||||||
|
this document.
|
||||||
|
|
||||||
|
## ☑ 3. Calendaring Data Model
|
||||||
|
|
||||||
|
### ☑ 3.1 Calendar Server
|
||||||
|
|
||||||
|
### ☑ 3.2 Recurrence and the Data Model
|
||||||
|
|
||||||
|
## ☑ 4. Calendar Resources
|
||||||
|
|
||||||
|
### ☑ 4.1 Calendar Object Resources
|
||||||
|
|
||||||
|
### ☑ 4.2 Calendar Collection
|
||||||
|
|
||||||
|
## ☐ 5. Calendar Access Feature
|
||||||
|
|
||||||
|
### ☑ 5.1 Calendar Access Support
|
||||||
|
|
||||||
|
#### ☑ 5.1.1 Example: Using OPTIONS for the Discovery of Calendar Access Support
|
||||||
|
|
||||||
|
### ☑ 5.2 Calendar Collection Properties
|
||||||
|
|
||||||
|
#### ☑ 5.2.1 CALDAV:calendar-description Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.2 CALDAV:calendar-timezone Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.3 CALDAV:supported-calendar-component-set Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.4 CALDAV:supported-calendar-data Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.5 CALDAV:max-resource-size Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.6 CALDAV:min-date-time Property
|
||||||
|
|
||||||
|
#### ☑ 5.2.7 CALDAV:max-date-time Property
|
||||||
|
|
||||||
|
#### ☐ 5.2.8 CALDAV:max-instances Property (Maybe set this :))
|
||||||
|
|
||||||
|
#### ☑ 5.2.9 CALDAV:max-attendees-per-instance Property (does not apply)
|
||||||
|
|
||||||
|
#### ☑ 5.2.10 Additional Precondition for PROPPATCH
|
||||||
|
|
||||||
|
### ☑ 5.3 Creating Resources
|
||||||
|
|
||||||
|
#### ☑ 5.3.1 MKCALENDAR Method
|
||||||
|
|
||||||
|
##### ☑ 5.3.1.1 Status Codes
|
||||||
|
|
||||||
|
##### ☑ 5.3.1.2 Example: Successful MKCALENDAR Request
|
||||||
|
|
||||||
|
- Example fails because of the tzid is not in the Olson database, but that's okay
|
||||||
|
|
||||||
|
#### ☑ 5.3.2 Creating Calendar Object Resources
|
||||||
|
|
||||||
|
##### ☐ 5.3.2.1 Additional Preconditions for PUT, COPY, and MOVE
|
||||||
|
|
||||||
|
### ☑ 5.3.3 Non-Standard Components, Properties, and Parameters
|
||||||
|
|
||||||
|
### ☑ 5.3.4 Calendar Object Resource Entity Tag
|
||||||
|
|
||||||
|
## ☐ 6. Calendaring Access Control
|
||||||
|
|
||||||
|
### ☐ 6.1 Calendaring Privilege
|
||||||
|
|
||||||
|
#### ☐ 6.1.1 CALDAV:read-free-busy Privilege
|
||||||
|
|
||||||
|
### ☑ 6.2 Additional Principal Property
|
||||||
|
|
||||||
|
#### ☑ 6.2.1 CALDAV:calendar-home-set Property
|
||||||
|
|
||||||
|
## ☐ 7. Calendaring Reports
|
||||||
|
|
||||||
|
- [ ] `DAV:expand-property`
|
||||||
|
|
||||||
|
### ☑ 7.1 REPORT Method
|
||||||
|
|
||||||
|
### ☑ 7.2 Ordinary Collections
|
||||||
|
|
||||||
|
### ☑ 7.3 Date and Floating Time
|
||||||
|
|
||||||
|
### ☑ 7.4 Time Range Filtering
|
||||||
|
|
||||||
|
### ☑ 7.5 Searching Text: Collations
|
||||||
|
|
||||||
|
#### ☑ 7.5.1 CALDAV:supported-collation-set Property
|
||||||
|
|
||||||
|
### ☐ 7.6 Partial Retrieval
|
||||||
|
|
||||||
|
### ☑ 7.7 Non-Standard Components, Properties, and Parameters
|
||||||
|
|
||||||
|
### ☑ 7.8 CALDAV:calendar-query REPORT
|
||||||
|
|
||||||
|
#### ☐ 7.8.1 Example: Partial Retrieval of Events by Time Range
|
||||||
|
|
||||||
|
#### ☐ 7.8.2 Example: Partial Retrieval of Recurring Events
|
||||||
|
|
||||||
|
#### ☐ 7.8.3 Example: Expanded Retrieval of Recurring Events
|
||||||
|
|
||||||
|
#### ☐ 7.8.4 Example: Partial Retrieval of Stored Free Busy Components
|
||||||
|
|
||||||
|
#### ☐ 7.8.5 Example: Retrieval of To-Dos by Alarm Time Range
|
||||||
|
|
||||||
|
#### ☐ 7.8.6 Example: Retrieval of Event by UID
|
||||||
|
|
||||||
|
#### ☐ 7.8.7 Example: Retrieval of Events by PARTSTAT
|
||||||
|
|
||||||
|
#### ☐ 7.8.8 Example: Retrieval of Events Only
|
||||||
|
|
||||||
|
#### ☐ 7.8.9 Example: Retrieval of All Pending To-Dos
|
||||||
|
|
||||||
|
#### ☐ 7.8.10 Example: Attempt to Query Unsupported Property
|
||||||
|
|
||||||
|
### ☐ 7.9 CALDAV:calendar-multiget REPORT
|
||||||
|
|
||||||
|
#### ☐ 7.9.1 Example: Successful CALDAV:calendar-multiget REPORT
|
||||||
|
|
||||||
|
### ☐ 7.10 CALDAV:free-busy-query REPORT
|
||||||
|
|
||||||
|
#### ☐ 7.10.1 Example: Successful CALDAV:free-busy-query REPORT
|
||||||
|
|
||||||
|
## ☐ 8. Guidelines
|
||||||
|
|
||||||
|
### ☐ 8.1 Client-to-Client Interoperability
|
||||||
|
|
||||||
|
### ☐ 8.2 Synchronization Operations
|
||||||
|
|
||||||
|
#### ☐ 8.2.1 Use of Reports
|
||||||
|
|
||||||
|
##### ☐ 8.2.1.1 Restrict the Time Range
|
||||||
|
|
||||||
|
##### ☐ 8.2.1.2 Synchronize by Time Range
|
||||||
|
|
||||||
|
##### ☐ 8.2.1.3 Synchronization Process
|
||||||
|
|
||||||
|
#### ☐ 8.2.2 Restrict the Properties Returned
|
||||||
|
|
||||||
|
### ☐ 8.3 Use of Locking
|
||||||
|
|
||||||
|
### ☐ 8.4 Finding Calendars
|
||||||
|
|
||||||
|
### ☐ 8.5 Storing and Using Attachments
|
||||||
|
|
||||||
|
#### ☐ 8.5.1 Inline Attachments
|
||||||
|
|
||||||
|
#### ☐ 8.5.2 External Attachments
|
||||||
|
|
||||||
|
### ☐ 8.6 Storing and Using Alarms
|
||||||
|
|
||||||
|
## ☐ 9. XML Element Definitions
|
||||||
|
|
||||||
|
### ☐ 9.1 CALDAV:calendar XML Element
|
||||||
|
|
||||||
|
### ☐ 9.2 CALDAV:mkcalendar XML Element
|
||||||
|
|
||||||
|
### ☐ 9.3 CALDAV:mkcalendar-response XML Element
|
||||||
|
|
||||||
|
### ☐ 9.4 CALDAV:supported-collation XML Element
|
||||||
|
|
||||||
|
### ☐ 9.5 CALDAV:calendar-query XML Element
|
||||||
|
|
||||||
|
### ☐ 9.6 CALDAV:calendar-data XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.1 CALDAV:comp XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.2 CALDAV:allcomp XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.3 CALDAV:allprop XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.4 CALDAV:prop XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.5 CALDAV:expand XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.6 CALDAV:limit-recurrence-set XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.6.7 CALDAV:limit-freebusy-set XML Element
|
||||||
|
|
||||||
|
### ☐ 9.7 CALDAV:filter XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.7.1 CALDAV:comp-filter XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.7.2 CALDAV:prop-filter XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.7.3 CALDAV:param-filter XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.7.4 CALDAV:is-not-defined XML Element
|
||||||
|
|
||||||
|
#### ☐ 9.7.5 CALDAV:text-match XML Element
|
||||||
|
|
||||||
|
### ☐ 9.8 CALDAV:timezone XML Element
|
||||||
|
|
||||||
|
### ☐ 9.9 CALDAV:time-range XML Element
|
||||||
|
|
||||||
|
### ☐ 9.10 CALDAV:calendar-multiget XML Element
|
||||||
|
|
||||||
|
### ☐ 9.11 CALDAV:free-busy-query XML Element
|
||||||
|
|
||||||
|
## ☐ 10. Internationalization Considerations
|
||||||
|
|
||||||
|
## ☐ 11. Security Considerations
|
||||||
|
|
||||||
|
## ☐ 12. IANA Considerations
|
||||||
|
|
||||||
|
### ☐ 12.1 Namespace Registration
|
||||||
|
|
||||||
|
## ☐ 13. Acknowledgements
|
||||||
|
|
||||||
|
## ☐ 14. References
|
||||||
|
|
||||||
|
### ☐ 14.1 Normative References
|
||||||
|
|
||||||
|
### ☐ 14.2 Informative References
|
||||||
|
|
||||||
|
## ☐ A. CalDAV Method Privilege Table (Normative)
|
||||||
|
|
||||||
|
## ☐ B. Calendar Collections Used in the Examples
|
||||||
196
src/integration_tests/caldav/calendar_report.rs
Normal file
196
src/integration_tests/caldav/calendar_report.rs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
use crate::integration_tests::{ResponseExtractString, get_app};
|
||||||
|
use axum::body::Body;
|
||||||
|
use axum::extract::Request;
|
||||||
|
use headers::{Authorization, HeaderMapExt};
|
||||||
|
use http::StatusCode;
|
||||||
|
use rstest::rstest;
|
||||||
|
use rustical_store_sqlite::tests::{TestStoreContext, test_store_context};
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
const ICS_1: &str = include_str!("resources/rfc4791_appb.ics");
|
||||||
|
|
||||||
|
const REPORT_7_8_1: &str = 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/>
|
||||||
|
<C:calendar-data>
|
||||||
|
<C:comp name="VCALENDAR">
|
||||||
|
<C:prop name="VERSION"/>
|
||||||
|
<C:comp name="VEVENT">
|
||||||
|
<C:prop name="SUMMARY"/>
|
||||||
|
<C:prop name="UID"/>
|
||||||
|
<C:prop name="DTSTART"/>
|
||||||
|
<C:prop name="DTEND"/>
|
||||||
|
<C:prop name="DURATION"/>
|
||||||
|
<C:prop name="RRULE"/>
|
||||||
|
<C:prop name="RDATE"/>
|
||||||
|
<C:prop name="EXRULE"/>
|
||||||
|
<C:prop name="EXDATE"/>
|
||||||
|
<C:prop name="RECURRENCE-ID"/>
|
||||||
|
</C:comp>
|
||||||
|
<C:comp name="VTIMEZONE"/>
|
||||||
|
</C:comp>
|
||||||
|
</C:calendar-data>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:time-range start="20060104T000000Z"
|
||||||
|
end="20060105T000000Z"/>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const REPORT_7_8_2: &str = r#"
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:D="DAV:"
|
||||||
|
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop>
|
||||||
|
<C:calendar-data>
|
||||||
|
<C:limit-recurrence-set start="20060103T000000Z"
|
||||||
|
end="20060105T000000Z"/>
|
||||||
|
</C:calendar-data>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:time-range start="20060103T000000Z"
|
||||||
|
end="20060105T000000Z"/>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const REPORT_7_8_3: &str = r#"
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:D="DAV:"
|
||||||
|
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop>
|
||||||
|
<C:calendar-data>
|
||||||
|
<C:expand start="20060103T000000Z"
|
||||||
|
end="20060105T000000Z"/>
|
||||||
|
</C:calendar-data>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:time-range start="20060103T000000Z"
|
||||||
|
end="20060105T000000Z"/>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const OUTPUT_7_8_3: &str = r#"
|
||||||
|
<D:response>
|
||||||
|
<D:href>http://cal.example.com/bernard/work/abcd2.ics</D:href>
|
||||||
|
<D:propstat>
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag>"fffff-abcd2"</D:getetag>
|
||||||
|
<C:calendar-data>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART:20060103T170000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID:20060103T170000
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:00959BC664CA650E933C892C@example.com
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART:20060104T190000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID:20060104T170000
|
||||||
|
SUMMARY:Event #2 bis
|
||||||
|
UID:00959BC664CA650E933C892C@example.com
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</C:calendar-data>
|
||||||
|
</D:prop>
|
||||||
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
|
</D:propstat>
|
||||||
|
</D:response>
|
||||||
|
<D:response>
|
||||||
|
<D:href>http://cal.example.com/bernard/work/abcd3.ics</D:href>
|
||||||
|
<D:propstat>
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag>"fffff-abcd3"</D:getetag>
|
||||||
|
<C:calendar-data>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
|
DTSTAMP:20060206T001220Z
|
||||||
|
DTSTART:20060104T150000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:DC6C50A017428C5216A2F1CD@example.com
|
||||||
|
X-ABC-GUID:E1CX5Dr-0007ym-Hz@example.com
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</C:calendar-data>
|
||||||
|
</D:prop>
|
||||||
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
|
</D:propstat>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ICS_1, REPORT_7_8_1)]
|
||||||
|
#[case(1, ICS_1, REPORT_7_8_2)]
|
||||||
|
#[case(2, ICS_1, REPORT_7_8_3)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_report(
|
||||||
|
#[from(test_store_context)]
|
||||||
|
#[future]
|
||||||
|
context: TestStoreContext,
|
||||||
|
#[case] case: usize,
|
||||||
|
#[case] ics: &'static str,
|
||||||
|
#[case] report: &'static str,
|
||||||
|
) {
|
||||||
|
let context = context.await;
|
||||||
|
let app = get_app(context.clone());
|
||||||
|
|
||||||
|
let (principal, addr_id) = ("user", "calendar");
|
||||||
|
let url = format!("/caldav/principal/{principal}/{addr_id}");
|
||||||
|
|
||||||
|
let request_template = || {
|
||||||
|
Request::builder()
|
||||||
|
.method("IMPORT")
|
||||||
|
.uri(&url)
|
||||||
|
.body(Body::from(ics))
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
// Try with correct credentials
|
||||||
|
let mut request = request_template();
|
||||||
|
request
|
||||||
|
.headers_mut()
|
||||||
|
.typed_insert(Authorization::basic("user", "pass"));
|
||||||
|
let response = app.clone().oneshot(request).await.unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let mut request = Request::builder()
|
||||||
|
.method("REPORT")
|
||||||
|
.uri(&url)
|
||||||
|
.body(Body::from(report))
|
||||||
|
.unwrap();
|
||||||
|
request
|
||||||
|
.headers_mut()
|
||||||
|
.typed_insert(Authorization::basic("user", "pass"));
|
||||||
|
let response = app.clone().oneshot(request).await.unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
|
||||||
|
let body = response.extract_string().await;
|
||||||
|
insta::assert_snapshot!(format!("{case}_report_body"), body);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use tower::ServiceExt;
|
|||||||
|
|
||||||
mod calendar;
|
mod calendar;
|
||||||
mod calendar_import;
|
mod calendar_import;
|
||||||
|
mod calendar_report;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -25,14 +25,6 @@ TZOFFSETTO:-0500
|
|||||||
END:STANDARD
|
END:STANDARD
|
||||||
END:VTIMEZONE
|
END:VTIMEZONE
|
||||||
BEGIN:VEVENT
|
BEGIN:VEVENT
|
||||||
DTSTAMP:20060206T001121Z
|
|
||||||
DTSTART;TZID=US/Eastern:20060104T140000
|
|
||||||
DURATION:PT1H
|
|
||||||
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
|
||||||
SUMMARY:Event #2 bis
|
|
||||||
UID:[UID]
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
DTSTAMP:20060206T001102Z
|
DTSTAMP:20060206T001102Z
|
||||||
DTSTART;TZID=US/Eastern:20060102T100000
|
DTSTART;TZID=US/Eastern:20060102T100000
|
||||||
DURATION:PT1H
|
DURATION:PT1H
|
||||||
@@ -49,6 +41,14 @@ SUMMARY:Event #2
|
|||||||
UID:[UID]
|
UID:[UID]
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
BEGIN:VEVENT
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T140000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
||||||
|
SUMMARY:Event #2 bis
|
||||||
|
UID:[UID]
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
DTSTAMP:20060206T001220Z
|
DTSTAMP:20060206T001220Z
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
source: src/integration_tests/caldav/calendar_report.rs
|
||||||
|
expression: body
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd2.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<getetag>"7d80077c5655339885a36b6dbe97336767fb85e6b12c94668bcac100ed971fac"</getetag>
|
||||||
|
<CAL:calendar-data>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
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060102T120000
|
||||||
|
DURATION:PT1H
|
||||||
|
RRULE:FREQ=DAILY;COUNT=5
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T140000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
||||||
|
SUMMARY:Event #2 bis
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd3.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<getetag>"c6a5b1cf6985805686df99e7f2e1cf286567dcb3383fc6fa1b12ce42d3fbc01c"</getetag>
|
||||||
|
<CAL:calendar-data>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:20060206T001220Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T100000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:abcd3
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
</multistatus>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
source: src/integration_tests/caldav/calendar_report.rs
|
||||||
|
expression: body
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd2.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<CAL:calendar-data>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
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060102T120000
|
||||||
|
DURATION:PT1H
|
||||||
|
RRULE:FREQ=DAILY;COUNT=5
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T140000
|
||||||
|
DURATION:PT1H
|
||||||
|
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
||||||
|
SUMMARY:Event #2 bis
|
||||||
|
UID:abcd2
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd3.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<CAL:calendar-data>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:20060206T001220Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T100000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:abcd3
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
</multistatus>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
source: src/integration_tests/caldav/calendar_report.rs
|
||||||
|
assertion_line: 195
|
||||||
|
expression: body
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd2.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<CAL:calendar-data>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DURATION:PT1H
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:abcd2
|
||||||
|
RECURRENCE-ID:20060103T170000Z
|
||||||
|
DTSTART:20060103T170000Z
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:20060206T001121Z
|
||||||
|
DURATION:PT1H
|
||||||
|
SUMMARY:Event #2
|
||||||
|
UID:abcd2
|
||||||
|
RECURRENCE-ID:20060104T170000Z
|
||||||
|
DTSTART:20060104T170000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
<response>
|
||||||
|
<href>/caldav/principal/user/calendar/abcd3.ics</href>
|
||||||
|
<propstat>
|
||||||
|
<prop>
|
||||||
|
<CAL:calendar-data>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||||
|
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||||
|
DTSTAMP:20060206T001220Z
|
||||||
|
DTSTART;TZID=US/Eastern:20060104T100000
|
||||||
|
DURATION:PT1H
|
||||||
|
LAST-MODIFIED:20060206T001330Z
|
||||||
|
ORGANIZER:mailto:cyrus@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:TENTATIVE
|
||||||
|
SUMMARY:Event #3
|
||||||
|
UID:abcd3
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
</CAL:calendar-data>
|
||||||
|
</prop>
|
||||||
|
<status>HTTP/1.1 200 OK</status>
|
||||||
|
</propstat>
|
||||||
|
</response>
|
||||||
|
</multistatus>
|
||||||
Reference in New Issue
Block a user