1use std::collections::{BTreeMap, HashSet};
4use std::future::Future;
5use std::pin::Pin;
6
7use aster_forge_xml::XmlSafetyPolicy;
8use async_trait::async_trait;
9use http::header::{CACHE_CONTROL, CONTENT_TYPE};
10use http::{HeaderValue, StatusCode};
11
12use crate::xml::{parse_report_request, parse_version_control_request};
13use crate::{
14 DavAutoVersion, DavBackendError, DavBackendErrorKind, DavCancellation, DavCapabilitySnapshot,
15 DavErrorCondition, DavMethod, DavMultiStatusError, DavMultiStatusErrorKind, DavMultiStatusItem,
16 DavMultiStatusLimits, DavPropStat, DavReportType, DavRequestedProperty, DavResponse,
17 DavVersioningState, DavXmlElement, DavXmlError, DavXmlNode, Depth, dav_element,
18 dav_error_element, dav_multistatus_bytes, dav_property_name_element, dav_property_text_element,
19 dav_text_element,
20};
21
22const DAV_NAMESPACE: &str = "DAV:";
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct DavReportLimits {
27 pub maximum_input_bytes: usize,
28 pub maximum_xml_depth: usize,
29 pub maximum_selection_depth: usize,
31 pub maximum_selection_properties: usize,
33 pub maximum_expansion_depth: usize,
35 pub maximum_expanded_resources: usize,
37 pub maximum_expanded_properties: usize,
39 pub multistatus: DavMultiStatusLimits,
40}
41
42impl DavReportLimits {
43 fn is_valid(self) -> bool {
44 self.maximum_input_bytes != 0
45 && self.maximum_xml_depth != 0
46 && self.maximum_selection_depth != 0
47 && self.maximum_selection_properties != 0
48 && self.maximum_expansion_depth != 0
49 && self.maximum_expanded_resources != 0
50 && self.maximum_expanded_properties != 0
51 }
52}
53
54impl Default for DavReportLimits {
55 fn default() -> Self {
56 let xml = XmlSafetyPolicy::untrusted();
57 Self {
58 maximum_input_bytes: xml.max_input_bytes,
59 maximum_xml_depth: xml.max_depth,
60 maximum_selection_depth: 16,
61 maximum_selection_properties: 100_000,
62 maximum_expansion_depth: 16,
63 maximum_expanded_resources: 10_000,
64 maximum_expanded_properties: 100_000,
65 multistatus: DavMultiStatusLimits::default(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct DavExpandPropertySelection {
73 pub property: DavRequestedProperty,
74 pub nested: Vec<Self>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct DavVersionTreeRequest {
80 pub properties: Option<Vec<DavRequestedProperty>>,
82 pub depth: Depth,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct DavExpandPropertyRequest {
89 pub properties: Vec<DavExpandPropertySelection>,
90 pub depth: Depth,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum DavReportRequest {
97 VersionTree(DavVersionTreeRequest),
98 ExpandProperty(DavExpandPropertyRequest),
99 Other {
101 report: DavReportType,
102 depth: Depth,
103 },
104}
105
106impl DavReportRequest {
107 #[must_use]
108 pub const fn report_type(&self) -> DavReportType {
109 match self {
110 Self::VersionTree(_) => DavReportType::VersionTree,
111 Self::ExpandProperty(_) => DavReportType::ExpandProperty,
112 Self::Other { report, .. } => *report,
113 }
114 }
115
116 #[must_use]
117 pub const fn depth(&self) -> Depth {
118 match self {
119 Self::VersionTree(request) => request.depth,
120 Self::ExpandProperty(request) => request.depth,
121 Self::Other { depth, .. } => *depth,
122 }
123 }
124}
125
126pub(crate) enum DavParsedReport {
127 VersionTree(Option<Vec<DavRequestedProperty>>),
128 ExpandProperty(Vec<DavExpandPropertySelection>),
129 Other(DavRequestedProperty),
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
134pub enum DavReportPlanError {
135 #[error("invalid REPORT limits")]
136 InvalidLimits,
137 #[error(transparent)]
138 Xml(#[from] DavXmlError),
139 #[error("unknown WebDAV REPORT type {name:?} in namespace {namespace:?}")]
140 UnknownType {
141 namespace: Option<String>,
142 name: String,
143 },
144 #[error("WebDAV REPORT type is not supported by this resource: {report:?}")]
145 NotAvailable { report: DavReportType },
146}
147
148pub trait DavReportErrorResponsePolicy {
150 fn unknown_type(&self, namespace: Option<&str>, name: &str) -> DavResponse;
151 fn not_available(&self, report: DavReportType) -> DavResponse;
152}
153
154pub fn plan_report_request(
160 snapshot: &DavCapabilitySnapshot,
161 body: &[u8],
162) -> Result<DavReportRequest, DavReportPlanError> {
163 plan_report_request_with_limits(snapshot, body, None, DavReportLimits::default())
164}
165
166pub fn plan_report_request_with_limits(
176 snapshot: &DavCapabilitySnapshot,
177 body: &[u8],
178 depth: Option<Depth>,
179 limits: DavReportLimits,
180) -> Result<DavReportRequest, DavReportPlanError> {
181 if !limits.is_valid() {
182 return Err(DavReportPlanError::InvalidLimits);
183 }
184 let parsed = parse_report_request(
185 body,
186 limits.maximum_input_bytes,
187 limits.maximum_xml_depth,
188 limits.maximum_selection_depth,
189 limits.maximum_selection_properties,
190 )?;
191 let depth = depth.unwrap_or(Depth::Zero);
192 let request = match parsed {
193 DavParsedReport::VersionTree(properties) => {
194 DavReportRequest::VersionTree(DavVersionTreeRequest { properties, depth })
195 }
196 DavParsedReport::ExpandProperty(properties) => {
197 DavReportRequest::ExpandProperty(DavExpandPropertyRequest { properties, depth })
198 }
199 DavParsedReport::Other(root) => {
200 if root.namespace.as_deref() != Some(DAV_NAMESPACE) {
201 return Err(DavReportPlanError::UnknownType {
202 namespace: root.namespace,
203 name: root.name,
204 });
205 }
206 let report = report_type(&root.name).ok_or(DavReportPlanError::UnknownType {
207 namespace: root.namespace,
208 name: root.name,
209 })?;
210 DavReportRequest::Other { report, depth }
211 }
212 };
213 let report = request.report_type();
214 if !snapshot.supports_report(report) {
215 return Err(DavReportPlanError::NotAvailable { report });
216 }
217 Ok(request)
218}
219
220pub fn report_plan_error_response<P: DavReportErrorResponsePolicy>(
226 error: &DavReportPlanError,
227 policy: &P,
228) -> Result<DavResponse, DavXmlError> {
229 match error {
230 DavReportPlanError::InvalidLimits => {
231 Ok(text_response(StatusCode::INTERNAL_SERVER_ERROR, ""))
232 }
233 DavReportPlanError::Xml(error) => xml_request_error_response(*error),
234 DavReportPlanError::UnknownType { namespace, name } => {
235 Ok(policy.unknown_type(namespace.as_deref(), name))
236 }
237 DavReportPlanError::NotAvailable { report } => Ok(policy.not_available(*report)),
238 }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum DavVersionControlAction {
244 PutUnderVersionControl,
245 AlreadyControlled,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub struct DavVersionControlPlan {
251 pub action: DavVersionControlAction,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
256pub enum DavVersionControlPlanError {
257 #[error("VERSION-CONTROL is not allowed for this target")]
258 MethodNotAllowed,
259 #[error(transparent)]
260 Xml(#[from] DavXmlError),
261}
262
263pub fn plan_version_control_request(
269 snapshot: &DavCapabilitySnapshot,
270 body: &[u8],
271) -> Result<DavVersionControlPlan, DavVersionControlPlanError> {
272 let DavVersioningMethodPlan::VersionControl(action) =
273 plan_versioning_method(snapshot, DavMethod::VersionControl)
274 .map_err(|_| DavVersionControlPlanError::MethodNotAllowed)?
275 else {
276 return Err(DavVersionControlPlanError::MethodNotAllowed);
277 };
278 parse_version_control_request(body)?;
279 Ok(DavVersionControlPlan { action })
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct DavVersionControlResult {
285 pub response_extensions: Vec<DavXmlElement>,
287}
288
289#[async_trait]
291pub trait DavVersionControlPort: Send + Sync {
292 async fn version_control(
293 &self,
294 plan: DavVersionControlPlan,
295 ) -> Result<DavVersionControlResult, DavBackendError>;
296}
297
298pub async fn execute_version_control<P: DavVersionControlPort>(
304 port: &P,
305 plan: DavVersionControlPlan,
306) -> Result<DavVersionControlResult, DavBackendError> {
307 port.version_control(plan).await
308}
309
310pub fn version_control_response(
316 result: DavVersionControlResult,
317) -> Result<DavResponse, DavXmlError> {
318 if result.response_extensions.is_empty() {
319 return Ok(DavResponse::empty(StatusCode::OK));
320 }
321 let mut root = dav_element("version-control-response");
322 root.namespaces
323 .insert("D".to_owned(), DAV_NAMESPACE.to_owned());
324 root.children.extend(
325 result
326 .response_extensions
327 .into_iter()
328 .map(DavXmlNode::Element),
329 );
330 xml_response(StatusCode::OK, &root)
331}
332
333pub fn version_control_plan_error_response(
339 snapshot: &DavCapabilitySnapshot,
340 error: DavVersionControlPlanError,
341) -> Result<DavResponse, DavXmlError> {
342 match error {
343 DavVersionControlPlanError::MethodNotAllowed => {
344 Ok(crate::method_not_allowed_response(snapshot))
345 }
346 DavVersionControlPlanError::Xml(error) => xml_request_error_response(error),
347 }
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum DavVersioningMethodPlan {
353 ReadOnly,
354 DirectMutation,
355 AutoCheckout,
356 AutoCheckoutCheckin,
357 AutoCheckinOnUnlock,
358 DeleteVersion,
359 VersionControl(DavVersionControlAction),
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
364pub enum DavVersioningPrecondition {
365 #[error("method is not allowed by the capability snapshot")]
366 MethodNotAllowed,
367 #[error("checked-in content requires checkout or auto-version")]
368 CannotModifyVersionControlledContent,
369 #[error("checked-in dead properties require checkout or auto-version")]
370 CannotModifyVersionControlledProperty,
371 #[error("immutable version content or dead properties cannot be modified")]
372 CannotModifyVersion,
373 #[error("immutable versions cannot be renamed")]
374 CannotRenameVersion,
375 #[error("server policy rejects deleting an immutable version")]
376 NoVersionDelete,
377}
378
379pub fn plan_versioning_method(
385 snapshot: &DavCapabilitySnapshot,
386 method: DavMethod,
387) -> Result<DavVersioningMethodPlan, DavVersioningPrecondition> {
388 if !snapshot.allows(method) {
389 return Err(DavVersioningPrecondition::MethodNotAllowed);
390 }
391 let facts = snapshot.declaration().versioning;
392 match method {
393 DavMethod::VersionControl => match facts.state {
394 DavVersioningState::Versionable => Ok(DavVersioningMethodPlan::VersionControl(
395 DavVersionControlAction::PutUnderVersionControl,
396 )),
397 DavVersioningState::CheckedIn | DavVersioningState::CheckedOut => Ok(
398 DavVersioningMethodPlan::VersionControl(DavVersionControlAction::AlreadyControlled),
399 ),
400 DavVersioningState::Unsupported | DavVersioningState::Version => {
401 Err(DavVersioningPrecondition::MethodNotAllowed)
402 }
403 },
404 DavMethod::Put | DavMethod::Proppatch => match facts.state {
405 DavVersioningState::Version => Err(DavVersioningPrecondition::CannotModifyVersion),
406 DavVersioningState::CheckedIn => {
407 let rejected = if method == DavMethod::Put {
408 DavVersioningPrecondition::CannotModifyVersionControlledContent
409 } else {
410 DavVersioningPrecondition::CannotModifyVersionControlledProperty
411 };
412 match facts.auto_version {
413 DavAutoVersion::None => Err(rejected),
414 DavAutoVersion::CheckoutCheckin => {
415 Ok(DavVersioningMethodPlan::AutoCheckoutCheckin)
416 }
417 DavAutoVersion::CheckoutUnlockedCheckin => {
418 if facts.write_locked {
419 Ok(DavVersioningMethodPlan::AutoCheckout)
420 } else {
421 Ok(DavVersioningMethodPlan::AutoCheckoutCheckin)
422 }
423 }
424 DavAutoVersion::Checkout => Ok(DavVersioningMethodPlan::AutoCheckout),
425 DavAutoVersion::LockedCheckout => {
426 if facts.write_locked {
427 Ok(DavVersioningMethodPlan::AutoCheckout)
428 } else {
429 Err(rejected)
430 }
431 }
432 }
433 }
434 DavVersioningState::Unsupported
435 | DavVersioningState::Versionable
436 | DavVersioningState::CheckedOut => Ok(DavVersioningMethodPlan::DirectMutation),
437 },
438 DavMethod::Move if facts.state == DavVersioningState::Version => {
439 Err(DavVersioningPrecondition::CannotRenameVersion)
440 }
441 DavMethod::Delete if facts.state == DavVersioningState::Version => {
442 if facts.allow_version_delete {
443 Ok(DavVersioningMethodPlan::DeleteVersion)
444 } else {
445 Err(DavVersioningPrecondition::NoVersionDelete)
446 }
447 }
448 DavMethod::Unlock if facts.auto_checkout_lock => {
449 Ok(DavVersioningMethodPlan::AutoCheckinOnUnlock)
450 }
451 DavMethod::Delete | DavMethod::Copy | DavMethod::Move | DavMethod::Unlock => {
452 Ok(DavVersioningMethodPlan::DirectMutation)
453 }
454 _ => Ok(DavVersioningMethodPlan::ReadOnly),
455 }
456}
457
458pub fn versioning_precondition_response(
464 snapshot: &DavCapabilitySnapshot,
465 error: DavVersioningPrecondition,
466) -> Result<DavResponse, DavXmlError> {
467 if error == DavVersioningPrecondition::MethodNotAllowed {
468 return Ok(crate::method_not_allowed_response(snapshot));
469 }
470 let condition = match error {
471 DavVersioningPrecondition::CannotModifyVersionControlledContent => {
472 DavErrorCondition::CannotModifyVersionControlledContent
473 }
474 DavVersioningPrecondition::CannotModifyVersionControlledProperty => {
475 DavErrorCondition::CannotModifyVersionControlledProperty
476 }
477 DavVersioningPrecondition::CannotModifyVersion => DavErrorCondition::CannotModifyVersion,
478 DavVersioningPrecondition::CannotRenameVersion => DavErrorCondition::CannotRenameVersion,
479 DavVersioningPrecondition::NoVersionDelete => DavErrorCondition::NoVersionDelete,
480 DavVersioningPrecondition::MethodNotAllowed => {
481 return Ok(crate::method_not_allowed_response(snapshot));
482 }
483 };
484 xml_response(StatusCode::FORBIDDEN, &dav_error_element(&condition))
485}
486
487#[derive(Debug, Clone, PartialEq, Eq)]
489pub struct DavVersionProperty {
490 pub property: DavRequestedProperty,
491 pub result: DavVersionPropertyResult,
492}
493
494impl DavVersionProperty {
495 #[must_use]
496 pub fn value(property: DavRequestedProperty, value: DavXmlElement) -> Self {
497 Self {
498 property,
499 result: DavVersionPropertyResult::Value(value),
500 }
501 }
502
503 #[must_use]
504 pub fn text(property: DavRequestedProperty, value: impl Into<String>) -> Self {
505 let element = dav_property_text_element(&property, value);
506 Self::value(property, element)
507 }
508
509 #[must_use]
510 pub fn hrefs(property: DavRequestedProperty, hrefs: impl IntoIterator<Item = String>) -> Self {
511 let mut element = property_element(&property);
512 element.children.extend(
513 hrefs
514 .into_iter()
515 .map(|href| DavXmlNode::Element(dav_text_element("href", href))),
516 );
517 Self::value(property, element)
518 }
519
520 #[must_use]
521 pub fn missing(property: DavRequestedProperty) -> Self {
522 Self {
523 property,
524 result: DavVersionPropertyResult::Missing,
525 }
526 }
527
528 #[must_use]
529 pub fn backend_error(property: DavRequestedProperty, kind: DavBackendErrorKind) -> Self {
530 Self {
531 property,
532 result: DavVersionPropertyResult::BackendError(kind),
533 }
534 }
535}
536
537#[derive(Debug, Clone, PartialEq, Eq)]
539pub enum DavVersionPropertyResult {
540 Value(DavXmlElement),
541 Missing,
542 BackendError(DavBackendErrorKind),
543}
544
545#[derive(Debug, Clone, PartialEq, Eq)]
547pub struct DavVersionReportItem {
548 pub href: String,
549 pub properties: Vec<DavVersionProperty>,
550}
551
552pub fn version_tree_response(
558 request: &DavVersionTreeRequest,
559 versions: Vec<DavVersionReportItem>,
560) -> Result<DavResponse, DavMultiStatusError> {
561 version_tree_response_with_limits(request, versions, DavMultiStatusLimits::default())
562}
563
564pub fn version_tree_response_with_limits(
570 request: &DavVersionTreeRequest,
571 versions: Vec<DavVersionReportItem>,
572 limits: DavMultiStatusLimits,
573) -> Result<DavResponse, DavMultiStatusError> {
574 let requested = request
575 .properties
576 .clone()
577 .unwrap_or_else(default_version_tree_properties);
578 let items = versions
579 .into_iter()
580 .map(|version| version_multistatus_item(version, &requested));
581 let mut response = DavResponse::bytes(
582 StatusCode::MULTI_STATUS,
583 dav_multistatus_bytes(items, limits)?,
584 );
585 response.headers.insert(
586 CONTENT_TYPE,
587 HeaderValue::from_static("application/xml; charset=utf-8"),
588 );
589 Ok(response)
590}
591
592#[derive(Debug, Clone, PartialEq, Eq)]
594pub enum DavExpandPropertyValue {
595 Element(DavXmlElement),
596 Hrefs(Vec<String>),
597}
598
599#[async_trait]
601pub trait DavExpandPropertyProvider: Send + Sync {
602 async fn property(
603 &self,
604 href: &str,
605 property: &DavRequestedProperty,
606 ) -> Result<Option<DavExpandPropertyValue>, DavBackendError>;
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
611pub enum DavExpandPropertyError {
612 #[error("invalid expand-property limits")]
613 InvalidLimits,
614 #[error("expand-property execution was cancelled")]
615 Cancelled,
616 #[error("expand-property cycle detected at {href}")]
617 Cycle { href: String },
618 #[error("expand-property depth limit exceeded")]
619 DepthLimitExceeded,
620 #[error("expand-property resource limit exceeded")]
621 ResourceLimitExceeded,
622 #[error("expand-property property limit exceeded")]
623 PropertyLimitExceeded,
624 #[error(transparent)]
625 Backend(#[from] DavBackendError),
626 #[error(transparent)]
627 MultiStatus(#[from] DavMultiStatusError),
628}
629
630pub async fn execute_expand_property<P: DavExpandPropertyProvider, C: DavCancellation>(
640 provider: &P,
641 root_href: &str,
642 request: &DavExpandPropertyRequest,
643 limits: DavReportLimits,
644 cancellation: &C,
645) -> Result<DavResponse, DavExpandPropertyError> {
646 if !limits.is_valid() {
647 return Err(DavExpandPropertyError::InvalidLimits);
648 }
649 let mut state = DavExpandExecutionState {
650 resources: 0,
651 properties: 0,
652 active_hrefs: HashSet::new(),
653 };
654 let item = expand_response_item(
655 provider,
656 root_href,
657 &request.properties,
658 0,
659 limits,
660 cancellation,
661 &mut state,
662 )
663 .await?;
664 let mut response = DavResponse::bytes(
665 StatusCode::MULTI_STATUS,
666 dav_multistatus_bytes([item], limits.multistatus)?,
667 );
668 response.headers.insert(
669 CONTENT_TYPE,
670 HeaderValue::from_static("application/xml; charset=utf-8"),
671 );
672 Ok(response)
673}
674
675struct DavExpandExecutionState {
676 resources: usize,
677 properties: usize,
678 active_hrefs: HashSet<String>,
679}
680
681#[expect(
682 clippy::too_many_lines,
683 reason = "The nested expand-property executor keeps one bounded traversal transaction and one canonical response grouping boundary."
684)]
685fn expand_response_item<'a, P: DavExpandPropertyProvider, C: DavCancellation>(
686 provider: &'a P,
687 href: &'a str,
688 selections: &'a [DavExpandPropertySelection],
689 depth: usize,
690 limits: DavReportLimits,
691 cancellation: &'a C,
692 state: &'a mut DavExpandExecutionState,
693) -> Pin<Box<dyn Future<Output = Result<DavMultiStatusItem, DavExpandPropertyError>> + Send + 'a>> {
694 Box::pin(async move {
695 checkpoint(cancellation)?;
696 if depth > limits.maximum_expansion_depth {
697 return Err(DavExpandPropertyError::DepthLimitExceeded);
698 }
699 state.resources = state
700 .resources
701 .checked_add(1)
702 .ok_or(DavExpandPropertyError::ResourceLimitExceeded)?;
703 if state.resources > limits.maximum_expanded_resources {
704 return Err(DavExpandPropertyError::ResourceLimitExceeded);
705 }
706 if !state.active_hrefs.insert(href.to_owned()) {
707 return Err(DavExpandPropertyError::Cycle {
708 href: href.to_owned(),
709 });
710 }
711
712 let result = async {
713 let mut groups: BTreeMap<u16, Vec<DavXmlElement>> = BTreeMap::new();
714 for selection in selections {
715 checkpoint(cancellation)?;
716 state.properties = state
717 .properties
718 .checked_add(1)
719 .ok_or(DavExpandPropertyError::PropertyLimitExceeded)?;
720 if state.properties > limits.maximum_expanded_properties {
721 return Err(DavExpandPropertyError::PropertyLimitExceeded);
722 }
723 match provider.property(href, &selection.property).await? {
724 None => groups
725 .entry(StatusCode::NOT_FOUND.as_u16())
726 .or_default()
727 .push(dav_property_name_element(&selection.property)),
728 Some(DavExpandPropertyValue::Element(element))
729 if selection.nested.is_empty() =>
730 {
731 groups
732 .entry(StatusCode::OK.as_u16())
733 .or_default()
734 .push(relexicalize(element, &selection.property));
735 }
736 Some(DavExpandPropertyValue::Hrefs(hrefs)) if selection.nested.is_empty() => {
737 let mut property = property_element(&selection.property);
738 property.children.extend(
739 hrefs
740 .into_iter()
741 .map(|href| DavXmlNode::Element(dav_text_element("href", href))),
742 );
743 groups
744 .entry(StatusCode::OK.as_u16())
745 .or_default()
746 .push(property);
747 }
748 Some(DavExpandPropertyValue::Hrefs(hrefs)) => {
749 let mut property = property_element(&selection.property);
750 for nested_href in hrefs {
751 checkpoint(cancellation)?;
752 match expand_response_item(
753 provider,
754 &nested_href,
755 &selection.nested,
756 depth + 1,
757 limits,
758 cancellation,
759 state,
760 )
761 .await
762 {
763 Ok(item) => property
764 .children
765 .push(DavXmlNode::Element(nested_response_element(item))),
766 Err(DavExpandPropertyError::Backend(error))
767 if error.kind == DavBackendErrorKind::NotFound =>
768 {
769 property.children.push(DavXmlNode::Element(
770 nested_response_element(DavMultiStatusItem::status(
771 nested_href,
772 StatusCode::NOT_FOUND.as_u16(),
773 )),
774 ));
775 }
776 Err(error) => return Err(error),
777 }
778 }
779 groups
780 .entry(StatusCode::OK.as_u16())
781 .or_default()
782 .push(property);
783 }
784 Some(DavExpandPropertyValue::Element(_)) => {
785 groups
786 .entry(StatusCode::FORBIDDEN.as_u16())
787 .or_default()
788 .push(dav_property_name_element(&selection.property));
789 }
790 }
791 }
792 if groups.is_empty() {
793 groups.insert(StatusCode::OK.as_u16(), Vec::new());
794 }
795 Ok(DavMultiStatusItem::properties(
796 href,
797 groups
798 .into_iter()
799 .map(|(status, properties)| DavPropStat { status, properties })
800 .collect(),
801 ))
802 }
803 .await;
804 state.active_hrefs.remove(href);
805 result
806 })
807}
808
809fn checkpoint(cancellation: &impl DavCancellation) -> Result<(), DavExpandPropertyError> {
810 if cancellation.is_cancelled() {
811 Err(DavExpandPropertyError::Cancelled)
812 } else {
813 Ok(())
814 }
815}
816
817fn version_multistatus_item(
818 version: DavVersionReportItem,
819 requested: &[DavRequestedProperty],
820) -> DavMultiStatusItem {
821 let mut groups: BTreeMap<u16, Vec<DavXmlElement>> = BTreeMap::new();
822 for property in requested {
823 let resolved = version.properties.iter().find(|candidate| {
824 candidate.property.name == property.name
825 && candidate.property.namespace == property.namespace
826 });
827 let (status, element) = match resolved.map(|property| &property.result) {
828 Some(DavVersionPropertyResult::Value(element)) => {
829 (StatusCode::OK, relexicalize(element.clone(), property))
830 }
831 Some(DavVersionPropertyResult::Missing) | None => {
832 (StatusCode::NOT_FOUND, dav_property_name_element(property))
833 }
834 Some(DavVersionPropertyResult::BackendError(kind)) => {
835 (backend_status(*kind), dav_property_name_element(property))
836 }
837 };
838 groups.entry(status.as_u16()).or_default().push(element);
839 }
840 if groups.is_empty() {
841 groups.insert(StatusCode::OK.as_u16(), Vec::new());
842 }
843 DavMultiStatusItem::properties(
844 version.href,
845 groups
846 .into_iter()
847 .map(|(status, properties)| DavPropStat { status, properties })
848 .collect(),
849 )
850}
851
852fn default_version_tree_properties() -> Vec<DavRequestedProperty> {
853 [
854 "version-name",
855 "creator-displayname",
856 "comment",
857 "predecessor-set",
858 "successor-set",
859 "checkout-set",
860 "getetag",
861 "getcontentlength",
862 "getcontenttype",
863 "getlastmodified",
864 ]
865 .into_iter()
866 .map(dav_property)
867 .collect()
868}
869
870fn dav_property(name: &str) -> DavRequestedProperty {
871 DavRequestedProperty {
872 name: name.to_owned(),
873 namespace: Some(DAV_NAMESPACE.to_owned()),
874 prefix: Some("D".to_owned()),
875 }
876}
877
878fn property_element(property: &DavRequestedProperty) -> DavXmlElement {
879 let mut element = property.prefix.as_ref().map_or_else(
880 || DavXmlElement::new(&property.name),
881 |prefix| DavXmlElement::new(&format!("{prefix}:{}", property.name)),
882 );
883 element.namespace.clone_from(&property.namespace);
884 if let Some(namespace) = &property.namespace {
885 element.namespaces.insert(
886 property.prefix.clone().unwrap_or_default(),
887 namespace.clone(),
888 );
889 }
890 element
891}
892
893fn relexicalize(mut element: DavXmlElement, property: &DavRequestedProperty) -> DavXmlElement {
894 element.name.clone_from(&property.name);
895 element.prefix.clone_from(&property.prefix);
896 element.namespace.clone_from(&property.namespace);
897 if let Some(namespace) = &property.namespace {
898 element.namespaces.insert(
899 property.prefix.clone().unwrap_or_default(),
900 namespace.clone(),
901 );
902 }
903 element
904}
905
906fn nested_response_element(item: DavMultiStatusItem) -> DavXmlElement {
907 let mut response = dav_element("response");
908 response
909 .children
910 .push(DavXmlNode::Element(dav_text_element("href", item.href)));
911 if let Some(status) = item.status {
912 response.children.push(DavXmlNode::Element(dav_text_element(
913 "status",
914 status_line(status),
915 )));
916 }
917 for propstat in item.propstats {
918 let mut element = dav_element("propstat");
919 let mut prop = dav_element("prop");
920 prop.children
921 .extend(propstat.properties.into_iter().map(DavXmlNode::Element));
922 element.children.push(DavXmlNode::Element(prop));
923 element.children.push(DavXmlNode::Element(dav_text_element(
924 "status",
925 status_line(propstat.status),
926 )));
927 response.children.push(DavXmlNode::Element(element));
928 }
929 response
930}
931
932fn status_line(status: u16) -> String {
933 let status = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
934 format!(
935 "HTTP/1.1 {} {}",
936 status.as_u16(),
937 status.canonical_reason().unwrap_or("Unknown Status")
938 )
939}
940
941fn backend_status(kind: DavBackendErrorKind) -> StatusCode {
942 match kind {
943 DavBackendErrorKind::NotFound => StatusCode::NOT_FOUND,
944 DavBackendErrorKind::Forbidden => StatusCode::FORBIDDEN,
945 DavBackendErrorKind::Conflict | DavBackendErrorKind::AlreadyExists => StatusCode::CONFLICT,
946 DavBackendErrorKind::InsufficientStorage => StatusCode::INSUFFICIENT_STORAGE,
947 DavBackendErrorKind::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
948 DavBackendErrorKind::Locked => StatusCode::LOCKED,
949 DavBackendErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
950 DavBackendErrorKind::Unsupported => StatusCode::NOT_IMPLEMENTED,
951 DavBackendErrorKind::Internal => StatusCode::INTERNAL_SERVER_ERROR,
952 }
953}
954
955pub fn expand_property_error_response(
961 error: &DavExpandPropertyError,
962) -> Result<DavResponse, DavXmlError> {
963 let response = match error {
964 DavExpandPropertyError::InvalidLimits => {
965 text_response(StatusCode::INTERNAL_SERVER_ERROR, "")
966 }
967 DavExpandPropertyError::Cancelled => text_response(StatusCode::SERVICE_UNAVAILABLE, ""),
968 DavExpandPropertyError::Cycle { .. }
969 | DavExpandPropertyError::DepthLimitExceeded
970 | DavExpandPropertyError::ResourceLimitExceeded
971 | DavExpandPropertyError::PropertyLimitExceeded => {
972 text_response(StatusCode::INSUFFICIENT_STORAGE, "")
973 }
974 DavExpandPropertyError::Backend(error) => {
975 return Ok(crate::backend_error_response(error));
976 }
977 DavExpandPropertyError::MultiStatus(error) => match &error.kind {
978 DavMultiStatusErrorKind::ItemLimitExceeded
979 | DavMultiStatusErrorKind::PropertyLimitExceeded
980 | DavMultiStatusErrorKind::OutputLimitExceeded => {
981 text_response(StatusCode::INSUFFICIENT_STORAGE, "")
982 }
983 DavMultiStatusErrorKind::Cancelled => {
984 text_response(StatusCode::SERVICE_UNAVAILABLE, "")
985 }
986 DavMultiStatusErrorKind::Backend(error) => {
987 return Ok(crate::backend_error_response(error));
988 }
989 DavMultiStatusErrorKind::InvalidLimits
990 | DavMultiStatusErrorKind::InvalidItem
991 | DavMultiStatusErrorKind::Xml
992 | DavMultiStatusErrorKind::Write => {
993 text_response(StatusCode::INTERNAL_SERVER_ERROR, "")
994 }
995 },
996 };
997 Ok(response)
998}
999
1000fn text_response(status: StatusCode, body: impl Into<String>) -> DavResponse {
1001 let mut response = DavResponse::bytes(status, body.into());
1002 response.headers.insert(
1003 CONTENT_TYPE,
1004 HeaderValue::from_static("text/plain; charset=utf-8"),
1005 );
1006 if status.is_client_error() || status.is_server_error() {
1007 response
1008 .headers
1009 .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
1010 }
1011 response
1012}
1013
1014fn xml_request_error_response(error: DavXmlError) -> Result<DavResponse, DavXmlError> {
1015 match error {
1016 DavXmlError::ExternalEntity => xml_response(
1017 StatusCode::FORBIDDEN,
1018 &dav_error_element(&DavErrorCondition::NoExternalEntities),
1019 ),
1020 DavXmlError::TooLarge => Ok(text_response(
1021 StatusCode::PAYLOAD_TOO_LARGE,
1022 "WebDAV XML body too large",
1023 )),
1024 DavXmlError::TooDeep | DavXmlError::Malformed | DavXmlError::InvalidGrammar => {
1025 Ok(text_response(StatusCode::BAD_REQUEST, "Invalid XML body"))
1026 }
1027 }
1028}
1029
1030fn xml_response(status: StatusCode, root: &DavXmlElement) -> Result<DavResponse, DavXmlError> {
1031 let mut response = DavResponse::bytes(status, root.to_bytes()?);
1032 response.headers.insert(
1033 CONTENT_TYPE,
1034 HeaderValue::from_static("application/xml; charset=utf-8"),
1035 );
1036 if status.is_client_error() || status.is_server_error() {
1037 response
1038 .headers
1039 .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
1040 }
1041 Ok(response)
1042}
1043
1044fn report_type(name: &str) -> Option<DavReportType> {
1045 DavReportType::ALL
1046 .into_iter()
1047 .find(|report| report.local_name() == name)
1048}