mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
Refactoring: Lots of fixes still necessary to get it into a working state
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1748,6 +1748,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"itertools",
|
||||
"quick-xml",
|
||||
"roxmltree",
|
||||
"rustical_auth",
|
||||
"serde",
|
||||
"strum",
|
||||
|
||||
@@ -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>)),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(""))
|
||||
}
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod calendar;
|
||||
pub mod event;
|
||||
pub mod propfind;
|
||||
|
||||
@@ -15,3 +15,4 @@ serde = { version = "1.0.197", features = ["derive"] }
|
||||
strum = "0.26"
|
||||
itertools = "0.12"
|
||||
thiserror = "1.0"
|
||||
roxmltree = "0.19"
|
||||
|
||||
131
crates/dav/src/dav_resource.rs
Normal file
131
crates/dav/src/dav_resource.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user