1mod scanner;
25
26static DOMAIN_REGEX: &str =
27 r"^(([a-zA-Z_\-]{1,63}\.)*?)*?([a-zA-Z_\-]{1,63})(\.[a-zA-Z_\-]{1,63})?$";
28
29#[derive(Debug, PartialEq, Clone)]
38pub enum ToCheck {
39 HostnameAndPort(String, u16),
41
42 #[cfg(feature = "http")]
43 #[allow(rustdoc::bare_urls)]
44 HttpOrHttpsUrl(hyper::Uri),
46}
47
48impl std::fmt::Display for ToCheck {
49 #[cfg(feature = "http")]
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 Self::HostnameAndPort(domain, port) => format!("{}:{}", domain, port).fmt(f),
53 Self::HttpOrHttpsUrl(uri) => uri.fmt(f),
54 }
55 }
56
57 #[cfg(not(feature = "http"))]
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::HostnameAndPort(domain, port) => format!("{}:{}", domain, port).fmt(f),
61 }
62 }
63}
64
65impl ToCheck {
66 fn from_host_and_port(domain_and_port: &str) -> Result<Self, String> {
67 let parts: Vec<String> = domain_and_port.split(':').map(String::from).collect();
68 if parts.len() != 2 {
69 return Err(format!(
70 "'{}' doesn't match <hostname>:<port> pattern",
71 domain_and_port
72 ));
73 }
74
75 let port: u16 = parts[1]
77 .parse()
78 .map_err(|err| format!("'{}', port error: {}", domain_and_port, err))?;
79
80 if port == 0 {
81 return Err("dynamic port number (0) can't be used here".into());
82 }
83
84 let hostname = parts[0].clone();
86 let regex = regex::Regex::new(DOMAIN_REGEX).unwrap();
87 let ip: Result<std::net::IpAddr, _> = hostname.parse();
88
89 if !regex.is_match(&hostname) && ip.is_err() {
90 return Err(format!("'{}' is not a valid hostname", hostname));
91 }
92 Ok(Self::HostnameAndPort(hostname, port))
93 }
94
95 #[cfg(feature = "http")]
96 fn from_http_url(http_url: &str) -> Result<Self, String> {
97 Ok(Self::HttpOrHttpsUrl(
98 http_url.parse::<hyper::Uri>().map_err(|e| e.to_string())?,
99 ))
100 }
101
102 #[cfg(not(feature = "http"))]
103 fn from_http_url(_uri: &str) -> Result<Self, String> {
104 panic!("Not compiled with 'http' feature")
105 }
106}
107
108impl std::str::FromStr for ToCheck {
109 type Err = String;
110
111 #[cfg(feature = "http")]
112 fn from_str(s: &str) -> Result<Self, Self::Err> {
113 if s.starts_with("http://") || s.starts_with("https://") {
114 Self::from_http_url(s)
115 } else {
116 Self::from_host_and_port(s)
117 }
118 }
119
120 #[cfg(not(feature = "http"))]
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 Self::from_host_and_port(s)
123 }
124}
125
126pub async fn wait_for_them(
140 hosts_ports_or_http_urls: &[ToCheck],
141 timeout: Option<u64>,
142 start_time: Option<std::time::Instant>,
143 silent: bool,
144) -> Vec<Option<u64>> {
145 let start_time = start_time.unwrap_or_else(std::time::Instant::now);
146 let futures = if silent {
147 scanner::wait_silent(hosts_ports_or_http_urls, timeout, start_time)
148 } else {
149 scanner::wait(hosts_ports_or_http_urls, timeout, start_time)
150 };
151
152 futures::future::join_all(futures).await
153}