1use super::Res;
6use anyhow::anyhow;
7use nom::{
8 branch::alt,
9 bytes::complete::tag,
10 character::complete::{alphanumeric1, digit1, hex_digit1, one_of, u16},
11 combinator::{map, map_res, opt, recognize},
12 multi::{count, many0, many1, separated_list0},
13 sequence::{delimited, pair, preceded, terminated, tuple},
14 Finish,
15};
16use std::{
17 fmt::Display,
18 net::{IpAddr, Ipv4Addr, Ipv6Addr},
19 path::PathBuf,
20 str::FromStr,
21};
22
23#[derive(Clone, Debug, Default, PartialEq, Eq)]
24pub(crate) struct HostSpec {
25 pub host: Option<Host>,
26 pub port: Option<u16>,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum Host {
31 Path(PathBuf),
32 Name(String),
33 Ip(IpAddr),
34}
35
36impl Display for Host {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Host::Path(path) => {
40 write!(f, "{}", path.to_str().unwrap_or("<invalid>"))
41 },
42 Host::Name(name) => write!(f, "{name}"),
43 Host::Ip(IpAddr::V4(ip)) => write!(f, "{ip}"),
44 Host::Ip(IpAddr::V6(ip)) => write!(f, "[{ip}]"),
45 }
46 }
47}
48
49impl FromStr for Host {
50 type Err = anyhow::Error;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 host(s)
54 .finish()
55 .map(|(_, host)| host)
56 .map_err(|e| anyhow!(e.to_string()))
57 }
58}
59
60pub(crate) fn hostspecs(i: &str) -> Res<&str, Vec<HostSpec>> {
62 many0(hostspec)(i)
64}
65
66pub(crate) fn hostspec(i: &str) -> Res<&str, HostSpec> {
68 terminated(
70 alt((
71 map(pair(host, port), |(host, port)| HostSpec {
72 host: Some(host),
73 port: Some(port),
74 }),
75 map(pair(host, opt(port)), |(host, port)| HostSpec {
76 host: Some(host),
77 port,
78 }),
79 map(pair(opt(host), port), |(host, port)| HostSpec {
80 host: host.map(std::convert::Into::into),
81 port: Some(port),
82 }),
83 )),
84 opt(tag(",")),
85 )(i)
86}
87
88fn port(i: &str) -> Res<&str, u16> {
90 preceded(tag(":"), u16)(i)
91}
92
93fn host(i: &str) -> Res<&str, Host> {
95 alt((
96 map(ipv4, |ip| Host::Ip(IpAddr::V4(ip))),
97 map(ipv6, |ip| Host::Ip(IpAddr::V6(ip))),
98 map(
99 recognize(many1(alt((alphanumeric1, recognize(one_of("._-")))))),
100 |s: &str| Host::Name(s.to_string()),
101 ),
102 ))(i)
103}
104
105fn ipv6(i: &str) -> Res<&str, Ipv6Addr> {
107 delimited(
108 tag("["),
109 map_res(
110 recognize(tuple((
111 separated_list0(tag(":"), hex_digit1),
112 tag("::"),
113 hex_digit1,
114 ))),
115 Ipv6Addr::from_str,
116 ),
117 tag("]"),
118 )(i)
119}
120
121fn ipv4(i: &str) -> Res<&str, Ipv4Addr> {
123 map_res(
124 recognize(pair(count(pair(digit1, tag(".")), 3), digit1)),
125 Ipv4Addr::from_str,
126 )(i)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_port() {
135 for (input, rem, output) in vec![(":123", "", 123)] {
137 assert_eq!(
138 port(input).unwrap(),
139 (rem, output),
140 "input: {:?}; rem: {:?}",
141 input,
142 rem
143 );
144 }
145
146 for input in vec![",123", ",:123", ":port"] {
147 assert!(port(input).is_err());
148 }
149 }
150
151 #[test]
152 fn test_host() {
153 for (input, rem, output) in vec![
155 ("myhost", "", Host::Name("myhost".into())),
156 ("myhost.mydomain", "", Host::Name("myhost.mydomain".into())),
157 (
158 "subdomain.myhost.com",
159 "",
160 Host::Name("subdomain.myhost.com".into()),
161 ),
162 (
163 "sub-domain.my_host.com",
164 "",
165 Host::Name("sub-domain.my_host.com".into()),
166 ),
167 (
168 "sub-domain.my_host.com/db",
169 "/db",
170 Host::Name("sub-domain.my_host.com".into()),
171 ),
172 (
173 "[2001:db8::1234]/database",
174 "/database",
175 Host::Ip(IpAddr::from_str("2001:db8::1234").unwrap()),
176 ),
177 ] {
178 assert_eq!(
179 host(input),
180 Ok((rem, output)),
181 "input: {input:?}; rem: {rem:?}",
182 );
183 }
184
185 for input in [",host", "/db"] {
187 assert!(host(input).is_err());
188 }
189 }
190
191 #[test]
192 fn test_ipv6() {
193 for (input, rem, output) in [(
195 "[2001:db8::1234]",
196 "",
197 Ipv6Addr::from_str("2001:db8::1234").unwrap(),
198 )] {
199 assert_eq!(
200 ipv6(input),
201 Ok((rem, output)),
202 "input: {input:?}; rem: {rem:?}",
203 );
204 }
205 }
206
207 #[test]
208 fn test_ipv4() {
209 for (input, rem, output) in
211 vec![("192.168.0.1", "", Ipv4Addr::new(192, 168, 0, 1))]
212 {
213 assert_eq!(
214 ipv4(input).unwrap(),
215 (rem, output),
216 "input: {:?}; rem: {:?}",
217 input,
218 rem
219 );
220 }
221 }
222
223 macro_rules! host {
224 ($s:expr) => {
225 Some(host($s).unwrap().1)
226 };
227 }
228
229 #[test]
230 fn test_hostspec() {
231 for (input, rem, output) in vec![(
233 "myhost:123",
234 "",
235 HostSpec {
236 host: host!("myhost"),
237 port: Some(123),
238 },
239 )] {
240 assert_eq!(
241 hostspec(input).unwrap(),
242 (rem, output),
243 "input: {input:?}; rem: {rem:?}",
244 );
245 }
246
247 for input in &["", ",", ",host:123"] {
248 assert!(hostspec(input).is_err());
249 }
250 }
251 #[test]
252 fn test_hostspecs() {
253 for (input, rem, output) in vec![
255 ("", "", vec![]),
256 (", ", ", ", vec![]),
257 (",host:123", ",host:123", vec![]),
258 (
259 "myhost:123",
260 "",
261 vec![HostSpec {
262 host: host!("myhost"),
263 port: Some(123),
264 }],
265 ),
266 (
267 "myhost:123,secondhost:65535",
268 "",
269 vec![
270 HostSpec {
271 host: host!("myhost"),
272 port: Some(123),
273 },
274 HostSpec {
275 host: host!("secondhost"),
276 port: Some(65535),
277 },
278 ],
279 ),
280 ] {
281 assert_eq!(
282 hostspecs(input).unwrap(),
283 (rem, output),
284 "input: {input:?}; rem: {rem:?}",
285 );
286 }
287
288 for input in &[",", ",host:123"] {}
289 }
290}