1#[cfg(feature = "caldav")]
8use icalendar::Calendar;
9use xmltree::Element;
10
11use crate::davpath::DavPath;
12
13pub use crate::dav_filters::{ParameterFilter, TextMatch};
15
16pub const NS_CALDAV_URI: &str = "urn:ietf:params:xml:ns:caldav";
18pub const NS_CALENDARSERVER_URI: &str = "http://calendarserver.org/ns/";
19
20pub const CALDAV_PROPERTIES: &[&str] = &[
22 "C:calendar-description",
23 "C:calendar-timezone",
24 "C:supported-calendar-component-set",
25 "C:supported-calendar-data",
26 "C:max-resource-size",
27 "C:min-date-time",
28 "C:max-date-time",
29 "C:max-instances",
30 "C:max-attendees-per-instance",
31 "C:calendar-home-set",
32 "C:calendar-user-address-set",
33 "C:schedule-inbox-URL",
34 "C:schedule-outbox-URL",
35];
36
37pub const DEFAULT_CALDAV_NAME: &str = "calendars";
39pub const DEFAULT_CALDAV_DIRECTORY: &str = "/calendars";
40pub const DEFAULT_CALDAV_DIRECTORY_ENDSLASH: &str = "/calendars/";
41
42#[derive(Debug, Clone, PartialEq)]
44pub enum CalDavResourceType {
45 Calendar,
46 ScheduleInbox,
47 ScheduleOutbox,
48 CalendarObject,
49 Regular,
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub enum CalendarComponentType {
55 VEvent,
56 VTodo,
57 VJournal,
58 VFreeBusy,
59 VTimezone,
60 VAlarm,
61}
62
63impl CalendarComponentType {
64 pub fn as_str(&self) -> &'static str {
65 match self {
66 CalendarComponentType::VEvent => "VEVENT",
67 CalendarComponentType::VTodo => "VTODO",
68 CalendarComponentType::VJournal => "VJOURNAL",
69 CalendarComponentType::VFreeBusy => "VFREEBUSY",
70 CalendarComponentType::VTimezone => "VTIMEZONE",
71 CalendarComponentType::VAlarm => "VALARM",
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct CalendarProperties {
79 pub description: Option<String>,
80 pub timezone: Option<String>,
81 pub supported_components: Vec<CalendarComponentType>,
82 pub max_resource_size: Option<u64>,
83 pub color: Option<String>,
84 pub display_name: Option<String>,
85}
86
87impl Default for CalendarProperties {
88 fn default() -> Self {
89 Self {
90 description: None,
91 timezone: None,
92 supported_components: vec![
93 CalendarComponentType::VEvent,
94 CalendarComponentType::VTodo,
95 CalendarComponentType::VJournal,
96 CalendarComponentType::VFreeBusy,
97 ],
98 max_resource_size: Some(1024 * 1024), color: None,
100 display_name: None,
101 }
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct CalendarQuery {
108 pub comp_filter: Option<ComponentFilter>,
109 pub time_range: Option<TimeRange>,
110 pub properties: Vec<String>,
111}
112
113#[derive(Debug, Clone)]
114pub struct ComponentFilter {
115 pub name: String,
116 pub is_not_defined: bool,
117 pub time_range: Option<TimeRange>,
118 pub prop_filters: Vec<PropertyFilter>,
119 pub comp_filters: Vec<ComponentFilter>,
120}
121
122#[derive(Debug, Clone)]
127pub struct PropertyFilter {
128 pub name: String,
129 pub is_not_defined: bool,
130 pub text_match: Option<TextMatch>,
131 pub time_range: Option<TimeRange>,
132 pub param_filters: Vec<ParameterFilter>,
133}
134
135#[derive(Debug, Clone)]
136pub struct TimeRange {
137 pub start: Option<String>,
139 pub end: Option<String>,
141}
142
143#[derive(Debug, Clone)]
145pub enum CalDavReportType {
146 CalendarQuery(CalendarQuery),
147 CalendarMultiget { hrefs: Vec<String> },
148 FreeBusyQuery { time_range: TimeRange },
149}
150
151pub fn create_supported_calendar_component_set(components: &[CalendarComponentType]) -> Element {
153 let mut elem = Element::new("C:supported-calendar-component-set");
154 elem.namespace = Some(NS_CALDAV_URI.to_string());
155
156 for comp in components {
157 let mut comp_elem = Element::new("C:comp");
158 comp_elem.namespace = Some(NS_CALDAV_URI.to_string());
159 comp_elem
160 .attributes
161 .insert("name".to_string(), comp.as_str().to_string());
162 elem.children.push(xmltree::XMLNode::Element(comp_elem));
163 }
164
165 elem
166}
167
168pub fn create_supported_calendar_data() -> Element {
169 let mut elem = Element::new("C:supported-calendar-data");
170 elem.namespace = Some(NS_CALDAV_URI.to_string());
171
172 let mut calendar_data = Element::new("C:calendar-data");
173 calendar_data.namespace = Some(NS_CALDAV_URI.to_string());
174 calendar_data
175 .attributes
176 .insert("content-type".to_string(), "text/calendar".to_string());
177 calendar_data
178 .attributes
179 .insert("version".to_string(), "2.0".to_string());
180
181 elem.children.push(xmltree::XMLNode::Element(calendar_data));
182 elem
183}
184
185pub fn create_calendar_home_set(prefix: &str, path: &str) -> Element {
186 let mut elem = Element::new("C:calendar-home-set");
187 elem.namespace = Some(NS_CALDAV_URI.to_string());
188
189 let mut href = Element::new("D:href");
190 href.namespace = Some("DAV:".to_string());
191 href.children
192 .push(xmltree::XMLNode::Text(format!("{prefix}{path}")));
193
194 elem.children.push(xmltree::XMLNode::Element(href));
195 elem
196}
197
198pub(crate) fn is_path_in_caldav_directory(dav_path: &DavPath) -> bool {
200 let path_string = dav_path.to_string();
201 path_string.len() > DEFAULT_CALDAV_DIRECTORY_ENDSLASH.len()
202 && path_string.starts_with(DEFAULT_CALDAV_DIRECTORY_ENDSLASH)
203}
204
205pub fn is_calendar_collection(resource_type: &[Element]) -> bool {
207 resource_type
208 .iter()
209 .any(|elem| elem.name == "calendar" && elem.namespace.as_deref() == Some(NS_CALDAV_URI))
210}
211
212pub fn is_calendar_data(content: &[u8]) -> bool {
214 if !content.starts_with(b"BEGIN:VCALENDAR") {
215 return false;
216 }
217
218 let trimmed = content.trim_ascii_end();
219 trimmed.ends_with(b"END:VCALENDAR")
220}
221
222#[cfg(feature = "caldav")]
240pub fn validate_calendar_data(content: &str) -> Result<Calendar, String> {
241 content
242 .parse::<Calendar>()
243 .map_err(|e| format!("Invalid iCalendar data: {}", e))
244}
245
246pub fn extract_calendar_uid(content: &str) -> Option<String> {
250 for line in content.lines() {
251 let line = line.trim();
252 if let Some(value) = line.strip_prefix("UID:") {
254 return Some(value.to_string());
255 }
256 if let Some(rest) = line.strip_prefix("UID;")
258 && let Some(colon_pos) = rest.find(':')
259 {
260 return Some(rest[colon_pos + 1..].to_string());
261 }
262 }
263 None
264}
265
266pub fn calendar_resource_type() -> Vec<Element> {
268 let mut collection = Element::new("D:collection");
269 collection.namespace = Some("DAV:".to_string());
270
271 let mut calendar = Element::new("C:calendar");
272 calendar.namespace = Some(NS_CALDAV_URI.to_string());
273
274 vec![collection, calendar]
275}
276
277pub fn schedule_inbox_resource_type() -> Vec<Element> {
279 let mut collection = Element::new("D:collection");
280 collection.namespace = Some("DAV:".to_string());
281
282 let mut schedule_inbox = Element::new("C:schedule-inbox");
283 schedule_inbox.namespace = Some(NS_CALDAV_URI.to_string());
284
285 vec![collection, schedule_inbox]
286}
287
288pub fn schedule_outbox_resource_type() -> Vec<Element> {
290 let mut collection = Element::new("D:collection");
291 collection.namespace = Some("DAV:".to_string());
292
293 let mut schedule_outbox = Element::new("C:schedule-outbox");
294 schedule_outbox.namespace = Some(NS_CALDAV_URI.to_string());
295
296 vec![collection, schedule_outbox]
297}