volans_core/multiaddr/
from_url.rs1use super::{Multiaddr, Protocol};
2use std::{error, fmt, net::IpAddr};
3
4pub fn from_url(url: &str) -> std::result::Result<Multiaddr, FromUrlErr> {
5 from_url_inner(url, false)
6}
7
8pub fn from_url_lossy(url: &str) -> std::result::Result<Multiaddr, FromUrlErr> {
9 from_url_inner(url, true)
10}
11
12fn from_url_inner(url: &str, lossy: bool) -> std::result::Result<Multiaddr, FromUrlErr> {
13 let url = url::Url::parse(url).map_err(|_| FromUrlErr::BadUrl)?;
14
15 match url.scheme() {
16 "ws" | "wss" | "http" | "https" => from_url_inner_http_ws(url, lossy),
18 "unix" => from_url_inner_path(url, lossy),
19 _ => Err(FromUrlErr::UnsupportedScheme),
20 }
21}
22
23fn from_url_inner_http_ws(
24 url: url::Url,
25 lossy: bool,
26) -> std::result::Result<Multiaddr, FromUrlErr> {
27 let (protocol, is_tls, default_port) = match url.scheme() {
28 "ws" => (Protocol::Ws, false, 80),
29 "wss" => (Protocol::Ws, true, 443),
30 "http" => (Protocol::Http, false, 80),
31 "https" => (Protocol::Http, true, 443),
32 _ => unreachable!("We only call this function for one of the given schemes; qed"),
33 };
34
35 let port = Protocol::Tcp(url.port().unwrap_or(default_port));
36 let ip = if let Some(hostname) = url.host_str() {
37 if let Ok(ip) = hostname.parse::<IpAddr>() {
38 Protocol::from(ip)
39 } else {
40 Protocol::Dns(hostname.into())
41 }
42 } else {
43 return Err(FromUrlErr::BadUrl);
44 };
45
46 if !lossy
47 && (!url.username().is_empty()
48 || url.password().is_some()
49 || url.query().is_some()
50 || url.fragment().is_some())
51 {
52 return Err(FromUrlErr::InformationLoss);
53 }
54
55 let mut multiaddr = Multiaddr::from(ip).with(port);
56 if is_tls {
57 multiaddr.push(Protocol::Tls);
58 }
59 multiaddr.push(protocol);
60 if !url.path().is_empty() && url.path() != "/" {
61 multiaddr.push(Protocol::Path(url.path().to_owned().into()));
62 }
63 Ok(multiaddr)
64}
65
66fn from_url_inner_path(url: url::Url, lossy: bool) -> std::result::Result<Multiaddr, FromUrlErr> {
67 let protocol = match url.scheme() {
68 "unix" => Protocol::Unix,
69 _ => unreachable!("We only call this function for one of the given schemes; qed"),
70 };
71
72 if !lossy
73 && (!url.username().is_empty()
74 || url.password().is_some()
75 || url.query().is_some()
76 || url.fragment().is_some())
77 {
78 return Err(FromUrlErr::InformationLoss);
79 }
80
81 Ok(Multiaddr::from(protocol).with(Protocol::Path(url.path().to_owned().into())))
82}
83
84#[derive(Debug)]
86pub enum FromUrlErr {
87 BadUrl,
89 UnsupportedScheme,
91 InformationLoss,
93}
94
95impl fmt::Display for FromUrlErr {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 FromUrlErr::BadUrl => write!(f, "Bad URL"),
99 FromUrlErr::UnsupportedScheme => write!(f, "Unrecognized URL scheme"),
100 FromUrlErr::InformationLoss => write!(f, "Some information in the URL would be lost"),
101 }
102 }
103}
104
105impl error::Error for FromUrlErr {}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn parse_garbage_doesnt_panic() {
113 for _ in 0..50 {
114 let url = (0..16).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
115 let url = String::from_utf8_lossy(&url);
116 assert!(from_url(&url).is_err());
117 }
118 }
119
120 #[test]
121 fn normal_usage_ws() {
122 let addr = from_url("ws://127.0.0.1:8000").unwrap();
123 assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/ws".parse().unwrap());
124 }
125
126 #[test]
127 fn normal_usage_wss() {
128 let addr = from_url("wss://127.0.0.1:8000").unwrap();
129 assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/tls/ws".parse().unwrap());
130 }
131
132 #[test]
133 fn default_ws_port() {
134 let addr = from_url("ws://127.0.0.1").unwrap();
135 assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/ws".parse().unwrap());
136 }
137
138 #[test]
139 fn default_http_port() {
140 let addr = from_url("http://127.0.0.1").unwrap();
141 assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/http".parse().unwrap());
142 }
143
144 #[test]
145 fn default_wss_port() {
146 let addr = from_url("wss://127.0.0.1").unwrap();
147 assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/tls/ws".parse().unwrap());
148 }
149
150 #[test]
151 fn default_https_port() {
152 let addr = from_url("https://127.0.0.1").unwrap();
153 assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/tls/http".parse().unwrap());
154 }
155
156 #[test]
157 fn dns_addr_ws() {
158 let addr = from_url("ws://example.com").unwrap();
159 assert_eq!(addr, "/dns/example.com/tcp/80/ws".parse().unwrap());
160 }
161
162 #[test]
163 fn dns_addr_http() {
164 let addr = from_url("http://example.com").unwrap();
165 assert_eq!(addr, "/dns/example.com/tcp/80/http".parse().unwrap());
166 }
167
168 #[test]
169 fn dns_addr_wss() {
170 let addr = from_url("wss://example.com").unwrap();
171 assert_eq!(addr, "/dns/example.com/tcp/443/tls/ws".parse().unwrap());
172 }
173
174 #[test]
175 fn dns_addr_https() {
176 let addr = from_url("https://example.com").unwrap();
177 assert_eq!(addr, "/dns/example.com/tcp/443/tls/http".parse().unwrap());
178 }
179
180 #[test]
181 fn bad_hostname() {
182 let addr = from_url("wss://127.0.0.1x").unwrap();
183 assert_eq!(addr, "/dns/127.0.0.1x/tcp/443/tls/ws".parse().unwrap());
184 }
185
186 #[test]
187 fn wrong_scheme() {
188 match from_url("foo://127.0.0.1") {
189 Err(FromUrlErr::UnsupportedScheme) => {}
190 _ => panic!(),
191 }
192 }
193
194 #[test]
195 fn dns_and_port() {
196 let addr = from_url("http://example.com:1000").unwrap();
197 assert_eq!(addr, "/dns/example.com/tcp/1000/http".parse().unwrap());
198 }
199
200 #[test]
201 fn username_lossy() {
202 let addr = "http://foo@example.com:1000/";
203 assert!(from_url(addr).is_err());
204 assert!(from_url_lossy(addr).is_ok());
205 assert!(from_url("http://@example.com:1000/").is_ok());
206 }
207
208 #[test]
209 fn password_lossy() {
210 let addr = "http://:bar@example.com:1000/";
211 assert!(from_url(addr).is_err());
212 assert!(from_url_lossy(addr).is_ok());
213 }
214
215 #[test]
216 fn path_lossy() {
217 let addr = "http://example.com:1000/foo";
218 assert!(from_url_lossy(addr).is_ok());
219 }
220
221 #[test]
222 fn fragment_lossy() {
223 let addr = "http://example.com:1000/#foo";
224 assert!(from_url(addr).is_err());
225 assert!(from_url_lossy(addr).is_ok());
226 }
227
228 #[test]
229 fn unix() {
230 let addr = from_url("unix:/foo/bar").unwrap();
231 assert_eq!(
232 addr,
233 Multiaddr::from(Protocol::Unix).with(Protocol::Path("/foo/bar".into()))
234 );
235 }
236
237 #[test]
238 fn ws_path() {
239 let addr = from_url("ws://1.2.3.4:1000/foo/bar").unwrap();
240 assert_eq!(
241 addr,
242 "/ip4/1.2.3.4/tcp/1000/ws/x-with-path/%2ffoo%2fbar"
243 .parse()
244 .unwrap()
245 );
246
247 let addr = from_url("ws://1.2.3.4:1000/").unwrap();
248 assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/ws".parse().unwrap());
249
250 let addr = from_url("wss://1.2.3.4:1000/foo/bar").unwrap();
251 assert_eq!(
252 addr,
253 "/ip4/1.2.3.4/tcp/1000/tls/ws/x-with-path/%2ffoo%2fbar"
254 .parse()
255 .unwrap()
256 );
257
258 let addr = from_url("wss://1.2.3.4:1000").unwrap();
259 assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/tls/ws".parse().unwrap());
260 }
261}