use serde::{Deserialize, Serialize};
use crate::rdap::RdapClient;
use crate::whois::{get_registry_url, get_whois_server};
pub use crate::whois::all_tlds;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TldInfo {
pub tld: String,
pub whois_server: Option<String>,
pub rdap_url: Option<String>,
pub registry_url: Option<String>,
pub tld_type: String,
}
pub async fn lookup_tld(tld: &str) -> TldInfo {
let tld = tld.trim_start_matches('.').to_lowercase();
let whois_server = get_whois_server(&tld).map(|s| s.to_string());
let registry_url = get_registry_url(&tld);
let tld_type = classify_tld(&tld);
let rdap_client = RdapClient::new();
let rdap_url = rdap_client.get_rdap_base_url_for_tld(&tld).await;
TldInfo {
tld,
whois_server,
rdap_url,
registry_url,
tld_type,
}
}
const IDN_CCTLDS: &[&str] = &[
"xn--2scrj9c", "xn--3e0b707e", "xn--3hcrj9c", "xn--45br5cyl", "xn--45brj9c", "xn--4dbrk0ce", "xn--54b7fta0cc", "xn--80ao21a", "xn--90a3ac", "xn--90ae", "xn--90ais", "xn--clchc0ea0b2g2a9gcd", "xn--d1alf", "xn--e1a4c", "xn--fiqs8s", "xn--fiqz9s", "xn--fpcrj9c3d", "xn--fzc2c9e2c", "xn--gecrj9c", "xn--h2breg3eve", "xn--h2brj9c", "xn--h2brj9c8c", "xn--j1amh", "xn--j6w193g", "xn--kprw13d", "xn--kpry57d", "xn--l1acc", "xn--lgbbat1ad8j", "xn--mgb9awbf", "xn--mgba3a4f16a", "xn--mgbaam7a8h", "xn--mgbah1a3hjkrd", "xn--mgbai9azgqp6j", "xn--mgbayh7gpa", "xn--mgbbh1a", "xn--mgbbh1a71e", "xn--mgbc0a9azcg", "xn--mgbcpq6gpa1a", "xn--mgberp4a5d4ar", "xn--mgbgu82a", "xn--mgbpl2fh", "xn--mgbtx2b", "xn--mgbx4cd0ab", "xn--mix891f", "xn--node", "xn--o3cw4h", "xn--ogbpf8fl", "xn--p1ai", "xn--pgbs0dh", "xn--q7ce6a", "xn--qxa6a", "xn--qxam", "xn--rvc1e0am3e", "xn--s9brj9c", "xn--wgbh1c", "xn--wgbl6a", "xn--xkc2al3hye2a", "xn--xkc2dl3a5ee0h", "xn--y9a3aq", "xn--yfro4i67o", "xn--ygbi2ammx", ];
fn classify_tld(tld: &str) -> String {
let tld = if tld.is_ascii() {
tld.to_ascii_lowercase()
} else {
crate::validation::domain_to_ascii(tld).unwrap_or_else(|_| tld.to_lowercase())
};
let tld = tld.as_str();
if (tld.len() == 2 && tld.chars().all(|c| c.is_ascii_alphabetic()))
|| IDN_CCTLDS.binary_search(&tld).is_ok()
{
"country-code".to_string()
} else if ["arpa", "root"].contains(&tld) {
"infrastructure".to_string()
} else if [
"aero", "asia", "cat", "coop", "edu", "gov", "int", "jobs", "mil", "museum", "post", "tel",
"travel", "xxx",
]
.contains(&tld)
{
"sponsored".to_string()
} else {
"generic".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_tld_country_code() {
assert_eq!(classify_tld("uk"), "country-code");
assert_eq!(classify_tld("de"), "country-code");
assert_eq!(classify_tld("jp"), "country-code");
}
#[test]
fn test_classify_tld_infrastructure() {
assert_eq!(classify_tld("arpa"), "infrastructure");
assert_eq!(classify_tld("root"), "infrastructure");
}
#[test]
fn test_classify_tld_sponsored() {
assert_eq!(classify_tld("edu"), "sponsored");
assert_eq!(classify_tld("gov"), "sponsored");
assert_eq!(classify_tld("museum"), "sponsored");
}
#[test]
fn test_classify_tld_generic() {
assert_eq!(classify_tld("com"), "generic");
assert_eq!(classify_tld("net"), "generic");
assert_eq!(classify_tld("org"), "generic");
assert_eq!(classify_tld("app"), "generic");
}
#[test]
fn test_classify_tld_idn_country_code() {
for tld in [
"xn--p1ai",
"рф",
"xn--fiqs8s",
"中国",
"xn--3e0b707e",
"한국",
"xn--4dbrk0ce",
"ישראל",
"XN--P1AI",
] {
assert_eq!(classify_tld(tld), "country-code", "{tld}");
}
for tld in [
"xn--80asehdb",
"онлайн",
"xn--q9jyb4c",
"みんな",
"xn--fiq228c5hs",
] {
assert_eq!(classify_tld(tld), "generic", "{tld}");
}
}
#[test]
fn idn_cctld_list_is_sorted_and_in_catalog() {
for w in IDN_CCTLDS.windows(2) {
assert!(w[0] < w[1], "IDN_CCTLDS not sorted at {w:?}");
}
let catalog = all_tlds();
for tld in IDN_CCTLDS {
assert!(
catalog.binary_search(tld).is_ok(),
".{tld} is not a known TLD"
);
}
}
#[test]
fn test_classify_tld_numeric_not_country_code() {
assert_eq!(classify_tld("a1"), "generic");
}
}