diff options
-rw-r--r-- | aero-dav/src/caldecoder.rs | 9 | ||||
-rw-r--r-- | aero-dav/src/calencoder.rs | 8 | ||||
-rw-r--r-- | aero-dav/src/caltypes.rs | 26 | ||||
-rw-r--r-- | aero-proto/src/dav.rs | 21 |
4 files changed, 57 insertions, 7 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index dbc6e18..b124154 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -162,6 +162,11 @@ impl QRead<Violation> for Violation { impl QRead<Property> for Property { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + if xml.maybe_open_start(CAL_URN, "calendar-home-set").await?.is_some() { + let href = xml.find().await?; + xml.close().await?; + return Ok(Property::CalendarHomeSet(href)) + } if xml.maybe_open_start(CAL_URN, "calendar-description").await?.is_some() { let lang = xml.prev_attr("xml:lang"); let text = xml.tag_string().await?; @@ -231,6 +236,10 @@ impl QRead<Property> for Property { impl QRead<PropertyRequest> for PropertyRequest { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + if xml.maybe_open(CAL_URN, "calendar-home-set").await?.is_some() { + xml.close().await?; + return Ok(Self::CalendarHomeSet) + } if xml.maybe_open(CAL_URN, "calendar-description").await?.is_some() { xml.close().await?; return Ok(Self::CalendarDescription) diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index 5323229..d4e79dc 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -88,6 +88,7 @@ impl QWrite for PropertyRequest { }; match self { + Self::CalendarHomeSet => atom("calendar-home-set").await, Self::CalendarDescription => atom("calendar-description").await, Self::CalendarTimezone => atom("calendar-timezone").await, Self::SupportedCalendarComponentSet => atom("supported-calendar-component-set").await, @@ -105,6 +106,13 @@ impl QWrite for PropertyRequest { impl QWrite for Property { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { match self { + Self::CalendarHomeSet(href) => { + let start = xml.create_cal_element("calendar-home-set"); + let end = start.to_end(); + xml.q.write_event_async(Event::Start(start.clone())).await?; + href.qwrite(xml).await?; + xml.q.write_event_async(Event::End(end)).await + } Self::CalendarDescription { lang, text } => { let mut start = xml.create_cal_element("calendar-description"); if let Some(the_lang) = lang { diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs index 5ac50e6..aa056d4 100644 --- a/aero-dav/src/caltypes.rs +++ b/aero-dav/src/caltypes.rs @@ -116,6 +116,7 @@ pub enum ResourceType { /// Check the matching Property object for documentation #[derive(Debug, PartialEq, Clone)] pub enum PropertyRequest { + CalendarHomeSet, CalendarDescription, CalendarTimezone, SupportedCalendarComponentSet, @@ -131,6 +132,31 @@ pub enum PropertyRequest { #[derive(Debug, PartialEq, Clone)] pub enum Property { + /// Name: calendar-home-set + /// + /// Namespace: urn:ietf:params:xml:ns:caldav + /// + /// Purpose: Identifies the URL of any WebDAV collections that contain + /// calendar collections owned by the associated principal resource. + /// + /// Conformance: This property SHOULD be defined on a principal + /// resource. If defined, it MAY be protected and SHOULD NOT be + /// returned by a PROPFIND DAV:allprop request (as defined in Section + /// 12.14.1 of [RFC2518]). + /// + /// Description: The CALDAV:calendar-home-set property is meant to allow + /// users to easily find the calendar collections owned by the + /// principal. Typically, users will group all the calendar + /// collections that they own under a common collection. This + /// property specifies the URL of collections that are either calendar + /// collections or ordinary collections that have child or descendant + /// calendar collections owned by the principal. + /// + /// Definition: + /// + /// <!ELEMENT calendar-home-set (DAV:href*)> + CalendarHomeSet(dav::Href), + /// Name: calendar-description /// /// Namespace: urn:ietf:params:xml:ns:caldav diff --git a/aero-proto/src/dav.rs b/aero-proto/src/dav.rs index b964760..c8e534e 100644 --- a/aero-proto/src/dav.rs +++ b/aero-proto/src/dav.rs @@ -212,7 +212,7 @@ async fn router(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<Re "HEAD" | "GET" => { tracing::warn!("HEAD+GET not correctly implemented"); return Ok(Response::builder() - .status(200) + .status(404) .body(text_body(""))?) }, "PROPFIND" => propfind(user, req, node).await, @@ -259,6 +259,7 @@ const ALLPROP: [dav::PropertyRequest<Calendar>; 10] = [ async fn propfind(user: std::sync::Arc<User>, req: Request<Incoming>, node: Box<dyn DavNode>) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { let depth = depth(&req); + 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. @@ -268,7 +269,7 @@ async fn propfind(user: std::sync::Arc<User>, req: Request<Incoming>, node: Box< tracing::debug!(recv=?propfind, "inferred propfind request"); if matches!(propfind, dav::PropFind::PropName) { - return serialize(node.multistatus_name(&user, depth)); + return serialize(status, node.multistatus_name(&user, depth)); } let propname = match propfind { @@ -281,7 +282,7 @@ async fn propfind(user: std::sync::Arc<User>, req: Request<Incoming>, node: Box< dav::PropFind::Prop(inner) => inner, }; - serialize(node.multistatus_val(&user, propname, depth)) + serialize(status, node.multistatus_val(&user, propname, depth)) } // ---- HTTP DAV Binding @@ -308,7 +309,7 @@ fn text_body(txt: &'static str) -> BoxBody<Bytes, std::io::Error> { BoxBody::new(Full::new(Bytes::from(txt)).map_err(|e| match e {})) } -fn serialize<T: dxml::QWrite + Send + 'static>(elem: T) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { +fn serialize<T: dxml::QWrite + Send + 'static>(status_ok: hyper::StatusCode, elem: T) -> Result<Response<BoxBody<Bytes, std::io::Error>>> { let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(1); // Build the writer @@ -318,7 +319,7 @@ 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()), ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".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)); + let decl = quick_xml::events::BytesDecl::from_start(quick_xml::events::BytesStart::from_content("xml version=\"1.0\" encoding=\"utf-8\"", 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 ... >"), @@ -336,7 +337,8 @@ fn serialize<T: dxml::QWrite + Send + 'static>(elem: T) -> Result<Response<BoxBo let boxed_body = BoxBody::new(stream); let response = Response::builder() - .status(hyper::StatusCode::OK) + .status(status_ok) + .header("content-type", "application/xml; charset=\"utf-8\"") .body(boxed_body)?; Ok(response) @@ -510,6 +512,7 @@ impl DavNode for HomeNode { dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetContentType, + dav::PropertyRequest::Extension(cal::PropertyRequest::CalendarHomeSet), ]) } fn properties(&self, user: &ArcUser, prop: dav::PropName<Calendar>) -> Vec<dav::AnyProperty<Calendar>> { @@ -517,6 +520,8 @@ impl DavNode for HomeNode { 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::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), + dav::PropertyRequest::Extension(cal::PropertyRequest::CalendarHomeSet) => + dav::AnyProperty::Value(dav::Property::Extension(cal::Property::CalendarHomeSet(dav::Href(CalendarListNode{}.path(user))))), v => dav::AnyProperty::Request(v), }).collect() } @@ -604,7 +609,9 @@ impl DavNode for CalendarNode { dav::ResourceType::Collection, dav::ResourceType::Extension(cal::ResourceType::Calendar), ])), - dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), + //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())), v => dav::AnyProperty::Request(v), }).collect() } |