1use std::{
3 net::{IpAddr, Ipv4Addr, Ipv6Addr},
4 str::FromStr,
5 sync::LazyLock,
6};
7
8use icann_rdap_common::rdns::{ip_to_reverse_dns, reverse_dns_to_ip};
9
10use {
11 cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr},
12 icann_rdap_common::{check::StringCheck, dns_types::DomainName},
13 pct_str::{PctString, UriReserved},
14 regex::Regex,
15 strum_macros::Display,
16};
17
18use crate::RdapClientError;
19
20#[derive(Display, Debug, Clone)]
22pub enum QueryType {
23 #[strum(serialize = "IpV4 Address Lookup")]
24 IpV4Addr(Ipv4Addr),
25
26 #[strum(serialize = "IpV6 Address Lookup")]
27 IpV6Addr(Ipv6Addr),
28
29 #[strum(serialize = "IpV4 CIDR Lookup")]
30 IpV4Cidr(Ipv4Cidr),
31
32 #[strum(serialize = "IpV6 CIDR Lookup")]
33 IpV6Cidr(Ipv6Cidr),
34
35 #[strum(serialize = "Autonomous System Number Lookup")]
36 AsNumber(u32),
37
38 #[strum(serialize = "Domain Lookup")]
39 Domain(DomainName),
40
41 #[strum(serialize = "A-Label Domain Lookup")]
42 ALabel(DomainName),
43
44 #[strum(serialize = "Reverse DNS Domain Lookup")]
45 ReverseDNs(IpAddr),
46
47 #[strum(serialize = "Entity Lookup")]
48 Entity(String),
49
50 #[strum(serialize = "Nameserver Lookup")]
51 Nameserver(DomainName),
52
53 #[strum(serialize = "Entity Name Search")]
54 EntityNameSearch(String),
55
56 #[strum(serialize = "Entity Handle Search")]
57 EntityHandleSearch(String),
58
59 #[strum(serialize = "Domain Name Search")]
60 DomainNameSearch(String),
61
62 #[strum(serialize = "Domain Nameserver Name Search")]
63 DomainNsNameSearch(String),
64
65 #[strum(serialize = "Domain Nameserver IP Address Search")]
66 DomainNsIpSearch(IpAddr),
67
68 #[strum(serialize = "Nameserver Name Search")]
69 NameserverNameSearch(String),
70
71 #[strum(serialize = "Nameserver IP Address Search")]
72 NameserverIpSearch(IpAddr),
73
74 #[strum(serialize = "Server Help Lookup")]
75 Help,
76
77 #[strum(serialize = "Explicit URL")]
78 Url(String),
79}
80
81impl QueryType {
82 pub fn query_url(&self, base_url: &str) -> Result<String, RdapClientError> {
83 let base_url = base_url.trim_end_matches('/');
84 match self {
85 Self::IpV4Addr(value) => Ok(format!(
86 "{base_url}/ip/{}",
87 PctString::encode(value.to_string().chars(), UriReserved::Path)
88 )),
89 Self::IpV6Addr(value) => Ok(format!(
90 "{base_url}/ip/{}",
91 PctString::encode(value.to_string().chars(), UriReserved::Path)
92 )),
93 Self::IpV4Cidr(value) => Ok(format!(
94 "{base_url}/ip/{}/{}",
95 PctString::encode(value.first_address().to_string().chars(), UriReserved::Path),
96 PctString::encode(
97 value.network_length().to_string().chars(),
98 UriReserved::Path
99 )
100 )),
101 Self::IpV6Cidr(value) => Ok(format!(
102 "{base_url}/ip/{}/{}",
103 PctString::encode(value.first_address().to_string().chars(), UriReserved::Path),
104 PctString::encode(
105 value.network_length().to_string().chars(),
106 UriReserved::Path
107 )
108 )),
109 Self::AsNumber(value) => Ok(format!(
110 "{base_url}/autnum/{}",
111 PctString::encode(value.to_string().chars(), UriReserved::Path)
112 )),
113 Self::Domain(value) => Ok(format!(
114 "{base_url}/domain/{}",
115 PctString::encode(value.trim_leading_dot().chars(), UriReserved::Path)
116 )),
117 Self::ReverseDNs(value) => Ok(format!(
118 "{base_url}/domain/{}",
119 PctString::encode(ip_to_reverse_dns(value).chars(), UriReserved::Path)
120 )),
121 Self::ALabel(value) => Ok(format!(
122 "{base_url}/domain/{}",
123 PctString::encode(value.to_ascii().chars(), UriReserved::Path),
124 )),
125 Self::Entity(value) => Ok(format!(
126 "{base_url}/entity/{}",
127 PctString::encode(value.chars(), UriReserved::Path)
128 )),
129 Self::Nameserver(value) => Ok(format!(
130 "{base_url}/nameserver/{}",
131 PctString::encode(value.to_ascii().chars(), UriReserved::Path)
132 )),
133 Self::EntityNameSearch(value) => search_query(value, "entities?fn", base_url),
134 Self::EntityHandleSearch(value) => search_query(value, "entities?handle", base_url),
135 Self::DomainNameSearch(value) => search_query(value, "domains?name", base_url),
136 Self::DomainNsNameSearch(value) => search_query(value, "domains?nsLdhName", base_url),
137 Self::DomainNsIpSearch(value) => {
138 search_query(&value.to_string(), "domains?nsIp", base_url)
139 }
140 Self::NameserverNameSearch(value) => search_query(value, "nameservers?name", base_url),
141 Self::NameserverIpSearch(value) => {
142 search_query(&value.to_string(), "nameservers?ip", base_url)
143 }
144 Self::Help => Ok(format!("{base_url}/help")),
145 Self::Url(url) => Ok(url.to_owned()),
146 }
147 }
148
149 pub fn domain(domain_name: &str) -> Result<Self, RdapClientError> {
150 Ok(Self::Domain(DomainName::from_str(domain_name)?))
151 }
152
153 pub fn alabel(alabel: &str) -> Result<Self, RdapClientError> {
154 Ok(Self::ALabel(DomainName::from_str(alabel)?))
155 }
156
157 pub fn rdns(domain_name: &str) -> Result<Self, RdapClientError> {
158 let value = reverse_dns_to_ip(domain_name).ok_or(RdapClientError::InvalidQueryValue)?;
159 Ok(Self::ReverseDNs(value))
160 }
161
162 pub fn rdns_ipstr(ip_address: &str) -> Result<Self, RdapClientError> {
163 let value =
164 IpAddr::from_str(ip_address).map_err(|_e| RdapClientError::InvalidQueryValue)?;
165 Ok(Self::ReverseDNs(value))
166 }
167
168 pub fn ns(nameserver: &str) -> Result<Self, RdapClientError> {
169 Ok(Self::Nameserver(DomainName::from_str(nameserver)?))
170 }
171
172 pub fn autnum(autnum: &str) -> Result<Self, RdapClientError> {
173 let value = autnum
174 .trim_start_matches(|c| -> bool { matches!(c, 'a' | 'A' | 's' | 'S') })
175 .parse::<u32>()
176 .map_err(|_e| RdapClientError::InvalidQueryValue)?;
177 Ok(Self::AsNumber(value))
178 }
179
180 pub fn ipv4(ip: &str) -> Result<Self, RdapClientError> {
181 let value = Ipv4Addr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?;
182 Ok(Self::IpV4Addr(value))
183 }
184
185 pub fn ipv6(ip: &str) -> Result<Self, RdapClientError> {
186 let value = Ipv6Addr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?;
187 Ok(Self::IpV6Addr(value))
188 }
189
190 pub fn ipv4cidr(cidr: &str) -> Result<Self, RdapClientError> {
191 let value = cidr::parsers::parse_cidr_ignore_hostbits::<IpCidr, _>(
192 cidr,
193 cidr::parsers::parse_loose_ip,
194 )
195 .map_err(|_e| RdapClientError::InvalidQueryValue)?;
196 if let IpCidr::V4(v4) = value {
197 Ok(Self::IpV4Cidr(v4))
198 } else {
199 Err(RdapClientError::AmbiguousQueryType)
200 }
201 }
202
203 pub fn ipv6cidr(cidr: &str) -> Result<Self, RdapClientError> {
204 let value = cidr::parsers::parse_cidr_ignore_hostbits::<IpCidr, _>(
205 cidr,
206 cidr::parsers::parse_loose_ip,
207 )
208 .map_err(|_e| RdapClientError::InvalidQueryValue)?;
209 if let IpCidr::V6(v6) = value {
210 Ok(Self::IpV6Cidr(v6))
211 } else {
212 Err(RdapClientError::AmbiguousQueryType)
213 }
214 }
215
216 pub fn domain_ns_ip_search(ip: &str) -> Result<Self, RdapClientError> {
217 let value = IpAddr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?;
218 Ok(Self::DomainNsIpSearch(value))
219 }
220
221 pub fn ns_ip_search(ip: &str) -> Result<Self, RdapClientError> {
222 let value = IpAddr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?;
223 Ok(Self::NameserverIpSearch(value))
224 }
225}
226
227fn search_query(value: &str, path_query: &str, base_url: &str) -> Result<String, RdapClientError> {
228 Ok(format!(
229 "{base_url}/{path_query}={}",
230 PctString::encode(value.chars(), UriReserved::Any)
231 ))
232}
233
234impl FromStr for QueryType {
235 type Err = RdapClientError;
236
237 fn from_str(s: &str) -> Result<Self, Self::Err> {
238 if s.starts_with("http://") || s.starts_with("https://") {
240 return Ok(Self::Url(s.to_owned()));
241 }
242
243 let autnum = s.trim_start_matches(|c| -> bool { matches!(c, 'a' | 'A' | 's' | 'S') });
245 if u32::from_str(autnum).is_ok() {
246 return Self::autnum(s);
247 }
248
249 if let Ok(ip_addr) = IpAddr::from_str(s) {
251 if ip_addr.is_ipv4() {
252 return Self::ipv4(s);
253 } else {
254 return Self::ipv6(s);
255 }
256 }
257
258 if let Ok(ip_cidr) = parse_cidr(s) {
260 return Ok(match ip_cidr {
261 IpCidr::V4(cidr) => Self::IpV4Cidr(cidr),
262 IpCidr::V6(cidr) => Self::IpV6Cidr(cidr),
263 });
264 }
265
266 if is_domain_name(s) {
268 return if is_nameserver(s) {
269 Self::ns(s)
270 } else if let Some(ip) = reverse_dns_to_ip(s) {
271 Ok(Self::ReverseDNs(ip))
272 } else {
273 Self::domain(s)
274 };
275 }
276
277 if !s.contains(|c: char| c.is_whitespace() || matches!(c, '.' | ',' | '"')) {
279 return Ok(Self::Entity(s.to_owned()));
280 }
281
282 Err(RdapClientError::AmbiguousQueryType)
284 }
285}
286
287fn parse_cidr(s: &str) -> Result<IpCidr, RdapClientError> {
288 let Some((prefix, suffix)) = s.split_once('/') else {
289 return Err(RdapClientError::InvalidQueryValue);
290 };
291 if prefix.chars().all(|c: char| c.is_ascii_alphanumeric()) {
292 let cidr = cidr::parsers::parse_short_ip_address_as_cidr(prefix)
293 .map_err(|_e| RdapClientError::InvalidQueryValue)?;
294 IpCidr::new(
295 cidr.first_address(),
296 suffix
297 .parse::<u8>()
298 .map_err(|_e| RdapClientError::InvalidQueryValue)?,
299 )
300 .map_err(|_e| RdapClientError::InvalidQueryValue)
301 } else {
302 cidr::parsers::parse_cidr_ignore_hostbits::<IpCidr, _>(s, cidr::parsers::parse_loose_ip)
303 .map_err(|_e| RdapClientError::InvalidQueryValue)
304 }
305}
306
307fn is_ldh_domain(text: &str) -> bool {
308 static LDH_DOMAIN_RE: LazyLock<Regex> =
309 LazyLock::new(|| Regex::new(r"^(?i)(\.?[a-zA-Z0-9-]+)*\.[a-zA-Z0-9-]+\.?$").unwrap());
310 LDH_DOMAIN_RE.is_match(text)
311}
312
313fn is_domain_name(text: &str) -> bool {
314 text.contains('.') && text.is_unicode_domain_name()
315}
316
317fn is_nameserver(text: &str) -> bool {
318 static NS_RE: LazyLock<Regex> = LazyLock::new(|| {
319 Regex::new(r"^(?i)(ns)[a-zA-Z0-9-]*\.[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.?$").unwrap()
320 });
321 NS_RE.is_match(text)
322}
323
324#[cfg(test)]
325mod tests {
326 use std::str::FromStr;
327
328 use rstest::rstest;
329
330 use super::*;
331
332 #[test]
333 fn test_ipv4_query_type_from_str() {
334 let s = "129.129.1.1";
336
337 let q = QueryType::from_str(s);
339
340 assert!(matches!(q.unwrap(), QueryType::IpV4Addr(_)))
342 }
343
344 #[test]
345 fn test_ipv6_query_type_from_str() {
346 let s = "2001::1";
348
349 let q = QueryType::from_str(s);
351
352 assert!(matches!(q.unwrap(), QueryType::IpV6Addr(_)))
354 }
355
356 #[test]
357 fn test_ipv4_cidr_query_type_from_str() {
358 let s = "129.129.1.1/8";
360
361 let q = QueryType::from_str(s);
363
364 assert!(matches!(q.unwrap(), QueryType::IpV4Cidr(_)))
366 }
367
368 #[test]
369 fn test_ipv6_cidr_query_type_from_str() {
370 let s = "2001::1/20";
372
373 let q = QueryType::from_str(s);
375
376 assert!(matches!(q.unwrap(), QueryType::IpV6Cidr(_)))
378 }
379
380 #[test]
381 fn test_number_query_type_from_str() {
382 let s = "16509";
384
385 let q = QueryType::from_str(s);
387
388 assert!(matches!(q.unwrap(), QueryType::AsNumber(_)))
390 }
391
392 #[test]
393 fn test_as_followed_by_number_query_type_from_str() {
394 let s = "as16509";
396
397 let q = QueryType::from_str(s);
399
400 assert!(matches!(q.unwrap(), QueryType::AsNumber(_)))
402 }
403
404 #[rstest]
405 #[case("example.com")]
406 #[case("foo.example.com")]
407 #[case("snark.fail")]
408 #[case("ns.fail")]
409 #[case(".com")]
410 fn test_domain_name_query_type_from_str(#[case] input: &str) {
411 let q = QueryType::from_str(input);
415
416 assert!(matches!(q.unwrap(), QueryType::Domain(_)))
418 }
419
420 #[rstest]
421 #[case("ns.example.com")]
422 #[case("ns1.example.com")]
423 #[case("NS1.example.com")]
424 fn test_name_server_query_type_from_str(#[case] input: &str) {
425 let q = QueryType::from_str(input);
429
430 assert!(matches!(q.unwrap(), QueryType::Nameserver(_)))
432 }
433
434 #[test]
435 fn test_single_word_query_type_from_str() {
436 let s = "foo";
438
439 let q = QueryType::from_str(s);
441
442 let q = q.unwrap();
444 assert!(matches!(q, QueryType::Entity(_)))
445 }
446
447 #[rstest]
448 #[case("https://example.com")]
449 #[case("http://foo.example.com")]
450 fn test_url_query_type_from_str(#[case] input: &str) {
451 let q = QueryType::from_str(input);
455
456 assert!(matches!(q.unwrap(), QueryType::Url(_)))
458 }
459
460 #[rstest]
461 #[case("ns.foo_bar.com")]
462 #[case("ns.foo bar.com")]
463 fn test_bad_input_query_type_from_str(#[case] input: &str) {
464 let q = QueryType::from_str(input);
468
469 assert!(q.is_err());
471 }
472
473 #[rstest]
474 #[case("10.0.0.0/8", "10.0.0.0/8")]
475 #[case("10.0.0/8", "10.0.0.0/8")]
476 #[case("10.0/8", "10.0.0.0/8")]
477 #[case("10/8", "10.0.0.0/8")]
478 #[case("10.0.0.0/24", "10.0.0.0/24")]
479 #[case("10.0.0/24", "10.0.0.0/24")]
480 #[case("10.0/24", "10.0.0.0/24")]
481 #[case("10/24", "10.0.0.0/24")]
482 #[case("129.129.1.1/8", "129.0.0.0/8")]
483 #[case("2001::1/32", "2001::/32")]
484 fn test_cidr_parse_cidr(#[case] actual: &str, #[case] expected: &str) {
485 let q = parse_cidr(actual);
489
490 assert_eq!(q.unwrap().to_string(), expected)
492 }
493
494 #[test]
495 fn test_ipv4addr_query_url() {
496 let q = QueryType::from_str("199.1.1.1").expect("query type");
498
499 let actual = q.query_url("https://example.com").expect("query url");
501
502 assert_eq!(actual, "https://example.com/ip/199.1.1.1")
504 }
505
506 #[test]
507 fn test_ipv6addr_query_url() {
508 let q = QueryType::from_str("2000::1").expect("query type");
510
511 let actual = q.query_url("https://example.com").expect("query url");
513
514 assert_eq!(actual, "https://example.com/ip/2000%3A%3A1")
516 }
517
518 #[test]
519 fn test_ipv4cidr_query_url() {
520 let q = QueryType::from_str("199.1.1.1/16").expect("query type");
522
523 let actual = q.query_url("https://example.com").expect("query url");
525
526 assert_eq!(actual, "https://example.com/ip/199.1.0.0/16")
528 }
529
530 #[test]
531 fn test_ipv6cidr_query_url() {
532 let q = QueryType::from_str("2000::1/16").expect("query type");
534
535 let actual = q.query_url("https://example.com").expect("query url");
537
538 assert_eq!(actual, "https://example.com/ip/2000%3A%3A/16")
540 }
541
542 #[test]
543 fn test_autnum_query_url() {
544 let q = QueryType::from_str("as16509").expect("query type");
546
547 let actual = q.query_url("https://example.com").expect("query url");
549
550 assert_eq!(actual, "https://example.com/autnum/16509")
552 }
553
554 #[test]
555 fn test_domain_query_url() {
556 let q = QueryType::from_str("example.com").expect("query type");
558
559 let actual = q.query_url("https://example.com").expect("query url");
561
562 assert_eq!(actual, "https://example.com/domain/example.com")
564 }
565
566 #[test]
567 fn test_ns_query_url() {
568 let q = QueryType::from_str("ns.example.com").expect("query type");
570
571 let actual = q.query_url("https://example.com").expect("query url");
573
574 assert_eq!(actual, "https://example.com/nameserver/ns.example.com")
576 }
577
578 #[test]
579 fn test_entity_query_url() {
580 let q = QueryType::from_str("foo").expect("query type");
582
583 let actual = q.query_url("https://example.com").expect("query url");
585
586 assert_eq!(actual, "https://example.com/entity/foo")
588 }
589
590 #[test]
591 fn test_entity_name_search_query_url() {
592 let q = QueryType::EntityNameSearch("foo".to_string());
594
595 let actual = q.query_url("https://example.com").expect("query url");
597
598 assert_eq!(actual, "https://example.com/entities?fn=foo")
600 }
601
602 #[test]
603 fn test_entity_handle_search_query_url() {
604 let q = QueryType::EntityHandleSearch("foo".to_string());
606
607 let actual = q.query_url("https://example.com").expect("query url");
609
610 assert_eq!(actual, "https://example.com/entities?handle=foo")
612 }
613
614 #[test]
615 fn test_domain_name_search_query_url() {
616 let q = QueryType::DomainNameSearch("foo".to_string());
618
619 let actual = q.query_url("https://example.com").expect("query url");
621
622 assert_eq!(actual, "https://example.com/domains?name=foo")
624 }
625
626 #[test]
627 fn test_domain_ns_name_search_query_url() {
628 let q = QueryType::DomainNsNameSearch("foo".to_string());
630
631 let actual = q.query_url("https://example.com").expect("query url");
633
634 assert_eq!(actual, "https://example.com/domains?nsLdhName=foo")
636 }
637
638 #[test]
639 fn test_domain_ns_ip_search_query_url() {
640 let q = QueryType::DomainNsIpSearch(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)));
642
643 let actual = q.query_url("https://example.com").expect("query url");
645
646 assert_eq!(actual, "https://example.com/domains?nsIp=1.1.1.1")
648 }
649
650 #[test]
651 fn test_ns_name_search_query_url() {
652 let q = QueryType::NameserverNameSearch("foo".to_string());
654
655 let actual = q.query_url("https://example.com").expect("query url");
657
658 assert_eq!(actual, "https://example.com/nameservers?name=foo")
660 }
661
662 #[test]
663 fn test_ns_ip_search_query_url() {
664 let q = QueryType::NameserverIpSearch(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)));
666
667 let actual = q.query_url("https://example.com").expect("query url");
669
670 assert_eq!(actual, "https://example.com/nameservers?ip=1.1.1.1")
672 }
673}