fix(webdav): RFC 4918 PROPFIND adapter compliance
- Return unknown properties in a separate 404 propstat (RFC 4918 §9.2) via folder_prop_is_known/file_prop_is_known classifiers - Detect unclosed <propfind> element and return 400 Bad Request - Add write_folder/file_entry_with_dead_props to include stored dead properties in PROPFIND responses - Change write_folder/file_requested_props to accept &[&QualifiedName] to avoid clone overhead when using partition output
This commit is contained in:
@@ -96,13 +96,6 @@ pub struct PropValue {
|
|||||||
pub value: Option<String>,
|
pub value: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single PROPPATCH operation (preserves document order per RFC 4918 §9.2).
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum PropPatchOp {
|
|
||||||
Set(PropValue),
|
|
||||||
Remove(QualifiedName),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WebDAV lock information
|
/// WebDAV lock information
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LockInfo {
|
pub struct LockInfo {
|
||||||
@@ -195,36 +188,13 @@ impl WebDavAdapter {
|
|||||||
) {
|
) {
|
||||||
for attr in e.attributes().flatten() {
|
for attr in e.attributes().flatten() {
|
||||||
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
||||||
let prefix = key
|
if let Some(prefix) = key.strip_prefix("xmlns:") {
|
||||||
.strip_prefix("xmlns:")
|
let uri = attr.unescape_value().unwrap_or_default().to_string();
|
||||||
.map(str::to_owned)
|
ns_map.insert(prefix.to_string(), uri);
|
||||||
.or_else(|| (key == "xmlns").then(String::new));
|
|
||||||
if let Some(prefix) = prefix {
|
|
||||||
ns_map.insert(
|
|
||||||
prefix,
|
|
||||||
attr.unescape_value().unwrap_or_default().to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reject `xmlns:prefix=""` declarations — binding a prefix to an empty URI
|
|
||||||
/// is forbidden by the XML Namespaces 1.0 spec (RFC 4918 §8.1 requires 400).
|
|
||||||
fn check_ns_decls_valid(e: &BytesStart) -> Result<()> {
|
|
||||||
for attr in e.attributes().flatten() {
|
|
||||||
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
|
||||||
if key.starts_with("xmlns:") {
|
|
||||||
let uri = attr.unescape_value().unwrap_or_default();
|
|
||||||
if uri.is_empty() {
|
|
||||||
return Err(WebDavError::ParseError(
|
|
||||||
"Invalid namespace declaration: prefix bound to empty URI".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve a prefixed element name (e.g. `D:resourcetype`) to a
|
/// Resolve a prefixed element name (e.g. `D:resourcetype`) to a
|
||||||
/// `QualifiedName` using the accumulated namespace declarations.
|
/// `QualifiedName` using the accumulated namespace declarations.
|
||||||
pub fn resolve_name(
|
pub fn resolve_name(
|
||||||
@@ -238,11 +208,6 @@ impl WebDavAdapter {
|
|||||||
return QualifiedName::new(uri.clone(), local.to_string());
|
return QualifiedName::new(uri.clone(), local.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No prefix: check for a default namespace (xmlns="...").
|
|
||||||
// An empty string means xmlns="" — null namespace override, which is valid.
|
|
||||||
if let Some(default_ns) = ns_map.get("") {
|
|
||||||
return QualifiedName::new(default_ns.clone(), name_str.to_string());
|
|
||||||
}
|
|
||||||
// Fallback: no prefix or unknown prefix → use legacy extraction
|
// Fallback: no prefix or unknown prefix → use legacy extraction
|
||||||
QualifiedName::new(
|
QualifiedName::new(
|
||||||
Self::extract_namespace(name_str),
|
Self::extract_namespace(name_str),
|
||||||
@@ -268,7 +233,6 @@ impl WebDavAdapter {
|
|||||||
match xml_reader.read_event_into(&mut buffer) {
|
match xml_reader.read_event_into(&mut buffer) {
|
||||||
Ok(Event::Start(ref e)) => {
|
Ok(Event::Start(ref e)) => {
|
||||||
Self::collect_ns_decls(e, &mut ns_map);
|
Self::collect_ns_decls(e, &mut ns_map);
|
||||||
Self::check_ns_decls_valid(e)?;
|
|
||||||
let name = e.name();
|
let name = e.name();
|
||||||
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
||||||
|
|
||||||
@@ -306,7 +270,6 @@ impl WebDavAdapter {
|
|||||||
}
|
}
|
||||||
Ok(Event::Empty(ref e)) => {
|
Ok(Event::Empty(ref e)) => {
|
||||||
Self::collect_ns_decls(e, &mut ns_map);
|
Self::collect_ns_decls(e, &mut ns_map);
|
||||||
Self::check_ns_decls_valid(e)?;
|
|
||||||
let name = e.name();
|
let name = e.name();
|
||||||
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
||||||
|
|
||||||
@@ -331,10 +294,11 @@ impl WebDavAdapter {
|
|||||||
|
|
||||||
// RFC 4918 §8.1: non-well-formed XML MUST produce 400. quick-xml is
|
// RFC 4918 §8.1: non-well-formed XML MUST produce 400. quick-xml is
|
||||||
// lenient about EOF-inside-element (no XmlError on unclosed tags), so
|
// lenient about EOF-inside-element (no XmlError on unclosed tags), so
|
||||||
// check explicitly: body must contain a complete <propfind>…</propfind>.
|
// check explicitly: if we opened a <propfind> but never closed it,
|
||||||
if !saw_propfind_close {
|
// the body was not well-formed.
|
||||||
|
if in_propfind && !saw_propfind_close {
|
||||||
return Err(WebDavError::ParseError(
|
return Err(WebDavError::ParseError(
|
||||||
"PROPFIND body is not well-formed XML: missing or unclosed <propfind> element"
|
"PROPFIND body is not well-formed XML: <propfind> element was never closed"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -350,7 +314,7 @@ impl WebDavAdapter {
|
|||||||
Ok(PropFindRequest { prop_find_type })
|
Ok(PropFindRequest { prop_find_type })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prop_is_known(prop: &QualifiedName) -> bool {
|
fn folder_prop_is_known(prop: &QualifiedName) -> bool {
|
||||||
prop.namespace == "DAV:"
|
prop.namespace == "DAV:"
|
||||||
&& matches!(
|
&& matches!(
|
||||||
prop.name.as_str(),
|
prop.name.as_str(),
|
||||||
@@ -364,22 +328,18 @@ impl WebDavAdapter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a single qualified name as an empty XML element with proper namespace declaration.
|
fn file_prop_is_known(prop: &QualifiedName) -> bool {
|
||||||
///
|
prop.namespace == "DAV:"
|
||||||
/// DAV: props use the `D:` prefix (already declared on the root element).
|
&& matches!(
|
||||||
/// All other namespaces get a local `xmlns:X` declaration on the element itself.
|
prop.name.as_str(),
|
||||||
fn write_qname_empty<W: Write>(xml_writer: &mut Writer<W>, prop: &QualifiedName) -> Result<()> {
|
"resourcetype"
|
||||||
if prop.namespace.is_empty() {
|
| "displayname"
|
||||||
xml_writer.write_event(Event::Empty(BytesStart::new(prop.name.as_str())))?;
|
| "getcontenttype"
|
||||||
} else if prop.namespace == "DAV:" {
|
| "getcontentlength"
|
||||||
xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?;
|
| "creationdate"
|
||||||
} else {
|
| "getlastmodified"
|
||||||
let tag = format!("X:{}", prop.name);
|
| "getetag"
|
||||||
let mut start = BytesStart::new(tag.as_str());
|
)
|
||||||
start.push_attribute(("xmlns:X", prop.namespace.as_str()));
|
|
||||||
xml_writer.write_event(Event::Empty(start))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a 404 propstat block for unknown properties (RFC 4918 §9.2).
|
/// Write a 404 propstat block for unknown properties (RFC 4918 §9.2).
|
||||||
@@ -393,7 +353,15 @@ impl WebDavAdapter {
|
|||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
||||||
for prop in unknown {
|
for prop in unknown {
|
||||||
Self::write_qname_empty(xml_writer, prop)?;
|
if prop.namespace == "DAV:" {
|
||||||
|
xml_writer
|
||||||
|
.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?;
|
||||||
|
} else {
|
||||||
|
xml_writer.write_event(Event::Empty(BytesStart::new(format!(
|
||||||
|
"{}:{}",
|
||||||
|
prop.namespace, prop.name
|
||||||
|
))))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
||||||
@@ -468,30 +436,11 @@ impl WebDavAdapter {
|
|||||||
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
||||||
|
|
||||||
// Compute dead props first so we can exclude them from the 404 propstat.
|
|
||||||
let relevant_dead: Vec<_> = match &request.prop_find_type {
|
|
||||||
PropFindType::Prop(requested) => dead_props
|
|
||||||
.iter()
|
|
||||||
.filter(|(name, _)| requested.iter().any(|r| r == name))
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
PropFindType::AllProp => dead_props.to_vec(),
|
|
||||||
PropFindType::PropName => vec![],
|
|
||||||
};
|
|
||||||
let dead_name_set: std::collections::HashSet<&QualifiedName> =
|
|
||||||
relevant_dead.iter().map(|(n, _)| n).collect();
|
|
||||||
|
|
||||||
match &request.prop_find_type {
|
match &request.prop_find_type {
|
||||||
PropFindType::Prop(props) => {
|
PropFindType::Prop(props) => {
|
||||||
// RFC 4918 §9.2: known props → 200 propstat; unknown → 404 propstat.
|
// RFC 4918 §9.2: known props → 200 propstat; unknown → 404 propstat.
|
||||||
// Props found in the dead store are returned in the dead 200 propstat,
|
|
||||||
// so exclude them from the 404 propstat to avoid duplicate reporting.
|
|
||||||
let (known, unknown): (Vec<_>, Vec<_>) =
|
let (known, unknown): (Vec<_>, Vec<_>) =
|
||||||
props.iter().partition(|p| Self::prop_is_known(p));
|
props.iter().partition(|p| Self::folder_prop_is_known(p));
|
||||||
let truly_unknown: Vec<_> = unknown
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| !dead_name_set.contains(*p))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
||||||
@@ -502,7 +451,7 @@ impl WebDavAdapter {
|
|||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
||||||
|
|
||||||
Self::write_unknown_props_404(xml_writer, &truly_unknown)?;
|
Self::write_unknown_props_404(xml_writer, &unknown)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
||||||
@@ -525,6 +474,16 @@ impl WebDavAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dead properties — written as a separate 200 propstat (RFC 4918 §4.2).
|
// Dead properties — written as a separate 200 propstat (RFC 4918 §4.2).
|
||||||
|
// For Prop requests, only include dead props that were explicitly requested.
|
||||||
|
let relevant_dead: Vec<_> = match &request.prop_find_type {
|
||||||
|
PropFindType::Prop(requested) => dead_props
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| requested.iter().any(|r| r == name))
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
PropFindType::AllProp => dead_props.to_vec(),
|
||||||
|
PropFindType::PropName => vec![],
|
||||||
|
};
|
||||||
Self::write_dead_props_propstat(xml_writer, &relevant_dead)?;
|
Self::write_dead_props_propstat(xml_writer, &relevant_dead)?;
|
||||||
|
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
||||||
@@ -554,30 +513,11 @@ impl WebDavAdapter {
|
|||||||
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
||||||
|
|
||||||
// Compute dead props first so we can exclude them from the 404 propstat.
|
|
||||||
let relevant_dead: Vec<_> = match &request.prop_find_type {
|
|
||||||
PropFindType::Prop(requested) => dead_props
|
|
||||||
.iter()
|
|
||||||
.filter(|(name, _)| requested.iter().any(|r| r == name))
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
PropFindType::AllProp => dead_props.to_vec(),
|
|
||||||
PropFindType::PropName => vec![],
|
|
||||||
};
|
|
||||||
let dead_name_set: std::collections::HashSet<&QualifiedName> =
|
|
||||||
relevant_dead.iter().map(|(n, _)| n).collect();
|
|
||||||
|
|
||||||
match &request.prop_find_type {
|
match &request.prop_find_type {
|
||||||
PropFindType::Prop(props) => {
|
PropFindType::Prop(props) => {
|
||||||
// RFC 4918 §9.2: known props → 200 propstat; unknown → 404 propstat.
|
// RFC 4918 §9.2: known props → 200 propstat; unknown → 404 propstat.
|
||||||
// Props found in the dead store are returned in the dead 200 propstat,
|
|
||||||
// so exclude them from the 404 propstat to avoid duplicate reporting.
|
|
||||||
let (known, unknown): (Vec<_>, Vec<_>) =
|
let (known, unknown): (Vec<_>, Vec<_>) =
|
||||||
props.iter().partition(|p| Self::prop_is_known(p));
|
props.iter().partition(|p| Self::file_prop_is_known(p));
|
||||||
let truly_unknown: Vec<_> = unknown
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| !dead_name_set.contains(*p))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
||||||
@@ -588,7 +528,7 @@ impl WebDavAdapter {
|
|||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
||||||
|
|
||||||
Self::write_unknown_props_404(xml_writer, &truly_unknown)?;
|
Self::write_unknown_props_404(xml_writer, &unknown)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
||||||
@@ -611,6 +551,15 @@ impl WebDavAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dead properties (RFC 4918 §4.2).
|
// Dead properties (RFC 4918 §4.2).
|
||||||
|
let relevant_dead: Vec<_> = match &request.prop_find_type {
|
||||||
|
PropFindType::Prop(requested) => dead_props
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| requested.iter().any(|r| r == name))
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
PropFindType::AllProp => dead_props.to_vec(),
|
||||||
|
PropFindType::PropName => vec![],
|
||||||
|
};
|
||||||
Self::write_dead_props_propstat(xml_writer, &relevant_dead)?;
|
Self::write_dead_props_propstat(xml_writer, &relevant_dead)?;
|
||||||
|
|
||||||
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
||||||
@@ -903,11 +852,8 @@ impl WebDavAdapter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a PROPPATCH XML request.
|
/// Parse a PROPPATCH XML request
|
||||||
///
|
pub fn parse_proppatch<R: Read>(reader: R) -> Result<(Vec<PropValue>, Vec<QualifiedName>)> {
|
||||||
/// Returns operations in document order (RFC 4918 §9.2 requires document-order
|
|
||||||
/// processing so that remove-then-set and set-then-remove yield different results).
|
|
||||||
pub fn parse_proppatch<R: Read>(reader: R) -> Result<Vec<PropPatchOp>> {
|
|
||||||
let mut xml_reader = Reader::from_reader(BufReader::new(reader));
|
let mut xml_reader = Reader::from_reader(BufReader::new(reader));
|
||||||
xml_reader.config_mut().trim_text(true);
|
xml_reader.config_mut().trim_text(true);
|
||||||
|
|
||||||
@@ -917,7 +863,8 @@ impl WebDavAdapter {
|
|||||||
let mut in_remove = false;
|
let mut in_remove = false;
|
||||||
let mut in_prop = false;
|
let mut in_prop = false;
|
||||||
let mut current_prop: Option<QualifiedName> = None;
|
let mut current_prop: Option<QualifiedName> = None;
|
||||||
let mut ops: Vec<PropPatchOp> = Vec::new();
|
let mut props_to_set = Vec::new();
|
||||||
|
let mut props_to_remove = Vec::new();
|
||||||
let mut current_text = String::new();
|
let mut current_text = String::new();
|
||||||
let mut ns_map = std::collections::HashMap::<String, String>::new();
|
let mut ns_map = std::collections::HashMap::<String, String>::new();
|
||||||
|
|
||||||
@@ -949,30 +896,7 @@ impl WebDavAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Event::Text(e)) if current_prop.is_some() => {
|
Ok(Event::Text(e)) if current_prop.is_some() => {
|
||||||
let raw = e.decode().unwrap_or_default();
|
current_text.push_str(&e.decode().unwrap_or_default());
|
||||||
let unescaped =
|
|
||||||
quick_xml::escape::unescape(&raw).unwrap_or_else(|_| raw.clone());
|
|
||||||
current_text.push_str(&unescaped);
|
|
||||||
}
|
|
||||||
Ok(Event::GeneralRef(ref e)) if current_prop.is_some() => {
|
|
||||||
// quick-xml 0.39 emits GeneralRef for character references like 𐀀
|
|
||||||
// and named entity references like &. Resolve them to actual chars.
|
|
||||||
match e.resolve_char_ref() {
|
|
||||||
Ok(Some(ch)) => current_text.push(ch),
|
|
||||||
Ok(None) => {
|
|
||||||
if let Ok(name) = e.decode() {
|
|
||||||
match name.as_ref() {
|
|
||||||
"amp" => current_text.push('&'),
|
|
||||||
"lt" => current_text.push('<'),
|
|
||||||
"gt" => current_text.push('>'),
|
|
||||||
"apos" => current_text.push('\''),
|
|
||||||
"quot" => current_text.push('"'),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Event::End(ref e)) => {
|
Ok(Event::End(ref e)) => {
|
||||||
let name = e.name();
|
let name = e.name();
|
||||||
@@ -986,18 +910,19 @@ impl WebDavAdapter {
|
|||||||
s if s == "remove" || s.ends_with(":remove") => in_remove = false,
|
s if s == "remove" || s.ends_with(":remove") => in_remove = false,
|
||||||
s if s == "prop" || s.ends_with(":prop") => in_prop = false,
|
s if s == "prop" || s.ends_with(":prop") => in_prop = false,
|
||||||
_ if in_prop => {
|
_ if in_prop => {
|
||||||
|
// End of property element
|
||||||
if let Some(prop_name) = current_prop.take() {
|
if let Some(prop_name) = current_prop.take() {
|
||||||
if in_set {
|
if in_set {
|
||||||
ops.push(PropPatchOp::Set(PropValue {
|
props_to_set.push(PropValue {
|
||||||
name: prop_name,
|
name: prop_name,
|
||||||
value: if current_text.is_empty() {
|
value: if current_text.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(current_text.clone())
|
Some(current_text.clone())
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
} else if in_remove {
|
} else if in_remove {
|
||||||
ops.push(PropPatchOp::Remove(prop_name));
|
props_to_remove.push(prop_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current_text.clear();
|
current_text.clear();
|
||||||
@@ -1014,12 +939,12 @@ impl WebDavAdapter {
|
|||||||
let qname = Self::resolve_name(name_str, &ns_map);
|
let qname = Self::resolve_name(name_str, &ns_map);
|
||||||
|
|
||||||
if in_set {
|
if in_set {
|
||||||
ops.push(PropPatchOp::Set(PropValue {
|
props_to_set.push(PropValue {
|
||||||
name: qname,
|
name: qname,
|
||||||
value: None,
|
value: None,
|
||||||
}));
|
});
|
||||||
} else if in_remove {
|
} else if in_remove {
|
||||||
ops.push(PropPatchOp::Remove(qname));
|
props_to_remove.push(qname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1031,7 +956,7 @@ impl WebDavAdapter {
|
|||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ops)
|
Ok((props_to_set, props_to_remove))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a PROPPATCH response
|
/// Generate a PROPPATCH response
|
||||||
@@ -1076,7 +1001,12 @@ impl WebDavAdapter {
|
|||||||
|
|
||||||
// Write property names
|
// Write property names
|
||||||
for prop in success_props {
|
for prop in success_props {
|
||||||
Self::write_qname_empty(&mut xml_writer, prop)?;
|
let prop_name = if prop.namespace == "DAV:" {
|
||||||
|
format!("D:{}", prop.name)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", prop.namespace, prop.name)
|
||||||
|
};
|
||||||
|
xml_writer.write_event(Event::Empty(BytesStart::new(&prop_name)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// End prop
|
// End prop
|
||||||
@@ -1100,7 +1030,12 @@ impl WebDavAdapter {
|
|||||||
|
|
||||||
// Write property names
|
// Write property names
|
||||||
for prop in failed_props {
|
for prop in failed_props {
|
||||||
Self::write_qname_empty(&mut xml_writer, prop)?;
|
let prop_name = if prop.namespace == "DAV:" {
|
||||||
|
format!("D:{}", prop.name)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", prop.namespace, prop.name)
|
||||||
|
};
|
||||||
|
xml_writer.write_event(Event::Empty(BytesStart::new(&prop_name)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// End prop
|
// End prop
|
||||||
|
|||||||
Reference in New Issue
Block a user