Skip to content

Commit 5f43285

Browse files
committed
fix(server): Drain requests on drop.
If a client sent an illegal request (like a GET request with a message body), or if there was a legal request with a body but the Handler didn't read all of it, the remaining bytes would be left in the stream. The next request to come from the same client would error, as the server would confuse the remaining bytes, and think the request was malformed. Fixes #197 Fixes #309
1 parent 11f10cc commit 5f43285

File tree

3 files changed

+74
-29
lines changed

3 files changed

+74
-29
lines changed

src/http.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::borrow::Cow::{Borrowed, Owned};
33
use std::borrow::IntoCow;
44
use std::cmp::min;
55
use std::old_io::{self, Reader, IoResult, BufWriter};
6+
use std::mem;
67
use std::num::from_u16;
8+
use std::ptr;
79
use std::str;
810
use std::string::CowString;
911

@@ -20,7 +22,7 @@ use HttpError::{HttpHeaderError, HttpIoError, HttpMethodError, HttpStatusError,
2022
HttpUriError, HttpVersionError};
2123
use HttpResult;
2224

23-
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
25+
use self::HttpReader::{SizedReader, ChunkedReader, EofReader};
2426
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
2527

2628
/// Readers to handle different Transfer-Encodings.
@@ -47,22 +49,31 @@ pub enum HttpReader<R> {
4749
/// > reliably; the server MUST respond with the 400 (Bad Request)
4850
/// > status code and then close the connection.
4951
EofReader(R),
50-
/// A Reader used for messages that should never have a body.
51-
///
52-
/// See https://tools.ietf.org/html/rfc7230#section-3.3.3
53-
EmptyReader(R),
5452
}
5553

5654
impl<R: Reader> HttpReader<R> {
5755

5856
/// Unwraps this HttpReader and returns the underlying Reader.
57+
#[inline]
5958
pub fn unwrap(self) -> R {
60-
match self {
61-
SizedReader(r, _) => r,
62-
ChunkedReader(r, _) => r,
63-
EofReader(r) => r,
64-
EmptyReader(r) => r,
65-
}
59+
let r = unsafe {
60+
ptr::read(match self {
61+
SizedReader(ref r, _) => r,
62+
ChunkedReader(ref r, _) => r,
63+
EofReader(ref r) => r,
64+
})
65+
};
66+
unsafe { mem::forget(self); }
67+
r
68+
}
69+
}
70+
71+
#[unsafe_destructor]
72+
impl<R: Reader> Drop for HttpReader<R> {
73+
#[inline]
74+
fn drop(&mut self) {
75+
let mut buf = [0u8; 512];
76+
while self.read(&mut buf).is_ok() {}
6677
}
6778
}
6879

@@ -116,7 +127,6 @@ impl<R: Reader> Reader for HttpReader<R> {
116127
EofReader(ref mut body) => {
117128
body.read(buf)
118129
},
119-
EmptyReader(_) => Err(old_io::standard_error(old_io::EndOfFile))
120130
}
121131
}
122132
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![feature(core, collections, hash, io, os, path, std_misc,
2-
slicing_syntax, box_syntax)]
2+
slicing_syntax, box_syntax, unsafe_destructor)]
33
#![deny(missing_docs)]
44
#![cfg_attr(test, deny(warnings))]
55
#![cfg_attr(test, feature(alloc, test))]

src/server/request.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! These are requests that a `hyper::Server` receives, and include its method,
44
//! target URI, headers, and message body.
5-
use std::old_io::IoResult;
5+
use std::old_io::{self, IoResult};
66
use std::old_io::net::ip::SocketAddr;
77

88
use {HttpResult};
@@ -11,7 +11,7 @@ use method::Method::{self, Get, Head};
1111
use header::{Headers, ContentLength, TransferEncoding};
1212
use http::{read_request_line};
1313
use http::HttpReader;
14-
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
14+
use http::HttpReader::{SizedReader, ChunkedReader};
1515
use uri::RequestUri;
1616

1717
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
@@ -26,7 +26,7 @@ pub struct Request<'a> {
2626
pub uri: RequestUri,
2727
/// The version of HTTP for this request.
2828
pub version: HttpVersion,
29-
body: HttpReader<&'a mut (Reader + 'a)>
29+
body: Body<HttpReader<&'a mut (Reader + 'a)>>
3030
}
3131

