From 70c6914217a9b48880e61b7fb59acd15c6e1421e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Mar 2016 15:03:35 -0500 Subject: [PATCH] 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 --- src/header/common/cache_control.rs | 7 ++----- src/header/common/content_length.rs | 11 +++++++++- src/header/common/mod.rs | 11 ++++++++-- src/header/common/range.rs | 4 ++-- src/header/common/transfer_encoding.rs | 7 +++++++ src/header/parsing.rs | 28 ++++++++++---------------- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index 68e8546f13..0629aea2e0 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -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]) -> ::Result { - let directives = raw.iter() - .filter_map(|line| from_one_comma_delimited(&line[..]).ok()) - .collect::>>() - .concat(); + let directives = try!(from_comma_delimited(raw)); if !directives.is_empty() { Ok(CacheControl(directives)) } else { diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs index 0f74d861c4..8c9707aeaa 100644 --- a/src/header/common/content_length.rs +++ b/src/header/common/content_length.rs @@ -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); }); diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index 090f02370f..56f82603d3 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -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); } } } diff --git a/src/header/common/range.rs b/src/header/common/range.rs index 8b09860b32..49c2d4b522 100644 --- a/src/header/common/range.rs +++ b/src/header/common/range.rs @@ -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); diff --git a/src/header/common/transfer_encoding.rs b/src/header/common/transfer_encoding.rs index c8eaa7bdad..13ae54a34c 100644 --- a/src/header/common/transfer_encoding.rs +++ b/src/header/common/transfer_encoding.rs @@ -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] + ))); } } diff --git a/src/header/parsing.rs b/src/header/parsing.rs index 49029b58eb..bbb158b0eb 100644 --- a/src/header/parsing.rs +++ b/src/header/parsing.rs @@ -23,24 +23,18 @@ pub fn from_raw_str(raw: &[u8]) -> ::Result { /// Reads a comma-delimited raw header into a Vec. #[inline] -pub fn from_comma_delimited(raw: &[Vec]) -> ::Result> { - if raw.len() != 1 { - return Err(::Error::Header); +pub fn from_comma_delimited>(raw: &[S]) -> ::Result> { + 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(raw: &[u8]) -> ::Result> { - 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.