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())); | ||||||
|     // we JUST checked that raw.len() == 1, so raw[0] WILL exist. |         result.extend(s.split(',') | ||||||
|     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() { |                       .filter_map(|x| match x.trim() { | ||||||
|                           "" => None, |                           "" => None, | ||||||
|                           y => Some(y) |                           y => Some(y) | ||||||
|                       }) |                       }) | ||||||
|         .filter_map(|x| x.parse().ok()) |                       .filter_map(|x| x.parse().ok())) | ||||||
|         .collect()) |     } | ||||||
|  |     Ok(result) | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Format an array into a comma-delimited string. | /// Format an array into a comma-delimited string. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user