From 7081c4498e707c1240c7e672d39ba4948fffb558 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 26 Jul 2017 16:01:30 -0700 Subject: [PATCH] fix(uri): fix Uri parsing of IPv6 and userinfo Closes #1269 --- src/client/dns.rs | 2 +- src/uri.rs | 88 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/client/dns.rs b/src/client/dns.rs index 5badd5c5f7..321a795ce7 100644 --- a/src/client/dns.rs +++ b/src/client/dns.rs @@ -48,6 +48,6 @@ impl Iterator for IpAddrs { pub type Answer = io::Result; fn work(hostname: String, port: u16) -> Answer { - debug!("resolve {:?}:{:?}", hostname, port); + debug!("resolve host={:?}, port={:?}", hostname, port); (&*hostname, port).to_socket_addrs().map(|i| IpAddrs { iter: i }) } diff --git a/src/uri.rs b/src/uri.rs index 0ac3ec1613..5c87a07555 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -69,6 +69,15 @@ impl Uri { // authority was empty return Err(UriError(ErrorKind::MissingAuthority)); } + { + let authority = &s.as_bytes()[scheme_end + 3..auth_end]; + let has_start_bracket = authority.contains(&b'['); + let has_end_bracket = authority.contains(&b']'); + if has_start_bracket ^ has_end_bracket { + // has only 1 of [ and ] + return Err(UriError(ErrorKind::Malformed)); + } + } let query = parse_query(&s); let fragment = parse_fragment(&s); Ok(Uri { @@ -160,18 +169,27 @@ impl Uri { /// Get the host of this `Uri`. #[inline] pub fn host(&self) -> Option<&str> { - if let Some(auth) = self.authority() { - auth.split(":").next() - } else { - None - } + self.authority().map(|auth| { + let host_port = auth.rsplit('@') + .next() + .expect("split always has at least 1 item"); + if host_port.as_bytes()[0] == b'[' { + let i = host_port.find(']') + .expect("parsing should validate matching brackets"); + &host_port[1..i] + } else { + host_port.split(':') + .next() + .expect("split always has at least 1 item") + } + }) } /// Get the port of this `Uri`. #[inline] pub fn port(&self) -> Option { match self.authority() { - Some(auth) => auth.find(":").and_then(|i| u16::from_str(&auth[i+1..]).ok()), + Some(auth) => auth.rfind(':').and_then(|i| auth[i+1..].parse().ok()), None => None, } } @@ -400,7 +418,7 @@ macro_rules! test_parse { let uri = Uri::from_str($str).unwrap(); println!("{:?} = {:#?}", $str, uri); $( - assert_eq!(uri.$method(), $value); + assert_eq!(uri.$method(), $value, stringify!($method)); )+ } ); @@ -423,6 +441,7 @@ test_parse! { scheme = Some("http"), authority = Some("127.0.0.1:61761"), + host = Some("127.0.0.1"), path = "/chunks", query = None, fragment = None, @@ -435,6 +454,7 @@ test_parse! { scheme = Some("https"), authority = Some("127.0.0.1:61761"), + host = Some("127.0.0.1"), path = "/", query = None, fragment = None, @@ -458,6 +478,7 @@ test_parse! { scheme = None, authority = Some("localhost"), + host = Some("localhost"), path = "", query = None, fragment = None, @@ -470,6 +491,7 @@ test_parse! { scheme = None, authority = Some("localhost:3000"), + host = Some("localhost"), path = "", query = None, fragment = None, @@ -478,11 +500,12 @@ test_parse! { test_parse! { test_uri_parse_absolute_with_default_port_http, - "http://127.0.0.1:80", + "http://127.0.0.1:80/foo", scheme = Some("http"), authority = Some("127.0.0.1:80"), - path = "/", + host = Some("127.0.0.1"), + path = "/foo", query = None, fragment = None, port = Some(80), @@ -494,18 +517,59 @@ test_parse! { scheme = Some("https"), authority = Some("127.0.0.1:443"), + host = Some("127.0.0.1"), path = "/", query = None, fragment = None, port = Some(443), } +test_parse! { + test_uri_parse_absolute_with_ipv6, + "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008", + + scheme = Some("https"), + authority = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008"), + host = Some("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + path = "/", + query = None, + fragment = None, + port = Some(8008), +} + +test_parse! { + test_uri_parse_absolute_with_ipv6_and_no_port, + "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + + scheme = Some("https"), + authority = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), + host = Some("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + path = "/", + query = None, + fragment = None, + port = None, +} + +test_parse! { + test_uri_parse_absolute_with_userinfo, + "https://seanmonstar:password@hyper.rs", + + scheme = Some("https"), + authority = Some("seanmonstar:password@hyper.rs"), + host = Some("hyper.rs"), + path = "/", + query = None, + fragment = None, + port = None, +} + test_parse! { test_uri_parse_fragment_questionmark, "http://127.0.0.1/#?", scheme = Some("http"), authority = Some("127.0.0.1"), + host = Some("127.0.0.1"), path = "/", query = None, fragment = Some("?"), @@ -518,6 +582,7 @@ test_parse! { scheme = Some("http"), authority = Some("127.0.0.1"), + host = Some("127.0.0.1"), path = "/path", query = Some(""), fragment = None, @@ -530,6 +595,7 @@ test_parse! { scheme = Some("http"), authority = Some("127.0.0.1"), + host = Some("127.0.0.1"), path = "/", query = Some("foo=bar"), fragment = None, @@ -541,6 +607,7 @@ test_parse! { "http://127.0.0.1#foo/bar", scheme = Some("http"), authority = Some("127.0.0.1"), + host = Some("127.0.0.1"), path = "/", query = None, fragment = Some("foo/bar"), @@ -552,6 +619,7 @@ test_parse! { "http://127.0.0.1#foo?bar", scheme = Some("http"), authority = Some("127.0.0.1"), + host = Some("127.0.0.1"), path = "/", query = None, fragment = Some("foo?bar"), @@ -571,6 +639,8 @@ fn test_uri_parse_error() { err("?key=val"); err("localhost/"); err("localhost?key=val"); + err("http://::1]"); + err("http://[::1"); } #[test]