diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-04-18 16:08:10 +0200 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-04-18 16:08:10 +0200 |
commit | e2bf412337fbbfc70f67ac84fc2ee9268c0c337e (patch) | |
tree | 63933a1017db91a788b4bab8408e19d16290847c /aero-proto/src/dav/mod.rs | |
parent | 2bda8ef081d9c8f47081845bb4545a12b6ae8a18 (diff) | |
download | aerogramme-e2bf412337fbbfc70f67ac84fc2ee9268c0c337e.tar.gz aerogramme-e2bf412337fbbfc70f67ac84fc2ee9268c0c337e.zip |
Finalize refactor
Diffstat (limited to 'aero-proto/src/dav/mod.rs')
-rw-r--r-- | aero-proto/src/dav/mod.rs | 655 |
1 files changed, 8 insertions, 647 deletions
diff --git a/aero-proto/src/dav/mod.rs b/aero-proto/src/dav/mod.rs index 379e210..de2e690 100644 --- a/aero-proto/src/dav/mod.rs +++ b/aero-proto/src/dav/mod.rs @@ -1,35 +1,31 @@ mod middleware; +mod controller; mod codec; +mod node; +mod resource; use std::net::SocketAddr; use std::sync::Arc; -use anyhow::{anyhow, bail, Result}; +use anyhow::Result; use hyper::service::service_fn; -use hyper::{Request, Response, body::Bytes}; +use hyper::{Request, Response}; use hyper::server::conn::http1 as http; use hyper::rt::{Read, Write}; -use hyper::body::Incoming; use hyper_util::rt::TokioIo; use futures::stream::{FuturesUnordered, StreamExt}; use tokio::net::TcpListener; use tokio::sync::watch; use tokio_rustls::TlsAcceptor; use tokio::net::TcpStream; +use futures::future::FutureExt; use tokio::io::{AsyncRead, AsyncWrite}; use rustls_pemfile::{certs, private_key}; use aero_user::config::{DavConfig, DavUnsecureConfig}; use aero_user::login::ArcLoginProvider; -use aero_collections::{user::User, calendar::Calendar, davdag::BlobId}; -use aero_dav::types as dav; -use aero_dav::caltypes as cal; -use aero_dav::acltypes as acl; -use aero_dav::realization::{All, self as all}; -use crate::dav::codec::{serialize, deserialize, depth, text_body}; - -type ArcUser = std::sync::Arc<User>; +use crate::dav::controller::Controller; pub struct Server { bind_addr: SocketAddr, @@ -108,7 +104,7 @@ impl Server { let login = login.clone(); tracing::info!("{:?} {:?}", req.method(), req.uri()); async { - match middleware::auth(login, req, |user, request| async { router(user, request).await }.boxed()).await { + match middleware::auth(login, req, |user, request| async { Controller::route(user, request).await }.boxed()).await { Ok(v) => Ok(v), Err(e) => { tracing::error!(err=?e, "internal error"); @@ -144,641 +140,6 @@ impl Server { } } -use http_body_util::BodyExt; - -//@FIXME We should not support only BasicAuth - - -/// Path is a voluntarily feature limited -/// compared to the expressiveness of a UNIX path -/// For example getting parent with ../ is not supported, scheme is not supported, etc. -/// More complex support could be added later if needed by clients -enum Path<'a> { - Abs(Vec<&'a str>), - Rel(Vec<&'a str>), -} -impl<'a> Path<'a> { - fn new(path: &'a str) -> Result<Self> { - // This check is naive, it does not aim at detecting all fully qualified - // URL or protect from any attack, its only goal is to help debugging. - if path.starts_with("http://") || path.starts_with("https://") { - anyhow::bail!("Full URL are not supported") - } - - let path_segments: Vec<_> = path.split("/").filter(|s| *s != "" && *s != ".").collect(); - if path.starts_with("/") { - return Ok(Path::Abs(path_segments)) - } - Ok(Path::Rel(path_segments)) - } -} - -async fn router(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { - let path = req.uri().path().to_string(); - let path_segments: Vec<_> = path.split("/").filter(|s| *s != "").collect(); - let method = req.method().as_str().to_uppercase(); - - let node = match (RootNode {}).fetch(&user, &path_segments).await { - Ok(v) => v, - Err(e) => { - tracing::warn!(err=?e, "dav node fetch failed"); - return Ok(Response::builder() - .status(404) - .body(codec::text_body("Resource not found"))?) - } - }; - let response = DavResponse { node, user, req }; - - match method.as_str() { - "OPTIONS" => return Ok(Response::builder() - .status(200) - .header("DAV", "1") - .header("Allow", "HEAD,GET,PUT,OPTIONS,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK,MKCALENDAR,REPORT") - .body(codec::text_body(""))?), - "HEAD" | "GET" => { - tracing::warn!("HEAD+GET not correctly implemented"); - return Ok(Response::builder() - .status(404) - .body(codec::text_body(""))?) - }, - "PUT" => { - todo!(); - }, - "DELETE" => { - todo!(); - }, - "PROPFIND" => response.propfind().await, - "REPORT" => response.report().await, - _ => return Ok(Response::builder() - .status(501) - .body(codec::text_body("HTTP Method not implemented"))?), - } -} - -const ALLPROP: [dav::PropertyRequest<All>; 10] = [ - dav::PropertyRequest::CreationDate, - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::GetContentLanguage, - dav::PropertyRequest::GetContentLength, - dav::PropertyRequest::GetContentType, - dav::PropertyRequest::GetEtag, - dav::PropertyRequest::GetLastModified, - dav::PropertyRequest::LockDiscovery, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::SupportedLock, -]; - -// ---------- Building objects - -// ---- HTTP DAV Binding -use futures::stream::TryStreamExt; -use http_body_util::BodyStream; -use http_body_util::StreamBody; -use http_body_util::combinators::BoxBody; -use hyper::body::Frame; -use tokio_util::sync::PollSender; -use std::io::{Error, ErrorKind}; -use futures::sink::SinkExt; -use tokio_util::io::{SinkWriter, CopyToBytes}; - - - -//--- -use futures::{future, future::BoxFuture, future::FutureExt}; - -/// A DAV node should implement the following methods -/// @FIXME not satisfied by BoxFutures but I have no better idea currently -trait DavNode: Send { - // recurence, filesystem hierarchy - /// This node direct children - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>>; - /// Recursively fetch a child (progress inside the filesystem hierarchy) - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>>; - - // node properties - /// Get the path - fn path(&self, user: &ArcUser) -> String; - /// Get the supported WebDAV properties - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All>; - /// Get the values for the given properties - fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>>; - - //@FIXME maybe add etag, maybe add a way to set content - - /// Utility function to get a propname response from a node - fn response_propname(&self, user: &ArcUser) -> dav::Response<All> { - dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat( - dav::Href(self.path(user)), - vec![ - dav::PropStat { - status: dav::Status(hyper::StatusCode::OK), - prop: dav::AnyProp(self.supported_properties(user).0.into_iter().map(dav::AnyProperty::Request).collect()), - error: None, - responsedescription: None, - } - ], - ), - error: None, - location: None, - responsedescription: None - } - } - - /// Utility function to get a prop response from a node & a list of propname - fn response_props(&self, user: &ArcUser, props: dav::PropName<All>) -> dav::Response<All> { - let mut prop_desc = vec![]; - let (found, not_found): (Vec<_>, Vec<_>) = self.properties(user, props).into_iter().partition(|v| matches!(v, dav::AnyProperty::Value(_))); - - // If at least one property has been found on this object, adding a HTTP 200 propstat to - // the response - if !found.is_empty() { - prop_desc.push(dav::PropStat { - status: dav::Status(hyper::StatusCode::OK), - prop: dav::AnyProp(found), - error: None, - responsedescription: None, - }); - } - - // If at least one property can't be found on this object, adding a HTTP 404 propstat to - // the response - if !not_found.is_empty() { - prop_desc.push(dav::PropStat { - status: dav::Status(hyper::StatusCode::NOT_FOUND), - prop: dav::AnyProp(not_found), - error: None, - responsedescription: None, - }) - } - - // Build the finale response - dav::Response { - status_or_propstat: dav::StatusOrPropstat::PropStat(dav::Href(self.path(user)), prop_desc), - error: None, - location: None, - responsedescription: None - } - } -} - -struct DavResponse { - node: Box<dyn DavNode>, - user: std::sync::Arc<User>, - req: Request<Incoming>, -} -impl DavResponse { - // --- Public API --- - - /// REPORT has been first described in the "Versioning Extension" of WebDAV - /// It allows more complex queries compared to PROPFIND - /// - /// Note: current implementation is not generic at all, it is heavily tied to CalDAV. - /// A rewrite would be required to make it more generic (with the extension system that has - /// been introduced in aero-dav) - async fn report(self) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { - let status = hyper::StatusCode::from_u16(207)?; - - let report = match deserialize::<cal::Report<All>>(self.req).await { - Ok(v) => v, - Err(e) => { - tracing::error!(err=?e, "unable to decode REPORT body"); - return Ok(Response::builder() - .status(400) - .body(text_body("Bad request"))?) - } - }; - - // Multiget is really like a propfind where Depth: 0|1|Infinity is replaced by an arbitrary - // list of URLs - let multiget = match report { - cal::Report::Multiget(m) => m, - _ => return Ok(Response::builder() - .status(501) - .body(text_body("Not implemented"))?), - }; - - // Getting the list of nodes - let (mut ok_node, mut not_found) = (Vec::new(), Vec::new()); - for h in multiget.href.into_iter() { - let maybe_collected_node = match Path::new(h.0.as_str()) { - Ok(Path::Abs(p)) => RootNode{}.fetch(&self.user, p.as_slice()).await.or(Err(h)), - Ok(Path::Rel(p)) => self.node.fetch(&self.user, p.as_slice()).await.or(Err(h)), - Err(_) => Err(h), - }; - - match maybe_collected_node { - Ok(v) => ok_node.push(v), - Err(h) => not_found.push(h), - }; - } - - // Getting props - let props = match multiget.selector { - None | Some(cal::CalendarSelector::AllProp) => Some(dav::PropName(ALLPROP.to_vec())), - Some(cal::CalendarSelector::PropName) => None, - Some(cal::CalendarSelector::Prop(inner)) => Some(inner), - }; - - serialize(status, Self::multistatus(&self.user, ok_node, not_found, props)) - } - - /// PROPFIND is the standard way to fetch WebDAV properties - async fn propfind(self) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { - let depth = depth(&self.req); - if matches!(depth, dav::Depth::Infinity) { - return Ok(Response::builder() - .status(501) - .body(text_body("Depth: Infinity not implemented"))?) - } - - let status = hyper::StatusCode::from_u16(207)?; - - // 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::<dav::PropFind<All>>(self.req).await.unwrap_or_else(|_| dav::PropFind::<All>::AllProp(None)); - tracing::debug!(recv=?propfind, "inferred propfind request"); - - // Collect nodes as PROPFIND is not limited to the targeted node - let mut nodes = vec![]; - if matches!(depth, dav::Depth::One | dav::Depth::Infinity) { - nodes.extend(self.node.children(&self.user).await); - } - nodes.push(self.node); - - // Expand properties request - let propname = match propfind { - dav::PropFind::PropName => None, - dav::PropFind::AllProp(None) => Some(dav::PropName(ALLPROP.to_vec())), - dav::PropFind::AllProp(Some(dav::Include(mut include))) => { - include.extend_from_slice(&ALLPROP); - Some(dav::PropName(include)) - }, - dav::PropFind::Prop(inner) => Some(inner), - }; - - // Not Found is currently impossible considering the way we designed this function - let not_found = vec![]; - serialize(status, Self::multistatus(&self.user, nodes, not_found, propname)) - } - - // --- Internal functions --- - /// Utility function to build a multistatus response from - /// a list of DavNodes - fn multistatus(user: &ArcUser, nodes: Vec<Box<dyn DavNode>>, not_found: Vec<dav::Href>, props: Option<dav::PropName<All>>) -> dav::Multistatus<All> { - // Collect properties on existing objects - let mut responses: Vec<dav::Response<All>> = match props { - Some(props) => nodes.into_iter().map(|n| n.response_props(user, props.clone())).collect(), - None => nodes.into_iter().map(|n| n.response_propname(user)).collect(), - }; - - // Register not found objects only if relevant - if !not_found.is_empty() { - responses.push(dav::Response { - status_or_propstat: dav::StatusOrPropstat::Status(not_found, dav::Status(hyper::StatusCode::NOT_FOUND)), - error: None, - location: None, - responsedescription: None, - }); - } - - // Build response - dav::Multistatus::<All> { - responses, - responsedescription: None, - } - } -} - -#[derive(Clone)] -struct RootNode {} -impl DavNode for RootNode { - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> { - if path.len() == 0 { - let this = self.clone(); - return async { Ok(Box::new(this) as Box<dyn DavNode>) }.boxed(); - } - - if path[0] == user.username { - let child = Box::new(HomeNode {}); - return child.fetch(user, &path[1..]); - } - - async { Err(anyhow!("Not found")) }.boxed() - } - - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> { - async { vec![Box::new(HomeNode { }) as Box<dyn DavNode>] }.boxed() - } - - fn path(&self, user: &ArcUser) -> String { - "/".into() - } - - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> { - dav::PropName(vec![ - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::GetContentType, - dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)), - ]) - } - fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> { - prop.0.into_iter().map(|n| match n { - dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName("DAV Root".to_string())), - dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ - dav::ResourceType::Collection, - ])), - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), - dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)) => - dav::AnyProperty::Value(dav::Property::Extension(all::Property::Acl(acl::Property::CurrentUserPrincipal(acl::User::Authenticated(dav::Href(HomeNode{}.path(user))))))), - v => dav::AnyProperty::Request(v), - }).collect() - } -} - -#[derive(Clone)] -struct HomeNode {} -impl DavNode for HomeNode { - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> { - if path.len() == 0 { - let node = Box::new(self.clone()) as Box<dyn DavNode>; - return async { Ok(node) }.boxed() - } - - if path[0] == "calendar" { - return async { - let child = Box::new(CalendarListNode::new(user).await?); - child.fetch(user, &path[1..]).await - }.boxed(); - } - - async { Err(anyhow!("Not found")) }.boxed() - } - - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> { - async { - CalendarListNode::new(user).await - .map(|c| vec![Box::new(c) as Box<dyn DavNode>]) - .unwrap_or(vec![]) - }.boxed() - } - - fn path(&self, user: &ArcUser) -> String { - format!("/{}/", user.username) - } - - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> { - dav::PropName(vec![ - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::GetContentType, - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)), - ]) - } - fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> { - prop.0.into_iter().map(|n| match n { - dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} home", user.username))), - dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ - dav::ResourceType::Collection, - dav::ResourceType::Extension(all::ResourceType::Acl(acl::ResourceType::Principal)), - ])), - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)) => - dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::CalendarHomeSet(dav::Href(/*CalendarListNode{}.path(user)*/ todo!()))))), - v => dav::AnyProperty::Request(v), - }).collect() - } -} - -#[derive(Clone)] -struct CalendarListNode { - list: Vec<String>, -} -impl CalendarListNode { - async fn new(user: &ArcUser) -> Result<Self> { - let list = user.calendars.list(user).await?; - Ok(Self { list }) - } -} -impl DavNode for CalendarListNode { - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> { - if path.len() == 0 { - let node = Box::new(self.clone()) as Box<dyn DavNode>; - return async { Ok(node) }.boxed(); - } - - async { - let cal = user.calendars.open(user, path[0]).await?.ok_or(anyhow!("Not found"))?; - let child = Box::new(CalendarNode { - col: cal, - calname: path[0].to_string() - }); - child.fetch(user, &path[1..]).await - }.boxed() - } - - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> { - let list = self.list.clone(); - async move { - //@FIXME maybe we want to be lazy here?! - futures::stream::iter(list.iter()) - .filter_map(|name| async move { - user.calendars.open(user, name).await - .ok() - .flatten() - .map(|v| (name, v)) - }) - .map(|(name, cal)| Box::new(CalendarNode { - col: cal, - calname: name.to_string(), - }) as Box<dyn DavNode>) - .collect::<Vec<Box<dyn DavNode>>>() - .await - }.boxed() - } - - fn path(&self, user: &ArcUser) -> String { - format!("/{}/calendar/", user.username) - } - - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> { - dav::PropName(vec![ - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::GetContentType, - ]) - } - fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> { - prop.0.into_iter().map(|n| match n { - dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} calendars", user.username))), - dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), - v => dav::AnyProperty::Request(v), - }).collect() - } -} - -#[derive(Clone)] -struct CalendarNode { - col: Arc<Calendar>, - calname: String, -} -impl DavNode for CalendarNode { - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> { - if path.len() == 0 { - let node = Box::new(self.clone()) as Box<dyn DavNode>; - return async { Ok(node) }.boxed() - } - - let col = self.col.clone(); - let calname = self.calname.clone(); - async move { - if let Some(blob_id) = col.dag().await.idx_by_filename.get(path[0]) { - let child = Box::new(EventNode { - col: col.clone(), - calname, - filename: path[0].to_string(), - blob_id: *blob_id, - }); - return child.fetch(user, &path[1..]).await - } - - Err(anyhow!("Not found")) - }.boxed() - } - - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> { - let col = self.col.clone(); - let calname = self.calname.clone(); - - async move { - col.dag().await.idx_by_filename.iter().map(|(filename, blob_id)| { - Box::new(EventNode { - col: col.clone(), - calname: calname.clone(), - filename: filename.to_string(), - blob_id: *blob_id, - }) as Box<dyn DavNode> - }).collect() - }.boxed() - } - - fn path(&self, user: &ArcUser) -> String { - format!("/{}/calendar/{}/", user.username, self.calname) - } - - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> { - dav::PropName(vec![ - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::GetContentType, - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet)), - ]) - } - fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> { - prop.0.into_iter().map(|n| match n { - dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} calendar", self.calname))), - dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ - dav::ResourceType::Collection, - dav::ResourceType::Extension(all::ResourceType::Cal(cal::ResourceType::Calendar)), - ])), - //dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), - //@FIXME seems wrong but seems to be what Thunderbird expects... - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("text/calendar".into())), - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet)) - => dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::SupportedCalendarComponentSet(vec![ - cal::CompSupport(cal::Component::VEvent), - ])))), - v => dav::AnyProperty::Request(v), - }).collect() - } -} - -const FAKE_ICS: &str = r#"BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Example Corp.//CalDAV Client//EN -BEGIN:VTIMEZONE -LAST-MODIFIED:20040110T032845Z -TZID:US/Eastern -BEGIN:DAYLIGHT -DTSTART:20000404T020000 -RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 -TZNAME:EDT -TZOFFSETFROM:-0500 -TZOFFSETTO:-0400 -END:DAYLIGHT -BEGIN:STANDARD -DTSTART:20001026T020000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:EST -TZOFFSETFROM:-0400 -TZOFFSETTO:-0500 -END:STANDARD -END:VTIMEZONE -BEGIN:VEVENT -DTSTAMP:20240406T001102Z -DTSTART;TZID=US/Eastern:20240406T100000 -DURATION:PT1H -SUMMARY:Event #1 -Description:Go Steelers! -UID:74855313FA803DA593CD579A@example.com -END:VEVENT -END:VCALENDAR"#; - -#[derive(Clone)] -struct EventNode { - col: Arc<Calendar>, - calname: String, - filename: String, - blob_id: BlobId, -} -impl DavNode for EventNode { - fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> { - if path.len() == 0 { - let node = Box::new(self.clone()) as Box<dyn DavNode>; - return async { Ok(node) }.boxed() - } - - async { Err(anyhow!("Not found")) }.boxed() - } - - fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> { - async { vec![] }.boxed() - } - - fn path(&self, user: &ArcUser) -> String { - format!("/{}/calendar/{}/{}", user.username, self.calname, self.filename) - } - - fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> { - dav::PropName(vec![ - dav::PropertyRequest::DisplayName, - dav::PropertyRequest::ResourceType, - dav::PropertyRequest::GetEtag, - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(cal::CalendarDataRequest::default()))), - ]) - } - fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> { - prop.0.into_iter().map(|n| match n { - dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} event", self.filename))), - dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![])), - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("text/calendar".into())), - dav::PropertyRequest::GetEtag => dav::AnyProperty::Value(dav::Property::GetEtag("\"abcdefg\"".into())), - dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(req))) => - dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::CalendarData(cal::CalendarDataPayload { - mime: None, - payload: FAKE_ICS.into() - })))), - v => dav::AnyProperty::Request(v), - }).collect() - } -} - - - // <D:propfind xmlns:D='DAV:' xmlns:A='http://apple.com/ns/ical/'> // <D:prop> // <D:getcontenttype/> |