Client should validate request URI. (#181)
This patch adds checks for the request URI and rejects invalid URIs. In the case of forwarding an HTTP 1.1 request with a path, an "http" pseudo header is added to satisfy the HTTP/2.0 spec. Closes #179
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
//! HTTP2 client side.
|
//! HTTP2 client side.
|
||||||
use {SendStream, RecvStream, ReleaseCapacity};
|
use {SendStream, RecvStream, ReleaseCapacity};
|
||||||
use codec::{Codec, RecvError};
|
use codec::{Codec, RecvError, SendError, UserError};
|
||||||
use frame::{Headers, Pseudo, Reason, Settings, StreamId};
|
use frame::{Headers, Pseudo, Reason, Settings, StreamId};
|
||||||
use proto;
|
use proto;
|
||||||
|
|
||||||
use bytes::{Bytes, IntoBuf};
|
use bytes::{Bytes, IntoBuf};
|
||||||
use futures::{Async, Future, MapErr, Poll};
|
use futures::{Async, Future, MapErr, Poll};
|
||||||
use http::{Request, Response};
|
use http::{uri, Request, Response, Method, Version};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_io::io::WriteAll;
|
use tokio_io::io::WriteAll;
|
||||||
|
|
||||||
@@ -46,7 +46,11 @@ pub struct ResponseFuture {
|
|||||||
/// Build a Client.
|
/// Build a Client.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
|
/// Initial `Settings` frame to send as part of the handshake.
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
|
||||||
|
/// The stream ID of the first (lowest) stream. Subsequent streams will use
|
||||||
|
/// monotonically increasing stream IDs.
|
||||||
stream_id: StreamId,
|
stream_id: StreamId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,20 +363,12 @@ impl Future for ResponseFuture {
|
|||||||
|
|
||||||
// ===== impl Peer =====
|
// ===== impl Peer =====
|
||||||
|
|
||||||
impl proto::Peer for Peer {
|
impl Peer {
|
||||||
type Send = Request<()>;
|
pub fn convert_send_message(
|
||||||
type Poll = Response<()>;
|
id: StreamId,
|
||||||
|
request: Request<()>,
|
||||||
|
end_of_stream: bool) -> Result<Headers, SendError>
|
||||||
fn dyn() -> proto::DynPeer {
|
{
|
||||||
proto::DynPeer::Client
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_server() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_send_message(id: StreamId, request: Self::Send, end_of_stream: bool) -> Headers {
|
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
|
|
||||||
let (
|
let (
|
||||||
@@ -380,14 +376,45 @@ impl proto::Peer for Peer {
|
|||||||
method,
|
method,
|
||||||
uri,
|
uri,
|
||||||
headers,
|
headers,
|
||||||
|
version,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
) = request.into_parts();
|
) = request.into_parts();
|
||||||
|
|
||||||
|
let is_connect = method == Method::CONNECT;
|
||||||
|
|
||||||
// Build the set pseudo header set. All requests will include `method`
|
// Build the set pseudo header set. All requests will include `method`
|
||||||
// and `path`.
|
// and `path`.
|
||||||
let pseudo = Pseudo::request(method, uri);
|
let mut pseudo = Pseudo::request(method, uri);
|
||||||
|
|
||||||
|
if pseudo.scheme.is_none() {
|
||||||
|
// If the scheme is not set, then there are a two options.
|
||||||
|
//
|
||||||
|
// 1) Authority is not set. In this case, a request was issued with
|
||||||
|
// a relative URI. This is permitted **only** when forwarding
|
||||||
|
// HTTP 1.x requests. If the HTTP version is set to 2.0, then
|
||||||
|
// this is an error.
|
||||||
|
//
|
||||||
|
// 2) Authority is set, then the HTTP method *must* be CONNECT.
|
||||||
|
//
|
||||||
|
// It is not possible to have a scheme but not an authority set (the
|
||||||
|
// `http` crate does not allow it).
|
||||||
|
//
|
||||||
|
if pseudo.authority.is_none() {
|
||||||
|
if version == Version::HTTP_2 {
|
||||||
|
return Err(UserError::MissingUriSchemeAndAuthority.into());
|
||||||
|
} else {
|
||||||
|
// This is acceptable as per the above comment. However,
|
||||||
|
// HTTP/2.0 requires that a scheme is set. Since we are
|
||||||
|
// forwarding an HTTP 1.1 request, the scheme is set to
|
||||||
|
// "http".
|
||||||
|
pseudo.set_scheme(uri::Scheme::HTTP);
|
||||||
|
}
|
||||||
|
} else if !is_connect {
|
||||||
|
// TODO: Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the HEADERS frame
|
// Create the HEADERS frame
|
||||||
let mut frame = Headers::new(id, pseudo, headers);
|
let mut frame = Headers::new(id, pseudo, headers);
|
||||||
@@ -396,7 +423,19 @@ impl proto::Peer for Peer {
|
|||||||
frame.set_end_stream()
|
frame.set_end_stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
frame
|
Ok(frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl proto::Peer for Peer {
|
||||||
|
type Poll = Response<()>;
|
||||||
|
|
||||||
|
fn dyn() -> proto::DynPeer {
|
||||||
|
proto::DynPeer::Client
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_server() -> bool {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError> {
|
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError> {
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ pub enum UserError {
|
|||||||
|
|
||||||
/// Illegal headers, such as connection-specific headers.
|
/// Illegal headers, such as connection-specific headers.
|
||||||
MalformedHeaders,
|
MalformedHeaders,
|
||||||
|
|
||||||
|
/// Request submitted with relative URI.
|
||||||
|
MissingUriSchemeAndAuthority,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== impl RecvError =====
|
// ===== impl RecvError =====
|
||||||
@@ -125,6 +128,7 @@ impl error::Error for UserError {
|
|||||||
ReleaseCapacityTooBig => "release capacity too big",
|
ReleaseCapacityTooBig => "release capacity too big",
|
||||||
OverflowedStreamId => "stream ID overflowed",
|
OverflowedStreamId => "stream ID overflowed",
|
||||||
MalformedHeaders => "malformed headers",
|
MalformedHeaders => "malformed headers",
|
||||||
|
MissingUriSchemeAndAuthority => "request URI missing scheme and authority",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ pub struct Continuation {
|
|||||||
headers: Iter,
|
headers: Iter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: These fields shouldn't be `pub`
|
||||||
#[derive(Debug, Default, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub struct Pseudo {
|
pub struct Pseudo {
|
||||||
// Request
|
// Request
|
||||||
@@ -405,10 +406,6 @@ impl Pseudo {
|
|||||||
pub fn request(method: Method, uri: Uri) -> Self {
|
pub fn request(method: Method, uri: Uri) -> Self {
|
||||||
let parts = uri::Parts::from(uri);
|
let parts = uri::Parts::from(uri);
|
||||||
|
|
||||||
fn to_string(src: Bytes) -> String<Bytes> {
|
|
||||||
unsafe { String::from_utf8_unchecked(src) }
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = parts
|
let path = parts
|
||||||
.path_and_query
|
.path_and_query
|
||||||
.map(|v| v.into())
|
.map(|v| v.into())
|
||||||
@@ -426,7 +423,7 @@ impl Pseudo {
|
|||||||
//
|
//
|
||||||
// TODO: Scheme must be set...
|
// TODO: Scheme must be set...
|
||||||
if let Some(scheme) = parts.scheme {
|
if let Some(scheme) = parts.scheme {
|
||||||
pseudo.set_scheme(to_string(scheme.into()));
|
pseudo.set_scheme(scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the URI includes an authority component, add it to the pseudo
|
// If the URI includes an authority component, add it to the pseudo
|
||||||
@@ -448,8 +445,8 @@ impl Pseudo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_scheme(&mut self, scheme: String<Bytes>) {
|
pub fn set_scheme(&mut self, scheme: uri::Scheme) {
|
||||||
self.scheme = Some(scheme);
|
self.scheme = Some(to_string(scheme.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_authority(&mut self, authority: String<Bytes>) {
|
pub fn set_authority(&mut self, authority: String<Bytes>) {
|
||||||
@@ -457,6 +454,10 @@ impl Pseudo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_string(src: Bytes) -> String<Bytes> {
|
||||||
|
unsafe { String::from_utf8_unchecked(src) }
|
||||||
|
}
|
||||||
|
|
||||||
// ===== impl Iter =====
|
// ===== impl Iter =====
|
||||||
|
|
||||||
impl Iterator for Iter {
|
impl Iterator for Iter {
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ use std::fmt;
|
|||||||
|
|
||||||
/// Either a Client or a Server
|
/// Either a Client or a Server
|
||||||
pub trait Peer {
|
pub trait Peer {
|
||||||
/// Message type sent into the transport
|
|
||||||
type Send;
|
|
||||||
|
|
||||||
/// Message type polled from the transport
|
/// Message type polled from the transport
|
||||||
type Poll: fmt::Debug;
|
type Poll: fmt::Debug;
|
||||||
|
|
||||||
@@ -18,8 +15,6 @@ pub trait Peer {
|
|||||||
|
|
||||||
fn is_server() -> bool;
|
fn is_server() -> bool;
|
||||||
|
|
||||||
fn convert_send_message(id: StreamId, headers: Self::Send, end_of_stream: bool) -> Headers;
|
|
||||||
|
|
||||||
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError>;
|
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError>;
|
||||||
|
|
||||||
fn is_local_init(id: StreamId) -> bool {
|
fn is_local_init(id: StreamId) -> bool {
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ impl Send {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let end_stream = frame.is_end_stream();
|
let end_stream = frame.is_end_stream();
|
||||||
|
|
||||||
// Update the state
|
// Update the state
|
||||||
|
|||||||
@@ -494,7 +494,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the message
|
// Convert the message
|
||||||
let headers = client::Peer::convert_send_message(stream_id, request, end_of_stream);
|
let headers = client::Peer::convert_send_message(
|
||||||
|
stream_id, request, end_of_stream)?;
|
||||||
|
|
||||||
let mut stream = me.store.insert(stream.id, stream);
|
let mut stream = me.store.insert(stream.id, stream);
|
||||||
|
|
||||||
|
|||||||
@@ -375,23 +375,12 @@ impl<T, B> fmt::Debug for Handshake<T, B>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl proto::Peer for Peer {
|
impl Peer {
|
||||||
type Send = Response<()>;
|
pub fn convert_send_message(
|
||||||
type Poll = Request<()>;
|
|
||||||
|
|
||||||
fn is_server() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dyn() -> proto::DynPeer {
|
|
||||||
proto::DynPeer::Server
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_send_message(
|
|
||||||
id: StreamId,
|
id: StreamId,
|
||||||
response: Self::Send,
|
response: Response<()>,
|
||||||
end_of_stream: bool,
|
end_of_stream: bool) -> frame::Headers
|
||||||
) -> frame::Headers {
|
{
|
||||||
use http::response::Parts;
|
use http::response::Parts;
|
||||||
|
|
||||||
// Extract the components of the HTTP request
|
// Extract the components of the HTTP request
|
||||||
@@ -417,6 +406,18 @@ impl proto::Peer for Peer {
|
|||||||
|
|
||||||
frame
|
frame
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl proto::Peer for Peer {
|
||||||
|
type Poll = Request<()>;
|
||||||
|
|
||||||
|
fn is_server() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn() -> proto::DynPeer {
|
||||||
|
proto::DynPeer::Server
|
||||||
|
}
|
||||||
|
|
||||||
fn convert_poll_message(headers: frame::Headers) -> Result<Self::Poll, RecvError> {
|
fn convert_poll_message(headers: frame::Headers) -> Result<Self::Poll, RecvError> {
|
||||||
use http::{uri, Version};
|
use http::{uri, Version};
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ fn request_stream_id_overflows() {
|
|||||||
.body(())
|
.body(())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
||||||
// second cannot use the next stream id, it's over
|
// second cannot use the next stream id, it's over
|
||||||
|
|
||||||
let poll_err = client.poll_ready().unwrap_err();
|
let poll_err = client.poll_ready().unwrap_err();
|
||||||
@@ -279,8 +278,71 @@ fn request_over_max_concurrent_streams_errors() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
fn http_11_request_without_scheme_or_authority() {
|
||||||
fn request_without_scheme() {}
|
let _ = ::env_logger::init();
|
||||||
|
let (io, srv) = mock::new();
|
||||||
|
|
||||||
|
let srv = srv.assert_client_handshake()
|
||||||
|
.unwrap()
|
||||||
|
.recv_settings()
|
||||||
|
.recv_frame(
|
||||||
|
frames::headers(1)
|
||||||
|
.request("GET", "/")
|
||||||
|
.scheme("http")
|
||||||
|
.eos(),
|
||||||
|
)
|
||||||
|
.send_frame(frames::headers(1).response(200).eos())
|
||||||
|
.close();
|
||||||
|
|
||||||
|
let h2 = Client::handshake(io)
|
||||||
|
.expect("handshake")
|
||||||
|
.and_then(|(mut client, h2)| {
|
||||||
|
// we send a simple req here just to drive the connection so we can
|
||||||
|
// receive the server settings.
|
||||||
|
let request = Request::builder()
|
||||||
|
.method(Method::GET)
|
||||||
|
.uri("/")
|
||||||
|
.body(())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// first request is allowed
|
||||||
|
let (response, _) = client.send_request(request, true).unwrap();
|
||||||
|
h2.drive(response)
|
||||||
|
.map(move |(h2, _)| (client, h2))
|
||||||
|
});
|
||||||
|
|
||||||
|
h2.join(srv).wait().expect("wait");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn http_2_request_without_scheme_or_authority() {
|
||||||
|
let _ = ::env_logger::init();
|
||||||
|
let (io, srv) = mock::new();
|
||||||
|
|
||||||
|
let srv = srv.assert_client_handshake()
|
||||||
|
.unwrap()
|
||||||
|
.recv_settings()
|
||||||
|
.close();
|
||||||
|
|
||||||
|
let h2 = Client::handshake(io)
|
||||||
|
.expect("handshake")
|
||||||
|
.and_then(|(mut client, h2)| {
|
||||||
|
// we send a simple req here just to drive the connection so we can
|
||||||
|
// receive the server settings.
|
||||||
|
let request = Request::builder()
|
||||||
|
.version(Version::HTTP_2)
|
||||||
|
.method(Method::GET)
|
||||||
|
.uri("/")
|
||||||
|
.body(())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// first request is allowed
|
||||||
|
assert!(client.send_request(request, true).is_err());
|
||||||
|
h2.expect("h2")
|
||||||
|
});
|
||||||
|
|
||||||
|
h2.join(srv).wait().expect("wait");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
|
|||||||
@@ -137,6 +137,16 @@ impl Mock<frame::Headers> {
|
|||||||
Mock(frame)
|
Mock(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scheme(self, value: &str) -> Self
|
||||||
|
{
|
||||||
|
let (id, mut pseudo, fields) = self.into_parts();
|
||||||
|
let value = value.parse().unwrap();
|
||||||
|
|
||||||
|
pseudo.set_scheme(value);
|
||||||
|
|
||||||
|
Mock(frame::Headers::new(id, pseudo, fields))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eos(mut self) -> Self {
|
pub fn eos(mut self) -> Self {
|
||||||
self.0.set_end_stream();
|
self.0.set_end_stream();
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub use self::futures::{Future, IntoFuture, Sink, Stream};
|
|||||||
pub use super::future_ext::{FutureExt, Unwrap};
|
pub use super::future_ext::{FutureExt, Unwrap};
|
||||||
|
|
||||||
// Re-export HTTP types
|
// Re-export HTTP types
|
||||||
pub use self::http::{HeaderMap, Method, Request, Response, StatusCode};
|
pub use self::http::{HeaderMap, Method, Request, Response, StatusCode, Version};
|
||||||
|
|
||||||
pub use self::bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
|
pub use self::bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user