http_acl/utils/
authority.rs

1//! Utilities for parsing authorities.
2
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
4
5/// Checks if a host is valid or if it is a valid IP address.
6pub 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/// Represents a parsed authority.
13#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct Authority {
15    /// The host, which can be a domain or an IP address.
16    pub host: Host,
17    /// The port.
18    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/// Represents a parsed host.
143#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
144pub enum Host {
145    /// A domain.
146    Domain(String),
147    /// An IP address.
148    Ip(IpAddr),
149}
150
151impl Host {
152    /// Returns true if the host is an IP address.
153    pub fn is_ip(&self) -> bool {
154        matches!(self, Host::Ip(_))
155    }
156
157    /// Returns true if the host is a domain.
158    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)]
207/// An error that can occur when parsing an authority.
208pub enum AuthorityError {
209    /// The host is invalid.
210    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    /// Parses an authority from a string.
223    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}