1use http_body_util::{BodyExt, Empty, Full};
2use hyper::{
3 HeaderMap, StatusCode,
4 body::{Body, Bytes},
5 header::{
6 ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_LENGTH, HeaderName,
7 HeaderValue, ORIGIN, VARY,
8 },
9 http::response::Builder,
10};
11
12use std::{collections::HashMap, str::FromStr};
13
14use super::{
15 constant::DEFAULT_RESPONSE_HEADERS, response::error_response::internal_server_error_response,
16};
17use crate::types::BoxBody;
18
19#[derive(Clone, Default)]
20pub enum BodyKind {
21 #[default]
22 Empty,
23 Text(String),
24 Binary(Vec<u8>),
25}
26
27#[derive(Default)]
28pub struct ResponseHandler {
29 response_builder: Builder,
30 status: Option<StatusCode>,
31 headers: HashMap<String, Option<String>>,
32 body_kind: BodyKind,
33}
34
35impl ResponseHandler {
36 pub fn into_response(
38 self,
39 request_headers: &HeaderMap,
40 cors_allow_credentials_origins: &[String],
41 ) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
42 let response = match self.body_kind {
44 BodyKind::Text(s) => self
45 .response_builder
46 .body(Full::new(Bytes::from(s.to_owned())).boxed()),
47 BodyKind::Binary(b) => self
48 .response_builder
49 .body(Full::new(Bytes::from(b)).boxed()),
50 BodyKind::Empty => self.response_builder.body(Empty::new().boxed()),
51 };
52
53 let mut response = match response {
54 Ok(x) => x,
55 Err(err) => {
56 return internal_server_error_response(
57 &format!("failed to create response: {}", err),
58 request_headers,
59 cors_allow_credentials_origins,
60 );
61 }
62 };
63
64 *response.status_mut() = if let Some(status) = self.status {
66 status
67 } else {
68 StatusCode::OK
69 };
70
71 let content_length = response.body().size_hint().exact().unwrap_or_default();
73
74 let headers = response.headers_mut();
75
76 headers.insert(CONTENT_LENGTH, HeaderValue::from(content_length));
77
78 for (header_key, header_value) in
80 default_response_headers(request_headers, cors_allow_credentials_origins).iter()
81 {
82 headers.insert(header_key, header_value.to_owned());
83 }
84
85 for (header_key, header_value) in self.headers {
87 match HeaderName::from_str(header_key.as_str()) {
88 Ok(header_key) => {
89 match HeaderValue::from_str(header_value.unwrap_or_default().as_str()) {
90 Ok(header_value) => {
91 headers.insert(header_key, header_value);
92 }
93 Err(err) => {
94 log::warn!(
95 "failed to create header with the header value (header key = {}) ({})",
96 header_key,
97 err
98 );
99 headers.insert(header_key, HeaderValue::from_static(""));
100 }
101 }
102 }
103 Err(err) => log::warn!(
104 "failed to create header with the header key: {} ({})",
105 header_key,
106 err
107 ),
108 };
109 }
110
111 Ok(response)
112 }
113
114 pub fn with_status(mut self, status: &StatusCode) -> Self {
116 self.status = Some(status.to_owned());
117 self
118 }
119
120 pub fn with_header(mut self, key: impl Into<String>, value: Option<impl Into<String>>) -> Self {
122 self.headers.insert(key.into(), value.map(|x| x.into()));
123 self
124 }
125
126 pub fn with_headers<K, V, I>(mut self, headers: I) -> Self
128 where
129 K: Into<String>,
130 V: Into<String>,
131 I: IntoIterator<Item = (K, Option<V>)>,
132 {
133 for (key, value) in headers {
134 self.headers.insert(key.into(), value.map(|x| x.into()));
135 }
136 self
137 }
138
139 pub fn with_custom_headers(
160 self,
161 custom_headers: Option<&HashMap<String, Option<String>>>,
162 ) -> Self {
163 match custom_headers {
164 Some(custom_headers) => self.with_headers(custom_headers.to_owned()),
165 None => self,
166 }
167 }
168
169 pub fn with_text(mut self, text: impl Into<String>, content_type: Option<&str>) -> Self {
171 let content_type = if let Some(content_type) = content_type {
172 content_type.into()
173 } else {
174 "text/plain; charset=utf-8".to_owned()
175 };
176 self.headers
177 .insert("content-type".into(), Some(content_type));
178
179 self.body_kind = BodyKind::Text(text.into());
180 self
181 }
182
183 pub fn with_json_body(mut self, body: impl Into<String>) -> Self {
185 self.headers
186 .insert("content-type".into(), Some("application/json".into()));
187 self.body_kind = BodyKind::Text(body.into());
188 self
189 }
190
191 pub fn with_binary_body(
193 mut self,
194 body: Vec<u8>,
195 content_type: Option<impl Into<String>>,
196 ) -> Self {
197 let content_type = if let Some(content_type) = content_type {
198 content_type.into()
199 } else {
200 "application/octet-stream".to_owned()
201 };
202 self.headers
203 .insert("content-type".into(), Some(content_type));
204
205 self.body_kind = BodyKind::Binary(body);
206
207 self
208 }
209}
210
211pub fn default_response_headers(
223 request_headers: &HeaderMap,
224 cors_allow_credentials_origins: &[String],
225) -> HeaderMap {
226 let mut header_map_src = Vec::with_capacity(DEFAULT_RESPONSE_HEADERS.len() + 1);
227
228 header_map_src.extend(
231 DEFAULT_RESPONSE_HEADERS
232 .iter()
233 .map(|(k, v)| (k.to_string(), v.to_string())),
234 );
235
236 let origin = if is_likely_authenticated_request(request_headers) {
249 request_headers
250 .get(ORIGIN)
251 .and_then(|value| value.to_str().ok())
252 .filter(|origin| {
253 is_credentialed_reflection_allowed(origin, cors_allow_credentials_origins)
254 })
255 .and_then(|origin| HeaderValue::from_str(origin).ok())
256 } else {
257 None
258 };
259 let (origin, vary) = if let Some(origin) = origin {
260 header_map_src.push((
261 ACCESS_CONTROL_ALLOW_CREDENTIALS.to_string(),
262 "true".to_owned(),
263 ));
264
265 (origin, HeaderValue::from_static("Origin"))
266 } else {
267 (HeaderValue::from_static("*"), HeaderValue::from_static("*"))
268 };
269 header_map_src.push((
270 ACCESS_CONTROL_ALLOW_ORIGIN.to_string(),
271 origin.to_str().unwrap_or_default().to_owned(),
272 ));
273 header_map_src.push((
274 VARY.to_string(),
275 vary.to_str().unwrap_or_default().to_owned(),
276 ));
277
278 header_map_src
280 .iter()
281 .fold(HeaderMap::new(), |mut ret, (header_key, header_value)| {
282 match HeaderName::from_str(header_key) {
283 Ok(header_key) => match HeaderValue::from_str(header_value.as_str()) {
284 Ok(header_value) => {
285 ret.insert(header_key, header_value);
286 ret
287 }
288 Err(err) => {
289 log::warn!(
290 "only header key set because failed to get header value: {} [key = {}] ({})",
291 header_value.as_str(),
292 header_key,
293 err
294 );
295 ret.insert(header_key, HeaderValue::from_static(""));
296 ret
297 }
298 },
299 Err(err) => {
300 log::warn!("failed to set header key: {} ({})", header_key, err);
301 ret
302 }
303 }
304 })
305}
306
307fn is_likely_authenticated_request(request_headers: &HeaderMap) -> bool {
309 request_headers.contains_key("cookie") || request_headers.contains_key("authorization")
310}
311
312fn is_credentialed_reflection_allowed(
317 origin: &str,
318 cors_allow_credentials_origins: &[String],
319) -> bool {
320 is_implicit_loopback_origin(origin)
321 || cors_allow_credentials_origins
322 .iter()
323 .any(|allowed| allowed == origin)
324}
325
326fn is_implicit_loopback_origin(origin: &str) -> bool {
342 for prefix in ["http://localhost", "http://127.0.0.1"] {
343 let Some(rest) = origin.strip_prefix(prefix) else {
344 continue;
345 };
346 if rest.is_empty() {
347 return true;
348 }
349 return rest
350 .strip_prefix(':')
351 .is_some_and(|port| !port.is_empty() && port.bytes().all(|b| b.is_ascii_digit()));
352 }
353 false
354}