use super::objects::{class, find_by_handle, first, objects, values};
use super::{push_bounded, MAX_STATUSES};
use crate::whois::parser::{parse_date, WhoisResponse};
pub(super) const TLDS: &[&str] = &["ar", "cr", "cz", "ls", "mk", "mw", "tz", "ve"];
pub(super) fn parse(domain: &str, server: &str, raw: &str) -> WhoisResponse {
let mut response = WhoisResponse::parse_internal(domain, server, raw);
let objects = objects(raw);
let Some(dom) = objects.iter().find(|o| class(o) == Some("domain")) else {
return response;
};
response.creation_date = first(dom, "registered").and_then(parse_date);
response.updated_date = first(dom, "changed").and_then(parse_date);
response.expiration_date = first(dom, "expire").and_then(parse_date);
let mut status = Vec::new();
for value in values(dom, "status") {
push_bounded(&mut status, value.to_string(), MAX_STATUSES);
}
response.status = status;
if let Some(handle) = first(dom, "registrant") {
if let Some(contact) = find_by_handle(&objects, "contact", handle) {
let org = first(contact, "org").map(str::to_string);
if let Some(name) = first(contact, "name").map(str::to_string).or(org.clone()) {
response.registrant = Some(name);
}
response.organization = org;
}
}
response
}
#[cfg(test)]
mod tests {
use super::*;
const CZ: &str = "% (c) 2006-2021 CZ.NIC, z.s.p.o.\n\
%\n\
% Whoisd Server Version: 3.15.0\n\
\n\
domain: nic.cz\n\
registrant: CZ-NIC\n\
admin-c: FEELA\n\
nsset: CZ.NIC\n\
keyset: AUTO-ZHHWRIDCGH9F46ZDG2K2VC284\n\
registrar: REG-CZNIC\n\
status: Sponsoring registrar change forbidden\n\
registered: 30.10.1997 01:00:00\n\
changed: 17.11.2020 14:25:52\n\
expire: 15.03.2027\n\
\n\
contact: CZ-NIC\n\
org: CZ.NIC, z.s.p.o.\n\
name: CZ.NIC, z.s.p.o.\n\
address: Milesovska 1136/5\n\
registrar: REG-CZNIC\n\
created: 17.10.2008 12:08:21\n\
changed: 15.05.2018 21:32:00\n\
\n\
contact: FEELA\n\
name: Ondřej Filip\n\
registrar: REG-MOJEID\n\
created: 04.01.2005 13:35:00\n\
\n\
nsset: CZ.NIC\n\
nserver: d.ns.nic.cz (193.29.206.1, 2001:678:1::1)\n\
nserver: a.ns.nic.cz (194.0.12.1, 2001:678:f::1)\n\
tech-c: JTALIR\n\
registrar: REG-CZNIC\n\
created: 05.10.2007 13:36:00\n";
const AR: &str = "% La información a la que estás accediendo se provee exclusivamente para\n\
% fines relacionados con operaciones sobre nombres de dominios y DNS.\n\
\n\
domain:\t\tnic.ar\n\
registrant:\t30710551665\n\
registrar:\tnicar\n\
registered:\t1998-05-29 00:00:00\n\
changed:\t2023-07-17 17:23:05.989470\n\
expire:\t\t2100-12-31 00:00:00\n\
\n\
contact:\t30710551665\n\
name:\t\tSECRETARIA LEGAL Y TECNICA\n\
registrar:\tnicar\n\
created:\t2013-08-19 00:00:00\n\
changed:\t2024-04-18 15:16:11.362047\n\
\n\
nserver:\tns5.rdns.ar ()\n\
nserver:\tns6.rdns.ar ()\n\
registrar:\tnicar\n\
created:\t2019-07-12 17:36:57.685138\n";
fn ymd(d: Option<chrono::DateTime<chrono::Utc>>) -> Option<String> {
d.map(|d| d.format("%Y-%m-%d").to_string())
}
#[test]
fn dates_come_from_the_domain_object_not_a_contact() {
let r = parse("nic.cz", "whois.nic.cz", CZ);
assert_eq!(ymd(r.creation_date).as_deref(), Some("1997-10-30"));
assert_eq!(ymd(r.expiration_date).as_deref(), Some("2027-03-15"));
assert_eq!(ymd(r.updated_date).as_deref(), Some("2020-11-17"));
}
#[test]
fn prose_status_is_kept_whole() {
let r = parse("nic.cz", "whois.nic.cz", CZ);
assert_eq!(r.status, vec!["Sponsoring registrar change forbidden"]);
}
#[test]
fn registrant_handle_resolves_to_its_contact_object() {
let r = parse("nic.cz", "whois.nic.cz", CZ);
assert_eq!(r.registrant.as_deref(), Some("CZ.NIC, z.s.p.o."));
assert_eq!(r.organization.as_deref(), Some("CZ.NIC, z.s.p.o."));
}
#[test]
fn generic_fields_still_parse() {
let r = parse("nic.cz", "whois.nic.cz", CZ);
assert_eq!(r.registrar.as_deref(), Some("REG-CZNIC"));
assert_eq!(r.nameservers, vec!["d.ns.nic.cz", "a.ns.nic.cz"]);
assert!(r.has_core_data());
}
#[test]
fn nic_ar_layout_parses_the_same_way() {
let r = parse("nic.ar", "whois.nic.ar", AR);
assert_eq!(ymd(r.creation_date).as_deref(), Some("1998-05-29"));
assert_eq!(ymd(r.expiration_date).as_deref(), Some("2100-12-31"));
assert_eq!(ymd(r.updated_date).as_deref(), Some("2023-07-17"));
assert_eq!(r.registrant.as_deref(), Some("SECRETARIA LEGAL Y TECNICA"));
assert!(r.status.is_empty());
}
#[test]
fn crlf_body_parses_like_lf() {
let r = parse("nic.cz", "whois.nic.cz", &CZ.replace('\n', "\r\n"));
assert_eq!(ymd(r.creation_date).as_deref(), Some("1997-10-30"));
assert_eq!(r.status, vec!["Sponsoring registrar change forbidden"]);
}
#[test]
fn reply_without_a_domain_object_keeps_the_generic_read() {
let raw = "% (c) CZ.NIC\n%\n%ERROR:101: no entries found\n";
let r = parse("free-name.cz", "whois.nic.cz", raw);
assert!(r.creation_date.is_none());
assert!(r.indicates_not_found() || r.is_available());
}
}