use std::net::IpAddr;
use std::time::Duration;
mod client;
mod error;
mod parse;
mod referral;
pub use error::WhoisError;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
pub struct WhoisResponse {
pub server: String,
pub raw: String,
pub registrar: Option<String>,
pub created: Option<String>,
pub expires: Option<String>,
pub nameservers: Vec<String>,
}
pub fn lookup(domain: &str) -> Result<WhoisResponse, WhoisError> {
lookup_with_timeout(domain, DEFAULT_TIMEOUT)
}
pub fn lookup_with_timeout(
domain: &str,
timeout: Duration,
) -> Result<WhoisResponse, WhoisError> {
let domain = normalize_domain(domain)?;
let tld = tld_of(&domain).ok_or_else(|| WhoisError::NoTld(domain.clone()))?;
let server = referral::lookup_server(tld, timeout)?;
finalize(server, &domain, timeout)
}
pub fn lookup_ip(ip: IpAddr) -> Result<WhoisResponse, WhoisError> {
lookup_ip_with_timeout(ip, DEFAULT_TIMEOUT)
}
pub fn lookup_ip_with_timeout(
ip: IpAddr,
timeout: Duration,
) -> Result<WhoisResponse, WhoisError> {
let query = ip.to_string();
let server = referral::lookup_server(&query, timeout)?;
finalize(server, &query, timeout)
}
fn finalize(
server: String,
target_query: &str,
timeout: Duration,
) -> Result<WhoisResponse, WhoisError> {
let raw = client::query(&server, client::WHOIS_PORT, target_query, timeout)?;
let fields = parse::extract(&raw);
Ok(WhoisResponse {
server,
raw,
registrar: fields.registrar,
created: fields.created,
expires: fields.expires,
nameservers: fields.nameservers,
})
}
fn normalize_domain(input: &str) -> Result<String, WhoisError> {
let s = input.trim().to_ascii_lowercase();
let s = s.strip_prefix("www.").unwrap_or(&s).to_string();
if s.is_empty() {
return Err(WhoisError::EmptyDomain);
}
Ok(s)
}
fn tld_of(domain: &str) -> Option<&str> {
domain.rsplit('.').next().filter(|t| !t.is_empty())
}
#[cfg(test)]
mod tests {
use super::{normalize_domain, tld_of};
#[test]
fn strips_www_and_lowercases() {
assert_eq!(normalize_domain(" WWW.Rust-Lang.ORG ").unwrap(), "rust-lang.org");
}
#[test]
fn rejects_empty() {
assert!(normalize_domain(" ").is_err());
}
#[test]
fn extracts_tld() {
assert_eq!(tld_of("rust-lang.org"), Some("org"));
assert_eq!(tld_of("bbc.co.uk"), Some("uk"));
}
}