From 4d65366ff368cc9ea35115cb7e701bfebb166bc6 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 8 Mar 2024 11:34:24 +0100 Subject: [PATCH] Fixed some parsing bugs --- aero-dav/fuzz/fuzz_targets/dav.rs | 4 + aero-dav/src/caldecoder.rs | 37 +++- aero-dav/src/calencoder.rs | 2 +- aero-dav/src/decoder.rs | 7 +- aero-dav/src/encoder.rs | 301 ++++++++++++++++-------------- aero-dav/src/xml.rs | 7 +- 6 files changed, 206 insertions(+), 152 deletions(-) diff --git a/aero-dav/fuzz/fuzz_targets/dav.rs b/aero-dav/fuzz/fuzz_targets/dav.rs index a3c6ece..5bd28bc 100644 --- a/aero-dav/fuzz/fuzz_targets/dav.rs +++ b/aero-dav/fuzz/fuzz_targets/dav.rs @@ -9,6 +9,7 @@ use quick_xml::reader::NsReader; use tokio::runtime::Runtime; use tokio::io::AsyncWriteExt; +// Split this file const tokens: [&str; 63] = [ "0", "1", @@ -125,6 +126,9 @@ impl Tag { #[derive(Arbitrary)] enum XmlNode { + //@FIXME: build RFC3339 and RFC822 Dates with chrono based on timestamps + //@FIXME: add small numbers + //@FIXME: add http status code Node(Tag, Vec), Number(u64), Text(Token), diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index fb840d6..3aae4ad 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -1,9 +1,39 @@ -//use super::types as dav; +use super::types as dav; use super::caltypes::*; use super::xml; use super::error; // ---- ROOT ELEMENTS --- +impl xml::QRead> for MkCalendar { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} + +impl> xml::QRead> for MkCalendarResponse { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} + +impl xml::QRead> for CalendarQuery { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} + +impl xml::QRead> for CalendarMultiget { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} + +impl xml::QRead for FreeBusyQuery { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} + // ---- EXTENSIONS --- impl xml::QRead for Violation { @@ -31,3 +61,8 @@ impl xml::QRead for ResourceType { } // ---- INNER XML ---- +impl xml::QRead for SupportedCollation { + async fn qread(_xml: &mut xml::Reader) -> Result { + unreachable!(); + } +} diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index 67892ed..a25d767 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -666,7 +666,7 @@ mod tests { use crate::types as dav; use crate::realization::Calendar; use tokio::io::AsyncWriteExt; - use chrono::{Utc,TimeZone,DateTime}; + use chrono::{Utc,TimeZone}; async fn serialize(elem: &impl QWrite) -> String { let mut buffer = Vec::new(); diff --git a/aero-dav/src/decoder.rs b/aero-dav/src/decoder.rs index 02bc376..28442a6 100644 --- a/aero-dav/src/decoder.rs +++ b/aero-dav/src/decoder.rs @@ -23,8 +23,8 @@ impl QRead> for PropFind { let propfind: PropFind = loop { // allprop if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? { - let includ = xml.maybe_find::>().await?; xml.close().await?; + let includ = xml.maybe_find::>().await?; break PropFind::AllProp(includ) } @@ -594,8 +594,9 @@ impl QRead for Href { #[cfg(test)] mod tests { use super::*; - use chrono::{FixedOffset, DateTime, TimeZone, Utc}; + use chrono::{FixedOffset, TimeZone}; use crate::realization::Core; + use quick_xml::reader::NsReader; #[tokio::test] async fn basic_propfind_propname() { @@ -910,7 +911,7 @@ mod tests { 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::ResourceType(vec![]), Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, diff --git a/aero-dav/src/encoder.rs b/aero-dav/src/encoder.rs index fd2f9ca..813efe6 100644 --- a/aero-dav/src/encoder.rs +++ b/aero-dav/src/encoder.rs @@ -633,6 +633,7 @@ impl QWrite for Violation { #[cfg(test)] mod tests { use super::*; + use super::super::xml; use crate::realization::Core; use tokio::io::AsyncWriteExt; @@ -653,43 +654,47 @@ mod tests { return got.into() } + async fn deserialize>(src: &str) -> T { + let mut rdr = xml::Reader::new(quick_xml::reader::NsReader::from_reader(src.as_bytes())).await.unwrap(); + rdr.find().await.unwrap() + } + #[tokio::test] async fn basic_href() { + let orig = Href("/SOGo/dav/so/".into()); - let got = serialize( - &Href("/SOGo/dav/so/".into()) - ).await; + let got = serialize(&orig).await; let expected = r#"/SOGo/dav/so/"#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::(got.as_str()).await, orig) } #[tokio::test] async fn basic_multistatus() { - let got = serialize( - &Multistatus::> { - responses: vec![], - responsedescription: Some(ResponseDescription("Hello world".into())) - }, - ).await; + let orig = Multistatus::> { + responses: vec![], + responsedescription: Some(ResponseDescription("Hello world".into())) + }; + let got = serialize(&orig).await; let expected = r#" Hello world "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_error_delete_locked() { - let got = serialize( - &Error::(vec![ + let orig = Error::(vec![ Violation::LockTokenSubmitted(vec![ Href("/locked/".into()) ]) - ]), - ).await; + ]); + let got = serialize(&orig).await; let expected = r#" @@ -698,72 +703,74 @@ mod tests { "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_propname_req() { - let got = serialize( - &PropFind::::PropName, - ).await; + let orig = PropFind::::PropName; + + let got = serialize(&orig).await; let expected = r#" "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_propname_res() { - let got = serialize( - &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, - }, - ).await; + let orig = 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, + }; + + let got = serialize(&orig).await; let expected = r#" @@ -798,100 +805,102 @@ mod tests { assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_allprop_req() { - let got = serialize( - &PropFind::::AllProp(None), - ).await; + let orig = PropFind::::AllProp(None); + let got = serialize(&orig).await; let expected = r#" "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_allprop_res() { - use chrono::{DateTime,FixedOffset,TimeZone}; - let got = serialize( - &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, 1, 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, - }, - ]), + use chrono::{FixedOffset,TimeZone}; + + let orig = 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, 1, 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, - }] - ), + ]), + status: Status(http::status::StatusCode::OK), 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, 1, 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::east_opt(0) - .unwrap() - .with_ymd_and_hms(1998, 1, 12, 9, 25, 56) - .unwrap()), - Property::ResourceType(vec![]), - Property::SupportedLock(vec![ - LockEntry { - lockscope: LockScope::Exclusive, - locktype: LockType::Write, - }, - LockEntry { - lockscope: LockScope::Shared, - locktype: LockType::Write, - }, - ]), + }] + ), + 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, 1, 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::east_opt(0) + .unwrap() + .with_ymd_and_hms(1998, 1, 12, 9, 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, - }] - ), + ]), + status: Status(http::status::StatusCode::OK), error: None, responsedescription: None, - location: None, - }, - ], - responsedescription: None, - } - ).await; + }] + ), + error: None, + responsedescription: None, + location: None, + }, + ], + responsedescription: None, + }; + + let got = serialize(&orig).await; let expected = r#" @@ -961,16 +970,17 @@ mod tests { "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>>(got.as_str()).await, orig) } #[tokio::test] async fn rfc_allprop_include() { - let got = serialize( - &PropFind::::AllProp(Some(Include(vec![ - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - ]))), - ).await; + let orig = PropFind::::AllProp(Some(Include(vec![ + PropertyRequest::DisplayName, + PropertyRequest::ResourceType, + ]))); + + let got = serialize(&orig).await; let expected = r#" @@ -981,6 +991,7 @@ mod tests { "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); + assert_eq!(deserialize::>(got.as_str()).await, orig) } #[tokio::test] diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs index 98037ac..f9e04eb 100644 --- a/aero-dav/src/xml.rs +++ b/aero-dav/src/xml.rs @@ -79,7 +79,7 @@ impl Reader { /// skip a node at current level /// I would like to make this one private but not ready pub async fn skip(&mut self) -> Result, ParsingError> { - //println!("skipping inside node {:?}", self.parents.last()); + //println!("skipping inside node {:?} value {:?}", self.parents.last(), self.cur); match &self.cur { Event::Start(b) => { let _span = self.rdr.read_to_end_into_async(b.to_end().name(), &mut self.buf).await?; @@ -212,8 +212,10 @@ impl Reader { } pub async fn collect>(&mut self) -> Result, ParsingError> { - self.ensure_parent_has_child()?; let mut acc = Vec::new(); + if !self.parent_has_child() { + return Ok(acc) + } loop { match N::qread(self).await { @@ -230,6 +232,7 @@ impl Reader { } pub async fn open(&mut self, ns: &[u8], key: &str) -> Result, ParsingError> { + //println!("try open tag {:?}", key); let evt = match self.peek() { Event::Empty(_) if self.is_tag(ns, key) => self.cur.clone(), Event::Start(_) if self.is_tag(ns, key) => self.next().await?,