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 #683
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use header::{Header, HeaderFormat};
|
||||
use header::parsing::{from_one_comma_delimited, fmt_comma_delimited};
|
||||
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||
|
||||
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
|
||||
///
|
||||
@@ -55,10 +55,7 @@ impl Header for CacheControl {
|
||||
}
|
||||
|
||||
fn parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl> {
|
||||
let directives = raw.iter()
|
||||
.filter_map(|line| from_one_comma_delimited(&line[..]).ok())
|
||||
.collect::<Vec<Vec<CacheDirective>>>()
|
||||
.concat();
|
||||
let directives = try!(from_comma_delimited(raw));
|
||||
if !directives.is_empty() {
|
||||
Ok(CacheControl(directives))
|
||||
} else {
|
||||
|
||||
@@ -79,7 +79,16 @@ __hyper__tm!(ContentLength, tests {
|
||||
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
|
||||
|
||||
test_header!(test_invalid, vec![b"34v95"], None);
|
||||
test_header!(test_duplicates, vec![b"5", b"5"], Some(HeaderField(5)));
|
||||
|
||||
// Can't use the test_header macro because "5, 5" gets cleaned to "5".
|
||||
#[test]
|
||||
fn test_duplicates() {
|
||||
let parsed = HeaderField::parse_header(&[b"5"[..].into(),
|
||||
b"5"[..].into()]).unwrap();
|
||||
assert_eq!(parsed, HeaderField(5));
|
||||
assert_eq!(format!("{}", parsed), "5");
|
||||
}
|
||||
|
||||
test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None);
|
||||
});
|
||||
|
||||
|
||||
@@ -156,8 +156,15 @@ macro_rules! test_header {
|
||||
assert_eq!(val.ok(), typed);
|
||||
// Test formatting
|
||||
if typed.is_some() {
|
||||
let res: &str = str::from_utf8($raw[0]).unwrap();
|
||||
assert_eq!(format!("{}", typed.unwrap()), res);
|
||||
let raw = &($raw)[..];
|
||||
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
|
||||
let mut joined = String::new();
|
||||
joined.push_str(iter.next().unwrap());
|
||||
for s in iter {
|
||||
joined.push_str(", ");
|
||||
joined.push_str(s);
|
||||
}
|
||||
assert_eq!(format!("{}", typed.unwrap()), joined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
use header::{Header, HeaderFormat};
|
||||
use header::parsing::{from_one_raw_str, from_one_comma_delimited};
|
||||
use header::parsing::{from_one_raw_str, from_comma_delimited};
|
||||
|
||||
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
|
||||
///
|
||||
@@ -130,7 +130,7 @@ impl FromStr for Range {
|
||||
|
||||
match (iter.next(), iter.next()) {
|
||||
(Some("bytes"), Some(ranges)) => {
|
||||
match from_one_comma_delimited(ranges.as_bytes()) {
|
||||
match from_comma_delimited(&[ranges]) {
|
||||
Ok(ranges) => {
|
||||
if ranges.is_empty() {
|
||||
return Err(::Error::Header);
|
||||
|
||||
@@ -38,6 +38,13 @@ header! {
|
||||
Some(HeaderField(
|
||||
vec![Encoding::Gzip, Encoding::Chunked]
|
||||
)));
|
||||
// Issue: #683
|
||||
test_header!(
|
||||
test2,
|
||||
vec![b"chunked", b"chunked"],
|
||||
Some(HeaderField(
|
||||
vec![Encoding::Chunked, Encoding::Chunked]
|
||||
)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,24 +23,18 @@ pub fn from_raw_str<T: str::FromStr>(raw: &[u8]) -> ::Result<T> {
|
||||
|
||||
/// Reads a comma-delimited raw header into a Vec.
|
||||
#[inline]
|
||||
pub fn from_comma_delimited<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<Vec<T>> {
|
||||
if raw.len() != 1 {
|
||||
return Err(::Error::Header);
|
||||
pub fn from_comma_delimited<T: str::FromStr, S: AsRef<[u8]>>(raw: &[S]) -> ::Result<Vec<T>> {
|
||||
let mut result = Vec::new();
|
||||
for s in raw {
|
||||
let s = try!(str::from_utf8(s.as_ref()));
|
||||
result.extend(s.split(',')
|
||||
.filter_map(|x| match x.trim() {
|
||||
"" => None,
|
||||
y => Some(y)
|
||||
})
|
||||
.filter_map(|x| x.parse().ok()))
|
||||
}
|
||||
// we JUST checked that raw.len() == 1, so raw[0] WILL exist.
|
||||
from_one_comma_delimited(& unsafe { raw.get_unchecked(0) }[..])
|
||||
}
|
||||
|
||||
/// Reads a comma-delimited raw string into a Vec.
|
||||
pub fn from_one_comma_delimited<T: str::FromStr>(raw: &[u8]) -> ::Result<Vec<T>> {
|
||||
let s = try!(str::from_utf8(raw));
|
||||
Ok(s.split(',')
|
||||
.filter_map(|x| match x.trim() {
|
||||
"" => None,
|
||||
y => Some(y)
|
||||
})
|
||||
.filter_map(|x| x.parse().ok())
|
||||
.collect())
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Format an array into a comma-delimited string.
|
||||
|
||||
Reference in New Issue
Block a user