lots of changes

This commit is contained in:
Lennart
2024-03-15 20:53:41 +01:00
parent b89c954752
commit ebf826f5b0
8 changed files with 97 additions and 80 deletions

View File

@@ -37,14 +37,16 @@ pub struct SupportedCalendarComponentSet {
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CalendarData {
#[serde(rename = "@content-type")]
content_type: &'static str,
#[serde(rename = "@version")]
version: &'static str,
}
impl Default for CalendarData {
fn default() -> Self {
Self {
content_type: "text/calendar;charset=utf-8",
content_type: "text/calendar",
version: "2.0",
}
}
@@ -121,13 +123,13 @@ pub enum CalendarProp {
CurrentUserPrincipal,
Owner,
Displayname,
#[strum(props(tagname = "IC:calendar-color"))]
// #[strum(props(tagname = "IC:calendar-color"))]
CalendarColor,
#[strum(props(tagname = "C:calendar-description"))]
// #[strum(props(tagname = "C:calendar-description"))]
CalendarDescription,
#[strum(props(tagname = "C:supported-calendar-component-set"))]
// #[strum(props(tagname = "C:supported-calendar-component-set"))]
SupportedCalendarComponentSet,
#[strum(props(tagname = "C:supported-calendar-data"))]
// #[strum(props(tagname = "C:supported-calendar-data"))]
SupportedCalendarData,
Getcontenttype,
CurrentUserPrivilegeSet,
@@ -141,9 +143,19 @@ pub enum CalendarPropResponse {
CurrentUserPrincipal(HrefElement),
Owner(HrefElement),
Displayname(TextNode),
#[serde(rename = "IC:calendar-color", alias = "calendar-color")]
CalendarColor(TextNode),
#[serde(rename = "C:calendar-description", alias = "calendar-description")]
CalendarDescription(TextNode),
#[serde(
rename = "C:supported-calendar-component-set",
alias = "supported-calendar-component-set"
)]
SupportedCalendarComponentSet(SupportedCalendarComponentSet),
#[serde(
rename = "C:supported-calendar-data",
alias = "supported-calendar-data"
)]
SupportedCalendarData(SupportedCalendarData),
Getcontenttype(TextNode),
MaxResourceSize(TextNode),

View File

@@ -29,7 +29,7 @@ pub struct Resourcetype {
#[serde(rename_all = "kebab-case")]
pub enum RootPropResponse {
Resourcetype(Resourcetype),
CurrentUser(HrefElement),
CurrentUserPrincipal(HrefElement),
}
#[async_trait(?Send)]
@@ -63,9 +63,9 @@ impl Resource for RootResource {
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
RootProp::Resourcetype => Ok(RootPropResponse::Resourcetype(Resourcetype::default())),
RootProp::CurrentUserPrincipal => Ok(RootPropResponse::CurrentUser(HrefElement::new(
format!("{}/{}/", self.prefix, self.principal),
))),
RootProp::CurrentUserPrincipal => Ok(RootPropResponse::CurrentUserPrincipal(
HrefElement::new(format!("{}/{}/", self.prefix, self.principal)),
)),
}
}
}

View File

