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. | ||||
| use {SendStream, RecvStream, ReleaseCapacity}; | ||||
| use codec::{Codec, RecvError}; | ||||
| use codec::{Codec, RecvError, SendError, UserError}; | ||||
| use frame::{Headers, Pseudo, Reason, Settings, StreamId}; | ||||
| use proto; | ||||
|  | ||||
| use bytes::{Bytes, IntoBuf}; | ||||
| 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::io::WriteAll; | ||||
|  | ||||
| @@ -46,7 +46,11 @@ pub struct ResponseFuture { | ||||
| /// Build a Client. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Builder { | ||||
|     /// Initial `Settings` frame to send as part of the handshake. | ||||
|     settings: Settings, | ||||
|  | ||||
|     /// The stream ID of the first (lowest) stream. Subsequent streams will use | ||||
|     /// monotonically increasing stream IDs. | ||||
|     stream_id: StreamId, | ||||
| } | ||||
|  | ||||
| @@ -359,20 +363,12 @@ impl Future for ResponseFuture { | ||||
|  | ||||
| // ===== impl Peer ===== | ||||
|  | ||||
| impl proto::Peer for Peer { | ||||
|     type Send = Request<()>; | ||||
|     type Poll = Response<()>; | ||||
|  | ||||
|  | ||||
|     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 { | ||||
| impl Peer { | ||||
|     pub fn convert_send_message( | ||||
|         id: StreamId, | ||||
|         request: Request<()>, | ||||
|         end_of_stream: bool) -> Result<Headers, SendError> | ||||
|     { | ||||
|         use http::request::Parts; | ||||
|  | ||||
|         let ( | ||||
| @@ -380,14 +376,45 @@ impl proto::Peer for Peer { | ||||
|                 method, | ||||
|                 uri, | ||||
|                 headers, | ||||
|                 version, | ||||
|                 .. | ||||
|             }, | ||||
|             _, | ||||
|         ) = request.into_parts(); | ||||
|  | ||||
|         let is_connect = method == Method::CONNECT; | ||||
|  | ||||
|         // Build the set pseudo header set. All requests will include `method` | ||||
|         // 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 | ||||
|         let mut frame = Headers::new(id, pseudo, headers); | ||||
| @@ -396,7 +423,19 @@ impl proto::Peer for Peer { | ||||
|             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> { | ||||
|   | ||||
| @@ -47,6 +47,9 @@ pub enum UserError { | ||||
|  | ||||
|     /// Illegal headers, such as connection-specific headers. | ||||
|     MalformedHeaders, | ||||
|  | ||||
|     /// Request submitted with relative URI. | ||||
|     MissingUriSchemeAndAuthority, | ||||
| } | ||||
|  | ||||
| // ===== impl RecvError ===== | ||||
| @@ -125,6 +128,7 @@ impl error::Error for UserError { | ||||
|             ReleaseCapacityTooBig => "release capacity too big", | ||||
|             OverflowedStreamId => "stream ID overflowed", | ||||
|             MalformedHeaders => "malformed headers", | ||||
|             MissingUriSchemeAndAuthority => "request URI missing scheme and authority", | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -63,6 +63,7 @@ pub struct Continuation { | ||||
|     headers: Iter, | ||||
| } | ||||
|  | ||||
| // TODO: These fields shouldn't be `pub` | ||||
| #[derive(Debug, Default, Eq, PartialEq)] | ||||
| pub struct Pseudo { | ||||
|     // Request | ||||
| @@ -405,10 +406,6 @@ impl Pseudo { | ||||
|     pub fn request(method: Method, uri: Uri) -> Self { | ||||
|         let parts = uri::Parts::from(uri); | ||||
|  | ||||
|         fn to_string(src: Bytes) -> String<Bytes> { | ||||
|             unsafe { String::from_utf8_unchecked(src) } | ||||
|         } | ||||
|  | ||||
|         let path = parts | ||||
|             .path_and_query | ||||
|             .map(|v| v.into()) | ||||
| @@ -426,7 +423,7 @@ impl Pseudo { | ||||
|         // | ||||
|         // TODO: Scheme must be set... | ||||
|         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 | ||||
| @@ -448,8 +445,8 @@ impl Pseudo { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_scheme(&mut self, scheme: String<Bytes>) { | ||||
|         self.scheme = Some(scheme); | ||||
|     pub fn set_scheme(&mut self, scheme: uri::Scheme) { | ||||
|         self.scheme = Some(to_string(scheme.into())); | ||||
|     } | ||||
|  | ||||
|     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 Iterator for Iter { | ||||
|   | ||||
| @@ -8,9 +8,6 @@ use std::fmt; | ||||
|  | ||||
| /// Either a Client or a Server | ||||
| pub trait Peer { | ||||
|     /// Message type sent into the transport | ||||
|     type Send; | ||||
|  | ||||
|     /// Message type polled from the transport | ||||
|     type Poll: fmt::Debug; | ||||
|  | ||||
| @@ -18,8 +15,6 @@ pub trait Peer { | ||||
|  | ||||
|     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 is_local_init(id: StreamId) -> bool { | ||||
|   | ||||
| @@ -74,8 +74,6 @@ impl Send { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|  | ||||
|         let end_stream = frame.is_end_stream(); | ||||
|  | ||||
|         // Update the state | ||||
|   | ||||
| @@ -494,7 +494,8 @@ where | ||||
|             } | ||||
|  | ||||
|             // 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); | ||||
|  | ||||
|   | ||||
| @@ -375,23 +375,12 @@ impl<T, B> fmt::Debug for Handshake<T, B> | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl proto::Peer for Peer { | ||||
|     type Send = Response<()>; | ||||
|     type Poll = Request<()>; | ||||
|  | ||||
|     fn is_server() -> bool { | ||||
|         true | ||||
|     } | ||||
|  | ||||
|     fn dyn() -> proto::DynPeer { | ||||
|         proto::DynPeer::Server | ||||
|     } | ||||
|  | ||||
|     fn convert_send_message( | ||||
| impl Peer { | ||||
|     pub fn convert_send_message( | ||||
|         id: StreamId, | ||||
|         response: Self::Send, | ||||
|         end_of_stream: bool, | ||||
|     ) -> frame::Headers { | ||||
|         response: Response<()>, | ||||
|         end_of_stream: bool) -> frame::Headers | ||||
|     { | ||||
|         use http::response::Parts; | ||||
|  | ||||
|         // Extract the components of the HTTP request | ||||
| @@ -417,6 +406,18 @@ impl proto::Peer for Peer { | ||||
|  | ||||
|         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> { | ||||
|         use http::{uri, Version}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user