1use std::collections::BTreeMap;
7use std::io::Read;
8
9use aster_forge_xml::{
10 BorrowedDocument, ElementRef, Error as ForgeXmlError, NodeRef, OwnedDocument, ParseOptions,
11 XmlSafetyError, XmlSafetyPolicy, XmlStreamWriter, XmlWriteAttribute,
12};
13
14const DAV_NAMESPACE: &str = "DAV:";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
18pub enum DavXmlError {
19 #[error("XML external entity declarations are not allowed")]
21 ExternalEntity,
22 #[error("XML nesting depth exceeds the configured limit")]
24 TooDeep,
25 #[error("XML input exceeds the configured size limit")]
27 TooLarge,
28 #[error("malformed XML input")]
30 Malformed,
31 #[error("invalid WebDAV XML grammar")]
33 InvalidGrammar,
34}
35
36impl From<XmlSafetyError> for DavXmlError {
37 fn from(error: XmlSafetyError) -> Self {
38 match error {
39 XmlSafetyError::ExternalEntity => Self::ExternalEntity,
40 XmlSafetyError::TooDeep => Self::TooDeep,
41 XmlSafetyError::InputTooLarge | XmlSafetyError::TextTooLarge => Self::TooLarge,
42 XmlSafetyError::InvalidPolicy
43 | XmlSafetyError::OutputTooLarge
44 | XmlSafetyError::TooManyElements
45 | XmlSafetyError::TooManyAttributes
46 | XmlSafetyError::TooManyEvents
47 | XmlSafetyError::InvalidEncoding
48 | XmlSafetyError::Malformed => Self::Malformed,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum DavXmlNode {
56 Element(DavXmlElement),
58 Text(String),
60 CData(String),
62 Comment(String),
64 ProcessingInstruction(String, Option<String>),
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct DavXmlElement {
74 pub name: String,
76 pub prefix: Option<String>,
78 pub namespace: Option<String>,
80 pub namespaces: BTreeMap<String, String>,
82 pub attributes: BTreeMap<String, String>,
84 pub children: Vec<DavXmlNode>,
86}
87
88impl DavXmlElement {
89 #[must_use]
91 pub fn new(name: &str) -> Self {
92 let (prefix, local_name) = name
93 .split_once(':')
94 .map_or((None, name), |(prefix, local)| {
95 (Some(prefix.to_owned()), local)
96 });
97 Self {
98 name: local_name.to_owned(),
99 prefix,
100 namespace: None,
101 namespaces: BTreeMap::new(),
102 attributes: BTreeMap::new(),
103 children: Vec::new(),
104 }
105 }
106
107 #[must_use]
109 pub fn dav(local_name: &str) -> Self {
110 let mut element = Self::new(&format!("D:{local_name}"));
111 element.namespace = Some(DAV_NAMESPACE.to_owned());
112 element
113 }
114
115 pub fn parse(bytes: &[u8]) -> Result<Self, DavXmlError> {
117 parse_element(bytes)
118 }
119
120 pub fn parse_reader(reader: impl Read) -> Result<Self, DavXmlError> {
122 let options = webdav_parse_options();
123 let document = OwnedDocument::from_reader_with_options(reader, &options)
124 .map_err(map_forge_xml_error)?;
125 Ok(element_from_forge(document.root()))
126 }
127
128 pub fn to_bytes(&self) -> Result<Vec<u8>, DavXmlError> {
130 let mut writer = XmlStreamWriter::new(Vec::new()).map_err(map_forge_xml_error)?;
131 write_element(&mut writer, self, &BTreeMap::new())?;
132 writer.finish().map_err(map_forge_xml_error)
133 }
134
135 pub fn child_elements(&self) -> impl Iterator<Item = &Self> {
137 self.children.iter().filter_map(|child| match child {
138 DavXmlNode::Element(element) => Some(element),
139 DavXmlNode::Text(_)
140 | DavXmlNode::CData(_)
141 | DavXmlNode::Comment(_)
142 | DavXmlNode::ProcessingInstruction(_, _) => None,
143 })
144 }
145
146 #[must_use]
148 pub fn text(&self) -> Option<String> {
149 let text = self
150 .children
151 .iter()
152 .filter_map(|child| match child {
153 DavXmlNode::Text(text) | DavXmlNode::CData(text) => Some(text.as_str()),
154 _ => None,
155 })
156 .collect::<String>();
157 (!text.is_empty()).then_some(text)
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct DavRequestedProperty {
164 pub name: String,
166 pub namespace: Option<String>,
168 pub prefix: Option<String>,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum DavPropfindRequest {
175 AllProp {
177 include: Vec<DavRequestedProperty>,
179 },
180 PropName,
182 Prop(Vec<DavRequestedProperty>),
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct DavPropertyPatchRequest {
189 pub set: bool,
191 pub property: DavPropertyPatchValue,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct DavPropertyPatchValue {
198 pub name: String,
200 pub namespace: Option<String>,
202 pub prefix: Option<String>,
204 pub element: DavXmlElement,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct DavLockRequestBody {
211 pub shared: bool,
213 pub owner: Option<DavXmlElement>,
215}
216
217pub fn parse_propfind_request(body: &[u8]) -> Result<DavPropfindRequest, DavXmlError> {
219 if body.is_empty() {
220 return Ok(DavPropfindRequest::AllProp {
221 include: Vec::new(),
222 });
223 }
224 let document = parse_document(body)?;
225 let root = document.root();
226 if !is_dav_element(root, "propfind") {
227 return Err(DavXmlError::InvalidGrammar);
228 }
229
230 let mut kind = None;
231 let mut include = Vec::new();
232 let mut include_seen = false;
233 for child in root.child_elements() {
234 if is_dav_element(child, "propname") {
235 if kind.is_some() {
236 return Err(DavXmlError::InvalidGrammar);
237 }
238 kind = Some(DavPropfindRequest::PropName);
239 } else if is_dav_element(child, "allprop") {
240 if kind.is_some() {
241 return Err(DavXmlError::InvalidGrammar);
242 }
243 kind = Some(DavPropfindRequest::AllProp {
244 include: Vec::new(),
245 });
246 } else if is_dav_element(child, "include") {
247 if include_seen {
248 return Err(DavXmlError::InvalidGrammar);
249 }
250 include_seen = true;
251 include.extend(child.child_elements().map(requested_property));
252 } else if is_dav_element(child, "prop") {
253 if kind.is_some() {
254 return Err(DavXmlError::InvalidGrammar);
255 }
256 kind = Some(DavPropfindRequest::Prop(
257 child.child_elements().map(requested_property).collect(),
258 ));
259 }
260 }
261
262 match kind {
263 Some(DavPropfindRequest::AllProp { .. }) => Ok(DavPropfindRequest::AllProp { include }),
264 Some(kind) if !include_seen => Ok(kind),
265 _ => Err(DavXmlError::InvalidGrammar),
266 }
267}
268
269pub fn parse_proppatch_request(body: &[u8]) -> Result<Vec<DavPropertyPatchRequest>, DavXmlError> {
271 let document = parse_document(body)?;
272 let root = document.root();
273 if !is_dav_element(root, "propertyupdate") {
274 return Err(DavXmlError::InvalidGrammar);
275 }
276 let root_lang = xml_lang_value(root).map(str::to_owned);
277 let mut patches = Vec::new();
278 for action in root.child_elements() {
279 let set = if is_dav_element(action, "set") {
280 true
281 } else if is_dav_element(action, "remove") {
282 false
283 } else {
284 continue;
286 };
287 let action_lang = xml_lang_value(action).or(root_lang.as_deref());
288 let dav_children = action
289 .child_elements()
290 .filter(|child| child.namespace() == Some(DAV_NAMESPACE))
291 .collect::<Vec<_>>();
292 if !matches!(dav_children.as_slice(), [child] if is_dav_element(*child, "prop")) {
293 return Err(DavXmlError::InvalidGrammar);
294 }
295 let prop_container = dav_children[0];
296 let container_lang = xml_lang_value(prop_container).or(action_lang);
297 for property in prop_container.child_elements() {
298 let mut element = element_from_forge(property);
299 let inherited_lang = xml_lang_value(property).or(container_lang);
300 if let Some(lang) = inherited_lang.filter(|lang| !lang.is_empty()) {
301 element
302 .attributes
303 .entry("xml:lang".to_owned())
304 .or_insert_with(|| lang.to_owned());
305 }
306 patches.push(DavPropertyPatchRequest {
307 set,
308 property: DavPropertyPatchValue {
309 name: element.name.clone(),
310 namespace: element.namespace.clone(),
311 prefix: element.prefix.clone(),
312 element,
313 },
314 });
315 }
316 }
317 if patches.is_empty() {
318 return Err(DavXmlError::InvalidGrammar);
319 }
320 Ok(patches)
321}
322
323pub fn parse_lock_request(body: &[u8]) -> Result<DavLockRequestBody, DavXmlError> {
325 let document = parse_document(body)?;
326 let root = document.root();
327 if !is_dav_element(root, "lockinfo") {
328 return Err(DavXmlError::InvalidGrammar);
329 }
330 let mut shared = None;
331 let mut write_lock = false;
332 let mut owner = None;
333 for child in root.child_elements() {
334 if is_dav_element(child, "lockscope") {
335 if shared.is_some() {
336 return Err(DavXmlError::InvalidGrammar);
337 }
338 let children = child
339 .child_elements()
340 .filter(|scope| scope.namespace() == Some(DAV_NAMESPACE))
341 .collect::<Vec<_>>();
342 shared = match children.as_slice() {
343 [scope] if is_dav_element(*scope, "exclusive") => Some(false),
344 [scope] if is_dav_element(*scope, "shared") => Some(true),
345 _ => return Err(DavXmlError::InvalidGrammar),
346 };
347 } else if is_dav_element(child, "locktype") {
348 if write_lock {
349 return Err(DavXmlError::InvalidGrammar);
350 }
351 let children = child
352 .child_elements()
353 .filter(|kind| kind.namespace() == Some(DAV_NAMESPACE))
354 .collect::<Vec<_>>();
355 if !matches!(children.as_slice(), [kind] if is_dav_element(*kind, "write")) {
356 return Err(DavXmlError::InvalidGrammar);
357 }
358 write_lock = true;
359 } else if is_dav_element(child, "owner") {
360 if owner.is_some() {
361 return Err(DavXmlError::InvalidGrammar);
362 }
363 owner = Some(element_from_forge(child));
364 }
365 }
366 match (shared, write_lock) {
367 (Some(shared), true) => Ok(DavLockRequestBody { shared, owner }),
368 _ => Err(DavXmlError::InvalidGrammar),
369 }
370}
371
372pub fn parse_report_root(body: &[u8]) -> Result<DavRequestedProperty, DavXmlError> {
374 let document = parse_document(body)?;
375 Ok(requested_property(document.root()))
376}
377
378fn parse_element(bytes: &[u8]) -> Result<DavXmlElement, DavXmlError> {
379 let document = parse_document(bytes)?;
380 Ok(element_from_forge(document.root()))
381}
382
383fn parse_document(bytes: &[u8]) -> Result<BorrowedDocument<'_>, DavXmlError> {
384 BorrowedDocument::parse_with_options(bytes, &webdav_parse_options())
387 .map_err(map_forge_xml_error)
388}
389
390fn is_dav_element<S: AsRef<[u8]>>(element: ElementRef<'_, S>, local_name: &str) -> bool {
391 element.name() == local_name && element.namespace() == Some(DAV_NAMESPACE)
392}
393
394fn requested_property<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> DavRequestedProperty {
395 DavRequestedProperty {
396 name: element.name().to_owned(),
397 namespace: element.namespace().map(str::to_owned),
398 prefix: element.prefix().map(str::to_owned),
399 }
400}
401
402fn xml_lang_value<'document, S: AsRef<[u8]>>(
403 element: ElementRef<'document, S>,
404) -> Option<&'document str> {
405 element.attribute("xml:lang")
406}
407
408fn webdav_parse_options() -> ParseOptions {
409 ParseOptions::new()
412 .safety_policy(XmlSafetyPolicy::untrusted())
413 .trim_whitespace(true)
414}
415
416fn map_forge_xml_error(error: ForgeXmlError) -> DavXmlError {
417 match error {
418 ForgeXmlError::Safety(error) => error.into(),
419 ForgeXmlError::InvalidXml(_) | ForgeXmlError::InvalidData(_) | ForgeXmlError::Io(_) => {
420 DavXmlError::Malformed
421 }
422 }
423}
424
425fn element_from_forge<S: AsRef<[u8]>>(element: ElementRef<'_, S>) -> DavXmlElement {
426 let mut namespaces = BTreeMap::new();
427 match (element.prefix(), element.namespace()) {
428 (Some(prefix), Some(namespace)) if prefix != "xml" => {
429 namespaces.insert(prefix.to_owned(), namespace.to_owned());
430 }
431 (None, Some(namespace)) => {
432 namespaces.insert(String::new(), namespace.to_owned());
433 }
434 (None, None) => {
437 namespaces.insert(String::new(), String::new());
438 }
439 _ => {}
440 }
441
442 let mut attributes = BTreeMap::new();
443 for attribute in element.attributes() {
444 if let (Some(prefix), Some(namespace)) = (attribute.prefix(), attribute.namespace())
445 && prefix != "xml"
446 {
447 namespaces
448 .entry(prefix.to_owned())
449 .or_insert_with(|| namespace.to_owned());
450 }
451 attributes.insert(
452 attribute.qualified_name().to_owned(),
453 attribute.value().to_owned(),
454 );
455 }
456
457 DavXmlElement {
458 name: element.name().to_owned(),
459 prefix: element.prefix().map(str::to_owned),
460 namespace: element.namespace().map(str::to_owned),
461 namespaces,
462 attributes,
463 children: element
464 .children()
465 .map(|child| match child {
466 NodeRef::Element(element) => DavXmlNode::Element(element_from_forge(element)),
467 NodeRef::Text(text) => DavXmlNode::Text(text.to_owned()),
468 NodeRef::CData(text) => DavXmlNode::CData(text.to_owned()),
469 NodeRef::Comment(text) => DavXmlNode::Comment(text.to_owned()),
470 NodeRef::ProcessingInstruction(instruction) => DavXmlNode::ProcessingInstruction(
471 instruction.target.to_owned(),
472 instruction.content.map(str::to_owned),
473 ),
474 })
475 .collect(),
476 }
477}
478
479fn write_element(
480 writer: &mut XmlStreamWriter<Vec<u8>>,
481 element: &DavXmlElement,
482 inherited_namespaces: &BTreeMap<String, String>,
483) -> Result<(), DavXmlError> {
484 let qualified_name = element.prefix.as_ref().map_or_else(
485 || element.name.clone(),
486 |prefix| format!("{prefix}:{}", element.name),
487 );
488 let mut namespaces = inherited_namespaces.clone();
489 let mut attributes = BTreeMap::new();
490 for (prefix, namespace) in &element.namespaces {
491 if namespaces.get(prefix) != Some(namespace) {
492 let name = if prefix.is_empty() {
493 "xmlns".to_owned()
494 } else {
495 format!("xmlns:{prefix}")
496 };
497 attributes.insert(name, namespace.clone());
498 }
499 namespaces.insert(prefix.clone(), namespace.clone());
500 }
501 attributes.extend(element.attributes.clone());
502 for (name, namespace) in &attributes {
503 if let Some(prefix) = namespace_declaration_prefix(name) {
504 namespaces.insert(prefix.to_owned(), namespace.clone());
505 }
506 }
507
508 if let Some(namespace) = &element.namespace {
509 let prefix = element.prefix.as_deref().unwrap_or("");
510 if namespaces.get(prefix).map(String::as_str) != Some(namespace) {
511 let binding_name = if prefix.is_empty() {
512 "xmlns".to_owned()
513 } else {
514 format!("xmlns:{prefix}")
515 };
516 match attributes.get(&binding_name) {
517 Some(binding) if binding != namespace => return Err(DavXmlError::Malformed),
518 Some(_) => {}
519 None => {
520 attributes.insert(binding_name, namespace.clone());
521 }
522 }
523 namespaces.insert(prefix.to_owned(), namespace.clone());
524 }
525 } else if element.prefix.is_none()
526 && namespaces
527 .get("")
528 .is_some_and(|namespace| !namespace.is_empty())
529 {
530 match attributes.get("xmlns") {
531 Some(namespace) if !namespace.is_empty() => return Err(DavXmlError::Malformed),
532 Some(_) => {}
533 None => {
534 attributes.insert("xmlns".to_owned(), String::new());
535 }
536 }
537 namespaces.insert(String::new(), String::new());
538 }
539
540 let write_attributes = attributes
541 .iter()
542 .map(|(name, value)| XmlWriteAttribute::new(name, value));
543 if element.children.is_empty() {
544 writer
545 .empty_element(&qualified_name, write_attributes)
546 .map_err(map_forge_xml_error)?;
547 return Ok(());
548 }
549 writer
550 .start_element(&qualified_name, write_attributes)
551 .map_err(map_forge_xml_error)?;
552 for child in &element.children {
553 match child {
554 DavXmlNode::Element(element) => write_element(writer, element, &namespaces)?,
555 DavXmlNode::Text(text) => writer.text(text).map_err(map_forge_xml_error)?,
556 DavXmlNode::CData(text) => writer.cdata(text).map_err(map_forge_xml_error)?,
557 DavXmlNode::Comment(text) => writer.comment(text).map_err(map_forge_xml_error)?,
558 DavXmlNode::ProcessingInstruction(target, content) => {
559 writer
560 .processing_instruction(target, content.as_deref())
561 .map_err(map_forge_xml_error)?;
562 }
563 }
564 }
565 writer.end_element().map_err(map_forge_xml_error)
566}
567
568fn namespace_declaration_prefix(name: &str) -> Option<&str> {
569 if name == "xmlns" {
570 Some("")
571 } else {
572 name.strip_prefix("xmlns:")
573 }
574}