1use std::time::Duration;
4
5use crate::{DavRequestedProperty, DavXmlElement, DavXmlNode, encode_href};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct DavPropStat {
10 pub status: u16,
12 pub properties: Vec<DavXmlElement>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum DavErrorCondition {
19 NoExternalEntities,
21 LockTokenSubmitted {
23 href: String,
25 },
26 LockTokenMatchesRequestUri,
28 PropfindFiniteDepth,
30 CannotModifyVersionControlledContent,
32 CannotModifyVersionControlledProperty,
34 CannotModifyVersion,
36 CannotRenameVersion,
38 NoVersionDelete,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct DavMultiStatusItem {
45 pub href: String,
47 pub status: Option<u16>,
49 pub propstats: Vec<DavPropStat>,
51 pub error: Option<DavErrorCondition>,
53}
54
55impl DavMultiStatusItem {
56 #[must_use]
58 pub fn properties(href: impl Into<String>, propstats: Vec<DavPropStat>) -> Self {
59 Self {
60 href: href.into(),
61 status: None,
62 propstats,
63 error: None,
64 }
65 }
66
67 #[must_use]
69 pub fn status(href: impl Into<String>, status: u16) -> Self {
70 Self {
71 href: href.into(),
72 status: Some(status),
73 propstats: Vec::new(),
74 error: None,
75 }
76 }
77
78 #[must_use]
80 pub fn with_error(mut self, error: DavErrorCondition) -> Self {
81 self.error = Some(error);
82 self
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct DavLockXml {
89 pub token: String,
91 pub owner: Option<DavXmlElement>,
93 pub timeout: Option<Duration>,
95 pub shared: bool,
97 pub deep: bool,
99 pub root_href: String,
101}
102
103#[must_use]
105pub fn dav_element(local_name: &str) -> DavXmlElement {
106 DavXmlElement::dav(local_name)
107}
108
109#[must_use]
111pub fn dav_text_element(local_name: &str, text: impl Into<String>) -> DavXmlElement {
112 let mut element = dav_element(local_name);
113 element.children.push(DavXmlNode::Text(text.into()));
114 element
115}
116
117#[must_use]
119pub fn dav_property_name_element(name: &DavRequestedProperty) -> DavXmlElement {
120 property_element(name, None)
121}
122
123#[must_use]
125pub fn dav_property_text_element(
126 name: &DavRequestedProperty,
127 text: impl Into<String>,
128) -> DavXmlElement {
129 property_element(name, Some(DavXmlNode::Text(text.into())))
130}
131
132#[must_use]
134pub fn dav_property_child_element(
135 name: &DavRequestedProperty,
136 child: DavXmlElement,
137) -> DavXmlElement {
138 property_element(name, Some(DavXmlNode::Element(child)))
139}
140
141#[must_use]
146pub fn dav_dead_property_element(
147 stored_name: &DavRequestedProperty,
148 requested_name: Option<&DavRequestedProperty>,
149 stored_xml: Option<&[u8]>,
150) -> DavXmlElement {
151 let output_name = requested_name.unwrap_or(stored_name);
152 let mut output = property_element(output_name, None);
153 let Some(stored_xml) = stored_xml.filter(|xml| !xml.is_empty()) else {
154 return output;
155 };
156 if let Ok(stored) = DavXmlElement::parse(stored_xml)
157 && stored.name == stored_name.name
158 && stored.namespace == stored_name.namespace
159 {
160 for (key, value) in stored.attributes {
161 if key.starts_with("xmlns") {
162 continue;
163 }
164 let key = if key == "lang" { "xml:lang" } else { &key };
165 output.attributes.entry(key.to_owned()).or_insert(value);
166 }
167 output.children = stored.children;
168 } else {
169 output.children.push(DavXmlNode::Text(
170 String::from_utf8_lossy(stored_xml).into_owned(),
171 ));
172 }
173 output
174}
175
176#[must_use]
178pub fn dav_error_element(condition: &DavErrorCondition) -> DavXmlElement {
179 let mut error = dav_element("error");
180 declare_dav_namespace(&mut error);
181 error
182 .children
183 .push(DavXmlNode::Element(error_condition_element(condition)));
184 error
185}
186
187#[must_use]
189pub fn dav_supported_lock_element() -> DavXmlElement {
190 let mut supported = dav_element("supportedlock");
191 for scope in ["exclusive", "shared"] {
192 let mut entry = dav_element("lockentry");
193 let mut lockscope = dav_element("lockscope");
194 lockscope
195 .children
196 .push(DavXmlNode::Element(dav_element(scope)));
197 entry.children.push(DavXmlNode::Element(lockscope));
198 let mut locktype = dav_element("locktype");
199 locktype
200 .children
201 .push(DavXmlNode::Element(dav_element("write")));
202 entry.children.push(DavXmlNode::Element(locktype));
203 supported.children.push(DavXmlNode::Element(entry));
204 }
205 supported
206}
207
208#[must_use]
210pub fn dav_lock_discovery_element(locks: &[DavLockXml]) -> DavXmlElement {
211 let mut discovery = dav_element("lockdiscovery");
212 discovery.children.extend(
213 locks
214 .iter()
215 .map(active_lock_element)
216 .map(DavXmlNode::Element),
217 );
218 discovery
219}
220
221#[must_use]
223pub fn dav_lock_response_element(locks: &[DavLockXml]) -> DavXmlElement {
224 let mut prop = dav_element("prop");
225 declare_dav_namespace(&mut prop);
226 prop.children
227 .push(DavXmlNode::Element(dav_lock_discovery_element(locks)));
228 prop
229}
230
231fn active_lock_element(lock: &DavLockXml) -> DavXmlElement {
232 let mut active = dav_element("activelock");
233 let mut lockscope = dav_element("lockscope");
234 lockscope
235 .children
236 .push(DavXmlNode::Element(dav_element(if lock.shared {
237 "shared"
238 } else {
239 "exclusive"
240 })));
241 active.children.push(DavXmlNode::Element(lockscope));
242
243 let mut locktype = dav_element("locktype");
244 locktype
245 .children
246 .push(DavXmlNode::Element(dav_element("write")));
247 active.children.push(DavXmlNode::Element(locktype));
248 active.children.push(DavXmlNode::Element(dav_text_element(
249 "depth",
250 if lock.deep { "Infinity" } else { "0" },
251 )));
252 if let Some(owner) = &lock.owner {
253 active.children.push(DavXmlNode::Element(owner.clone()));
254 }
255 active.children.push(DavXmlNode::Element(dav_text_element(
256 "timeout",
257 lock.timeout.map_or_else(
258 || "Infinite".to_owned(),
259 |timeout| format!("Second-{}", timeout.as_secs()),
260 ),
261 )));
262
263 let mut token = dav_element("locktoken");
264 token.children.push(DavXmlNode::Element(dav_text_element(
265 "href",
266 encode_href(&lock.token),
267 )));
268 active.children.push(DavXmlNode::Element(token));
269
270 let mut lockroot = dav_element("lockroot");
271 lockroot.children.push(DavXmlNode::Element(dav_text_element(
272 "href",
273 lock.root_href.clone(),
274 )));
275 active.children.push(DavXmlNode::Element(lockroot));
276 active
277}
278
279fn error_condition_element(condition: &DavErrorCondition) -> DavXmlElement {
280 let (name, href) = error_condition_parts(condition);
281 let mut element = dav_element(name);
282 if let Some(href) = href {
283 element
284 .children
285 .push(DavXmlNode::Element(dav_text_element("href", href)));
286 }
287 element
288}
289
290pub(crate) fn error_condition_parts(condition: &DavErrorCondition) -> (&'static str, Option<&str>) {
291 match condition {
292 DavErrorCondition::NoExternalEntities => ("no-external-entities", None),
293 DavErrorCondition::LockTokenSubmitted { href } => {
294 ("lock-token-submitted", Some(href.as_str()))
295 }
296 DavErrorCondition::LockTokenMatchesRequestUri => ("lock-token-matches-request-uri", None),
297 DavErrorCondition::PropfindFiniteDepth => ("propfind-finite-depth", None),
298 DavErrorCondition::CannotModifyVersionControlledContent => {
299 ("cannot-modify-version-controlled-content", None)
300 }
301 DavErrorCondition::CannotModifyVersionControlledProperty => {
302 ("cannot-modify-version-controlled-property", None)
303 }
304 DavErrorCondition::CannotModifyVersion => ("cannot-modify-version", None),
305 DavErrorCondition::CannotRenameVersion => ("cannot-rename-version", None),
306 DavErrorCondition::NoVersionDelete => ("no-version-delete", None),
307 }
308}
309
310fn declare_dav_namespace(element: &mut DavXmlElement) {
311 element
312 .attributes
313 .insert("xmlns:D".to_owned(), "DAV:".to_owned());
314}
315
316fn property_element(name: &DavRequestedProperty, child: Option<DavXmlNode>) -> DavXmlElement {
317 let prefix = name
318 .prefix
319 .as_deref()
320 .filter(|prefix| !matches!(*prefix, "xml" | "xmlns"))
321 .unwrap_or_else(|| default_property_prefix(name.namespace.as_deref()));
322 let tag = if name.namespace.is_some() {
323 format!("{prefix}:{}", name.name)
324 } else {
325 name.name.clone()
326 };
327 let mut element = DavXmlElement::new(&tag);
328 element.namespace.clone_from(&name.namespace);
329 if let Some(namespace) = &name.namespace
330 && (namespace != "DAV:" || prefix != "D")
331 {
332 element
333 .attributes
334 .insert(format!("xmlns:{prefix}"), namespace.clone());
335 }
336 if let Some(child) = child {
337 element.children.push(child);
338 }
339 element
340}
341
342fn default_property_prefix(namespace: Option<&str>) -> &str {
343 match namespace {
344 Some("DAV:") => "D",
345 Some(_) => "A",
346 None => "",
347 }
348}