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::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use header::{Header, HeaderFormat};
|
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)
|
/// `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> {
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl> {
|
||||||
let directives = raw.iter()
|
let directives = try!(from_comma_delimited(raw));
|
||||||
.filter_map(|line| from_one_comma_delimited(&line[..]).ok())
|
|
||||||
.collect::<Vec<Vec<CacheDirective>>>()
|
|
||||||
.concat();
|
|
||||||
if !directives.is_empty() {
|
if !directives.is_empty() {
|
||||||
Ok(CacheControl(directives))
|
Ok(CacheControl(directives))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -79,7 +79,16 @@ __hyper__tm!(ContentLength, tests {
|
|||||||
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
|
test_header!(test1, vec![b"3495"], Some(HeaderField(3495)));
|
||||||
|
|
||||||
test_header!(test_invalid, vec![b"34v95"], None);
|
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);
|
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);
|
assert_eq!(val.ok(), typed);
|
||||||
// Test formatting
|
// Test formatting
|
||||||
if typed.is_some() {
|
if typed.is_some() {
|
||||||
let res: &str = str::from_utf8($raw[0]).unwrap();
|
let raw = &($raw)[..];
|
||||||
assert_eq!(format!("{}", typed.unwrap()), res);
|
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 std::str::FromStr;
|
||||||
|
|
||||||
use header::{Header, HeaderFormat};
|
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)
|
/// `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()) {
|
match (iter.next(), iter.next()) {
|
||||||
(Some("bytes"), Some(ranges)) => {
|
(Some("bytes"), Some(ranges)) => {
|
||||||
match from_one_comma_delimited(ranges.as_bytes()) {
|
match from_comma_delimited(&[ranges]) {
|
||||||
Ok(ranges) => {
|
Ok(ranges) => {
|
||||||
if ranges.is_empty() {
|
if ranges.is_empty() {
|
||||||
return Err(::Error::Header);
|
return Err(::Error::Header);
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ header! {
|
|||||||
Some(HeaderField(
|
Some(HeaderField(
|
||||||
vec![Encoding::Gzip, Encoding::Chunked]
|
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.
|
/// Reads a comma-delimited raw header into a Vec.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_comma_delimited<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<Vec<T>> {
|
pub fn from_comma_delimited<T: str::FromStr, S: AsRef<[u8]>>(raw: &[S]) -> ::Result<Vec<T>> {
|
||||||
if raw.len() != 1 {
|
let mut result = Vec::new();
|
||||||
return Err(::Error::Header);
|
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.
|
Ok(result)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format an array into a comma-delimited string.
|
/// Format an array into a comma-delimited string.
|
||||||
|
|||||||
Reference in New Issue
Block a user