use crate::Request;
use rama_core::{extensions::Extensions, telemetry::tracing};
use rama_net::AuthorityInputExt;
use rama_net::address::{Domain, IntoDomain};
#[derive(Debug, Clone)]
pub struct DomainMatcher {
domain: Domain,
sub: bool,
}
impl DomainMatcher {
#[must_use]
pub fn exact(domain: impl IntoDomain) -> Self {
Self {
domain: domain.into_domain(),
sub: false,
}
}
#[must_use]
pub fn sub(domain: impl IntoDomain) -> Self {
Self {
domain: domain.into_domain(),
sub: true,
}
}
}
impl<Body> rama_core::matcher::Matcher<Request<Body>> for DomainMatcher {
fn matches(&self, _: Option<&Extensions>, req: &Request<Body>) -> bool {
let Some(authority) = req.authority() else {
tracing::error!("DomainMatcher: failed to resolve authority");
return false;
};
let host = authority.host;
if host.try_as_ip().is_ok() {
tracing::trace!("DomainMatcher: host is an IP — no match");
return false;
}
let Ok(domain) = host.try_into_domain() else {
tracing::trace!("DomainMatcher: host is not a domain — no match");
return false;
};
if self.sub {
tracing::trace!("DomainMatcher: ({}).is_parent_of({})", self.domain, domain);
self.domain.is_parent_of(&domain)
} else {
tracing::trace!("DomainMatcher: ({}) == ({})", self.domain, domain);
self.domain == domain
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Request;
use rama_core::matcher::Matcher as _;
fn req_with_host(host_header: &str) -> Request<()> {
Request::builder()
.uri("/")
.header("host", host_header)
.body(())
.unwrap()
}
#[test]
fn plain_domain_matches() {
let m = DomainMatcher::exact(rama_net::address::Domain::from_static("example.com"));
assert!(m.matches(None, &req_with_host("example.com")));
}
#[test]
fn pct_encoded_reg_name_matches_via_bridge() {
let m = DomainMatcher::exact(rama_net::address::Domain::from_static("example.com"));
assert!(m.matches(None, &req_with_host("exa%6Dple.com")));
}
#[test]
fn ip_host_does_not_match_domain() {
let m = DomainMatcher::exact(rama_net::address::Domain::from_static("127.0.0.1"));
assert!(!m.matches(None, &req_with_host("127.0.0.1")));
}
#[test]
fn pct_encoded_ip_does_not_match_domain() {
let m = DomainMatcher::exact(rama_net::address::Domain::from_static("127.0.0.1"));
assert!(!m.matches(None, &req_with_host("%31%32%37.0.0.1")));
}
#[test]
fn subdomain_match() {
let m = DomainMatcher::sub(rama_net::address::Domain::from_static("example.com"));
assert!(m.matches(None, &req_with_host("api.example.com")));
assert!(m.matches(None, &req_with_host("example.com")));
assert!(!m.matches(None, &req_with_host("other.example")));
}
}