mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 05:52:19 +00:00
lots of changes
This commit is contained in:
@@ -37,14 +37,16 @@ pub struct SupportedCalendarComponentSet {
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct CalendarData {
|
pub struct CalendarData {
|
||||||
|
#[serde(rename = "@content-type")]
|
||||||
content_type: &'static str,
|
content_type: &'static str,
|
||||||
|
#[serde(rename = "@version")]
|
||||||
version: &'static str,
|
version: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CalendarData {
|
impl Default for CalendarData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
content_type: "text/calendar;charset=utf-8",
|
content_type: "text/calendar",
|
||||||
version: "2.0",
|
version: "2.0",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,13 +123,13 @@ pub enum CalendarProp {
|
|||||||
CurrentUserPrincipal,
|
CurrentUserPrincipal,
|
||||||
Owner,
|
Owner,
|
||||||
Displayname,
|
Displayname,
|
||||||
#[strum(props(tagname = "IC:calendar-color"))]
|
// #[strum(props(tagname = "IC:calendar-color"))]
|
||||||
CalendarColor,
|
CalendarColor,
|
||||||
#[strum(props(tagname = "C:calendar-description"))]
|
// #[strum(props(tagname = "C:calendar-description"))]
|
||||||
CalendarDescription,
|
CalendarDescription,
|
||||||
#[strum(props(tagname = "C:supported-calendar-component-set"))]
|
// #[strum(props(tagname = "C:supported-calendar-component-set"))]
|
||||||
SupportedCalendarComponentSet,
|
SupportedCalendarComponentSet,
|
||||||
#[strum(props(tagname = "C:supported-calendar-data"))]
|
// #[strum(props(tagname = "C:supported-calendar-data"))]
|
||||||
SupportedCalendarData,
|
SupportedCalendarData,
|
||||||
Getcontenttype,
|
Getcontenttype,
|
||||||
CurrentUserPrivilegeSet,
|
CurrentUserPrivilegeSet,
|
||||||
@@ -141,9 +143,19 @@ pub enum CalendarPropResponse {
|
|||||||
CurrentUserPrincipal(HrefElement),
|
CurrentUserPrincipal(HrefElement),
|
||||||
Owner(HrefElement),
|
Owner(HrefElement),
|
||||||
Displayname(TextNode),
|
Displayname(TextNode),
|
||||||
|
#[serde(rename = "IC:calendar-color", alias = "calendar-color")]
|
||||||
CalendarColor(TextNode),
|
CalendarColor(TextNode),
|
||||||
|
#[serde(rename = "C:calendar-description", alias = "calendar-description")]
|
||||||
CalendarDescription(TextNode),
|
CalendarDescription(TextNode),
|
||||||
|
#[serde(
|
||||||
|
rename = "C:supported-calendar-component-set",
|
||||||
|
alias = "supported-calendar-component-set"
|
||||||
|
)]
|
||||||
SupportedCalendarComponentSet(SupportedCalendarComponentSet),
|
SupportedCalendarComponentSet(SupportedCalendarComponentSet),
|
||||||
|
#[serde(
|
||||||
|
rename = "C:supported-calendar-data",
|
||||||
|
alias = "supported-calendar-data"
|
||||||
|
)]
|
||||||
SupportedCalendarData(SupportedCalendarData),
|
SupportedCalendarData(SupportedCalendarData),
|
||||||
Getcontenttype(TextNode),
|
Getcontenttype(TextNode),
|
||||||
MaxResourceSize(TextNode),
|
MaxResourceSize(TextNode),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub struct Resourcetype {
|
|||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum RootPropResponse {
|
pub enum RootPropResponse {
|
||||||
Resourcetype(Resourcetype),
|
Resourcetype(Resourcetype),
|
||||||
CurrentUser(HrefElement),
|
CurrentUserPrincipal(HrefElement),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
@@ -63,9 +63,9 @@ impl Resource for RootResource {
|
|||||||
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
|
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
|
||||||
match prop {
|
match prop {
|
||||||
RootProp::Resourcetype => Ok(RootPropResponse::Resourcetype(Resourcetype::default())),
|
RootProp::Resourcetype => Ok(RootPropResponse::Resourcetype(Resourcetype::default())),
|
||||||
RootProp::CurrentUserPrincipal => Ok(RootPropResponse::CurrentUser(HrefElement::new(
|
RootProp::CurrentUserPrincipal => Ok(RootPropResponse::CurrentUserPrincipal(
|
||||||
format!("{}/{}/", self.prefix, self.principal),
|
HrefElement::new(format!("{}/{}/", self.prefix, self.principal)),
|
||||||
))),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use actix_web::http::header::ContentType;
|
|||||||
use actix_web::web::{Data, Path};
|
use actix_web::web::{Data, Path};
|
||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use quick_xml::events::BytesText;
|
|
||||||
use roxmltree::{Node, NodeType};
|
use roxmltree::{Node, NodeType};
|
||||||
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
||||||
use rustical_dav::namespace::Namespace;
|
use rustical_dav::namespace::Namespace;
|
||||||
@@ -51,22 +50,28 @@ async fn handle_report_calendar_query<C: CalendarStore + ?Sized>(
|
|||||||
.map(|node| node.tag_name().name())
|
.map(|node| node.tag_name().name())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut event_results = Vec::new();
|
let event_resources: Vec<_> = events
|
||||||
for event in events {
|
.iter()
|
||||||
let path = format!("{}/{}", request.path(), event.get_uid());
|
.map(|event| {
|
||||||
let event_resource = EventResource {
|
let path = format!("{}/{}", request.path(), event.get_uid());
|
||||||
cal_store: cal_store.clone(),
|
EventResource {
|
||||||
path: path.clone(),
|
cal_store: cal_store.clone(),
|
||||||
event,
|
path: path.clone(),
|
||||||
};
|
event: event.clone(),
|
||||||
event_results.push(event_resource.propfind(props.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| {
|
let output = generate_multistatus(vec![Namespace::Dav, Namespace::CalDAV], |writer| {
|
||||||
for result in event_results {
|
for result in event_responses {
|
||||||
writer.write_event(quick_xml::events::Event::Text(BytesText::from_escaped(
|
writer
|
||||||
result,
|
.write_serializable("response", &result)
|
||||||
)))?;
|
.map_err(|_e| quick_xml::Error::TextNotFound)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
@@ -90,8 +95,6 @@ pub async fn route_report_calendar<A: CheckAuthentication, C: CalendarStore + ?S
|
|||||||
let query_node = doc.root_element();
|
let query_node = doc.root_element();
|
||||||
let events = context.store.read().await.get_events(&cid).await.unwrap();
|
let events = context.store.read().await.get_events(&cid).await.unwrap();
|
||||||
|
|
||||||
dbg!(&body);
|
|
||||||
|
|
||||||
// TODO: implement filtering
|
// TODO: implement filtering
|
||||||
match query_node.tag_name().name() {
|
match query_node.tag_name().name() {
|
||||||
"calendar-query" => {}
|
"calendar-query" => {}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use actix_web::http::StatusCode;
|
|||||||
use actix_web::web::{Data, Path};
|
use actix_web::web::{Data, Path};
|
||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use quick_xml::events::BytesText;
|
|
||||||
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
||||||
use rustical_dav::depth_extractor::Depth;
|
use rustical_dav::depth_extractor::Depth;
|
||||||
use rustical_dav::namespace::Namespace;
|
use rustical_dav::namespace::Namespace;
|
||||||
use rustical_dav::resource::{HandlePropfind, Resource};
|
use rustical_dav::resource::{HandlePropfind, Resource};
|
||||||
use rustical_dav::xml_snippets::generate_multistatus;
|
use rustical_dav::xml_snippets::generate_multistatus;
|
||||||
use rustical_store::calendar::CalendarStore;
|
use rustical_store::calendar::CalendarStore;
|
||||||
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -76,7 +76,6 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
|
|||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let props = parse_propfind(&body)?;
|
let props = parse_propfind(&body)?;
|
||||||
// let req_path = req.path().to_string();
|
|
||||||
let auth_info = auth.inner;
|
let auth_info = auth.inner;
|
||||||
|
|
||||||
let resource = R::acquire_from_request(
|
let resource = R::acquire_from_request(
|
||||||
@@ -87,21 +86,26 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
|
|||||||
)
|
)
|
||||||
.await?;
|
.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 {
|
if depth != Depth::Zero {
|
||||||
for member in resource.get_members().await? {
|
for member in &members {
|
||||||
responses.push(member.propfind(props.clone())?);
|
member_responses.push(member.propfind(props.clone())?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = generate_multistatus(
|
let output = generate_multistatus(
|
||||||
vec![Namespace::Dav, Namespace::CalDAV, Namespace::ICal],
|
vec![Namespace::Dav, Namespace::CalDAV, Namespace::ICal],
|
||||||
|writer| {
|
|writer| {
|
||||||
for response in responses {
|
writer
|
||||||
writer.write_event(quick_xml::events::Event::Text(BytesText::from_escaped(
|
.write_serializable("response", &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(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@@ -111,3 +115,17 @@ pub async fn route_propfind<A: CheckAuthentication, R: Resource, C: CalendarStor
|
|||||||
.content_type(ContentType::xml())
|
.content_type(ContentType::xml())
|
||||||
.body(output))
|
.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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ futures-util = "0.3"
|
|||||||
quick-xml = "0.31"
|
quick-xml = "0.31"
|
||||||
rustical_auth = { path = "../auth/" }
|
rustical_auth = { path = "../auth/" }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
strum = "0.26.2"
|
strum = "0.26"
|
||||||
|
itertools = "0.12"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
pub mod depth_extractor;
|
pub mod depth_extractor;
|
||||||
pub mod namespace;
|
pub mod namespace;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod tagname;
|
|
||||||
pub mod xml_snippets;
|
pub mod xml_snippets;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use actix_web::{http::StatusCode, HttpRequest};
|
use actix_web::{http::StatusCode, HttpRequest};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use quick_xml::Writer;
|
use itertools::Itertools;
|
||||||
use rustical_auth::AuthInfo;
|
use rustical_auth::AuthInfo;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -51,18 +51,25 @@ struct PropstatElement<T: Serialize> {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
struct PropstatResponseElement<T: Serialize> {
|
struct PropstatResponseElement<T1: Serialize, T2: Serialize> {
|
||||||
href: String,
|
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 {
|
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 {
|
impl<R: Resource> HandlePropfind for R {
|
||||||
fn propfind(&self, props: Vec<&str>) -> Result<String> {
|
fn propfind(&self, props: Vec<&str>) -> Result<impl Serialize> {
|
||||||
let mut props = props;
|
let mut props = props.into_iter().unique().collect_vec();
|
||||||
if props.contains(&"allprops") {
|
if props.contains(&"allprops") {
|
||||||
if props.len() != 1 {
|
if props.len() != 1 {
|
||||||
// allprops MUST be the only queried prop per spec
|
// 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();
|
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 invalid_props = Vec::<&str>::new();
|
||||||
let mut prop_responses = Vec::new();
|
let mut prop_responses = Vec::new();
|
||||||
for prop in props {
|
for prop in props {
|
||||||
@@ -89,31 +93,22 @@ impl<R: Resource> HandlePropfind for R {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write_serializable(
|
let mut propstats = Vec::new();
|
||||||
"response",
|
propstats.push(PropstatType::Normal(PropstatElement {
|
||||||
&PropstatResponseElement {
|
status: format!("HTTP/1.1 {}", StatusCode::OK),
|
||||||
href: self.get_path().to_owned(),
|
prop: PropWrapper {
|
||||||
propstat: PropstatElement {
|
prop: prop_responses,
|
||||||
status: format!("HTTP/1.1 {}", StatusCode::OK),
|
|
||||||
prop: PropWrapper {
|
|
||||||
prop: prop_responses,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)?;
|
}));
|
||||||
if !invalid_props.is_empty() {
|
if !invalid_props.is_empty() {
|
||||||
// TODO: proper error reporting
|
propstats.push(PropstatType::NotFound(PropstatElement {
|
||||||
writer.write_serializable(
|
status: format!("HTTP/1.1 {}", StatusCode::NOT_FOUND),
|
||||||
"response",
|
prop: TagList(invalid_props.iter().map(|&s| s.to_owned()).collect()),
|
||||||
&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()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(std::str::from_utf8(&output_buffer)?.to_string())
|
Ok(PropstatResponseElement {
|
||||||
|
href: self.get_path().to_owned(),
|
||||||
|
propstat: propstats,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user