diff --git a/crates/caldav/src/calendar_object/methods.rs b/crates/caldav/src/calendar_object/methods.rs index c2dfec6..9199e3d 100644 --- a/crates/caldav/src/calendar_object/methods.rs +++ b/crates/caldav/src/calendar_object/methods.rs @@ -1,4 +1,5 @@ use crate::Error; +use crate::error::Precondition; use actix_web::HttpRequest; use actix_web::HttpResponse; use actix_web::http::header; @@ -60,16 +61,21 @@ pub async fn put_event( } = path.into_inner(); if !user.is_principal(&principal) { - return Ok(HttpResponse::Unauthorized().body("")); + return Ok(HttpResponse::Unauthorized().finish()); } let overwrite = Some(&HeaderValue::from_static("*")) != req.headers().get(header::IF_NONE_MATCH); - let object = CalendarObject::from_ics(object_id, body)?; + let object = match CalendarObject::from_ics(object_id, body) { + Ok(obj) => obj, + Err(_) => { + return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); + } + }; store .put_object(principal, calendar_id, object, overwrite) .await?; - Ok(HttpResponse::Created().body("")) + Ok(HttpResponse::Created().finish()) } diff --git a/crates/caldav/src/error.rs b/crates/caldav/src/error.rs index 8bce2c6..71116e2 100644 --- a/crates/caldav/src/error.rs +++ b/crates/caldav/src/error.rs @@ -1,6 +1,36 @@ -use actix_web::{HttpResponse, http::StatusCode}; +use actix_web::{ + HttpResponse, + http::{StatusCode, header::ContentType}, +}; +use rustical_xml::{XmlSerialize, XmlSerializeRoot}; use tracing::error; +#[derive(Debug, thiserror::Error, XmlSerialize)] +pub enum Precondition { + #[error("valid-calendar-data")] + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + ValidCalendarData, +} + +impl actix_web::ResponseError for Precondition { + fn status_code(&self) -> StatusCode { + StatusCode::PRECONDITION_FAILED + } + fn error_response(&self) -> HttpResponse { + let mut output: Vec<_> = b"\n".into(); + let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4); + + let error = rustical_dav::xml::ErrorElement(self); + if let Err(err) = error.serialize_root(&mut writer) { + return rustical_dav::Error::from(err).error_response(); + } + + HttpResponse::PreconditionFailed() + .content_type(ContentType::xml()) + .body(String::from_utf8(output).unwrap()) + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Unauthorized")] @@ -26,6 +56,9 @@ pub enum Error { #[error(transparent)] IcalError(#[from] rustical_ical::Error), + + #[error(transparent)] + PreconditionFailed(Precondition), } impl actix_web::ResponseError for Error { @@ -44,6 +77,7 @@ impl actix_web::ResponseError for Error { Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR, Error::NotFound => StatusCode::NOT_FOUND, Error::IcalError(err) => err.status_code(), + Error::PreconditionFailed(err) => err.status_code(), } } fn error_response(&self) -> actix_web::HttpResponse { @@ -51,6 +85,7 @@ impl actix_web::ResponseError for Error { match self { Error::DavError(err) => err.error_response(), Error::IcalError(err) => err.error_response(), + Error::PreconditionFailed(err) => err.error_response(), _ => HttpResponse::build(self.status_code()).body(self.to_string()), } } diff --git a/crates/dav/src/xml/error.rs b/crates/dav/src/xml/error.rs new file mode 100644 index 0000000..2f24858 --- /dev/null +++ b/crates/dav/src/xml/error.rs @@ -0,0 +1,12 @@ +use rustical_xml::{XmlRootTag, XmlSerialize}; + +#[derive(XmlSerialize, XmlRootTag)] +#[xml(ns = "crate::namespace::NS_DAV", root = b"error")] +#[xml(ns_prefix( + crate::namespace::NS_DAV = b"", + crate::namespace::NS_CARDDAV = b"CARD", + crate::namespace::NS_CALDAV = b"CAL", + crate::namespace::NS_CALENDARSERVER = b"CS", + crate::namespace::NS_DAVPUSH = b"PUSH" +))] +pub struct ErrorElement<'t, T: XmlSerialize>(#[xml(ty = "untagged")] pub &'t T); diff --git a/crates/dav/src/xml/href.rs b/crates/dav/src/xml/href.rs new file mode 100644 index 0000000..a392663 --- /dev/null +++ b/crates/dav/src/xml/href.rs @@ -0,0 +1,14 @@ +use derive_more::From; +use rustical_xml::{XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, Debug, Clone, From, PartialEq)] +pub struct HrefElement { + #[xml(ns = "crate::namespace::NS_DAV")] + pub href: String, +} + +impl HrefElement { + pub fn new(href: String) -> Self { + Self { href } + } +} diff --git a/crates/dav/src/xml/mod.rs b/crates/dav/src/xml/mod.rs index 8f6d55d..34d1012 100644 --- a/crates/dav/src/xml/mod.rs +++ b/crates/dav/src/xml/mod.rs @@ -2,22 +2,12 @@ pub mod multistatus; mod propfind; mod resourcetype; pub mod tag_list; -use derive_more::derive::From; pub use multistatus::MultistatusElement; +mod href; +pub use href::HrefElement; pub use propfind::{PropElement, PropfindElement, PropfindType}; pub use resourcetype::{Resourcetype, ResourcetypeInner}; -use rustical_xml::{XmlDeserialize, XmlSerialize}; pub use tag_list::TagList; +mod error; pub mod sync_collection; - -#[derive(XmlDeserialize, XmlSerialize, Debug, Clone, From, PartialEq)] -pub struct HrefElement { - #[xml(ns = "crate::namespace::NS_DAV")] - pub href: String, -} - -impl HrefElement { - pub fn new(href: String) -> Self { - Self { href } - } -} +pub use error::ErrorElement; diff --git a/crates/xml/derive/src/field.rs b/crates/xml/derive/src/field.rs index dab74e3..39e8aa4 100644 --- a/crates/xml/derive/src/field.rs +++ b/crates/xml/derive/src/field.rs @@ -296,6 +296,12 @@ impl Field { None => quote! { None }, }; + let target = if let syn::Type::Reference(_) = self.field.ty { + quote! { (*self.#target_field_index) } + } else { + quote! { self.#target_field_index } + }; + match (&self.attrs.xml_ty, self.attrs.flatten.is_present()) { (FieldType::Attr, _) => None, (FieldType::Text, true) => Some(quote! { @@ -309,7 +315,7 @@ impl Field { (FieldType::Tag, true) => { let field_name = self.xml_name(); Some(quote! { - for item in self.#target_field_index.iter() { + for item in #target.iter() { #serializer(item, #ns, Some(#field_name), namespaces, writer)?; } }) @@ -317,16 +323,16 @@ impl Field { (FieldType::Tag, false) => { let field_name = self.xml_name(); Some(quote! { - #serializer(&self.#target_field_index, #ns, Some(#field_name), namespaces, writer)?; + #serializer(&#target, #ns, Some(#field_name), namespaces, writer)?; }) } (FieldType::Untagged, true) => Some(quote! { - for item in self.#target_field_index.iter() { + for item in #target.iter() { #serializer(item, None, None, namespaces, writer)?; } }), (FieldType::Untagged, false) => Some(quote! { - #serializer(&self.#target_field_index, None, None, namespaces, writer)?; + #serializer(&#target, None, None, namespaces, writer)?; }), // We ignore this :) (FieldType::TagName | FieldType::Namespace, _) => None, diff --git a/crates/xml/derive/src/xml_struct/impl_se.rs b/crates/xml/derive/src/xml_struct/impl_se.rs new file mode 100644 index 0000000..e1a6b03 --- /dev/null +++ b/crates/xml/derive/src/xml_struct/impl_se.rs @@ -0,0 +1,150 @@ +use quote::quote; + +use crate::{Field, attrs::FieldType}; + +use super::NamedStruct; + +impl NamedStruct { + pub fn impl_se(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + let tag_writers: Vec<_> = self.fields.iter().filter_map(Field::tag_writer).collect(); + + let untagged_attributes = self + .fields + .iter() + .filter(|field| field.attrs.xml_ty == FieldType::Untagged) + .filter(|field| !field.attrs.flatten.is_present()) + .map(|field| { + let field_ident = field.target_field_index(); + quote! { + if let Some(attrs) = self.#field_ident.attributes() { + bytes_start.extend_attributes(attrs); + } + } + }); + + let attributes = self + .fields + .iter() + .filter(|field| field.attrs.xml_ty == FieldType::Attr) + .map(|field| { + let field_name = field.xml_name(); + let field_index = field.target_field_index(); + quote! { + ::quick_xml::events::attributes::Attribute { + key: ::quick_xml::name::QName(#field_name), + value: ::std::borrow::Cow::from(::rustical_xml::ValueSerialize::serialize(&self.#field_index).into_bytes()) + } + } + }); + + let tag_name_field = self + .fields + .iter() + .find(|field| field.attrs.xml_ty == FieldType::TagName) + .map(|field| { + let field_index = field.target_field_index(); + quote! { + let tag_str = self.#field_index.to_string(); + let tag = Some(tag.unwrap_or(tag_str.as_bytes())); + } + }); + + let namespace_field = self + .fields + .iter() + .find(|field| field.attrs.xml_ty == FieldType::Namespace) + .map(|field| { + let field_index = field.target_field_index(); + quote! { + let ns = self.#field_index; + } + }); + + let is_empty = tag_writers.is_empty(); + + // If we are the root element write the xmlns attributes + let prefix_attributes = if self.attrs.root.is_some() { + self.attrs + .ns_prefix + .iter() + .map(|(ns, prefix)| { + let sep = if !prefix.value().is_empty() { + b":".to_vec() + } else { + b"".to_vec() + }; + let attr_name = [b"xmlns".as_ref(), &sep, &prefix.value()].concat(); + let a = syn::LitByteStr::new(&attr_name, prefix.span()); + quote! { + bytes_start.push_attribute((#a.as_ref(), #ns.as_ref())); + } + }) + .collect() + } else { + vec![] + }; + + quote! { + impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { + fn serialize( + &self, + ns: Option<::quick_xml::name::Namespace>, + tag: Option<&[u8]>, + namespaces: &::std::collections::HashMap<::quick_xml::name::Namespace, &[u8]>, + writer: &mut ::quick_xml::Writer + ) -> ::std::io::Result<()> { + use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; + + #tag_name_field; + #namespace_field; + + let prefix = ns + .map(|ns| namespaces.get(&ns)) + .unwrap_or(None) + .map(|prefix| { + if !prefix.is_empty() { + [*prefix, b":"].concat() + } else { + Vec::new() + } + }); + let has_prefix = prefix.is_some(); + let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat()); + let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname)); + // + if let Some(qname) = &qname { + let mut bytes_start = BytesStart::from(qname.to_owned()); + if !has_prefix { + if let Some(ns) = &ns { + bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref())); + } + } + #(#prefix_attributes);* + if let Some(attrs) = self.attributes() { + bytes_start.extend_attributes(attrs); + } + #(#untagged_attributes);* + if #is_empty { + writer.write_event(Event::Empty(bytes_start))?; + } else { + writer.write_event(Event::Start(bytes_start))?; + } + } + if !#is_empty { + #(#tag_writers);* + if let Some(qname) = &qname { + writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?; + } + } + Ok(()) + } + + fn attributes<'a>(&self) -> Option>> { + Some(vec![ #(#attributes),* ]) + } + } + } + } +} diff --git a/crates/xml/derive/src/xml_struct.rs b/crates/xml/derive/src/xml_struct/mod.rs similarity index 56% rename from crates/xml/derive/src/xml_struct.rs rename to crates/xml/derive/src/xml_struct/mod.rs index ffee367..5071939 100644 --- a/crates/xml/derive/src/xml_struct.rs +++ b/crates/xml/derive/src/xml_struct/mod.rs @@ -1,10 +1,12 @@ use crate::Field; -use crate::attrs::{FieldType, StructAttrs}; +use crate::attrs::StructAttrs; use core::panic; use darling::FromDeriveInput; use quote::quote; use syn::{DataStruct, DeriveInput}; +mod impl_se; + fn invalid_field_branch(ident: &syn::Ident, allow: bool) -> proc_macro2::TokenStream { let ident = ident.to_string(); if allow { @@ -190,147 +192,4 @@ impl NamedStruct { } } } - - pub fn impl_se(&self) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let ident = &self.ident; - let tag_writers: Vec<_> = self.fields.iter().filter_map(Field::tag_writer).collect(); - - let untagged_attributes = self - .fields - .iter() - .filter(|field| field.attrs.xml_ty == FieldType::Untagged) - .filter(|field| !field.attrs.flatten.is_present()) - .map(|field| { - let field_ident = field.field_ident(); - quote! { - if let Some(attrs) = self.#field_ident.attributes() { - bytes_start.extend_attributes(attrs); - } - } - }); - - let attributes = self - .fields - .iter() - .filter(|field| field.attrs.xml_ty == FieldType::Attr) - .map(|field| { - let field_name = field.xml_name(); - let field_index = field.target_field_index(); - quote! { - ::quick_xml::events::attributes::Attribute { - key: ::quick_xml::name::QName(#field_name), - value: ::std::borrow::Cow::from(::rustical_xml::ValueSerialize::serialize(&self.#field_index).into_bytes()) - } - } - }); - - let tag_name_field = self - .fields - .iter() - .find(|field| field.attrs.xml_ty == FieldType::TagName) - .map(|field| { - let field_index = field.target_field_index(); - quote! { - let tag_str = self.#field_index.to_string(); - let tag = Some(tag.unwrap_or(tag_str.as_bytes())); - } - }); - - let namespace_field = self - .fields - .iter() - .find(|field| field.attrs.xml_ty == FieldType::Namespace) - .map(|field| { - let field_index = field.target_field_index(); - quote! { - let ns = self.#field_index; - } - }); - - let is_empty = tag_writers.is_empty(); - - // If we are the root element write the xmlns attributes - let prefix_attributes = if self.attrs.root.is_some() { - self.attrs - .ns_prefix - .iter() - .map(|(ns, prefix)| { - let sep = if !prefix.value().is_empty() { - b":".to_vec() - } else { - b"".to_vec() - }; - let attr_name = [b"xmlns".as_ref(), &sep, &prefix.value()].concat(); - let a = syn::LitByteStr::new(&attr_name, prefix.span()); - quote! { - bytes_start.push_attribute((#a.as_ref(), #ns.as_ref())); - } - }) - .collect() - } else { - vec![] - }; - - quote! { - impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { - fn serialize( - &self, - ns: Option<::quick_xml::name::Namespace>, - tag: Option<&[u8]>, - namespaces: &::std::collections::HashMap<::quick_xml::name::Namespace, &[u8]>, - writer: &mut ::quick_xml::Writer - ) -> ::std::io::Result<()> { - use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; - - #tag_name_field; - #namespace_field; - - let prefix = ns - .map(|ns| namespaces.get(&ns)) - .unwrap_or(None) - .map(|prefix| { - if !prefix.is_empty() { - [*prefix, b":"].concat() - } else { - Vec::new() - } - }); - let has_prefix = prefix.is_some(); - let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat()); - let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname)); - - if let Some(qname) = &qname { - let mut bytes_start = BytesStart::from(qname.to_owned()); - if !has_prefix { - if let Some(ns) = &ns { - bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref())); - } - } - #(#prefix_attributes);* - if let Some(attrs) = self.attributes() { - bytes_start.extend_attributes(attrs); - } - #(#untagged_attributes);* - if #is_empty { - writer.write_event(Event::Empty(bytes_start))?; - } else { - writer.write_event(Event::Start(bytes_start))?; - } - } - if !#is_empty { - #(#tag_writers);* - if let Some(qname) = &qname { - writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?; - } - } - Ok(()) - } - - fn attributes<'a>(&self) -> Option>> { - Some(vec![ #(#attributes),* ]) - } - } - } - } } diff --git a/crates/xml/src/de.rs b/crates/xml/src/de.rs index 6cce7f0..dd259fb 100644 --- a/crates/xml/src/de.rs +++ b/crates/xml/src/de.rs @@ -4,8 +4,8 @@ use std::io::BufRead; pub use xml_derive::XmlDeserialize; pub use xml_derive::XmlDocument; -use crate::XmlError; use crate::XmlRootTag; +use crate::{XmlError, XmlSerialize}; pub trait XmlDeserialize: Sized { fn deserialize(