rama_http/matcher/
domain.rs1use crate::Request;
2
3use rama_core::{extensions::Extensions, telemetry::tracing};
4use rama_net::AuthorityInputExt;
5use rama_net::address::{Domain, IntoDomain};
6
7#[derive(Debug, Clone)]
8pub struct DomainMatcher {
10 domain: Domain,
11 sub: bool,
12}
13
14impl DomainMatcher {
15 #[must_use]
19 pub fn exact(domain: impl IntoDomain) -> Self {
20 Self {
21 domain: domain.into_domain(),
22 sub: false,
23 }
24 }
25 #[must_use]
30 pub fn sub(domain: impl IntoDomain) -> Self {
31 Self {
32 domain: domain.into_domain(),
33 sub: true,
34 }
35 }
36}
37
38impl<Body> rama_core::matcher::Matcher<Request<Body>> for DomainMatcher {
39 fn matches(&self, _: Option<&Extensions>, req: &Request<Body>) -> bool {
40 let Some(authority) = req.authority() else {
41 tracing::error!("DomainMatcher: failed to resolve authority");
42 return false;
43 };
44 let host = authority.host;
45
46 if host.try_as_ip().is_ok() {
51 tracing::trace!("DomainMatcher: host is an IP — no match");
52 return false;
53 }
54 let Ok(domain) = host.try_into_domain() else {
57 tracing::trace!("DomainMatcher: host is not a domain — no match");
58 return false;
59 };
60 if self.sub {
61 tracing::trace!("DomainMatcher: ({}).is_parent_of({})", self.domain, domain);
62 self.domain.is_parent_of(&domain)
63 } else {
64 tracing::trace!("DomainMatcher: ({}) == ({})", self.domain, domain);
65 self.domain == domain
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::Request;
74 use rama_core::matcher::Matcher as _;
75
76 fn req_with_host(host_header: &str) -> Request<()> {
77 Request::builder()
81 .uri("/")
82 .header("host", host_header)
83 .body(())
84 .unwrap()
85 }
86
87 #[test]
88 fn plain_domain_matches() {
89 let m = DomainMatcher::exact(rama_net::address::Domain::from_static("example.com"));
90 assert!(m.matches(None, &req_with_host("example.com")));
91 }
92
93 #[test]
94 fn pct_encoded_reg_name_matches_via_bridge() {
95 let m = DomainMatcher::exact(rama_net::address::Domain::from_static("example.com"));
98 assert!(m.matches(None, &req_with_host("exa%6Dple.com")));
99 }
100
101 #[test]
102 fn ip_host_does_not_match_domain() {
103 let m = DomainMatcher::exact(rama_net::address::Domain::from_static("127.0.0.1"));
106 assert!(!m.matches(None, &req_with_host("127.0.0.1")));
107 }
108
109 #[test]
110 fn pct_encoded_ip_does_not_match_domain() {
111 let m = DomainMatcher::exact(rama_net::address::Domain::from_static("127.0.0.1"));
115 assert!(!m.matches(None, &req_with_host("%31%32%37.0.0.1")));
116 }
117
118 #[test]
119 fn subdomain_match() {
120 let m = DomainMatcher::sub(rama_net::address::Domain::from_static("example.com"));
121 assert!(m.matches(None, &req_with_host("api.example.com")));
122 assert!(m.matches(None, &req_with_host("example.com")));
123 assert!(!m.matches(None, &req_with_host("other.example")));
124 }
125}