Skip to main content

http_acl/utils/
authority.rs

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