feat(server): properly handle requests that shouldn't have bodies
This commit is contained in:
11
src/http.rs
11
src/http.rs
@@ -17,7 +17,7 @@ use HttpError::{HttpHeaderError, HttpIoError, HttpMethodError, HttpStatusError,
|
|||||||
HttpUriError, HttpVersionError};
|
HttpUriError, HttpVersionError};
|
||||||
use HttpResult;
|
use HttpResult;
|
||||||
|
|
||||||
use self::HttpReader::{SizedReader, ChunkedReader, EofReader};
|
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
|
||||||
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
||||||
|
|
||||||
/// Readers to handle different Transfer-Encodings.
|
/// Readers to handle different Transfer-Encodings.
|
||||||
@@ -30,6 +30,7 @@ pub enum HttpReader<R> {
|
|||||||
/// A Reader used when Transfer-Encoding is `chunked`.
|
/// A Reader used when Transfer-Encoding is `chunked`.
|
||||||
ChunkedReader(R, Option<uint>),
|
ChunkedReader(R, Option<uint>),
|
||||||
/// A Reader used for responses that don't indicate a length or chunked.
|
/// 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
|
/// Note: This should only used for `Response`s. It is illegal for a
|
||||||
/// `Request` to be made with both `Content-Length` and
|
/// `Request` to be made with both `Content-Length` and
|
||||||
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
||||||
@@ -43,6 +44,10 @@ pub enum HttpReader<R> {
|
|||||||
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
||||||
/// > status code and then close the connection.
|
/// > status code and then close the connection.
|
||||||
EofReader(R),
|
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<R: Reader> HttpReader<R> {
|
impl<R: Reader> HttpReader<R> {
|
||||||
@@ -53,6 +58,7 @@ impl<R: Reader> HttpReader<R> {
|
|||||||
SizedReader(r, _) => r,
|
SizedReader(r, _) => r,
|
||||||
ChunkedReader(r, _) => r,
|
ChunkedReader(r, _) => r,
|
||||||
EofReader(r) => r,
|
EofReader(r) => r,
|
||||||
|
EmptyReader(r) => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +112,8 @@ impl<R: Reader> Reader for HttpReader<R> {
|
|||||||
},
|
},
|
||||||
EofReader(ref mut body) => {
|
EofReader(ref mut body) => {
|
||||||
body.read(buf)
|
body.read(buf)
|
||||||
}
|
},
|
||||||
|
EmptyReader(_) => Err(io::standard_error(io::EndOfFile))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ impl MockStream {
|
|||||||
write: MemWriter::new(),
|
write: MemWriter::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_input(input: &[u8]) -> MockStream {
|
||||||
|
MockStream {
|
||||||
|
read: MemReader::new(input.to_vec()),
|
||||||
|
write: MemWriter::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Reader for MockStream {
|
impl Reader for MockStream {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
|
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ use std::io::net::ip::SocketAddr;
|
|||||||
|
|
||||||
use {HttpResult};
|
use {HttpResult};
|
||||||
use version::{HttpVersion};
|
use version::{HttpVersion};
|
||||||
use method;
|
use method::Method::{mod, Get, Head};
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use header::common::ContentLength;
|
use header::common::{ContentLength, TransferEncoding};
|
||||||
use http::{read_request_line};
|
use http::{read_request_line};
|
||||||
use http::HttpReader;
|
use http::HttpReader;
|
||||||
use http::HttpReader::{SizedReader, ChunkedReader};
|
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
|
|
||||||
pub type InternalReader<'a> = &'a mut Reader + 'a;
|
pub type InternalReader<'a> = &'a mut Reader + 'a;
|
||||||
@@ -22,7 +22,7 @@ pub struct Request<'a> {
|
|||||||
/// The IP address of the remote connection.
|
/// The IP address of the remote connection.
|
||||||
pub remote_addr: SocketAddr,
|
pub remote_addr: SocketAddr,
|
||||||
/// The `Method`, such as `Get`, `Post`, etc.
|
/// The `Method`, such as `Get`, `Post`, etc.
|
||||||
pub method: method::Method,
|
pub method: Method,
|
||||||
/// The headers of the incoming request.
|
/// The headers of the incoming request.
|
||||||
pub headers: Headers,
|
pub headers: Headers,
|
||||||
/// The target request-uri for this request.
|
/// The target request-uri for this request.
|
||||||
@@ -44,14 +44,18 @@ impl<'a> Request<'a> {
|
|||||||
debug!("Headers: [\n{}]", headers);
|
debug!("Headers: [\n{}]", headers);
|
||||||
|
|
||||||
|
|
||||||
let body = if headers.has::<ContentLength>() {
|
let body = if method == Get || method == Head {
|
||||||
|
EmptyReader(stream)
|
||||||
|
} else if headers.has::<ContentLength>() {
|
||||||
match headers.get::<ContentLength>() {
|
match headers.get::<ContentLength>() {
|
||||||
Some(&ContentLength(len)) => SizedReader(stream, len),
|
Some(&ContentLength(len)) => SizedReader(stream, len),
|
||||||
None => unreachable!()
|
None => unreachable!()
|
||||||
}
|
}
|
||||||
} else {
|
} else if headers.has::<TransferEncoding>() {
|
||||||
todo!("check for Transfer-Encoding: chunked");
|
todo!("check for Transfer-Encoding: chunked");
|
||||||
ChunkedReader(stream, None)
|
ChunkedReader(stream, None)
|
||||||
|
} else {
|
||||||
|
EmptyReader(stream)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Request {
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user