diff --git a/crates/dav-proto/resources/requests/lockinfo-001.json b/crates/dav-proto/resources/requests/lockinfo-001.json
index ae2b2587..34cb0cdd 100644
--- a/crates/dav-proto/resources/requests/lockinfo-001.json
+++ b/crates/dav-proto/resources/requests/lockinfo-001.json
@@ -5,8 +5,8 @@
{
"type": "ElementStart",
"data": {
- "name": "D:href",
- "attrs": null
+ "name": "href",
+ "attrs": "xmlns=\"DAV:\""
}
},
{
diff --git a/crates/dav-proto/resources/requests/lockinfo-002.json b/crates/dav-proto/resources/requests/lockinfo-002.json
index e405d67d..e6138681 100644
--- a/crates/dav-proto/resources/requests/lockinfo-002.json
+++ b/crates/dav-proto/resources/requests/lockinfo-002.json
@@ -5,8 +5,8 @@
{
"type": "ElementStart",
"data": {
- "name": "D:href",
- "attrs": null
+ "name": "href",
+ "attrs": "xmlns=\"DAV:\""
}
},
{
diff --git a/crates/dav-proto/resources/requests/propfind-001.json b/crates/dav-proto/resources/requests/propfind-001.json
index 0cb6e986..769ce837 100644
--- a/crates/dav-proto/resources/requests/propfind-001.json
+++ b/crates/dav-proto/resources/requests/propfind-001.json
@@ -4,29 +4,29 @@
{
"type": "DeadProperty",
"data": {
- "name": "R:bigbox",
- "attrs": null
+ "name": "bigbox",
+ "attrs": "xmlns=\"http://ns.example.com/boxschema/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "R:author",
- "attrs": null
+ "name": "author",
+ "attrs": "xmlns=\"http://ns.example.com/boxschema/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "R:DingALing",
- "attrs": null
+ "name": "DingALing",
+ "attrs": "xmlns=\"http://ns.example.com/boxschema/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "R:Random",
- "attrs": null
+ "name": "Random",
+ "attrs": "xmlns=\"http://ns.example.com/boxschema/\""
}
}
]
diff --git a/crates/dav-proto/resources/requests/propfind-007.json b/crates/dav-proto/resources/requests/propfind-007.json
index 8abfd1d7..3963b0e4 100644
--- a/crates/dav-proto/resources/requests/propfind-007.json
+++ b/crates/dav-proto/resources/requests/propfind-007.json
@@ -22,8 +22,8 @@
{
"type": "DeadProperty",
"data": {
- "name": "apple:calendar-color",
- "attrs": null
+ "name": "calendar-color",
+ "attrs": "xmlns=\"http://apple.com/ns/ical/\""
}
},
{
diff --git a/crates/dav-proto/resources/requests/propfind-010.json b/crates/dav-proto/resources/requests/propfind-010.json
new file mode 100644
index 00000000..42e94363
--- /dev/null
+++ b/crates/dav-proto/resources/requests/propfind-010.json
@@ -0,0 +1,36 @@
+{
+ "type": "Prop",
+ "data": [
+ {
+ "type": "WebDav",
+ "data": {
+ "type": "ResourceType"
+ }
+ },
+ {
+ "type": "WebDav",
+ "data": {
+ "type": "DisplayName"
+ }
+ },
+ {
+ "type": "WebDav",
+ "data": {
+ "type": "SyncToken"
+ }
+ },
+ {
+ "type": "WebDav",
+ "data": {
+ "type": "GetCTag"
+ }
+ },
+ {
+ "type": "DeadProperty",
+ "data": {
+ "name": "me-card",
+ "attrs": "xmlns=\"http://calendarserver.org/ns/\" hello=\"world & test\""
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/crates/dav-proto/resources/requests/propfind-010.xml b/crates/dav-proto/resources/requests/propfind-010.xml
new file mode 100644
index 00000000..b9fb7388
--- /dev/null
+++ b/crates/dav-proto/resources/requests/propfind-010.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/crates/dav-proto/resources/requests/report-022.json b/crates/dav-proto/resources/requests/report-022.json
index 238f186d..76eb5a5d 100644
--- a/crates/dav-proto/resources/requests/report-022.json
+++ b/crates/dav-proto/resources/requests/report-022.json
@@ -14,8 +14,8 @@
"property": {
"type": "DeadProperty",
"data": {
- "name": "B:title",
- "attrs": null
+ "name": "title",
+ "attrs": "xmlns=\"http://www.example.com/ns/\""
}
},
"match_": "Sales"
@@ -31,29 +31,29 @@
{
"type": "DeadProperty",
"data": {
- "name": "B:department",
- "attrs": null
+ "name": "department",
+ "attrs": "xmlns=\"http://www.example.com/ns/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "B:phone",
- "attrs": null
+ "name": "phone",
+ "attrs": "xmlns=\"http://www.example.com/ns/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "B:office",
- "attrs": null
+ "name": "office",
+ "attrs": "xmlns=\"http://www.example.com/ns/\""
}
},
{
"type": "DeadProperty",
"data": {
- "name": "B:salary",
- "attrs": null
+ "name": "salary",
+ "attrs": "xmlns=\"http://www.example.com/ns/\""
}
}
],
diff --git a/crates/dav-proto/src/parser/mod.rs b/crates/dav-proto/src/parser/mod.rs
index aa79a521..fa94b08a 100644
--- a/crates/dav-proto/src/parser/mod.rs
+++ b/crates/dav-proto/src/parser/mod.rs
@@ -20,11 +20,14 @@ pub mod tokenizer;
#[derive(Debug, Clone)]
pub enum Error {
- Xml(quick_xml::Error),
- UnexpectedToken {
- expected: Option>,
- found: Token<'static>,
- },
+ Xml(Box),
+ UnexpectedToken(Box),
+}
+
+#[derive(Debug, Clone)]
+pub struct UnexpectedToken {
+ pub expected: Option>,
+ pub found: Token<'static>,
}
pub type Result = std::result::Result;
@@ -43,7 +46,10 @@ pub enum Token<'x> {
}
#[derive(Debug, Clone)]
-pub struct RawElement<'x>(pub BytesStart<'x>);
+pub struct RawElement<'x> {
+ pub element: BytesStart<'x>,
+ pub namespace: Option>,
+}
pub trait DavParser: Sized {
fn parse(stream: &mut Tokenizer<'_>) -> Result;
@@ -89,20 +95,50 @@ impl Token<'_> {
match self {
Token::ElementStart { name, raw } => Token::ElementStart {
name,
- raw: RawElement(raw.0.into_owned()),
+ raw: raw.into_owned(),
},
Token::ElementEnd => Token::ElementEnd,
Token::Bytes(bytes) => Token::Bytes(bytes.into_owned().into()),
Token::Text(text) => Token::Text(text.into_owned().into()),
- Token::UnknownElement(raw) => Token::UnknownElement(RawElement(raw.0.into_owned())),
+ Token::UnknownElement(raw) => Token::UnknownElement(raw.into_owned()),
Token::Eof => Token::Eof,
}
}
pub fn into_unexpected(self) -> Error {
- Error::UnexpectedToken {
+ Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: None,
found: self.into_owned(),
+ }))
+ }
+}
+
+impl<'x> RawElement<'x> {
+ pub fn new(element: BytesStart<'x>) -> Self {
+ RawElement {
+ element,
+ namespace: None,
+ }
+ }
+
+ pub fn with_namespace(self, namespace: quick_xml::name::Namespace<'_>) -> Self {
+ RawElement {
+ element: self.element,
+ namespace: Some(Cow::Owned(namespace.into_inner().to_vec())),
+ }
+ }
+
+ pub fn with_namespace_static(self, namespace: &'static [u8]) -> Self {
+ RawElement {
+ element: self.element,
+ namespace: Some(Cow::Borrowed(namespace)),
+ }
+ }
+
+ pub fn into_owned(self) -> RawElement<'static> {
+ RawElement {
+ element: self.element.into_owned(),
+ namespace: self.namespace,
}
}
}
@@ -123,16 +159,17 @@ impl PartialEq for Token<'_> {
) => {
l_name == r_name
&& l_raw
- .0
+ .element
.attributes_raw()
.trim_ascii()
- .eq_ignore_ascii_case(r_raw.0.attributes_raw().trim_ascii())
+ .eq_ignore_ascii_case(r_raw.element.attributes_raw().trim_ascii())
}
(Self::Bytes(l0), Self::Bytes(r0)) => l0 == r0,
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
- (Self::UnknownElement(l0), Self::UnknownElement(r0)) => {
- l0.0.as_ref().eq_ignore_ascii_case(r0.0.as_ref())
- }
+ (Self::UnknownElement(l0), Self::UnknownElement(r0)) => l0
+ .element
+ .as_ref()
+ .eq_ignore_ascii_case(r0.element.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
@@ -140,19 +177,19 @@ impl PartialEq for Token<'_> {
impl NamedElement {
pub fn into_unexpected(self) -> Error {
- Error::UnexpectedToken {
+ Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: None,
found: Token::ElementStart {
name: self,
- raw: RawElement(BytesStart::new("")),
+ raw: RawElement::new(BytesStart::new("")),
},
- }
+ }))
}
}
impl Default for RawElement<'_> {
fn default() -> Self {
- RawElement(BytesStart::new(""))
+ RawElement::new(BytesStart::new(""))
}
}
@@ -160,9 +197,9 @@ impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::Xml(err) => write!(f, "XML error: {}", err),
- Error::UnexpectedToken { expected, found } => {
- write!(f, "Unexpected token: {found:?}")?;
- if let Some(expected) = expected {
+ Error::UnexpectedToken(err) => {
+ write!(f, "Unexpected token: {:?}", err.found)?;
+ if let Some(expected) = &err.expected {
write!(f, ", expected: {expected:?}")?;
}
Ok(())
diff --git a/crates/dav-proto/src/parser/property.rs b/crates/dav-proto/src/parser/property.rs
index 8c19b472..41ea17f2 100644
--- a/crates/dav-proto/src/parser/property.rs
+++ b/crates/dav-proto/src/parser/property.rs
@@ -65,7 +65,7 @@ impl Tokenizer<'_> {
break;
}
Token::UnknownElement(name) => {
- elements.push(DavProperty::DeadProperty(name.into()));
+ elements.push(DavProperty::DeadProperty((&name).into()));
self.expect_element_end()?;
}
token => return Err(token.into_unexpected()),
@@ -369,7 +369,7 @@ impl Tokenizer<'_> {
}
Token::UnknownElement(raw) => {
elements.push(DavPropertyValue {
- property: DavProperty::DeadProperty(raw.into()),
+ property: DavProperty::DeadProperty((&raw).into()),
value: DavValue::DeadProperty(DeadProperty::parse(self)?),
});
}
diff --git a/crates/dav-proto/src/parser/tokenizer.rs b/crates/dav-proto/src/parser/tokenizer.rs
index c78aba41..0a66a388 100644
--- a/crates/dav-proto/src/parser/tokenizer.rs
+++ b/crates/dav-proto/src/parser/tokenizer.rs
@@ -12,7 +12,7 @@ use quick_xml::{
use crate::schema::{Attribute, AttributeValue, Element, NamedElement, Namespace};
-use super::{Error, RawElement, Token, XmlValueParser};
+use super::{Error, RawElement, Token, UnexpectedToken, XmlValueParser};
pub struct Tokenizer<'x> {
xml: NsReader<&'x [u8]>,
@@ -47,7 +47,10 @@ impl<'x> Tokenizer<'x> {
return Ok(Token::ElementEnd);
}
Event::Text(text) if text.iter().any(|ch| !ch.is_ascii_whitespace()) => {
- return text.unescape().map(Token::Text).map_err(Error::Xml);
+ return text
+ .unescape()
+ .map(Token::Text)
+ .map_err(|err| Error::Xml(Box::new(err)));
}
Event::CData(bytes) => return Ok(Token::Bytes(bytes.into_inner())),
Event::Eof => return Ok(Token::Eof),
@@ -59,26 +62,29 @@ impl<'x> Tokenizer<'x> {
// Parse element
let name = tag.name();
match resolve_result {
- ResolveResult::Bound(ns) if !ns.as_ref().is_empty() => {
+ ResolveResult::Bound(raw_ns) if !raw_ns.as_ref().is_empty() => {
if let (Some(ns), Some(element)) = (
- Namespace::try_parse(ns.as_ref()),
+ Namespace::try_parse(raw_ns.as_ref()),
Element::try_parse(name.local_name().as_ref()).copied(),
) {
return Ok(Token::ElementStart {
name: NamedElement { ns, element },
- raw: RawElement(tag),
+ raw: RawElement::new(tag)
+ .with_namespace_static(ns.namespace().as_bytes()),
});
} else {
- return Ok(Token::UnknownElement(RawElement(tag)));
+ return Ok(Token::UnknownElement(
+ RawElement::new(tag).with_namespace(raw_ns),
+ ));
}
}
ResolveResult::Unknown(p) => {
- return Err(Error::Xml(quick_xml::Error::Namespace(
+ return Err(Error::Xml(Box::new(quick_xml::Error::Namespace(
quick_xml::name::NamespaceError::UnknownPrefix(p),
- )))
+ ))))
}
_ => {
- return Ok(Token::UnknownElement(RawElement(tag)));
+ return Ok(Token::UnknownElement(RawElement::new(tag)));
}
}
}
@@ -87,24 +93,24 @@ impl<'x> Tokenizer<'x> {
pub fn unwrap_named_element(&mut self) -> super::Result {
match self.token()? {
Token::ElementStart { name, .. } => Ok(name),
- found => Err(Error::UnexpectedToken {
+ found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: None,
found: found.into_owned(),
- }),
+ }))),
}
}
pub fn expect_named_element(&mut self, expected: NamedElement) -> super::Result<()> {
match self.token()? {
Token::ElementStart { name, .. } if name == expected => Ok(()),
- found => Err(Error::UnexpectedToken {
+ found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: Token::ElementStart {
name: expected,
raw: RawElement::default(),
}
.into(),
found: found.into_owned(),
- }),
+ }))),
}
}
@@ -112,24 +118,24 @@ impl<'x> Tokenizer<'x> {
match self.token()? {
Token::ElementStart { name, .. } if name == expected => Ok(true),
Token::Eof => Ok(false),
- found => Err(Error::UnexpectedToken {
+ found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: Token::ElementStart {
name: expected,
raw: RawElement::default(),
}
.into(),
found: found.into_owned(),
- }),
+ }))),
}
}
pub fn expect_element_end(&mut self) -> super::Result<()> {
match self.token()? {
Token::ElementEnd => Ok(()),
- found => Err(Error::UnexpectedToken {
+ found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
expected: Token::ElementEnd.into(),
found: found.into_owned(),
- }),
+ }))),
}
}
@@ -249,7 +255,7 @@ impl RawElement<'_> {
pub fn attributes(
&self,
) -> impl Iterator- >> + '_ {
- self.0.attributes().filter_map(|attr| match attr {
+ self.element.attributes().filter_map(|attr| match attr {
Ok(attr) => match attr.unescape_value() {
Ok(value) => Attribute::from_param(attr.key.as_ref(), value).map(Ok),
Err(err) => Some(Err(err.into())),
@@ -261,13 +267,13 @@ impl RawElement<'_> {
impl From for Error {
fn from(err: quick_xml::Error) -> Self {
- Error::Xml(err)
+ Error::Xml(Box::new(err))
}
}
impl From for Error {
fn from(err: AttrError) -> Self {
- Error::Xml(err.into())
+ Error::Xml(Box::new(err.into()))
}
}
diff --git a/crates/dav-proto/src/requests/mod.rs b/crates/dav-proto/src/requests/mod.rs
index 906f768a..0b902f2f 100644
--- a/crates/dav-proto/src/requests/mod.rs
+++ b/crates/dav-proto/src/requests/mod.rs
@@ -27,7 +27,7 @@ impl DavParser for DeadProperty {
loop {
match stream.token()? {
Token::ElementStart { raw, .. } | Token::UnknownElement(raw) => {
- items.0.push(DeadPropertyTag::ElementStart(raw.into()));
+ items.0.push(DeadPropertyTag::ElementStart((&raw).into()));
depth += 1;
}
Token::ElementEnd => {
@@ -142,14 +142,42 @@ impl ArchivedDeadElementTag {
}
}
-impl From> for DeadElementTag {
- fn from(raw: RawElement<'_>) -> Self {
- let name = String::from_utf8_lossy(raw.0.name().as_ref().trim_ascii()).into_owned();
- let attr = raw.0.attributes_raw().trim_ascii();
+impl From<&RawElement<'_>> for DeadElementTag {
+ fn from(raw: &RawElement<'_>) -> Self {
+ let name = std::str::from_utf8(raw.element.local_name().as_ref())
+ .unwrap_or("invalid-utf8")
+ .trim_ascii()
+ .to_string();
+ let mut attrs = String::with_capacity(raw.element.attributes_raw().len());
+ if let Some(namespace) = &raw.namespace {
+ attrs.push_str("xmlns=\"");
+ attrs.push_str(std::str::from_utf8(namespace).unwrap_or("invalid-utf8"));
+ attrs.push('"');
+ }
+
+ for attr in raw.element.attributes().flatten() {
+ if attr.key.as_ref() == b"xmlns" || attr.key.as_ref().starts_with(b"xmlns:") {
+ // Skip namespace attributes
+ continue;
+ }
+ if let (Ok(key), Ok(value)) = (
+ std::str::from_utf8(attr.key.as_ref()),
+ std::str::from_utf8(attr.value.as_ref()),
+ ) {
+ if !attrs.is_empty() {
+ attrs.push(' ');
+ }
+ attrs.push_str(key);
+ attrs.push('=');
+ attrs.push('"');
+ attrs.push_str(value);
+ attrs.push('"');
+ }
+ }
DeadElementTag {
name,
- attrs: (!attr.is_empty()).then(|| String::from_utf8_lossy(attr).into_owned()),
+ attrs: (!attrs.is_empty()).then_some(attrs),
}
}
}
diff --git a/crates/dav-proto/src/requests/report.rs b/crates/dav-proto/src/requests/report.rs
index a867fb50..fd1dcd67 100644
--- a/crates/dav-proto/src/requests/report.rs
+++ b/crates/dav-proto/src/requests/report.rs
@@ -528,7 +528,7 @@ impl DavParser for ExpandProperty {
depth: depth - 1,
});
} else {
- let attrs = raw.0.attributes_raw().trim_ascii();
+ let attrs = raw.element.attributes_raw().trim_ascii();
ep.properties.push(ExpandPropertyItem {
property: DavProperty::DeadProperty(DeadElementTag {
name,
diff --git a/crates/dav-proto/src/schema/mod.rs b/crates/dav-proto/src/schema/mod.rs
index 4a3e2b9e..62d9830c 100644
--- a/crates/dav-proto/src/schema/mod.rs
+++ b/crates/dav-proto/src/schema/mod.rs
@@ -66,10 +66,8 @@ impl Namespace {
Namespace::CalendarServer => "C",
}
}
-}
-impl AsRef for Namespace {
- fn as_ref(&self) -> &str {
+ pub fn namespace(&self) -> &'static str {
match self {
Namespace::Dav => "DAV:",
Namespace::CalDav => "urn:ietf:params:xml:ns:caldav",
@@ -79,6 +77,12 @@ impl AsRef for Namespace {
}
}
+impl AsRef for Namespace {
+ fn as_ref(&self) -> &str {
+ self.namespace()
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum Element {
diff --git a/tests/src/webdav/lock.rs b/tests/src/webdav/lock.rs
index b6a973a5..43316cc1 100644
--- a/tests/src/webdav/lock.rs
+++ b/tests/src/webdav/lock.rs
@@ -32,7 +32,7 @@ pub async fn test(test: &WebDavTest) {
.with_status(StatusCode::CREATED);
let lock_token = response
.with_value(
- "D:prop.D:lockdiscovery.D:activelock.D:owner.D:href",
+ "D:prop.D:lockdiscovery.D:activelock.D:owner.href",
"super-owner",
)
.with_value("D:prop.D:lockdiscovery.D:activelock.D:depth", "infinity")
@@ -55,7 +55,7 @@ pub async fn test(test: &WebDavTest) {
.await
.with_status(StatusCode::OK)
.with_value(
- "D:prop.D:lockdiscovery.D:activelock.D:owner.D:href",
+ "D:prop.D:lockdiscovery.D:activelock.D:owner.href",
"super-owner",
)
.with_any_value(
@@ -117,7 +117,7 @@ pub async fn test(test: &WebDavTest) {
props
.get(DavProperty::WebDav(WebDavProperty::LockDiscovery))
.with_some_values([
- "D:activelock.D:owner.D:href:super-owner",
+ "D:activelock.D:owner.href:super-owner",
"D:activelock.D:depth:infinity",
format!("D:activelock.D:locktoken.D:href:{lock_token}").as_str(),
format!("D:activelock.D:lockroot.D:href:{path}").as_str(),
diff --git a/tests/src/webdav/prop.rs b/tests/src/webdav/prop.rs
index e1c89a66..ffedb231 100644
--- a/tests/src/webdav/prop.rs
+++ b/tests/src/webdav/prop.rs
@@ -501,8 +501,8 @@ pub async fn test(test: &WebDavTest) {
),
(
DavProperty::DeadProperty(DeadElementTag::new(
- "C:my-dead-element".to_string(),
- None,
+ "my-dead-element".to_string(),
+ Some("xmlns=\"http://example.com/ns/\"".to_string()),
)),
"this is a dead but exciting element",
),
@@ -514,8 +514,8 @@ pub async fn test(test: &WebDavTest) {
let mut props = vec![
(
DavProperty::DeadProperty(DeadElementTag::new(
- "C:my-dead-element".to_string(),
- None,
+ "my-dead-element".to_string(),
+ Some("xmlns=\"http://example.com/ns/\"".to_string()),
)),
"",
),
@@ -604,8 +604,8 @@ pub async fn test(test: &WebDavTest) {
let mut chunky_props = vec![
DavProperty::WebDav(WebDavProperty::DisplayName),
DavProperty::DeadProperty(DeadElementTag::new(
- "C:my-chunky-dead-element".to_string(),
- None,
+ "my-chunky-dead-element".to_string(),
+ Some("xmlns=\"http://example.com/ns/\"".to_string()),
)),
];
if !is_file {