Skip to content

Commit ca8958f

Browse files
committed
fix(headers): correctly handle repeated headers
HeaderX: a HeaderX: b MUST be interpreted as HeaderX: a, b See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 Fixes hyperium#683
1 parent 146df53 commit ca8958f

File tree

6 files changed

+31
-27
lines changed

6 files changed

+31
-27
lines changed

src/header/common/cache_control.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt;
22
use std::str::FromStr;
33
use header::{Header, HeaderFormat};
4-
use header::parsing::{from_one_comma_delimited, fmt_comma_delimited};
4+
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
55

66
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
77
///
@@ -55,10 +55,7 @@ impl Header for CacheControl {
5555
}
5656

5757
fn parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl> {
58-
let directives = raw.iter()
59-
.filter_map(|line| from_one_comma_delimited(&line[..]).ok())
60-
.collect::<Vec<Vec<CacheDirective>>>()
61-
.concat();
58+
let directives = try!(from_comma_delimited(raw));
6259
if !directives.is_empty() {
6360
Ok(CacheControl(directives))
6461
} else {

src/header/common/content_length.rs

-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ __hyper__tm!(ContentLength, tests {
7979
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
8080

8181
test_header!(test_invalid, vec![b"34v95"], None);
82-
test_header!(test_duplicates, vec![b"5", b"5"], Some(HeaderField(5)));
8382
test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None);
8483
});
8584

src/header/common/mod.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,15 @@ macro_rules! test_header {
156156
assert_eq!(val.ok(), typed);
157157
// Test formatting
158158
if typed.is_some() {
159-
let res: &str = str::from_utf8($raw[0]).unwrap();
160-
assert_eq!(format!("{}", typed.unwrap()), res);
159+
let raw = &($raw)[..];
160+
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
161+
let mut joined = String::new();
162+
joined.push_str(iter.next().unwrap());
163+
for s in iter {
164+
joined.push_str(", ");
165+
joined.push_str(s);
166+
}
167+
assert_eq!(format!("{}", typed.unwrap()), joined);
161168
}
162169
}
163170
}

src/header/common/range.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fmt::{self, Display};
22
use std::str::FromStr;
33

44
use header::{Header, HeaderFormat};
5-
use header::parsing::{from_one_raw_str, from_one_comma_delimited};
5+
use header::parsing::{from_one_raw_str, from_comma_delimited};
66

77
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
88
///
@@ -130,7 +130,7 @@ impl FromStr for Range {
130130

131131
match (iter.next(), iter.next()) {
132132
(Some("bytes"), Some(ranges)) => {
133-
match from_one_comma_delimited(ranges.as_bytes()) {
133+
match from_comma_delimited(&[ranges]) {
134134
Ok(ranges) => {
135135
if ranges.is_empty() {
136136
return Err(::Error::Header);

src/header/common/transfer_encoding.rs

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ header! {
3838
Some(HeaderField(
3939
vec![Encoding::Gzip, Encoding::Chunked]
4040
)));
41+
// Issue: #683
42+
test_header!(
43+
test2,
44+
vec![b"chunked", b"chunked"],
45+
Some(HeaderField(
46+
vec![Encoding::Chunked, Encoding::Chunked]
47+
)));
4148

4249
}
4350
}

src/header/parsing.rs

+11-17
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,18 @@ pub fn from_raw_str<T: str::FromStr>(raw: &[u8]) -> ::Result<T> {
2323

2424
/// Reads a comma-delimited raw header into a Vec.
2525
#[inline]
26-
pub fn from_comma_delimited<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<Vec<T>> {
27-
if raw.len() != 1 {
28-
return Err(::Error::Header);
26+
pub fn from_comma_delimited<T: str::FromStr, S: AsRef<[u8]>>(raw: &[S]) -> ::Result<Vec<T>> {
27+
let mut result = Vec::new();
28+
for s in raw {
29+
let s = try!(str::from_utf8(s.as_ref()));
30+
result.extend(s.split(',')
31+
.filter_map(|x| match x.trim() {
32+
"" => None,
33+
y => Some(y)
34+
})
35+
.filter_map(|x| x.parse().ok()))
2936
}
30-
// we JUST checked that raw.len() == 1, so raw[0] WILL exist.
31-
from_one_comma_delimited(& unsafe { raw.get_unchecked(0) }[..])
32-
}
33-
34-
/// Reads a comma-delimited raw string into a Vec.
35-
pub fn from_one_comma_delimited<T: str::FromStr>(raw: &[u8]) -> ::Result<Vec<T>> {
36-
let s = try!(str::from_utf8(raw));
37-
Ok(s.split(',')
38-
.filter_map(|x| match x.trim() {
39-
"" => None,
40-
y => Some(y)
41-
})
42-
.filter_map(|x| x.parse().ok())
43-
.collect())
37+
Ok(result)
4438
}
4539

4640
/// Format an array into a comma-delimited string.

0 commit comments

Comments
 (0)