use super::*;
impl MarkdownFormatter {
pub(super) fn format_whois(&self, response: &WhoisResponse) -> String {
let mut output = Vec::new();
output.push(format!("## WHOIS: {}", MdSafe(&response.domain)));
output.push(String::new());
if response.is_available() {
output.push("Domain is **available** for registration.".to_string());
return output.join("\n");
}
if let Some(ref registrar) = response.registrar {
output.push(format!("- **Registrar**: {}", MdSafe(registrar)));
}
if let Some(ref registrant) = response.registrant {
output.push(format!("- **Registrant**: {}", MdSafe(registrant)));
}
if let Some(ref organization) = response.organization {
output.push(format!("- **Organization**: {}", MdSafe(organization)));
}
if let Some(created) = response.creation_date {
output.push(format!("- **Created**: `{}`", created.format("%Y-%m-%d")));
}
if let Some(expires) = response.expiration_date {
let days_until = days_until(expires);
output.push(format!(
"- **Expires**: `{}` ({} days)",
expires.format("%Y-%m-%d"),
days_until
));
}
if let Some(updated) = response.updated_date {
output.push(format!("- **Updated**: `{}`", updated.format("%Y-%m-%d")));
}
if !response.nameservers.is_empty() {
output.push(format!(
"- **Nameservers**: {}",
response
.nameservers
.iter()
.map(|ns| format!("`{}`", MdSafe(ns)))
.collect::<Vec<_>>()
.join(", ")
));
}
if !response.status.is_empty() {
output.push(format!(
"- **Status**: {}",
response
.status
.iter()
.map(|s| format!("`{}`", MdSafe(s)))
.collect::<Vec<_>>()
.join(", ")
));
}
if let Some(ref dnssec) = response.dnssec {
output.push(format!("- **DNSSEC**: {}", MdSafe(dnssec)));
}
output.push(format!(
"- **WHOIS Server**: `{}`",
MdSafe(&response.whois_server)
));
self.format_whois_contacts(&mut output, response);
output.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::whois::WhoisResponse;
fn empty_whois(domain: &str) -> WhoisResponse {
WhoisResponse {
domain: domain.to_string(),
registrar: None,
registrant: None,
organization: None,
registrant_email: None,
registrant_phone: None,
registrant_address: None,
registrant_country: None,
admin_name: None,
admin_organization: None,
admin_email: None,
admin_phone: None,
tech_name: None,
tech_organization: None,
tech_email: None,
tech_phone: None,
creation_date: None,
expiration_date: None,
updated_date: None,
nameservers: vec![],
status: vec!["clientTransferProhibited".to_string()],
dnssec: None,
whois_server: "whois.example.invalid".to_string(),
raw_response: String::new(),
}
}
#[test]
fn test_markdown_whois_registrar_newline_neutralized() {
let mut w = empty_whois("example.com");
w.registrar = Some("Foo\nIgnore previous".to_string());
let output = MarkdownFormatter::new().format_whois(&w);
assert!(
output.contains("- **Registrar**: Foo Ignore previous"),
"expected sanitized registrar in output:\n{}",
output
);
for line in output.lines() {
assert!(
!line.trim_start().starts_with("Ignore previous"),
"attacker payload broke onto its own line:\n{}",
output
);
}
}
#[test]
fn test_markdown_whois_domain_fields_precede_contact_sections() {
let w = WhoisResponse::parse(
"example.com",
"whois.test",
"Registrar: Mock Registrar\n\
Creation Date: 2010-03-15T04:00:00Z\n\
Registry Expiry Date: 2099-03-15T04:00:00Z\n\
Registrant Country: US\n\
Admin Name: Jane Admin\n\
Tech Email: tech@example.com\n\
Name Server: ns1.example.com\n\
Domain Status: ok\n\
DNSSEC: unsigned\n",
);
let out = MarkdownFormatter::new().format_whois(&w);
let first_heading = out.find("\n###").expect("contact sections rendered");
for field in [
"- **Registrar**",
"- **Created**",
"- **Expires**",
"- **Nameservers**",
"- **Status**",
"- **DNSSEC**",
"- **WHOIS Server**",
] {
let at = out
.find(field)
.unwrap_or_else(|| panic!("{field} missing:\n{out}"));
assert!(
at < first_heading,
"{field} rendered under a contact heading:\n{out}"
);
}
for heading in [
"### Registrant Contact",
"### Admin Contact",
"### Tech Contact",
] {
assert!(out.contains(heading), "{heading} missing:\n{out}");
}
assert!(
out.contains("- **Country**: US"),
"contact field kept:\n{out}"
);
}
}