Skip to main content

dnslib/core/dns/
names.rs

1//! DNS name normalization helpers shared by vendor adapters.
2
3/// Strip the trailing `.{zone_name}` suffix from an FQDN and return a
4/// zone-relative owner name.
5///
6/// Returns `"@"` for the zone apex. If the name is outside the supplied zone,
7/// the original name is returned unchanged.
8pub fn relative_to_zone(fqdn: &str, zone_name: &str) -> String {
9    let fqdn_lower = fqdn.to_lowercase();
10    let zone_lower = zone_name.to_lowercase();
11
12    if fqdn_lower == zone_lower {
13        return "@".to_string();
14    }
15
16    let suffix = format!(".{zone_lower}");
17    if fqdn_lower.ends_with(&suffix) {
18        fqdn[..fqdn.len() - suffix.len()].to_string()
19    } else {
20        fqdn.to_string()
21    }
22}
23
24/// True when `domain` is the zone apex or a name below the zone.
25pub fn domain_matches_zone(domain: &str, zone_name: &str) -> bool {
26    let domain = domain.to_lowercase();
27    let zone = zone_name.to_lowercase();
28    domain == zone || domain.ends_with(&format!(".{zone}"))
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn relative_to_zone_returns_at_for_apex() {
37        assert_eq!(relative_to_zone("example.com", "example.com"), "@");
38    }
39
40    #[test]
41    fn relative_to_zone_strips_zone_suffix_case_insensitively() {
42        assert_eq!(relative_to_zone("Api.Example.Com", "example.com"), "Api");
43    }
44
45    #[test]
46    fn relative_to_zone_preserves_out_of_zone_name() {
47        assert_eq!(relative_to_zone("other.net", "example.com"), "other.net");
48    }
49
50    #[test]
51    fn domain_matches_zone_accepts_apex_and_children() {
52        assert!(domain_matches_zone("example.com", "example.com"));
53        assert!(domain_matches_zone("api.example.com", "example.com"));
54        assert!(!domain_matches_zone("badexample.com", "example.com"));
55    }
56}