Skip to main content

pharos_corefile/
helpers.rs

1use crate::normalize_key;
2
3/// Normalize a Corefile host into zero or more fully qualified Domains.
4pub fn normalize_domain(host: &str) -> Vec<String> {
5    // shortcut: Domains are normalized strings until pharos-proto Name is available.
6    normalize_key(host)
7        .unwrap_or_default()
8        .into_iter()
9        .map(|key| key.domain)
10        .collect()
11}
12
13/// Use explicit arguments, or copy and normalize the Server Block Domains.
14pub fn origins_from_args_or_server_block(args: &[&str], server_block: &[&str]) -> Vec<String> {
15    if args.is_empty() {
16        server_block
17            .iter()
18            .map(|host| {
19                normalize_domain(host)
20                    .into_iter()
21                    .next()
22                    .unwrap_or_else(|| (*host).to_owned())
23            })
24            .collect()
25    } else {
26        args.iter()
27            .flat_map(|host| normalize_domain(host))
28            .collect()
29    }
30}
31
32/// Domains for which a Plugin permits Fallthrough.
33#[derive(Clone, Debug, Default, Eq, PartialEq)]
34pub struct Fallthrough {
35    pub domains: Vec<String>,
36}
37
38impl Fallthrough {
39    /// Set matching Domains; no arguments means the root Domain.
40    pub fn set_domains_from_args(&mut self, domains: &[&str]) {
41        self.domains = if domains.is_empty() {
42            vec![".".into()]
43        } else {
44            domains
45                .iter()
46                .flat_map(|domain| normalize_domain(domain))
47                .collect()
48        };
49    }
50
51    /// Whether a name is in any configured Domain.
52    pub fn through(&self, name: &str) -> bool {
53        self.domains.iter().any(|domain| {
54            if domain == "." {
55                return true;
56            }
57            let Some(prefix) = name.len().checked_sub(domain.len()) else {
58                return false;
59            };
60            // RFC 4343 ยง3: DNS name lookup compares ASCII letters case-insensitively.
61            if !name.is_char_boundary(prefix) || !name[prefix..].eq_ignore_ascii_case(domain) {
62                return false;
63            }
64            if prefix == 0 {
65                return true;
66            }
67            let bytes = name.as_bytes();
68            if bytes[prefix - 1] != b'.' {
69                return false;
70            }
71            let escapes = bytes[..prefix - 1]
72                .iter()
73                .rev()
74                .take_while(|&&byte| byte == b'\\')
75                .count();
76            escapes % 2 == 0
77        })
78    }
79}