1use actix_web::http::{StatusCode as ActixStatusCode, header as actix_header};
4use actix_web::{HttpRequest, HttpResponse};
5use bytes::Bytes;
6use futures::{Stream, StreamExt};
7use http::{HeaderMap, HeaderName, HeaderValue, Uri};
8
9use crate::protocol::DavProtocolError;
10use crate::{
11 DavBodyError, DavBodyPolicy, DavCapabilityContext, DavCapabilityProvider,
12 DavCapabilitySnapshot, DavCapabilityTarget, DavConditionalPlan, DavConditionalResource,
13 DavFileSystem, DavIfEvaluationError, DavLockEnforcementError, DavLockSystem, DavMethod,
14 DavMutationCredentials, DavParentCollectionError, DavPath, DavRequestHead, DavRequestOrigin,
15 DavRequestTarget, DavResponse, DavResponseBody, IfHeader,
16};
17
18pub enum DavPreparedBody {
20 None,
21 Xml(Vec<u8>),
22 Bytes(Vec<u8>),
23}
24
25impl DavPreparedBody {
26 #[must_use]
28 pub fn xml(&self) -> &[u8] {
29 match self {
30 Self::None | Self::Bytes(_) => &[],
31 Self::Xml(body) => body,
32 }
33 }
34
35 #[must_use]
37 pub fn bytes(&self) -> &[u8] {
38 match self {
39 Self::Bytes(body) => body,
40 Self::None | Self::Xml(_) => &[],
41 }
42 }
43}
44
45pub fn request_head(
51 request: &HttpRequest,
52 mount_path: &str,
53) -> Result<Option<DavRequestHead>, DavProtocolError> {
54 let request_target = request_target(request, mount_path)?;
55 let Some(method) = DavMethod::from_name(request.method().as_str()) else {
56 return Ok(None);
57 };
58 let headers = convert_header_map(request.headers())?;
59 DavRequestHead::parse_known_method(method, &request_target, &headers).map(Some)
60}
61
62pub fn request_target<'a>(
68 request: &HttpRequest,
69 mount_path: &'a str,
70) -> Result<DavRequestTarget<'a>, DavProtocolError> {
71 let uri: Uri = request
72 .uri()
73 .to_string()
74 .parse()
75 .map_err(|_| DavProtocolError::bad_request("Invalid request URI"))?;
76 let connection = request.connection_info();
77 let origin = DavRequestOrigin {
78 scheme: connection.scheme().to_string(),
79 host: connection.host().to_string(),
80 };
81 DavRequestHead::parse_target(&uri, mount_path, &origin)
82}
83
84pub async fn capability_snapshot<Provider: DavCapabilityProvider>(
90 provider: &Provider,
91 target: &DavCapabilityTarget,
92 context: &DavCapabilityContext,
93) -> Result<DavCapabilitySnapshot, crate::DavCapabilityEvaluationError> {
94 crate::plan_capabilities_with_provider(provider, target, context).await
95}
96
97pub fn gate_request_method(
103 request: &HttpRequest,
104 snapshot: &DavCapabilitySnapshot,
105) -> Result<DavMethod, crate::DavMethodGateError> {
106 crate::gate_method(DavMethod::from_name(request.method().as_str()), snapshot)
107}
108
109pub fn into_response(response: DavResponse) -> HttpResponse {
111 let status = ActixStatusCode::from_u16(response.status.as_u16())
112 .unwrap_or(ActixStatusCode::INTERNAL_SERVER_ERROR);
113 let mut builder = HttpResponse::build(status);
114 for (name, value) in &response.headers {
115 let name = actix_header::HeaderName::from_bytes(name.as_str().as_bytes());
116 let value = actix_header::HeaderValue::from_bytes(value.as_bytes());
117 if let (Ok(name), Ok(value)) = (name, value) {
118 builder.insert_header((name, value));
119 }
120 }
121 match response.body {
122 DavResponseBody::Empty => builder.finish(),
123 DavResponseBody::Bytes(body) => builder.body(body),
124 DavResponseBody::Stream(stream) => streaming_response(&mut builder, stream),
125 DavResponseBody::MultiStatus(stream) => streaming_response(&mut builder, stream),
126 }
127}
128
129fn streaming_response<S, E>(builder: &mut actix_web::HttpResponseBuilder, stream: S) -> HttpResponse
130where
131 S: Stream<Item = Result<Bytes, E>> + 'static,
132 E: 'static,
133{
134 let stream = stream.map(|item| {
135 item.map_err(|_| {
136 actix_web::error::ErrorInternalServerError("WebDAV response stream failed")
137 })
138 });
139 builder.streaming(stream)
140}
141
142#[must_use]
144#[expect(
145 clippy::needless_pass_by_value,
146 reason = "The owned error signature composes directly with Result::map_err at the Actix boundary."
147)]
148pub fn protocol_error_response(error: DavProtocolError) -> HttpResponse {
149 into_response(crate::protocol_error_response(&error))
150}
151
152pub fn converted_headers(source: &actix_header::HeaderMap) -> Result<HeaderMap, DavProtocolError> {
158 convert_header_map(source)
159}
160
161pub async fn enforce_if_header_with_backends(
167 if_header: Option<&IfHeader>,
168 filesystem: &dyn DavFileSystem,
169 lock_system: &dyn DavLockSystem,
170 request_path: &DavPath,
171 prefix: &str,
172 request_scheme: &str,
173 request_host: &str,
174) -> Result<(), DavIfEvaluationError> {
175 crate::enforce_if_header_with_backends(
176 if_header,
177 filesystem,
178 lock_system,
179 request_path,
180 prefix,
181 request_scheme,
182 request_host,
183 )
184 .await
185}
186
187pub async fn enforce_unlocked(
193 lock_system: &dyn DavLockSystem,
194 path: &DavPath,
195 deep: bool,
196 prefix: &str,
197 if_header: Option<&IfHeader>,
198 request_scheme: &str,
199 request_host: &str,
200) -> Result<DavMutationCredentials, DavLockEnforcementError> {
201 crate::enforce_unlocked(
202 lock_system,
203 path,
204 deep,
205 prefix,
206 if_header,
207 request_scheme,
208 request_host,
209 )
210 .await
211}
212
213pub async fn enforce_parent_unlocked(
219 lock_system: &dyn DavLockSystem,
220 path: &DavPath,
221 prefix: &str,
222 if_header: Option<&IfHeader>,
223 request_scheme: &str,
224 request_host: &str,
225) -> Result<DavMutationCredentials, DavLockEnforcementError> {
226 crate::enforce_parent_unlocked(
227 lock_system,
228 path,
229 prefix,
230 if_header,
231 request_scheme,
232 request_host,
233 )
234 .await
235}
236
237pub fn plan_http_conditionals(
243 headers: &actix_header::HeaderMap,
244 method: DavMethod,
245 resource: DavConditionalResource<'_>,
246) -> Result<DavConditionalPlan, crate::DavConditionalPlanError> {
247 let headers = converted_headers(headers)?;
248 crate::plan_http_conditionals(method, &headers, resource)
249}
250
251#[must_use]
253pub fn capability_error_response(error: &crate::DavCapabilityEvaluationError) -> HttpResponse {
254 into_response(crate::capability_evaluation_error_response(error))
255}
256
257#[must_use]
259pub fn method_gate_error_response(snapshot: &DavCapabilitySnapshot) -> HttpResponse {
260 into_response(crate::method_not_allowed_response(snapshot))
261}
262
263#[must_use]
265pub fn conditional_plan_error_response(error: &crate::DavConditionalPlanError) -> HttpResponse {
266 into_response(crate::conditional_plan_error_response(error))
267}
268
269#[must_use]
271pub fn lock_enforcement_error_response(
272 error: DavLockEnforcementError,
273 prefix: &str,
274) -> HttpResponse {
275 match error {
276 DavLockEnforcementError::Backend(error) => {
277 into_response(crate::backend_error_response(&error))
278 }
279 DavLockEnforcementError::Conflict { path } => into_response(
280 crate::lock_conflict_response(prefix, &path)
281 .unwrap_or_else(|_| DavResponse::empty(http::StatusCode::INTERNAL_SERVER_ERROR)),
282 ),
283 }
284}
285
286#[must_use]
288pub fn parent_collection_error_response(error: DavParentCollectionError) -> HttpResponse {
289 match error {
290 DavParentCollectionError::MethodNotAllowed => into_response(
291 crate::mutation_plan_error_response(crate::DavMutationPlanError::MethodNotAllowed),
292 ),
293 DavParentCollectionError::Conflict => into_response(crate::mutation_plan_error_response(
294 crate::DavMutationPlanError::Conflict,
295 )),
296 DavParentCollectionError::Backend(error) => {
297 into_response(crate::backend_error_response(&error))
298 }
299 }
300}
301
302pub fn convert_header_map(source: &actix_header::HeaderMap) -> Result<HeaderMap, DavProtocolError> {
308 let mut headers = HeaderMap::with_capacity(source.len());
309 for (name, value) in source {
310 let name = HeaderName::from_bytes(name.as_str().as_bytes())
311 .map_err(|_| DavProtocolError::bad_request("Invalid request header"))?;
312 let value = HeaderValue::from_bytes(value.as_bytes())
313 .map_err(|_| DavProtocolError::bad_request("Invalid request header"))?;
314 headers.append(name, value);
315 }
316 Ok(headers)
317}
318
319pub async fn ensure_empty_body(payload: &mut actix_web::web::Payload) -> Result<(), DavBodyError> {
325 while let Some(chunk) = payload.next().await {
326 let chunk = chunk.map_err(|_| DavBodyError::ReadFailed)?;
327 if !chunk.is_empty() {
328 return Err(DavBodyError::BodyNotAllowed);
329 }
330 }
331 Ok(())
332}
333
334pub async fn collect_bounded_body(
340 payload: &mut actix_web::web::Payload,
341 maximum: usize,
342) -> Result<Vec<u8>, DavBodyError> {
343 let mut body = Vec::with_capacity(maximum.min(4096));
344 while let Some(chunk) = payload.next().await {
345 let chunk = chunk.map_err(|_| DavBodyError::ReadFailed)?;
346 let next_len = body
347 .len()
348 .checked_add(chunk.len())
349 .ok_or(DavBodyError::BodyTooLarge)?;
350 if next_len > maximum {
351 return Err(DavBodyError::BodyTooLarge);
352 }
353 body.extend_from_slice(&chunk);
354 }
355 Ok(body)
356}
357
358pub async fn prepare_request_body(
364 policy: DavBodyPolicy,
365 payload: &mut actix_web::web::Payload,
366) -> Result<DavPreparedBody, DavBodyError> {
367 match policy {
368 DavBodyPolicy::Empty => ensure_empty_body(payload)
369 .await
370 .map(|()| DavPreparedBody::None),
371 DavBodyPolicy::BoundedXml { maximum } => collect_bounded_body(payload, maximum)
372 .await
373 .map(DavPreparedBody::Xml),
374 DavBodyPolicy::Bounded { maximum } => collect_bounded_body(payload, maximum)
375 .await
376 .map(DavPreparedBody::Bytes),
377 DavBodyPolicy::Stream | DavBodyPolicy::Unused => Ok(DavPreparedBody::None),
378 }
379}