diff options
Diffstat (limited to 'aero-dav/src/decoder.rs')
-rw-r--r-- | aero-dav/src/decoder.rs | 947 |
1 files changed, 947 insertions, 0 deletions
diff --git a/aero-dav/src/decoder.rs b/aero-dav/src/decoder.rs new file mode 100644 index 0000000..65cb712 --- /dev/null +++ b/aero-dav/src/decoder.rs @@ -0,0 +1,947 @@ +use std::future::Future; + +use quick_xml::events::Event; +use quick_xml::events::attributes::AttrError; +use quick_xml::name::{Namespace, QName, PrefixDeclaration, ResolveResult, ResolveResult::*}; +use quick_xml::reader::NsReader; +use tokio::io::AsyncBufRead; + +use super::types::*; +use super::error::ParsingError; +use super::xml::{Node, QRead, Reader, IRead, DAV_URN, CAL_URN}; + +//@TODO (1) Rewrite all objects as Href, +// where we return Ok(None) instead of trying to find the object at any cost. +// Add a xml.find<E: Qread>() -> Result<Option<E>, ParsingError> or similar for the cases we +// really need the object +// (2) Rewrite QRead and replace Result<Option<_>, _> with Result<_, _>, not found being a possible +// error. +// (3) Rewrite vectors with xml.collect<E: QRead>() -> Result<Vec<E>, _> +// (4) Something for alternatives would be great but no idea yet + +// ---- ROOT ---- + +/// Propfind request +impl<E: Extension> QRead<PropFind<E>> for PropFind<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "propfind").await?; + let propfind: PropFind<E> = loop { + // allprop + if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? { + let includ = xml.maybe_find::<Include<E>>().await?; + xml.close().await?; + break PropFind::AllProp(includ) + } + + // propname + if let Some(_) = xml.maybe_open(DAV_URN, "propname").await? { + xml.close().await?; + break PropFind::PropName + } + + // prop + let (mut maybe_prop, mut dirty) = (None, false); + xml.maybe_read::<PropName<E>>(&mut maybe_prop, &mut dirty).await?; + if let Some(prop) = maybe_prop { + break PropFind::Prop(prop) + } + + // not found, skipping + xml.skip().await?; + }; + xml.close().await?; + + Ok(propfind) + } +} + +/// PROPPATCH request +impl<E: Extension> QRead<PropertyUpdate<E>> for PropertyUpdate<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "propertyupdate").await?; + let collected_items = xml.collect::<PropertyUpdateItem<E>>().await?; + xml.close().await?; + Ok(PropertyUpdate(collected_items)) + } +} + +/// Generic response +impl<E: Extension, N: Node<N>> QRead<Multistatus<E,N>> for Multistatus<E,N> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "multistatus").await?; + let mut responses = Vec::new(); + let mut responsedescription = None; + + loop { + let mut dirty = false; + xml.maybe_push(&mut responses, &mut dirty).await?; + xml.maybe_read(&mut responsedescription, &mut dirty).await?; + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => xml.skip().await?, + }; + } + } + + xml.close().await?; + Ok(Multistatus { responses, responsedescription }) + } +} + +// LOCK REQUEST +impl QRead<LockInfo> for LockInfo { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "lockinfo").await?; + let (mut m_scope, mut m_type, mut owner) = (None, None, None); + loop { + let mut dirty = false; + xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?; + xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?; + xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?; + + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => xml.skip().await?, + }; + } + } + xml.close().await?; + match (m_scope, m_type) { + (Some(lockscope), Some(locktype)) => Ok(LockInfo { lockscope, locktype, owner }), + _ => Err(ParsingError::MissingChild), + } + } +} + +// LOCK RESPONSE +impl<E: Extension> QRead<PropValue<E>> for PropValue<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "prop").await?; + let mut acc = xml.collect::<Property<E>>().await?; + xml.close().await?; + Ok(PropValue(acc)) + } +} + + +/// Error response +impl<E: Extension> QRead<Error<E>> for Error<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "error").await?; + let violations = xml.collect::<Violation<E>>().await?; + xml.close().await?; + Ok(Error(violations)) + } +} + + + +// ---- INNER XML +impl<E: Extension, N: Node<N>> QRead<Response<E,N>> for Response<E,N> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "response").await?; + let (mut status, mut error, mut responsedescription, mut location) = (None, None, None, None); + let mut href = Vec::new(); + let mut propstat = Vec::new(); + + loop { + let mut dirty = false; + xml.maybe_read::<Status>(&mut status, &mut dirty).await?; + xml.maybe_push::<Href>(&mut href, &mut dirty).await?; + xml.maybe_push::<PropStat<E,N>>(&mut propstat, &mut dirty).await?; + xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; + xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; + xml.maybe_read::<Location>(&mut location, &mut dirty).await?; + + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => { xml.skip().await? }, + }; + } + } + + xml.close().await?; + match (status, &propstat[..], &href[..]) { + (Some(status), &[], &[_, ..]) => Ok(Response { + status_or_propstat: StatusOrPropstat::Status(href, status), + error, responsedescription, location, + }), + (None, &[_, ..], &[_, ..]) => Ok(Response { + status_or_propstat: StatusOrPropstat::PropStat(href.into_iter().next().unwrap(), propstat), + error, responsedescription, location, + }), + (Some(_), &[_, ..], _) => Err(ParsingError::InvalidValue), + _ => Err(ParsingError::MissingChild), + } + } +} + +impl<E: Extension, N: Node<N>> QRead<PropStat<E,N>> for PropStat<E,N> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "propstat").await?; + + let (mut m_prop, mut m_status, mut error, mut responsedescription) = (None, None, None, None); + + loop { + let mut dirty = false; + xml.maybe_read::<N>(&mut m_prop, &mut dirty).await?; + xml.maybe_read::<Status>(&mut m_status, &mut dirty).await?; + xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; + xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; + + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => xml.skip().await?, + }; + } + } + + xml.close().await?; + match (m_prop, m_status) { + (Some(prop), Some(status)) => Ok(PropStat { prop, status, error, responsedescription }), + _ => Err(ParsingError::MissingChild), + } + } +} + +impl QRead<Status> for Status { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "status").await?; + let fullcode = xml.tag_string().await?; + let txtcode = fullcode.splitn(3, ' ').nth(1).ok_or(ParsingError::InvalidValue)?; + let code = http::status::StatusCode::from_bytes(txtcode.as_bytes()).or(Err(ParsingError::InvalidValue))?; + xml.close().await?; + Ok(Status(code)) + } +} + +impl QRead<ResponseDescription> for ResponseDescription { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "responsedescription").await?; + let cnt = xml.tag_string().await?; + xml.close().await?; + Ok(ResponseDescription(cnt)) + } +} + +impl QRead<Location> for Location { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "location").await?; + let href = xml.find::<Href>().await?; + xml.close().await?; + Ok(Location(href)) + } +} + +impl<E: Extension> QRead<PropertyUpdateItem<E>> for PropertyUpdateItem<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + match Remove::qread(xml).await { + Err(ParsingError::Recoverable) => (), + otherwise => return otherwise.map(PropertyUpdateItem::Remove), + } + Set::qread(xml).await.map(PropertyUpdateItem::Set) + } +} + +impl<E: Extension> QRead<Remove<E>> for Remove<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "remove").await?; + let propname = xml.find::<PropName<E>>().await?; + xml.close().await?; + Ok(Remove(propname)) + } +} + +impl<E: Extension> QRead<Set<E>> for Set<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "set").await?; + let propvalue = xml.find::<PropValue<E>>().await?; + xml.close().await?; + Ok(Set(propvalue)) + } +} + +impl<E: Extension> QRead<Violation<E>> for Violation<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + if xml.maybe_open(DAV_URN, "lock-token-matches-request-uri").await?.is_some() { + xml.close().await?; + Ok(Violation::LockTokenMatchesRequestUri) + } else if xml.maybe_open(DAV_URN, "lock-token-submitted").await?.is_some() { + let links = xml.collect::<Href>().await?; + xml.close().await?; + Ok(Violation::LockTokenSubmitted(links)) + } else if xml.maybe_open(DAV_URN, "no-conflicting-lock").await?.is_some() { + let links = xml.collect::<Href>().await?; + xml.close().await?; + Ok(Violation::NoConflictingLock(links)) + } else if xml.maybe_open(DAV_URN, "no-external-entities").await?.is_some() { + xml.close().await?; + Ok(Violation::NoExternalEntities) + } else if xml.maybe_open(DAV_URN, "preserved-live-properties").await?.is_some() { + xml.close().await?; + Ok(Violation::PreservedLiveProperties) + } else if xml.maybe_open(DAV_URN, "propfind-finite-depth").await?.is_some() { + xml.close().await?; + Ok(Violation::PropfindFiniteDepth) + } else if xml.maybe_open(DAV_URN, "cannot-modify-protected-property").await?.is_some() { + xml.close().await?; + Ok(Violation::CannotModifyProtectedProperty) + } else { + E::Error::qread(xml).await.map(Violation::Extension) + } + } +} + +impl<E: Extension> QRead<Include<E>> for Include<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "include").await?; + let acc = xml.collect::<PropertyRequest<E>>().await?; + xml.close().await?; + Ok(Include(acc)) + } +} + +impl<E: Extension> QRead<PropName<E>> for PropName<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "prop").await?; + let acc = xml.collect::<PropertyRequest<E>>().await?; + xml.close().await?; + Ok(PropName(acc)) + } +} + +impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + let maybe = if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { + Some(PropertyRequest::CreationDate) + } else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() { + Some(PropertyRequest::DisplayName) + } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() { + Some(PropertyRequest::GetContentLanguage) + } else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() { + Some(PropertyRequest::GetContentLength) + } else if xml.maybe_open(DAV_URN, "getcontenttype").await?.is_some() { + Some(PropertyRequest::GetContentType) + } else if xml.maybe_open(DAV_URN, "getetag").await?.is_some() { + Some(PropertyRequest::GetEtag) + } else if xml.maybe_open(DAV_URN, "getlastmodified").await?.is_some() { + Some(PropertyRequest::GetLastModified) + } else if xml.maybe_open(DAV_URN, "lockdiscovery").await?.is_some() { + Some(PropertyRequest::LockDiscovery) + } else if xml.maybe_open(DAV_URN, "resourcetype").await?.is_some() { + Some(PropertyRequest::ResourceType) + } else if xml.maybe_open(DAV_URN, "supportedlock").await?.is_some() { + Some(PropertyRequest::SupportedLock) + } else { + None + }; + + match maybe { + Some(pr) => { + xml.close().await?; + Ok(pr) + }, + None => E::PropertyRequest::qread(xml).await.map(PropertyRequest::Extension), + } + } +} + +impl<E: Extension> QRead<Property<E>> for Property<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + use chrono::{DateTime, FixedOffset, TimeZone}; + + // Core WebDAV properties + if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { + let datestr = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::CreationDate(DateTime::parse_from_rfc3339(datestr.as_str())?)) + } else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() { + let name = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::DisplayName(name)) + } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() { + let lang = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::GetContentLanguage(lang)) + } else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() { + let cl = xml.tag_string().await?.parse::<u64>()?; + xml.close().await?; + return Ok(Property::GetContentLength(cl)) + } else if xml.maybe_open(DAV_URN, "getcontenttype").await?.is_some() { + let ct = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::GetContentType(ct)) + } else if xml.maybe_open(DAV_URN, "getetag").await?.is_some() { + let etag = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::GetEtag(etag)) + } else if xml.maybe_open(DAV_URN, "getlastmodified").await?.is_some() { + let datestr = xml.tag_string().await?; + xml.close().await?; + return Ok(Property::GetLastModified(DateTime::parse_from_rfc2822(datestr.as_str())?)) + } else if xml.maybe_open(DAV_URN, "lockdiscovery").await?.is_some() { + let acc = xml.collect::<ActiveLock>().await?; + xml.close().await?; + return Ok(Property::LockDiscovery(acc)) + } else if xml.maybe_open(DAV_URN, "resourcetype").await?.is_some() { + let acc = xml.collect::<ResourceType<E>>().await?; + xml.close().await?; + return Ok(Property::ResourceType(acc)) + } else if xml.maybe_open(DAV_URN, "supportedlock").await?.is_some() { + let acc = xml.collect::<LockEntry>().await?; + xml.close().await?; + return Ok(Property::SupportedLock(acc)) + } + + // Option 2: an extension property, delegating + E::Property::qread(xml).await.map(Property::Extension) + } +} + +impl QRead<ActiveLock> for ActiveLock { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "activelock").await?; + let (mut m_scope, mut m_type, mut m_depth, mut owner, mut timeout, mut locktoken, mut m_root) = + (None, None, None, None, None, None, None); + + loop { + let mut dirty = false; + xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?; + xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?; + xml.maybe_read::<Depth>(&mut m_depth, &mut dirty).await?; + xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?; + xml.maybe_read::<Timeout>(&mut timeout, &mut dirty).await?; + xml.maybe_read::<LockToken>(&mut locktoken, &mut dirty).await?; + xml.maybe_read::<LockRoot>(&mut m_root, &mut dirty).await?; + + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => { xml.skip().await?; }, + } + } + } + + xml.close().await?; + match (m_scope, m_type, m_depth, m_root) { + (Some(lockscope), Some(locktype), Some(depth), Some(lockroot)) => + Ok(ActiveLock { lockscope, locktype, depth, owner, timeout, locktoken, lockroot }), + _ => Err(ParsingError::MissingChild), + } + } +} + +impl QRead<Depth> for Depth { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "depth").await?; + let depth_str = xml.tag_string().await?; + xml.close().await?; + match depth_str.as_str() { + "0" => Ok(Depth::Zero), + "1" => Ok(Depth::One), + "infinity" => Ok(Depth::Infinity), + _ => Err(ParsingError::WrongToken), + } + } +} + +impl QRead<Owner> for Owner { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "owner").await?; + + let mut owner = Owner::Unknown; + loop { + match xml.peek() { + Event::Text(_) | Event::CData(_) => { + let txt = xml.tag_string().await?; + if matches!(owner, Owner::Unknown) { + owner = Owner::Txt(txt); + } + } + Event::Start(_) | Event::Empty(_) => { + match Href::qread(xml).await { + Ok(href) => { owner = Owner::Href(href); }, + Err(ParsingError::Recoverable) => { xml.skip().await?; }, + Err(e) => return Err(e), + } + } + Event::End(_) => break, + _ => { xml.skip().await?; }, + } + }; + xml.close().await?; + Ok(owner) + } +} + +impl QRead<Timeout> for Timeout { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + const SEC_PFX: &str = "SEC_PFX"; + xml.open(DAV_URN, "timeout").await?; + + let timeout = match xml.tag_string().await?.as_str() { + "Infinite" => Timeout::Infinite, + seconds => match seconds.strip_prefix(SEC_PFX) { + Some(secs) => Timeout::Seconds(secs.parse::<u32>()?), + None => return Err(ParsingError::InvalidValue), + }, + }; + + xml.close().await?; + Ok(timeout) + } +} + +impl QRead<LockToken> for LockToken { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "locktoken").await?; + let href = Href::qread(xml).await?; + xml.close().await?; + Ok(LockToken(href)) + } +} + +impl QRead<LockRoot> for LockRoot { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "lockroot").await?; + let href = Href::qread(xml).await?; + xml.close().await?; + Ok(LockRoot(href)) + } +} + +impl<E: Extension> QRead<ResourceType<E>> for ResourceType<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + if xml.maybe_open(DAV_URN, "collection").await?.is_some() { + xml.close().await?; + return Ok(ResourceType::Collection) + } + + E::ResourceType::qread(xml).await.map(ResourceType::Extension) + } +} + +impl QRead<LockEntry> for LockEntry { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "lockentry").await?; + let (mut maybe_scope, mut maybe_type) = (None, None); + + loop { + let mut dirty = false; + xml.maybe_read::<LockScope>(&mut maybe_scope, &mut dirty).await?; + xml.maybe_read::<LockType>(&mut maybe_type, &mut dirty).await?; + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => xml.skip().await?, + }; + } + } + + xml.close().await?; + match (maybe_scope, maybe_type) { + (Some(lockscope), Some(locktype)) => Ok(LockEntry { lockscope, locktype }), + _ => Err(ParsingError::MissingChild), + } + } +} + +impl QRead<LockScope> for LockScope { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "lockscope").await?; + + let lockscope = loop { + if xml.maybe_open(DAV_URN, "exclusive").await?.is_some() { + xml.close().await?; + break LockScope::Exclusive + } else if xml.maybe_open(DAV_URN, "shared").await?.is_some() { + xml.close().await?; + break LockScope::Shared + } + + xml.skip().await?; + }; + + xml.close().await?; + Ok(lockscope) + } +} + +impl QRead<LockType> for LockType { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "locktype").await?; + + let locktype = loop { + if xml.maybe_open(DAV_URN, "write").await?.is_some() { + xml.close().await?; + break LockType::Write + } + + xml.skip().await?; + }; + + xml.close().await?; + Ok(locktype) + } +} + +impl QRead<Href> for Href { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "href").await?; + let mut url = xml.tag_string().await?; + xml.close().await?; + Ok(Href(url)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::{FixedOffset, DateTime, TimeZone, Utc}; + use crate::realization::Core; + + #[tokio::test] + async fn basic_propfind_propname() { + let src = r#"<?xml version="1.0" encoding="utf-8" ?> +<rando/> +<garbage><old/></garbage> +<D:propfind xmlns:D="DAV:"> + <D:propname/> +</D:propfind> +"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<PropFind::<Core>>().await.unwrap(); + + assert_eq!(got, PropFind::<Core>::PropName); + } + + #[tokio::test] + async fn basic_propfind_prop() { + let src = r#"<?xml version="1.0" encoding="utf-8" ?> +<rando/> +<garbage><old/></garbage> +<D:propfind xmlns:D="DAV:"> + <D:prop> + <D:displayname/> + <D:getcontentlength/> + <D:getcontenttype/> + <D:getetag/> + <D:getlastmodified/> + <D:resourcetype/> + <D:supportedlock/> + </D:prop> +</D:propfind> +"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<PropFind::<Core>>().await.unwrap(); + + assert_eq!(got, PropFind::Prop(PropName(vec![ + PropertyRequest::DisplayName, + PropertyRequest::GetContentLength, + PropertyRequest::GetContentType, + PropertyRequest::GetEtag, + PropertyRequest::GetLastModified, + PropertyRequest::ResourceType, + PropertyRequest::SupportedLock, + ]))); + } + + #[tokio::test] + async fn rfc_lock_error() { + let src = r#"<?xml version="1.0" encoding="utf-8" ?> + <D:error xmlns:D="DAV:"> + <D:lock-token-submitted> + <D:href>/locked/</D:href> + </D:lock-token-submitted> + </D:error>"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<Error::<Core>>().await.unwrap(); + + assert_eq!(got, Error(vec![ + Violation::LockTokenSubmitted(vec![ + Href("/locked/".into()) + ]) + ])); + } + + + #[tokio::test] + async fn rfc_propertyupdate() { + let src = r#"<?xml version="1.0" encoding="utf-8" ?> + <D:propertyupdate xmlns:D="DAV:" + xmlns:Z="http://ns.example.com/standards/z39.50/"> + <D:set> + <D:prop> + <Z:Authors> + <Z:Author>Jim Whitehead</Z:Author> + <Z:Author>Roy Fielding</Z:Author> + </Z:Authors> + </D:prop> + </D:set> + <D:remove> + <D:prop><Z:Copyright-Owner/></D:prop> + </D:remove> + </D:propertyupdate>"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<PropertyUpdate::<Core>>().await.unwrap(); + + assert_eq!(got, PropertyUpdate(vec![ + PropertyUpdateItem::Set(Set(PropValue(vec![]))), + PropertyUpdateItem::Remove(Remove(PropName(vec![]))), + ])); + } + + #[tokio::test] + async fn rfc_lockinfo() { + let src = r#" +<?xml version="1.0" encoding="utf-8" ?> +<D:lockinfo xmlns:D='DAV:'> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo> +"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<LockInfo>().await.unwrap(); + + assert_eq!(got, LockInfo { + lockscope: LockScope::Exclusive, + locktype: LockType::Write, + owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))), + }); + } + + #[tokio::test] + async fn rfc_multistatus_name() { + let src = r#" +<?xml version="1.0" encoding="utf-8" ?> + <multistatus xmlns="DAV:"> + <response> + <href>http://www.example.com/container/</href> + <propstat> + <prop xmlns:R="http://ns.example.com/boxschema/"> + <R:bigbox/> + <R:author/> + <creationdate/> + <displayname/> + <resourcetype/> + <supportedlock/> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> + </response> + <response> + <href>http://www.example.com/container/front.html</href> + <propstat> + <prop xmlns:R="http://ns.example.com/boxschema/"> + <R:bigbox/> + <creationdate/> + <displayname/> + <getcontentlength/> + <getcontenttype/> + <getetag/> + <getlastmodified/> + <resourcetype/> + <supportedlock/> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> + </response> + </multistatus> +"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<Multistatus::<Core, PropName<Core>>>().await.unwrap(); + + assert_eq!(got, Multistatus { + responses: vec![ + Response { + status_or_propstat: StatusOrPropstat::PropStat( + Href("http://www.example.com/container/".into()), + vec![PropStat { + prop: PropName(vec![ + PropertyRequest::CreationDate, + PropertyRequest::DisplayName, + PropertyRequest::ResourceType, + PropertyRequest::SupportedLock, + ]), + status: Status(http::status::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + responsedescription: None, + location: None, + }, + Response { + status_or_propstat: StatusOrPropstat::PropStat( + Href("http://www.example.com/container/front.html".into()), + vec![PropStat { + prop: PropName(vec![ + PropertyRequest::CreationDate, + PropertyRequest::DisplayName, + PropertyRequest::GetContentLength, + PropertyRequest::GetContentType, + PropertyRequest::GetEtag, + PropertyRequest::GetLastModified, + PropertyRequest::ResourceType, + PropertyRequest::SupportedLock, + ]), + status: Status(http::status::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + responsedescription: None, + location: None, + }, + ], + responsedescription: None, + }); + } + + + #[tokio::test] + async fn rfc_multistatus_value() { + let src = r#" + <?xml version="1.0" encoding="utf-8" ?> + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>/container/</D:href> + <D:propstat> + <D:prop xmlns:R="http://ns.example.com/boxschema/"> + <R:bigbox><R:BoxType>Box type A</R:BoxType></R:bigbox> + <R:author><R:Name>Hadrian</R:Name></R:author> + <D:creationdate>1997-12-01T17:42:21-08:00</D:creationdate> + <D:displayname>Example collection</D:displayname> + <D:resourcetype><D:collection/></D:resourcetype> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + <D:lockentry> + <D:lockscope><D:shared/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/container/front.html</D:href> + <D:propstat> + <D:prop xmlns:R="http://ns.example.com/boxschema/"> + <R:bigbox><R:BoxType>Box type B</R:BoxType> + </R:bigbox> + <D:creationdate>1997-12-01T18:27:21-08:00</D:creationdate> + <D:displayname>Example HTML resource</D:displayname> + <D:getcontentlength>4525</D:getcontentlength> + <D:getcontenttype>text/html</D:getcontenttype> + <D:getetag>"zzyzx"</D:getetag> + <D:getlastmodified + >Mon, 12 Jan 1998 09:25:56 GMT</D:getlastmodified> + <D:resourcetype/> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + <D:lockentry> + <D:lockscope><D:shared/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + </D:multistatus>"#; + + let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); + let got = rdr.find::<Multistatus::<Core, PropValue<Core>>>().await.unwrap(); + + assert_eq!(got, Multistatus { + responses: vec![ + Response { + status_or_propstat: StatusOrPropstat::PropStat( + Href("/container/".into()), + vec![PropStat { + prop: PropValue(vec![ + Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 17, 42, 21).unwrap()), + Property::DisplayName("Example collection".into()), + Property::ResourceType(vec![ResourceType::Collection]), + Property::SupportedLock(vec![ + LockEntry { + lockscope: LockScope::Exclusive, + locktype: LockType::Write, + }, + LockEntry { + lockscope: LockScope::Shared, + locktype: LockType::Write, + }, + ]), + ]), + status: Status(http::status::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + responsedescription: None, + location: None, + + }, + Response { + status_or_propstat: StatusOrPropstat::PropStat( + Href("/container/front.html".into()), + vec![PropStat { + prop: PropValue(vec![ + Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 18, 27, 21).unwrap()), + Property::DisplayName("Example HTML resource".into()), + Property::GetContentLength(4525), + Property::GetContentType("text/html".into()), + Property::GetEtag(r#""zzyzx""#.into()), + Property::GetLastModified(FixedOffset::west_opt(0).unwrap().with_ymd_and_hms(1998, 01, 12, 09, 25, 56).unwrap()), + //Property::ResourceType(vec![]), + Property::SupportedLock(vec![ + LockEntry { + lockscope: LockScope::Exclusive, + locktype: LockType::Write, + }, + LockEntry { + lockscope: LockScope::Shared, + locktype: LockType::Write, + }, + ]), + ]), + status: Status(http::status::StatusCode::OK), + error: None, + responsedescription: None, + }], + ), + error: None, + responsedescription: None, + location: None, + + }, + ], + responsedescription: None, + }); + } + +} |