1#[cfg(feature = "caldav")]
8use icalendar::Calendar;
9use xmltree::Element;
10
11pub const NS_CALDAV_URI: &str = "urn:ietf:params:xml:ns:caldav";
13pub const NS_CALENDARSERVER_URI: &str = "http://calendarserver.org/ns/";
14
15pub const CALDAV_PROPERTIES: &[&str] = &[
17 "C:calendar-description",
18 "C:calendar-timezone",
19 "C:supported-calendar-component-set",
20 "C:supported-calendar-data",
21 "C:max-resource-size",
22 "C:min-date-time",
23 "C:max-date-time",
24 "C:max-instances",
25 "C:max-attendees-per-instance",
26 "C:calendar-home-set",
27 "C:calendar-user-address-set",
28 "C:schedule-inbox-URL",
29 "C:schedule-outbox-URL",
30];
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum CalDavResourceType {
35 Calendar,
36 ScheduleInbox,
37 ScheduleOutbox,
38 CalendarObject,
39 Regular,
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub enum CalendarComponentType {
45 VEvent,
46 VTodo,
47 VJournal,
48 VFreeBusy,
49 VTimezone,
50 VAlarm,
51}
52
53impl CalendarComponentType {
54 pub fn as_str(&self) -> &'static str {
55 match self {
56 CalendarComponentType::VEvent => "VEVENT",
57 CalendarComponentType::VTodo => "VTODO",
58 CalendarComponentType::VJournal => "VJOURNAL",
59 CalendarComponentType::VFreeBusy => "VFREEBUSY",
60 CalendarComponentType::VTimezone => "VTIMEZONE",
61 CalendarComponentType::VAlarm => "VALARM",
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct CalendarProperties {
69 pub description: Option<String>,
70 pub timezone: Option<String>,
71 pub supported_components: Vec<CalendarComponentType>,
72 pub max_resource_size: Option<u64>,
73 pub color: Option<String>,
74 pub display_name: Option<String>,
75}
76
77impl Default for CalendarProperties {
78 fn default() -> Self {
79 Self {
80 description: None,
81 timezone: None,
82 supported_components: vec![
83 CalendarComponentType::VEvent,
84 CalendarComponentType::VTodo,
85 CalendarComponentType::VJournal,
86 CalendarComponentType::VFreeBusy,
87 ],
88 max_resource_size: Some(1024 * 1024), color: None,
90 display_name: None,
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
97pub struct CalendarQuery {
98 pub comp_filter: Option<ComponentFilter>,
99 pub time_range: Option<TimeRange>,
100 pub properties: Vec<String>,
101}
102
103#[derive(Debug, Clone)]
104pub struct ComponentFilter {
105 pub name: String,
106 pub is_not_defined: bool,
107 pub time_range: Option<TimeRange>,
108 pub prop_filters: Vec<PropertyFilter>,
109 pub comp_filters: Vec<ComponentFilter>,
110}
111
112#[derive(Debug, Clone)]
113pub struct PropertyFilter {
114 pub name: String,
115 pub is_not_defined: bool,
116 pub text_match: Option<TextMatch>,
117 pub time_range: Option<TimeRange>,
118 pub param_filters: Vec<ParameterFilter>,
119}
120
121#[derive(Debug, Clone)]
122pub struct ParameterFilter {
123 pub name: String,
124 pub is_not_defined: bool,
125 pub text_match: Option<TextMatch>,
126}
127
128#[derive(Debug, Clone)]
129pub struct TextMatch {
130 pub text: String,
131 pub collation: Option<String>,
132 pub negate_condition: bool,
133}
134
135#[derive(Debug, Clone)]
136pub struct TimeRange {
137 pub start: Option<String>, pub end: Option<String>, }
140
141#[derive(Debug, Clone)]
143pub enum CalDavReportType {
144 CalendarQuery(CalendarQuery),
145 CalendarMultiget { hrefs: Vec<String> },
146 FreeBusyQuery { time_range: TimeRange },
147}
148
149pub fn create_supported_calendar_component_set(components: &[CalendarComponentType]) -> Element {
151 let mut elem = Element::new("supported-calendar-component-set");
152 elem.namespace = Some(NS_CALDAV_URI.to_string());
153
154 for comp in components {
155 let mut comp_elem = Element::new("comp");
156 comp_elem.namespace = Some(NS_CALDAV_URI.to_string());
157 comp_elem
158 .attributes
159 .insert("name".to_string(), comp.as_str().to_string());
160 elem.children.push(xmltree::XMLNode::Element(comp_elem));
161 }
162
163 elem
164}
165
166pub fn create_supported_calendar_data() -> Element {
167 let mut elem = Element::new("supported-calendar-data");
168 elem.namespace = Some(NS_CALDAV_URI.to_string());
169
170 let mut calendar_data = Element::new("calendar-data");
171 calendar_data.namespace = Some(NS_CALDAV_URI.to_string());
172 calendar_data
173 .attributes
174 .insert("content-type".to_string(), "text/calendar".to_string());
175 calendar_data
176 .attributes
177 .insert("version".to_string(), "2.0".to_string());
178
179 elem.children.push(xmltree::XMLNode::Element(calendar_data));
180 elem
181}
182
183pub fn create_calendar_home_set(path: &str) -> Element {
184 let mut elem = Element::new("calendar-home-set");
185 elem.namespace = Some(NS_CALDAV_URI.to_string());
186
187 let mut href = Element::new("href");
188 href.namespace = Some("DAV:".to_string());
189 href.children.push(xmltree::XMLNode::Text(path.to_string()));
190
191 elem.children.push(xmltree::XMLNode::Element(href));
192 elem
193}
194
195pub fn is_calendar_collection(resource_type: &[Element]) -> bool {
197 resource_type
198 .iter()
199 .any(|elem| elem.name == "calendar" && elem.namespace.as_deref() == Some(NS_CALDAV_URI))
200}
201
202pub fn is_calendar_data(content: &[u8]) -> bool {
204 content.starts_with(b"BEGIN:VCALENDAR")
205 && (content.ends_with(b"END:VCALENDAR") || content.ends_with(b"END:VCALENDAR\n"))
206}
207
208#[cfg(feature = "caldav")]
209pub fn validate_calendar_data(content: &str) -> Result<Calendar, String> {
211 content
212 .parse::<Calendar>()
213 .map_err(|e| format!("Invalid iCalendar data: {}", e))
214}
215
216#[cfg(not(feature = "caldav"))]
217pub fn validate_calendar_data(_content: &str) -> Result<(), String> {
219 Err("CalDAV feature not enabled".to_string())
220}
221
222pub fn extract_calendar_uid(content: &str) -> Option<String> {
224 for line in content.lines() {
225 let line = line.trim();
226 if let Some(uid) = line.strip_prefix("UID:") {
227 return Some(uid.to_string());
228 }
229 }
230 None
231}
232
233pub fn calendar_resource_type() -> Vec<Element> {
235 let mut collection = Element::new("collection");
236 collection.namespace = Some("DAV:".to_string());
237
238 let mut calendar = Element::new("calendar");
239 calendar.namespace = Some(NS_CALDAV_URI.to_string());
240
241 vec![collection, calendar]
242}
243
244pub fn schedule_inbox_resource_type() -> Vec<Element> {
246 let mut collection = Element::new("collection");
247 collection.namespace = Some("DAV:".to_string());
248
249 let mut schedule_inbox = Element::new("schedule-inbox");
250 schedule_inbox.namespace = Some(NS_CALDAV_URI.to_string());
251
252 vec![collection, schedule_inbox]
253}
254
255pub fn schedule_outbox_resource_type() -> Vec<Element> {
257 let mut collection = Element::new("collection");
258 collection.namespace = Some("DAV:".to_string());
259
260 let mut schedule_outbox = Element::new("schedule-outbox");
261 schedule_outbox.namespace = Some(NS_CALDAV_URI.to_string());
262
263 vec![collection, schedule_outbox]
264}