Skip to content

Commit a60a67c

Browse files
committed
feat(server): properly handle requests that shouldn't have bodies
1 parent a355092 commit a60a67c

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

src/http.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use HttpError::{HttpHeaderError, HttpIoError, HttpMethodError, HttpStatusError,
1717
HttpUriError, HttpVersionError};
1818
use HttpResult;
1919

20-
use self::HttpReader::{SizedReader, ChunkedReader, EofReader};
20+
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
2121
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
2222

2323
/// Readers to handle different Transfer-Encodings.
@@ -30,6 +30,7 @@ pub enum HttpReader<R> {
3030
/// A Reader used when Transfer-Encoding is `chunked`.
3131
ChunkedReader(R, Option<uint>),
3232
/// A Reader used for responses that don't indicate a length or chunked.
33+
///
3334
/// Note: This should only used for `Response`s. It is illegal for a
3435
/// `Request` to be made with both `Content-Length` and
3536
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
@@ -43,6 +44,10 @@ pub enum HttpReader<R> {
4344
/// > reliably; the server MUST respond with the 400 (Bad Request)
4445
/// > status code and then close the connection.
4546
EofReader(R),
47+
/// A Reader used for messages that should never have a body.
48+
///
49+
/// See https://tools.ietf.org/html/rfc7230#section-3.3.3
50+
EmptyReader(R),
4651
}
4752

4853
impl<R: Reader> HttpReader<R> {
@@ -53,6 +58,7 @@ impl<R: Reader> HttpReader<R> {
5358
SizedReader(r, _) => r,
5459
ChunkedReader(r, _) => r,
5560
EofReader(r) => r,
61+
EmptyReader(r) => r,
5662
}
5763
}
5864
}
@@ -106,7 +112,8 @@ impl<R: Reader> Reader for HttpReader<R> {
106112
},
107113
EofReader(ref mut body) => {
108114
body.read(buf)
109-
}
115+
},
116+
EmptyReader(_) => Err(io::standard_error(io::EndOfFile))
110117
}
111118
}
112119
}

src/mock.rs

+7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ impl MockStream {
4040
write: MemWriter::new(),
4141
}
4242
}
43+
44+
pub fn with_input(input: &[u8]) -> MockStream {
45+
MockStream {
46+
read: MemReader::new(input.to_vec()),
47+
write: MemWriter::new(),
48+
}
49+
}
4350
}
4451
impl Reader for MockStream {
4552
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {

src/server/request.rs

+58-6
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ use std::io::net::ip::SocketAddr;
77

88
use {HttpResult};
99
use version::{HttpVersion};
10-
use method;
10+
use method::Method::{mod, Get, Head};
1111
use header::Headers;
12-
use header::common::ContentLength;
12+
use header::common::{ContentLength, TransferEncoding};
1313
use http::{read_request_line};
1414
use http::HttpReader;
15-
use http::HttpReader::{SizedReader, ChunkedReader};
15+
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
1616
use uri::RequestUri;
1717

1818
pub type InternalReader<'a> = &'a mut Reader + 'a;
@@ -22,7 +22,7 @@ pub struct Request<'a> {
2222
/// The IP address of the remote connection.
2323
pub remote_addr: SocketAddr,
2424
/// The `Method`, such as `Get`, `Post`, etc.
25-
pub method: method::Method,
25+
pub method: Method,
2626
/// The headers of the incoming request.
2727
pub headers: Headers,
2828
/// The target request-uri for this request.
@@ -44,14 +44,18 @@ impl<'a> Request<'a> {
4444
debug!("Headers: [\n{}]", headers);
4545

4646

47-
let body = if headers.has::<ContentLength>() {
47+
let body = if method == Get || method == Head {
48+
EmptyReader(stream)
49+
} else if headers.has::<ContentLength>() {
4850
match headers.get::<ContentLength>() {
4951
Some(&ContentLength(len)) => SizedReader(stream, len),
5052
None => unreachable!()
5153
}
52-
} else {
54+
} else if headers.has::<TransferEncoding>() {
5355
todo!("check for Transfer-Encoding: chunked");
5456
ChunkedReader(stream, None)
57+
} else {
58+
EmptyReader(stream)
5559
};
5660

5761
Ok(Request {
@@ -71,3 +75,51 @@ impl<'a> Reader for Request<'a> {
7175
}
7276
}
7377

78+
#[cfg(test)]
79+
mod tests {
80+
use mock::MockStream;
81+
use super::Request;
82+
83+
macro_rules! sock(
84+
($s:expr) => (::std::str::from_str::<::std::io::net::ip::SocketAddr>($s).unwrap())
85+
)
86+
87+
#[test]
88+
fn test_get_empty_body() {
89+
let mut stream = MockStream::with_input(b"\
90+
GET / HTTP/1.1\r\n\
91+
Host: example.domain\r\n\
92+
\r\n\
93+
I'm a bad request.\r\n\
94+
");
95+
96+
let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap();
97+
assert_eq!(req.read_to_string(), Ok("".into_string()));
98+
}
99+
100+
#[test]
101+
fn test_head_empty_body() {
102+
let mut stream = MockStream::with_input(b"\
103+
HEAD / HTTP/1.1\r\n\
104+
Host: example.domain\r\n\
105+
\r\n\
106+
I'm a bad request.\r\n\
107+
");
108+
109+
let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap();
110+
assert_eq!(req.read_to_string(), Ok("".into_string()));
111+
}
112+
113+
#[test]
114+
fn test_post_empty_body() {
115+
let mut stream = MockStream::with_input(b"\
116+
POST / HTTP/1.1\r\n\
117+
Host: example.domain\r\n\
118+
\r\n\
119+
I'm a bad request.\r\n\
120+
");
121+
122+
let mut req = Request::new(&mut stream, sock!("127.0.0.1:80")).unwrap();
123+
assert_eq!(req.read_to_string(), Ok("".into_string()));
124+
}
125+
}

0 commit comments

Comments
 (0)