xml: Strict namespace, some tests and restructuring

This commit is contained in:
Lennart
2025-01-15 19:12:54 +01:00
parent d021e7b8bf
commit 3e0571bb72
15 changed files with 176 additions and 157 deletions

View File

@@ -21,6 +21,7 @@ pub(crate) struct CalendarMultigetRequest {
#[xml(ty = "untagged")]
pub(crate) prop: PropfindType,
#[xml(flatten)]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
pub(crate) href: Vec<String>,
}

View File

@@ -19,15 +19,19 @@ use crate::{
#[allow(dead_code)]
pub(crate) struct TimeRangeElement {
#[xml(ty = "attr")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) start: Option<UtcDateTime>,
#[xml(ty = "attr")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) end: Option<UtcDateTime>,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)]
struct ParamFilterElement {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
is_not_defined: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
text_match: Option<TextMatchElement>,
#[xml(ty = "attr")]
@@ -38,18 +42,24 @@ struct ParamFilterElement {
#[allow(dead_code)]
struct TextMatchElement {
#[xml(ty = "attr")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
collation: String,
#[xml(ty = "attr")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
negate_collation: String,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub(crate) struct PropFilterElement {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
is_not_defined: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
time_range: Option<TimeRangeElement>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
text_match: Option<TextMatchElement>,
#[xml(flatten)]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
param_filter: Vec<ParamFilterElement>,
}
@@ -57,14 +67,16 @@ pub(crate) struct PropFilterElement {
#[allow(dead_code)]
// https://datatracker.ietf.org/doc/html/rfc4791#section-9.7.1
pub(crate) struct CompFilterElement {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) is_not_defined: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) time_range: Option<TimeRangeElement>,
#[xml(flatten)]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV", flatten)]
pub(crate) prop_filter: Vec<PropFilterElement>,
#[xml(flatten)]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV", flatten)]
pub(crate) comp_filter: Vec<CompFilterElement>,
#[xml(ty = "attr")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV", ty = "attr")]
pub(crate) name: String,
}
@@ -155,6 +167,7 @@ impl CompFilterElement {
#[allow(dead_code)]
// https://datatracker.ietf.org/doc/html/rfc4791#section-9.7
pub(crate) struct FilterElement {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) comp_filter: CompFilterElement,
}
@@ -170,8 +183,11 @@ impl FilterElement {
pub struct CalendarQueryRequest {
#[xml(ty = "untagged")]
pub prop: PropfindType,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) filter: Option<FilterElement>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) timezone: Option<String>,
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
pub(crate) timezone_id: Option<String>,
}

View File

@@ -5,9 +5,10 @@ use actix_web::{
};
use calendar_multiget::{handle_calendar_multiget, CalendarMultigetRequest};
use calendar_query::{handle_calendar_query, CalendarQueryRequest};
use rustical_dav::xml::sync_collection::SyncCollectionRequest;
use rustical_store::{auth::User, CalendarStore};
use rustical_xml::{XmlDeserialize, XmlDocument};
use sync_collection::{handle_sync_collection, SyncCollectionRequest};
use sync_collection::handle_sync_collection;
use tracing::instrument;
mod calendar_multiget;
@@ -16,8 +17,11 @@ mod sync_collection;
#[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)]
pub(crate) enum ReportRequest {
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarMultiget(CalendarMultigetRequest),
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarQuery(CalendarQueryRequest),
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
SyncCollection(SyncCollectionRequest),
}
@@ -133,4 +137,31 @@ mod tests {
})
)
}
#[test]
fn test_xml_calendar_multiget() {
let report_request = ReportRequest::parse_str(r#"
<?xml version="1.0" encoding="UTF-8"?>
<calendar-multiget xmlns="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<D:displayname/>
</D:prop>
<D:href>/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b</D:href>
</calendar-multiget>
"#).unwrap();
assert_eq!(
report_request,
ReportRequest::CalendarMultiget(CalendarMultigetRequest {
prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![
Propname("getetag".to_owned()),
Propname("displayname".to_owned())
])),
href: vec![
"/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned()
]
})
)
}
}

View File

