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