1use std::collections::BTreeMap;
7use std::io::{Read, Write};
8
9use aster_forge_xml::{
10 BorrowedDocument, ElementRef, Error as ForgeXmlError, NodeRef, OwnedDocument, ParseOptions,
11 XmlSafetyError, XmlSafetyPolicy, XmlStreamWriter, XmlWriteAttribute, is_valid_xml_local_name,
12 is_valid_xml_namespace_name,
13};
14
15use crate::deltav::{DavExpandPropertySelection, DavParsedReport};
16
17const DAV_NAMESPACE: &str = "DAV:";
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
21pub enum DavXmlError {
22 #[error("XML external entity declarations are not allowed")]
24 ExternalEntity,
25 #[error("XML nesting depth exceeds the configured limit")]
27 TooDeep,
28 #[error("XML input exceeds the configured size limit")]
30 TooLarge,
31 #[error("malformed XML input")]
33 Malformed,
34 #[error("invalid WebDAV XML grammar")]
36 InvalidGrammar,
37}
38
39impl From<XmlSafetyError> for DavXmlError {
40 fn from(error: XmlSafetyError) -> Self {
41 match error {
42 XmlSafetyError::ExternalEntity => Self::ExternalEntity,
43 XmlSafetyError::TooDeep => Self::TooDeep,
44 XmlSafetyError::InputTooLarge | XmlSafetyError::TextTooLarge => Self::TooLarge,
45 XmlSafetyError::InvalidPolicy
46 | XmlSafetyError::OutputTooLarge
47 | XmlSafetyError::TooManyElements
48 | XmlSafetyError::TooManyAttributes
49 | XmlSafetyError::TooManyEvents
50 | XmlSafetyError::InvalidEncoding
51 | XmlSafetyError::Malformed => Self::Malformed,
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum DavXmlNode {
59 Element(DavXmlElement),
61 Text(String),
63 CData(String),
65 Comment(String),
67 ProcessingInstruction(String, Option<String>),
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct DavXmlElement {
77 pub name: String,
79 pub prefix: Option<String>,
81 pub namespace: Option<String>,
83 pub namespaces: BTreeMap<String, String>,
85 pub attributes: BTreeMap<String, String>,
87 pub children: Vec<DavXmlNode>,
89}
90
91impl DavXmlElement {
92 #[must_use]
94 pub fn new(name: &str) -> Self {
95 let (prefix, local_name) = name
96 .split_once(':')
97 .map_or((None, name), |(prefix, local)| {
98 (Some(prefix.to_owned()), local)
99 });
100 Self {
101 name: local_name.to_owned(),
102 prefix,
103 namespace: None,
104 namespaces: BTreeMap::new(),
105 attributes: BTreeMap::new(),
106 children: Vec::new(),
107 }
108 }
109
110 #[must_use]
112 pub fn dav(local_name: &str) -> Self {
113 let mut element = Self::new(&format!("D:{local_name}"));
114 element.namespace = Some(DAV_NAMESPACE.to_owned());
115 element
116 }
117
118 pub fn parse(bytes: &[u8]) -> Result<Self, DavXmlError> {
124 parse_element(bytes)
125 }
126
127 pub fn parse_reader(reader: impl Read) -> Result<Self, DavXmlError> {
133 let options = webdav_parse_options();
134 let document = OwnedDocument::from_reader_with_options(reader, &options)
135 .map_err(|error| map_forge_xml_error(&error))?;
136 Ok(element_from_forge(document.root()))
137 }
138
139 pub fn to_bytes(&self) -> Result<Vec<u8>, DavXmlError> {
145 let mut writer =
146 XmlStreamWriter::new(Vec::new()).map_err(|error| map_forge_xml_error(&error))?;
147 write_element(&mut writer, self, &BTreeMap::new())
148 .map_err(|error| map_forge_xml_error(&error))?;
149 writer.finish().map_err(|error| map_forge_xml_error(&error))
150 }
151
152 pub fn child_elements(&self) -> impl Iterator<Item = &Self> {
154 self.children.iter().filter_map(|child| match child {
155 DavXmlNode::Element(element) => Some(element),
156 DavXmlNode::Text(_)
157 | DavXmlNode::CData(_)
158 | DavXmlNode::Comment(_)
159 | DavXmlNode::ProcessingInstruction(_, _) => None,
160 })
161 }
162
163 #[must_use]
165 pub fn text(&self) -> Option<String> {
166 let text = self
167 .children
168 .iter()
169 .filter_map(|child| match child {
170 DavXmlNode::Text(text) | DavXmlNode::CData(text) => Some(text.as_str()),
171 _ => None,
172 })
173 .collect::<String>();
174 (!text.is_empty()).then_some(text)
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct DavRequestedProperty {
181 pub name: String,
183 pub namespace: Option<String>,
185 pub prefix: Option<String>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub enum DavPropfindRequest {
192 AllProp {
194 include: Vec<DavRequestedProperty>,
196 },
197 PropName,
199 Prop(Vec<DavRequestedProperty>),
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct DavPropertyPatchRequest {
206 pub set: bool,
208 pub property: DavPropertyPatchValue,
210}
211
212#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct DavPropertyPatchValue {
215 pub name: String,
217 pub namespace: Option<String>,
219 pub prefix: Option<String>,
221 pub element: DavXmlElement,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
227pub struct DavLockRequestBody {
228 pub shared: bool,
230 pub owner: Option<DavXmlElement>,
232}
233
234pub fn parse_propfind_request(body: &[u8]) -> Result<DavPropfindRequest, DavXmlError> {
240 if body.is_empty() {
241 return Ok(DavPropfindRequest::AllProp {
242 include: Vec::new(),
243 });
244 }
245 let document = parse_document(body)?;
246 let root = document.root();
247 if !is_dav_element(root, "propfind") {
248 return Err(DavXmlError::InvalidGrammar);
249 }
250 require_element_content(root)?;
251
252 let mut kind = None;
253 let mut include = Vec::new();
254 let mut include_seen = false;
255 for child in root.child_elements() {
256 if is_dav_element(child, "propname") {
257 if kind.is_some() {
258 return Err(DavXmlError::InvalidGrammar);
259 }
260 require_element_content(child)?;
261 kind = Some(DavPropfindRequest::PropName);
262 } else if is_dav_element(child, "allprop") {
263 if kind.is_some() {
264 return Err(DavXmlError::InvalidGrammar);
265 }
266 require_element_content(child)?;
267 kind = Some(DavPropfindRequest::AllProp {
268 include: Vec::new(),
269 });
270 } else if is_dav_element(child, "include") {
271 if include_seen {
272 return Err(DavXmlError::InvalidGrammar);
273 }
274 include_seen = true;
275 require_property_names(child)?;
276 include.extend(child.child_elements().map(requested_property));
277 } else if is_dav_element(child, "prop") {
278 if kind.is_some() {
279 return Err(DavXmlError::InvalidGrammar);
280 }
281 require_property_names(child)?;
282 kind = Some(DavPropfindRequest::Prop(
283 child.child_elements().map(requested_property).collect(),
284 ));
285 }
286 }
287
288 match kind {
289 Some(DavPropfindRequest::AllProp { .. }) => Ok(DavPropfindRequest::AllProp { include }),
290 Some(kind) if !include_seen => Ok(kind),
291 _ => Err(DavXmlError::InvalidGrammar),
292 }
293}
294
295pub fn parse_proppatch_request(body: &[u8]) -> Result<Vec<DavPropertyPatchRequest>, DavXmlError> {
301 let document = parse_document(body)?;
302 let root = document.root();
303 if !is_dav_element(root, "propertyupdate") {
304 return Err(DavXmlError::InvalidGrammar);
305 }
306 require_element_content(root)?;
307 let root_lang = xml_lang_value(root).map(str::to_owned);
308 let mut patches = Vec::new();
309 for action in root.child_elements() {
310 let set = if is_dav_element(action, "set") {
311 true
312 } else if is_dav_element(action, "remove") {
313 false
314 } else {
315 continue;
317 };
318 require_element_content(action)?;
319 let action_lang = xml_lang_value(action).or(root_lang.as_deref());
320 let prop_container =
321 unique_dav_child(action, "prop")?.ok_or(DavXmlError::InvalidGrammar)?;
322 require_element_content(prop_container)?;
323 let container_lang = xml_lang_value(prop_container).or(action_lang);
324 for property in prop_container.child_elements() {
325 if !set {
326 require_property_name(property)?;
327 }
328 let mut element = element_from_forge(property);
329 let inherited_lang = xml_lang_value(property).or(container_lang);
330 if let Some(lang) = inherited_lang.filter(|lang| !lang.is_empty()) {
331 element
332 .attributes
333 .entry("xml:lang".to_owned())
334 .or_insert_with(|| lang.to_owned());
335 }
336 patches.push(DavPropertyPatchRequest {
337 set,
338 property: DavPropertyPatchValue {
339 name: element.name.clone(),
340 namespace: element.namespace.clone(),
341 prefix: element.prefix.clone(),
342 element,
343 },
344 });
345 }
346 }
347 if patches.is_empty() {
348 return Err(DavXmlError::InvalidGrammar);
349 }
350 Ok(patches)
351}
352
353pub fn parse_lock_request(body: &[u8]) -> Result<DavLockRequestBody, DavXmlError> {
359 let document = parse_document(body)?;
360 let root = document.root();
361 if !is_dav_element(root, "lockinfo") {
362 return Err(DavXmlError::InvalidGrammar);
363 }
364 require_element_content(root)?;
365 let mut shared = None;
366 let mut write_lock = false;
367 let mut owner = None;
368 for child in root.child_elements() {
369 if is_dav_element(child, "lockscope") {
370 if shared.is_some() {
371 return Err(DavXmlError::InvalidGrammar);
372 }
373 require_element_content(child)?;
374 let exclusive_scope = unique_dav_child(child, "exclusive")?;
375 let shared_scope = unique_dav_child(child, "shared")?;
376 let (selected_scope, is_shared) = match (exclusive_scope, shared_scope) {
377 (Some(scope), None) => (scope, false),
378 (None, Some(scope)) => (scope, true),
379 (Some(_), Some(_)) | (None, None) => return Err(DavXmlError::InvalidGrammar),
380 };
381 require_element_content(selected_scope)?;
382 shared = Some(is_shared);
383 } else if is_dav_element(child, "locktype") {
384 if write_lock {
385 return Err(DavXmlError::InvalidGrammar);
386 }
387 require_element_content(child)?;
388 let write = unique_dav_child(child, "write")?.ok_or(DavXmlError::InvalidGrammar)?;
389 require_element_content(write)?;
390 write_lock = true;
391 } else if is_dav_element(child, "owner") {
392 if owner.is_some() {
393 return Err(DavXmlError::InvalidGrammar);
394 }
395 owner = Some(element_from_forge(child));
396 }
397 }
398 match (shared, write_lock) {
399 (Some(shared), true) => Ok(DavLockRequestBody { shared, owner }),
400 _ => Err(DavXmlError::InvalidGrammar),
401 }
402}
403
404pub(crate) fn parse_report_request(
405 body: &[u8],
406 maximum_input_bytes: usize,
407 maximum_xml_depth: usize,
408 maximum_selection_depth: usize,
409 maximum_selection_properties: usize,
410) -> Result<DavParsedReport, DavXmlError> {
411 let options = webdav_parse_options()
412 .max_size(maximum_input_bytes)
413 .max_depth(maximum_xml_depth);
414 let document = parse_document_with_options(body, &options)?;
415 let root = document.root();
416 if is_dav_element(root, "version-tree") {
417 return Ok(DavParsedReport::VersionTree(parse_version_tree_prop(root)?));
418 }
419 if is_dav_element(root, "expand-property") {
420 return Ok(DavParsedReport::ExpandProperty(parse_expand_property(
421 root,
422 maximum_selection_depth,
423 maximum_selection_properties,
424 )?));
425 }
426 Ok(DavParsedReport::Other(requested_property(root)))
427}
428
429pub(crate) fn parse_version_control_request(body: &[u8]) -> Result<(), DavXmlError> {
431 if body.is_empty() {
432 return Ok(());
433 }
434 let document = parse_document(body)?;
435 if !is_dav_element(document.root(), "version-control") {
436 return Err(DavXmlError::InvalidGrammar);
437 }
438 Ok(())
441}
442
443fn parse_element(bytes: &[u8]) -> Result<DavXmlElement, DavXmlError> {
444 let document = parse_document(bytes)?;
445 Ok(element_from_forge(document.root()))
446}
447
448fn parse_document(bytes: &[u8]) -> Result<BorrowedDocument<'_>, DavXmlError> {
449 BorrowedDocument::parse_with_options(bytes, &webdav_parse_options())
452 .map_err(|error| map_forge_xml_error(&error))
453}
454
455fn parse_document_with_options<'a>(
456 bytes: &'a [u8],
457 options: &ParseOptions,
458) -> Result<BorrowedDocument<'a>, DavXmlError> {
459 BorrowedDocument::parse_with_options(bytes, options)
460 .map_err(|error| map_forge_xml_error(&error))
461}
462
463fn is_dav_element<S: AsRef<[u8]>>(element: ElementRef<'_, S>, local_name: &str) -> bool {
464 element.name() == local_name && element.namespace() == Some(DAV_NAMESPACE)
465}
466
467fn parse_version_tree_prop<S: AsRef<[u8]>>(
468 root: ElementRef<'_, S>,
469) -> Result<Option<Vec<DavRequestedProperty>>, DavXmlError> {
470 require_element_content(root)?;
471 if let Some(prop) = unique_dav_child(root, "prop")? {
472 require_property_names(prop)?;
473 return Ok(Some(
474 prop.child_elements().map(requested_property).collect(),
475 ));
476 }
477 Ok(None)
478}
479
480fn parse_expand_property<S: AsRef<[u8]>>(
481 root: ElementRef<'_, S>,
482 maximum_depth: usize,
483 maximum_properties: usize,
484) -> Result<Vec<DavExpandPropertySelection>, DavXmlError> {
485 require_element_content(root)?;
486 if root
487 .child_elements()
488 .any(|child| !is_dav_element(child, "property"))
489 {
490 return Err(DavXmlError::InvalidGrammar);
491 }
492 let mut count = 0usize;
493 root.child_elements()
494 .filter(|child| is_dav_element(*child, "property"))
495 .map(|property| {
496 parse_expand_property_selection(
497 property,
498 1,
499 maximum_depth,
500 maximum_properties,
501 &mut count,
502 )
503 })
504 .collect()
505}
506
507fn parse_expand_property_selection<S: AsRef<[u8]>>(
508 property: ElementRef<'_, S>,
509 depth: usize,
510 maximum_depth: usize,
511 maximum_properties: usize,
512 count: &mut usize,
513) -> Result<DavExpandPropertySelection, DavXmlError> {
514 if depth > maximum_depth {
515 return Err(DavXmlError::TooDeep);
516 }
517 *count = count.checked_add(1).ok_or(DavXmlError::TooLarge)?;
518 if *count > maximum_properties {
519 return Err(DavXmlError::TooLarge);
520 }
521 require_element_content(property)?;
522 if property
523 .child_elements()
524 .any(|child| !is_dav_element(child, "property"))
525 {
526 return Err(DavXmlError::InvalidGrammar);
527 }
528 let name = property
529 .attribute("name")
530 .filter(|name| is_valid_xml_local_name(name))
531 .ok_or(DavXmlError::InvalidGrammar)?;
532 let namespace = property.attribute("namespace").unwrap_or(DAV_NAMESPACE);
533 if namespace.is_empty() || !is_valid_xml_namespace_name(namespace) {
534 return Err(DavXmlError::InvalidGrammar);
535 }
536 let nested = property
537 .child_elements()
538 .filter(|child| is_dav_element(*child, "property"))
539 .map(|child| {
540 parse_expand_property_selection(
541 child,
542 depth + 1,
543 maximum_depth,
544 maximum_properties,
545 count,
546 )
547 })
548 .collect::<Result<Vec<_>, _>>()?;
549 Ok(DavExpandPropertySelection {
550 property: DavRequestedProperty {
551 name: name.to_owned(),
552 namespace: Some(namespace.to_owned()),
553 prefix: None,
554 },
555 nested,
556 })
557}
558
559fn unique_dav_child<'document, S: AsRef<[u8]>>(
560 parent: ElementRef<'document, S>,
561 local_name: &str,
562) -> Result<Option<ElementRef<'document, S>>, DavXmlError> {
563 let mut selected = None;
564 for child in parent.child_elements() {
565 if is_dav_element(child, local_name) {
566 if selected.is_some() {
567 return Err(DavXmlError::InvalidGrammar);
568 }
569 selected = Some(child);
570 }
571 }
572 Ok(selected)
573}
574
575fn require_element_content<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> Result<(), DavXmlError> {
576 if element
577 .children()
578 .any(|child| matches!(child, NodeRef::Text(_) | NodeRef::CData(_)))
579 {
580 Err(DavXmlError::InvalidGrammar)
581 } else {
582 Ok(())
583 }
584}
585
586fn require_property_names<S: AsRef<[u8]>>(container: ElementRef<'_, S>) -> Result<(), DavXmlError> {
587 require_element_content(container)?;
588 for property in container.child_elements() {
589 require_property_name(property)?;
590 }
591 Ok(())
592}
593
594fn require_property_name<S: AsRef<[u8]>>(property: ElementRef<'_, S>) -> Result<(), DavXmlError> {
595 require_element_content(property)
599}
600
601fn requested_property<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> DavRequestedProperty {
602 DavRequestedProperty {
603 name: element.name().to_owned(),
604 namespace: element.namespace().map(str::to_owned),
605 prefix: element.prefix().map(str::to_owned),
606 }
607}
608
609fn xml_lang_value<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> Option<&str> {
610 element.attribute("xml:lang")
611}
612
613fn webdav_parse_options() -> ParseOptions {
614 ParseOptions::new()
617 .safety_policy(XmlSafetyPolicy::untrusted())
618 .trim_whitespace(true)
619}
620
621fn map_forge_xml_error(error: &ForgeXmlError) -> DavXmlError {
622 match error {
623 ForgeXmlError::Safety(error) => (*error).into(),
624 ForgeXmlError::InvalidXml(_) | ForgeXmlError::InvalidData(_) | ForgeXmlError::Io(_) => {
625 DavXmlError::Malformed
626 }
627 }
628}
629
630fn element_from_forge<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> DavXmlElement {
631 let mut namespaces = BTreeMap::new();
632 match (element.prefix(), element.namespace()) {
633 (Some(prefix), Some(namespace)) if prefix != "xml" => {
634 namespaces.insert(prefix.to_owned(), namespace.to_owned());
635 }
636 (None, Some(namespace)) => {
637 namespaces.insert(String::new(), namespace.to_owned());
638 }
639 (None, None) => {
642 namespaces.insert(String::new(), String::new());
643 }
644 _ => {}
645 }
646
647 let mut attributes = BTreeMap::new();
648 for attribute in element.attributes() {
649 if let (Some(prefix), Some(namespace)) = (attribute.prefix(), attribute.namespace())
650 && prefix != "xml"
651 {
652 namespaces
653 .entry(prefix.to_owned())
654 .or_insert_with(|| namespace.to_owned());
655 }
656 attributes.insert(
657 attribute.qualified_name().to_owned(),
658 attribute.value().to_owned(),
659 );
660 }
661
662 DavXmlElement {
663 name: element.name().to_owned(),
664 prefix: element.prefix().map(str::to_owned),
665 namespace: element.namespace().map(str::to_owned),
666 namespaces,
667 attributes,
668 children: element
669 .children()
670 .map(|child| match child {
671 NodeRef::Element(element) => DavXmlNode::Element(element_from_forge(element)),
672 NodeRef::Text(text) => DavXmlNode::Text(text.to_owned()),
673 NodeRef::CData(text) => DavXmlNode::CData(text.to_owned()),
674 NodeRef::Comment(text) => DavXmlNode::Comment(text.to_owned()),
675 NodeRef::ProcessingInstruction(instruction) => DavXmlNode::ProcessingInstruction(
676 instruction.target.to_owned(),
677 instruction.content.map(str::to_owned),
678 ),
679 })
680 .collect(),
681 }
682}
683
684pub(crate) fn write_element<W: Write>(
685 writer: &mut XmlStreamWriter<W>,
686 element: &DavXmlElement,
687 inherited_namespaces: &BTreeMap<String, String>,
688) -> Result<(), ForgeXmlError> {
689 let qualified_name = element.prefix.as_ref().map_or_else(
690 || element.name.clone(),
691 |prefix| format!("{prefix}:{}", element.name),
692 );
693 let mut namespaces = inherited_namespaces.clone();
694 let mut attributes = BTreeMap::new();
695 for (prefix, namespace) in &element.namespaces {
696 if namespaces.get(prefix) != Some(namespace) {
697 let name = if prefix.is_empty() {
698 "xmlns".to_owned()
699 } else {
700 format!("xmlns:{prefix}")
701 };
702 attributes.insert(name, namespace.clone());
703 }
704 namespaces.insert(prefix.clone(), namespace.clone());
705 }
706 attributes.extend(element.attributes.clone());
707 for (name, namespace) in &attributes {
708 if let Some(prefix) = namespace_declaration_prefix(name) {
709 namespaces.insert(prefix.to_owned(), namespace.clone());
710 }
711 }
712
713 if let Some(namespace) = &element.namespace {
714 let prefix = element.prefix.as_deref().unwrap_or("");
715 if namespaces.get(prefix).map(String::as_str) != Some(namespace) {
716 let binding_name = if prefix.is_empty() {
717 "xmlns".to_owned()
718 } else {
719 format!("xmlns:{prefix}")
720 };
721 match attributes.get(&binding_name) {
722 Some(binding) if binding != namespace => {
723 return Err(ForgeXmlError::InvalidData(
724 "conflicting XML namespace binding".to_owned(),
725 ));
726 }
727 Some(_) => {}
728 None => {
729 attributes.insert(binding_name, namespace.clone());
730 }
731 }
732 namespaces.insert(prefix.to_owned(), namespace.clone());
733 }
734 } else if element.prefix.is_none()
735 && namespaces
736 .get("")
737 .is_some_and(|namespace| !namespace.is_empty())
738 {
739 match attributes.get("xmlns") {
740 Some(namespace) if !namespace.is_empty() => {
741 return Err(ForgeXmlError::InvalidData(
742 "conflicting XML default namespace binding".to_owned(),
743 ));
744 }
745 Some(_) => {}
746 None => {
747 attributes.insert("xmlns".to_owned(), String::new());
748 }
749 }
750 namespaces.insert(String::new(), String::new());
751 }
752
753 let write_attributes = attributes
754 .iter()
755 .map(|(name, value)| XmlWriteAttribute::new(name, value));
756 if element.children.is_empty() {
757 writer.empty_element(&qualified_name, write_attributes)?;
758 return Ok(());
759 }
760 writer.start_element(&qualified_name, write_attributes)?;
761 for child in &element.children {
762 match child {
763 DavXmlNode::Element(element) => write_element(writer, element, &namespaces)?,
764 DavXmlNode::Text(text) => writer.text(text)?,
765 DavXmlNode::CData(text) => writer.cdata(text)?,
766 DavXmlNode::Comment(text) => writer.comment(text)?,
767 DavXmlNode::ProcessingInstruction(target, content) => {
768 writer.processing_instruction(target, content.as_deref())?;
769 }
770 }
771 }
772 writer.end_element()
773}
774
775fn namespace_declaration_prefix(name: &str) -> Option<&str> {
776 if name == "xmlns" {
777 Some("")
778 } else {
779 name.strip_prefix("xmlns:")
780 }
781}