1use super::header::{
2 accepts_event_stream_response, accepts_json_response, get_header, is_json_media_type,
3 matches_media_type, normalize_header_name, normalize_headers, parse_content_length,
4 strict_content_length_values, validate_header_name, validate_header_value,
5};
6use super::method::HttpMethod;
7use super::query::{parse_query, parse_query_pairs, split_path_query};
8use crate::percent::validate_percent_encoding;
9use crate::routing::host::normalize_host_header;
10#[cfg(feature = "auth")]
11use crate::AuthPrincipal;
12use crate::{validate_value, BootError, ModuleRef, ProviderToken, Result, Validate};
13use serde::{de::DeserializeOwned, Serialize};
14use std::collections::BTreeMap;
15use std::fmt;
16use std::str::FromStr;
17use std::sync::Arc;
18#[cfg(feature = "auth")]
19use std::sync::RwLock;
20
21#[derive(Debug, Clone)]
23pub struct BootRequest {
24 pub method: HttpMethod,
25 pub path: String,
26 pub query_string: Option<String>,
27 pub query: BTreeMap<String, String>,
28 pub params: BTreeMap<String, String>,
29 pub host_params: BTreeMap<String, String>,
30 pub headers: BTreeMap<String, String>,
31 pub appended_headers: Vec<(String, String)>,
32 pub body: Vec<u8>,
33 module_ref: Option<ModuleRef>,
34 #[cfg(feature = "auth")]
35 auth_principal: Arc<RwLock<Option<AuthPrincipal>>>,
36}
37
38impl PartialEq for BootRequest {
39 fn eq(&self, other: &Self) -> bool {
40 self.method == other.method
41 && self.path == other.path
42 && self.query_string == other.query_string
43 && self.query == other.query
44 && self.params == other.params
45 && self.host_params == other.host_params
46 && self.headers == other.headers
47 && self.appended_headers == other.appended_headers
48 && self.body == other.body
49 }
50}
51
52impl Eq for BootRequest {}
53
54impl BootRequest {
55 pub fn new(method: HttpMethod, path: impl Into<String>) -> Self {
56 let (path, query_string, query) = split_path_query(path.into());
57 Self {
58 method,
59 path,
60 query_string,
61 query,
62 params: BTreeMap::new(),
63 host_params: BTreeMap::new(),
64 headers: BTreeMap::new(),
65 appended_headers: Vec::new(),
66 body: Vec::new(),
67 module_ref: None,
68 #[cfg(feature = "auth")]
69 auth_principal: Arc::new(RwLock::new(None)),
70 }
71 }
72
73 pub fn method(&self) -> HttpMethod {
74 self.method
75 }
76
77 pub fn path(&self) -> &str {
78 &self.path
79 }
80
81 pub fn query_string(&self) -> Option<&str> {
82 self.query_string.as_deref()
83 }
84
85 pub fn with_query_string(mut self, query_string: impl Into<String>) -> Self {
86 let query_string = query_string.into();
87 self.query = parse_query(&query_string);
88 self.query_string = Some(query_string);
89 self
90 }
91
92 pub(crate) fn with_matched_path(mut self, path: impl Into<String>) -> Self {
93 self.path = path.into();
94 self
95 }
96
97 pub(crate) fn with_module_ref(mut self, module_ref: ModuleRef) -> Self {
98 self.module_ref = Some(module_ref);
99 self
100 }
101
102 pub fn module_ref(&self) -> Option<&ModuleRef> {
103 self.module_ref.as_ref()
104 }
105
106 pub fn get<T>(&self) -> Result<Arc<T>>
107 where
108 T: Send + Sync + 'static,
109 {
110 self.module_ref
111 .as_ref()
112 .ok_or_else(|| BootError::MissingProvider(ProviderToken::of::<T>().to_string()))?
113 .get::<T>()
114 }
115
116 pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
117 where
118 T: Send + Sync + 'static,
119 {
120 self.module_ref
121 .as_ref()
122 .ok_or_else(|| BootError::MissingProvider(ProviderToken::named(token).to_string()))?
123 .get_named::<T>(token)
124 }
125
126 pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
127 where
128 T: Send + Sync + 'static,
129 {
130 match &self.module_ref {
131 Some(module_ref) => module_ref.get_optional::<T>(),
132 None => Ok(None),
133 }
134 }
135
136 pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
137 where
138 T: Send + Sync + 'static,
139 {
140 match &self.module_ref {
141 Some(module_ref) => module_ref.get_optional_named::<T>(token),
142 None => Ok(None),
143 }
144 }
145
146 #[cfg(feature = "auth")]
147 pub fn with_auth_principal(mut self, principal: AuthPrincipal) -> Self {
148 self.auth_principal = Arc::new(RwLock::new(Some(principal)));
149 self
150 }
151
152 #[cfg(feature = "auth")]
153 pub fn set_auth_principal(&self, principal: AuthPrincipal) -> Result<()> {
154 *self
155 .auth_principal
156 .write()
157 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))? =
158 Some(principal);
159 Ok(())
160 }
161
162 #[cfg(feature = "auth")]
163 pub fn clear_auth_principal(&self) -> Result<()> {
164 *self
165 .auth_principal
166 .write()
167 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))? =
168 None;
169 Ok(())
170 }
171
172 #[cfg(feature = "auth")]
173 pub fn auth_principal(&self) -> Result<Option<AuthPrincipal>> {
174 Ok(self
175 .auth_principal
176 .read()
177 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))?
178 .clone())
179 }
180
181 #[cfg(feature = "auth")]
182 pub fn require_auth_principal(&self) -> Result<AuthPrincipal> {
183 self.auth_principal()?
184 .ok_or_else(|| BootError::Unauthorized("missing authenticated principal".to_string()))
185 }
186
187 #[cfg(feature = "session")]
188 pub fn session(&self) -> Result<crate::Session> {
189 let manager = self.get::<crate::SessionManager>()?;
190 let session_id = manager.require_session_id(self)?;
191 crate::Session::from_manager_arc(manager, session_id)
192 }
193
194 #[cfg(feature = "session")]
195 pub fn optional_session(&self) -> Result<Option<crate::Session>> {
196 let Some(manager) = self.get_optional::<crate::SessionManager>()? else {
197 return Ok(None);
198 };
199 let Some(session_id) = manager.session_id(self)? else {
200 return Ok(None);
201 };
202 crate::Session::from_manager_arc(manager, session_id).map(Some)
203 }
204
205 pub fn with_path_params(mut self, params: BTreeMap<String, String>) -> Self {
206 self.params = params;
207 self
208 }
209
210 pub fn with_host_params(mut self, params: BTreeMap<String, String>) -> Self {
211 self.host_params = params;
212 self
213 }
214
215 pub fn with_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
216 self.params.insert(name.into(), value.into());
217 self
218 }
219
220 pub fn with_host_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
221 self.host_params.insert(name.into(), value.into());
222 self
223 }
224
225 pub fn with_query_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
226 self.query.insert(name.into(), value.into());
227 self.query_string = None;
228 self
229 }
230
231 pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
232 self.body = body.into();
233 self
234 }
235
236 pub fn body(&self) -> &[u8] {
237 &self.body
238 }
239
240 pub fn into_body(self) -> Vec<u8> {
241 self.body
242 }
243
244 pub fn with_text(self, body: impl Into<String>) -> Self {
245 self.with_body(body.into())
246 .with_header("content-type", "text/plain; charset=utf-8")
247 }
248
249 pub fn with_json<T>(self, body: &T) -> Result<Self>
250 where
251 T: Serialize,
252 {
253 let body = serde_json::to_vec(body).map_err(|err| BootError::Internal(err.to_string()))?;
254 Ok(self
255 .with_body(body)
256 .with_header("content-type", "application/json"))
257 }
258
259 pub fn with_content_type(self, content_type: impl Into<String>) -> Self {
260 self.with_header("content-type", content_type)
261 }
262
263 pub fn with_content_length(self, content_length: u64) -> Self {
264 self.with_header("content-length", content_length.to_string())
265 }
266
267 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
268 self.headers
269 .insert(normalize_header_name(name), value.into());
270 self
271 }
272
273 pub fn with_headers(mut self, headers: BTreeMap<String, String>) -> Self {
274 self.headers = normalize_headers(headers);
275 self
276 }
277
278 pub fn append_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
279 self.appended_headers
280 .push((normalize_header_name(name), value.into()));
281 self
282 }
283
284 pub fn header_entries(&self) -> impl Iterator<Item = (&str, &str)> {
285 self.headers
286 .iter()
287 .map(|(name, value)| (name.as_str(), value.as_str()))
288 .chain(
289 self.appended_headers
290 .iter()
291 .map(|(name, value)| (name.as_str(), value.as_str())),
292 )
293 }
294
295 pub fn validate_headers(&self) -> Result<()> {
296 for (name, value) in self.header_entries() {
297 validate_request_header(name, value)?;
298 }
299
300 Ok(())
301 }
302
303 pub fn text(&self) -> Result<String> {
304 String::from_utf8(self.body.clone()).map_err(|err| BootError::BadRequest(err.to_string()))
305 }
306
307 pub fn param(&self, name: &str) -> Option<&str> {
308 self.params.get(name).map(String::as_str)
309 }
310
311 pub fn param_as<T>(&self, name: &str) -> Result<T>
312 where
313 T: FromStr,
314 T::Err: fmt::Display,
315 {
316 parse_required_value(
317 self.param(name).map(ToString::to_string),
318 "path parameter",
319 name,
320 )
321 }
322
323 pub fn optional_param_as<T>(&self, name: &str) -> Result<Option<T>>
324 where
325 T: FromStr,
326 T::Err: fmt::Display,
327 {
328 parse_optional_value(
329 self.param(name).map(ToString::to_string),
330 "path parameter",
331 name,
332 )
333 }
334
335 pub fn host_param(&self, name: &str) -> Option<&str> {
336 self.host_params.get(name).map(String::as_str)
337 }
338
339 pub fn host_param_as<T>(&self, name: &str) -> Result<T>
340 where
341 T: FromStr,
342 T::Err: fmt::Display,
343 {
344 parse_required_value(
345 self.host_param(name).map(ToString::to_string),
346 "host parameter",
347 name,
348 )
349 }
350
351 pub fn optional_host_param_as<T>(&self, name: &str) -> Result<Option<T>>
352 where
353 T: FromStr,
354 T::Err: fmt::Display,
355 {
356 parse_optional_value(
357 self.host_param(name).map(ToString::to_string),
358 "host parameter",
359 name,
360 )
361 }
362
363 pub fn params<T>(&self) -> Result<T>
364 where
365 T: DeserializeOwned,
366 {
367 let params = serde_urlencoded::to_string(&self.params)
368 .map_err(|err| BootError::BadRequest(err.to_string()))?;
369 serde_urlencoded::from_str(¶ms).map_err(|err| BootError::BadRequest(err.to_string()))
370 }
371
372 pub fn validated_params<T>(&self) -> Result<T>
373 where
374 T: DeserializeOwned + Validate,
375 {
376 let value = self.params()?;
377 validate_value(value)
378 }
379
380 pub fn host_params<T>(&self) -> Result<T>
381 where
382 T: DeserializeOwned,
383 {
384 let params = serde_urlencoded::to_string(&self.host_params)
385 .map_err(|err| BootError::BadRequest(err.to_string()))?;
386 serde_urlencoded::from_str(¶ms).map_err(|err| BootError::BadRequest(err.to_string()))
387 }
388
389 pub fn validated_host_params<T>(&self) -> Result<T>
390 where
391 T: DeserializeOwned + Validate,
392 {
393 let value = self.host_params()?;
394 validate_value(value)
395 }
396
397 pub fn query_param(&self, name: &str) -> Option<&str> {
398 self.query.get(name).map(String::as_str)
399 }
400
401 pub fn query_value(&self, name: &str) -> Result<Option<String>> {
402 Ok(self
403 .query_pairs()?
404 .into_iter()
405 .find_map(|(key, value)| (key == name).then_some(value)))
406 }
407
408 pub fn query_value_as<T>(&self, name: &str) -> Result<T>
409 where
410 T: FromStr,
411 T::Err: fmt::Display,
412 {
413 parse_required_value(self.query_value(name)?, "query parameter", name)
414 }
415
416 pub fn optional_query_value_as<T>(&self, name: &str) -> Result<Option<T>>
417 where
418 T: FromStr,
419 T::Err: fmt::Display,
420 {
421 parse_optional_value(self.query_value(name)?, "query parameter", name)
422 }
423
424 pub fn query_values(&self, name: &str) -> Result<Vec<String>> {
425 Ok(self
426 .query_pairs()?
427 .into_iter()
428 .filter_map(|(key, value)| (key == name).then_some(value))
429 .collect())
430 }
431
432 pub fn query_values_as<T>(&self, name: &str) -> Result<Vec<T>>
433 where
434 T: FromStr,
435 T::Err: fmt::Display,
436 {
437 self.query_values(name)?
438 .into_iter()
439 .map(|value| parse_value(value, "query parameter", name))
440 .collect()
441 }
442
443 pub fn query_pairs(&self) -> Result<Vec<(String, String)>> {
444 match self.query_string.as_deref() {
445 Some(query) => parse_query_pairs(query),
446 None => Ok(self
447 .query
448 .iter()
449 .map(|(key, value)| (key.clone(), value.clone()))
450 .collect()),
451 }
452 }
453
454 pub fn header(&self, name: &str) -> Option<&str> {
455 get_header(&self.headers, name)
456 }
457
458 pub fn header_as<T>(&self, name: &str) -> Result<T>
459 where
460 T: FromStr,
461 T::Err: fmt::Display,
462 {
463 parse_required_value(self.header(name).map(ToString::to_string), "header", name)
464 }
465
466 pub fn optional_header_as<T>(&self, name: &str) -> Result<Option<T>>
467 where
468 T: FromStr,
469 T::Err: fmt::Display,
470 {
471 parse_optional_value(self.header(name).map(ToString::to_string), "header", name)
472 }
473
474 pub fn host(&self) -> Option<&str> {
475 self.header("host").and_then(normalize_host_header)
476 }
477
478 pub fn ip(&self) -> Option<String> {
479 self.forwarded_for_ip()
480 .or_else(|| self.forwarded_header_ip("x-forwarded-for"))
481 .or_else(|| self.forwarded_header_ip("x-real-ip"))
482 }
483
484 pub fn ip_as<T>(&self) -> Result<T>
485 where
486 T: FromStr,
487 T::Err: fmt::Display,
488 {
489 parse_required_value(self.ip(), "IP address", "ip")
490 }
491
492 pub fn optional_ip_as<T>(&self) -> Result<Option<T>>
493 where
494 T: FromStr,
495 T::Err: fmt::Display,
496 {
497 parse_optional_value(self.ip(), "IP address", "ip")
498 }
499
500 pub fn header_values(&self, name: &str) -> Vec<&str> {
501 let mut values = self.header(name).into_iter().collect::<Vec<_>>();
502 values.extend(
503 self.appended_headers
504 .iter()
505 .filter(|(key, _)| key.eq_ignore_ascii_case(name))
506 .map(|(_, value)| value.as_str()),
507 );
508 values
509 }
510
511 pub fn authorization(&self) -> Option<&str> {
512 self.header_values("authorization").into_iter().next()
513 }
514
515 fn forwarded_header_ip(&self, name: &str) -> Option<String> {
516 self.header_values(name)
517 .into_iter()
518 .flat_map(|value| value.split(','))
519 .map(str::trim)
520 .find(|value| !value.is_empty())
521 .map(ToString::to_string)
522 }
523
524 fn forwarded_for_ip(&self) -> Option<String> {
525 self.header_values("forwarded")
526 .into_iter()
527 .flat_map(|value| value.split(','))
528 .flat_map(|entry| entry.split(';'))
529 .filter_map(|part| part.trim().split_once('='))
530 .find_map(|(key, value)| {
531 key.trim().eq_ignore_ascii_case("for").then(|| {
532 value
533 .trim()
534 .trim_matches('"')
535 .trim_matches(['[', ']'])
536 .to_string()
537 })
538 })
539 .filter(|value| !value.is_empty())
540 }
541
542 pub fn bearer_token(&self) -> Option<&str> {
543 let authorization = self.authorization()?.trim();
544 let mut parts = authorization.splitn(2, char::is_whitespace);
545 let scheme = parts.next()?;
546 let token = parts.next()?.trim();
547
548 if scheme.eq_ignore_ascii_case("bearer") && !token.is_empty() {
549 Some(token)
550 } else {
551 None
552 }
553 }
554
555 pub fn require_bearer_token(&self) -> Result<&str> {
556 self.bearer_token()
557 .ok_or_else(|| BootError::Unauthorized("missing bearer token".to_string()))
558 }
559
560 pub fn content_type(&self) -> Option<&str> {
561 self.header_values("content-type").into_iter().next()
562 }
563
564 pub fn content_length(&self) -> Result<Option<u64>> {
565 let Some(content_length) = self.header_values("content-length").into_iter().next() else {
566 return Ok(None);
567 };
568
569 parse_content_length(content_length)
570 .map(Some)
571 .ok_or_else(|| {
572 BootError::BadRequest(format!("invalid content-length header: {content_length}"))
573 })
574 }
575
576 pub fn strict_content_length(&self) -> Result<Option<u64>> {
577 strict_content_length_values(
578 self.header_values("content-length"),
579 |content_length| {
580 BootError::BadRequest(format!("invalid content-length header: {content_length}"))
581 },
582 |expected_content_length, content_length| {
583 BootError::BadRequest(format!(
584 "conflicting content-length headers: {expected_content_length} != {content_length}"
585 ))
586 },
587 )
588 }
589
590 pub fn validate_content_length(&self) -> Result<()> {
591 let Some(content_length) = self.strict_content_length()? else {
592 return Ok(());
593 };
594 let actual_body_length = self.body.len() as u64;
595 if actual_body_length == content_length {
596 return Ok(());
597 }
598
599 Err(BootError::BadRequest(format!(
600 "content-length header does not match request body length: expected {content_length}, got {actual_body_length}"
601 )))
602 }
603
604 pub fn validate_body_limit(&self, body_limit: usize) -> Result<()> {
605 if self
606 .strict_content_length()?
607 .is_some_and(|content_length| content_length > body_limit as u64)
608 || self.body.len() > body_limit
609 {
610 return Err(BootError::PayloadTooLarge(format!(
611 "request body exceeds {body_limit} bytes"
612 )));
613 }
614
615 Ok(())
616 }
617
618 pub fn validate(&self) -> Result<()> {
619 self.validate_headers()?;
620 self.validate_content_length()
621 }
622
623 pub fn validate_with_body_limit(&self, body_limit: usize) -> Result<()> {
624 self.validate_headers()?;
625 self.validate_body_limit(body_limit)?;
626 self.validate_content_length()
627 }
628
629 pub fn is_content_type(&self, media_type: &str) -> bool {
630 self.content_type()
631 .is_some_and(|content_type| matches_media_type(content_type, media_type))
632 }
633
634 pub fn is_json_content_type(&self) -> bool {
635 self.content_type().is_some_and(is_json_media_type)
636 }
637
638 pub fn require_json_content_type(&self) -> Result<()> {
639 if self.is_json_content_type() {
640 return Ok(());
641 }
642
643 let message = match self.content_type() {
644 Some(content_type) => format!("expected JSON content type, got {content_type}"),
645 None => "expected JSON content type".to_string(),
646 };
647 Err(BootError::UnsupportedMediaType(message))
648 }
649
650 pub fn accepts_json(&self) -> bool {
651 accepts_json_response(&self.header_values("accept"))
652 }
653
654 pub fn require_accepts_json(&self) -> Result<()> {
655 if self.accepts_json() {
656 return Ok(());
657 }
658
659 Err(BootError::NotAcceptable(
660 "expected client to accept JSON response".to_string(),
661 ))
662 }
663
664 pub fn accepts_event_stream(&self) -> bool {
665 accepts_event_stream_response(&self.header_values("accept"))
666 }
667
668 pub fn require_accepts_event_stream(&self) -> Result<()> {
669 if self.accepts_event_stream() {
670 return Ok(());
671 }
672
673 Err(BootError::NotAcceptable(
674 "expected client to accept text/event-stream response".to_string(),
675 ))
676 }
677
678 pub fn json<T>(&self) -> Result<T>
679 where
680 T: DeserializeOwned,
681 {
682 serde_json::from_slice(&self.body).map_err(|err| BootError::BadRequest(err.to_string()))
683 }
684
685 pub fn validated_json<T>(&self) -> Result<T>
686 where
687 T: DeserializeOwned + Validate,
688 {
689 let value = self.json()?;
690 validate_value(value)
691 }
692
693 pub fn json_with_content_type<T>(&self) -> Result<T>
694 where
695 T: DeserializeOwned,
696 {
697 self.require_json_content_type()?;
698 self.json()
699 }
700
701 pub fn validated_json_with_content_type<T>(&self) -> Result<T>
702 where
703 T: DeserializeOwned + Validate,
704 {
705 self.require_json_content_type()?;
706 self.validated_json()
707 }
708
709 pub fn query<T>(&self) -> Result<T>
710 where
711 T: DeserializeOwned,
712 {
713 let query = match self.query_string.as_deref() {
714 Some(query) => {
715 validate_percent_encoding(query)?;
716 query.to_string()
717 }
718 None => serde_urlencoded::to_string(&self.query)
719 .map_err(|err| BootError::BadRequest(err.to_string()))?,
720 };
721 serde_urlencoded::from_str(&query).map_err(|err| BootError::BadRequest(err.to_string()))
722 }
723
724 pub fn validated_query<T>(&self) -> Result<T>
725 where
726 T: DeserializeOwned + Validate,
727 {
728 let value = self.query()?;
729 validate_value(value)
730 }
731}
732
733pub(super) fn parse_required_value<T>(value: Option<String>, label: &str, name: &str) -> Result<T>
734where
735 T: FromStr,
736 T::Err: fmt::Display,
737{
738 let Some(value) = value else {
739 return Err(BootError::BadRequest(format!("missing {label}: {name}")));
740 };
741 parse_value(value, label, name)
742}
743
744pub(super) fn parse_optional_value<T>(
745 value: Option<String>,
746 label: &str,
747 name: &str,
748) -> Result<Option<T>>
749where
750 T: FromStr,
751 T::Err: fmt::Display,
752{
753 value
754 .map(|value| parse_value(value, label, name))
755 .transpose()
756}
757
758pub(super) fn parse_value<T>(value: String, label: &str, name: &str) -> Result<T>
759where
760 T: FromStr,
761 T::Err: fmt::Display,
762{
763 value
764 .parse::<T>()
765 .map_err(|error| BootError::BadRequest(format!("invalid {label} {name}: {error}")))
766}
767
768fn validate_request_header(name: &str, value: &str) -> Result<()> {
769 validate_header_name(name).map_err(|message| {
770 BootError::BadRequest(format!("invalid request header name {name:?}: {message}"))
771 })?;
772 validate_header_value(value).map_err(|message| {
773 BootError::BadRequest(format!(
774 "invalid request header value for {name:?}: {message}"
775 ))
776 })
777}