#[cfg(feature = "caldav")]
use icalendar::Calendar;
use xmltree::Element;
use crate::davpath::DavPath;
pub use crate::dav_filters::{ParameterFilter, TextMatch};
pub const NS_CALDAV_URI: &str = "urn:ietf:params:xml:ns:caldav";
pub const NS_CALENDARSERVER_URI: &str = "http://calendarserver.org/ns/";
pub const CALDAV_PROPERTIES: &[&str] = &[
"C:calendar-description",
"C:calendar-timezone",
"C:supported-calendar-component-set",
"C:supported-calendar-data",
"C:max-resource-size",
"C:min-date-time",
"C:max-date-time",
"C:max-instances",
"C:max-attendees-per-instance",
"C:calendar-home-set",
"C:calendar-user-address-set",
"C:schedule-inbox-URL",
"C:schedule-outbox-URL",
];
pub const DEFAULT_CALDAV_NAME: &str = "calendars";
pub const DEFAULT_CALDAV_DIRECTORY: &str = "/calendars";
pub const DEFAULT_CALDAV_DIRECTORY_ENDSLASH: &str = "/calendars/";
#[derive(Debug, Clone, PartialEq)]
pub enum CalDavResourceType {
Calendar,
ScheduleInbox,
ScheduleOutbox,
CalendarObject,
Regular,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CalendarComponentType {
VEvent,
VTodo,
VJournal,
VFreeBusy,
VTimezone,
VAlarm,
}
impl CalendarComponentType {
pub fn as_str(&self) -> &'static str {
match self {
CalendarComponentType::VEvent => "VEVENT",
CalendarComponentType::VTodo => "VTODO",
CalendarComponentType::VJournal => "VJOURNAL",
CalendarComponentType::VFreeBusy => "VFREEBUSY",
CalendarComponentType::VTimezone => "VTIMEZONE",
CalendarComponentType::VAlarm => "VALARM",
}
}
}
#[derive(Debug, Clone)]
pub struct CalendarProperties {
pub description: Option<String>,
pub timezone: Option<String>,
pub supported_components: Vec<CalendarComponentType>,
pub max_resource_size: Option<u64>,
pub color: Option<String>,
pub display_name: Option<String>,
}
impl Default for CalendarProperties {
fn default() -> Self {
Self {
description: None,
timezone: None,
supported_components: vec![
CalendarComponentType::VEvent,
CalendarComponentType::VTodo,
CalendarComponentType::VJournal,
CalendarComponentType::VFreeBusy,
],
max_resource_size: Some(1024 * 1024), color: None,
display_name: None,
}
}
}
#[derive(Debug, Clone)]
pub struct CalendarQuery {
pub comp_filter: Option<ComponentFilter>,
pub time_range: Option<TimeRange>,
pub properties: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ComponentFilter {
pub name: String,
pub is_not_defined: bool,
pub time_range: Option<TimeRange>,
pub prop_filters: Vec<PropertyFilter>,
pub comp_filters: Vec<ComponentFilter>,
}
#[derive(Debug, Clone)]
pub struct PropertyFilter {
pub name: String,
pub is_not_defined: bool,
pub text_match: Option<TextMatch>,
pub time_range: Option<TimeRange>,
pub param_filters: Vec<ParameterFilter>,
}
#[derive(Debug, Clone)]
pub struct TimeRange {
pub start: Option<String>,
pub end: Option<String>,
}
#[derive(Debug, Clone)]
pub enum CalDavReportType {
CalendarQuery(CalendarQuery),
CalendarMultiget { hrefs: Vec<String> },
FreeBusyQuery { time_range: TimeRange },
}
pub fn create_supported_calendar_component_set(components: &[CalendarComponentType]) -> Element {
let mut elem = Element::new("C:supported-calendar-component-set");
elem.namespace = Some(NS_CALDAV_URI.to_string());
for comp in components {
let mut comp_elem = Element::new("C:comp");
comp_elem.namespace = Some(NS_CALDAV_URI.to_string());
comp_elem
.attributes
.insert("name".to_string(), comp.as_str().to_string());
elem.children.push(xmltree::XMLNode::Element(comp_elem));
}
elem
}
pub fn create_supported_calendar_data() -> Element {
let mut elem = Element::new("C:supported-calendar-data");
elem.namespace = Some(NS_CALDAV_URI.to_string());
let mut calendar_data = Element::new("C:calendar-data");
calendar_data.namespace = Some(NS_CALDAV_URI.to_string());
calendar_data
.attributes
.insert("content-type".to_string(), "text/calendar".to_string());
calendar_data
.attributes
.insert("version".to_string(), "2.0".to_string());
elem.children.push(xmltree::XMLNode::Element(calendar_data));
elem
}
pub fn create_calendar_home_set(prefix: &str, path: &str) -> Element {
let mut elem = Element::new("C:calendar-home-set");
elem.namespace = Some(NS_CALDAV_URI.to_string());
let mut href = Element::new("D:href");
href.namespace = Some("DAV:".to_string());
href.children
.push(xmltree::XMLNode::Text(format!("{prefix}{path}")));
elem.children.push(xmltree::XMLNode::Element(href));
elem
}
pub(crate) fn is_path_in_caldav_directory(dav_path: &DavPath) -> bool {
let path_string = dav_path.to_string();
path_string.len() > DEFAULT_CALDAV_DIRECTORY_ENDSLASH.len()
&& path_string.starts_with(DEFAULT_CALDAV_DIRECTORY_ENDSLASH)
}
pub fn is_calendar_collection(resource_type: &[Element]) -> bool {
resource_type
.iter()
.any(|elem| elem.name == "calendar" && elem.namespace.as_deref() == Some(NS_CALDAV_URI))
}
pub fn is_calendar_data(content: &[u8]) -> bool {
if !content.starts_with(b"BEGIN:VCALENDAR") {
return false;
}
let trimmed = content.trim_ascii_end();
trimmed.ends_with(b"END:VCALENDAR")
}
#[cfg(feature = "caldav")]
pub fn validate_calendar_data(content: &str) -> Result<Calendar, String> {
content
.parse::<Calendar>()
.map_err(|e| format!("Invalid iCalendar data: {}", e))
}
pub fn extract_calendar_uid(content: &str) -> Option<String> {
for line in content.lines() {
let line = line.trim();
if let Some(value) = line.strip_prefix("UID:") {
return Some(value.to_string());
}
if let Some(rest) = line.strip_prefix("UID;")
&& let Some(colon_pos) = rest.find(':')
{
return Some(rest[colon_pos + 1..].to_string());
}
}
None
}
pub fn calendar_resource_type() -> Vec<Element> {
let mut collection = Element::new("D:collection");
collection.namespace = Some("DAV:".to_string());
let mut calendar = Element::new("C:calendar");
calendar.namespace = Some(NS_CALDAV_URI.to_string());
vec![collection, calendar]
}
pub fn schedule_inbox_resource_type() -> Vec<Element> {
let mut collection = Element::new("D:collection");
collection.namespace = Some("DAV:".to_string());
let mut schedule_inbox = Element::new("C:schedule-inbox");
schedule_inbox.namespace = Some(NS_CALDAV_URI.to_string());
vec![collection, schedule_inbox]
}
pub fn schedule_outbox_resource_type() -> Vec<Element> {
let mut collection = Element::new("D:collection");
collection.namespace = Some("DAV:".to_string());
let mut schedule_outbox = Element::new("C:schedule-outbox");
schedule_outbox.namespace = Some(NS_CALDAV_URI.to_string());
vec![collection, schedule_outbox]
}