1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
4
5pub fn is_valid_host(host: &str) -> bool {
7 host.parse::<std::net::SocketAddr>().is_ok()
8 || host.parse::<IpAddr>().is_ok()
9 || url::Host::parse(host).is_ok()
10}
11
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct Authority {
15 pub host: Host,
17 pub port: u16,
19}
20
21impl std::fmt::Display for Authority {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 if self.port == 0 {
24 write!(f, "{}", self.host)
25 } else {
26 write!(f, "{}:{}", self.host, self.port)
27 }
28 }
29}
30
31impl From<SocketAddr> for Authority {
32 fn from(value: SocketAddr) -> Self {
33 Authority {
34 host: match value {
35 SocketAddr::V4(addr) => Host::Ip(IpAddr::V4(*addr.ip())),
36 SocketAddr::V6(addr) => Host::Ip(IpAddr::V6(*addr.ip())),
37 },
38 port: value.port(),
39 }
40 }
41}
42
43impl From<SocketAddrV4> for Authority {
44 fn from(value: SocketAddrV4) -> Self {
45 Authority {
46 host: Host::Ip(IpAddr::V4(*value.ip())),
47 port: value.port(),
48 }
49 }
50}
51
52impl From<SocketAddrV6> for Authority {
53 fn from(value: SocketAddrV6) -> Self {
54 Authority {
55 host: Host::Ip(IpAddr::V6(*value.ip())),
56 port: value.port(),
57 }
58 }
59}
60
61impl From<(String, u16)> for Authority {
62 fn from(value: (String, u16)) -> Self {
63 Authority {
64 host: Host::Domain(value.0),
65 port: value.1,
66 }
67 }
68}
69
70impl From<(&str, u16)> for Authority {
71 fn from(value: (&str, u16)) -> Self {
72 Authority {
73 host: Host::Domain(value.0.to_string()),
74 port: value.1,
75 }
76 }
77}
78
79impl From<(IpAddr, u16)> for Authority {
80 fn from(value: (IpAddr, u16)) -> Self {
81 Authority {
82 host: Host::Ip(value.0),
83 port: value.1,
84 }
85 }
86}
87
88impl From<(Ipv4Addr, u16)> for Authority {
89 fn from(value: (Ipv4Addr, u16)) -> Self {
90 Authority {
91 host: Host::Ip(IpAddr::V4(value.0)),
92 port: value.1,
93 }
94 }
95}
96
97impl From<String> for Authority {
98 fn from(value: String) -> Self {
99 Authority {
100 host: Host::Domain(value),
101 port: 0,
102 }
103 }
104}
105
106impl From<&str> for Authority {
107 fn from(value: &str) -> Self {
108 Authority {
109 host: Host::Domain(value.to_string()),
110 port: 0,
111 }
112 }
113}
114
115impl From<IpAddr> for Authority {
116 fn from(value: IpAddr) -> Self {
117 Authority {
118 host: Host::Ip(value),
119 port: 0,
120 }
121 }
122}
123
124impl From<Ipv4Addr> for Authority {
125 fn from(value: Ipv4Addr) -> Self {
126 Authority {
127 host: Host::Ip(IpAddr::V4(value)),
128 port: 0,
129 }
130 }
131}
132
133impl From<Ipv6Addr> for Authority {
134 fn from(value: Ipv6Addr) -> Self {
135 Authority {
136 host: Host::Ip(IpAddr::V6(value)),
137 port: 0,
138 }
139 }
140}
141
142#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
144pub enum Host {
145 Domain(String),
147 Ip(IpAddr),
149}
150
151impl Host {
152 pub fn is_ip(&self) -> bool {
154 matches!(self, Host::Ip(_))
155 }
156
157 pub fn is_domain(&self) -> bool {
159 matches!(self, Host::Domain(_))
160 }
161}
162
163impl std::fmt::Display for Host {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 match self {
166 Host::Domain(domain) => write!(f, "{domain}"),
167 Host::Ip(ip) => match ip {
168 IpAddr::V4(ip) => write!(f, "{ip}"),
169 IpAddr::V6(ip) => write!(f, "[{ip}]"),
170 },
171 }
172 }
173}
174
175impl From<String> for Host {
176 fn from(value: String) -> Self {
177 Host::Domain(value)
178 }
179}
180
181impl From<&str> for Host {
182 fn from(value: &str) -> Self {
183 Host::Domain(value.to_string())
184 }
185}
186
187impl From<IpAddr> for Host {
188 fn from(value: IpAddr) -> Self {
189 Host::Ip(value)
190 }
191}
192
193impl From<Ipv4Addr> for Host {
194 fn from(value: Ipv4Addr) -> Self {
195 Host::Ip(IpAddr::V4(value))
196 }
197}
198
199impl From<Ipv6Addr> for Host {
200 fn from(value: Ipv6Addr) -> Self {
201 Host::Ip(IpAddr::V6(value))
202 }
203}
204
205#[non_exhaustive]
206#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
207pub enum AuthorityError {
209 InvalidHost,
211}
212
213impl std::fmt::Display for AuthorityError {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 AuthorityError::InvalidHost => write!(f, "invalid host"),
217 }
218 }
219}
220
221impl Authority {
222 pub fn parse(authority: &str) -> Result<Self, AuthorityError> {
224 if let Ok(addr) = authority.parse::<std::net::SocketAddr>() {
225 return Ok(Self {
226 host: Host::Ip(addr.ip()),
227 port: addr.port(),
228 });
229 }
230
231 if let Ok(ip) = authority.parse::<IpAddr>() {
232 return Ok(Self {
233 host: Host::Ip(ip),
234 port: 0,
235 });
236 }
237
238 match url::Host::parse(authority) {
239 Ok(url::Host::Domain(domain)) => Ok(Self {
240 host: Host::Domain(domain),
241 port: 0,
242 }),
243 Ok(url::Host::Ipv4(ip)) => Ok(Self {
244 host: Host::Ip(ip.into()),
245 port: 0,
246 }),
247 Ok(url::Host::Ipv6(ip)) => Ok(Self {
248 host: Host::Ip(ip.into()),
249 port: 0,
250 }),
251 Err(_) => {
252 if let Some((domain, port)) = authority.split_once(':')
253 && let Ok(port) = port.parse::<u16>()
254 {
255 url::Host::parse(domain).map_err(|_| AuthorityError::InvalidHost)?;
256
257 return Ok(Self {
258 host: Host::Domain(domain.to_string()),
259 port,
260 });
261 }
262
263 Err(AuthorityError::InvalidHost)
264 }
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
273
274 #[test]
275 fn test_is_valid_host() {
276 assert!(is_valid_host("localhost"));
277 assert!(is_valid_host("example.com"));
278 assert!(is_valid_host("127.0.0.1"));
279 assert!(is_valid_host("::1"));
280 assert!(is_valid_host("[::1]"));
281 }
282
283 #[test]
284 fn test_authority_parse() {
285 assert_eq!(
286 Authority::parse("localhost").unwrap(),
287 Authority {
288 host: Host::Domain("localhost".to_string()),
289 port: 0
290 }
291 );
292 assert_eq!(
293 Authority::parse("localhost:5000").unwrap(),
294 Authority {
295 host: Host::Domain("localhost".to_string()),
296 port: 5000
297 }
298 );
299 assert_eq!(
300 Authority::parse("example.com").unwrap(),
301 Authority {
302 host: Host::Domain("example.com".to_string()),
303 port: 0
304 }
305 );
306 assert_eq!(
307 Authority::parse("example.com:443").unwrap(),
308 Authority {
309 host: Host::Domain("example.com".to_string()),
310 port: 443
311 }
312 );
313 assert_eq!(
314 Authority::parse("127.0.0.1").unwrap(),
315 Authority {
316 host: Host::Ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
317 port: 0
318 }
319 );
320 assert_eq!(
321 Authority::parse("127.0.0.1:80").unwrap(),
322 Authority {
323 host: Host::Ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
324 port: 80
325 }
326 );
327 assert_eq!(
328 Authority::parse("::1").unwrap(),
329 Authority {
330 host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
331 port: 0
332 }
333 );
334 assert_eq!(
335 Authority::parse("[::1]").unwrap(),
336 Authority {
337 host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
338 port: 0
339 }
340 );
341 assert_eq!(
342 Authority::parse("[::1]:80").unwrap(),
343 Authority {
344 host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
345 port: 80
346 }
347 );
348 }
349}