use chrono::{DateTime, NaiveDateTime, Utc};
use once_cell::sync::Lazy;
use regex::Regex;
use super::RegistryParser;
use crate::whois::parser::WhoisResponse;
static NS_PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^p\.\s+\[ネームサーバ\]\s+(.+)$").expect("Invalid JPRS NS regex")
});
static ORG_PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^g\.\s+\[Organization\]\s+(.+)$").expect("Invalid JPRS org regex")
});
static ORG_JP_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^f\.\s+\[組織名\]\s+(.+)$").expect("Invalid JPRS org JP regex"));
static STATUS_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\[状態\]\s+(.+)$").expect("Invalid JPRS status regex"));
static UPDATED_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\[最終更新\]\s+(.+)$").expect("Invalid JPRS updated regex"));
static CREATED_PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^\[(?:登録年月日|接続年月日)\]\s+(.+)$").expect("Invalid JPRS created regex")
});
static SIGNING_KEY_LINE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^s\.\s+\[署名鍵\][^\S\n]*(.+)?$").expect("Invalid JPRS signing key regex")
});
static NS_EN_PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?im)^(?:Name Server|p\.)\s*:?\s+(.+)$").expect("Invalid JPRS NS EN regex")
});
#[derive(Debug, Clone, Default)]
pub struct JprsParser;
impl JprsParser {
pub fn new() -> Self {
Self
}
fn parse_jprs_date(date_str: &str) -> Option<DateTime<Utc>> {
let cleaned = date_str.trim().trim_end_matches("(JST)").trim();
if let Ok(dt) = NaiveDateTime::parse_from_str(cleaned, "%Y/%m/%d %H:%M:%S") {
return Some(dt.and_utc() - chrono::Duration::hours(9));
}
if let Ok(dt) =
NaiveDateTime::parse_from_str(&format!("{} 00:00:00", cleaned), "%Y/%m/%d %H:%M:%S")
{
return Some(dt.and_utc());
}
None
}
}
impl RegistryParser for JprsParser {
fn supported_tlds(&self) -> &[&str] {
&["jp"]
}
fn parse(&self, domain: &str, server: &str, raw: &str) -> WhoisResponse {
let mut nameservers = Vec::new();
let mut organization = None;
let mut status = Vec::new();
let mut updated_date = None;
let mut creation_date = None;
let mut has_signing_key = false;
for caps in NS_PATTERN.captures_iter(raw) {
if let Some(m) = caps.get(1) {
let ns = m.as_str().trim().to_lowercase();
if !ns.is_empty() && !nameservers.contains(&ns) {
nameservers.push(ns);
}
}
}
if nameservers.is_empty() {
for caps in NS_EN_PATTERN.captures_iter(raw) {
if let Some(m) = caps.get(1) {
let ns = m.as_str().trim().to_lowercase();
if !ns.is_empty() && !nameservers.contains(&ns) {
nameservers.push(ns);
}
}
}
}
if let Some(caps) = ORG_PATTERN.captures(raw) {
if let Some(m) = caps.get(1) {
let org = m.as_str().trim().to_string();
if !org.is_empty() {
organization = Some(org);
}
}
}
if organization.is_none() {
if let Some(caps) = ORG_JP_PATTERN.captures(raw) {
if let Some(m) = caps.get(1) {
let org = m.as_str().trim().to_string();
if !org.is_empty() {
organization = Some(org);
}
}
}
}
if let Some(caps) = STATUS_PATTERN.captures(raw) {
if let Some(m) = caps.get(1) {
let s = m.as_str().trim().to_string();
if !s.is_empty() {
status.push(s);
}
}
}
if let Some(caps) = UPDATED_PATTERN.captures(raw) {
if let Some(m) = caps.get(1) {
updated_date = Self::parse_jprs_date(m.as_str());
}
}
if let Some(caps) = CREATED_PATTERN.captures(raw) {
if let Some(m) = caps.get(1) {
creation_date = Self::parse_jprs_date(m.as_str());
}
}
if let Some(caps) = SIGNING_KEY_LINE.captures(raw) {
if let Some(m) = caps.get(1) {
if !m.as_str().trim().is_empty() {
has_signing_key = true;
}
}
}
WhoisResponse {
domain: domain.to_string(),
registrar: Some("JPRS".to_string()),
registrant: organization.clone(),
organization,
registrant_email: None,
registrant_phone: None,
registrant_address: None,
registrant_country: Some("JP".to_string()),
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,
expiration_date: None, updated_date,
nameservers,
status,
dnssec: if has_signing_key {
Some("signedDelegation".to_string())
} else {
None
},
whois_server: server.to_string(),
raw_response: raw.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_JPRS_RESPONSE: &str = r#"[ JPRS database provides information on network administration. Its use is ]
[ restricted to network administration purposes. For further information, ]
[ use 'whois -h whois.jprs.jp help'. To suppress Japanese output, add'/e' ]
[ at the end of command, e.g. 'whois -h whois.jprs.jp xxx/e'. ]
Domain Information: [ドメイン情報]
a. [ドメイン名] TOKYO.JP
e. [そしきめい]
f. [組織名] 東京ドメイン
g. [Organization] Tokyo Domain
k. [組織種別]
l. [Organization Type]
m. [登録担当者]
n. [技術連絡担当者]
p. [ネームサーバ] a.dns.jp
p. [ネームサーバ] b.dns.jp
p. [ネームサーバ] c.dns.jp
p. [ネームサーバ] d.dns.jp
p. [ネームサーバ] e.dns.jp
p. [ネームサーバ] f.dns.jp
p. [ネームサーバ] g.dns.jp
p. [ネームサーバ] h.dns.jp
s. [署名鍵]
[状態] Reserved
[登録年月日]
[接続年月日]
[最終更新] 2005/03/30 17:37:52 (JST)"#;
const SAMPLE_JPRS_DOMAIN: &str = r#"[ JPRS database provides information on network administration. Its use is ]
Domain Information: [ドメイン情報]
a. [ドメイン名] EXAMPLE.JP
f. [組織名] 株式会社エグザンプル
g. [Organization] Example Inc.
p. [ネームサーバ] ns1.example.jp
p. [ネームサーバ] ns2.example.jp
s. [署名鍵]
[状態] Active
[登録年月日] 2001/03/22
[接続年月日] 2001/03/22
[最終更新] 2024/04/01 01:05:10 (JST)"#;
#[test]
fn test_jprs_nameservers() {
let parser = JprsParser::new();
let result = parser.parse("tokyo.jp", "whois.jprs.jp", SAMPLE_JPRS_RESPONSE);
assert_eq!(result.nameservers.len(), 8);
assert!(result.nameservers.contains(&"a.dns.jp".to_string()));
assert!(result.nameservers.contains(&"h.dns.jp".to_string()));
}
#[test]
fn test_jprs_organization() {
let parser = JprsParser::new();
let result = parser.parse("tokyo.jp", "whois.jprs.jp", SAMPLE_JPRS_RESPONSE);
assert_eq!(result.organization, Some("Tokyo Domain".to_string()));
}
#[test]
fn test_jprs_status() {
let parser = JprsParser::new();
let result = parser.parse("tokyo.jp", "whois.jprs.jp", SAMPLE_JPRS_RESPONSE);
assert!(result.status.contains(&"Reserved".to_string()));
}
#[test]
fn test_jprs_updated_date() {
let parser = JprsParser::new();
let result = parser.parse("tokyo.jp", "whois.jprs.jp", SAMPLE_JPRS_RESPONSE);
assert!(result.updated_date.is_some());
}
#[test]
fn test_jprs_domain_with_dates() {
let parser = JprsParser::new();
let result = parser.parse("example.jp", "whois.jprs.jp", SAMPLE_JPRS_DOMAIN);
assert_eq!(result.nameservers.len(), 2);
assert!(result.nameservers.contains(&"ns1.example.jp".to_string()));
assert!(result.nameservers.contains(&"ns2.example.jp".to_string()));
assert_eq!(result.organization, Some("Example Inc.".to_string()));
assert!(result.status.contains(&"Active".to_string()));
assert!(result.creation_date.is_some());
assert!(result.updated_date.is_some());
}
#[test]
fn test_jprs_no_signing_key() {
let parser = JprsParser::new();
let result = parser.parse("tokyo.jp", "whois.jprs.jp", SAMPLE_JPRS_RESPONSE);
assert!(result.dnssec.is_none());
}
#[test]
fn test_supported_tlds() {
let parser = JprsParser::new();
assert_eq!(parser.supported_tlds(), &["jp"]);
}
}