1use actix_web::http::{StatusCode as ActixStatusCode, header as actix_header};
4use actix_web::{HttpRequest, HttpResponse};
5use futures::StreamExt;
6use http::{HeaderMap, HeaderName, HeaderValue, Uri};
7
8use crate::protocol::DavProtocolError;
9use crate::{
10 DavBodyError, DavBodyPolicy, DavFileSystem, DavIfEvaluationError, DavLockSystem, DavMethod,
11 DavPath, DavPrecondition, DavRequestHead, DavRequestOrigin, DavResponse, DavResponseBody,
12 IfHeader,
13};
14
15pub enum DavPreparedBody {
17 None,
18 Xml(Vec<u8>),
19}
20
21impl DavPreparedBody {
22 #[must_use]
24 pub fn xml(&self) -> &[u8] {
25 match self {
26 Self::None => &[],
27 Self::Xml(body) => body,
28 }
29 }
30}
31
32pub fn request_head(
34 request: &HttpRequest,
35 mount_path: &str,
36) -> Result<Option<DavRequestHead>, DavProtocolError> {
37 let Some(method) = DavMethod::from_name(request.method().as_str()) else {
38 return Ok(None);
39 };
40 let uri: Uri = request
41 .uri()
42 .to_string()
43 .parse()
44 .map_err(|_| DavProtocolError::bad_request("Invalid request URI"))?;
45 let headers = convert_header_map(request.headers())?;
46 let connection = request.connection_info();
47 let origin = DavRequestOrigin {
48 scheme: connection.scheme().to_string(),
49 host: connection.host().to_string(),
50 };
51 DavRequestHead::parse(method, &uri, &headers, mount_path, &origin).map(Some)
52}
53
54pub fn into_response(response: DavResponse) -> HttpResponse {
56 let status = ActixStatusCode::from_u16(response.status.as_u16())
57 .unwrap_or(ActixStatusCode::INTERNAL_SERVER_ERROR);
58 let mut builder = HttpResponse::build(status);
59 for (name, value) in &response.headers {
60 let name = actix_header::HeaderName::from_bytes(name.as_str().as_bytes());
61 let value = actix_header::HeaderValue::from_bytes(value.as_bytes());
62 if let (Ok(name), Ok(value)) = (name, value) {
63 builder.insert_header((name, value));
64 }
65 }
66 match response.body {
67 DavResponseBody::Empty => builder.finish(),
68 DavResponseBody::Bytes(body) => builder.body(body),
69 DavResponseBody::Stream(stream) => {
70 let stream = stream.map(|item| {
71 item.map_err(|error| actix_web::error::ErrorInternalServerError(error.to_string()))
72 });
73 builder.streaming(stream)
74 }
75 }
76}
77
78#[must_use]
80pub fn protocol_error_response(error: DavProtocolError) -> HttpResponse {
81 into_response(crate::protocol_error_response(&error))
82}
83
84pub fn converted_headers(source: &actix_header::HeaderMap) -> Result<HeaderMap, HttpResponse> {
86 convert_header_map(source).map_err(protocol_error_response)
87}
88
89pub async fn enforce_if_header_with_backends(
91 if_header: Option<&IfHeader>,
92 filesystem: &dyn DavFileSystem,
93 lock_system: &dyn DavLockSystem,
94 request_path: &DavPath,
95 prefix: &str,
96 request_scheme: &str,
97 request_host: &str,
98) -> Result<(), HttpResponse> {
99 match crate::enforce_if_header_with_backends(
100 if_header,
101 filesystem,
102 lock_system,
103 request_path,
104 prefix,
105 request_scheme,
106 request_host,
107 )
108 .await
109 {
110 Ok(()) => Ok(()),
111 Err(DavIfEvaluationError::Protocol(error)) => Err(protocol_error_response(error)),
112 Err(DavIfEvaluationError::Backend(error)) => {
113 Err(into_response(crate::backend_error_response(&error)))
114 }
115 }
116}
117
118pub async fn enforce_unlocked(
120 lock_system: &dyn DavLockSystem,
121 path: &DavPath,
122 deep: bool,
123 prefix: &str,
124 if_header: Option<&IfHeader>,
125 request_scheme: &str,
126 request_host: &str,
127) -> Result<(), HttpResponse> {
128 crate::enforce_unlocked(
129 lock_system,
130 path,
131 deep,
132 prefix,
133 if_header,
134 request_scheme,
135 request_host,
136 )
137 .await
138 .map_err(into_response)
139}
140
141pub async fn enforce_parent_unlocked(
143 lock_system: &dyn DavLockSystem,
144 path: &DavPath,
145 prefix: &str,
146 if_header: Option<&IfHeader>,
147 request_scheme: &str,
148 request_host: &str,
149) -> Result<(), HttpResponse> {
150 crate::enforce_parent_unlocked(
151 lock_system,
152 path,
153 prefix,
154 if_header,
155 request_scheme,
156 request_host,
157 )
158 .await
159 .map_err(into_response)
160}
161
162pub fn evaluate_http_etag_preconditions(
164 headers: &actix_header::HeaderMap,
165 resource_exists: bool,
166 current_etag: Option<&str>,
167 safe_method: bool,
168) -> Result<DavPrecondition, HttpResponse> {
169 let headers = converted_headers(headers)?;
170 crate::evaluate_http_etag_preconditions(&headers, resource_exists, current_etag, safe_method)
171 .map_err(protocol_error_response)
172}
173
174pub fn convert_header_map(source: &actix_header::HeaderMap) -> Result<HeaderMap, DavProtocolError> {
176 let mut headers = HeaderMap::with_capacity(source.len());
177 for (name, value) in source {
178 let name = HeaderName::from_bytes(name.as_str().as_bytes())
179 .map_err(|_| DavProtocolError::bad_request("Invalid request header"))?;
180 let value = HeaderValue::from_bytes(value.as_bytes())
181 .map_err(|_| DavProtocolError::bad_request("Invalid request header"))?;
182 headers.append(name, value);
183 }
184 Ok(headers)
185}
186
187pub async fn ensure_empty_body(payload: &mut actix_web::web::Payload) -> Result<(), DavBodyError> {
189 while let Some(chunk) = payload.next().await {
190 let chunk = chunk.map_err(|_| DavBodyError::ReadFailed)?;
191 if !chunk.is_empty() {
192 return Err(DavBodyError::BodyNotAllowed);
193 }
194 }
195 Ok(())
196}
197
198pub async fn collect_bounded_xml_body(
200 payload: &mut actix_web::web::Payload,
201 maximum: usize,
202) -> Result<Vec<u8>, DavBodyError> {
203 let mut body = Vec::with_capacity(maximum.min(4096));
204 while let Some(chunk) = payload.next().await {
205 let chunk = chunk.map_err(|_| DavBodyError::ReadFailed)?;
206 let next_len = body
207 .len()
208 .checked_add(chunk.len())
209 .ok_or(DavBodyError::XmlTooLarge)?;
210 if next_len > maximum {
211 return Err(DavBodyError::XmlTooLarge);
212 }
213 body.extend_from_slice(&chunk);
214 }
215 Ok(body)
216}
217
218pub async fn prepare_request_body(
220 method: DavMethod,
221 payload: &mut actix_web::web::Payload,
222 xml_limit: usize,
223) -> Result<DavPreparedBody, DavBodyError> {
224 match method.body_policy() {
225 DavBodyPolicy::Empty => ensure_empty_body(payload)
226 .await
227 .map(|()| DavPreparedBody::None),
228 DavBodyPolicy::BoundedXml => collect_bounded_xml_body(payload, xml_limit)
229 .await
230 .map(DavPreparedBody::Xml),
231 DavBodyPolicy::Stream | DavBodyPolicy::Unused => Ok(DavPreparedBody::None),
232 }
233}