diff --git a/misc/multiaddr/Cargo.toml b/misc/multiaddr/Cargo.toml index f20bb989..54ac4324 100644 --- a/misc/multiaddr/Cargo.toml +++ b/misc/multiaddr/Cargo.toml @@ -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 } diff --git a/misc/multiaddr/src/from_url.rs b/misc/multiaddr/src/from_url.rs index b617c643..454672da 100644 --- a/misc/multiaddr/src/from_url.rs +++ b/misc/multiaddr/src/from_url.rs @@ -57,11 +57,11 @@ fn from_url_inner(url: &str, lossy: bool) -> std::result::Result std::result::Result { - 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 { 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)) } - diff --git a/misc/multiaddr/tests/lib.rs b/misc/multiaddr/tests/lib.rs index f0c8625b..0516a682 100644 --- a/misc/multiaddr/tests/lib.rs +++ b/misc/multiaddr/tests/lib.rs @@ -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"))]); diff --git a/transports/websocket/src/browser.rs b/transports/websocket/src/browser.rs index 0822262c..e9998c96 100644 --- a/transports/websocket/src/browser.rs +++ b/transports/websocket/src/browser.rs @@ -298,41 +298,41 @@ fn multiaddr_to_target(addr: &Multiaddr) -> Result { } 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(()), } diff --git a/transports/websocket/src/desktop.rs b/transports/websocket/src/desktop.rs index 08404934..2714761a 100644 --- a/transports/websocket/src/desktop.rs +++ b/transports/websocket/src/desktop.rs @@ -80,27 +80,27 @@ where fn listen_on(self, original_addr: Multiaddr) -> Result> { 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> { 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