Allow a path for WS multiaddresses (#1093)

* Allow a path for WS multiaddresses

* Fix tests

* Finish

* Tests

* Don't accept any path other than /
This commit is contained in:
Pierre Krieger
2019-05-24 14:12:44 +02:00
committed by GitHub
parent 39c476edeb
commit e7ab8eb1c3
6 changed files with 117 additions and 58 deletions

View File

@ -57,11 +57,11 @@ fn from_url_inner(url: &str, lossy: bool) -> std::result::Result<Multiaddr, From
/// Called when `url.scheme()` is an Internet-like URL.
fn from_url_inner_http_ws(url: url::Url, lossy: bool) -> std::result::Result<Multiaddr, FromUrlErr> {
let (protocol, default_port) = match url.scheme() {
"ws" => (Protocol::Ws, 80),
"wss" => (Protocol::Wss, 443),
"http" => (Protocol::Http, 80),
"https" => (Protocol::Https, 443),
let (protocol, lost_path, default_port) = match url.scheme() {
"ws" => (Protocol::Ws(url.path().to_owned().into()), false, 80),
"wss" => (Protocol::Wss(url.path().to_owned().into()), false, 443),
"http" => (Protocol::Http, true, 80),
"https" => (Protocol::Https, true, 443),
_ => unreachable!("We only call this function for one of the given schemes; qed")
};
@ -78,7 +78,7 @@ fn from_url_inner_http_ws(url: url::Url, lossy: bool) -> std::result::Result<Mul
if !lossy {
if !url.username().is_empty() || url.password().is_some() ||
(url.path() != "/" && !url.path().is_empty()) ||
(lost_path && url.path() != "/" && !url.path().is_empty()) ||
url.query().is_some() || url.fragment().is_some()
{
return Err(FromUrlErr::InformationLoss);
@ -260,4 +260,19 @@ mod tests {
let addr = from_url("unix:/foo/bar").unwrap();
assert_eq!(addr, Multiaddr::from(Protocol::Unix("/foo/bar".into())));
}
#[test]
fn ws_path() {
let addr = from_url("ws://1.2.3.4:1000/foo/bar").unwrap();
assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/x-parity-ws/%2ffoo%2fbar".parse().unwrap());
let addr = from_url("ws://1.2.3.4:1000/").unwrap();
assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/ws".parse().unwrap());
let addr = from_url("wss://1.2.3.4:1000/foo/bar").unwrap();
assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/x-parity-wss/%2ffoo%2fbar".parse().unwrap());
let addr = from_url("wss://1.2.3.4:1000").unwrap();
assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/wss".parse().unwrap());
}
}

View File

@ -37,9 +37,16 @@ const UDT: u32 = 301;
const UNIX: u32 = 400;
const UTP: u32 = 302;
const WS: u32 = 477;
const WS_WITH_PATH: u32 = 4770; // Note: not standard
const WSS: u32 = 478;
const WSS_WITH_PATH: u32 = 4780; // Note: not standard
/// `Protocol` describes all possible multiaddress protocols.
///
/// For `Unix`, `Ws` and `Wss` we use `&str` instead of `Path` to allow
/// cross-platform usage of `Protocol` since encoding `Paths` to bytes is
/// platform-specific. This means that the actual validation of paths needs to
/// happen separately.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Protocol<'a> {
Dccp(u16),
@ -62,13 +69,10 @@ pub enum Protocol<'a> {
Tcp(u16),
Udp(u16),
Udt,
/// For `Unix` we use `&str` instead of `Path` to allow cross-platform usage of
/// `Protocol` since encoding `Paths` to bytes is platform-specific.
/// This means that the actual validation of paths needs to happen separately.
Unix(Cow<'a, str>),
Utp,
Ws,
Wss
Ws(Cow<'a, str>),
Wss(Cow<'a, str>),
}
impl<'a> Protocol<'a> {
@ -134,8 +138,18 @@ impl<'a> Protocol<'a> {
.and_then(|s| read_onion(&s.to_uppercase()))
.map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)),
"quic" => Ok(Protocol::Quic),
"ws" => Ok(Protocol::Ws),
"wss" => Ok(Protocol::Wss),
"ws" => Ok(Protocol::Ws(Cow::Borrowed("/"))),
"wss" => Ok(Protocol::Wss(Cow::Borrowed("/"))),
"x-parity-ws" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
let decoded = percent_encoding::percent_decode(s.as_bytes()).decode_utf8()?;
Ok(Protocol::Ws(decoded))
}
"x-parity-wss" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
let decoded = percent_encoding::percent_decode(s.as_bytes()).decode_utf8()?;
Ok(Protocol::Wss(decoded))
}
"p2p-websocket-star" => Ok(Protocol::P2pWebSocketStar),
"p2p-webrtc-star" => Ok(Protocol::P2pWebRtcStar),
"p2p-webrtc-direct" => Ok(Protocol::P2pWebRtcDirect),
@ -247,8 +261,18 @@ impl<'a> Protocol<'a> {
Ok((Protocol::Unix(Cow::Borrowed(str::from_utf8(data)?)), rest))
}
UTP => Ok((Protocol::Utp, input)),
WS => Ok((Protocol::Ws, input)),
WSS => Ok((Protocol::Wss, input)),
WS => Ok((Protocol::Ws(Cow::Borrowed("/")), input)),
WS_WITH_PATH => {
let (n, input) = decode::usize(input)?;
let (data, rest) = split_at(n, input)?;
Ok((Protocol::Ws(Cow::Borrowed(str::from_utf8(data)?)), rest))
}
WSS => Ok((Protocol::Wss(Cow::Borrowed("/")), input)),
WSS_WITH_PATH => {
let (n, input) = decode::usize(input)?;
let (data, rest) = split_at(n, input)?;
Ok((Protocol::Wss(Cow::Borrowed(str::from_utf8(data)?)), rest))
}
_ => Err(Error::UnknownProtocolId(id))
}
}
@ -318,8 +342,20 @@ impl<'a> Protocol<'a> {
Protocol::Udt => w.write_all(encode::u32(UDT, &mut buf))?,
Protocol::Http => w.write_all(encode::u32(HTTP, &mut buf))?,
Protocol::Https => w.write_all(encode::u32(HTTPS, &mut buf))?,
Protocol::Ws => w.write_all(encode::u32(WS, &mut buf))?,
Protocol::Wss => w.write_all(encode::u32(WSS, &mut buf))?,
Protocol::Ws(ref s) if s == "/" => w.write_all(encode::u32(WS, &mut buf))?,
Protocol::Ws(s) => {
w.write_all(encode::u32(WS_WITH_PATH, &mut buf))?;
let bytes = s.as_bytes();
w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?;
w.write_all(&bytes)?
},
Protocol::Wss(ref s) if s == "/" => w.write_all(encode::u32(WSS, &mut buf))?,
Protocol::Wss(s) => {
w.write_all(encode::u32(WSS_WITH_PATH, &mut buf))?;
let bytes = s.as_bytes();
w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?;
w.write_all(&bytes)?
},
Protocol::P2pWebSocketStar => w.write_all(encode::u32(P2P_WEBSOCKET_STAR, &mut buf))?,
Protocol::P2pWebRtcStar => w.write_all(encode::u32(P2P_WEBRTC_STAR, &mut buf))?,
Protocol::P2pWebRtcDirect => w.write_all(encode::u32(P2P_WEBRTC_DIRECT, &mut buf))?,
@ -357,8 +393,8 @@ impl<'a> Protocol<'a> {
Udt => Udt,
Unix(cow) => Unix(Cow::Owned(cow.into_owned())),
Utp => Utp,
Ws => Ws,
Wss => Wss
Ws(cow) => Ws(Cow::Owned(cow.into_owned())),
Wss(cow) => Wss(Cow::Owned(cow.into_owned())),
}
}
}
@ -391,8 +427,16 @@ impl<'a> fmt::Display for Protocol<'a> {
Udt => f.write_str("/udt"),
Unix(s) => write!(f, "/unix/{}", s),
Utp => f.write_str("/utp"),
Ws => f.write_str("/ws"),
Wss => f.write_str("/wss"),
Ws(ref s) if s == "/" => f.write_str("/ws"),
Ws(s) => {
let encoded = percent_encoding::percent_encode(s.as_bytes(), percent_encoding::PATH_SEGMENT_ENCODE_SET);
write!(f, "/x-parity-ws/{}", encoded)
},
Wss(ref s) if s == "/" => f.write_str("/wss"),
Wss(s) => {
let encoded = percent_encoding::percent_encode(s.as_bytes(), percent_encoding::PATH_SEGMENT_ENCODE_SET);
write!(f, "/x-parity-wss/{}", encoded)
},
}
}
}
@ -452,4 +496,3 @@ fn read_onion(s: &str) -> Result<([u8; 10], u16)> {
Ok((buf, port))
}