mirror of
https://github.com/lennart-k/rustical.git
synced 2026-01-30 11:48:18 +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",
|
||||
"regex",
|
||||
"rrule",
|
||||
"rstest",
|
||||
"rustical_xml",
|
||||
"serde",
|
||||
"sha2",
|
||||
"similar-asserts",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
|
||||
@@ -21,3 +21,5 @@ rrule.workspace = true
|
||||
serde.workspace = true
|
||||
sha2.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);
|
||||
// TODO: Make nice, this is just a bodge to get correct behaviour
|
||||
let mut empty = true;
|
||||
|
||||
for prop in &self.event.properties {
|
||||
rrule_set = match prop.name.as_str() {
|
||||
@@ -76,49 +78,63 @@ impl EventObject {
|
||||
})?)?
|
||||
.validate(dtstart)
|
||||
.unwrap();
|
||||
empty = false;
|
||||
rrule_set.rrule(rrule)
|
||||
}
|
||||
"RDATE" => {
|
||||
let rdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||
empty = false;
|
||||
rrule_set.rdate(rdate)
|
||||
}
|
||||
"EXDATE" => {
|
||||
let exdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||
empty = false;
|
||||
rrule_set.exdate(exdate)
|
||||
}
|
||||
_ => rrule_set,
|
||||
}
|
||||
}
|
||||
if empty {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
start: Option<DateTime<Utc>>,
|
||||
end: Option<DateTime<Utc>>,
|
||||
overrides: &[Self],
|
||||
) -> Result<Vec<IcalEvent>, Error> {
|
||||
let Some(mut rrule_set) = self.recurrence_ruleset()? else {
|
||||
return Ok(vec![self.event.clone()]);
|
||||
};
|
||||
let mut events = vec![];
|
||||
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 {
|
||||
rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
if let Some(end) = end {
|
||||
rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC));
|
||||
}
|
||||
let mut events = vec![];
|
||||
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 {
|
||||
let date = CalDateTime::from(date);
|
||||
let dateformat = if dtstart.is_date() {
|
||||
let date = CalDateTime::from(date.to_utc());
|
||||
let recurrence_id = if dtstart.is_date() {
|
||||
date.format_date()
|
||||
} else {
|
||||
date.format()
|
||||
@@ -131,7 +147,7 @@ impl EventObject {
|
||||
.as_ref()
|
||||
.expect("overrides have a recurrence id")
|
||||
.value
|
||||
&& override_id == &dateformat
|
||||
&& override_id == &recurrence_id
|
||||
{
|
||||
// We have an override for this occurence
|
||||
//
|
||||
@@ -154,13 +170,13 @@ impl EventObject {
|
||||
|
||||
ev.set_property(Property {
|
||||
name: "RECURRENCE-ID".to_string(),
|
||||
value: Some(dateformat.clone()),
|
||||
value: Some(recurrence_id.clone()),
|
||||
params: vec![],
|
||||
});
|
||||
ev.set_property(Property {
|
||||
name: "DTSTART".to_string(),
|
||||
value: Some(dateformat),
|
||||
params: dtstart_prop.params.clone(),
|
||||
value: Some(recurrence_id),
|
||||
params: vec![],
|
||||
});
|
||||
if let Some(duration) = computed_duration {
|
||||
let dtend = date + duration;
|
||||
@@ -183,10 +199,12 @@ impl EventObject {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::CalendarObject;
|
||||
use crate::{CalDateTime, CalendarObject};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ical::generator::Emitter;
|
||||
use rstest::rstest;
|
||||
|
||||
const ICS: &str = r"BEGIN:VCALENDAR
|
||||
const ICS_1: &str = r"BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
@@ -206,16 +224,16 @@ RRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;BYDAY=TU,TH,SU
|
||||
END:VEVENT
|
||||
END:VCALENDAR";
|
||||
|
||||
const EXPANDED: [&str; 4] = [
|
||||
const EXPANDED_1: &[&str] = &[
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
DTSTAMP:20250502T132758Z\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250506T090000\r
|
||||
DTSTART;TZID=Europe/Berlin:20250506T090000\r
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
||||
RECURRENCE-ID:20250506T070000Z\r
|
||||
DTSTART:20250506T070000Z\r
|
||||
DTEND:20250506T072500Z\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
@@ -223,9 +241,9 @@ DTSTAMP:20250502T132758Z\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250508T090000\r
|
||||
DTSTART;TZID=Europe/Berlin:20250508T090000\r
|
||||
DTEND;TZID=Europe/Berlin:20250508T092500\r
|
||||
RECURRENCE-ID:20250508T070000Z\r
|
||||
DTSTART:20250508T070000Z\r
|
||||
DTEND:20250508T072500Z\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
@@ -234,8 +252,8 @@ SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250511T090000\r
|
||||
DTSTART;TZID=Europe/Berlin:20250511T090000\r
|
||||
DTEND;TZID=Europe/Berlin:20250511T092500\r
|
||||
DTSTART:20250511T070000Z\r
|
||||
DTEND:20250511T072500Z\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
@@ -244,25 +262,124 @@ SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250520T090000\r
|
||||
DTSTART;TZID=Europe/Berlin:20250520T090000\r
|
||||
DTEND;TZID=Europe/Berlin:20250520T092500\r
|
||||
DTSTA:20250520T070000Z\r
|
||||
DTEND:20250520T072500Z\r
|
||||
END:VEVENT\r\n",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_expand_recurrence() {
|
||||
let event = CalendarObject::from_ics(ICS.to_string(), None).unwrap();
|
||||
const ICS_2: &str = r"BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
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 {
|
||||
panic!()
|
||||
};
|
||||
|
||||
let events: Vec<String> = event
|
||||
.expand_recurrence(None, None, overrides)
|
||||
.expand_recurrence(from, to, overrides)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|event| Emitter::generate(&event))
|
||||
.collect();
|
||||
assert_eq!(events.as_slice()[0], EXPANDED[0]);
|
||||
assert_eq!(events.as_slice(), &EXPANDED);
|
||||
assert_eq!(events.len(), expanded.len());
|
||||
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> {
|
||||
if let Ok(datetime) = NaiveDateTime::parse_from_str(value, LOCAL_DATE_TIME) {
|
||||
if let Some(timezone) = timezone {
|
||||
|
||||
@@ -5,14 +5,14 @@ use quick_xml::events::BytesStart;
|
||||
use crate::{XmlDeserialize, XmlError};
|
||||
|
||||
// TODO: actually implement
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Unparsed(BytesStart<'static>);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Unparsed(String);
|
||||
|
||||
impl Unparsed {
|
||||
#[must_use]
|
||||
pub fn tag_name(&self) -> String {
|
||||
// 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![];
|
||||
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_import;
|
||||
mod calendar_report;
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
|
||||
@@ -25,14 +25,6 @@ TZOFFSETTO:-0500
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
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
|
||||
DTSTART;TZID=US/Eastern:20060102T100000
|
||||
DURATION:PT1H
|
||||
@@ -49,6 +41,14 @@ SUMMARY:Event #2
|
||||
UID:[UID]
|
||||
END: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=NEEDS-ACTION:mailto:lisa@example.com
|
||||
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