pub(crate) fn normalize_host(target: &str) -> String {
gossan_core::domain::normalize_host(target)
}
pub(crate) fn parent_domain(host: &str) -> String {
let host = normalize_host(host);
let bare = host.trim_start_matches('[').trim_end_matches(']');
if bare.parse::<std::net::IpAddr>().is_ok() {
return host;
}
if let Some(reg) = gossan_core::domain::registrable(&host) {
return reg;
}
let labels: Vec<&str> = host.split('.').filter(|s| !s.is_empty()).collect();
if labels.len() < 2 {
return host;
}
labels[labels.len() - 2..].join(".")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_host_strips_https_scheme() {
assert_eq!(normalize_host("https://example.com"), "example.com");
}
#[test]
fn normalize_host_strips_http_scheme() {
assert_eq!(normalize_host("http://example.com"), "example.com");
}
#[test]
fn normalize_host_strips_port() {
assert_eq!(normalize_host("example.com:443"), "example.com");
}
#[test]
fn normalize_host_strips_scheme_and_port() {
assert_eq!(normalize_host("https://example.com:8443"), "example.com");
}
#[test]
fn normalize_host_lowercases() {
assert_eq!(normalize_host("EXAMPLE.COM"), "example.com");
}
#[test]
fn normalize_host_mixed_case_with_scheme() {
assert_eq!(normalize_host("https://EXAMPLE.COM"), "example.com");
}
#[test]
fn normalize_host_strips_trailing_dot() {
assert_eq!(normalize_host("example.com."), "example.com");
}
#[test]
fn normalize_host_plain_hostname_unchanged() {
assert_eq!(normalize_host("example.com"), "example.com");
}
#[test]
fn parent_domain_returns_ip_unchanged() {
assert_eq!(parent_domain("1.2.3.4"), "1.2.3.4");
assert_eq!(parent_domain("https://1.2.3.4:443/x"), "1.2.3.4");
}
#[test]
fn parent_domain_uses_psl_for_multipart_tld() {
assert_eq!(parent_domain("shop.example.co.uk"), "example.co.uk");
assert_ne!(parent_domain("shop.example.co.uk"), "co.uk");
}
#[test]
fn normalize_host_empty_string_does_not_panic() {
let _ = normalize_host("");
}
#[test]
fn normalize_host_ipv4_preserved() {
assert_eq!(normalize_host("1.2.3.4"), "1.2.3.4");
}
#[test]
fn normalize_host_ipv4_with_port_stripped() {
assert_eq!(normalize_host("1.2.3.4:8080"), "1.2.3.4");
}
#[test]
fn normalize_host_with_path_strips_path() {
let result = normalize_host("https://example.com/some/path");
assert!(!result.contains('/'), "path must be stripped: got {result:?}");
assert!(result.contains("example.com"), "host must be preserved: got {result:?}");
}
#[test]
fn normalize_host_consistent_on_repeat_calls() {
let a = normalize_host("https://Example.COM:443/path");
let b = normalize_host("https://Example.COM:443/path");
assert_eq!(a, b);
}
#[test]
fn normalize_host_subdomain_preserved() {
assert_eq!(normalize_host("sub.example.com"), "sub.example.com");
}
#[test]
fn normalize_host_deep_subdomain_preserved() {
assert_eq!(normalize_host("a.b.c.example.com"), "a.b.c.example.com");
}
#[test]
fn parent_domain_two_labels() {
assert_eq!(parent_domain("example.com"), "example.com");
}
#[test]
fn parent_domain_subdomain() {
assert_eq!(parent_domain("sub.example.com"), "example.com");
}
#[test]
fn parent_domain_deep_subdomain() {
assert_eq!(parent_domain("a.b.c.example.com"), "example.com");
}
#[test]
fn parent_domain_single_label_returns_as_is() {
assert_eq!(parent_domain("localhost"), "localhost");
}
#[test]
fn parent_domain_empty_string_does_not_panic() {
let _ = parent_domain("");
}
#[test]
fn parent_domain_only_dots_does_not_panic() {
let result = parent_domain("...");
let _ = result;
}
#[test]
fn parent_domain_trailing_dot_handled() {
let result = parent_domain("example.com.");
assert_eq!(result, "example.com");
}
#[test]
fn parent_domain_consistent_on_same_host() {
let a = parent_domain("sub.example.com");
let b = parent_domain("sub.example.com");
assert_eq!(a, b);
}
#[test]
fn parent_domain_different_parents_not_equal() {
let p1 = parent_domain("api.example.com");
let p2 = parent_domain("api.attacker.com");
assert_ne!(p1, p2);
}
#[test]
fn parent_domain_siblings_share_parent() {
let p1 = parent_domain("api.example.com");
let p2 = parent_domain("www.example.com");
assert_eq!(p1, p2);
}
#[test]
fn parent_domain_ipv4_returns_ip_unchanged() {
let result = parent_domain("1.2.3.4");
assert_eq!(result, "1.2.3.4");
}
}