Skip to main content

tirith_core/
data.rs

1// Embedded data from build.rs: known domains, popular repos.
2
3// Include generated data
4include!(concat!(env!("OUT_DIR"), "/known_domains_gen.rs"));
5include!(concat!(env!("OUT_DIR"), "/popular_repos_gen.rs"));
6include!(concat!(env!("OUT_DIR"), "/psl_gen.rs"));
7
8/// Check if a domain is in the known high-value targets list.
9pub fn is_known_domain(domain: &str) -> bool {
10    let lower = domain.to_lowercase();
11    KNOWN_DOMAINS.iter().any(|d| *d == lower)
12}
13
14/// Check if a repo (owner/name) is in the popular repos list.
15pub fn is_popular_repo(owner: &str, name: &str) -> bool {
16    let owner_lower = owner.to_lowercase();
17    let name_lower = name.to_lowercase();
18    POPULAR_REPOS
19        .iter()
20        .any(|(o, n)| o.to_lowercase() == owner_lower && n.to_lowercase() == name_lower)
21}
22
23/// Get all known domains for confusable checking.
24pub fn known_domains() -> &'static [&'static str] {
25    KNOWN_DOMAINS
26}
27
28/// Check if a suffix is in the public suffix list.
29pub fn is_public_suffix(suffix: &str) -> bool {
30    let lower = suffix.to_lowercase();
31    PUBLIC_SUFFIXES.iter().any(|s| *s == lower)
32}
33
34/// Extract the registrable domain (eTLD+1) from a hostname.
35/// Returns None if the entire hostname is a public suffix or has no suffix match.
36pub fn registrable_domain(host: &str) -> Option<String> {
37    let lower = host.to_lowercase().trim_end_matches('.').to_string();
38    let labels: Vec<&str> = lower.split('.').collect();
39    if labels.len() < 2 {
40        return None;
41    }
42    // Try multi-part suffixes first (longest match)
43    for i in 0..labels.len() {
44        let suffix = labels[i..].join(".");
45        if is_public_suffix(&suffix) {
46            if i == 0 {
47                // Entire hostname is a public suffix
48                return None;
49            }
50            // eTLD+1 = one label before the suffix + suffix
51            return Some(labels[i - 1..].join("."));
52        }
53    }
54    // Fallback: treat last label as TLD
55    if labels.len() >= 2 {
56        Some(labels[labels.len() - 2..].join("."))
57    } else {
58        None
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_known_domain() {
68        assert!(is_known_domain("github.com"));
69        assert!(is_known_domain("GitHub.com"));
70        assert!(!is_known_domain("notaknowndomain.com"));
71    }
72
73    #[test]
74    fn test_popular_repo() {
75        assert!(is_popular_repo("torvalds", "linux"));
76        assert!(!is_popular_repo("nobody", "nothing"));
77    }
78}