From f372a95b017587bd964ef80fdfdef7c2128bca15 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sun, 17 Mar 2024 10:31:05 +0100 Subject: basic propfind --- aero-proto/src/dav.rs | 95 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 15 deletions(-) (limited to 'aero-proto/src') diff --git a/aero-proto/src/dav.rs b/aero-proto/src/dav.rs index 3981b61..0bbb7f7 100644 --- a/aero-proto/src/dav.rs +++ b/aero-proto/src/dav.rs @@ -22,9 +22,9 @@ use rustls_pemfile::{certs, private_key}; use aero_user::config::{DavConfig, DavUnsecureConfig}; use aero_user::login::ArcLoginProvider; use aero_collections::user::User; -use aero_dav::types::{PropFind, Multistatus, PropValue, ResponseDescription}; -use aero_dav::realization::{Core, Calendar}; -use aero_dav::xml as dav; +use aero_dav::types as dav; +use aero_dav::realization::Calendar; +use aero_dav::xml as dxml; pub struct Server { bind_addr: SocketAddr, @@ -196,7 +196,13 @@ async fn router(user: std::sync::Arc, req: Request) -> Result = 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) + .header("DAV", "1") + .body(text_body(""))?), ("PROPFIND", []) => propfind_root(user, req).await, (_, [ username, ..]) if *username != user.username => return Ok(Response::builder() .status(403) @@ -216,14 +222,73 @@ async fn router(user: std::sync::Arc, req: Request) -> Result async fn propfind_root(user: std::sync::Arc, req: Request) -> Result>> { - tracing::info!("root"); + 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. + // @FIXME here we handle any invalid data as an allprop, an empty request is thus correctly + // handled, but corrupted requests are also silently handled as allprop. + let propfind = deserialize::>(req).await.unwrap_or_else(|_| dav::PropFind::::AllProp(None)); + tracing::debug!(recv=?propfind, "inferred propfind request"); + + if matches!(propfind, dav::PropFind::PropName) { + return serialize(dav::Multistatus::> { + 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())), + }); + } - let r = deserialize::>(req).await?; - println!("r: {:?}", r); - serialize(Multistatus::> { - responses: vec![], - responsedescription: Some(ResponseDescription("hello world".to_string())), - }) + let propname = match propfind { + dav::PropFind::PropName => unreachable!(), + dav::PropFind::AllProp(None) => supported_propname.clone(), + dav::PropFind::AllProp(Some(dav::Include(mut include))) => { + include.extend_from_slice(supported_propname.as_slice()); + 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::> { + 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())), + }; + + serialize(multistatus) } async fn propfind_home(user: std::sync::Arc, req: &Request) -> Result>> { @@ -263,7 +328,7 @@ async fn collections(_user: std::sync::Arc, _req: Request BoxBody { BoxBody::new(Full::new(Bytes::from(txt)).map_err(|e| match e {})) } -fn serialize(elem: T) -> Result>> { +fn serialize(elem: T) -> Result>> { let (tx, rx) = tokio::sync::mpsc::channel::(1); // Build the writer @@ -286,7 +351,7 @@ fn serialize(elem: T) -> Result tracing::debug!("fully serialized object"), Err(e) => tracing::error!(err=?e, "failed to serialize object"), @@ -308,14 +373,14 @@ fn serialize(elem: T) -> Result>(req: Request) -> Result { +async fn deserialize>(req: Request) -> Result { let stream_of_frames = BodyStream::new(req.into_body()); let stream_of_bytes = stream_of_frames .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)); let async_read = tokio_util::io::StreamReader::new(stream_of_bytes); let async_read = std::pin::pin!(async_read); - let mut rdr = dav::Reader::new(quick_xml::reader::NsReader::from_reader(async_read)).await?; + let mut rdr = dxml::Reader::new(quick_xml::reader::NsReader::from_reader(async_read)).await?; let parsed = rdr.find::().await?; Ok(parsed) } -- cgit v1.2.3