@@ -1,65 +1,22 @@
use actix_web::{http::StatusCode, HttpRequest};
use rustical_dav::{
resource::{CommonPropertiesProp, EitherProp, Resource},
xml::{multistatus::ResponseElement, MultistatusElement},
xml::{PropElement, PropfindType},
xml::{
multistatus::ResponseElement, sync_collection::SyncCollectionRequest, MultistatusElement,
PropElement, PropfindType,
},
};
use rustical_store::{
auth::User,
synctoken::{format_synctoken, parse_synctoken},
CalendarStore,
};
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize};
use crate::{
calendar_object::resource::{CalendarObjectProp, CalendarObjectResource},
Error,
};
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum SyncLevel {
One,
Infinity,
}
impl ValueDeserialize for SyncLevel {
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlError> {
Ok(match val {
"1" => Self::One,
"Infinity" => Self::Infinity,
_ => {
return Err(rustical_xml::XmlError::Other(
"Invalid sync-level".to_owned(),
))
}
})
}
}
impl ValueSerialize for SyncLevel {
fn serialize(&self) -> String {
match self {
SyncLevel::One => "1",
SyncLevel::Infinity => "Infinity",
}
.to_owned()
}
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)]
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
pub(crate) struct SyncCollectionRequest {
pub(crate) sync_token: String,
pub(crate) sync_level: SyncLevel,
pub(crate) timezone: Option<String>,
#[xml(ty = "untagged")]
pub prop: PropfindType,
pub(crate) limit: Option<u64>,
}
pub async fn handle_sync_collection<C: CalendarStore>(
sync_collection: SyncCollectionRequest,
req: HttpRequest,

View File

@@ -8,26 +8,34 @@ use tracing_actix_web::RootSpan;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct Resourcetype {
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV")]
addressbook: Option<()>,
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
collection: Option<()>,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct MkcolAddressbookProp {
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
resourcetype: Option<Resourcetype>,
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
displayname: Option<String>,
#[xml(rename = b"addressbook-description")]
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV")]
description: Option<String>,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct PropElement<T: XmlDeserialize> {
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
prop: T,
}
#[derive(XmlDeserialize, XmlRootTag, Clone, Debug, PartialEq)]
#[xml(root = b"mkcol")]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
struct MkcolRequest {
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
set: PropElement<MkcolAddressbookProp>,
}

View File

@@ -16,11 +16,12 @@ use rustical_xml::XmlDeserialize;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
pub struct AddressbookMultigetRequest {
#[xml(ty = "untagged")]
prop: PropfindType,
#[xml(flatten)]
href: Vec<String>,
#[xml(ns = "rustical_dav::namespace::NS_DAV", ty = "untagged")]
pub(crate) prop: PropfindType,
#[xml(ns = "rustical_dav::namespace::NS_DAV", flatten)]
pub(crate) href: Vec<String>,
}
pub async fn get_objects_addressbook_multiget<AS: AddressbookStore>(

View File

@@ -4,9 +4,10 @@ use actix_web::{
HttpRequest, Responder,
};
use addressbook_multiget::{handle_addressbook_multiget, AddressbookMultigetRequest};
use rustical_dav::xml::sync_collection::SyncCollectionRequest;
use rustical_store::{auth::User, AddressbookStore};
use rustical_xml::{XmlDeserialize, XmlDocument};
use sync_collection::{handle_sync_collection, SyncCollectionRequest};
use sync_collection::handle_sync_collection;
use tracing::instrument;
mod addressbook_multiget;
@@ -14,7 +15,9 @@ mod sync_collection;
#[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)]
pub(crate) enum ReportRequest {
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV")]
AddressbookMultiget(AddressbookMultigetRequest),
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
SyncCollection(SyncCollectionRequest),
}
@@ -61,8 +64,7 @@ pub async fn route_report_addressbook<AS: AddressbookStore>(
#[cfg(test)]
mod tests {
use rustical_dav::xml::{PropElement, Propname};
use sync_collection::SyncLevel;
use rustical_dav::xml::{sync_collection::SyncLevel, PropElement, Propname};
use super::*;
@@ -92,4 +94,31 @@ mod tests {
})
)
}
#[test]
fn test_xml_addressbook_multiget() {
let report_request = ReportRequest::parse_str(r#"
<?xml version="1.0" encoding="UTF-8"?>
<addressbook-multiget xmlns="urn:ietf:params:xml:ns:carddav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<address-data/>
</D:prop>
<D:href>/carddav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b</D:href>
</addressbook-multiget>
"#).unwrap();
assert_eq!(
report_request,
ReportRequest::AddressbookMultiget(AddressbookMultigetRequest {
prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![
Propname("getetag".to_owned()),
Propname("address-data".to_owned())
])),
href: vec![
"/carddav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned()
]
})
)
}
}

View File

@@ -5,56 +5,16 @@ use crate::{
use actix_web::{http::StatusCode, HttpRequest};
use rustical_dav::{
resource::{CommonPropertiesProp, EitherProp, Resource},
xml::{multistatus::ResponseElement, MultistatusElement},
xml::{PropElement, PropfindType},
xml::{
multistatus::ResponseElement, sync_collection::SyncCollectionRequest, MultistatusElement,
PropElement, PropfindType,
},
};
use rustical_store::{
auth::User,
synctoken::{format_synctoken, parse_synctoken},
AddressbookStore,
};
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize};
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum SyncLevel {
One,
Infinity,
}
impl ValueDeserialize for SyncLevel {
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlError> {
Ok(match val {
"1" => Self::One,
"Infinity" => Self::Infinity,
_ => {
return Err(rustical_xml::XmlError::Other(
"Invalid sync-level".to_owned(),
))
}
})
}
}
impl ValueSerialize for SyncLevel {
fn serialize(&self) -> String {
match self {
SyncLevel::One => "1",
SyncLevel::Infinity => "Infinity",
}
.to_owned()
}
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
pub(crate) struct SyncCollectionRequest {
pub(crate) sync_token: String,
pub(crate) sync_level: SyncLevel,
#[xml(ty = "untagged")]
pub prop: PropfindType,
pub(crate) limit: Option<u64>,
}
pub async fn handle_sync_collection<AS: AddressbookStore>(
sync_collection: SyncCollectionRequest,

View File

@@ -8,6 +8,7 @@ pub use propfind::{PropElement, PropfindElement, PropfindType, Propname};
pub use resourcetype::{Resourcetype, ResourcetypeInner};
use rustical_xml::{XmlDeserialize, XmlSerialize};
pub use tag_list::TagList;
pub mod sync_collection;
#[derive(XmlDeserialize, XmlSerialize, Debug, Clone, From, PartialEq)]
pub struct HrefElement {

View File

@@ -0,0 +1,49 @@
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize};
use super::PropfindType;
#[derive(Clone, Debug, PartialEq)]
pub enum SyncLevel {
One,
Infinity,
}
impl ValueDeserialize for SyncLevel {
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlError> {
Ok(match val {
"1" => Self::One,
"Infinity" => Self::Infinity,
_ => {
return Err(rustical_xml::XmlError::Other(
"Invalid sync-level".to_owned(),
))
}
})
}
}
impl ValueSerialize for SyncLevel {
fn serialize(&self) -> String {
match self {
SyncLevel::One => "1",
SyncLevel::Infinity => "Infinity",
}
.to_owned()
}
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
#[xml(ns = "crate::namespace::NS_DAV")]
pub struct SyncCollectionRequest {
#[xml(ns = "crate::namespace::NS_DAV")]
pub sync_token: String,
#[xml(ns = "crate::namespace::NS_DAV")]
pub sync_level: SyncLevel,
#[xml(ns = "crate::namespace::NS_DAV", ty = "untagged")]
pub prop: PropfindType,
#[xml(ns = "crate::namespace::NS_DAV")]
pub limit: Option<u64>,
}

View File

@@ -3,15 +3,9 @@ use std::collections::HashMap;
use darling::{util::Flag, FromDeriveInput, FromField, FromMeta, FromVariant};
use syn::LitByteStr;
#[derive(Default, FromMeta, Clone)]
pub struct ContainerAttrs {
pub ns_strict: Flag,
}
#[derive(Default, FromMeta, Clone)]
pub struct TagAttrs {
pub rename: Option<LitByteStr>,
pub ns_strict: Flag,
pub ns: Option<syn::Path>,
}
@@ -27,18 +21,12 @@ pub struct VariantAttrs {
#[derive(Default, FromDeriveInput, Clone)]
#[darling(attributes(xml))]
pub struct EnumAttrs {
#[darling(flatten)]
// TODO: implement ns_strict
pub _container: ContainerAttrs,
pub untagged: Flag,
}
#[derive(Default, FromDeriveInput, Clone)]
#[darling(attributes(xml))]
pub struct StructAttrs {
#[darling(flatten)]
pub container: ContainerAttrs,
pub root: Option<LitByteStr>,
pub ns: Option<syn::Path>,
#[darling(default)]

View File

@@ -1,5 +1,5 @@
use super::{
attrs::{ContainerAttrs, FieldAttrs, FieldType},
attrs::{FieldAttrs, FieldType},
get_generic_type,
};
use darling::FromField;
@@ -23,20 +23,14 @@ pub struct Field {
pub field: syn::Field,
pub field_num: usize,
pub attrs: FieldAttrs,
pub container_attrs: ContainerAttrs,
}
impl Field {
pub fn from_syn_field(
field: syn::Field,
field_num: usize,
container_attrs: ContainerAttrs,
) -> Self {
pub fn from_syn_field(field: syn::Field, field_num: usize) -> Self {
Self {
attrs: FieldAttrs::from_field(&field).unwrap(),
field,
field_num,
container_attrs,
}
}
@@ -51,11 +45,6 @@ impl Field {
})
}
/// Whether to enforce the correct XML namespace
pub fn ns_strict(&self) -> bool {
self.attrs.common.ns_strict.is_present() || self.container_attrs.ns_strict.is_present()
}
/// Field identifier
pub fn field_ident(&self) -> &Option<syn::Ident> {
&self.field.ident
@@ -169,25 +158,18 @@ impl Field {
return None;
}
let namespace_match = if self.ns_strict() {
if self.attrs.common.ns.is_some() {
let namespace_match = if self.attrs.common.ns.is_some() {
quote! {quick_xml::name::ResolveResult::Bound(ns)}
} else {
quote! {quick_xml::name::ResolveResult::Unbound}
}
} else {
quote! {_}
};
let namespace_condition = if self.ns_strict() {
self.attrs
let namespace_condition = self
.attrs
.common
.ns
.as_ref()
.map(|ns| quote! { if ns == #ns })
} else {
None
};
.map(|ns| quote! { if ns == #ns });
let field_name = self.xml_name();
let builder_field_ident = self.builder_field_ident();

View File

@@ -25,9 +25,7 @@ impl NamedStruct {
.named
.iter()
.enumerate()
.map(|(i, field)| {
Field::from_syn_field(field.to_owned(), i, attrs.container.clone())
})
.map(|(i, field)| Field::from_syn_field(field.to_owned(), i))
.collect(),
attrs,
ident: input.ident.to_owned(),
@@ -38,9 +36,7 @@ impl NamedStruct {
.unnamed
.iter()
.enumerate()
.map(|(i, field)| {
Field::from_syn_field(field.to_owned(), i, attrs.container.clone())
})
.map(|(i, field)| Field::from_syn_field(field.to_owned(), i))
.collect(),
attrs,
ident: input.ident.to_owned(),

View File

@@ -81,7 +81,7 @@ fn test_tagged_enum_complex() {
nice: (),
}
let asd = Propfind::parse_str(
let _ = Propfind::parse_str(
r#"
<propfind>
<prop>

View File

@@ -154,7 +154,7 @@ fn test_struct_ns() {
const NS_HELLO: Namespace = Namespace(b"hello");
#[derive(Debug, XmlDeserialize, XmlRootTag, PartialEq)]
#[xml(root = b"document", ns_strict)]
#[xml(root = b"document")]
struct Document {
#[xml(ns = "NS_HELLO")]
child: (),
@@ -169,7 +169,7 @@ fn test_struct_attr() {
const NS_HELLO: Namespace = Namespace(b"hello");
#[derive(Debug, XmlDeserialize, XmlRootTag, PartialEq)]
#[xml(root = b"document", ns_strict)]
#[xml(root = b"document")]
struct Document {
#[xml(ns = "NS_HELLO")]
child: (),
@@ -196,12 +196,12 @@ fn test_struct_attr() {
#[test]
fn test_struct_generics() {
#[derive(XmlDeserialize, XmlRootTag)]
#[xml(root = b"document", ns_strict)]
#[xml(root = b"document")]
struct Document<T: XmlDeserialize> {
child: T,
}
let doc = Document::<Unparsed>::parse_str(
let _ = Document::<Unparsed>::parse_str(
r#"
<document>
<child>
@@ -216,12 +216,12 @@ fn test_struct_generics() {
#[test]
fn test_struct_unparsed() {
#[derive(XmlDeserialize, XmlRootTag)]
#[xml(root = b"document", ns_strict)]
#[xml(root = b"document")]
struct Document {
child: Unparsed,
}
let doc = Document::parse_str(
let _ = Document::parse_str(
r#"
<document>
<child>
@@ -236,7 +236,7 @@ fn test_struct_unparsed() {
#[test]
fn test_xml_values() {
#[derive(XmlDeserialize, XmlRootTag, PartialEq, Debug)]
#[xml(root = b"document", ns_strict)]
#[xml(root = b"document")]
struct Document {
href: String,
}