From 7a0a91f8230cb4bc4c179845555a512752b7d1f3 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 25 May 2024 22:00:09 +0200 Subject: [PATCH] Refactoring: Lots of fixes still necessary to get it into a working state --- Cargo.lock | 1 + crates/caldav/src/lib.rs | 37 +++-- crates/caldav/src/resources/calendar.rs | 115 ++++++++------- crates/caldav/src/resources/event.rs | 97 ++++++++----- crates/caldav/src/resources/principal.rs | 128 ++++++++++------- crates/caldav/src/resources/root.rs | 75 ++++++---- crates/caldav/src/routes/calendar.rs | 48 ++++--- crates/caldav/src/routes/event.rs | 2 +- crates/caldav/src/routes/mod.rs | 1 - crates/dav/Cargo.toml | 1 + crates/dav/src/dav_resource.rs | 131 ++++++++++++++++++ crates/dav/src/lib.rs | 2 + .../src/routes => dav/src}/propfind.rs | 49 ++++--- crates/dav/src/resource.rs | 6 +- 14 files changed, 470 insertions(+), 223 deletions(-) create mode 100644 crates/dav/src/dav_resource.rs rename crates/{caldav/src/routes => dav/src}/propfind.rs (77%) diff --git a/Cargo.lock b/Cargo.lock index a08ae40..d32c18e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,6 +1748,7 @@ dependencies = [ "futures-util", "itertools", "quick-xml", + "roxmltree", "rustical_auth", "serde", "strum", diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 55898fb..d3cc7ad 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -3,12 +3,12 @@ use actix_web::web::{self, Data}; use actix_web::{guard, HttpResponse, Responder}; use resources::calendar::CalendarResource; use resources::event::EventResource; -use resources::principal::PrincipalCalendarsResource; +use resources::principal::PrincipalResource; use resources::root::RootResource; -use routes::propfind::route_propfind; use routes::{calendar, event}; use rustical_auth::CheckAuthentication; use rustical_dav::error::Error; +use rustical_dav::propfind::{handle_propfind, ServicePrefix}; use rustical_store::calendar::CalendarStore; use std::str::FromStr; use std::sync::Arc; @@ -18,7 +18,6 @@ pub mod resources; pub mod routes; pub struct CalDavContext { - pub prefix: String, pub store: Arc>, } @@ -32,14 +31,14 @@ pub fn configure_dav( auth: Arc, store: Arc>, ) { - let propfind_method = || Method::from_str("PROPFIND").unwrap(); - let report_method = || Method::from_str("REPORT").unwrap(); - let mkcol_method = || Method::from_str("MKCOL").unwrap(); + let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap()); + let report_method = || web::method(Method::from_str("REPORT").unwrap()); + let mkcol_method = || web::method(Method::from_str("MKCOL").unwrap()); cfg.app_data(Data::new(CalDavContext { - prefix, store: store.clone(), })) + .app_data(Data::new(ServicePrefix(prefix))) .app_data(Data::from(store.clone())) .app_data(Data::from(auth)) .service( @@ -48,25 +47,25 @@ pub fn configure_dav( .guard(guard::Method(Method::OPTIONS)) .to(options_handler), ) + .service(web::resource("").route(propfind_method().to(handle_propfind::))) .service( - web::resource("").route(web::method(propfind_method()).to(route_propfind::< - A, - RootResource, - C, - >)), + web::resource("/{principal}") + .route(propfind_method().to(handle_propfind::>)), ) - .service(web::resource("/{principal}").route( - web::method(propfind_method()).to(route_propfind::, C>), - )) + // .service(DavResourceService::::new("/{principal}")) .service( web::resource("/{principal}/{calendar}") - .route(web::method(report_method()).to(calendar::route_report_calendar::)) - .route(web::method(propfind_method()).to(route_propfind::, C>)) - .route(web::method(mkcol_method()).to(calendar::route_mkcol_calendar::)), + .route(report_method().to(calendar::route_report_calendar::)) + // .route(web::method(propfind_method()).to(route_propfind::, C>)) + .route(propfind_method().to(handle_propfind::>)) + .route(mkcol_method().to(calendar::route_mkcol_calendar::)) + .route(web::method(Method::DELETE).to(calendar::delete_calendar::)), ) + // .service(web::resource("/{principal}/{calendar}").route(route)) .service( web::resource("/{principal}/{calendar}/{event}") - .route(web::method(propfind_method()).to(route_propfind::, C>)) + // .route(web::method(propfind_method()).to(route_propfind::, C>)) + .route(propfind_method().to(handle_propfind::>)) .route(web::method(Method::DELETE).to(event::delete_event::)) .route(web::method(Method::GET).to(event::get_event::)) .route(web::method(Method::PUT).to(event::put_event::)), diff --git a/crates/caldav/src/resources/calendar.rs b/crates/caldav/src/resources/calendar.rs index df57437..71610e8 100644 --- a/crates/caldav/src/resources/calendar.rs +++ b/crates/caldav/src/resources/calendar.rs @@ -2,11 +2,9 @@ use actix_web::{web::Data, HttpRequest}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use rustical_auth::AuthInfo; +use rustical_dav::dav_resource::{Resource, ResourceService}; use rustical_dav::error::Error; -use rustical_dav::{ - resource::Resource, - xml_snippets::{HrefElement, TextNode}, -}; +use rustical_dav::xml_snippets::{HrefElement, TextNode}; use rustical_store::calendar::{Calendar, CalendarStore}; use serde::Serialize; use std::sync::Arc; @@ -15,10 +13,10 @@ use tokio::sync::RwLock; pub struct CalendarResource { pub cal_store: Arc>, - pub calendar: Calendar, pub path: String, pub prefix: String, pub principal: String, + pub calendar_id: String, } #[derive(Serialize)] @@ -159,51 +157,17 @@ pub enum CalendarPropResponse { CurrentUserPrivilegeSet(UserPrivilegeSet), } -#[async_trait(?Send)] -impl Resource for CalendarResource { - type MemberType = Self; - type UriComponents = (String, String); // principal, calendar_id +pub struct CalendarFile { + pub calendar: Calendar, + pub principal: String, + pub prefix: String, + pub path: String, +} + +impl Resource for CalendarFile { type PropType = CalendarProp; type PropResponse = CalendarPropResponse; - async fn acquire_from_request( - req: HttpRequest, - _auth_info: AuthInfo, - uri_components: Self::UriComponents, - prefix: String, - ) -> Result { - let cal_store = req - .app_data::>>() - .ok_or(anyhow!("no calendar store in app_data!"))? - .clone() - .into_inner(); - - let (principal, cid) = uri_components; - // TODO: fix errors - let calendar = cal_store - .read() - .await - .get_calendar(&cid) - .await - .map_err(|_e| Error::NotFound)?; - Ok(Self { - cal_store, - calendar, - path: req.path().to_string(), - prefix, - principal, - }) - } - - fn get_path(&self) -> &str { - &self.path - } - - async fn get_members(&self) -> Result> { - // As of now the calendar resource has no members - Ok(vec![]) - } - fn get_prop(&self, prop: Self::PropType) -> Result { match prop { CalendarProp::Resourcetype => { @@ -246,4 +210,61 @@ impl Resource for CalendarResource { ), } } + + fn get_path(&self) -> &str { + &self.path + } +} + +#[async_trait(?Send)] +impl ResourceService for CalendarResource { + type MemberType = CalendarFile; + type PathComponents = (String, String); // principal, calendar_id + type File = CalendarFile; + + async fn get_file(&self) -> Result { + let calendar = self + .cal_store + .read() + .await + .get_calendar(&self.calendar_id) + .await + .map_err(|_e| Error::NotFound)?; + Ok(CalendarFile { + calendar, + prefix: self.prefix.to_owned(), + principal: self.principal.to_owned(), + path: self.path.to_owned(), + }) + } + + async fn get_members( + &self, + _auth_info: AuthInfo, + _path_components: Self::PathComponents, + ) -> Result> { + // As of now the calendar resource has no members + Ok(vec![]) + } + + async fn new( + req: HttpRequest, + auth_info: AuthInfo, + path_components: Self::PathComponents, + prefix: String, + ) -> Result { + let cal_store = req + .app_data::>>() + .ok_or(anyhow!("no calendar store in app_data!"))? + .clone() + .into_inner(); + + Ok(Self { + prefix, + path: req.path().to_owned(), + principal: auth_info.user_id, + calendar_id: path_components.1, + cal_store, + }) + } } diff --git a/crates/caldav/src/resources/event.rs b/crates/caldav/src/resources/event.rs index 34556c3..38cd840 100644 --- a/crates/caldav/src/resources/event.rs +++ b/crates/caldav/src/resources/event.rs @@ -2,8 +2,9 @@ use actix_web::{web::Data, HttpRequest}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use rustical_auth::AuthInfo; -use rustical_dav::error::Error; -use rustical_dav::{resource::Resource, xml_snippets::TextNode}; +use rustical_dav::dav_resource::Resource; +use rustical_dav::xml_snippets::TextNode; +use rustical_dav::{dav_resource::ResourceService, error::Error}; use rustical_store::calendar::CalendarStore; use rustical_store::event::Event; use serde::Serialize; @@ -14,7 +15,8 @@ use tokio::sync::RwLock; pub struct EventResource { pub cal_store: Arc>, pub path: String, - pub event: Event, + pub cid: String, + pub uid: String, } #[derive(EnumString, Debug, VariantNames, IntoStaticStr, Clone)] @@ -33,42 +35,16 @@ pub enum PrincipalPropResponse { Getcontenttype(TextNode), } -#[async_trait(?Send)] -impl Resource for EventResource { - type UriComponents = (String, String, String); // principal, calendar, event - type MemberType = Self; +pub struct EventFile { + pub event: Event, +} + +impl Resource for EventFile { type PropType = EventProp; type PropResponse = PrincipalPropResponse; fn get_path(&self) -> &str { - &self.path - } - - async fn get_members(&self) -> Result> { - Ok(vec![]) - } - - async fn acquire_from_request( - req: HttpRequest, - _auth_info: AuthInfo, - uri_components: Self::UriComponents, - _prefix: String, - ) -> Result { - let (_principal, cid, uid) = uri_components; - - let cal_store = req - .app_data::>>() - .ok_or(anyhow!("no calendar store in app_data!"))? - .clone() - .into_inner(); - - let event = cal_store.read().await.get_event(&cid, &uid).await?; - - Ok(Self { - cal_store, - event, - path: req.path().to_string(), - }) + "asd" } fn get_prop(&self, prop: Self::PropType) -> Result { @@ -77,7 +53,7 @@ impl Resource for EventResource { self.event.get_etag(), )))), EventProp::CalendarData => Ok(PrincipalPropResponse::CalendarData(TextNode(Some( - self.event.get_ics(), + self.event.get_ics().to_owned(), )))), EventProp::Getcontenttype => Ok(PrincipalPropResponse::Getcontenttype(TextNode(Some( "text/calendar;charset=utf-8".to_owned(), @@ -85,3 +61,52 @@ impl Resource for EventResource { } } } + +#[async_trait(?Send)] +impl ResourceService for EventResource { + type PathComponents = (String, String, String); // principal, calendar, event + type File = EventFile; + type MemberType = EventFile; + + async fn get_members( + &self, + _auth_info: AuthInfo, + _path_components: Self::PathComponents, + ) -> Result> { + Ok(vec![]) + } + + async fn new( + req: HttpRequest, + _auth_info: AuthInfo, + path_components: Self::PathComponents, + _prefix: String, + ) -> Result { + let (_principal, cid, uid) = path_components; + + let cal_store = req + .app_data::>>() + .ok_or(anyhow!("no calendar store in app_data!"))? + .clone() + .into_inner(); + + // let event = cal_store.read().await.get_event(&cid, &uid).await?; + + Ok(Self { + cal_store, + cid, + uid, + path: req.path().to_string(), + }) + } + + async fn get_file(&self) -> Result { + let event = self + .cal_store + .read() + .await + .get_event(&self.cid, &self.uid) + .await?; + Ok(EventFile { event }) + } +} diff --git a/crates/caldav/src/resources/principal.rs b/crates/caldav/src/resources/principal.rs index 3a202aa..b807c0f 100644 --- a/crates/caldav/src/resources/principal.rs +++ b/crates/caldav/src/resources/principal.rs @@ -1,24 +1,32 @@ -use actix_web::{web::Data, HttpRequest}; +use std::sync::Arc; + +use actix_web::web::Data; +use actix_web::HttpRequest; use anyhow::{anyhow, Result}; use async_trait::async_trait; use rustical_auth::AuthInfo; -use rustical_dav::error::Error; -use rustical_dav::{resource::Resource, xml_snippets::HrefElement}; +use rustical_dav::dav_resource::{Resource, ResourceService}; +use rustical_dav::xml_snippets::HrefElement; use rustical_store::calendar::CalendarStore; use serde::Serialize; -use std::sync::Arc; use strum::{EnumString, IntoStaticStr, VariantNames}; use tokio::sync::RwLock; -use super::calendar::CalendarResource; +use super::calendar::CalendarFile; -pub struct PrincipalCalendarsResource { +pub struct PrincipalResource { prefix: String, principal: String, path: String, cal_store: Arc>, } +pub struct PrincipalFile { + prefix: String, + principal: String, + path: String, +} + #[derive(Serialize, Default)] #[serde(rename_all = "kebab-case")] pub struct Resourcetype { @@ -51,55 +59,10 @@ pub enum PrincipalProp { } #[async_trait(?Send)] -impl Resource for PrincipalCalendarsResource { - type UriComponents = (); - type MemberType = CalendarResource; +impl Resource for PrincipalFile { type PropType = PrincipalProp; type PropResponse = PrincipalPropResponse; - fn get_path(&self) -> &str { - &self.path - } - - async fn get_members(&self) -> Result> { - let calendars = self - .cal_store - .read() - .await - .get_calendars(&self.principal) - .await?; - let mut out = Vec::new(); - for calendar in calendars { - let path = format!("{}/{}", &self.path, &calendar.id); - out.push(CalendarResource { - cal_store: self.cal_store.clone(), - calendar, - path, - prefix: self.prefix.clone(), - principal: self.principal.clone(), - }) - } - Ok(out) - } - - async fn acquire_from_request( - req: HttpRequest, - auth_info: AuthInfo, - _uri_components: Self::UriComponents, - prefix: String, - ) -> Result { - let cal_store = req - .app_data::>>() - .ok_or(anyhow!("no calendar store in app_data!"))? - .clone() - .into_inner(); - Ok(Self { - cal_store, - prefix, - principal: auth_info.user_id, - path: req.path().to_string(), - }) - } fn get_prop(&self, prop: Self::PropType) -> Result { match prop { PrincipalProp::Resourcetype => { @@ -121,4 +84,65 @@ impl Resource for PrincipalCalendarsResource { } } } + + fn get_path(&self) -> &str { + &self.path + } +} + +#[async_trait(?Send)] +impl ResourceService for PrincipalResource { + type PathComponents = (String,); + type MemberType = CalendarFile; + type File = PrincipalFile; + + async fn new( + req: HttpRequest, + auth_info: AuthInfo, + _path_components: Self::PathComponents, + prefix: String, + ) -> Result { + let cal_store = req + .app_data::>>() + .ok_or(anyhow!("no calendar store in app_data!"))? + .clone() + .into_inner(); + + Ok(Self { + cal_store, + path: req.path().to_owned(), + principal: auth_info.user_id, + prefix, + }) + } + + async fn get_file(&self) -> Result { + Ok(PrincipalFile { + principal: self.principal.to_owned(), + prefix: self.prefix.to_owned(), + path: self.path.to_owned(), + }) + } + + async fn get_members( + &self, + _auth_info: AuthInfo, + _path_components: Self::PathComponents, + ) -> Result> { + let calendars = self + .cal_store + .read() + .await + .get_calendars(&self.principal) + .await?; + Ok(calendars + .into_iter() + .map(|cal| CalendarFile { + calendar: cal, + principal: self.principal.to_owned(), + prefix: self.prefix.to_owned(), + path: self.path.to_owned(), + }) + .collect()) + } } diff --git a/crates/caldav/src/resources/root.rs b/crates/caldav/src/resources/root.rs index 83ad55d..8f45651 100644 --- a/crates/caldav/src/resources/root.rs +++ b/crates/caldav/src/resources/root.rs @@ -2,8 +2,9 @@ use actix_web::HttpRequest; use anyhow::Result; use async_trait::async_trait; use rustical_auth::AuthInfo; +use rustical_dav::dav_resource::{Resource, ResourceService}; use rustical_dav::error::Error; -use rustical_dav::{resource::Resource, xml_snippets::HrefElement}; +use rustical_dav::xml_snippets::HrefElement; use serde::Serialize; use strum::{EnumString, IntoStaticStr, VariantNames}; @@ -33,34 +34,16 @@ pub enum RootPropResponse { CurrentUserPrincipal(HrefElement), } -#[async_trait(?Send)] -impl Resource for RootResource { - type UriComponents = (); - type MemberType = Self; +pub struct RootFile { + pub prefix: String, + pub principal: String, + pub path: String, +} + +impl Resource for RootFile { type PropType = RootProp; type PropResponse = RootPropResponse; - fn get_path(&self) -> &str { - &self.path - } - - async fn get_members(&self) -> Result> { - Ok(vec![]) - } - - async fn acquire_from_request( - req: HttpRequest, - auth_info: AuthInfo, - _uri_components: Self::UriComponents, - prefix: String, - ) -> Result { - Ok(Self { - prefix, - principal: auth_info.user_id, - path: req.path().to_string(), - }) - } - fn get_prop(&self, prop: Self::PropType) -> Result { match prop { RootProp::Resourcetype => Ok(RootPropResponse::Resourcetype(Resourcetype::default())), @@ -69,4 +52,44 @@ impl Resource for RootResource { )), } } + + fn get_path(&self) -> &str { + &self.path + } +} + +#[async_trait(?Send)] +impl ResourceService for RootResource { + type PathComponents = (); + type MemberType = RootFile; + type File = RootFile; + + async fn get_members( + &self, + _auth_info: AuthInfo, + _path_components: Self::PathComponents, + ) -> Result> { + Ok(vec![]) + } + + async fn new( + req: HttpRequest, + auth_info: AuthInfo, + _path_components: Self::PathComponents, + prefix: String, + ) -> Result { + Ok(Self { + prefix, + principal: auth_info.user_id, + path: req.path().to_string(), + }) + } + + async fn get_file(&self) -> Result { + Ok(RootFile { + path: self.path.to_owned(), + principal: self.principal.to_owned(), + prefix: self.prefix.to_owned(), + }) + } } diff --git a/crates/caldav/src/routes/calendar.rs b/crates/caldav/src/routes/calendar.rs index 5db5bd6..303d911 100644 --- a/crates/caldav/src/routes/calendar.rs +++ b/crates/caldav/src/routes/calendar.rs @@ -1,4 +1,4 @@ -use crate::resources::event::EventResource; +use crate::resources::event::EventFile; use crate::CalDavContext; use crate::Error; use actix_web::http::header::ContentType; @@ -7,8 +7,8 @@ use actix_web::{HttpRequest, HttpResponse}; use anyhow::Result; use roxmltree::{Node, NodeType}; use rustical_auth::{AuthInfoExtractor, CheckAuthentication}; +use rustical_dav::dav_resource::HandlePropfind; use rustical_dav::namespace::Namespace; -use rustical_dav::resource::HandlePropfind; use rustical_dav::xml_snippets::generate_multistatus; use rustical_store::calendar::{Calendar, CalendarStore}; use rustical_store::event::Event; @@ -36,9 +36,9 @@ async fn _parse_filter(filter_node: &Node<'_, '_>) { async fn handle_report_calendar_query( query_node: Node<'_, '_>, - request: HttpRequest, + _request: HttpRequest, events: Vec, - cal_store: Arc>, + _cal_store: Arc>, ) -> Result { let prop_node = query_node .children() @@ -50,22 +50,25 @@ async fn handle_report_calendar_query( .map(|node| node.tag_name().name()) .collect(); - let event_resources: Vec<_> = events - .iter() + let event_files: Vec<_> = events + .into_iter() .map(|event| { - let path = format!("{}/{}", request.path(), event.get_uid()); - EventResource { - cal_store: cal_store.clone(), - path: path.clone(), - event: event.clone(), + // TODO: fix + // let path = format!("{}/{}", request.path(), event.get_uid()); + EventFile { + event, // cal_store: cal_store.clone(), } }) .collect(); - let event_results: Result, _> = event_resources - .iter() - .map(|ev| ev.propfind(props.clone())) - .collect(); - let event_responses = event_results?; + let mut event_responses = Vec::new(); + for event_file in event_files { + event_responses.push(event_file.propfind(props.clone()).await?); + } + // let event_results: Result, _> = event_files + // .iter() + // .map(|ev| ev.propfind(props.clone())) + // .collect(); + // let event_responses = event_results?; let output = generate_multistatus(vec![Namespace::Dav, Namespace::CalDAV], |writer| { for result in event_responses { @@ -178,3 +181,16 @@ pub async fn route_mkcol_calendar( + context: Data>, + path: Path<(String, String)>, + auth: AuthInfoExtractor, +) -> Result { + let _user = auth.inner.user_id; + // TODO: verify whether user is authorized + let (_principal, cid) = path.into_inner(); + context.store.write().await.delete_calendar(&cid).await?; + + Ok(HttpResponse::Ok().body("")) +} diff --git a/crates/caldav/src/routes/event.rs b/crates/caldav/src/routes/event.rs index 3024357..b093a78 100644 --- a/crates/caldav/src/routes/event.rs +++ b/crates/caldav/src/routes/event.rs @@ -63,7 +63,7 @@ pub async fn get_event( Ok(HttpResponse::Ok() .insert_header(("ETag", event.get_etag())) - .body(event.get_ics())) + .body(event.get_ics().to_owned())) } pub async fn put_event( diff --git a/crates/caldav/src/routes/mod.rs b/crates/caldav/src/routes/mod.rs index 538e825..ec0aeac 100644 --- a/crates/caldav/src/routes/mod.rs +++ b/crates/caldav/src/routes/mod.rs @@ -1,3 +1,2 @@ pub mod calendar; pub mod event; -pub mod propfind; diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index e8d5cee..38ff660 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -15,3 +15,4 @@ serde = { version = "1.0.197", features = ["derive"] } strum = "0.26" itertools = "0.12" thiserror = "1.0" +roxmltree = "0.19" diff --git a/crates/dav/src/dav_resource.rs b/crates/dav/src/dav_resource.rs new file mode 100644 index 0000000..dfd8ac1 --- /dev/null +++ b/crates/dav/src/dav_resource.rs @@ -0,0 +1,131 @@ +use crate::{error::Error, xml_snippets::TagList}; +use actix_web::{http::StatusCode, HttpRequest}; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use itertools::Itertools; +use rustical_auth::AuthInfo; +use serde::Serialize; +use std::str::FromStr; +use strum::VariantNames; + +#[async_trait(?Send)] +pub trait Resource { + type PropType: FromStr + VariantNames + Into<&'static str> + Clone; + type PropResponse: Serialize; + + fn list_dead_props() -> &'static [&'static str] { + Self::PropType::VARIANTS + } + + fn get_prop(&self, prop: Self::PropType) -> Result; + + fn get_path(&self) -> &str; +} + +// A resource is identified by a URI and has properties +// A resource can also be a collection +// A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed +// A resource exists +#[async_trait(?Send)] +pub trait ResourceService: Sized { + type MemberType: Resource; + type PathComponents: Sized + Clone; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) + type File: Resource; + + async fn new( + req: HttpRequest, + auth_info: AuthInfo, + path_components: Self::PathComponents, + prefix: String, + ) -> Result; + + async fn get_file(&self) -> Result; + + async fn get_members( + &self, + auth_info: AuthInfo, + path_components: Self::PathComponents, + ) -> Result>; +} + +#[derive(Serialize)] +pub struct PropWrapper { + #[serde(rename = "$value")] + prop: T, +} + +#[derive(Serialize)] +#[serde(rename_all = "kebab-case")] +struct PropstatElement { + prop: T, + status: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct PropstatResponseElement { + href: String, + propstat: Vec>, +} + +#[derive(Serialize)] +#[serde(untagged)] +enum PropstatType { + Normal(PropstatElement), + NotFound(PropstatElement), +} + +#[async_trait(?Send)] +pub trait HandlePropfind { + async fn propfind(&self, props: Vec<&str>) -> Result; +} + +#[async_trait(?Send)] +impl HandlePropfind for R { + async fn propfind( + &self, + props: Vec<&str>, + ) -> Result>, TagList>> { + let mut props = props.into_iter().unique().collect_vec(); + if props.contains(&"allprops") { + if props.len() != 1 { + // allprops MUST be the only queried prop per spec + return Err(anyhow!("allprops MUST be the only queried prop")); + } + props = R::list_dead_props().into(); + } + + let mut invalid_props = Vec::<&str>::new(); + let mut prop_responses = Vec::new(); + for prop in props { + if let Ok(valid_prop) = R::PropType::from_str(prop) { + match self.get_prop(valid_prop.clone()) { + Ok(response) => { + prop_responses.push(response); + } + Err(_) => invalid_props.push(prop), + } + } else { + invalid_props.push(prop); + } + } + + let mut propstats = Vec::new(); + propstats.push(PropstatType::Normal(PropstatElement { + status: format!("HTTP/1.1 {}", StatusCode::OK), + prop: PropWrapper { + prop: prop_responses, + }, + })); + if !invalid_props.is_empty() { + propstats.push(PropstatType::NotFound(PropstatElement { + status: format!("HTTP/1.1 {}", StatusCode::NOT_FOUND), + prop: TagList(invalid_props.iter().map(|&s| s.to_owned()).collect()), + })); + } + Ok(PropstatResponseElement { + href: self.get_path().to_owned(), + propstat: propstats, + }) + } +} diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 7069710..e126b2a 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -1,5 +1,7 @@ +pub mod dav_resource; pub mod depth_extractor; pub mod error; pub mod namespace; +pub mod propfind; pub mod resource; pub mod xml_snippets; diff --git a/crates/caldav/src/routes/propfind.rs b/crates/dav/src/propfind.rs similarity index 77% rename from crates/caldav/src/routes/propfind.rs rename to crates/dav/src/propfind.rs index ee3933d..3cd2e13 100644 --- a/crates/caldav/src/routes/propfind.rs +++ b/crates/dav/src/propfind.rs @@ -1,18 +1,20 @@ -use crate::CalDavContext; +use crate::dav_resource::HandlePropfind; +use crate::dav_resource::ResourceService; +use crate::depth_extractor::Depth; +use crate::namespace::Namespace; +use crate::xml_snippets::generate_multistatus; use actix_web::http::header::ContentType; use actix_web::http::StatusCode; use actix_web::web::{Data, Path}; use actix_web::{HttpRequest, HttpResponse}; use anyhow::{anyhow, Result}; use rustical_auth::{AuthInfoExtractor, CheckAuthentication}; -use rustical_dav::depth_extractor::Depth; -use rustical_dav::namespace::Namespace; -use rustical_dav::resource::{HandlePropfind, Resource}; -use rustical_dav::xml_snippets::generate_multistatus; -use rustical_store::calendar::CalendarStore; use serde::Serialize; use thiserror::Error; +// This is not the final place for this struct +pub struct ServicePrefix(pub String); + #[derive(Debug, Error)] pub enum Error { #[error("invalid propfind request: {0}")] @@ -67,33 +69,36 @@ pub fn parse_propfind(body: &str) -> Result, Error> { } } -pub async fn route_propfind( - path: Path, +pub async fn handle_propfind< + A: CheckAuthentication, + R: ResourceService + ?Sized, + // C: CalendarStore + ?Sized, +>( + path: Path, body: String, req: HttpRequest, - context: Data>, + prefix: Data, auth: AuthInfoExtractor, depth: Depth, -) -> Result { +) -> Result { // TODO: fix errors - let props = parse_propfind(&body).map_err(|_e| anyhow!("propfing parsing error"))?; + let props = parse_propfind(&body).map_err(|_e| anyhow!("propfind parsing error"))?; let auth_info = auth.inner; + let prefix = prefix.0.to_owned(); + let path_components = path.into_inner(); - let resource = R::acquire_from_request( - req, - auth_info, - path.into_inner(), - context.prefix.to_string(), - ) - .await?; + let resource_service = R::new(req, auth_info.clone(), path_components.clone(), prefix).await?; - let response = resource.propfind(props.clone())?; - let members = resource.get_members().await?; + let resource = resource_service.get_file().await?; + let response = resource.propfind(props.clone()).await?; let mut member_responses = Vec::new(); if depth != Depth::Zero { - for member in &members { - member_responses.push(member.propfind(props.clone())?); + for member in resource_service + .get_members(auth_info, path_components) + .await? + { + member_responses.push(member.propfind(props.clone()).await?); } } diff --git a/crates/dav/src/resource.rs b/crates/dav/src/resource.rs index cf80c56..e30c2f8 100644 --- a/crates/dav/src/resource.rs +++ b/crates/dav/src/resource.rs @@ -13,8 +13,8 @@ use strum::VariantNames; // A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed // A resource exists #[async_trait(?Send)] -pub trait Resource: Sized { - type MemberType: Resource; +pub trait OldResource: Sized { + type MemberType: OldResource; type UriComponents: Sized; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) type PropType: FromStr + VariantNames + Into<&'static str> + Clone; type PropResponse: Serialize; @@ -66,7 +66,7 @@ pub trait HandlePropfind { fn propfind(&self, props: Vec<&str>) -> Result; } -impl HandlePropfind for R { +impl HandlePropfind for R { fn propfind(&self, props: Vec<&str>) -> Result { let mut props = props.into_iter().unique().collect_vec(); if props.contains(&"allprops") {