Skip to content

Commit 31f117e

Browse files
committed
fix(client): improve HttpReader selection for client Responses
Closes #436
1 parent af062ac commit 31f117e

File tree

4 files changed

+123
-62
lines changed

4 files changed

+123
-62
lines changed

src/header/common/content_length.rs

+82-33
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,86 @@
1-
header! {
2-
#[doc="`Content-Length` header, defined in"]
3-
#[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)"]
4-
#[doc=""]
5-
#[doc="When a message does not have a `Transfer-Encoding` header field, a"]
6-
#[doc="Content-Length header field can provide the anticipated size, as a"]
7-
#[doc="decimal number of octets, for a potential payload body. For messages"]
8-
#[doc="that do include a payload body, the Content-Length field-value"]
9-
#[doc="provides the framing information necessary for determining where the"]
10-
#[doc="body (and message) ends. For messages that do not include a payload"]
11-
#[doc="body, the Content-Length indicates the size of the selected"]
12-
#[doc="representation."]
13-
#[doc=""]
14-
#[doc="# ABNF"]
15-
#[doc="```plain"]
16-
#[doc="Content-Length = 1*DIGIT"]
17-
#[doc="```"]
18-
#[doc=""]
19-
#[doc="# Example values"]
20-
#[doc="* `3495`"]
21-
#[doc=""]
22-
#[doc="# Example"]
23-
#[doc="```"]
24-
#[doc="use hyper::header::{Headers, ContentLength};"]
25-
#[doc=""]
26-
#[doc="let mut headers = Headers::new();"]
27-
#[doc="headers.set(ContentLength(1024u64));"]
28-
#[doc="```"]
29-
(ContentLength, "Content-Length") => [u64]
30-
31-
test_content_length {
32-
// Testcase from RFC
33-
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
1+
use std::fmt;
2+
3+
use header::{HeaderFormat, Header, parsing};
4+
5+
#[doc="`Content-Length` header, defined in"]
6+
#[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)"]
7+
#[doc=""]
8+
#[doc="When a message does not have a `Transfer-Encoding` header field, a"]
9+
#[doc="Content-Length header field can provide the anticipated size, as a"]
10+
#[doc="decimal number of octets, for a potential payload body. For messages"]
11+
#[doc="that do include a payload body, the Content-Length field-value"]
12+
#[doc="provides the framing information necessary for determining where the"]
13+
#[doc="body (and message) ends. For messages that do not include a payload"]
14+
#[doc="body, the Content-Length indicates the size of the selected"]
15+
#[doc="representation."]
16+
#[doc=""]
17+
#[doc="# ABNF"]
18+
#[doc="```plain"]
19+
#[doc="Content-Length = 1*DIGIT"]
20+
#[doc="```"]
21+
#[doc=""]
22+
#[doc="# Example values"]
23+
#[doc="* `3495`"]
24+
#[doc=""]
25+
#[doc="# Example"]
26+
#[doc="```"]
27+
#[doc="use hyper::header::{Headers, ContentLength};"]
28+
#[doc=""]
29+
#[doc="let mut headers = Headers::new();"]
30+
#[doc="headers.set(ContentLength(1024u64));"]
31+
#[doc="```"]
32+
#[derive(Clone, Copy, Debug, PartialEq)]
33+
pub struct ContentLength(pub u64);
34+
35+
impl Header for ContentLength {
36+
#[inline]
37+
fn header_name() -> &'static str {
38+
"Content-Length"
39+
}
40+
fn parse_header(raw: &[Vec<u8>]) -> ::Result<ContentLength> {
41+
// If multiple Content-Length headers were sent, everything can still
42+
// be alright if they all contain the same value, and all parse
43+
// correctly. If not, then it's an error.
44+
raw.iter()
45+
.map(::std::ops::Deref::deref)
46+
.map(parsing::from_raw_str)
47+
.fold(None, |prev, x| {
48+
match (prev, x) {
49+
(None, x) => Some(x),
50+
(e@Some(Err(_)), _ ) => e,
51+
(Some(Ok(prev)), Ok(x)) if prev == x => Some(Ok(prev)),
52+
_ => Some(Err(::Error::Header))
53+
}
54+
})
55+
.unwrap_or(Err(::Error::Header))
56+
.map(ContentLength)
57+
}
58+
}
59+
60+
impl HeaderFormat for ContentLength {
61+
#[inline]
62+
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
63+
fmt::Display::fmt(&self.0, f)
3464
}
3565
}
3666

67+
impl fmt::Display for ContentLength {
68+
#[inline]
69+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70+
fmt::Display::fmt(&self.0, f)
71+
}
72+
}
73+
74+
__hyper__deref!(ContentLength => u64);
75+
__hyper_generate_header_serialization!(ContentLength);
76+
77+
__hyper__tm!(ContentLength, tests {
78+
// Testcase from RFC
79+
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
80+
81+
test_header!(test_invalid, vec![b"34v95"], None);
82+
test_header!(test_duplicates, vec![b"5", b"5"], Some(HeaderField(5)));
83+
test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None);
84+
});
85+
3786
bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] });