3232

@@ -39,18 +39,19 @@ impl<'a> Request<'a> {
3939
let headers = try!(Headers::from_raw(&mut stream));
4040
debug!("{:?}", headers);
4141

42-
let body = if method == Get || method == Head {
43-
EmptyReader(stream)
44-
} else if headers.has::<ContentLength>() {
45-
match headers.get::<ContentLength>() {
46-
Some(&ContentLength(len)) => SizedReader(stream, len),
47-
None => unreachable!()
48-
}
42+
let body = if let Some(len) = headers.get::<ContentLength>() {
43+
SizedReader(stream, **len)
4944
} else if headers.has::<TransferEncoding>() {
5045
todo!("check for Transfer-Encoding: chunked");
5146
ChunkedReader(stream, None)
5247
} else {
53-
EmptyReader(stream)
48+
SizedReader(stream, 0)
49+
};
50+
51+
let body = if method == Get || method == Head {
52+
Body::Empty(body)
53+
} else {
54+
Body::NonEmpty(body)
5455
};
5556

5657
Ok(Request {
@@ -68,13 +69,31 @@ impl<'a> Request<'a> {
6869
RequestUri, HttpVersion,
6970
HttpReader<&'a mut (Reader + 'a)>,) {
7071
(self.remote_addr, self.method, self.headers,
71-
self.uri, self.version, self.body)
72+
self.uri, self.version, self.body.into_inner())
7273
}
7374
}
7475

7576
impl<'a> Reader for Request<'a> {
77+
#[inline]
7678
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
77-
self.body.read(buf)
79+
match self.body {
80+
Body::Empty(..) => Err(old_io::standard_error(old_io::EndOfFile)),
81+
Body::NonEmpty(ref mut r) => r.read(buf)
82+
}
83+
}
84+
}
85+
86+
enum Body<R> {
87+
Empty(R),
88+
NonEmpty(R),
89+
}
90+
91+
impl<R> Body<R> {
92+
fn into_inner(self) -> R {
93+
match self {
94+
Body::Empty(r) => r,
95+
Body::NonEmpty(r) => r
96+
}
7897
}
7998
}
8099

@@ -95,8 +114,9 @@ mod tests {
95114
let mut stream = MockStream::with_input(b"\
96115
GET / HTTP/1.1\r\n\
97116
Host: example.domain\r\n\
117+
Content-Length: 18\r\n\
98118
\r\n\
99-
I'm a bad request.\r\n\
119+
I'm a bad request.\
100120
");
101121

102122
let mut req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
@@ -108,16 +128,17 @@ mod tests {
108128
let mut stream = MockStream::with_input(b"\
109129
HEAD / HTTP/1.1\r\n\
110130
Host: example.domain\r\n\
131+
Content-Length: 18\r\n\
111132
\r\n\
112-
I'm a bad request.\r\n\
133+
I'm a bad request.\
113134
");
114135

115136
let mut req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
116137
assert_eq!(req.read_to_string(), Ok("".to_string()));
117138
}
118139

119140
#[test]
120-
fn test_post_empty_body() {
141+
fn test_post_body_with_no_content_length() {
121142
let mut stream = MockStream::with_input(b"\
122143
POST / HTTP/1.1\r\n\
123144
Host: example.domain\r\n\
@@ -129,6 +150,20 @@ mod tests {
129150
assert_eq!(req.read_to_string(), Ok("".to_string()));
130151
}
131152

153+
#[test]
154+
fn test_unexpected_body_drains_upon_drop() {
155+
let mut stream = MockStream::with_input(b"\
156+
GET / HTTP/1.1\r\n\
157+
Host: example.domain\r\n\
158+
Content-Length: 18\r\n\
159+
\r\n\
160+
I'm a bad request.\
161+
");
162+
163+
Request::new(&mut stream, sock("127.0.0.1:80")).unwrap().read_to_string().unwrap();
164+
assert!(stream.read.eof());
165+
}
166+
132167
#[test]
133168
fn test_parse_chunked_request() {
134169
let mut stream = MockStream::with_input(b"\

0 commit comments

Comments
 (0)