Refactoring: Lots of fixes still necessary to get it into a working state

This commit is contained in:
Lennart
2024-05-25 22:00:09 +02:00
parent 35acfe1575
commit 7a0a91f823
14 changed files with 470 additions and 223 deletions

1
Cargo.lock generated
View File

@@ -1748,6 +1748,7 @@ dependencies = [
"futures-util",
"itertools",
"quick-xml",
"roxmltree",
"rustical_auth",
"serde",
"strum",

View File

@@ -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<C: CalendarStore + ?Sized> {
pub prefix: String,
pub store: Arc<RwLock<C>>,
}
@@ -32,14 +31,14 @@ pub fn configure_dav<A: CheckAuthentication, C: CalendarStore + ?Sized>(
auth: Arc<A>,
store: Arc<RwLock<C>>,
) {
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<A: CheckAuthentication, C: CalendarStore + ?Sized>(
.guard(guard::Method(Method::OPTIONS))
.to(options_handler),
)
.service(web::resource("").route(propfind_method().to(handle_propfind::<A, RootResource>)))
.service(
web::resource("").route(web::method(propfind_method()).to(route_propfind::<
A,
RootResource,
C,
>)),
web::resource("/{principal}")
.route(propfind_method().to(handle_propfind::<A, PrincipalResource<C>>)),
)
.service(web::resource("/{principal}").route(
web::method(propfind_method()).to(route_propfind::<A, PrincipalCalendarsResource<C>, C>),
))
// .service(DavResourceService::<PrincipalResource>::new("/{principal}"))
.service(
web::resource("/{principal}/{calendar}")
.route(web::method(report_method()).to(calendar::route_report_calendar::<A, C>))
.route(web::method(propfind_method()).to(route_propfind::<A, CalendarResource<C>, C>))
.route(web::method(mkcol_method()).to(calendar::route_mkcol_calendar::<A, C>)),
.route(report_method().to(calendar::route_report_calendar::<A, C>))
// .route(web::method(propfind_method()).to(route_propfind::<A, CalendarResource<C>, C>))
.route(propfind_method().to(handle_propfind::<A, CalendarResource<C>>))
.route(mkcol_method().to(calendar::route_mkcol_calendar::<A, C>))
.route(web::method(Method::DELETE).to(calendar::delete_calendar::<A, C>)),
)
// .service(web::resource("/{principal}/{calendar}").route(route))
.service(
web::resource("/{principal}/{calendar}/{event}")
.route(web::method(propfind_method()).to(route_propfind::<A, EventResource<C>, C>))
// .route(web::method(propfind_method()).to(route_propfind::<A, EventResource<C>, C>))
.route(propfind_method().to(handle_propfind::<A, EventResource<C>>))
.route(web::method(Method::DELETE).to(event::delete_event::<A, C>))
.route(web::method(Method::GET).to(event::get_event::<A, C>))
.route(web::method(Method::PUT).to(event::put_event::<A, C>)),

View File

@@ -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<C: CalendarStore + ?Sized> {
pub cal_store: Arc<RwLock<C>>,
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<C: CalendarStore + ?Sized> Resource for CalendarResource<C> {
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<Self, Error> {
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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<Vec<Self::MemberType>> {
// As of now the calendar resource has no members
Ok(vec![])
}
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
CalendarProp::Resourcetype => {
@@ -246,4 +210,61 @@ impl<C: CalendarStore + ?Sized> Resource for CalendarResource<C> {
),
}
}
fn get_path(&self) -> &str {
&self.path
}
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> ResourceService for CalendarResource<C> {
type MemberType = CalendarFile;
type PathComponents = (String, String); // principal, calendar_id
type File = CalendarFile;
async fn get_file(&self) -> Result<Self::File> {
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<Vec<Self::MemberType>> {
// 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<Self, rustical_dav::error::Error> {
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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,
})
}
}

View File

@@ -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<C: CalendarStore + ?Sized> {
pub cal_store: Arc<RwLock<C>>,
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<C: CalendarStore + ?Sized> Resource for EventResource<C> {
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<Vec<Self::MemberType>> {
Ok(vec![])
}
async fn acquire_from_request(
req: HttpRequest,
_auth_info: AuthInfo,
uri_components: Self::UriComponents,
_prefix: String,
) -> Result<Self, Error> {
let (_principal, cid, uid) = uri_components;
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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<Self::PropResponse> {
@@ -77,7 +53,7 @@ impl<C: CalendarStore + ?Sized> Resource for EventResource<C> {
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<C: CalendarStore + ?Sized> Resource for EventResource<C> {
}
}
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> ResourceService for EventResource<C> {
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<Vec<Self::MemberType>> {
Ok(vec![])
}
async fn new(
req: HttpRequest,
_auth_info: AuthInfo,
path_components: Self::PathComponents,
_prefix: String,
) -> Result<Self, Error> {
let (_principal, cid, uid) = path_components;
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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<Self::File> {
let event = self
.cal_store
.read()
.await
.get_event(&self.cid, &self.uid)
.await?;
Ok(EventFile { event })
}
}

View File

@@ -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<C: CalendarStore + ?Sized> {
pub struct PrincipalResource<C: CalendarStore + ?Sized> {
prefix: String,
principal: String,
path: String,
cal_store: Arc<RwLock<C>>,
}
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<C: CalendarStore + ?Sized> Resource for PrincipalCalendarsResource<C> {
type UriComponents = ();
type MemberType = CalendarResource<C>;
impl Resource for PrincipalFile {
type PropType = PrincipalProp;
type PropResponse = PrincipalPropResponse;
fn get_path(&self) -> &str {
&self.path
}
async fn get_members(&self) -> Result<Vec<Self::MemberType>> {
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<Self, Error> {
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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<Self::PropResponse> {
match prop {
PrincipalProp::Resourcetype => {
@@ -121,4 +84,65 @@ impl<C: CalendarStore + ?Sized> Resource for PrincipalCalendarsResource<C> {
}
}
}
fn get_path(&self) -> &str {
&self.path
}
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> ResourceService for PrincipalResource<C> {
type PathComponents = (String,);
type MemberType = CalendarFile;
type File = PrincipalFile;
async fn new(
req: HttpRequest,
auth_info: AuthInfo,
_path_components: Self::PathComponents,
prefix: String,
) -> Result<Self, rustical_dav::error::Error> {
let cal_store = req
.app_data::<Data<RwLock<C>>>()
.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<Self::File> {
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<Vec<Self::MemberType>> {
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())
}
}

View File

@@ -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<Vec<Self::MemberType>> {
Ok(vec![])
}
async fn acquire_from_request(
req: HttpRequest,
auth_info: AuthInfo,
_uri_components: Self::UriComponents,
prefix: String,
) -> Result<Self, Error> {
Ok(Self {
prefix,
principal: auth_info.user_id,
path: req.path().to_string(),
})
}
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
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<Vec<Self::MemberType>> {
Ok(vec![])
}
async fn new(
req: HttpRequest,
auth_info: AuthInfo,
_path_components: Self::PathComponents,
prefix: String,
) -> Result<Self, Error> {
Ok(Self {
prefix,
principal: auth_info.user_id,
path: req.path().to_string(),
})
}
async fn get_file(&self) -> Result<Self::File> {
Ok(RootFile {
path: self.path.to_owned(),
principal: self.principal.to_owned(),
prefix: self.prefix.to_owned(),
})
}
}

View File

@@ -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<C: CalendarStore + ?Sized>(
query_node: Node<'_, '_>,
request: HttpRequest,
_request: HttpRequest,
events: Vec<Event>,
cal_store: Arc<RwLock<C>>,
_cal_store: Arc<RwLock<C>>,
) -> Result<HttpResponse, Error> {
let prop_node = query_node
.children()
@@ -50,22 +50,25 @@ async fn handle_report_calendar_query<C: CalendarStore + ?Sized>(
.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<Vec<_>, _> = 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<Vec<_>, _> = 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<A: CheckAuthentication, C: CalendarStore + ?Si
Ok(HttpResponse::Created().body(""))
}
pub async fn delete_calendar<A: CheckAuthentication, C: CalendarStore + ?Sized>(
context: Data<CalDavContext<C>>,
path: Path<(String, String)>,
auth: AuthInfoExtractor<A>,
) -> Result<HttpResponse, Error> {
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(""))
}

View File

@@ -63,7 +63,7 @@ pub async fn get_event<A: CheckAuthentication, C: CalendarStore + ?Sized>(
Ok(HttpResponse::Ok()
.insert_header(("ETag", event.get_etag()))
.body(event.get_ics()))
.body(event.get_ics().to_owned()))
}
pub async fn put_event<A: CheckAuthentication, C: CalendarStore + ?Sized>(

View File

@@ -1,3 +1,2 @@
pub mod calendar;
pub mod event;
pub mod propfind;

View File

@@ -15,3 +15,4 @@ serde = { version = "1.0.197", features = ["derive"] }
strum = "0.26"
itertools = "0.12"
thiserror = "1.0"
roxmltree = "0.19"

View File

@@ -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<Self::PropResponse>;
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<Self, Error>;
async fn get_file(&self) -> Result<Self::File>;
async fn get_members(
&self,
auth_info: AuthInfo,
path_components: Self::PathComponents,
) -> Result<Vec<Self::MemberType>>;
}
#[derive(Serialize)]
pub struct PropWrapper<T: Serialize> {
#[serde(rename = "$value")]
prop: T,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
struct PropstatElement<T: Serialize> {
prop: T,
status: String,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PropstatResponseElement<T1: Serialize, T2: Serialize> {
href: String,
propstat: Vec<PropstatType<T1, T2>>,
}
#[derive(Serialize)]
#[serde(untagged)]
enum PropstatType<T1: Serialize, T2: Serialize> {
Normal(PropstatElement<T1>),
NotFound(PropstatElement<T2>),
}
#[async_trait(?Send)]
pub trait HandlePropfind {
async fn propfind(&self, props: Vec<&str>) -> Result<impl Serialize>;
}
#[async_trait(?Send)]
impl<R: Resource> HandlePropfind for R {
async fn propfind(
&self,
props: Vec<&str>,
) -> Result<PropstatResponseElement<PropWrapper<Vec<R::PropResponse>>, 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,
})
}
}

View File

@@ -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;

View File

@@ -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<Vec<&str>, Error> {
}
}
pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStore + ?Sized>(
path: Path<R::UriComponents>,
pub async fn handle_propfind<
A: CheckAuthentication,
R: ResourceService + ?Sized,
// C: CalendarStore + ?Sized,
>(
path: Path<R::PathComponents>,
body: String,
req: HttpRequest,
context: Data<CalDavContext<C>>,
prefix: Data<ServicePrefix>,
auth: AuthInfoExtractor<A>,
depth: Depth,
) -> Result<HttpResponse, rustical_dav::error::Error> {
) -> Result<HttpResponse, crate::error::Error> {
// 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?);
}
}

View File

@@ -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 Serialize>;
}
impl<R: Resource> HandlePropfind for R {
impl<R: OldResource> HandlePropfind for R {
fn propfind(&self, props: Vec<&str>) -> Result<impl Serialize> {
let mut props = props.into_iter().unique().collect_vec();
if props.contains(&"allprops") {