multiaddr/
from_url.rs
1use crate::{Multiaddr, Protocol};
2use std::{error, fmt, iter, net::IpAddr};
3
4pub fn from_url(url: &str) -> std::result::Result<Multiaddr, FromUrlErr> {
29 from_url_inner(url, false)
30}
31
32pub fn from_url_lossy(url: &str) -> std::result::Result<Multiaddr, FromUrlErr> {
49 from_url_inner(url, true)
50}
51
52fn from_url_inner(url: &str, lossy: bool) -> std::result::Result<Multiaddr, FromUrlErr> {
54 let url = url::Url::parse(url).map_err(|_| FromUrlErr::BadUrl)?;
55
56 match url.scheme() {
57 "ws" | "wss" | "http" | "https" => from_url_inner_http_ws(url, lossy),
59 "unix" => from_url_inner_path(url, lossy),
60 _ => Err(FromUrlErr::UnsupportedScheme),
61 }
62}
63
64fn from_url_inner_http_ws(
66 url: url::Url,
67 lossy: bool,
68) -> std::result::Result<Multiaddr, FromUrlErr> {
69 let (protocol, lost_path, default_port) = match url.scheme() {
70 "ws" => (Protocol::Ws(url.path().to_owned().into()), false, 80),
71 "wss" => (Protocol::Wss(url.path().to_owned().into()), false, 443),
72 "http" => (Protocol::Http, true, 80),
73 "https" => (Protocol::Https, true, 443),
74 _ => unreachable!("We only call this function for one of the given schemes; qed"),
75 };
76
77 let port = Protocol::Tcp(url.port().unwrap_or(default_port));
78 let ip = if let Some(hostname) = url.host_str() {
79 if let Ok(ip) = hostname.parse::<IpAddr>() {
80 Protocol::from(ip)
81 } else {
82 Protocol::Dns(hostname.into())
83 }
84 } else {
85 return Err(FromUrlErr::BadUrl);
86 };
87
88 if !lossy
89 && (!url.username().is_empty()
90 || url.password().is_some()
91 || (lost_path && url.path() != "/" && !url.path().is_empty())
92 || url.query().is_some()
93 || url.fragment().is_some())
94 {
95 return Err(FromUrlErr::InformationLoss);
96 }
97
98 Ok(iter::once(ip)
99 .chain(iter::once(port))
100 .chain(iter::once(protocol))
101 .collect())
102}
103
104fn from_url_inner_path(url: url::Url, lossy: bool) -> std::result::Result<Multiaddr, FromUrlErr> {
106 let protocol = match url.scheme() {
107 "unix" => Protocol::Unix(url.path().to_owned().into()),
108 _ => unreachable!("We only call this function for one of the given schemes; qed"),
109 };
110
111 if !lossy
112 && (!url.username().is_empty()
113 || url.password().is_some()
114 || url.query().is_some()
115 || url.fragment().is_some())
116 {
117 return Err(FromUrlErr::InformationLoss);
118 }
119
120 Ok(Multiaddr::from(protocol))
121}
122
123#[derive(Debug)]
125pub enum FromUrlErr {
126 BadUrl,
128 UnsupportedScheme,
130 InformationLoss,
132}
133
134impl fmt::Display for FromUrlErr {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 FromUrlErr::BadUrl => write!(f, "Bad URL"),
138 FromUrlErr::UnsupportedScheme => write!(f, "Unrecognized URL scheme"),
139 FromUrlErr::InformationLoss => write!(f, "Some information in the URL would be lost"),
140 }
141 }
142}
143
144impl error::Error for FromUrlErr {}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn parse_garbage_doesnt_panic() {
152 for _ in 0..50 {
153 let url = (0..16).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
154 let url = String::from_utf8_lossy(&url);
155 assert!(from_url(&url).is_err());
156 }
157 }
158
159 #[test]
160 fn normal_usage_ws() {
161 let addr = from_url("ws://127.0.0.1:8000").unwrap();
162 assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/ws".parse().unwrap());
163 }
164
165 #[test]
166 fn normal_usage_wss() {
167 let addr = from_url("wss://127.0.0.1:8000").unwrap();
168 assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/wss".parse().unwrap());
169 }
170
171 #[test]
172 fn default_ws_port() {
173 let addr = from_url("ws://127.0.0.1").unwrap();
174 assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/ws".parse().unwrap());
175 }
176
177 #[test]
178 fn default_http_port() {
179 let addr = from_url("http://127.0.0.1").unwrap();
180 assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/http".parse().unwrap());
181 }
182
183 #[test]
184 fn default_wss_port() {
185 let addr = from_url("wss://127.0.0.1").unwrap();
186 assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/wss".parse().unwrap());
187 }
188
189 #[test]
190 fn default_https_port() {
191 let addr = from_url("https://127.0.0.1").unwrap();
192 assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/https".parse().unwrap());
193 }
194
195 #[test]
196 fn dns_addr_ws() {
197 let addr = from_url("ws://example.com").unwrap();
198 assert_eq!(addr, "/dns/example.com/tcp/80/ws".parse().unwrap());
199 }
200
201 #[test]
202 fn dns_addr_http() {
203 let addr = from_url("http://example.com").unwrap();
204 assert_eq!(addr, "/dns/example.com/tcp/80/http".parse().unwrap());
205 }
206
207 #[test]
208 fn dns_addr_wss() {
209 let addr = from_url("wss://example.com").unwrap();
210 assert_eq!(addr, "/dns/example.com/tcp/443/wss".parse().unwrap());
211 }
212
213 #[test]
214 fn dns_addr_https() {
215 let addr = from_url("https://example.com").unwrap();
216 assert_eq!(addr, "/dns/example.com/tcp/443/https".parse().unwrap());
217 }
218
219 #[test]
220 fn bad_hostname() {
221 let addr = from_url("wss://127.0.0.1x").unwrap();
222 assert_eq!(addr, "/dns/127.0.0.1x/tcp/443/wss".parse().unwrap());
223 }
224
225 #[test]
226 fn wrong_scheme() {
227 match from_url("foo://127.0.0.1") {
228 Err(FromUrlErr::UnsupportedScheme) => {}
229 _ => panic!(),
230 }
231 }
232
233 #[test]
234 fn dns_and_port() {
235 let addr = from_url("http://example.com:1000").unwrap();
236 assert_eq!(addr, "/dns/example.com/tcp/1000/http".parse().unwrap());
237 }
238
239 #[test]
240 fn username_lossy() {
241 let addr = "http://foo@example.com:1000/";
242 assert!(from_url(addr).is_err());
243 assert!(from_url_lossy(addr).is_ok());
244 assert!(from_url("http://@example.com:1000/").is_ok());
245 }
246
247 #[test]
248 fn password_lossy() {
249 let addr = "http://:bar@example.com:1000/";
250 assert!(from_url(addr).is_err());
251 assert!(from_url_lossy(addr).is_ok());
252 }
253
254 #[test]
255 fn path_lossy() {
256 let addr = "http://example.com:1000/foo";
257 assert!(from_url(addr).is_err());
258 assert!(from_url_lossy(addr).is_ok());
259 }
260
261 #[test]
262 fn fragment_lossy() {
263 let addr = "http://example.com:1000/#foo";
264 assert!(from_url(addr).is_err());
265 assert!(from_url_lossy(addr).is_ok());
266 }
267
268 #[test]
269 fn unix() {
270 let addr = from_url("unix:/foo/bar").unwrap();
271 assert_eq!(addr, Multiaddr::from(Protocol::Unix("/foo/bar".into())));
272 }
273
274 #[test]
275 fn ws_path() {
276 let addr = from_url("ws://1.2.3.4:1000/foo/bar").unwrap();
277 assert_eq!(
278 addr,
279 "/ip4/1.2.3.4/tcp/1000/x-parity-ws/%2ffoo%2fbar"
280 .parse()
281 .unwrap()
282 );
283
284 let addr = from_url("ws://1.2.3.4:1000/").unwrap();
285 assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/ws".parse().unwrap());
286
287 let addr = from_url("wss://1.2.3.4:1000/foo/bar").unwrap();
288 assert_eq!(
289 addr,
290 "/ip4/1.2.3.4/tcp/1000/x-parity-wss/%2ffoo%2fbar"
291 .parse()
292 .unwrap()
293 );
294
295 let addr = from_url("wss://1.2.3.4:1000").unwrap();
296 assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/wss".parse().unwrap());
297 }
298}