Skip to main content

rustical_caldav/calendar_object/
resource.rs

1use super::prop::{
2    CalendarData, CalendarObjectProp, CalendarObjectPropName, CalendarObjectPropWrapper,
3    CalendarObjectPropWrapperName,
4};
5use crate::Error;
6use caldata::generator::Emitter;
7use derive_more::derive::{From, Into};
8use rustical_dav::{
9    extensions::CommonPropertiesExtension,
10    privileges::UserPrivilegeSet,
11    resource::{PrincipalUri, Resource, ResourceName},
12    resourcetype,
13    xml::Resourcetype,
14};
15use rustical_ical::CalendarObject;
16use rustical_store::auth::Principal;
17use std::borrow::Cow;
18
19#[derive(Clone, From, Into)]
20pub struct CalendarObjectResource {
21    pub object: CalendarObject,
22    pub object_id: String,
23    pub principal: String,
24}
25
26impl ResourceName for CalendarObjectResource {
27    fn get_name(&self) -> Cow<'_, str> {
28        Cow::from(format!("{}.ics", self.object_id))
29    }
30}
31
32impl Resource for CalendarObjectResource {
33    type Prop = CalendarObjectPropWrapper;
34    type Error = Error;
35    type Principal = Principal;
36
37    fn is_collection(&self) -> bool {
38        false
39    }
40
41    fn get_resourcetype(&self) -> Resourcetype {
42        resourcetype!()
43    }
44
45    fn get_prop(
46        &self,
47        puri: &impl PrincipalUri,
48        user: &Principal,
49        prop: &CalendarObjectPropWrapperName,
50    ) -> Result<Self::Prop, Self::Error> {
51        Ok(match prop {
52            CalendarObjectPropWrapperName::CalendarObject(prop) => {
53                CalendarObjectPropWrapper::CalendarObject(match prop {
54                    CalendarObjectPropName::Getetag => {
55                        CalendarObjectProp::Getetag(self.object.get_etag())
56                    }
57                    // expanding on a PROPFIND is not defined by RFC 4791.
58                    // We still do it but return a NotFound error if there's no matching occurence
59                    // In report methods we can catch the error and ignore it
60                    CalendarObjectPropName::CalendarData(CalendarData { expand, .. }) => {
61                        CalendarObjectProp::CalendarData(expand.as_ref().map_or_else(
62                            || Ok::<_, Error>(self.object.get_ics().to_owned()),
63                            |expand| {
64                                Ok(self
65                                    .object
66                                    .get_inner()
67                                    .expand_recurrence(
68                                        Some(expand.start.to_utc()),
69                                        Some(expand.end.to_utc()),
70                                    )
71                                    .ok_or(Error::NotFound)?
72                                    .generate())
73                            },
74                        )?)
75                    }
76                    CalendarObjectPropName::Getcontenttype => {
77                        CalendarObjectProp::Getcontenttype("text/calendar;charset=utf-8")
78                    }
79                })
80            }
81            CalendarObjectPropWrapperName::Common(prop) => CalendarObjectPropWrapper::Common(
82                CommonPropertiesExtension::get_prop(self, puri, user, prop)?,
83            ),
84        })
85    }
86
87    fn get_displayname(&self) -> Option<&str> {
88        None
89    }
90
91    fn get_owner(&self) -> Option<&str> {
92        Some(&self.principal)
93    }
94
95    fn get_etag(&self) -> Option<String> {
96        Some(self.object.get_etag())
97    }
98
99    fn get_user_privileges(&self, user: &Principal) -> Result<UserPrivilegeSet, Self::Error> {
100        Ok(UserPrivilegeSet::owner_only(
101            user.is_principal(&self.principal),
102        ))
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use chrono::{TimeZone, Utc};
109    use rustical_dav::resource::Resource;
110    use rustical_ical::{CalendarObject, UtcDateTime};
111    use rustical_store::auth::Principal;
112
113    use crate::{
114        CalDavPrincipalUri, Error,
115        calendar_object::{
116            CalendarData, CalendarObjectPropName, CalendarObjectPropWrapperName, ExpandElement,
117            resource::CalendarObjectResource,
118        },
119    };
120
121    #[test]
122    fn test_expand_out_of_range() {
123        let resource = CalendarObjectResource {
124            principal: "user@rustical.dev".to_string(),
125            object_id: "asd".to_string(),
126            object: CalendarObject::from_ics(
127                r"BEGIN:VCALENDAR
128VERSION:2.0
129PRODID:-//Example Corp.//CalDAV Client//EN
130BEGIN:VEVENT
131UID:20010712T182145Z-123401@example.com
132DTSTAMP:20060712T182145Z
133DTSTART:20060714T170000Z
134RRULE:FREQ=YEARLY;COUNT=1
135DTEND:20060715T040000Z
136SUMMARY:Bastille Day Party
137END:VEVENT
138END:VCALENDAR"
139                    .to_string(),
140            )
141            .unwrap(),
142        };
143        assert!(matches!(
144            resource.get_prop(
145                &CalDavPrincipalUri("/"),
146                &Principal {
147                    id: "user@rustical.dev".to_string(),
148                    displayname: None,
149                    principal_type: rustical_store::auth::PrincipalType::Individual,
150                    password: None,
151                    memberships: vec![],
152                },
153                &CalendarObjectPropWrapperName::CalendarObject(
154                    CalendarObjectPropName::CalendarData(CalendarData {
155                        expand: Some(ExpandElement {
156                            start: UtcDateTime(Utc.with_ymd_and_hms(1900, 1, 1, 0, 0, 0).unwrap()),
157                            end: UtcDateTime(Utc.with_ymd_and_hms(1901, 1, 1, 0, 0, 0).unwrap()),
158                        }),
159                        ..Default::default()
160                    },)
161                ),
162            ),
163            Err(Error::NotFound)
164        ));
165    }
166}