Skip to content

Commit 75c7117

Browse files
committed
fix(client): be resilient to invalid response bodies
When an Http11Message knows that the previous response should not have included a body per RFC7230, and fails to parse the following response, the bytes are shuffled along, checking for the start of the next response. Closes #640
1 parent 5c7195a commit 75c7117

File tree

6 files changed

+183
-57
lines changed

6 files changed

+183
-57
lines changed

src/client/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,10 @@ fn get_host_and_port(url: &Url) -> ::Result<(String, u16)> {
446446

447447
#[cfg(test)]
448448
mod tests {
449+
use std::io::Read;
449450
use header::Server;
450451
use super::{Client, RedirectPolicy};
452+
use super::pool::Pool;
451453
use url::Url;
452454

453455
mock_connector!(MockRedirectPolicy {
@@ -494,4 +496,31 @@ mod tests {
494496
let res = client.get("http://127.0.0.1").send().unwrap();
495497
assert_eq!(res.headers.get(), Some(&Server("mock2".to_owned())));
496498
}
499+
500+
mock_connector!(Issue640Connector {
501+
b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\n",
502+
b"GET",
503+
b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n",
504+
b"HEAD",
505+
b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n",
506+
b"POST"
507+
});
508+
509+
// see issue #640
510+
#[test]
511+
fn test_head_response_body_keep_alive() {
512+
let client = Client::with_connector(Pool::with_connector(Default::default(), Issue640Connector));
513+
514+
let mut s = String::new();
515+
client.get("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
516+
assert_eq!(s, "GET");
517+
518+
let mut s = String::new();
519+
client.head("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
520+
assert_eq!(s, "");
521+
522+
let mut s = String::new();
523+
client.post("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
524+
assert_eq!(s, "POST");
525+
}
497526
}

src/client/pool.rs

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl Default for Config {
3434

3535
#[derive(Debug)]
3636
struct PoolImpl<S> {
37-
conns: HashMap<Key, Vec<S>>,
37+
conns: HashMap<Key, Vec<PooledStreamInner<S>>>,
3838
config: Config,
3939
}
4040

@@ -90,7 +90,7 @@ impl<C: NetworkConnector> Pool<C> {
9090
}
9191

9292
impl<S> PoolImpl<S> {
93-
fn reuse(&mut self, key: Key, conn: S) {
93+
fn reuse(&mut self, key: Key, conn: PooledStreamInner<S>) {
9494
trace!("reuse {:?}", key);
9595
let conns = self.conns.entry(key).or_insert(vec![]);
9696
if conns.len() < self.config.max_idle {
@@ -105,83 +105,107 @@ impl<C: NetworkConnector<Stream=S>, S: NetworkStream + Send> NetworkConnector fo
105105
let key = key(host, port, scheme);
106106
let mut locked = self.inner.lock().unwrap();
107107
let mut should_remove = false;
108-
let conn = match locked.conns.get_mut(&key) {
108+
let inner = match locked.conns.get_mut(&key) {
109109
Some(ref mut vec) => {
110110
trace!("Pool had connection, using");
111111
should_remove = vec.len() == 1;
112112
vec.pop().unwrap()
113113
}
114-
_ => try!(self.connector.connect(host, port, scheme))
114+
_ => PooledStreamInner {
115+
key: key.clone(),
116+
stream: try!(self.connector.connect(host, port, scheme)),
117+
previous_response_expected_no_content: false,
118+
}
115119
};
116120
if should_remove {
117121
locked.conns.remove(&key);
118122
}
119123
Ok(PooledStream {
120-
inner: Some((key, conn)),
124+
inner: Some(inner),
121125
is_closed: false,
122-
pool: self.inner.clone()
126+
pool: self.inner.clone(),
123127
})
124128
}
125129
}
126130

127131
/// A Stream that will try to be returned to the Pool when dropped.
128132
pub struct PooledStream<S> {
129-
inner: Option<(Key, S)>,
133+
inner: Option<PooledStreamInner<S>>,
130134
is_closed: bool,
131-
pool: Arc<Mutex<PoolImpl<S>>>
135+
pool: Arc<Mutex<PoolImpl<S>>>,
136+
}
137+
138+
#[derive(Debug)]
139+
struct PooledStreamInner<S> {
140+
key: Key,
141+
stream: S,
142+
previous_response_expected_no_content: bool,
132143
}
133144

134145
impl<S: NetworkStream> Read for PooledStream<S> {
135146
#[inline]
136147
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
137-
self.inner.as_mut().unwrap().1.read(buf)
148+
self.inner.as_mut().unwrap().stream.read(buf)
138149
}
139150
}
140151

141152
impl<S: NetworkStream> Write for PooledStream<S> {
142153
#[inline]
143154
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
144-
self.inner.as_mut().unwrap().1.write(buf)
155+
self.inner.as_mut().unwrap().stream.write(buf)
145156
}
146157

147158
#[inline]
148159
fn flush(&mut self) -> io::Result<()> {
149-
self.inner.as_mut().unwrap().1.flush()
160+
self.inner.as_mut().unwrap().stream.flush()
150161
}
151162
}
152163

153164
impl<S: NetworkStream> NetworkStream for PooledStream<S> {
154165
#[inline]
155166
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
156-
self.inner.as_mut().unwrap().1.peer_addr()
167+
self.inner.as_mut().unwrap().stream.peer_addr()
157168
}
158169

159170
#[cfg(feature = "timeouts")]
160171
#[inline]
161172
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
162-
self.inner.as_ref().unwrap().1.set_read_timeout(dur)
173+
self.inner.as_ref().unwrap().stream.set_read_timeout(dur)
163174
}
164175

165176
#[cfg(feature = "timeouts")]
166177
#[inline]
167178
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
168-
self.inner.as_ref().unwrap().1.set_write_timeout(dur)
179+
self.inner.as_ref().unwrap().stream.set_write_timeout(dur)
169180
}
170181

171182
#[inline]
172183
fn close(&mut self, how: Shutdown) -> io::Result<()> {
173184
self.is_closed = true;
174-
self.inner.as_mut().unwrap().1.close(how)
185+
self.inner.as_mut().unwrap().stream.close(how)
186+
}
187+
188+
#[inline]
189+
fn set_previous_response_expected_no_content(&mut self, expected: bool) {
190+
trace!("set_previous_response_expected_no_content {}", expected);
191+
self.inner.as_mut().unwrap().previous_response_expected_no_content = expected;
192+
}
193+
194+
#[inline]
195+
fn previous_response_expected_no_content(&self) -> bool {
196+
let answer = self.inner.as_ref().unwrap().previous_response_expected_no_content;
197+
trace!("previous_response_expected_no_content {}", answer);
198+
answer
175199
}
176200
}
177201

178202
impl<S> Drop for PooledStream<S> {
179203
fn drop(&mut self) {
180204
trace!("PooledStream.drop, is_closed={}", self.is_closed);
181205
if !self.is_closed {
182-
self.inner.take().map(|(key, conn)| {
206+
self.inner.take().map(|inner| {
183207
if let Ok(mut pool) = self.pool.lock() {
184-
pool.reuse(key, conn);
208+
pool.reuse(inner.key.clone(), inner);
185209
}
186210
// else poisoned, give up
187211
});

src/client/response.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ impl Response {
6464
pub fn status_raw(&self) -> &RawStatus {
6565
&self.status_raw
6666
}
67-
6867
}
6968

7069
impl Read for Response {
@@ -91,11 +90,11 @@ impl Drop for Response {
9190
//
9291
// otherwise, the response has been drained. we should check that the
9392
// server has agreed to keep the connection open
94-
trace!("Response.is_drained = {:?}", self.is_drained);
93+
trace!("Response.drop is_drained={}", self.is_drained);
9594
if !(self.is_drained && http::should_keep_alive(self.version, &self.headers)) {
96-
trace!("closing connection");
95+
trace!("Response.drop closing connection");
9796
if let Err(e) = self.message.close_connection() {
98-
error!("error closing connection: {}", e);
97+
error!("Response.drop error closing connection: {}", e);
9998
}
10099
}
101100
}

src/http/h1.rs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use http::{
3333
use header;
3434
use version;
3535

36+
const MAX_INVALID_RESPONSE_BYTES: usize = 1024 * 128;
37+
3638
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
3739
#[derive(Debug)]
3840
pub struct Http11Message {
@@ -169,19 +171,38 @@ impl HttpMessage for Http11Message {
169171
}
170172
};
171173

174+
let expected_no_content = stream.previous_response_expected_no_content();
175+
trace!("previous_response_expected_no_content = {}", expected_no_content);
176+
172177
let mut stream = BufReader::new(stream);
173178

174-
let head = match parse_response(&mut stream) {
175-
Ok(head) => head,
176-
Err(e) => {
177-
self.stream = Some(stream.into_inner());
178-
return Err(e);
179-
}
180-
};
179+
let mut invalid_bytes_read = 0;
180+
let head;
181+
loop {
182+
head = match parse_response(&mut stream) {
183+
Ok(head) => head,
184+
Err(::Error::Version)
185+
if expected_no_content && invalid_bytes_read < MAX_INVALID_RESPONSE_BYTES => {
186+
trace!("expected_no_content, found content");
187+
invalid_bytes_read += 1;
188+
stream.consume(1);
189+
continue;
190+
}
191+
Err(e) => {
192+
self.stream = Some(stream.into_inner());
193+
return Err(e);
194+
}
195+
};
196+
break;
197+
}
198+
181199
let raw_status = head.subject;
182200
let headers = head.headers;
183201

184202
let method = self.method.take().unwrap_or(Method::Get);
203+
204+
let is_empty = !should_have_response_body(&method, raw_status.0);
205+
stream.get_mut().set_previous_response_expected_no_content(is_empty);
185206
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
186207
// 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body.
187208
// 2. Status 2xx to a CONNECT cannot have a body.
@@ -190,27 +211,24 @@ impl HttpMessage for Http11Message {
190211
// 5. Content-Length header has a sized body.
191212
// 6. Not Client.
192213
// 7. Read till EOF.
193-
self.reader = Some(match (method, raw_status.0) {
194-
(Method::Head, _) => EmptyReader(stream),
195-
(_, 100...199) | (_, 204) | (_, 304) => EmptyReader(stream),
196-
(Method::Connect, 200...299) => EmptyReader(stream),
197-
_ => {
198-
if let Some(&TransferEncoding(ref codings)) = headers.get() {
199-
if codings.last() == Some(&Chunked) {
200-
ChunkedReader(stream, None)
201-
} else {
202-
trace!("not chuncked. read till eof");
203-
EofReader(stream)
204-
}
205-
} else if let Some(&ContentLength(len)) = headers.get() {
206-
SizedReader(stream, len)
207-
} else if headers.has::<ContentLength>() {
208-
trace!("illegal Content-Length: {:?}", headers.get_raw("Content-Length"));
209-
return Err(Error::Header);
214+
self.reader = Some(if is_empty {
215+
EmptyReader(stream)
216+
} else {
217+
if let Some(&TransferEncoding(ref codings)) = headers.get() {
218+
if codings.last() == Some(&Chunked) {
219+
ChunkedReader(stream, None)
210220
} else {
211-
trace!("neither Transfer-Encoding nor Content-Length");
221+
trace!("not chuncked. read till eof");
212222
EofReader(stream)
213223
}
224+
} else if let Some(&ContentLength(len)) = headers.get() {
225+
SizedReader(stream, len)
226+
} else if headers.has::<ContentLength>() {
227+
trace!("illegal Content-Length: {:?}", headers.get_raw("Content-Length"));
228+
return Err(Error::Header);
229+
} else {
230+
trace!("neither Transfer-Encoding nor Content-Length");
231+
EofReader(stream)
214232
}
215233
});
216234

@@ -226,7 +244,9 @@ impl HttpMessage for Http11Message {
226244

227245
fn has_body(&self) -> bool {
228246
match self.reader {
229-
Some(EmptyReader(..)) => false,
247+
Some(EmptyReader(..)) |
248+
Some(SizedReader(_, 0)) |
249+
Some(ChunkedReader(_, Some(0))) => false,
230250
_ => true
231251
}
232252
}
@@ -597,6 +617,18 @@ fn read_chunk_size<R: Read>(rdr: &mut R) -> io::Result<u64> {
597617
Ok(size)
598618
}
599619

620+
fn should_have_response_body(method: &Method, status: u16) -> bool {
621+
trace!("should_have_response_body({:?}, {})", method, status);
622+
match (method, status) {
623+
(&Method::Head, _) |
624+
(_, 100...199) |
625+
(_, 204) |
626+
(_, 304) |
627+
(&Method::Connect, 200...299) => false,
628+
_ => true
629+
}
630+
}
631+
600632
/// Writers to handle different Transfer-Encodings.
601633
pub enum HttpWriter<W: Write> {
602634
/// A no-op Writer, used initially before Transfer-Encoding is determined.

0 commit comments

Comments
 (0)