diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-18 12:00:40 +0100 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-18 12:00:40 +0100 |
commit | 3b57d21e30302be409556711db98e16dc1a9badc (patch) | |
tree | f7c5eed66a5899485bcb4194dd29163d96abbe20 | |
parent | f372a95b017587bd964ef80fdfdef7c2128bca15 (diff) | |
download | aerogramme-3b57d21e30302be409556711db98e16dc1a9badc.tar.gz aerogramme-3b57d21e30302be409556711db98e16dc1a9badc.zip |
WIP DAV nodes
-rw-r--r-- | aero-proto/src/dav.rs | 273 |
1 files changed, 223 insertions, 50 deletions
diff --git a/aero-proto/src/dav.rs b/aero-proto/src/dav.rs index 0bbb7f7..2bc1247 100644 --- a/aero-proto/src/dav.rs +++ b/aero-proto/src/dav.rs @@ -23,6 +23,7 @@ use aero_user::config::{DavConfig, DavUnsecureConfig}; use aero_user::login::ArcLoginProvider; use aero_collections::user::User; use aero_dav::types as dav; +use aero_dav::caltypes as cal; use aero_dav::realization::Calendar; use aero_dav::xml as dxml; @@ -196,8 +197,6 @@ async fn router(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<Re let path_segments: Vec<_> = path.split("/").filter(|s| *s != "").collect(); let method = req.method().as_str().to_uppercase(); - //@FIXME check depth, handle it - match (method.as_str(), path_segments.as_slice()) { ("OPTIONS", _) => return Ok(Response::builder() .status(200) @@ -220,12 +219,20 @@ async fn router(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<Re /// <D:propfind xmlns:D='DAV:' xmlns:A='http://apple.com/ns/ical/'> /// <D:prop><D:getcontenttype/><D:resourcetype/><D:displayname/><A:calendar-color/> /// </D:prop></D:propfind> +const SUPPORTED_PROPNAME: [dav::PropertyRequest<Calendar>; 2] = [ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, +]; async fn propfind_root(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { - let supported_propname = vec![ + let node = RootNode {}; + let depth = depth(&req); + + + /*let supported_propname = vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, - ]; + ];*/ // A client may choose not to submit a request body. An empty PROPFIND // request body MUST be treated as if it were an 'allprop' request. @@ -235,60 +242,20 @@ async fn propfind_root(user: std::sync::Arc<User>, req: Request<Incoming>) -> Re tracing::debug!(recv=?propfind, "inferred propfind request"); if matches!(propfind, dav::PropFind::PropName) { - return serialize(dav::Multistatus::<Calendar, dav::PropName<Calendar>> { - responses: vec![dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat( - dav::Href(format!("./{}/", user.username)), - vec![dav::PropStat { - prop: dav::PropName(supported_propname), - status: dav::Status(hyper::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - location: None, - responsedescription: Some(dav::ResponseDescription("user home directory".into())), - }], - responsedescription: Some(dav::ResponseDescription("propname response".to_string())), - }); + return serialize(node.multistatus_name(&user, depth)); } let propname = match propfind { dav::PropFind::PropName => unreachable!(), - dav::PropFind::AllProp(None) => supported_propname.clone(), + dav::PropFind::AllProp(None) => dav::PropName(SUPPORTED_PROPNAME.to_vec()), dav::PropFind::AllProp(Some(dav::Include(mut include))) => { - include.extend_from_slice(supported_propname.as_slice()); - include + include.extend_from_slice(&SUPPORTED_PROPNAME); + dav::PropName(include) }, - dav::PropFind::Prop(dav::PropName(inner)) => inner, - }; - - let values = propname.iter().filter_map(|n| match n { - dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName(format!("{} home", user.username))), - dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), - _ => None, - }).collect(); - - let multistatus = dav::Multistatus::<Calendar, dav::PropValue<Calendar>> { - responses: vec![ dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat( - dav::Href(format!("./{}/", user.username)), - vec![dav::PropStat { - prop: dav::PropValue(values), - status: dav::Status(hyper::StatusCode::OK), - error: None, - responsedescription: None, - }], - ), - error: None, - location: None, - responsedescription: Some(dav::ResponseDescription("Root node".into())), - } ], - responsedescription: Some(dav::ResponseDescription("hello world".to_string())), + dav::PropFind::Prop(inner) => inner, }; - serialize(multistatus) + serialize(node.multistatus_val(&user, &propname, depth)) } async fn propfind_home(user: std::sync::Arc<User>, req: &Request<impl hyper::body::Body>) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { @@ -327,6 +294,8 @@ async fn collections(_user: std::sync::Arc<User>, _req: Request<impl hyper::body } +// ---- HTTP DAV Binding + use futures::stream::TryStreamExt; use http_body_util::BodyStream; use http_body_util::StreamBody; @@ -337,6 +306,13 @@ use std::io::{Error, ErrorKind}; use futures::sink::SinkExt; use tokio_util::io::{SinkWriter, CopyToBytes}; +fn depth(req: &Request<impl hyper::body::Body>) -> dav::Depth { + match req.headers().get("Depth").map(hyper::header::HeaderValue::to_str) { + Some(Ok("0")) => dav::Depth::Zero, + Some(Ok("1")) => dav::Depth::One, + _ => dav::Depth::Infinity, + } +} fn text_body(txt: &'static str) -> BoxBody<Bytes, std::io::Error> { BoxBody::new(Full::new(Bytes::from(txt)).map_err(|e| match e {})) @@ -352,6 +328,11 @@ fn serialize<T: dxml::QWrite + Send + 'static>(elem: T) -> Result<Response<BoxBo let q = quick_xml::writer::Writer::new_with_indent(&mut writer, b' ', 4); let ns_to_apply = vec![ ("xmlns:D".into(), "DAV:".into()) ]; let mut qwriter = dxml::Writer { q, ns_to_apply }; + let decl = quick_xml::events::BytesDecl::from_start(quick_xml::events::BytesStart::from_content("xml encoding='utf-8' version='1.0'", 0)); + match qwriter.q.write_event_async(quick_xml::events::Event::Decl(decl)).await { + Ok(_) => (), + Err(e) => tracing::error!(err=?e, "unable to write XML declaration <?xml ... >"), + } match elem.qwrite(&mut qwriter).await { Ok(_) => tracing::debug!("fully serialized object"), Err(e) => tracing::error!(err=?e, "failed to serialize object"), @@ -384,3 +365,195 @@ async fn deserialize<T: dxml::Node<T>>(req: Request<Incoming>) -> Result<T> { let parsed = rdr.find::<T>().await?; Ok(parsed) } + +//--- + +type ArcUser = std::sync::Arc<User>; +trait DavNode { + // recurence + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>>; + + // node properties + fn name(&self, user: &ArcUser) -> String; + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar>; + fn properties(&self, user: &ArcUser, props: &dav::PropName<Calendar>) -> dav::PropValue<Calendar>; + + // building DAV responses + fn multistatus_name(&self, user: &ArcUser, depth: dav::Depth) -> dav::Multistatus<Calendar, dav::PropName<Calendar>> { + let mut names = vec![(".".into(), self.supported_properties(user))]; + if matches!(depth, dav::Depth::One | dav::Depth::Infinity) { + names.extend(self.children(user).iter().map(|c| (format!("./{}", c.name(user)), c.supported_properties(user)))); + } + + dav::Multistatus::<Calendar, dav::PropName<Calendar>> { + responses: names.into_iter().map(|(url, names)| dav::Response { + status_or_propstat: dav::StatusOrPropstat::PropStat( + dav::Href(url), + vec![dav::PropStat { + prop: names, + status: dav::Status(hyper::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + location: None, + responsedescription: None, + }).collect(), + responsedescription: None, + } + } + + fn multistatus_val(&self, user: &ArcUser, props: &dav::PropName<Calendar>, depth: dav::Depth) -> dav::Multistatus<Calendar, dav::PropValue<Calendar>> { + let mut values = vec![(".".into(), self.properties(user, props))]; + if matches!(depth, dav::Depth::One | dav::Depth::Infinity) { + values.extend(self + .children(user) + .iter() + .map(|c| (format!("./{}", c.name(user)), c.properties(user, props))) + ); + } + + dav::Multistatus::<Calendar, dav::PropValue<Calendar>> { + responses: values.into_iter().map(|(url, propval)| dav::Response { + status_or_propstat: dav::StatusOrPropstat::PropStat( + dav::Href(url), + vec![dav::PropStat { + prop: propval, + status: dav::Status(hyper::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + location: None, + responsedescription: None, + }).collect(), + responsedescription: None, + } + } +} + +struct RootNode {} +impl DavNode for RootNode { + fn name(&self, _user: &ArcUser) -> String { + "/".into() + } + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>> { + vec![Box::new(HomeNode { })] + } + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar> { + dav::PropName(vec![ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, + ]) + } + fn properties(&self, user: &ArcUser, prop: &dav::PropName<Calendar>) -> dav::PropValue<Calendar> { + dav::PropValue(prop.0.iter().filter_map(|n| match n { + dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName("DAV Root".to_string())), + dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), + _ => None, + }).collect()) + } +} + +struct HomeNode {} +impl DavNode for HomeNode { + fn name(&self, user: &ArcUser) -> String { + format!("{}/", user.username) + } + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>> { + vec![Box::new(CalendarListNode { })] + } + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar> { + dav::PropName(vec![ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, + ]) + } + fn properties(&self, user: &ArcUser, prop: &dav::PropName<Calendar>) -> dav::PropValue<Calendar> { + dav::PropValue(prop.0.iter().filter_map(|n| match n { + dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName(format!("{} home", user.username))), + dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), + _ => None, + }).collect()) + } +} + +struct CalendarListNode {} +impl DavNode for CalendarListNode { + fn name(&self, _user: &ArcUser) -> String { + "calendar/".into() + } + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>> { + vec![Box::new(CalendarNode { name: "personal".into() })] + } + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar> { + dav::PropName(vec![ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, + ]) + } + fn properties(&self, user: &ArcUser, prop: &dav::PropName<Calendar>) -> dav::PropValue<Calendar> { + dav::PropValue(prop.0.iter().filter_map(|n| match n { + dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName(format!("{} calendars", user.username))), + dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), + _ => None, + }).collect()) + } +} + +struct CalendarNode { + name: String, +} +impl DavNode for CalendarNode { + fn name(&self, _user: &ArcUser) -> String { + format!("{}/", self.name) + } + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>> { + vec![Box::new(EventNode { file: "something.ics".into() })] + } + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar> { + dav::PropName(vec![ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, + ]) + } + fn properties(&self, user: &ArcUser, prop: &dav::PropName<Calendar>) -> dav::PropValue<Calendar> { + dav::PropValue(prop.0.iter().filter_map(|n| match n { + dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName(format!("{} calendar", self.name))), + dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![ + dav::ResourceType::Collection, + dav::ResourceType::Extension(cal::ResourceType::Calendar), + ])), + _ => None, + }).collect()) + } +} + +struct EventNode { + file: String, +} +impl DavNode for EventNode { + fn name(&self, _user: &ArcUser) -> String { + self.file.to_string() + } + fn children(&self, user: &ArcUser) -> Vec<Box<dyn DavNode>> { + vec![] + } + fn supported_properties(&self, user: &ArcUser) -> dav::PropName<Calendar> { + dav::PropName(vec![ + dav::PropertyRequest::DisplayName, + dav::PropertyRequest::ResourceType, + ]) + } + fn properties(&self, user: &ArcUser, prop: &dav::PropName<Calendar>) -> dav::PropValue<Calendar> { + dav::PropValue(prop.0.iter().filter_map(|n| match n { + dav::PropertyRequest::DisplayName => Some(dav::Property::DisplayName(format!("{} event", self.file))), + dav::PropertyRequest::ResourceType => Some(dav::Property::ResourceType(vec![])), + _ => None, + }).collect()) + } +} + + |