ezwhois_rs/lib.rs
1#![crate_type = "lib"]
2//! Whois information parsing and querying crate. Provides a high level API.
3//!
4//! Enable the 'parser' flag if you want to use the parser.
5//! Everything related to the parser can be found at [parser]
6//!
7#![doc = include_str!("../README.md")]
8
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10use tokio::net::TcpStream;
11use std::future::Future;
12
13#[cfg(feature = "parser")]
14pub mod parser;
15
16#[derive(Clone)]
17/// Configuration for your WHOIS instance
18pub struct WhoisOpt {
19 pub whois_server: &'static str,
20 pub domain2lookup: &'static str
21}
22
23#[derive(Clone)]
24/// Whois instance, used for querying a domain to a specific WHOIS server for WHOIS data
25/// ```
26/// use ezwhois_rs::{
27/// parser::Parser,
28/// Whois,
29/// WhoisOpt,
30/// WhoisResolver
31/// };
32///
33/// #[tokio::main]
34/// async fn main() {
35/// let client = Whois::new(WhoisOpt{
36/// whois_server: "whois.iana.org:43",
37/// domain2lookup: "simpaix.net"
38/// });
39/// let res = client.query().await.expect("expected a response");
40///
41/// let parser = Parser::new();
42/// let info = parser.parse(res).unwrap();
43/// println!("creation date: {}\nexpire: {}",
44/// info.creation_date.unwrap().format("%d/%m/%Y %H:%M"),
45/// info.registry_expirity_date.unwrap().format("%d/%m/%Y %H:%M")
46/// ); // info.registry_domain_id , etc etc
47/// }
48/// ```
49pub struct Whois{
50 target: WhoisOpt
51}
52
53pub trait WhoisResolver: Sized {
54 type Error;
55
56 /// Creates a new whois instance and configures the target
57 fn new(opt: WhoisOpt) -> Whois;
58
59 /// Queries the WHOIS server and retrieves domain information.
60 /// Returns WHOIS information as a string.
61 ///
62 /// So that you can use any arbitrary parser.
63 fn query(&self) -> impl Future<Output = Result<String, Self::Error>>;
64}
65
66impl WhoisResolver for Whois {
67 type Error = Box<dyn std::error::Error>;
68
69 fn new(opt: WhoisOpt) -> Whois {
70 Whois{target: opt}
71 }
72
73 async fn query(&self) -> Result<String, Self::Error> {
74 let q1 = Whois::lookup(self.target.whois_server, self.target.domain2lookup).await?;
75 let main_server =
76 if let Some((_, b)) = q1.split_once("whois:") {
77 b.trim().split_once("\n").ok_or_else(|| errors::WhoisError::MissingNewline)?.0
78 } else { return Err(Box::new(errors::WhoisError::GeneralErr { ctx: "could not find whois server to lookup" }));};
79
80 let port: &str = self.target.whois_server.split_once(":").ok_or_else(|| {
81 Box::new(errors::WhoisError::GeneralErr{ ctx: "whois server should be in host:port format" })
82 })?.1;
83 Ok(Whois::lookup(&format!("{main_server}:{port}"), self.target.domain2lookup).await?)
84 }
85}
86
87impl Whois {
88 /// private!
89 /// Sends a query request to the WHOIS server and returns a String that holds WHOIS information
90 async fn lookup(whois_server: &str, domain2_lookup: &str) -> Result<String, Box<dyn std::error::Error>> {
91 let mut conn = TcpStream::connect(whois_server).await?;
92 conn.write(format!("{domain2_lookup}\r\n").as_bytes()).await?;
93
94 let mut data: Vec<u8> = vec![];
95 conn.read_to_end(&mut data).await?;
96
97 if data.len() == 0 {
98 return Err(Box::new(errors::WhoisError::WhoisServerIO { ctx: "Wrote to WHOIS server, but got no response" }));
99 }
100 Ok(String::from_utf8(data)?)
101 }
102}
103
104// Errors that may occur for parent module
105pub mod errors {
106 use thiserror::Error;
107
108 #[derive(Error, Debug)]
109 pub enum WhoisError {
110 #[error("Error caused by I/O on the WHOIS server: {ctx}")]
111 WhoisServerIO{ctx: &'static str},
112
113 #[error("error: {ctx}")]
114 GeneralErr{ctx: &'static str},
115
116 #[error("couldn't find newline seperator")]
117 MissingNewline
118 }
119}
120
121#[tokio::test]
122async fn test_client() {
123 let client = Whois::new(WhoisOpt{
124 whois_server: "whois.iana.org:43",
125 domain2lookup: "simpaix.net"
126 });
127 let res = client.query().await.expect("expected a response");
128
129 let parser = parser::Parser::new();
130 let info = parser.parse(res).unwrap();
131 println!("creation date: {}\nexpire: {}\n\nor the complete whois info:\n\n-------------{:#?}------------",
132 info.creation_date.unwrap().format("%d/%m/%Y %H:%M"),
133 info.registry_expirity_date.unwrap().format("%d/%m/%Y %H:%M"),
134 info
135 );
136}
137/*
138successes:
139
140---- test_client stdout ----
141creation date: 20/06/2023 12:13
142expire: 20/06/2025 12:13
143
144or the complete whois info:
145
146-------------WhoisInformation {
147 domain_name: Some(
148 "SIMPAIX.NET",
149 ),
150 registry_domain_id: Some(
151 "2791830160_DOMAIN_NET-VRSN",
152 ),
153 registrar_whois_server: Some(
154 "whois.namecheap.com",
155 ),
156 registrar_url: Some(
157 "http://www.namecheap.com",
158 ),
159 updated_date: Some(
160 2024-05-21T08:07:15Z,
161 ),
162 creation_date: Some(
163 2023-06-20T12:13:22Z,
164 ),
165 registry_expirity_date: Some(
166 2025-06-20T12:13:22Z,
167 ),
168 registrar: Some(
169 "NameCheap, Inc.",
170 ),
171 registrar_iana_id: Some(
172 "1068",
173 ),
174 registrar_abuse_email_contact: Some(
175 "abuse@namecheap.com",
176 ),
177 registrar_abuse_phone_contact: Some(
178 "+1.6613102107",
179 ),
180 domain_status: Some(
181 "clientTransferProhibited https://icann.org/epp#clientTransferProhibited",
182 ),
183 name_servers: Some(
184 [
185 "IRENA.NS.CLOUDFLARE.COM",
186 ],
187 ),
188 dnssec: Some(
189 "unsigned",
190 ),
191}------------
192*/