aster_forge_webdav/
property.rs1use std::collections::{BTreeMap, BTreeSet};
4use std::time::SystemTime;
5
6use http::StatusCode;
7
8use crate::response::{xml_document_response, xml_request_error_response};
9use crate::{
10 DavErrorCondition, DavMultiStatusItem, DavPropStat, DavPropfindRequest, DavRequestedProperty,
11 DavResponse, DavXmlElement, DavXmlError, dav_error_element, dav_multistatus_element,
12 dav_property_name_element,
13};
14
15type PropertyKey = (String, Option<String>);
16
17#[must_use]
19pub fn format_creation_date(time: SystemTime) -> String {
20 chrono::DateTime::<chrono::Utc>::from(time).to_rfc3339()
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct DavProppatchAtomicPlan {
26 pub apply: bool,
28 pub statuses: Vec<StatusCode>,
30}
31
32pub fn plan_atomic_proppatch(protected: impl IntoIterator<Item = bool>) -> DavProppatchAtomicPlan {
38 let protected = protected.into_iter().collect::<Vec<_>>();
39 let has_protected = protected.iter().any(|protected| *protected);
40 let statuses = protected
41 .into_iter()
42 .map(|protected| {
43 if has_protected {
44 if protected {
45 StatusCode::FORBIDDEN
46 } else {
47 StatusCode::FAILED_DEPENDENCY
48 }
49 } else {
50 StatusCode::OK
51 }
52 })
53 .collect();
54 DavProppatchAtomicPlan {
55 apply: !has_protected,
56 statuses,
57 }
58}
59
60pub fn build_propfind_item<E, F>(
65 href: String,
66 request: &DavPropfindRequest,
67 available: &[DavRequestedProperty],
68 mut resolve: F,
69) -> Result<DavMultiStatusItem, E>
70where
71 F: FnMut(&DavRequestedProperty) -> Result<Option<DavXmlElement>, E>,
72{
73 let groups = match request {
74 DavPropfindRequest::AllProp { include } => {
75 let mut keys = available.iter().map(property_key).collect::<BTreeSet<_>>();
76 let mut ok = resolve_available(available, &mut resolve)?;
77 let extra = include
78 .iter()
79 .filter(|property| keys.insert(property_key(property)))
80 .cloned()
81 .collect::<Vec<_>>();
82 let (mut included, missing) = resolve_requested(&extra, &mut resolve)?;
83 ok.append(&mut included);
84 propstat_groups(ok, missing)
85 }
86 DavPropfindRequest::PropName => vec![DavPropStat {
87 status: 200,
88 properties: available.iter().map(dav_property_name_element).collect(),
89 }],
90 DavPropfindRequest::Prop(requested) => {
91 let (ok, missing) = resolve_requested(requested, &mut resolve)?;
92 propstat_groups(ok, missing)
93 }
94 };
95 Ok(DavMultiStatusItem::properties(href, groups))
96}
97
98pub fn build_proppatch_item(
100 href: String,
101 outcomes: impl IntoIterator<Item = (u16, DavXmlElement)>,
102) -> DavMultiStatusItem {
103 let mut groups = BTreeMap::<u16, Vec<DavXmlElement>>::new();
104 for (status, property) in outcomes {
105 groups.entry(status).or_default().push(property);
106 }
107 DavMultiStatusItem::properties(
108 href,
109 groups
110 .into_iter()
111 .map(|(status, properties)| DavPropStat { status, properties })
112 .collect(),
113 )
114}
115
116pub fn property_multistatus_response(
118 items: Vec<DavMultiStatusItem>,
119) -> Result<DavResponse, DavXmlError> {
120 xml_document_response(StatusCode::MULTI_STATUS, dav_multistatus_element(items))
121}
122
123pub fn propfind_xml_error_response(error: DavXmlError) -> Result<DavResponse, DavXmlError> {
125 xml_request_error_response(error, "Invalid PROPFIND body")
126}
127
128pub fn proppatch_xml_error_response(error: DavXmlError) -> Result<DavResponse, DavXmlError> {
130 xml_request_error_response(error, "Invalid PROPPATCH body")
131}
132
133pub fn propfind_finite_depth_response() -> Result<DavResponse, DavXmlError> {
135 xml_document_response(
136 StatusCode::FORBIDDEN,
137 dav_error_element(&DavErrorCondition::PropfindFiniteDepth),
138 )
139}
140
141#[must_use]
143pub const fn propfind_request_label(request: &DavPropfindRequest) -> &'static str {
144 match request {
145 DavPropfindRequest::AllProp { .. } => "allprop",
146 DavPropfindRequest::PropName => "propname",
147 DavPropfindRequest::Prop(_) => "prop",
148 }
149}
150
151fn resolve_available<E, F>(
152 available: &[DavRequestedProperty],
153 resolve: &mut F,
154) -> Result<Vec<DavXmlElement>, E>
155where
156 F: FnMut(&DavRequestedProperty) -> Result<Option<DavXmlElement>, E>,
157{
158 let mut elements = Vec::with_capacity(available.len());
159 for property in available {
160 if let Some(element) = resolve(property)? {
161 elements.push(element);
162 }
163 }
164 Ok(elements)
165}
166
167fn resolve_requested<E, F>(
168 requested: &[DavRequestedProperty],
169 resolve: &mut F,
170) -> Result<(Vec<DavXmlElement>, Vec<DavXmlElement>), E>
171where
172 F: FnMut(&DavRequestedProperty) -> Result<Option<DavXmlElement>, E>,
173{
174 let mut ok = Vec::new();
175 let mut missing = Vec::new();
176 for property in requested {
177 match resolve(property)? {
178 Some(element) => ok.push(element),
179 None => missing.push(dav_property_name_element(property)),
180 }
181 }
182 Ok((ok, missing))
183}
184
185fn propstat_groups(ok: Vec<DavXmlElement>, missing: Vec<DavXmlElement>) -> Vec<DavPropStat> {
186 let mut groups = Vec::with_capacity(2);
187 if !ok.is_empty() {
188 groups.push(DavPropStat {
189 status: 200,
190 properties: ok,
191 });
192 }
193 if !missing.is_empty() {
194 groups.push(DavPropStat {
195 status: 404,
196 properties: missing,
197 });
198 }
199 groups
200}
201
202fn property_key(property: &DavRequestedProperty) -> PropertyKey {
203 (property.name.clone(), property.namespace.clone())
204}