From 04ad124799d88150a8adaa1fdb0b6a8eb4818dc2 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:55:15 +0200 Subject: [PATCH] Some refactoring --- crates/caldav/src/calendar/methods/report.rs | 10 +- crates/caldav/src/calendar/mod.rs | 1 + crates/caldav/src/calendar/prop.rs | 97 ++++++++++++++ crates/caldav/src/calendar/resource.rs | 125 +++---------------- crates/caldav/src/event/resource.rs | 21 +--- crates/caldav/src/lib.rs | 75 +++++++---- crates/caldav/src/principal/mod.rs | 30 +++-- crates/caldav/src/root/mod.rs | 24 +--- crates/dav/src/propfind.rs | 13 +- crates/dav/src/proppatch.rs | 2 +- crates/dav/src/resource.rs | 26 ++-- 11 files changed, 215 insertions(+), 209 deletions(-) create mode 100644 crates/caldav/src/calendar/prop.rs diff --git a/crates/caldav/src/calendar/methods/report.rs b/crates/caldav/src/calendar/methods/report.rs index 9681d6c..1346c3c 100644 --- a/crates/caldav/src/calendar/methods/report.rs +++ b/crates/caldav/src/calendar/methods/report.rs @@ -180,13 +180,11 @@ pub async fn route_report_calendar, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct CalendarData { + #[serde(rename = "@content-type")] + content_type: String, + #[serde(rename = "@version")] + version: String, +} + +impl Default for CalendarData { + fn default() -> Self { + Self { + content_type: "text/calendar".to_owned(), + version: "2.0".to_owned(), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(rename_all = "kebab-case")] +pub struct SupportedCalendarData { + #[serde(rename = "C:calendar-data", alias = "calendar-data")] + calendar_data: CalendarData, +} + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(rename_all = "kebab-case")] +pub struct Resourcetype { + #[serde(rename = "C:calendar", alias = "calendar")] + calendar: (), + collection: (), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum UserPrivilege { + Read, + ReadAcl, + Write, + WriteAcl, + WriteContent, + ReadCurrentUserPrivilegeSet, + Bind, + Unbind, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct UserPrivilegeWrapper { + #[serde(rename = "$value")] + privilege: UserPrivilege, +} + +impl From for UserPrivilegeWrapper { + fn from(value: UserPrivilege) -> Self { + Self { privilege: value } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct UserPrivilegeSet { + privilege: Vec, +} + +impl Default for UserPrivilegeSet { + fn default() -> Self { + Self { + privilege: vec![ + UserPrivilege::Read.into(), + UserPrivilege::ReadAcl.into(), + UserPrivilege::Write.into(), + UserPrivilege::WriteAcl.into(), + UserPrivilege::WriteContent.into(), + UserPrivilege::ReadCurrentUserPrivilegeSet.into(), + UserPrivilege::Bind.into(), + UserPrivilege::Unbind.into(), + ], + } + } +} diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 626557c..ba218ae 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -13,6 +13,11 @@ use std::sync::Arc; use strum::{EnumString, VariantNames}; use tokio::sync::RwLock; +use super::prop::{ + Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData, + UserPrivilegeSet, +}; + pub struct CalendarResource { pub cal_store: Arc>, pub path: String, @@ -20,102 +25,6 @@ pub struct CalendarResource { pub calendar_id: String, } -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct SupportedCalendarComponent { - #[serde(rename = "@name")] - pub name: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct SupportedCalendarComponentSet { - #[serde(rename = "C:comp")] - pub comp: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct CalendarData { - #[serde(rename = "@content-type")] - content_type: String, - #[serde(rename = "@version")] - version: String, -} - -impl Default for CalendarData { - fn default() -> Self { - Self { - content_type: "text/calendar".to_owned(), - version: "2.0".to_owned(), - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, Default)] -#[serde(rename_all = "kebab-case")] -pub struct SupportedCalendarData { - #[serde(rename = "C:calendar-data", alias = "calendar-data")] - calendar_data: CalendarData, -} - -#[derive(Debug, Clone, Deserialize, Serialize, Default)] -#[serde(rename_all = "kebab-case")] -pub struct Resourcetype { - #[serde(rename = "C:calendar", alias = "calendar")] - calendar: (), - collection: (), -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum UserPrivilege { - Read, - ReadAcl, - Write, - WriteAcl, - WriteContent, - ReadCurrentUserPrivilegeSet, - Bind, - Unbind, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct UserPrivilegeWrapper { - #[serde(rename = "$value")] - privilege: UserPrivilege, -} - -impl From for UserPrivilegeWrapper { - fn from(value: UserPrivilege) -> Self { - Self { privilege: value } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct UserPrivilegeSet { - privilege: Vec, -} - -impl Default for UserPrivilegeSet { - fn default() -> Self { - Self { - privilege: vec![ - UserPrivilege::Read.into(), - UserPrivilege::ReadAcl.into(), - UserPrivilege::Write.into(), - UserPrivilege::WriteAcl.into(), - UserPrivilege::WriteContent.into(), - UserPrivilege::ReadCurrentUserPrivilegeSet.into(), - UserPrivilege::Bind.into(), - UserPrivilege::Unbind.into(), - ], - } - } -} - #[derive(EnumString, Debug, VariantNames, Clone)] #[strum(serialize_all = "kebab-case")] pub enum CalendarPropName { @@ -176,7 +85,6 @@ impl InvalidProperty for CalendarProp { pub struct CalendarFile { pub calendar: Calendar, pub principal: String, - pub path: String, } impl Resource for CalendarFile { @@ -190,7 +98,7 @@ impl Resource for CalendarFile { Ok(CalendarProp::Resourcetype(Resourcetype::default())) } CalendarPropName::CurrentUserPrincipal => Ok(CalendarProp::CurrentUserPrincipal( - HrefElement::new(format!("{}/{}/", prefix, self.principal)), + HrefElement::new(format!("{}/user/{}/", prefix, self.principal)), )), CalendarPropName::Owner => Ok(CalendarProp::Owner(HrefElement::new(format!( "{}/{}/", @@ -306,10 +214,6 @@ impl Resource for CalendarFile { CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly), } } - - fn get_path(&self) -> &str { - &self.path - } } #[async_trait(?Send)] @@ -330,14 +234,13 @@ impl ResourceService for CalendarResource { Ok(CalendarFile { calendar, principal: self.principal.to_owned(), - path: self.path.to_owned(), }) } async fn get_members( &self, _auth_info: AuthInfo, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { // As of now the calendar resource has no members since events are shown with REPORT Ok(self .cal_store @@ -346,16 +249,18 @@ impl ResourceService for CalendarResource { .get_events(&self.principal, &self.calendar_id) .await? .into_iter() - .map(|event| EventFile { - path: format!("{}/{}", self.path, &event.get_uid()), - event, + .map(|event| { + ( + format!("{}/{}", self.path, &event.get_uid()), + EventFile { event }, + ) }) .collect()) } async fn new( - req: HttpRequest, - auth_info: AuthInfo, + req: &HttpRequest, + auth_info: &AuthInfo, path_components: Self::PathComponents, ) -> Result { let cal_store = req @@ -366,7 +271,7 @@ impl ResourceService for CalendarResource { Ok(Self { path: req.path().to_owned(), - principal: auth_info.user_id, + principal: auth_info.user_id.to_owned(), calendar_id: path_components.1, cal_store, }) diff --git a/crates/caldav/src/event/resource.rs b/crates/caldav/src/event/resource.rs index bc240e7..2fab1a9 100644 --- a/crates/caldav/src/event/resource.rs +++ b/crates/caldav/src/event/resource.rs @@ -47,7 +47,6 @@ impl InvalidProperty for EventProp { #[derive(Clone)] pub struct EventFile { pub event: Event, - pub path: String, } impl Resource for EventFile { @@ -55,10 +54,6 @@ impl Resource for EventFile { type Prop = EventProp; type Error = Error; - fn get_path(&self) -> &str { - &self.path - } - fn get_prop(&self, _prefix: &str, prop: Self::PropName) -> Result { match prop { EventPropName::Getetag => Ok(EventProp::Getetag(self.event.get_etag())), @@ -79,16 +74,9 @@ impl ResourceService for EventResource { type MemberType = EventFile; type Error = Error; - async fn get_members( - &self, - _auth_info: AuthInfo, - ) -> Result, Self::Error> { - Ok(vec![]) - } - async fn new( - req: HttpRequest, - _auth_info: AuthInfo, + req: &HttpRequest, + _auth_info: &AuthInfo, path_components: Self::PathComponents, ) -> Result { let (principal, cid, uid) = path_components; @@ -115,10 +103,7 @@ impl ResourceService for EventResource { .await .get_event(&self.principal, &self.cid, &self.uid) .await?; - Ok(EventFile { - event, - path: self.path.to_owned(), - }) + Ok(EventFile { event }) } async fn save_file(&self, _file: Self::File) -> Result<(), Self::Error> { diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index ea0e3f5..35e1a08 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -58,30 +58,57 @@ pub fn configure_dav( .route(proppatch_method().to(route_proppatch::)), ) .service( - web::resource("/{principal}") - .route(propfind_method().to(route_propfind::>)) - .route(proppatch_method().to(route_proppatch::>)), - ) - .service( - web::resource("/{principal}/{calendar}") - .route(report_method().to(calendar::methods::report::route_report_calendar::)) - .route(propfind_method().to(route_propfind::>)) - .route(proppatch_method().to(route_proppatch::>)) - .route( - mkcalendar_method().to(calendar::methods::mkcalendar::route_mkcol_calendar::), - ) - .route( - web::method(Method::DELETE) - .to(calendar::methods::delete::route_delete_calendar::), - ), - ) - .service( - web::resource("/{principal}/{calendar}/{event}") - .route(propfind_method().to(route_propfind::>)) - .route(proppatch_method().to(route_proppatch::>)) - .route(web::method(Method::DELETE).to(event::methods::delete_event::)) - .route(web::method(Method::GET).to(event::methods::get_event::)) - .route(web::method(Method::PUT).to(event::methods::put_event::)), + web::scope("/user").service( + web::scope("/{principal}") + .service( + web::resource("") + .route(propfind_method().to(route_propfind::>)) + .route(proppatch_method().to(route_proppatch::>)), + ) + .service( + web::scope("/{calendar}") + .service( + web::resource("") + .route( + report_method().to( + calendar::methods::report::route_report_calendar::, + ), + ) + .route( + propfind_method().to(route_propfind::>), + ) + .route( + proppatch_method() + .to(route_proppatch::>), + ) + .route(mkcalendar_method().to( + calendar::methods::mkcalendar::route_mkcol_calendar::, + )) + .route( + web::method(Method::DELETE).to( + calendar::methods::delete::route_delete_calendar::, + ), + ), + ) + .service( + web::resource("/{event}") + .route(propfind_method().to(route_propfind::>)) + .route( + proppatch_method().to(route_proppatch::>), + ) + .route( + web::method(Method::DELETE) + .to(event::methods::delete_event::), + ) + .route( + web::method(Method::GET).to(event::methods::get_event::), + ) + .route( + web::method(Method::PUT).to(event::methods::put_event::), + ), + ), + ), + ), ); } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index f16f524..c05f3c5 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -23,7 +23,6 @@ pub struct PrincipalResource { #[derive(Clone)] pub struct PrincipalFile { principal: String, - path: String, } #[derive(Deserialize, Serialize, Default, Debug)] @@ -80,20 +79,16 @@ impl Resource for PrincipalFile { HrefElement::new(format!("{}/{}/", prefix, self.principal)), )), PrincipalPropName::PrincipalUrl => Ok(PrincipalProp::PrincipalUrl(HrefElement::new( - format!("{}/{}/", prefix, self.principal), + format!("{}/user/{}/", prefix, self.principal), ))), PrincipalPropName::CalendarHomeSet => Ok(PrincipalProp::CalendarHomeSet( - HrefElement::new(format!("{}/{}/", prefix, self.principal)), + HrefElement::new(format!("{}/user/{}/", prefix, self.principal)), )), PrincipalPropName::CalendarUserAddressSet => Ok(PrincipalProp::CalendarUserAddressSet( - HrefElement::new(format!("{}/{}/", prefix, self.principal)), + HrefElement::new(format!("{}/user/{}/", prefix, self.principal)), )), } } - - fn get_path(&self) -> &str { - &self.path - } } #[async_trait(?Send)] @@ -104,8 +99,8 @@ impl ResourceService for PrincipalResource { type Error = Error; async fn new( - req: HttpRequest, - auth_info: AuthInfo, + req: &HttpRequest, + auth_info: &AuthInfo, (principal,): Self::PathComponents, ) -> Result { if auth_info.user_id != principal { @@ -127,14 +122,13 @@ impl ResourceService for PrincipalResource { async fn get_file(&self) -> Result { Ok(PrincipalFile { principal: self.principal.to_owned(), - path: self.path.to_owned(), }) } async fn get_members( &self, _auth_info: AuthInfo, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let calendars = self .cal_store .read() @@ -143,10 +137,14 @@ impl ResourceService for PrincipalResource { .await?; Ok(calendars .into_iter() - .map(|cal| CalendarFile { - path: format!("{}/{}", &self.path, &cal.id), - calendar: cal, - principal: self.principal.to_owned(), + .map(|cal| { + ( + format!("{}/{}", &self.path, &cal.id), + CalendarFile { + calendar: cal, + principal: self.principal.to_owned(), + }, + ) }) .collect()) } diff --git a/crates/caldav/src/root/mod.rs b/crates/caldav/src/root/mod.rs index a795446..e7e6508 100644 --- a/crates/caldav/src/root/mod.rs +++ b/crates/caldav/src/root/mod.rs @@ -9,13 +9,13 @@ use strum::{EnumString, VariantNames}; pub struct RootResource { principal: String, - path: String, } #[derive(EnumString, Debug, VariantNames, Clone)] #[strum(serialize_all = "kebab-case")] pub enum RootPropName { Resourcetype, + // Defined by RFC 5397 CurrentUserPrincipal, } @@ -43,7 +43,6 @@ impl InvalidProperty for RootProp { #[derive(Clone)] pub struct RootFile { pub principal: String, - pub path: String, } impl Resource for RootFile { @@ -55,14 +54,10 @@ impl Resource for RootFile { match prop { RootPropName::Resourcetype => Ok(RootProp::Resourcetype(Resourcetype::default())), RootPropName::CurrentUserPrincipal => Ok(RootProp::CurrentUserPrincipal( - HrefElement::new(format!("{}/{}/", prefix, self.principal)), + HrefElement::new(format!("{}/user/{}/", prefix, self.principal)), )), } } - - fn get_path(&self) -> &str { - &self.path - } } #[async_trait(?Send)] @@ -72,27 +67,18 @@ impl ResourceService for RootResource { type File = RootFile; type Error = Error; - async fn get_members( - &self, - _auth_info: AuthInfo, - ) -> Result, Self::Error> { - Ok(vec![]) - } - async fn new( - req: HttpRequest, - auth_info: AuthInfo, + _req: &HttpRequest, + auth_info: &AuthInfo, _path_components: Self::PathComponents, ) -> Result { Ok(Self { - principal: auth_info.user_id, - path: req.path().to_string(), + principal: auth_info.user_id.to_owned(), }) } async fn get_file(&self) -> Result { Ok(RootFile { - path: self.path.to_owned(), principal: self.principal.to_owned(), }) } diff --git a/crates/dav/src/propfind.rs b/crates/dav/src/propfind.rs index 6fbece6..496ecea 100644 --- a/crates/dav/src/propfind.rs +++ b/crates/dav/src/propfind.rs @@ -38,7 +38,7 @@ struct PropfindElement { } pub async fn route_propfind( - path: Path, + path_components: Path, body: String, req: HttpRequest, prefix: Data, @@ -48,9 +48,10 @@ pub async fn route_propfind debug!("{body}"); let auth_info = auth.inner; let prefix = prefix.0.to_owned(); - let path_components = path.into_inner(); + let path_components = path_components.into_inner(); + let path = req.path().to_owned(); - let resource_service = R::new(req, auth_info.clone(), path_components.clone()).await?; + let resource_service = R::new(&req, &auth_info, path_components.clone()).await?; // A request body is optional. If empty we MUST return all props let propfind: PropfindElement = if !body.is_empty() { @@ -75,13 +76,13 @@ pub async fn route_propfind let mut member_responses = Vec::new(); if depth != Depth::Zero { - for member in resource_service.get_members(auth_info).await? { - member_responses.push(member.propfind(&prefix, props.clone()).await?); + for (path, member) in resource_service.get_members(auth_info).await? { + member_responses.push(member.propfind(&prefix, path, props.clone()).await?); } } let resource = resource_service.get_file().await?; - let response = resource.propfind(&prefix, props).await?; + let response = resource.propfind(&prefix, path, props).await?; Ok(MultistatusElement { responses: vec![response], diff --git a/crates/dav/src/proppatch.rs b/crates/dav/src/proppatch.rs index 793a85d..30c6fa2 100644 --- a/crates/dav/src/proppatch.rs +++ b/crates/dav/src/proppatch.rs @@ -59,7 +59,7 @@ pub async fn route_proppatch Result; - fn get_path(&self) -> &str; - fn set_prop(&mut self, _prop: Self::Prop) -> Result<(), crate::Error> { Err(crate::Error::PropReadOnly) } @@ -48,14 +46,19 @@ pub trait ResourceService: Sized { type Error: ResponseError + From + From; async fn new( - req: HttpRequest, - auth_info: AuthInfo, + req: &HttpRequest, + auth_info: &AuthInfo, path_components: Self::PathComponents, ) -> Result; async fn get_file(&self) -> Result; - async fn get_members(&self, auth_info: AuthInfo) -> Result, Self::Error>; + async fn get_members( + &self, + _auth_info: AuthInfo, + ) -> Result, Self::Error> { + Ok(vec![]) + } async fn save_file(&self, file: Self::File) -> Result<(), Self::Error>; } @@ -99,8 +102,12 @@ pub enum PropstatType { pub trait HandlePropfind { type Error: ResponseError + From + From; - async fn propfind(&self, prefix: &str, props: Vec<&str>) - -> Result; + async fn propfind( + &self, + prefix: &str, + path: String, + props: Vec<&str>, + ) -> Result; } #[async_trait(?Send)] @@ -110,6 +117,7 @@ impl HandlePropfind for R { async fn propfind( &self, prefix: &str, + path: String, props: Vec<&str>, ) -> Result>, TagList>, R::Error> { let mut props = props; @@ -125,7 +133,7 @@ impl HandlePropfind for R { .map(|&prop| prop.to_string()) .collect(); return Ok(PropstatResponseElement { - href: self.get_path().to_owned(), + href: path, propstat: vec![PropstatType::Normal(PropstatElement { prop: PropWrapper::TagList(TagList::from(props)), status: format!("HTTP/1.1 {}", StatusCode::OK), @@ -177,7 +185,7 @@ impl HandlePropfind for R { })); } Ok(PropstatResponseElement { - href: self.get_path().to_owned(), + href: path, propstat: propstats, }) }