1use std::{
2 collections::HashMap,
3 fs::File,
4 io::{self, Read, Write},
5 net::{SocketAddr, TcpStream, ToSocketAddrs},
6 path::Path,
7 str::FromStr,
8 sync::LazyLock,
9 time::Duration,
10};
11
12use hickory_client::{
13 client::{Client, SyncClient},
14 op::DnsResponse,
15 rr::{DNSClass, Name, RData, Record, RecordType},
16 udp::UdpClientConnection,
17};
18use regex::Regex;
19use serde_json::{Map, Value};
20#[cfg(feature = "tokio")]
21use tokio::io::{AsyncReadExt, AsyncWriteExt};
22use validators::models::Host;
23
24use crate::{WhoIsError, WhoIsLookupOptions, WhoIsServerValue};
25
26const DEFAULT_WHOIS_HOST_PORT: u16 = 43;
27const DEFAULT_WHOIS_HOST_QUERY: &str = "$addr\r\n";
28
29static RE_SERVER: LazyLock<Regex> = LazyLock::new(|| {
30 Regex::new(r"(ReferralServer|Registrar Whois|Whois Server|WHOIS Server|Registrar WHOIS Server):[^\S\n]*(r?whois://)?(.*)").unwrap()
31});
32
33fn extract_referral_host(query_result: &str) -> Option<&str> {
35 let host = RE_SERVER.captures(query_result)?.get(3)?.as_str().trim();
36
37 if host.is_empty() { None } else { Some(host) }
38}
39
40#[cfg(not(feature = "charset"))]
42fn decode_response(bytes: &[u8]) -> String {
43 match std::str::from_utf8(bytes) {
44 Ok(s) => s.to_string(),
45 Err(_) => String::from_utf8_lossy(bytes).into_owned(),
46 }
47}
48
49#[cfg(feature = "charset")]
51fn decode_response(bytes: &[u8]) -> String {
52 if let Ok(s) = std::str::from_utf8(bytes) {
53 return s.to_string();
54 }
55
56 let mut detector = chardetng::EncodingDetector::new();
57
58 detector.feed(bytes, true);
59
60 detector.guess(None, true).decode(bytes).0.into_owned()
61}
62
63fn format_ip_query(host: &Host) -> String {
65 match host {
66 Host::IPv4(ip) => ip.to_string(),
67 Host::IPv6(ip) => ip.to_string(),
68 Host::Domain(domain) => domain.to_string(),
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct WhoIs {
75 map: HashMap<String, WhoIsServerValue>,
76 ip: WhoIsServerValue,
77}
78
79impl WhoIs {
80 pub fn from_host<T: AsRef<str>>(host: T) -> Result<WhoIs, WhoIsError> {
82 Ok(Self {
83 map: HashMap::new(), ip: WhoIsServerValue::from_string(host)?
84 })
85 }
86
87 #[inline]
89 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<WhoIs, WhoIsError> {
90 let path = path.as_ref();
91
92 let file = File::open(path)?;
93
94 let map: Map<String, Value> = serde_json::from_reader(file)?;
95
96 Self::from_inner(map)
97 }
98
99 #[cfg(feature = "tokio")]
100 #[inline]
102 pub async fn from_path_async<P: AsRef<Path>>(path: P) -> Result<WhoIs, WhoIsError> {
103 let file = tokio::fs::read(path).await?;
104
105 let map: Map<String, Value> = serde_json::from_slice(file.as_slice())?;
106
107 Self::from_inner(map)
108 }
109
110 #[inline]
112 pub fn from_string<S: AsRef<str>>(string: S) -> Result<WhoIs, WhoIsError> {
113 let string = string.as_ref();
114
115 let map: Map<String, Value> = serde_json::from_str(string)?;
116
117 Self::from_inner(map)
118 }
119
120 fn from_inner(mut map: Map<String, Value>) -> Result<WhoIs, WhoIsError> {
121 let ip = match map.remove("_") {
122 Some(server) => {
123 if let Value::Object(server) = server {
124 match server.get("ip") {
125 Some(server) => {
126 if server.is_null() {
127 return Err(WhoIsError::MapError(
128 "`ip` in the `_` object in the server list is null.",
129 ));
130 }
131
132 WhoIsServerValue::from_value(server)?
133 },
134 None => {
135 return Err(WhoIsError::MapError(
136 "Cannot find `ip` in the `_` object in the server list.",
137 ));
138 },
139 }
140 } else {
141 return Err(WhoIsError::MapError("`_` in the server list is not an object."));
142 }
143 },
144 None => return Err(WhoIsError::MapError("Cannot find `_` in the server list.")),
145 };
146
147 let mut new_map: HashMap<String, WhoIsServerValue> = HashMap::with_capacity(map.len());
148
149 for (k, v) in map {
150 if !v.is_null() {
151 let server_value = WhoIsServerValue::from_value(&v)?;
152 new_map.insert(k, server_value);
153 }
154 }
155
156 Ok(WhoIs {
157 map: new_map,
158 ip,
159 })
160 }
161}
162
163impl WhoIs {
164 pub fn can_find_server_for_tld<T: AsRef<str>, D: AsRef<str>>(
166 &mut self,
167 tld: T,
168 dns_server: D,
169 ) -> Result<bool, WhoIsError> {
170 let mut tld = tld.as_ref();
171 let dns_server = dns_server.as_ref();
172
173 let address = match dns_server.parse() {
174 Ok(address) => address,
175 Err(_error) => {
176 return Err(WhoIsError::MapError("The DNS server address is incorrect."));
177 },
178 };
179 let conn = UdpClientConnection::new(address).map_err(io::Error::other)?;
180 let client = SyncClient::new(conn);
181
182 loop {
183 if self.map.contains_key(tld) {
184 return Ok(true);
185 }
186
187 match tld.find('.') {
188 Some(index) => {
189 tld = &tld[index + 1..];
190 },
191 None => {
192 tld = "";
193 },
194 }
195
196 if tld.is_empty() {
197 return Ok(false);
198 }
199
200 let name =
201 Name::from_str(&format!("_nicname._tcp.{tld}.")).map_err(io::Error::other)?;
202 let response: DnsResponse =
203 client.query(&name, DNSClass::IN, RecordType::SRV).map_err(io::Error::other)?;
204 let answers: &[Record] = response.answers();
205
206 for record in answers {
207 if let Some(RData::SRV(record)) = record.data() {
208 let target = record.target().to_string();
209 let new_server =
210 match WhoIsServerValue::from_string(target.trim_end_matches('.')) {
211 Ok(new_server) => new_server,
212 Err(_error) => continue,
213 };
214
215 self.map.insert(tld.to_string(), new_server);
216
217 return Ok(true);
218 }
219 }
220 }
221 }
222
223 pub fn get_server_by_tld(&self, mut tld: &str) -> Option<&WhoIsServerValue> {
225 let mut server;
226
227 loop {
228 server = self.map.get(tld);
229
230 if server.is_some() {
231 break;
232 }
233
234 if tld.is_empty() {
235 break;
236 }
237
238 match tld.find('.') {
239 Some(index) => {
240 tld = &tld[index + 1..];
241 },
242 None => {
243 tld = "";
244 },
245 }
246 }
247
248 server
249 }
250
251 fn lookup_once(
252 server: &WhoIsServerValue,
253 text: &str,
254 timeout: Option<Duration>,
255 ) -> Result<(String, Vec<u8>), WhoIsError> {
256 let addr = server.host.to_addr_string(DEFAULT_WHOIS_HOST_PORT);
257
258 let mut client = if let Some(timeout) = timeout {
259 let socket_addrs: Vec<SocketAddr> = addr.to_socket_addrs()?.collect();
260
261 if socket_addrs.is_empty() {
262 return Err(io::Error::new(
263 io::ErrorKind::AddrNotAvailable,
264 "the host is not resolved to any socket address",
265 )
266 .into());
267 }
268
269 let mut client = None;
270
271 for socket_addr in socket_addrs.iter().take(socket_addrs.len() - 1) {
272 if let Ok(c) = TcpStream::connect_timeout(socket_addr, timeout) {
273 client = Some(c);
274 break;
275 }
276 }
277
278 let client = if let Some(client) = client {
279 client
280 } else {
281 let socket_addr = &socket_addrs[socket_addrs.len() - 1];
282 TcpStream::connect_timeout(socket_addr, timeout)?
283 };
284
285 client.set_read_timeout(Some(timeout))?;
286 client.set_write_timeout(Some(timeout))?;
287 client
288 } else {
289 TcpStream::connect(&addr)?
290 };
291
292 if let Some(query) = &server.query {
293 client.write_all(query.replace("$addr", text).as_bytes())?;
294 } else {
295 client.write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes())?;
296 }
297
298 client.flush()?;
299
300 let mut query_result = Vec::new();
301
302 client.read_to_end(&mut query_result)?;
303
304 Ok((server.host.host.to_string(), query_result))
306 }
307
308 fn lookup_inner(
309 server: &WhoIsServerValue,
310 text: &str,
311 timeout: Option<Duration>,
312 mut follow: u16,
313 ) -> Result<Vec<u8>, WhoIsError> {
314 let mut query_result = Self::lookup_once(server, text, timeout)?;
315
316 while follow > 0 {
317 let body = decode_response(&query_result.1);
318
319 if let Some(h) = extract_referral_host(&body)
320 && !h.eq_ignore_ascii_case(&query_result.0)
321 && let Ok(server) = WhoIsServerValue::from_string(h)
322 {
323 query_result = Self::lookup_once(&server, text, timeout)?;
324
325 follow -= 1;
326
327 continue;
328 }
329
330 break;
331 }
332
333 Ok(query_result.1)
334 }
335
336 pub fn lookup_raw(&self, options: WhoIsLookupOptions) -> Result<Vec<u8>, WhoIsError> {
338 match &options.target.0 {
339 Host::IPv4(_) | Host::IPv6(_) => {
340 let server = match &options.server {
341 Some(server) => server,
342 None => &self.ip,
343 };
344
345 let text = format_ip_query(&options.target.0);
346
347 Self::lookup_inner(server, &text, options.timeout, options.follow)
348 },
349 Host::Domain(domain) => {
350 let server = match &options.server {
351 Some(server) => server,
352 None => match self.get_server_by_tld(domain.as_str()) {
353 Some(server) => server,
354 None => {
355 return Err(WhoIsError::MapError(
356 "No whois server is known for this kind of object.",
357 ));
358 },
359 },
360 };
361
362 let text = server.encode_domain(domain);
364
365 Self::lookup_inner(server, text.as_ref(), options.timeout, options.follow)
366 },
367 }
368 }
369
370 #[inline]
372 pub fn lookup(&self, options: WhoIsLookupOptions) -> Result<String, WhoIsError> {
373 Ok(decode_response(&self.lookup_raw(options)?))
374 }
375}
376
377#[cfg(feature = "tokio")]
378impl WhoIs {
379 async fn lookup_once_async(
380 server: &WhoIsServerValue,
381 text: &str,
382 timeout: Option<Duration>,
383 ) -> Result<(String, Vec<u8>), WhoIsError> {
384 let addr = server.host.to_addr_string(DEFAULT_WHOIS_HOST_PORT);
385
386 if let Some(timeout) = timeout {
387 let socket_addrs: Vec<SocketAddr> = addr.to_socket_addrs()?.collect();
388
389 if socket_addrs.is_empty() {
390 return Err(io::Error::new(
391 io::ErrorKind::AddrNotAvailable,
392 "the host is not resolved to any socket address",
393 )
394 .into());
395 }
396
397 let mut client = None;
398
399 for socket_addr in socket_addrs.iter().take(socket_addrs.len() - 1) {
400 if let Ok(c) =
401 tokio::time::timeout(timeout, tokio::net::TcpStream::connect(&socket_addr))
402 .await?
403 {
404 client = Some(c);
405 break;
406 }
407 }
408
409 let mut client = if let Some(client) = client {
410 client
411 } else {
412 let socket_addr = &socket_addrs[socket_addrs.len() - 1];
413 tokio::time::timeout(timeout, tokio::net::TcpStream::connect(socket_addr)).await??
414 };
415
416 if let Some(query) = &server.query {
417 tokio::time::timeout(
418 timeout,
419 client.write_all(query.replace("$addr", text).as_bytes()),
420 )
421 .await??;
422 } else {
423 tokio::time::timeout(
424 timeout,
425 client.write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes()),
426 )
427 .await??;
428 }
429
430 tokio::time::timeout(timeout, client.flush()).await??;
431
432 let mut query_result = Vec::new();
433
434 tokio::time::timeout(timeout, client.read_to_end(&mut query_result)).await??;
435
436 Ok((server.host.host.to_string(), query_result))
438 } else {
439 let mut client = tokio::net::TcpStream::connect(&addr).await?;
440
441 if let Some(query) = &server.query {
442 client.write_all(query.replace("$addr", text).as_bytes()).await?;
443 } else {
444 client
445 .write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes())
446 .await?;
447 }
448
449 client.flush().await?;
450
451 let mut query_result = Vec::new();
452
453 client.read_to_end(&mut query_result).await?;
454
455 Ok((server.host.host.to_string(), query_result))
457 }
458 }
459
460 async fn lookup_inner_async<'a>(
461 server: &'a WhoIsServerValue,
462 text: &'a str,
463 timeout: Option<Duration>,
464 mut follow: u16,
465 ) -> Result<Vec<u8>, WhoIsError> {
466 let mut query_result = Self::lookup_once_async(server, text, timeout).await?;
467
468 while follow > 0 {
469 let body = decode_response(&query_result.1);
470
471 if let Some(h) = extract_referral_host(&body)
472 && !h.eq_ignore_ascii_case(&query_result.0)
473 && let Ok(server) = WhoIsServerValue::from_string(h)
474 {
475 query_result = Self::lookup_once_async(&server, text, timeout).await?;
476
477 follow -= 1;
478
479 continue;
480 }
481
482 break;
483 }
484
485 Ok(query_result.1)
486 }
487
488 pub async fn lookup_raw_async(
490 &self,
491 options: WhoIsLookupOptions,
492 ) -> Result<Vec<u8>, WhoIsError> {
493 match &options.target.0 {
494 Host::IPv4(_) | Host::IPv6(_) => {
495 let server = match &options.server {
496 Some(server) => server,
497 None => &self.ip,
498 };
499
500 let text = format_ip_query(&options.target.0);
501
502 Self::lookup_inner_async(server, &text, options.timeout, options.follow).await
503 },
504 Host::Domain(domain) => {
505 let server = match &options.server {
506 Some(server) => server,
507 None => match self.get_server_by_tld(domain.as_str()) {
508 Some(server) => server,
509 None => {
510 return Err(WhoIsError::MapError(
511 "No whois server is known for this kind of object.",
512 ));
513 },
514 },
515 };
516
517 let text = server.encode_domain(domain);
519
520 Self::lookup_inner_async(server, text.as_ref(), options.timeout, options.follow)
521 .await
522 },
523 }
524 }
525
526 #[inline]
528 pub async fn lookup_async(&self, options: WhoIsLookupOptions) -> Result<String, WhoIsError> {
529 Ok(decode_response(&self.lookup_raw_async(options).await?))
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::{decode_response, extract_referral_host};
536
537 #[test]
538 fn extract_referral_host_trims_trailing_cr() {
539 let body = "Domain: example.com\r\nReferralServer: whois://whois.arin.net\r\n";
540
541 assert_eq!(Some("whois.arin.net"), extract_referral_host(body));
542 }
543
544 #[test]
545 fn decode_response_passes_through_utf8() {
546 assert_eq!("café", decode_response("café".as_bytes()));
547 }
548
549 #[cfg(feature = "charset")]
551 #[test]
552 fn decode_response_detects_windows_1252() {
553 assert_eq!("café", decode_response(&[b'c', b'a', b'f', 0xE9]));
554 }
555
556 #[cfg(not(feature = "charset"))]
557 #[test]
558 fn decode_response_is_lossy_without_charset() {
559 assert_eq!("caf\u{FFFD}", decode_response(&[b'c', b'a', b'f', 0xE9]));
560 }
561}