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