http_acl/utils/
authority.rs

1//! Utilities for parsing authorities.
2
3/// Checks if a host is valid or if it is a valid IP address.
4pub fn is_valid_host(host: &str) -> bool {
5    host.parse::<std::net::SocketAddr>().is_ok()
6        || host.parse::<std::net::IpAddr>().is_ok()
7        || url::Host::parse(host).is_ok()
8}
9
10/// Represents a parsed authority.
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub struct Authority {
13    /// The host, which can be a domain or an IP address.
14    pub host: Host,
15    /// The port.
16    pub port: u16,
17}
18
19impl std::fmt::Display for Authority {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        if self.port == 0 {
22            write!(f, "{}", self.host)
23        } else {
24            write!(f, "{}:{}", self.host, self.port)
25        }
26    }
27}
28
29/// Represents a parsed host.
30#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
31pub enum Host {
32    /// A domain.
33    Domain(String),
34    /// An IP address.
35    Ip(std::net::IpAddr),
36}
37
38impl std::fmt::Display for Host {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Host::Domain(domain) => write!(f, "{}", domain),
42            Host::Ip(ip) => match ip {
43                std::net::IpAddr::V4(ip) => write!(f, "{}", ip),
44                std::net::IpAddr::V6(ip) => write!(f, "[{}]", ip),
45            },
46        }
47    }
48}
49
50#[non_exhaustive]
51#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
52/// An error that can occur when parsing an authority.
53pub enum AuthorityError {
54    /// The host is invalid.
55    InvalidHost,
56}
57
58impl std::fmt::Display for AuthorityError {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            AuthorityError::InvalidHost => write!(f, "invalid host"),
62        }
63    }
64}
65
66impl Authority {
67    /// Parses an authority from a string.
68    pub fn parse(authority: &str) -> Result<Self, AuthorityError> {
69        if let Ok(addr) = authority.parse::<std::net::SocketAddr>() {
70            return Ok(Self {
71                host: Host::Ip(addr.ip()),
72                port: addr.port(),
73            });
74        }
75
76        if let Ok(ip) = authority.parse::<std::net::IpAddr>() {
77            return Ok(Self {
78                host: Host::Ip(ip),
79                port: 0,
80            });
81        }
82
83        match url::Host::parse(authority) {
84            Ok(url::Host::Domain(domain)) => Ok(Self {
85                host: Host::Domain(domain),
86                port: 0,
87            }),
88            Ok(url::Host::Ipv4(ip)) => Ok(Self {
89                host: Host::Ip(ip.into()),
90                port: 0,
91            }),
92            Ok(url::Host::Ipv6(ip)) => Ok(Self {
93                host: Host::Ip(ip.into()),
94                port: 0,
95            }),
96            Err(_) => {
97                if let Some((domain, port)) = authority.split_once(':') {
98                    if let Ok(port) = port.parse::<u16>() {
99                        url::Host::parse(domain).map_err(|_| AuthorityError::InvalidHost)?;
100
101                        return Ok(Self {
102                            host: Host::Domain(domain.to_string()),
103                            port,
104                        });
105                    }
106                }
107
108                Err(AuthorityError::InvalidHost)
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
118
119    #[test]
120    fn test_is_valid_host() {
121        assert!(is_valid_host("localhost"));
122        assert!(is_valid_host("example.com"));
123        assert!(is_valid_host("127.0.0.1"));
124        assert!(is_valid_host("::1"));
125        assert!(is_valid_host("[::1]"));
126    }
127
128    #[test]
129    fn test_authority_parse() {
130        assert_eq!(
131            Authority::parse("localhost").unwrap(),
132            Authority {
133                host: Host::Domain("localhost".to_string()),
134                port: 0
135            }
136        );
137        assert_eq!(
138            Authority::parse("localhost:5000").unwrap(),
139            Authority {
140                host: Host::Domain("localhost".to_string()),
141                port: 5000
142            }
143        );
144        assert_eq!(
145            Authority::parse("example.com").unwrap(),
146            Authority {
147                host: Host::Domain("example.com".to_string()),
148                port: 0
149            }
150        );
151        assert_eq!(
152            Authority::parse("example.com:443").unwrap(),
153            Authority {
154                host: Host::Domain("example.com".to_string()),
155                port: 443
156            }
157        );
158        assert_eq!(
159            Authority::parse("127.0.0.1").unwrap(),
160            Authority {
161                host: Host::Ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
162                port: 0
163            }
164        );
165        assert_eq!(
166            Authority::parse("127.0.0.1:80").unwrap(),
167            Authority {
168                host: Host::Ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
169                port: 80
170            }
171        );
172        assert_eq!(
173            Authority::parse("::1").unwrap(),
174            Authority {
175                host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
176                port: 0
177            }
178        );
179        assert_eq!(
180            Authority::parse("[::1]").unwrap(),
181            Authority {
182                host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
183                port: 0
184            }
185        );
186        assert_eq!(
187            Authority::parse("[::1]:80").unwrap(),
188            Authority {
189                host: Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
190                port: 80
191            }
192        );
193    }
194}