/// Match a domain against a pattern supporting wildcards. /// /// Supported patterns: /// - `*` matches any domain /// - `*.example.com` matches any subdomain of example.com /// - `example.com` exact match /// - `**.example.com` matches any depth of subdomain pub fn domain_matches(pattern: &str, domain: &str) -> bool { let pattern = pattern.trim(); let domain = domain.trim(); if pattern == "*" { return true; } if pattern.eq_ignore_ascii_case(domain) { return true; } // Wildcard patterns if pattern.starts_with("*.") || pattern.starts_with("*.") { let suffix = &pattern[2..]; // e.g., "example.com" // Match exact parent or any single-level subdomain if domain.eq_ignore_ascii_case(suffix) { return true; } if domain.len() > suffix.len() + 1 && domain.as_bytes()[domain.len() - suffix.len() - 1] == b'.' && domain[domain.len() - suffix.len()..].eq_ignore_ascii_case(suffix) { // Check it's a single level subdomain for `*.` let prefix = &domain[..domain.len() - suffix.len() - 1]; return !prefix.contains('.'); } return false; } if pattern.starts_with("**.") { let suffix = &pattern[3..]; // Match exact parent or any depth of subdomain if domain.eq_ignore_ascii_case(suffix) { return true; } if domain.len() > suffix.len() + 1 && domain.as_bytes()[domain.len() - suffix.len() - 1] == b'.' && domain[domain.len() - suffix.len()..].eq_ignore_ascii_case(suffix) { return true; } return false; } // Use glob-match for more complex patterns (case-insensitive via lowercasing) let pattern_lower = pattern.to_lowercase(); let domain_lower = domain.to_lowercase(); glob_match::glob_match(&pattern_lower, &domain_lower) } /// Check if a domain matches any of the given patterns. pub fn domain_matches_any(patterns: &[&str], domain: &str) -> bool { patterns.iter().any(|p| domain_matches(p, domain)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_exact_match() { assert!(domain_matches("example.com", "example.com")); assert!(!domain_matches("example.com", "other.com")); } #[test] fn test_wildcard_all() { assert!(domain_matches("*", "anything.com")); assert!(domain_matches("*", "sub.domain.example.com")); } #[test] fn test_wildcard_subdomain() { assert!(domain_matches("*.example.com", "www.example.com")); assert!(domain_matches("*.example.com", "api.example.com")); assert!(domain_matches("*.example.com", "example.com")); assert!(!domain_matches("*.example.com", "deep.sub.example.com")); } #[test] fn test_double_wildcard() { assert!(domain_matches("**.example.com", "www.example.com")); assert!(domain_matches("**.example.com", "deep.sub.example.com")); assert!(domain_matches("**.example.com", "example.com")); } #[test] fn test_case_insensitive() { assert!(domain_matches("Example.COM", "example.com")); assert!(domain_matches("*.EXAMPLE.com", "WWW.example.COM")); } }