src/header/common/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,13 @@ macro_rules! test_header {
148148
fn $id() {
149149
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
150150
let val = HeaderField::parse_header(&a[..]);
151+
let typed: Option<HeaderField> = $typed;
151152
// Test parsing
152-
assert_eq!(val.ok(), $typed);
153+
assert_eq!(val.ok(), typed);
153154
// Test formatting
154-
if $typed != None {
155+
if typed.is_some() {
155156
let res: &str = str::from_utf8($raw[0]).unwrap();
156-
assert_eq!(format!("{}", $typed.unwrap()), res);
157+
assert_eq!(format!("{}", typed.unwrap()), res);
157158
}
158159
}
159160
}

src/header/parsing.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
use std::str;
44
use std::fmt::{self, Display};
55

6-
/// Reads a single raw string when parsing a header
6+
/// Reads a single raw string when parsing a header.
77
pub fn from_one_raw_str<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<T> {
88
if raw.len() != 1 || unsafe { raw.get_unchecked(0) } == b"" { return Err(::Error::Header) }
99
// we JUST checked that raw.len() == 1, so raw[0] WILL exist.
10-
let s: &str = try!(str::from_utf8(& unsafe { raw.get_unchecked(0) }[..]));
11-
if let Ok(x) = str::FromStr::from_str(s) {
12-
Ok(x)
13-
} else {
14-
Err(::Error::Header)
15-
}
10+
from_raw_str(& unsafe { raw.get_unchecked(0) })
11+
}
12+
13+
/// Reads a raw string into a value.
14+
pub fn from_raw_str<T: str::FromStr>(raw: &[u8]) -> ::Result<T> {
15+
let s = try!(str::from_utf8(raw));
16+
T::from_str(s).or(Err(::Error::Header))
1617
}
1718

1819
/// Reads a comma-delimited raw header into a Vec.

src/http/h1.rs

+29-19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use version;
3636
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
3737
#[derive(Debug)]
3838
pub struct Http11Message {
39+
method: Option<Method>,
3940
stream: Option<Box<NetworkStream + Send>>,
4041
writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>,
4142
reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>,
@@ -91,8 +92,8 @@ impl HttpMessage for Http11Message {
9192
try!(write!(&mut stream, "{} {} {}{}",
9293
head.method, uri, version, LINE_ENDING));
9394

94-
let stream = match head.method {
95-
Method::Get | Method::Head => {
95+
let stream = match &head.method {
96+
&Method::Get | &Method::Head => {
9697
debug!("headers={:?}", head.headers);
9798
try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING));
9899
EmptyWriter(stream)
@@ -137,6 +138,7 @@ impl HttpMessage for Http11Message {
137138
}
138139
};
139140

141+
self.method = Some(head.method.clone());
140142
self.writer = Some(stream);
141143

142144
Ok(head)
@@ -159,30 +161,37 @@ impl HttpMessage for Http11Message {
159161
let raw_status = head.subject;
160162
let headers = head.headers;
161163

162-
let body = if headers.has::<TransferEncoding>() {
163-
match headers.get::<TransferEncoding>() {
164-
Some(&TransferEncoding(ref codings)) => {
165-
if codings.len() > 1 {
166-
trace!("TODO: #2 handle other codings: {:?}", codings);
167-
};
168-
169-
if codings.contains(&Chunked) {
164+
let method = self.method.take().unwrap_or(Method::Get);
165+
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
166+
// 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body.
167+
// 2. Status 2xx to a CONNECT cannot have a body.
168+
// 3. Transfer-Encoding: chunked has a chunked body.
169+
// 4. If multiple differing Content-Length headers or invalid, close connection.
170+
// 5. Content-Length header has a sized body.
171+
// 6. Not Client.
172+
// 7. Read till EOF.
173+
let body = match (method, raw_status.0) {
174+
(Method::Head, _) => EmptyReader(stream),
175+
(_, 100...199) | (_, 204) | (_, 304) => EmptyReader(stream),
176+
(Method::Connect, 200...299) => EmptyReader(stream),
177+
_ => {
178+
if let Some(&TransferEncoding(ref codings)) = headers.get() {
179+
if codings.last() == Some(&Chunked) {
170180
ChunkedReader(stream, None)
171181
} else {
172182
trace!("not chuncked. read till eof");
173183
EofReader(stream)
174184
}
185+
} else if let Some(&ContentLength(len)) = headers.get() {
186+
SizedReader(stream, len)
187+
} else if headers.has::<ContentLength>() {
188+
trace!("illegal Content-Length: {:?}", headers.get_raw("Content-Length"));
189+
return Err(Error::Header);
190+
} else {
191+
trace!("neither Transfer-Encoding nor Content-Length");
192+
EofReader(stream)
175193
}
176-
None => unreachable!()
177-
}
178-
} else if headers.has::<ContentLength>() {
179-
match headers.get::<ContentLength>() {
180-
Some(&ContentLength(len)) => SizedReader(stream, len),
181-
None => unreachable!()
182194
}
183-
} else {
184-
trace!("neither Transfer-Encoding nor Content-Length");
185-
EofReader(stream)
186195
};
187196

188197
self.reader = Some(body);
@@ -259,6 +268,7 @@ impl Http11Message {
259268
/// the peer.
260269
pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message {
261270
Http11Message {
271+
method: None,
262272
stream: Some(stream),
263273
writer: None,
264274
reader: None,

0 commit comments

Comments
 (0)