diff --git a/src/http.rs b/src/http.rs index 53f30a86b6..77ddb6f7df 100644 --- a/src/http.rs +++ b/src/http.rs @@ -17,7 +17,7 @@ use HttpError::{HttpHeaderError, HttpIoError, HttpMethodError, HttpStatusError, HttpUriError, HttpVersionError}; use HttpResult; -use self::HttpReader::{SizedReader, ChunkedReader, EofReader}; +use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader}; use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; /// Readers to handle different Transfer-Encodings. @@ -30,6 +30,7 @@ pub enum HttpReader { /// A Reader used when Transfer-Encoding is `chunked`. ChunkedReader(R, Option), /// A Reader used for responses that don't indicate a length or chunked. + /// /// Note: This should only used for `Response`s. It is illegal for a /// `Request` to be made with both `Content-Length` and /// `Transfer-Encoding: chunked` missing, as explained from the spec: @@ -43,6 +44,10 @@ pub enum HttpReader { /// > reliably; the server MUST respond with the 400 (Bad Request) /// > status code and then close the connection. EofReader(R), + /// A Reader used for messages that should never have a body. + /// + /// See https://tools.ietf.org/html/rfc7230#section-3.3.3 + EmptyReader(R), } impl HttpReader { @@ -53,6 +58,7 @@ impl HttpReader { SizedReader(r, _) => r, ChunkedReader(r, _) => r, EofReader(r) => r, + EmptyReader(r) => r, } } } @@ -106,7 +112,8 @@ impl Reader for HttpReader { }, EofReader(ref mut body) => { body.read(buf) - } + }, + EmptyReader(_) => Err(io::standard_error(io::EndOfFile)) } } } diff --git a/src/mock.rs b/src/mock.rs index 5f7504e44d..54cb525085 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -40,6 +40,13 @@ impl MockStream { write: MemWriter::new(), } } + + pub fn with_input(input: &[u8]) -> MockStream { + MockStream { + read: MemReader::new(input.to_vec()), + write: MemWriter::new(), + } + } } impl Reader for MockStream { fn read(&mut self, buf: &mut [u8]) -> IoResult { diff --git a/src/server/request.rs b/src/server/request.rs index f15df58b44..a09a416930 100644 --- a/src/server/request.rs +++ b/src/server/request.rs @@ -7,12 +7,12 @@ use std::io::net::ip::SocketAddr; use {HttpResult}; use version::{HttpVersion}; -use method; +use method::Method::{mod, Get, Head}; use header::Headers; -use header::common::ContentLength; +use header::common::{ContentLength, TransferEncoding}; use http::{read_request_line}; use http::HttpReader; -use http::HttpReader::{SizedReader, ChunkedReader}; +use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader}; use uri::RequestUri; pub type InternalReader<'a> = &'a mut Reader + 'a; @@ -22,7 +22,7 @@ pub struct Request<'a> { /// The IP address of the remote connection. pub remote_addr: SocketAddr, /// The `Method`, such as `Get`, `Post`, etc. - pub method: method::Method, + pub method: Method, /// The headers of the incoming request. pub headers: Headers, /// The target request-uri for this request. @@ -44,14 +44,18 @@ impl<'a> Request<'a> { debug!("Headers: [\n{}]", headers); - let body = if headers.has::() { + let body = if method == Get || method == Head { + EmptyReader(stream) + } else if headers.has::() { match headers.get::() { Some(&ContentLength(len)) => SizedReader(stream, len), None => unreachable!() } - } else { + } else if headers.has::() { todo!("check for Transfer-Encoding: chunked"); ChunkedReader(stream, None) + } else { + EmptyReader(stream) }; Ok(Request { @@ -71,3 +75,51 @@ impl<'a> Reader for Request<'a> { } } +#[cfg(test)] +mod tests { + use mock::MockStream; + use super::Request; + + macro_rules! sock( + ($s:expr) => (::std::str::from_str::<::std::io::net::ip::SocketAddr>($s).unwrap()) + ) + + #[test] + fn test_get_empty_body() { + let mut stream = MockStream::with_input(b"\ + GET / HTTP/1.1\r\n\ + Host: example.domain\r\n\ + \r\n\ + I'm a bad request.\r\n\ + "); + + let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap(); + assert_eq!(req.read_to_string(), Ok("".into_string())); + } + + #[test] + fn test_head_empty_body() { + let mut stream = MockStream::with_input(b"\ + HEAD / HTTP/1.1\r\n\ + Host: example.domain\r\n\ + \r\n\ + I'm a bad request.\r\n\ + "); + + let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap(); + assert_eq!(req.read_to_string(), Ok("".into_string())); + } + + #[test] + fn test_post_empty_body() { + let mut stream = MockStream::with_input(b"\ + POST / HTTP/1.1\r\n\ + Host: example.domain\r\n\ + \r\n\ + I'm a bad request.\r\n\ + "); + + let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap(); + assert_eq!(req.read_to_string(), Ok("".into_string())); + } +}