Files
rustical/crates/caldav/src/calendar/resource.rs
2024-10-31 18:09:57 +01:00

338 lines
12 KiB
Rust

use super::methods::mkcalendar::route_mkcalendar;
use super::methods::report::route_report_calendar;
use super::prop::{
Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData,
SupportedReportSet, UserPrivilegeSet,
};
use crate::calendar_object::resource::CalendarObjectResource;
use crate::principal::PrincipalResource;
use crate::Error;
use actix_web::dev::ResourceMap;
use actix_web::http::Method;
use actix_web::web;
use actix_web::{web::Data, HttpRequest};
use async_trait::async_trait;
use derive_more::derive::{From, Into};
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
use rustical_dav::xml::HrefElement;
use rustical_store::{Calendar, CalendarStore};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
use strum::{EnumString, VariantNames};
pub struct CalendarResourceService<C: CalendarStore + ?Sized> {
pub cal_store: Arc<C>,
pub path: String,
pub principal: String,
pub calendar_id: String,
}
#[derive(EnumString, Debug, VariantNames, Clone)]
#[strum(serialize_all = "kebab-case")]
pub enum CalendarPropName {
Resourcetype,
CurrentUserPrincipal,
Owner,
Displayname,
CalendarColor,
CalendarDescription,
CalendarTimezone,
CalendarOrder,
SupportedCalendarComponentSet,
SupportedCalendarData,
Getcontenttype,
CurrentUserPrivilegeSet,
MaxResourceSize,
SupportedReportSet,
SyncToken,
Getctag,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum CalendarProp {
// WebDAV (RFC 2518)
Resourcetype(Resourcetype),
Displayname(Option<String>),
Getcontenttype(String),
// WebDAV Current Principal Extension (RFC 5397)
CurrentUserPrincipal(HrefElement),
// WebDAV Access Control (RFC 3744)
Owner(HrefElement),
CurrentUserPrivilegeSet(UserPrivilegeSet),
// CalDAV (RFC 4791)
#[serde(rename = "IC:calendar-color", alias = "calendar-color")]
CalendarColor(Option<String>),
#[serde(rename = "C:calendar-description", alias = "calendar-description")]
CalendarDescription(Option<String>),
#[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")]
CalendarTimezone(Option<String>),
#[serde(rename = "IC:calendar-order", alias = "calendar-order")]
CalendarOrder(Option<i64>),
#[serde(
rename = "C:supported-calendar-component-set",
alias = "supported-calendar-component-set"
)]
SupportedCalendarComponentSet(SupportedCalendarComponentSet),
#[serde(
rename = "C:supported-calendar-data",
alias = "supported-calendar-data"
)]
SupportedCalendarData(SupportedCalendarData),
MaxResourceSize(i64),
SupportedReportSet(SupportedReportSet),
// Collection Synchronization (RFC 6578)
SyncToken(String),
// Didn't find the spec
Getctag(String),
#[serde(other)]
Invalid,
}
impl InvalidProperty for CalendarProp {
fn invalid_property(&self) -> bool {
matches!(self, Self::Invalid)
}
}
#[derive(Clone, Debug, From, Into)]
pub struct CalendarResource(Calendar);
impl Resource for CalendarResource {
type PropName = CalendarPropName;
type Prop = CalendarProp;
type Error = Error;
fn get_prop(
&self,
rmap: &ResourceMap,
prop: Self::PropName,
) -> Result<Self::Prop, Self::Error> {
Ok(match prop {
CalendarPropName::Resourcetype => CalendarProp::Resourcetype(Resourcetype::default()),
CalendarPropName::CurrentUserPrincipal => {
CalendarProp::CurrentUserPrincipal(HrefElement::new(
PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(),
))
}
CalendarPropName::Owner => CalendarProp::Owner(HrefElement::new(
PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(),
)),
CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()),
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()),
CalendarPropName::CalendarDescription => {
CalendarProp::CalendarDescription(self.0.description.clone())
}
CalendarPropName::CalendarTimezone => {
CalendarProp::CalendarTimezone(self.0.timezone.clone())
}
CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)),
CalendarPropName::SupportedCalendarComponentSet => {
CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet {
comp: vec![
SupportedCalendarComponent {
name: "VEVENT".to_owned(),
},
SupportedCalendarComponent {
name: "VTODO".to_owned(),
},
SupportedCalendarComponent {
name: "VJOURNAL".to_owned(),
},
],
})
}
CalendarPropName::SupportedCalendarData => {
CalendarProp::SupportedCalendarData(SupportedCalendarData::default())
}
CalendarPropName::Getcontenttype => {
CalendarProp::Getcontenttype("text/calendar;charset=utf-8".to_owned())
}
CalendarPropName::MaxResourceSize => CalendarProp::MaxResourceSize(10000000),
CalendarPropName::CurrentUserPrivilegeSet => {
CalendarProp::CurrentUserPrivilegeSet(UserPrivilegeSet::default())
}
CalendarPropName::SupportedReportSet => {
CalendarProp::SupportedReportSet(SupportedReportSet::default())
}
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()),
CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()),
})
}
fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> {
match prop {
CalendarProp::Resourcetype(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::CurrentUserPrincipal(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Owner(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Displayname(displayname) => {
self.0.displayname = displayname;
Ok(())
}
CalendarProp::CalendarColor(color) => {
self.0.color = color;
Ok(())
}
CalendarProp::CalendarDescription(description) => {
self.0.description = description;
Ok(())
}
CalendarProp::CalendarTimezone(timezone) => {
self.0.timezone = timezone;
Ok(())
}
CalendarProp::CalendarOrder(order) => {
self.0.order = order.unwrap_or_default();
Ok(())
}
CalendarProp::SupportedCalendarComponentSet(_) => {
Err(rustical_dav::Error::PropReadOnly)
}
CalendarProp::SupportedCalendarData(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::CurrentUserPrivilegeSet(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly),
CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly),
}
}
fn remove_prop(&mut self, prop: Self::PropName) -> Result<(), rustical_dav::Error> {
match prop {
CalendarPropName::Resourcetype => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::CurrentUserPrincipal => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::Owner => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::Displayname => {
self.0.displayname = None;
Ok(())
}
CalendarPropName::CalendarColor => {
self.0.color = None;
Ok(())
}
CalendarPropName::CalendarDescription => {
self.0.description = None;
Ok(())
}
CalendarPropName::CalendarTimezone => {
self.0.timezone = None;
Ok(())
}
CalendarPropName::CalendarOrder => {
self.0.order = 0;
Ok(())
}
CalendarPropName::SupportedCalendarComponentSet => {
Err(rustical_dav::Error::PropReadOnly)
}
CalendarPropName::SupportedCalendarData => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly),
CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly),
}
}
#[inline]
fn resource_name() -> &'static str {
"caldav_calendar"
}
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> ResourceService for CalendarResourceService<C> {
type MemberType = CalendarObjectResource;
type PathComponents = (String, String); // principal, calendar_id
type Resource = CalendarResource;
type Error = Error;
async fn get_resource(&self, principal: String) -> Result<Self::Resource, Error> {
if self.principal != principal {
return Err(Error::Unauthorized);
}
let calendar = self
.cal_store
.get_calendar(&self.principal, &self.calendar_id)
.await
.map_err(|_e| Error::NotFound)?;
Ok(calendar.into())
}
async fn get_members(
&self,
rmap: &ResourceMap,
) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
Ok(self
.cal_store
.get_objects(&self.principal, &self.calendar_id)
.await?
.into_iter()
.map(|object| {
(
CalendarObjectResource::get_url(
rmap,
vec![&self.principal, &self.calendar_id, object.get_id()],
)
.unwrap(),
object.into(),
)
})
.collect())
}
async fn new(
req: &HttpRequest,
path_components: Self::PathComponents,
) -> Result<Self, Self::Error> {
let cal_store = req
.app_data::<Data<C>>()
.expect("no calendar store in app_data!")
.clone()
.into_inner();
Ok(Self {
path: req.path().to_owned(),
principal: path_components.0,
calendar_id: path_components.1,
cal_store,
})
}
async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error> {
self.cal_store
.update_calendar(
self.principal.to_owned(),
self.calendar_id.to_owned(),
file.into(),
)
.await?;
Ok(())
}
async fn delete_resource(&self, use_trashbin: bool) -> Result<(), Self::Error> {
self.cal_store
.delete_calendar(&self.principal, &self.calendar_id, use_trashbin)
.await?;
Ok(())
}
#[inline]
fn actix_additional_routes(res: actix_web::Resource) -> actix_web::Resource {
let report_method = web::method(Method::from_str("REPORT").unwrap());
let mkcalendar_method = web::method(Method::from_str("MKCALENDAR").unwrap());
res.route(report_method.to(route_report_calendar::<C>))
.route(mkcalendar_method.to(route_mkcalendar::<C>))
}
}