use std::collections::HashMap;
use std::sync::LazyLock;
pub static WHOIS_SERVERS: LazyLock<HashMap<&'static str, String>> = LazyLock::new(|| {
let mut m: HashMap<&'static str, String> = NIC_TLDS
.split_ascii_whitespace()
.map(|tld| (tld, format!("whois.nic.{tld}")))
.collect();
for (host, tlds) in HOSTED_TLDS {
for tld in tlds.split_ascii_whitespace() {
m.insert(tld, host.to_string());
}
}
for alias in IDN_ALIASES.split_ascii_whitespace() {
let server = crate::validation::domain_to_ascii(alias)
.ok()
.and_then(|ascii| m.get(ascii.as_str()).cloned());
if let Some(server) = server {
m.insert(alias, server);
}
}
m
});
const NIC_TLDS: &str = "
aaa aarp abb abbott abbvie abc abogado abudhabi ac academy accenture accountant accountants aco
actor ad adult aeg af afl africa ag agakhan agency ai airbus airforce airtel akdn alibaba alipay
allfinanz allstate ally alsace alstom amazon americanfamily amfam amsterdam anz aol apartments
aquarelle ar arab archi army art arte as asda asia associates at attorney auction audi audible
audio auspost author auto autos aw aws azure baby band bank bar barcelona barclaycard barclays
barefoot bargains baseball basketball bauhaus bayern bbc bbt bbva bcg bcn beats beauty beer
berlin best bestbuy bet bharti bible bid bike bing bingo bio biz bj black blackfriday
blockbuster blog bloomberg blue bm bms bmw bnpparibas bo boats boehringer bofa bond book bosch
bostik boston bot boutique box bradesco bridgestone broadway broker brother build builders
business buy buzz bzh cab cafe call cam camera camp canon capetown capital capitalone car cards
care career careers cars casa case cash casino cat catering catholic cba cd center ceo cern cfa
cfd ch chanel charity chat cheap chintai christmas church ci cipriani circle citadel city cl
claims cleaning clinic clinique clothing cloud club clubmed cm coach codes coffee college
commbank community company compare computer comsec condos construction consulting contact
contractors cooking cool coop corsica coupon coupons courses cpa cr credit creditcard
creditunion cricket crown crs cruise cruises cuisinella cv cx cyou cz dance data date dating dds
deal dealer deals degree delivery deloitte delta democrat dental dentist desi design diamonds
diet digital direct directory discount discover dish dnp do doctor dog domains dot download dtv
dubai durban dvag dvr dz earth ec eco edeka education email emerck energy engineer engineering
enterprises epson equipment ericsson erni es estate eurovision eus events exchange expert
exposed express extraspace fage fail fairwinds faith family fan fans farm fashion fast fedex
ferrari fidelity fido film finance financial fire firestone firmdale fish fishing fit fitness
flights florist flowers fm fo football forex forsale foundation fox fr free fresenius frl
frogans fun fund furniture futbol fyi ga gal gallery gallo gallup game games garden gay gd gdn
ge gea gent genting george ggee gh gifts gives giving gl glass global gmbh gmo gmx godaddy gold
goldpoint golf goodyear gop got gov gp graphics gratis green gripe grocery group gs gucci guide
guitars guru hair hamburg haus hdfc hdfcbank healthcare help helsinki hermes hiphop hkt hn
hockey holdings holiday homedepot homes honda horse hospital host hosting hot hotels hotmail
house ht hu hughes hyundai ibm icbc ice icu ifm ikano im imamat imdb immo immobilien inc
industries info ink institute insurance insure international investments io ir irish ismaili ist
istanbul it itv jaguar java jeep jetzt jewelry jio jll jobs joburg jot joy jprs juniper kaufen
kddi kerryhotels kerryproperties kfh ki kia kids kim kindle kitchen kiwi kn komatsu kosher krd
kuokgroup kw kyoto kz la lacaixa lamborghini lamer land landrover lasalle lat latino latrobe law
lawyer lds lease leclerc lefrak legal lego lexus lgbt li lidl life lifeinsurance lighting like
limited limo live llc llp loan loans locker locus lol london lotte lotto love lpl lplfinancial
ls ltd ltda lundbeck luxe luxury lv ly madrid maif maison makeup man management mango market
marketing markets marriott mba mc mckinsey md me med media melbourne memorial men menu merck
merckmsd mg miami microsoft mini mit ml mls mma mn mobi mobile moda moe moi mom monash money
monster mormon mortgage moscow moto motorcycles movie mr ms msd mtn mtr museum mw mz nab nagoya
name navy nec netbank network news next nextdirect nf ngo nhk nico nikon ninja nissay nokia
norton now nowruz nowtv nra nrw ntt nyc obi observer office okinawa olayan olayangroup ollo
omega one ong onl online ooo open oracle orange organic origins osaka otsuka ott ovh paris pars
partners parts party pay pccw pet pg philips phone photo photography photos physio pics pictet
pictures pin ping pink pioneer pizza place playstation plumbing plus pm pnc pohl poker politie
porn post press prime pro productions progressive promo properties protection pub pw pwc qpon
quebec quest racing radio re read realestate realtor realty recipes red redumbrella rehab reise
reisen reit reliance ren rent rentals repair report republican rest restaurant review reviews
rexroth rich richardli ricoh ril rip rocks rodeo rogers room rugby ruhr run rwe ryukyu saarland
safe safety sakura sale salon samsclub samsung sandvik sandvikcoromant sanofi sap sarl save saxo
sbi sbs sc scb schmidt scholarships school schule schwarz science scot sd seat secure security
seek select services seven sew sex sfr sh shangrila shell shia shiksha shoes shop shopping show
silk sina singles site ski skin sky skype sl sling sm smart smile sn sncf so soccer social
softbank software solar solutions song sony spa space sport spot srl ss st stada star statebank
stc stcgroup stockholm storage store stream studio study style sucks supplies supply support
surf surgery suzuki swatch swiss sydney systems tab taipei talk taobao tatamotors tatar tattoo
tax taxi tc tci td tdk team tech technology tel temasek tennis teva tf tg thd theater theatre
tiaa tickets tienda tips tires tirol tl tm tmall today tokyo tools top toray toshiba total tours
town toyota toys trade trading training travel travelers travelersinsurance trv tube tui tunes
tushu tv tvs ubank ubs uk unicom university uno ups us vacations vanguard ve vegas ventures
verisign versicherung vet vg viajes video vig viking villas vin vip virgin visa vision viva
vodka volvo vote voting voto voyage walmart walter wanggou watch watches webcam weber website
wed wedding weibo weir wf whoswho wien wiki win windows wine wme woodside work works world wow
wtc wtf xbox xerox xin xn--11b4c3d xn--1ck2e1b xn--3pxu8k xn--42c2d9a xn--4gbrim xn--55qw42g
xn--5su34j936bgsg xn--5tzm5g xn--6frz82g xn--80adxhks xn--80aqecdr1a xn--80asehdb xn--80aswg
xn--8y0a063a xn--9dbq2a xn--9krt00a xn--b4w605ferd xn--bck1b9a5dre4c xn--c1avg xn--c2br7g
xn--cck2b3b xn--cckwcxetd xn--czrs0t xn--d1acj3b xn--eckvdtc9d xn--fct429k xn--fhbei xn--fjq720a
xn--fzys8d69uvgm xn--g2xx48c xn--gckr3f0f xn--gk3at1e xn--i1b6b1a6a2e xn--j1aef xn--jlq480n2rg
xn--jvr189m xn--kcrx77d1x4a xn--kput3i xn--mgba7c0bbn0a xn--mgbab2bd xn--mgbca7dzdo
xn--mgbi4ecexp xn--mgbt3dhd xn--mk1bu44c xn--mxtq1m xn--ngbc5azd xn--ngbe9e0a xn--ngbrx
xn--nqv7f xn--nqv7fs00ema xn--pssy2u xn--rovu88b xn--ses554g xn--t60b56a xn--tckwe xn--tiq49xqyj
xn--unup4y xn--vermgensberater-ctb xn--vermgensberatung-pwb xn--vhquv xn--w4r85el8fhu5dnra
xn--w4rs40l xn--zfr164b xxx xyz yachts yahoo yamaxun yandex yoga yokohama you yt zappos zara
zero zone zuerich
";
const HOSTED_TLDS: &[(&str, &str)] = &[
("ccwhois.verisign-grs.com", "cc"),
("kero.yachay.pe", "pe"),
("virgil.nic.vi", "vi"),
("whois.aeda.net.ae", "ae xn--mgbaam7a8h"),
("whois.aero", "aero"),
("whois.afilias-grs.info", "bz lc"),
("whois.afilias-srs.net", "pr schaeffler"),
("whois.amnic.net", "am xn--y9a3aq"),
("whois.ande.gov.gn", "gn"),
("whois.ati.tn", "tn xn--pgbs0dh"),
("whois.auda.org.au", "au"),
("whois.ax", "ax"),
("whois.bhregistry.bh", "bh xn--mgbcpq6gpa1a"),
("whois.bnnic.bn", "bn"),
("whois.cctld.by", "by xn--90ais"),
("whois.cctld.uz", "uz"),
("whois.cira.ca", "ca"),
("whois.cmc.iq", "iq xn--mgbtx2b"),
("whois.cnnic.cn", "cn xn--fiqs8s xn--fiqz9s"),
("whois.co.ug", "ug"),
("whois.denic.de", "de"),
("whois.dmdomains.dm", "dm"),
("whois.dnrs.vu", "vu"),
("whois.dns.be", "be"),
("whois.dns.hr", "hr"),
("whois.dns.lu", "lu"),
("whois.dns.pl", "pl"),
("whois.dns.pt", "pt"),
("whois.domain-registry.nl", "nl"),
("whois.dominio.gq", "gq"),
("whois.domreg.lt", "lt"),
("whois.dot.cf", "cf"),
("whois.dot.tk", "tk"),
("whois.dotmasr.eg", "xn--wgbh1c"),
("whois.dotukr.com", "xn--j1amh"),
("whois.educause.edu", "edu"),
("whois.eu", "eu xn--e1a4c xn--qxa6a"),
("whois.fi", "fi"),
("whois.gg", "gg"),
("whois.gtld.knet.cn", "baidu"),
(
"whois.gtld.zdns.cn",
"wang xn--30rr7y xn--3bst00m xn--45q11c xn--6qq986b3xl xn--9et52u xn--czru2d xn--efvy88h
xn--fiq64b xn--hxt814e",
),
("whois.gtlds.nic.br", "bom final globo rio uol"),
("whois.hkirc.hk", "hk xn--j6w193g"),
("whois.iana.org", "arpa int"),
("whois.id", "id"),
("whois.identitydigital.services", "gi vc"),
("whois.iis.nu", "nu"),
("whois.iis.se", "se"),
("whois.imena.bg", "xn--90ae"),
("whois.irs.net.nz", "nz"),
("whois.isnic.is", "is"),
("whois.isoc.org.il", "il xn--4dbrk0ce"),
("whois.je", "je"),
("whois.jprs.jp", "jp"),
("whois.kenic.or.ke", "ke"),
("whois.kg", "kg"),
("whois.kr", "kr xn--3e0b707e xn--cg4bki"),
("whois.kyregistry.ky", "ky"),
("whois.lbdr.org.lb", "lb"),
("whois.marnet.mk", "mk xn--d1alf"),
("whois.mediaserv.net", "gf mq"),
("whois.mn", "xn--l1acc"),
("whois.monic.mo", "mo xn--mix891f"),
("whois.mx", "mx"),
("whois.mynic.my", "my xn--mgbx4cd0ab"),
("whois.nc", "nc"),
(
"whois.ngtld.cn",
"xn--1qqw23a xn--55qx5d xn--io0a7i xn--xhq521b",
),
("whois.nic.dz", "xn--lgbbat1ad8j"),
(
"whois.nic.gmo",
"datsun fujitsu hisamitsu hitachi infiniti jcb mitsubishi nissan panasonic sharp yodobashi",
),
("whois.nic.ir", "xn--mgba3a4f16a"),
("whois.nic.kz", "xn--80ao21a"),
("whois.nic.la", "xn--q7ce6a"),
("whois.nic.mr", "xn--mgbah1a3hjkrd"),
("whois.nic.net.bw", "bw"),
("whois.nic.net.jo", "jo"),
("whois.nic.net.ng", "ng"),
("whois.nic.net.sa", "sa xn--mgberp4a5d4ar"),
("whois.nic.net.sb", "sb"),
("whois.nic.org.uy", "uy"),
("whois.nic.rwe", "sener"),
(
"whois.nixiregistry.in",
"in xn--2scrj9c xn--3hcrj9c xn--45br5cyl xn--45brj9c xn--fpcrj9c3d xn--gecrj9c
xn--h2breg3eve xn--h2brj9c xn--h2brj9c8c xn--mgbbh1a xn--mgbbh1a71e xn--mgbgu82a
xn--rvc1e0am3e xn--s9brj9c xn--xkc2dl3a5ee0h",
),
("whois.norid.no", "no"),
("whois.pknic.net.pk", "pk"),
("whois.publicinterestregistry.org", "org"),
("whois.punktum.dk", "dk"),
("whois.register.bg", "bg"),
("whois.register.si", "si"),
("whois.registre.bf", "bf"),
("whois.registre.ma", "ma"),
("whois.registro.br", "br"),
(
"whois.registry.click",
"click country diy feedback food forum hiv lifestyle living pid property sexy trust vana",
),
("whois.registry.co", "co"),
("whois.registry.gift", "gift"),
("whois.registry.gov.mm", "mm"),
("whois.registry.gy", "gy"),
("whois.registry.hm", "hm"),
("whois.registry.om", "om xn--mgb9awbf"),
("whois.registry.pf", "pf"),
("whois.registry.ps", "ps xn--ygbi2ammx"),
("whois.registry.qa", "qa xn--wgbl6a"),
("whois.registryservices.music", "music"),
("whois.ricta.org.rw", "rw"),
("whois.rnids.rs", "rs xn--90a3ac"),
("whois.rotld.ro", "ro"),
("whois.ryce-rsp.com", "cologne koeln"),
("whois.sgnic.sg", "sg"),
("whois.sk-nic.sk", "sk"),
("whois.sr", "sr"),
("whois.sx", "sx"),
("whois.ta.sgnic.sg", "xn--clchc0ea0b2g2a9gcd"),
("whois.tcinet.ru", "ru su xn--p1ai"),
(
"whois.teleinfo.cn",
"anquan shouji xihuan xn--3ds443g xn--fiq228c5hs xn--vuq861b yun",
),
("whois.thnic.co.th", "th xn--o3cw4h"),
("whois.tld.ee", "ee"),
("whois.tld.mu", "mu"),
("whois.tld.sy", "sy xn--ogbpf8fl"),
("whois.tonicregistry.to", "to"),
("whois.trabis.gov.tr", "tr"),
("whois.twnic.net.tw", "tw xn--kprw13d xn--kpry57d"),
("whois.tznic.or.tz", "tz"),
("whois.ua", "ua"),
("whois.uniregistry.net", "juegos link"),
("whois.verisign-grs.com", "com net"),
("whois.weare.ie", "ie"),
("whois.website.ws", "ws"),
("whois.y.net.ye", "ye"),
("whois.zh.sgnic.sg", "xn--yfro4i67o"),
("whois.zicta.zm", "zm"),
("whois1.nic.bi", "bi"),
("www.whois.fj", "fj"),
];
const IDN_ALIASES: &str = "
vermögensberater vermögensberatung ευ бг бел дети ею католик ком мкд мон москва онлайн орг рф
сайт срб укр қаз հայ ישראל קום ابوظبي البحرين الجزائر السعودية العليان امارات ایران بارت بازار بيتك
بھارت تونس سورية شبكة عراق عرب عمان فلسطين قطر كاثوليك كوم مصر مليسيا موريتانيا موقع همراه ڀارت
कॉम नेट भारत भारतम् भारोत संगठन ভারত ভাৰত ਭਾਰਤ ભારત ଭାରତ இந்தியா சிங்கப்பூர் భారత్ ಭಾರತ ഭാരതം คอม ไทย
ລາວ アマゾン クラウド コム ストア セール ファッション ポイント 中信 中国 中國 中文网 亚马逊 企业
佛山 信息 八卦 公司 公益 台湾 台灣 商城 商店 嘉里 嘉里大酒店 在线 大拿 天主教 娱乐 家電 广东
微博 慈善 我爱你 手机 政务 政府 新加坡 新闻 时尚 書籍 机构 淡马锡 游戏 澳門 点看 移动 组织机构
网址 网店 网站 网络 联通 购物 通販 集团 電訊盈科 飞利浦 食品 香格里拉 香港 닷넷 닷컴 삼성 한국
";
static SLD_WHOIS_SERVERS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
let mut m = HashMap::new();
m.insert("co.za", "whois.registry.net.za");
m.insert("net.za", "whois.registry.net.za");
m.insert("org.za", "whois.registry.net.za");
m.insert("web.za", "whois.registry.net.za");
m
});
pub fn get_whois_server(tld: &str) -> Option<&'static str> {
let lower = tld.to_lowercase();
if let Some(server) = WHOIS_SERVERS.get(lower.as_str()) {
return Some(server);
}
if !lower.is_ascii() {
if let Ok(ascii) = crate::validation::domain_to_ascii(&lower) {
return WHOIS_SERVERS.get(ascii.as_str()).map(String::as_str);
}
}
None
}
pub fn get_whois_server_for_domain(domain: &str) -> Option<&'static str> {
let lower = domain.trim_end_matches('.').to_lowercase();
let lower = if lower.is_ascii() {
lower
} else {
crate::validation::domain_to_ascii(&lower).unwrap_or(lower)
};
let labels: Vec<&str> = lower.rsplit('.').collect();
if labels.len() >= 2 {
let sld_zone = format!("{}.{}", labels[1], labels[0]);
if let Some(server) = SLD_WHOIS_SERVERS.get(sld_zone.as_str()) {
return Some(server);
}
}
labels.first().and_then(|tld| get_whois_server(tld))
}
pub fn get_tld(domain: &str) -> &str {
domain.rsplit('.').next().unwrap_or(domain)
}
pub fn get_registry_url(tld: &str) -> String {
let tld_lower = tld.to_lowercase();
let tld_lower = if tld_lower.is_ascii() {
tld_lower
} else {
crate::validation::domain_to_ascii(&tld_lower).unwrap_or(tld_lower)
};
match tld_lower.as_str() {
"com" | "net" | "cc" | "tv" => {
return "https://www.verisign.com/en_US/domain-names/index.xhtml".to_string()
}
"org" => return "https://thenew.org/org-people/domain-management/whois/".to_string(),
"edu" => return "https://www.educause.edu/whois".to_string(),
"gov" => return "https://domains.dotgov.gov/".to_string(),
"app" | "dev" | "page" => return "https://www.registry.google/policies/whois/".to_string(),
_ => {}
}
if let Some(whois_server) = get_whois_server(&tld_lower) {
if let Some(suffix) = whois_server.strip_prefix("whois.nic.") {
if suffix == tld_lower {
return format!("https://nic.{}", suffix);
}
}
if whois_server.starts_with("whois.") {
if tld_lower.len() == 2 {
return format!("https://nic.{}", tld_lower);
}
}
}
format!("https://www.iana.org/domains/root/db/{}.html", tld_lower)
}
pub const RDAP_ONLY_TLDS: &str = "
ads android app boo cal channel chrome dad day dclk dev docs drive eat esq fly foo gbiz gle
gmail goog google guge hangout here how ing map meet meme mov new nexus page phd play prod prof
rsvp search soy youtube zip xn--flw351e xn--q9jyb4c xn--qcka1pmc みんな グーグル 谷歌
";
pub const WHOIS_RETIRED_TLDS: &str = "
apple brussels cymru wales vlaanderen pharmacy na xn--p1acf рус lk xn--fzc2c9e2c ලංකා
xn--xkc2al3hye2a இலங்கை mt
";
pub const NO_WHOIS_TLDS: &str = "
able aetna aig al americanexpress amex amica analytics ao aq aramco athleta axa az ba banamex bb
bd booking bs bt bv calvinklein caravan cbn cbre cg chase cisco citi citic ck cu cw cy dell dhl
dj dupont eg er et farmers ferrero fk flickr flir ford frontier ftr gap gb gm gr grainger gt gu
gw hbo health homegoods homesense hsbc hyatt ieee intuit ipiranga itau jm jmp jnj jpmorgan kh
km kp kpmg kpn kred lanxess lilly lincoln lr marshalls mattel mh mil mint mlb mp mv nba ne
netflix neustar nfl ni nike np nr pa pfizer ph pn praxi pru prudential py sas sj sohu staples
statefarm sv sz target tj tjmaxx tjx tkmaxx tt va vivo vn weather weatherchannel web williamhill
winners xn--54b7fta0cc xn--czr694b xn--imr513n xn--mgba3a3ejt xn--mgbai9azgqp6j xn--mgbayh7gpa
xn--mgbc0a9azcg xn--mgbpl2fh xn--node xn--nyqy26a xn--otu796d xn--qxam
xn--rhqv96g za zw ελ ارامكو الاردن المغرب سودان پاکستان বাংলা გე 世界 健康 商标 招聘
餐厅
";
pub(crate) fn has_no_whois_server(tld: &str) -> bool {
static NO_WHOIS: LazyLock<std::collections::HashSet<&'static str>> = LazyLock::new(|| {
[RDAP_ONLY_TLDS, WHOIS_RETIRED_TLDS, NO_WHOIS_TLDS]
.into_iter()
.flat_map(str::split_ascii_whitespace)
.collect()
});
NO_WHOIS.contains(tld.to_lowercase().as_str())
}
pub fn all_tlds() -> &'static [&'static str] {
static ALL: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
let mut v: Vec<&'static str> = WHOIS_SERVERS.keys().copied().collect();
for list in [RDAP_ONLY_TLDS, WHOIS_RETIRED_TLDS, NO_WHOIS_TLDS] {
v.extend(list.split_ascii_whitespace());
}
v.sort_unstable();
v.dedup();
v
});
&ALL
}
#[cfg(test)]
mod all_tlds_tests {
use super::*;
#[test]
fn whois_retired_tlds_stay_in_catalog() {
let tlds = all_tlds();
for tld in [
"apple",
"brussels",
"cymru",
"wales",
"vlaanderen",
"pharmacy",
"na",
"xn--p1acf",
"рус",
"lk",
"xn--fzc2c9e2c",
"ලංකා",
"xn--xkc2al3hye2a",
"இலங்கை",
"mt",
] {
assert!(
tlds.binary_search(&tld).is_ok(),
".{tld} missing from all_tlds() — WHOIS-retired TLDs must stay in the catalog"
);
}
}
#[test]
fn all_tlds_is_sorted_deduped_and_large() {
let tlds = all_tlds();
assert!(
tlds.len() > 1500,
"expected the full catalog (incl. no-WHOIS TLDs), got {}",
tlds.len()
);
for w in tlds.windows(2) {
assert!(w[0] < w[1], "not strictly sorted at {:?}", w);
}
}
#[test]
fn no_whois_tlds_are_catalog_only_and_disjoint() {
let tlds = all_tlds();
let others: Vec<&str> = RDAP_ONLY_TLDS
.split_ascii_whitespace()
.chain(WHOIS_RETIRED_TLDS.split_ascii_whitespace())
.collect();
for tld in NO_WHOIS_TLDS.split_ascii_whitespace() {
assert!(
tlds.binary_search(&tld).is_ok(),
".{tld} missing from all_tlds() — no-WHOIS TLDs must stay in the catalog"
);
assert!(
get_whois_server(tld).is_none(),
".{tld} is listed as no-WHOIS but resolves to a WHOIS server"
);
assert!(
!others.contains(&tld),
".{tld} must appear in exactly one catalog list"
);
}
}
#[test]
fn no_whois_idn_tlds_are_paired_in_both_forms() {
let entries: Vec<&str> = NO_WHOIS_TLDS.split_ascii_whitespace().collect();
let set: std::collections::HashSet<&str> = entries.iter().copied().collect();
let unicode_entries: Vec<&&str> = entries.iter().filter(|t| !t.is_ascii()).collect();
assert!(!unicode_entries.is_empty(), "expected IDN no-WHOIS TLDs");
for tld in &unicode_entries {
let ascii = crate::validation::domain_to_ascii(tld)
.unwrap_or_else(|_| panic!("no-WHOIS IDN TLD {tld} must convert to an A-label"));
assert!(
set.contains(ascii.as_str()),
"Unicode no-WHOIS TLD {tld} lacks its punycode form {ascii}"
);
}
let punycode_count = entries.iter().filter(|t| t.starts_with("xn--")).count();
assert_eq!(
unicode_entries.len(),
punycode_count,
"every punycode no-WHOIS entry must have exactly one Unicode form"
);
}
#[test]
fn all_tlds_includes_whois_and_rdap_only() {
let tlds = all_tlds();
assert!(tlds.contains(&"com"), "com (WHOIS) should be present");
assert!(tlds.contains(&"app"), "app (RDAP-only) should be present");
assert!(tlds.contains(&"dev"), "dev (RDAP-only) should be present");
for tld in ["dclk", "map", "prod", "xn--q9jyb4c", "みんな", "谷歌"] {
assert!(
tlds.binary_search(&tld).is_ok(),
".{tld} (Google, RDAP-only) should be in the catalog"
);
}
}
#[test]
fn unicode_aliases_agree_with_punycode_entries() {
let unicode_keys: Vec<&str> = WHOIS_SERVERS
.keys()
.copied()
.filter(|k| !k.is_ascii())
.collect();
assert!(!unicode_keys.is_empty(), "expected Unicode alias entries");
for key in &unicode_keys {
let ascii = crate::validation::domain_to_ascii(key)
.unwrap_or_else(|_| panic!("Unicode key {key} must convert to an A-label"));
assert_eq!(
WHOIS_SERVERS.get(ascii.as_str()),
WHOIS_SERVERS.get(key),
"alias {key} and its A-label {ascii} must map to the same server"
);
}
let punycode_count = WHOIS_SERVERS
.keys()
.filter(|k| k.starts_with("xn--"))
.count();
assert_eq!(
unicode_keys.len(),
punycode_count,
"every punycode entry must have exactly one Unicode alias"
);
}
#[test]
fn server_tables_list_each_tld_once() {
let listed = NIC_TLDS.split_ascii_whitespace().count()
+ HOSTED_TLDS
.iter()
.map(|(_, tlds)| tlds.split_ascii_whitespace().count())
.sum::<usize>()
+ IDN_ALIASES.split_ascii_whitespace().count();
assert_eq!(listed, WHOIS_SERVERS.len());
}
#[test]
fn server_tables_are_sorted() {
fn assert_sorted<'a>(list: &str, items: impl IntoIterator<Item = &'a str>) {
let items: Vec<&str> = items.into_iter().collect();
for pair in items.windows(2) {
assert!(
pair[0] < pair[1],
"{list}: {:?} is listed after {:?}",
pair[1],
pair[0]
);
}
}
assert_sorted("NIC_TLDS", NIC_TLDS.split_ascii_whitespace());
assert_sorted(
"HOSTED_TLDS hosts",
HOSTED_TLDS.iter().map(|(host, _)| *host),
);
for (host, tlds) in HOSTED_TLDS {
assert_sorted(host, tlds.split_ascii_whitespace());
}
assert_sorted("IDN_ALIASES", IDN_ALIASES.split_ascii_whitespace());
}
#[test]
fn get_whois_server_accepts_unicode_and_punycode_forms() {
assert_eq!(get_whois_server("рф"), Some("whois.tcinet.ru"));
assert_eq!(get_whois_server("xn--p1ai"), Some("whois.tcinet.ru"));
assert_eq!(get_whois_server("РФ"), Some("whois.tcinet.ru"));
assert_eq!(get_whois_server("XN--P1AI"), Some("whois.tcinet.ru"));
assert_eq!(get_whois_server("中国"), Some("whois.cnnic.cn"));
assert_eq!(get_whois_server("ไทย"), Some("whois.thnic.co.th"));
}
#[test]
fn conac_tlds_moved_to_per_tld_servers() {
assert_eq!(
get_whois_server("xn--55qw42g"),
Some("whois.nic.xn--55qw42g")
);
assert_eq!(get_whois_server("公益"), Some("whois.nic.xn--55qw42g"));
assert_eq!(
get_whois_server("xn--zfr164b"),
Some("whois.nic.xn--zfr164b")
);
assert_eq!(get_whois_server("政务"), Some("whois.nic.xn--zfr164b"));
}
#[test]
fn jo_and_bh_servers_follow_iana() {
assert_eq!(get_whois_server("jo"), Some("whois.nic.net.jo"));
for tld in ["bh", "xn--mgbcpq6gpa1a", "البحرين"] {
assert_eq!(get_whois_server(tld), Some("whois.bhregistry.bh"), "{tld}");
}
for tld in ["jo", "bh", "xn--mgbcpq6gpa1a", "البحرين"] {
assert!(!has_no_whois_server(tld), "{tld} still marked no-WHOIS");
}
}
#[test]
fn get_whois_server_for_domain_accepts_idn_domains() {
assert_eq!(
get_whois_server_for_domain("пример.рф"),
Some("whois.tcinet.ru")
);
assert_eq!(
get_whois_server_for_domain("xn--e1afmkfd.xn--p1ai"),
Some("whois.tcinet.ru")
);
assert_eq!(
get_whois_server_for_domain("пример.рф."),
Some("whois.tcinet.ru")
);
assert_eq!(
get_whois_server_for_domain("例え.jp"),
Some("whois.jprs.jp")
);
}
#[test]
fn get_registry_url_uses_punycode_for_idn_tlds() {
assert_eq!(
get_registry_url("рф").as_str(),
"https://www.iana.org/domains/root/db/xn--p1ai.html"
);
assert_eq!(get_registry_url("дети").as_str(), "https://nic.xn--d1acj3b");
assert_eq!(
get_registry_url("дети"),
get_registry_url("xn--d1acj3b"),
"both forms of an IDN TLD must produce the same registry URL"
);
}
#[test]
fn get_registry_url_does_not_misattribute_shared_whois_hosts() {
assert_eq!(
get_registry_url("datsun").as_str(),
"https://www.iana.org/domains/root/db/datsun.html"
);
assert_eq!(get_registry_url("gmo"), "https://nic.gmo");
}
#[test]
fn iana_delisted_whois_servers_stay_removed() {
for tld in [
"apple",
"brussels",
"cymru",
"wales",
"vlaanderen",
"pharmacy",
"na",
"xn--p1acf",
"рус",
"lk",
"xn--fzc2c9e2c",
"ලංකා",
"xn--xkc2al3hye2a",
"இலங்கை",
"mt",
] {
assert!(
get_whois_server(tld).is_none(),
".{tld} must not be mapped (IANA-delisted, dead hostname)"
);
}
}
#[test]
fn relocated_and_unlisted_cctld_servers_are_mapped() {
assert_eq!(get_whois_server("ga"), Some("whois.nic.ga"));
assert_eq!(get_whois_server("ps"), Some("whois.registry.ps"));
assert_eq!(get_whois_server("xn--ygbi2ammx"), Some("whois.registry.ps"));
assert_eq!(get_whois_server("فلسطين"), Some("whois.registry.ps"));
}
#[test]
fn sld_zones_resolve_before_tld() {
assert_eq!(
get_whois_server_for_domain("google.co.za"),
Some("whois.registry.net.za")
);
assert_eq!(
get_whois_server_for_domain("example.web.za"),
Some("whois.registry.net.za")
);
assert_eq!(get_whois_server_for_domain("nic.za"), None);
assert_eq!(
get_whois_server_for_domain("example.com"),
Some("whois.verisign-grs.com")
);
assert_eq!(
get_whois_server_for_domain("bbc.co.uk"),
Some("whois.nic.uk")
);
assert_eq!(
get_whois_server_for_domain("com"),
Some("whois.verisign-grs.com")
);
}
}