1use std::time::Duration;
4
5use http::StatusCode;
6
7use crate::{DavRequestedProperty, DavXmlElement, DavXmlNode, encode_href};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct DavPropStat {
12 pub status: u16,
14 pub properties: Vec<DavXmlElement>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum DavErrorCondition {
21 NoExternalEntities,
23 LockTokenSubmitted {
25 href: String,
27 },
28 LockTokenMatchesRequestUri,
30 PropfindFiniteDepth,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct DavMultiStatusItem {
37 pub href: String,
39 pub status: Option<u16>,
41 pub propstats: Vec<DavPropStat>,
43 pub error: Option<DavErrorCondition>,
45}
46
47impl DavMultiStatusItem {
48 #[must_use]
50 pub fn properties(href: impl Into<String>, propstats: Vec<DavPropStat>) -> Self {
51 Self {
52 href: href.into(),
53 status: None,
54 propstats,
55 error: None,
56 }
57 }
58
59 #[must_use]
61 pub fn status(href: impl Into<String>, status: u16) -> Self {
62 Self {
63 href: href.into(),
64 status: Some(status),
65 propstats: Vec::new(),
66 error: None,
67 }
68 }
69
70 #[must_use]
72 pub fn with_error(mut self, error: DavErrorCondition) -> Self {
73 self.error = Some(error);
74 self
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct DavLockXml {
81 pub token: String,
83 pub owner: Option<DavXmlElement>,
85 pub timeout: Option<Duration>,
87 pub shared: bool,
89 pub deep: bool,
91 pub root_href: String,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct DavVersionXml {
98 pub href: String,
100 pub version_name: String,
102 pub creator: String,
104 pub content_length: i64,
106 pub last_modified: String,
108}
109
110#[must_use]
112pub fn dav_element(local_name: &str) -> DavXmlElement {
113 DavXmlElement::dav(local_name)
114}
115
116#[must_use]
118pub fn dav_text_element(local_name: &str, text: impl Into<String>) -> DavXmlElement {
119 let mut element = dav_element(local_name);
120 element.children.push(DavXmlNode::Text(text.into()));
121 element
122}
123
124#[must_use]
126pub fn dav_property_name_element(name: &DavRequestedProperty) -> DavXmlElement {
127 property_element(name, None)
128}
129
130#[must_use]
132pub fn dav_property_text_element(
133 name: &DavRequestedProperty,
134 text: impl Into<String>,
135) -> DavXmlElement {
136 property_element(name, Some(DavXmlNode::Text(text.into())))
137}
138
139#[must_use]
141pub fn dav_property_child_element(
142 name: &DavRequestedProperty,
143 child: DavXmlElement,
144) -> DavXmlElement {
145 property_element(name, Some(DavXmlNode::Element(child)))
146}
147
148#[must_use]
153pub fn dav_dead_property_element(
154 stored_name: &DavRequestedProperty,
155 requested_name: Option<&DavRequestedProperty>,
156 stored_xml: Option<&[u8]>,
157) -> DavXmlElement {
158 let output_name = requested_name.unwrap_or(stored_name);
159 let mut output = property_element(output_name, None);
160 let Some(stored_xml) = stored_xml.filter(|xml| !xml.is_empty()) else {
161 return output;
162 };
163 if let Ok(stored) = DavXmlElement::parse(stored_xml)
164 && stored.name == stored_name.name
165 && stored.namespace == stored_name.namespace
166 {
167 for (key, value) in stored.attributes {
168 if key.starts_with("xmlns") {
169 continue;
170 }
171 let key = if key == "lang" { "xml:lang" } else { &key };
172 output.attributes.entry(key.to_owned()).or_insert(value);
173 }
174 output.children = stored.children;
175 } else {
176 output.children.push(DavXmlNode::Text(
177 String::from_utf8_lossy(stored_xml).into_owned(),
178 ));
179 }
180 output
181}
182
183#[must_use]
185pub fn dav_status_element(status: u16) -> DavXmlElement {
186 let status = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
187 dav_text_element(
188 "status",
189 format!(
190 "HTTP/1.1 {} {}",
191 status.as_u16(),
192 status.canonical_reason().unwrap_or("Unknown"),
193 ),
194 )
195}
196
197#[must_use]
199pub fn dav_error_element(condition: &DavErrorCondition) -> DavXmlElement {
200 let mut error = dav_element("error");
201 declare_dav_namespace(&mut error);
202 error
203 .children
204 .push(DavXmlNode::Element(error_condition_element(condition)));
205 error
206}
207
208#[must_use]
210pub fn dav_propstat_element(propstat: DavPropStat) -> DavXmlElement {
211 let mut element = dav_element("propstat");
212 let mut properties = dav_element("prop");
213 properties
214 .children
215 .extend(propstat.properties.into_iter().map(DavXmlNode::Element));
216 element.children.push(DavXmlNode::Element(properties));
217 element
218 .children
219 .push(DavXmlNode::Element(dav_status_element(propstat.status)));
220 element
221}
222
223#[must_use]
225pub fn dav_response_element(item: DavMultiStatusItem) -> DavXmlElement {
226 let mut response = dav_element("response");
227 response
228 .children
229 .push(DavXmlNode::Element(dav_text_element("href", item.href)));
230 response.children.extend(
231 item.propstats
232 .into_iter()
233 .map(dav_propstat_element)
234 .map(DavXmlNode::Element),
235 );
236 if let Some(status) = item.status {
237 response
238 .children
239 .push(DavXmlNode::Element(dav_status_element(status)));
240 }
241 if let Some(error) = item.error {
242 let mut error_element = dav_element("error");
243 error_element
244 .children
245 .push(DavXmlNode::Element(error_condition_element(&error)));
246 response.children.push(DavXmlNode::Element(error_element));
247 }
248 response
249}
250
251#[must_use]
253pub fn dav_multistatus_element(items: Vec<DavMultiStatusItem>) -> DavXmlElement {
254 let mut multistatus = dav_element("multistatus");
255 declare_dav_namespace(&mut multistatus);
256 multistatus.children.extend(
257 items
258 .into_iter()
259 .map(dav_response_element)
260 .map(DavXmlNode::Element),
261 );
262 multistatus
263}
264
265#[must_use]
267pub fn dav_supported_lock_element() -> DavXmlElement {
268 let mut supported = dav_element("supportedlock");
269 for scope in ["exclusive", "shared"] {
270 let mut entry = dav_element("lockentry");
271 let mut lockscope = dav_element("lockscope");
272 lockscope
273 .children
274 .push(DavXmlNode::Element(dav_element(scope)));
275 entry.children.push(DavXmlNode::Element(lockscope));
276 let mut locktype = dav_element("locktype");
277 locktype
278 .children
279 .push(DavXmlNode::Element(dav_element("write")));
280 entry.children.push(DavXmlNode::Element(locktype));
281 supported.children.push(DavXmlNode::Element(entry));
282 }
283 supported
284}
285
286#[must_use]
288pub fn dav_lock_discovery_element(locks: &[DavLockXml]) -> DavXmlElement {
289 let mut discovery = dav_element("lockdiscovery");
290 discovery.children.extend(
291 locks
292 .iter()
293 .map(active_lock_element)
294 .map(DavXmlNode::Element),
295 );
296 discovery
297}
298
299#[must_use]
301pub fn dav_lock_response_element(locks: &[DavLockXml]) -> DavXmlElement {
302 let mut prop = dav_element("prop");
303 declare_dav_namespace(&mut prop);
304 prop.children
305 .push(DavXmlNode::Element(dav_lock_discovery_element(locks)));
306 prop
307}
308
309#[must_use]
311pub fn dav_version_multistatus_element(versions: Vec<DavVersionXml>) -> DavXmlElement {
312 let items = versions
313 .into_iter()
314 .map(|version| {
315 DavMultiStatusItem::properties(
316 version.href,
317 vec![DavPropStat {
318 status: StatusCode::OK.as_u16(),
319 properties: vec![
320 dav_text_element("version-name", version.version_name),
321 dav_text_element("creator-displayname", version.creator),
322 dav_text_element("getcontentlength", version.content_length.to_string()),
323 dav_text_element("getlastmodified", version.last_modified),
324 ],
325 }],
326 )
327 })
328 .collect();
329 dav_multistatus_element(items)
330}
331
332fn active_lock_element(lock: &DavLockXml) -> DavXmlElement {
333 let mut active = dav_element("activelock");
334 let mut lockscope = dav_element("lockscope");
335 lockscope
336 .children
337 .push(DavXmlNode::Element(dav_element(if lock.shared {
338 "shared"
339 } else {
340 "exclusive"
341 })));
342 active.children.push(DavXmlNode::Element(lockscope));
343
344 let mut locktype = dav_element("locktype");
345 locktype
346 .children
347 .push(DavXmlNode::Element(dav_element("write")));
348 active.children.push(DavXmlNode::Element(locktype));
349 active.children.push(DavXmlNode::Element(dav_text_element(
350 "depth",
351 if lock.deep { "Infinity" } else { "0" },
352 )));
353 if let Some(owner) = &lock.owner {
354 active.children.push(DavXmlNode::Element(owner.clone()));
355 }
356 active.children.push(DavXmlNode::Element(dav_text_element(
357 "timeout",
358 lock.timeout.map_or_else(
359 || "Infinite".to_owned(),
360 |timeout| format!("Second-{}", timeout.as_secs()),
361 ),
362 )));
363
364 let mut token = dav_element("locktoken");
365 token.children.push(DavXmlNode::Element(dav_text_element(
366 "href",
367 encode_href(&lock.token),
368 )));
369 active.children.push(DavXmlNode::Element(token));
370
371 let mut lockroot = dav_element("lockroot");
372 lockroot.children.push(DavXmlNode::Element(dav_text_element(
373 "href",
374 lock.root_href.clone(),
375 )));
376 active.children.push(DavXmlNode::Element(lockroot));
377 active
378}
379
380fn error_condition_element(condition: &DavErrorCondition) -> DavXmlElement {
381 match condition {
382 DavErrorCondition::NoExternalEntities => dav_element("no-external-entities"),
383 DavErrorCondition::LockTokenSubmitted { href } => {
384 let mut condition = dav_element("lock-token-submitted");
385 condition
386 .children
387 .push(DavXmlNode::Element(dav_text_element("href", href.clone())));
388 condition
389 }
390 DavErrorCondition::LockTokenMatchesRequestUri => {
391 dav_element("lock-token-matches-request-uri")
392 }
393 DavErrorCondition::PropfindFiniteDepth => dav_element("propfind-finite-depth"),
394 }
395}
396
397fn declare_dav_namespace(element: &mut DavXmlElement) {
398 element
399 .attributes
400 .insert("xmlns:D".to_owned(), "DAV:".to_owned());
401}
402
403fn property_element(name: &DavRequestedProperty, child: Option<DavXmlNode>) -> DavXmlElement {
404 let prefix = name
405 .prefix
406 .as_deref()
407 .filter(|prefix| !matches!(*prefix, "xml" | "xmlns"))
408 .unwrap_or_else(|| default_property_prefix(name.namespace.as_deref()));
409 let tag = if name.namespace.is_some() {
410 format!("{prefix}:{}", name.name)
411 } else {
412 name.name.clone()
413 };
414 let mut element = DavXmlElement::new(&tag);
415 element.namespace.clone_from(&name.namespace);
416 if let Some(namespace) = &name.namespace
417 && (namespace != "DAV:" || prefix != "D")
418 {
419 element
420 .attributes
421 .insert(format!("xmlns:{prefix}"), namespace.clone());
422 }
423 if let Some(child) = child {
424 element.children.push(child);
425 }
426 element
427}
428
429fn default_property_prefix(namespace: Option<&str>) -> &str {
430 match namespace {
431 Some("DAV:") => "D",
432 Some(_) => "A",
433 None => "",
434 }
435}