1use std::time::Duration;
4
5use http::header::{CACHE_CONTROL, CONTENT_TYPE};
6use http::{HeaderMap, HeaderValue, StatusCode};
7
8use crate::DavLockSystem;
9use crate::response::{no_store_empty_response, xml_request_error_response};
10use crate::{
11 DavErrorCondition, DavFileSystem, DavLock, DavLockXml, DavPath, DavProtocolError,
12 DavRequestHead, DavResponse, DavXmlElement, DavXmlError, FsError, IfHeader, OpenOptions,
13 dav_error_element, dav_lock_discovery_element, dav_lock_response_element, href_for_dav_path,
14 parent_relative_path, parse_lock_request, parse_lock_timeout, protocol_error_response,
15 submitted_lock_tokens,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum DavLockPlan {
21 Acquire {
22 owner: Option<DavXmlElement>,
23 timeout: Duration,
24 shared: bool,
25 deep: bool,
26 },
27 Refresh {
28 token: String,
29 timeout: Duration,
30 },
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
35pub enum DavLockPlanError {
36 #[error(transparent)]
37 Protocol(#[from] DavProtocolError),
38 #[error(transparent)]
39 Xml(#[from] DavXmlError),
40}
41
42pub async fn enforce_unlocked(
44 lock_system: &dyn DavLockSystem,
45 path: &DavPath,
46 deep: bool,
47 prefix: &str,
48 if_header: Option<&IfHeader>,
49 request_scheme: &str,
50 request_host: &str,
51) -> Result<(), DavResponse> {
52 if let Some(lock) = unsubmitted_lock_conflicts(
53 lock_system,
54 path,
55 deep,
56 prefix,
57 if_header,
58 request_scheme,
59 request_host,
60 )
61 .await
62 .into_iter()
63 .next()
64 {
65 return Err(lock_conflict_response(prefix, &lock.path)
66 .unwrap_or_else(|_| DavResponse::empty(StatusCode::INTERNAL_SERVER_ERROR)));
67 }
68 Ok(())
69}
70
71pub async fn unsubmitted_lock_conflicts(
73 lock_system: &dyn DavLockSystem,
74 path: &DavPath,
75 deep: bool,
76 prefix: &str,
77 if_header: Option<&IfHeader>,
78 request_scheme: &str,
79 request_host: &str,
80) -> Vec<crate::DavLock> {
81 let mut conflicts = lock_system.conflicting_locks(path, deep).await;
82 conflicts.retain(|lock| {
83 let lock_href = href_for_dav_path(prefix, &lock.path);
84 let submitted_tokens = if_header.map_or_else(Vec::new, |if_header| {
85 submitted_lock_tokens(if_header, &lock_href, request_scheme, request_host)
86 });
87 !submitted_tokens.iter().any(|token| token == &lock.token)
88 });
89 conflicts
90}
91
92pub async fn enforce_parent_unlocked(
94 lock_system: &dyn DavLockSystem,
95 path: &DavPath,
96 prefix: &str,
97 if_header: Option<&IfHeader>,
98 request_scheme: &str,
99 request_host: &str,
100) -> Result<(), DavResponse> {
101 let Some(parent) = parent_relative_path(path.as_str()) else {
102 return Ok(());
103 };
104 let parent_path = DavPath::new(&parent).map_err(|_| {
105 protocol_error_response(&DavProtocolError::bad_request("Invalid request path"))
106 })?;
107 enforce_unlocked(
108 lock_system,
109 &parent_path,
110 false,
111 prefix,
112 if_header,
113 request_scheme,
114 request_host,
115 )
116 .await
117}
118
119pub async fn ensure_lock_target_exists(
124 filesystem: &dyn DavFileSystem,
125 path: &DavPath,
126) -> Result<bool, FsError> {
127 match filesystem.metadata(path).await {
128 Ok(_) => Ok(true),
129 Err(FsError::NotFound) if !path.is_collection() => {
130 let mut file = filesystem
131 .open(
132 path,
133 OpenOptions {
134 write: true,
135 create: true,
136 truncate: true,
137 size: Some(0),
138 ..OpenOptions::default()
139 },
140 )
141 .await?;
142 file.flush().await?;
143 Ok(false)
144 }
145 Err(FsError::NotFound) => Err(FsError::NotFound),
146 Err(error) => Err(error),
147 }
148}
149
150pub fn plan_lock_request(
152 headers: &HeaderMap,
153 body: &[u8],
154 request_head: &DavRequestHead,
155 prefix: &str,
156 maximum_timeout: Duration,
157) -> Result<DavLockPlan, DavLockPlanError> {
158 let timeout = parse_lock_timeout(headers, maximum_timeout)?;
159 if body.is_empty() {
160 let request_href = href_for_dav_path(prefix, &request_head.target);
161 let tokens = request_head
162 .if_header
163 .as_ref()
164 .map_or_else(Vec::new, |if_header| {
165 submitted_lock_tokens(
166 if_header,
167 &request_href,
168 &request_head.origin.scheme,
169 &request_head.origin.host,
170 )
171 });
172 if tokens.len() != 1 {
173 return Err(DavProtocolError::bad_request("Invalid LOCK refresh token").into());
174 }
175 return Ok(DavLockPlan::Refresh {
176 token: tokens[0].clone(),
177 timeout,
178 });
179 }
180
181 let request = parse_lock_request(body)?;
182 let depth = request_head
183 .depth
184 .ok_or_else(|| DavProtocolError::bad_request("LOCK Depth was not parsed"))?;
185 Ok(DavLockPlan::Acquire {
186 owner: request.owner,
187 timeout,
188 shared: request.shared,
189 deep: depth.is_infinity(),
190 })
191}
192
193fn lock_success_response(
194 lock: &DavLock,
195 status: StatusCode,
196 prefix: &str,
197 include_lock_token_header: bool,
198) -> Result<DavResponse, DavXmlError> {
199 let body = dav_lock_response_element(&[DavLockXml {
200 token: lock.token.clone(),
201 owner: lock.owner.as_deref().cloned(),
202 timeout: lock.timeout,
203 shared: lock.shared,
204 deep: lock.deep,
205 root_href: href_for_dav_path(prefix, &lock.path),
206 }])
207 .to_bytes()?;
208 let mut response = DavResponse::bytes(status, body);
209 response.headers.insert(
210 CONTENT_TYPE,
211 HeaderValue::from_static("application/xml; charset=utf-8"),
212 );
213 if include_lock_token_header {
214 let value = HeaderValue::from_str(&format!("<{}>", lock.token))
215 .map_err(|_| DavXmlError::Malformed)?;
216 response.headers.insert("Lock-Token", value);
217 }
218 Ok(response)
219}
220
221pub fn lock_refresh_success_response(
223 lock: &DavLock,
224 prefix: &str,
225) -> Result<DavResponse, DavXmlError> {
226 lock_success_response(lock, StatusCode::OK, prefix, false)
227}
228
229pub fn lock_acquire_success_response(
231 lock: &DavLock,
232 prefix: &str,
233 resource_existed: bool,
234) -> Result<DavResponse, DavXmlError> {
235 lock_success_response(
236 lock,
237 if resource_existed {
238 StatusCode::OK
239 } else {
240 StatusCode::CREATED
241 },
242 prefix,
243 true,
244 )
245}
246
247#[must_use]
249pub fn lock_discovery_element(locks: &[DavLock], prefix: &str) -> DavXmlElement {
250 let locks = locks
251 .iter()
252 .map(|lock| DavLockXml {
253 token: lock.token.clone(),
254 owner: lock.owner.as_deref().cloned(),
255 timeout: lock.timeout,
256 shared: lock.shared,
257 deep: lock.deep,
258 root_href: href_for_dav_path(prefix, &lock.path),
259 })
260 .collect::<Vec<_>>();
261 dav_lock_discovery_element(&locks)
262}
263
264pub fn lock_conflict_response(prefix: &str, path: &DavPath) -> Result<DavResponse, DavXmlError> {
266 lock_condition_response(
267 StatusCode::LOCKED,
268 DavErrorCondition::LockTokenSubmitted {
269 href: href_for_dav_path(prefix, path),
270 },
271 )
272}
273
274pub fn unlock_token_mismatch_response() -> Result<DavResponse, DavXmlError> {
276 lock_condition_response(
277 StatusCode::CONFLICT,
278 DavErrorCondition::LockTokenMatchesRequestUri,
279 )
280}
281
282#[must_use]
284pub fn unlock_success_response() -> DavResponse {
285 no_store_empty_response(StatusCode::NO_CONTENT)
286}
287
288#[must_use]
290pub fn lock_limit_response() -> DavResponse {
291 let mut response = DavResponse::bytes(
292 StatusCode::INSUFFICIENT_STORAGE,
293 "WebDAV active lock limit exceeded",
294 );
295 response.headers.insert(
296 CONTENT_TYPE,
297 HeaderValue::from_static("text/plain; charset=utf-8"),
298 );
299 response
300 .headers
301 .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
302 response
303}
304
305pub fn lock_xml_error_response(error: DavXmlError) -> Result<DavResponse, DavXmlError> {
307 xml_request_error_response(error, "Invalid LOCK body")
308}
309
310fn lock_condition_response(
311 status: StatusCode,
312 condition: DavErrorCondition,
313) -> Result<DavResponse, DavXmlError> {
314 let mut response = DavResponse::bytes(status, dav_error_element(&condition).to_bytes()?);
315 response.headers.insert(
316 CONTENT_TYPE,
317 HeaderValue::from_static("application/xml; charset=utf-8"),
318 );
319 response
320 .headers
321 .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
322 Ok(response)
323}