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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 58 deletions

View File

@ -15,6 +15,7 @@ byteorder = "1.3.1"
bytes = "0.4.12"
data-encoding = "2.1"
multihash = { package = "parity-multihash", version = "0.1.0", path = "../multihash" }
percent-encoding = "1.0.1"
serde = "1.0.70"
unsigned-varint = "0.2"
url = { version = "1.7.2", default-features = false }

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))
}

View File

@ -98,8 +98,8 @@ impl Arbitrary for Proto {
17 => Proto(Udt),
18 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))),
19 => Proto(Utp),
20 => Proto(Ws),
21 => Proto(Wss),
20 => Proto(Ws("/".into())),
21 => Proto(Wss("/".into())),
22 => {
let mut a = [0; 10];
g.fill(&mut a);
@ -196,15 +196,15 @@ fn construct_success() {
// /ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio
ma_valid("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"29200108A07AC542013AC986FFFE317095061F40DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B",
vec![Ip6(addr6.clone()), Tcp(8000), Ws, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))
vec![Ip6(addr6.clone()), Tcp(8000), Ws("/".into()), P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))
]);
ma_valid("/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"9302047F000001062382DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B",
vec![P2pWebRtcStar, Ip4(local.clone()), Tcp(9090), Ws, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))
vec![P2pWebRtcStar, Ip4(local.clone()), Tcp(9090), Ws("/".into()), P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))
]);
ma_valid("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/wss/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"29200108A07AC542013AC986FFFE317095061F40DE03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B",
vec![Ip6(addr6.clone()), Tcp(8000), Wss, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]);
vec![Ip6(addr6.clone()), Tcp(8000), Wss("/".into()), P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]);
ma_valid("/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B",
vec![Ip4(local.clone()), Tcp(9090), P2pCircuit, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]);

View File

@ -298,41 +298,41 @@ fn multiaddr_to_target(addr: &Multiaddr) -> Result<String, ()> {
}
match (&protocols[0], &protocols[1], &protocols[2]) {
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Ws) => {
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Ws(ref ws_path)) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("ws://{}:{}/", ip, port))
Ok(format!("ws://{}:{}{}", ip, port, ws_path))
}
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Ws) => {
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Ws(ref ws_path)) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("ws://[{}]:{}/", ip, port))
Ok(format!("ws://[{}]:{}{}", ip, port, ws_path))
}
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Wss) => {
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Wss(ref ws_path)) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("wss://{}:{}/", ip, port))
Ok(format!("wss://{}:{}{}", ip, port, ws_path))
}
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Wss) => {
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Wss(ref ws_path)) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("wss://[{}]:{}/", ip, port))
Ok(format!("wss://[{}]:{}{}", ip, port, ws_path))
}
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Ws) => {
Ok(format!("ws://{}:{}/", ns, port))
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Ws(ref ws_path)) => {
Ok(format!("ws://{}:{}{}", ns, port, ws_path))
}
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Ws) => {
Ok(format!("ws://{}:{}/", ns, port))
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Ws(ref ws_path)) => {
Ok(format!("ws://{}:{}{}", ns, port, ws_path))
}
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Wss) => {
Ok(format!("wss://{}:{}/", ns, port))
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Wss(ref ws_path)) => {
Ok(format!("wss://{}:{}{}", ns, port, ws_path))
}
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Wss) => {
Ok(format!("wss://{}:{}/", ns, port))
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Wss(ref ws_path)) => {
Ok(format!("wss://{}:{}{}", ns, port, ws_path))
}
_ => Err(()),
}

View File

@ -80,27 +80,27 @@ where
fn listen_on(self, original_addr: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
let mut inner_addr = original_addr.clone();
match inner_addr.pop() {
Some(Protocol::Ws) => {}
Some(Protocol::Ws(ref path)) if path == "/" || path.is_empty() => {},
_ => return Err(TransportError::MultiaddrNotSupported(original_addr)),
};
let inner_listen = self.transport.listen_on(inner_addr)
.map_err(|err| err.map(WsError::Underlying))?;
let listen = inner_listen.map_err(WsError::Underlying).map(|event| {
let listen = inner_listen.map_err(WsError::Underlying).map(move |event| {
match event {
ListenerEvent::NewAddress(mut a) => {
a = a.with(Protocol::Ws);
a = a.with(Protocol::Ws(From::from("/")));
debug!("Listening on {}", a);
ListenerEvent::NewAddress(a)
}
ListenerEvent::AddressExpired(mut a) => {
a = a.with(Protocol::Ws);
a = a.with(Protocol::Ws(From::from("/")));
ListenerEvent::AddressExpired(a)
}
ListenerEvent::Upgrade { upgrade, mut listen_addr, mut remote_addr } => {
listen_addr = listen_addr.with(Protocol::Ws);
remote_addr = remote_addr.with(Protocol::Ws);
listen_addr = listen_addr.with(Protocol::Ws(From::from("/")));
remote_addr = remote_addr.with(Protocol::Ws(From::from("/")));
// Upgrade the listener to websockets like the websockets library requires us to do.
let upgraded = upgrade.map_err(WsError::Underlying).and_then(move |stream| {
@ -157,9 +157,9 @@ where
fn dial(self, original_addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
let mut inner_addr = original_addr.clone();
let is_wss = match inner_addr.pop() {
Some(Protocol::Ws) => false,
Some(Protocol::Wss) => true,
let (ws_path, is_wss) = match inner_addr.pop() {
Some(Protocol::Ws(path)) => (path.into_owned(), false),
Some(Protocol::Wss(path)) => (path.into_owned(), true),
_ => {
trace!(
"Ignoring dial attempt for {} because it is not a websocket multiaddr",
@ -171,7 +171,7 @@ where
debug!("Dialing {} through inner transport", inner_addr);
let ws_addr = client_addr_to_ws(&inner_addr, is_wss);
let ws_addr = client_addr_to_ws(&inner_addr, &ws_path, is_wss);
let inner_dial = self.transport.dial(inner_addr)
.map_err(|err| err.map(WsError::Underlying))?;
@ -242,7 +242,7 @@ where TErr: error::Error + 'static
}
}
fn client_addr_to_ws(client_addr: &Multiaddr, is_wss: bool) -> String {
fn client_addr_to_ws(client_addr: &Multiaddr, ws_path: &str, is_wss: bool) -> String {
let inner = {
let protocols: Vec<_> = client_addr.iter().collect();
@ -268,9 +268,9 @@ fn client_addr_to_ws(client_addr: &Multiaddr, is_wss: bool) -> String {
};
if is_wss {
format!("wss://{}", inner)
format!("wss://{}{}", inner, ws_path)
} else {
format!("ws://{}", inner)
format!("ws://{}{}", inner, ws_path)
}
}
@ -301,7 +301,7 @@ mod tests {
.into_new_address()
.expect("listen address");
assert_eq!(Some(Protocol::Ws), addr.iter().nth(2));
assert_eq!(Some(Protocol::Ws("/".into())), addr.iter().nth(2));
assert_ne!(Some(Protocol::Tcp(0)), addr.iter().nth(1));
let listener = listener
@ -335,7 +335,7 @@ mod tests {
.into_new_address()
.expect("listen address");
assert_eq!(Some(Protocol::Ws), addr.iter().nth(2));
assert_eq!(Some(Protocol::Ws("/".into())), addr.iter().nth(2));
assert_ne!(Some(Protocol::Tcp(0)), addr.iter().nth(1));
let listener = listener