use crate::bootstrap::{KNOWN_ENTITY_TAGS, extract_entity_tag};
use crate::error::Result;
use std::fmt;
use url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueryType {
Domain,
Ip,
Autnum,
Entity,
Nameserver,
Help,
DomainSearch,
DomainSearchByNameserver,
DomainSearchByNameserverIp,
NameserverSearch,
NameserverSearchByIp,
EntitySearch,
EntitySearchByHandle,
}
impl fmt::Display for QueryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
QueryType::Domain => "domain",
QueryType::Ip => "ip",
QueryType::Autnum => "autnum",
QueryType::Entity => "entity",
QueryType::Nameserver => "nameserver",
QueryType::Help => "help",
QueryType::DomainSearch => "domain-search",
QueryType::DomainSearchByNameserver => "domain-search-by-nameserver",
QueryType::DomainSearchByNameserverIp => "domain-search-by-nameserver-ip",
QueryType::NameserverSearch => "nameserver-search",
QueryType::NameserverSearchByIp => "nameserver-search-by-ip",
QueryType::EntitySearch => "entity-search",
QueryType::EntitySearchByHandle => "entity-search-by-handle",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct RdapRequest {
pub query_type: QueryType,
pub query: String,
pub server: Option<Url>,
}
impl RdapRequest {
pub fn new(query_type: QueryType, query: impl Into<String>) -> Self {
Self {
query_type,
query: query.into(),
server: None,
}
}
pub fn with_server(mut self, server: Url) -> Self {
self.server = Some(server);
self
}
pub fn build_url(&self, base_url: &Url) -> Result<Url> {
let path = match self.query_type {
QueryType::Domain => format!("domain/{}", urlencoding::encode(&self.query)),
QueryType::Ip => format!("ip/{}", self.query),
QueryType::Autnum => {
let asn = self.query.trim_start_matches("AS").trim_start_matches("as");
format!("autnum/{}", asn)
}
QueryType::Entity => format!("entity/{}", urlencoding::encode(&self.query)),
QueryType::Nameserver => format!("nameserver/{}", urlencoding::encode(&self.query)),
QueryType::Help => "help".to_string(),
QueryType::DomainSearch => {
return Ok(base_url.join(&format!(
"domains?name={}",
urlencoding::encode(&self.query)
))?);
}
QueryType::DomainSearchByNameserver => {
return Ok(base_url.join(&format!(
"domains?nsLdhName={}",
urlencoding::encode(&self.query)
))?);
}
QueryType::DomainSearchByNameserverIp => {
return Ok(base_url.join(&format!("domains?nsIp={}", self.query))?);
}
QueryType::NameserverSearch => {
return Ok(base_url.join(&format!(
"nameservers?name={}",
urlencoding::encode(&self.query)
))?);
}
QueryType::NameserverSearchByIp => {
return Ok(base_url.join(&format!("nameservers?ip={}", self.query))?);
}
QueryType::EntitySearch => {
return Ok(
base_url.join(&format!("entities?fn={}", urlencoding::encode(&self.query)))?
);
}
QueryType::EntitySearchByHandle => {
return Ok(base_url.join(&format!(
"entities?handle={}",
urlencoding::encode(&self.query)
))?);
}
};
Ok(base_url.join(&path)?)
}
pub fn detect_type(query: &str) -> Result<QueryType> {
if query.to_uppercase().starts_with("AS") && query[2..].chars().all(|c| c.is_ascii_digit())
{
return Ok(QueryType::Autnum);
}
if query.chars().all(|c| c.is_ascii_digit()) {
return Ok(QueryType::Autnum);
}
if query.contains(':') || query.chars().all(|c| c.is_ascii_digit() || c == '.') {
return Ok(QueryType::Ip);
}
if is_entity_handle(query) {
return Ok(QueryType::Entity);
}
Ok(QueryType::Domain)
}
}
fn is_entity_handle(query: &str) -> bool {
if !query.contains('-') {
return false;
}
if query.contains('.') {
return false;
}
if let Some(tag) = extract_entity_tag(query) {
return KNOWN_ENTITY_TAGS
.iter()
.any(|&known| known.eq_ignore_ascii_case(&tag));
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_type() {
assert_eq!(
RdapRequest::detect_type("example.com").unwrap(),
QueryType::Domain
);
assert_eq!(
RdapRequest::detect_type("192.0.2.1").unwrap(),
QueryType::Ip
);
assert_eq!(
RdapRequest::detect_type("2001:db8::1").unwrap(),
QueryType::Ip
);
assert_eq!(
RdapRequest::detect_type("AS15169").unwrap(),
QueryType::Autnum
);
assert_eq!(
RdapRequest::detect_type("15169").unwrap(),
QueryType::Autnum
);
}
#[test]
fn test_detect_entity_ripe() {
assert_eq!(
RdapRequest::detect_type("ORG-LA1994-RIPE").unwrap(),
QueryType::Entity
);
}
#[test]
fn test_detect_entity_arin() {
assert_eq!(
RdapRequest::detect_type("YANGY12-ARIN").unwrap(),
QueryType::Entity
);
}
#[test]
fn test_detect_entity_apnic() {
assert_eq!(
RdapRequest::detect_type("IRT-APNIC-AP-APNIC").unwrap(),
QueryType::Entity
);
}
#[test]
fn test_detect_entity_lacnic() {
assert_eq!(
RdapRequest::detect_type("IB1234-LACNIC").unwrap(),
QueryType::Entity
);
}
#[test]
fn test_detect_entity_lowercase() {
assert_eq!(
RdapRequest::detect_type("org-la1994-ripe").unwrap(),
QueryType::Entity
);
}
#[test]
fn test_detect_domain_not_entity() {
assert_eq!(
RdapRequest::detect_type("example-ripe.com").unwrap(),
QueryType::Domain
);
}
#[test]
fn test_detect_unknown_tag_is_domain() {
assert_eq!(
RdapRequest::detect_type("HANDLE-UNKNOWN").unwrap(),
QueryType::Domain
);
}
#[test]
fn test_is_entity_handle() {
assert!(is_entity_handle("ORG-LA1994-RIPE"));
assert!(is_entity_handle("YANGY12-ARIN"));
assert!(is_entity_handle("TEST-APNIC"));
assert!(is_entity_handle("IB1234-LACNIC"));
assert!(is_entity_handle("AS1234-AFRINIC"));
assert!(is_entity_handle("X-FRNIC"));
assert!(is_entity_handle("TEST-GLAUCA"));
assert!(is_entity_handle("NORID-HDL-NORID"));
assert!(!is_entity_handle("example.com"));
assert!(!is_entity_handle("SOMEHANDLE"));
assert!(!is_entity_handle("192.168.1.1"));
assert!(!is_entity_handle("HANDLE-UNKNOWN"));
}
}