mod denic;
mod educause;
mod eis;
mod eurid;
mod isoc_il;
mod jprs;
mod kisa;
mod nic_it;
mod nic_lv;
mod nominet;
mod sidn;
use super::parser::WhoisResponse;
pub(crate) const MAX_NAMESERVERS: usize = 32;
pub(crate) const MAX_STATUSES: usize = 32;
pub(crate) fn push_bounded(vec: &mut Vec<String>, value: String, cap: usize) {
if !value.is_empty() && vec.len() < cap && !vec.contains(&value) {
vec.push(value);
}
}
type ParseFn = fn(&str, &str, &str) -> WhoisResponse;
const PARSERS: &[(&[&str], ParseFn)] = &[
(denic::TLDS, denic::parse), (educause::TLDS, educause::parse), (eis::TLDS, eis::parse), (eurid::TLDS, eurid::parse), (isoc_il::TLDS, isoc_il::parse), (jprs::TLDS, jprs::parse), (kisa::TLDS, kisa::parse), (nic_it::TLDS, nic_it::parse), (nic_lv::TLDS, nic_lv::parse), (nominet::TLDS, nominet::parse), (sidn::TLDS, sidn::parse), ];
pub(crate) fn parse(domain: &str, server: &str, raw: &str) -> WhoisResponse {
let zones: Vec<String> = [
extract_second_level_tld(domain),
super::get_tld(domain).map(str::to_lowercase),
]
.into_iter()
.flatten()
.collect();
for (tlds, parse) in PARSERS {
if zones.iter().any(|zone| tlds.contains(&zone.as_str())) {
return parse(domain, server, raw);
}
}
WhoisResponse::parse_internal(domain, server, raw)
}
fn extract_second_level_tld(domain: &str) -> Option<String> {
let parts: Vec<&str> = domain.rsplit('.').collect();
if parts.len() >= 3 {
Some(format!(
"{}.{}",
parts[1].to_lowercase(),
parts[0].to_lowercase()
))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_second_level_tld() {
assert_eq!(
extract_second_level_tld("example.co.uk"),
Some("co.uk".to_string())
);
assert_eq!(extract_second_level_tld("example.com"), None);
}
#[test]
fn test_parser_registry_selects_denic_for_de() {
let result = parse(
"example.de",
"whois.denic.de",
"Domain: example.de\nStatus: connect",
);
assert_eq!(result.status, vec!["active"]);
}
#[test]
fn test_parser_registry_selects_nominet_for_uk() {
let result = parse(
"example.co.uk",
"whois.nic.uk",
"Domain name:\n example.co.uk\n\nRegistrar:\n Example Registrar Ltd [Tag = EXAMPLE]\n",
);
assert_eq!(result.registrar.as_deref(), Some("Example Registrar Ltd"));
}
#[test]
fn test_parser_registry_uses_generic_for_unknown() {
let result = parse(
"example.com",
"whois.verisign-grs.com",
"Domain Name: example.com\nRegistrar: Example Registrar, Inc.\nStatus: connect\n",
);
assert_eq!(result.registrar.as_deref(), Some("Example Registrar, Inc."));
assert_eq!(result.status, vec!["connect"]);
}
#[test]
fn test_parser_registry_selects_kisa_for_korean_idn_tlds() {
let raw = "# ENGLISH\n\
\n\
Domain Name : example.xn--3e0b707e\n\
Authorized Agency : Whois Corp.(http://whois.co.kr)\n\
\n\
Primary Name Server\n\
\x20 Host Name : ns1.example.kr\n";
for domain in ["example.xn--3e0b707e", "example.xn--cg4bki"] {
let result = parse(domain, "whois.kr", raw);
assert_eq!(
result.registrar.as_deref(),
Some("Whois Corp."),
"{domain} must use the KISA parser"
);
assert_eq!(result.nameservers, vec!["ns1.example.kr"], "{domain}");
}
}
#[test]
fn test_parser_registry_selects_eurid_for_eu_idn_tlds() {
let raw = "Domain: example.xn--e1a4c\n\
\n\
Name servers:\n\
\x20 ns1.example.eu (192.0.2.1)\n\
\x20 ns2.example.eu\n\
\n\
Keys:\n\
\x20 flags:KSK protocol:3 algorithm:RSA_SHA256 pubKey:AwEAAtest\n";
for domain in ["example.xn--e1a4c", "example.xn--qxa6a"] {
let result = parse(domain, "whois.eu", raw);
assert_eq!(
result.nameservers,
vec!["ns1.example.eu", "ns2.example.eu"],
"{domain} must use the EURid parser"
);
assert_eq!(
result.dnssec.as_deref(),
Some("signedDelegation"),
"{domain}"
);
}
}
}