actix_web_lab/
redirect_host.rs1use std::collections::BTreeSet;
2
3use actix_web::{HttpResponse, http::StatusCode};
4
5#[derive(Debug, Clone, Default)]
14pub struct HostAllowlist {
15 hosts: BTreeSet<String>,
16}
17
18impl HostAllowlist {
19 pub fn new<I, S>(hosts: I) -> Self
21 where
22 I: IntoIterator<Item = S>,
23 S: Into<String>,
24 {
25 Self {
26 hosts: hosts
27 .into_iter()
28 .map(|host| normalize_host(host.into()))
29 .collect(),
30 }
31 }
32
33 pub fn contains(&self, host: &str) -> bool {
35 self.hosts.contains(&normalize_host(host))
36 }
37}
38
39pub(crate) fn reject_untrusted_host(
40 configured_allowlist: Option<&HostAllowlist>,
41 host: &str,
42) -> Option<HttpResponse<()>> {
43 if configured_allowlist.is_some_and(|allowlist| !allowlist.contains(host)) {
44 return Some(HttpResponse::with_body(StatusCode::BAD_REQUEST, ()));
45 }
46
47 None
48}
49
50fn normalize_host(host: impl AsRef<str>) -> String {
51 host.as_ref().trim().to_ascii_lowercase()
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn host_matching_is_case_insensitive() {
60 let allowlist = HostAllowlist::new(["Example.COM:8443"]);
61
62 assert!(allowlist.contains("example.com:8443"));
63 assert!(allowlist.contains("EXAMPLE.COM:8443"));
64 assert!(!allowlist.contains("example.com"));
65 }
66}