@@ -5,7 +5,6 @@ use actix_web::http::header::ContentType;
use actix_web::web::{Data, Path};
use actix_web::{HttpRequest, HttpResponse};
use anyhow::Result;
use quick_xml::events::BytesText;
use roxmltree::{Node, NodeType};
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
use rustical_dav::namespace::Namespace;
@@ -51,22 +50,28 @@ async fn handle_report_calendar_query<C: CalendarStore + ?Sized>(
.map(|node| node.tag_name().name())
.collect();
let mut event_results = Vec::new();
for event in events {
let path = format!("{}/{}", request.path(), event.get_uid());
let event_resource = EventResource {
cal_store: cal_store.clone(),
path: path.clone(),
event,
};
event_results.push(event_resource.propfind(props.clone())?);
}
let event_resources: Vec<_> = events
.iter()
.map(|event| {
let path = format!("{}/{}", request.path(), event.get_uid());
EventResource {
cal_store: cal_store.clone(),
path: path.clone(),
event: event.clone(),
}
})
.collect();
let event_results: Result<Vec<_>, _> = event_resources
.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_results {
writer.write_event(quick_xml::events::Event::Text(BytesText::from_escaped(
result,
)))?;
for result in event_responses {
writer
.write_serializable("response", &result)
.map_err(|_e| quick_xml::Error::TextNotFound)?;
}
Ok(())
})?;
@@ -90,8 +95,6 @@ pub async fn route_report_calendar<A: CheckAuthentication, C: CalendarStore + ?S
let query_node = doc.root_element();
let events = context.store.read().await.get_events(&cid).await.unwrap();
dbg!(&body);
// TODO: implement filtering
match query_node.tag_name().name() {
"calendar-query" => {}

View File

@@ -4,13 +4,13 @@ use actix_web::http::StatusCode;
use actix_web::web::{Data, Path};
use actix_web::{HttpRequest, HttpResponse};
use anyhow::Result;
use quick_xml::events::BytesText;
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;
#[derive(Debug, Error)]
@@ -76,7 +76,6 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
depth: Depth,
) -> Result<HttpResponse, Error> {
let props = parse_propfind(&body)?;
// let req_path = req.path().to_string();
let auth_info = auth.inner;
let resource = R::acquire_from_request(
@@ -87,21 +86,26 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
)
.await?;
let mut responses = vec![resource.propfind(props.clone())?];
let response = resource.propfind(props.clone())?;
let members = resource.get_members().await?;
let mut member_responses = Vec::new();
if depth != Depth::Zero {
for member in resource.get_members().await? {
responses.push(member.propfind(props.clone())?);
for member in &members {
member_responses.push(member.propfind(props.clone())?);
}
}
let output = generate_multistatus(
vec![Namespace::Dav, Namespace::CalDAV, Namespace::ICal],
|writer| {
for response in responses {
writer.write_event(quick_xml::events::Event::Text(BytesText::from_escaped(
response,
)))?;
writer
.write_serializable("response", &response)
.map_err(|_e| quick_xml::Error::TextNotFound)?;
for response in member_responses {
writer
.write_serializable("response", &response)
.map_err(|_e| quick_xml::Error::TextNotFound)?;
}
Ok(())
},
@@ -111,3 +115,17 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
.content_type(ContentType::xml())
.body(output))
}
#[derive(Serialize)]
struct MultistatusElement<T1: Serialize, T2: Serialize> {
#[serde(rename = "$value")]
responses: Vec<T1>,
#[serde(rename = "$value")]
member_responses: Vec<T2>,
#[serde(rename = "@xmlns")]
ns_dav: &'static str,
#[serde(rename = "@xmlns:C")]
ns_caldav: &'static str,
#[serde(rename = "@xmlns:IC")]
ns_ical: &'static str,
}

View File

@@ -12,4 +12,5 @@ futures-util = "0.3"
quick-xml = "0.31"
rustical_auth = { path = "../auth/" }
serde = { version = "1.0.197", features = ["derive"] }
strum = "0.26.2"
strum = "0.26"
itertools = "0.12"

View File

@@ -1,5 +1,4 @@
pub mod depth_extractor;
pub mod namespace;
pub mod resource;
pub mod tagname;
pub mod xml_snippets;

View File

@@ -1,7 +1,7 @@
use actix_web::{http::StatusCode, HttpRequest};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use quick_xml::Writer;
use itertools::Itertools;
use rustical_auth::AuthInfo;
use serde::Serialize;
use std::str::FromStr;
@@ -51,18 +51,25 @@ struct PropstatElement<T: Serialize> {
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
struct PropstatResponseElement<T: Serialize> {
struct PropstatResponseElement<T1: Serialize, T2: Serialize> {
href: String,
propstat: PropstatElement<T>,
propstat: Vec<PropstatType<T1, T2>>,
}
#[derive(Serialize)]
#[serde(untagged)]
enum PropstatType<T1: Serialize, T2: Serialize> {
Normal(PropstatElement<T1>),
NotFound(PropstatElement<T2>),
}
pub trait HandlePropfind {
fn propfind(&self, props: Vec<&str>) -> Result<String>;
fn propfind(&self, props: Vec<&str>) -> Result<impl Serialize>;
}
impl<R: Resource> HandlePropfind for R {
fn propfind(&self, props: Vec<&str>) -> Result<String> {
let mut props = props;
fn propfind(&self, props: Vec<&str>) -> Result<impl Serialize> {
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
@@ -71,9 +78,6 @@ impl<R: Resource> HandlePropfind for R {
props = R::list_dead_props().into();
}
let mut output_buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut output_buffer, b' ', 2);
let mut invalid_props = Vec::<&str>::new();
let mut prop_responses = Vec::new();
for prop in props {
@@ -89,31 +93,22 @@ impl<R: Resource> HandlePropfind for R {
}
}
writer.write_serializable(
"response",
&PropstatResponseElement {
href: self.get_path().to_owned(),
propstat: PropstatElement {
status: format!("HTTP/1.1 {}", StatusCode::OK),
prop: PropWrapper {
prop: prop_responses,
},
},
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() {
// TODO: proper error reporting
writer.write_serializable(
"response",
&PropstatResponseElement {
href: self.get_path().to_owned(),
propstat: PropstatElement {
status: format!("HTTP/1.1 {}", StatusCode::NOT_FOUND),
prop: TagList(invalid_props.iter().map(|&s| s.to_owned()).collect()),
},
},
)?;
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(std::str::from_utf8(&output_buffer)?.to_string())
Ok(PropstatResponseElement {
href: self.get_path().to_owned(),
propstat: propstats,
})
}
}

View File

@@ -1,11 +0,0 @@
use strum::EnumProperty;
pub trait TagName {
fn tagname(self) -> &'static str;
}
impl<P: EnumProperty + Into<&'static str>> TagName for P {
fn tagname(self) -> &'static str {
self.get_str("tagname").unwrap_or(self.into())
}
}