From 50ccdaa7e7db574ec9890c220765ffd2da5e493b Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Sat, 23 Jul 2016 12:54:16 -0700 Subject: [PATCH] feat(header): introduce header::Raw (#869) The Raw type repesents the raw bytes of a header-value. Having a special type allows a couple of benefits: - The exact representation has become private, allowing "uglier" internals. Specifically, since the common case is for a header to only have 1 line of bytes, an enum is used to skip allocating a Vec for only 1 line. Additionally, a Cow<'static, [u8]> is used, so static bytes don't require a copy. Finally, since we can use static bytes, when parsing, we can compare the incoming bytes against a couple of the most common header-values, and possibly remove another copy. - As its own type, the `Headers.set_raw` method can be generic over `Into`, which allows for more ergnomic method calls. BREAKING CHANGE: `Header::parse_header` now receives `&Raw`, instead of a `&[Vec]`. `Raw` provides several methods to ease using it, but may require some changes to existing code. --- .../access_control_allow_credentials.rs | 11 +- .../common/access_control_allow_origin.rs | 21 +- src/header/common/authorization.rs | 40 +-- src/header/common/cache_control.rs | 14 +- src/header/common/connection.rs | 4 +- src/header/common/content_disposition.rs | 32 +-- src/header/common/content_length.rs | 9 +- src/header/common/cookie.rs | 6 +- src/header/common/expect.rs | 11 +- src/header/common/host.rs | 8 +- src/header/common/if_none_match.rs | 4 +- src/header/common/if_range.rs | 4 +- src/header/common/mod.rs | 28 ++- src/header/common/origin.rs | 8 +- src/header/common/pragma.rs | 10 +- src/header/common/prefer.rs | 14 +- src/header/common/preference_applied.rs | 4 +- src/header/common/range.rs | 62 ++--- src/header/common/referrer_policy.rs | 8 +- src/header/common/set_cookie.rs | 6 +- .../common/strict_transport_security.rs | 20 +- src/header/common/upgrade.rs | 2 +- src/header/common/vary.rs | 4 +- src/header/internals/item.rs | 23 +- src/header/mod.rs | 138 ++++++----- src/header/parsing.rs | 14 +- src/header/raw.rs | 229 ++++++++++++++++++ 27 files changed, 489 insertions(+), 245 deletions(-) create mode 100644 src/header/raw.rs diff --git a/src/header/common/access_control_allow_credentials.rs b/src/header/common/access_control_allow_credentials.rs index 34c12cf3f3..2ea6666039 100644 --- a/src/header/common/access_control_allow_credentials.rs +++ b/src/header/common/access_control_allow_credentials.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display}; use std::str; use unicase::UniCase; -use header::{Header}; +use header::{Header, Raw}; /// `Access-Control-Allow-Credentials` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) @@ -46,16 +46,15 @@ impl Header for AccessControlAllowCredentials { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { - if raw.len() == 1 { + fn parse_header(raw: &Raw) -> ::Result { + if let Some(line) = raw.one() { let text = unsafe { // safe because: - // 1. we just checked raw.len == 1 - // 2. we don't actually care if it's utf8, we just want to + // 1. we don't actually care if it's utf8, we just want to // compare the bytes with the "case" normalized. If it's not // utf8, then the byte comparison will fail, and we'll return // None. No big deal. - str::from_utf8_unchecked(raw.get_unchecked(0)) + str::from_utf8_unchecked(line) }; if UniCase(text) == ACCESS_CONTROL_ALLOW_CREDENTIALS_TRUE { return Ok(AccessControlAllowCredentials); diff --git a/src/header/common/access_control_allow_origin.rs b/src/header/common/access_control_allow_origin.rs index 418e9c8cda..0de165927f 100644 --- a/src/header/common/access_control_allow_origin.rs +++ b/src/header/common/access_control_allow_origin.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Display}; +use std::str; -use header::{Header}; +use header::{Header, Raw}; /// The `Access-Control-Allow-Origin` response header, /// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) @@ -59,16 +60,16 @@ impl Header for AccessControlAllowOrigin { "Access-Control-Allow-Origin" } - fn parse_header(raw: &[Vec]) -> ::Result { - if raw.len() != 1 { - return Err(::Error::Header) + fn parse_header(raw: &Raw) -> ::Result { + if let Some(line) = raw.one() { + Ok(match line { + b"*" => AccessControlAllowOrigin::Any, + b"null" => AccessControlAllowOrigin::Null, + _ => AccessControlAllowOrigin::Value(try!(str::from_utf8(line)).into()) + }) + } else { + Err(::Error::Header) } - let value = unsafe { raw.get_unchecked(0) }; - Ok(match &value[..] { - b"*" => AccessControlAllowOrigin::Any, - b"null" => AccessControlAllowOrigin::Null, - _ => AccessControlAllowOrigin::Value(try!(String::from_utf8(value.clone()))) - }) } fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/header/common/authorization.rs b/src/header/common/authorization.rs index 02c75d1a50..c5c874f6b7 100644 --- a/src/header/common/authorization.rs +++ b/src/header/common/authorization.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Display}; use std::str::{FromStr, from_utf8}; use std::ops::{Deref, DerefMut}; use serialize::base64::{ToBase64, FromBase64, Standard, Config, Newline}; -use header::{Header}; +use header::{Header, Raw}; /// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2) /// @@ -77,25 +77,26 @@ impl Header for Authorization where ::Err: 'st NAME } - fn parse_header(raw: &[Vec]) -> ::Result> { - if raw.len() != 1 { - return Err(::Error::Header); - } - let header = try!(from_utf8(unsafe { &raw.get_unchecked(0)[..] })); - if let Some(scheme) = ::scheme() { - if header.starts_with(scheme) && header.len() > scheme.len() + 1 { - match header[scheme.len() + 1..].parse::().map(Authorization) { + fn parse_header(raw: &Raw) -> ::Result> { + if let Some(line) = raw.one() { + let header = try!(from_utf8(line)); + if let Some(scheme) = ::scheme() { + if header.starts_with(scheme) && header.len() > scheme.len() + 1 { + match header[scheme.len() + 1..].parse::().map(Authorization) { + Ok(h) => Ok(h), + Err(_) => Err(::Error::Header) + } + } else { + Err(::Error::Header) + } + } else { + match header.parse::().map(Authorization) { Ok(h) => Ok(h), Err(_) => Err(::Error::Header) } - } else { - Err(::Error::Header) } } else { - match header.parse::().map(Authorization) { - Ok(h) => Ok(h), - Err(_) => Err(::Error::Header) - } + Err(::Error::Header) } } @@ -231,8 +232,7 @@ mod tests { #[test] fn test_raw_auth_parse() { - let header: Authorization = Header::parse_header( - &[b"foo bar baz".to_vec()]).unwrap(); + let header: Authorization = Header::parse_header(&b"foo bar baz".as_ref().into()).unwrap(); assert_eq!(header.0, "foo bar baz"); } @@ -256,7 +256,7 @@ mod tests { #[test] fn test_basic_auth_parse() { let auth: Authorization = Header::parse_header( - &[b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_vec()]).unwrap(); + &b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".as_ref().into()).unwrap(); assert_eq!(auth.0.username, "Aladdin"); assert_eq!(auth.0.password, Some("open sesame".to_owned())); } @@ -264,7 +264,7 @@ mod tests { #[test] fn test_basic_auth_parse_no_password() { let auth: Authorization = Header::parse_header( - &[b"Basic QWxhZGRpbjo=".to_vec()]).unwrap(); + &b"Basic QWxhZGRpbjo=".as_ref().into()).unwrap(); assert_eq!(auth.0.username, "Aladdin"); assert_eq!(auth.0.password, Some("".to_owned())); } @@ -282,7 +282,7 @@ mod tests { #[test] fn test_bearer_auth_parse() { let auth: Authorization = Header::parse_header( - &[b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()]).unwrap(); + &b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into()).unwrap(); assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG"); } } diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index 438a390330..d79f20f600 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -1,6 +1,6 @@ use std::fmt; use std::str::FromStr; -use header::Header; +use header::{Header, Raw}; 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,7 +55,7 @@ impl Header for CacheControl { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let directives = try!(from_comma_delimited(raw)); if !directives.is_empty() { Ok(CacheControl(directives)) @@ -167,27 +167,27 @@ mod tests { #[test] fn test_parse_multiple_headers() { - let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]); + let cache = Header::parse_header(&vec![b"no-cache".to_vec(), b"private".to_vec()].into()); assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache, CacheDirective::Private]))) } #[test] fn test_parse_argument() { - let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]); + let cache = Header::parse_header(&vec![b"max-age=100, private".to_vec()].into()); assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100), CacheDirective::Private]))) } #[test] fn test_parse_quote_form() { - let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]); + let cache = Header::parse_header(&vec![b"max-age=\"200\"".to_vec()].into()); assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) } #[test] fn test_parse_extension() { - let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]); + let cache = Header::parse_header(&vec![b"foo, bar=baz".to_vec()].into()); assert_eq!(cache.ok(), Some(CacheControl(vec![ CacheDirective::Extension("foo".to_owned(), None), CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))]))) @@ -195,7 +195,7 @@ mod tests { #[test] fn test_parse_bad_syntax() { - let cache: ::Result = Header::parse_header(&[b"foo=".to_vec()]); + let cache: ::Result = Header::parse_header(&vec![b"foo=".to_vec()].into()); assert_eq!(cache.ok(), None) } } diff --git a/src/header/common/connection.rs b/src/header/common/connection.rs index 5e868333e0..a48594a30a 100644 --- a/src/header/common/connection.rs +++ b/src/header/common/connection.rs @@ -127,8 +127,8 @@ mod tests { use unicase::UniCase; fn parse_option(header: Vec) -> Connection { - let val = vec![header]; - let connection: Connection = Header::parse_header(&val[..]).unwrap(); + let val = header.into(); + let connection: Connection = Header::parse_header(&val).unwrap(); connection } diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 5b49ac269c..ca3abb2333 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -11,7 +11,7 @@ use std::fmt; use unicase::UniCase; use url::percent_encoding; -use header::{Header, parsing}; +use header::{Header, Raw, parsing}; use header::parsing::{parse_extended_value, HTTP_VALUE}; use header::shared::Charset; @@ -94,7 +94,7 @@ impl Header for ContentDisposition { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { parsing::from_one_raw_str(raw).and_then(|s: String| { let mut sections = s.split(';'); let disposition = match sections.next() { @@ -201,10 +201,10 @@ mod tests { #[test] fn test_parse_header() { - assert!(ContentDisposition::parse_header([b"".to_vec()].as_ref()).is_err()); + assert!(ContentDisposition::parse_header(&"".into()).is_err()); - let a = [b"form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".to_vec()]; - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext("form-data".to_owned()), parameters: vec![ @@ -217,8 +217,8 @@ mod tests { }; assert_eq!(a, b); - let a = [b"attachment; filename=\"image.jpg\"".to_vec()]; - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let a = "attachment; filename=\"image.jpg\"".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -229,8 +229,8 @@ mod tests { }; assert_eq!(a, b); - let a = [b"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".to_vec()]; - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -245,19 +245,19 @@ mod tests { #[test] fn test_display() { - let a = [b"attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates".to_vec()]; - let as_string = ::std::str::from_utf8(&(a[0])).unwrap(); - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let a = as_string.into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!(as_string, display_rendered); - let a = [b"attachment; filename*=UTF-8''black%20and%20white.csv".to_vec()]; - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); - let a = [b"attachment; filename=colourful.csv".to_vec()]; - let a: ContentDisposition = ContentDisposition::parse_header(a.as_ref()).unwrap(); + let a = "attachment; filename=colourful.csv".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); } diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs index c3932cb5f7..3d7328518a 100644 --- a/src/header/common/content_length.rs +++ b/src/header/common/content_length.rs @@ -1,6 +1,6 @@ use std::fmt; -use header::{Header, parsing}; +use header::{Header, Raw, parsing}; /// `Content-Length` header, defined in /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2) @@ -40,12 +40,11 @@ impl Header for ContentLength { static NAME: &'static str = "Content-Length"; NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { // If multiple Content-Length headers were sent, everything can still // be alright if they all contain the same value, and all parse // correctly. If not, then it's an error. raw.iter() - .map(::std::ops::Deref::deref) .map(parsing::from_raw_str) .fold(None, |prev, x| { match (prev, x) { @@ -84,8 +83,8 @@ __hyper__tm!(ContentLength, tests { // 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(); + let parsed = HeaderField::parse_header(&vec![b"5".to_vec(), + b"5".to_vec()].into()).unwrap(); assert_eq!(parsed, HeaderField(5)); assert_eq!(format!("{}", parsed), "5"); } diff --git a/src/header/common/cookie.rs b/src/header/common/cookie.rs index b404d8e81a..bb909db7ee 100644 --- a/src/header/common/cookie.rs +++ b/src/header/common/cookie.rs @@ -1,4 +1,4 @@ -use header::{Header, CookiePair, CookieJar}; +use header::{Header, Raw, CookiePair, CookieJar}; use std::fmt::{self, Display}; use std::str::from_utf8; @@ -43,7 +43,7 @@ impl Header for Cookie { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let mut cookies = Vec::with_capacity(raw.len()); for cookies_raw in raw.iter() { let cookies_str = try!(from_utf8(&cookies_raw[..])); @@ -96,7 +96,7 @@ impl Cookie { #[test] fn test_parse() { - let h = Header::parse_header(&[b"foo=bar; baz=quux".to_vec()][..]); + let h = Header::parse_header(&b"foo=bar; baz=quux".as_ref().into()); let c1 = CookiePair::new("foo".to_owned(), "bar".to_owned()); let c2 = CookiePair::new("baz".to_owned(), "quux".to_owned()); assert_eq!(h.ok(), Some(Cookie(vec![c1, c2]))); diff --git a/src/header/common/expect.rs b/src/header/common/expect.rs index 9f27b1b7d2..f80ede3190 100644 --- a/src/header/common/expect.rs +++ b/src/header/common/expect.rs @@ -3,7 +3,7 @@ use std::str; use unicase::UniCase; -use header::{Header}; +use header::{Header, Raw}; /// The `Expect` header. /// @@ -34,16 +34,15 @@ impl Header for Expect { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { - if raw.len() == 1 { + fn parse_header(raw: &Raw) -> ::Result { + if let Some(line) = raw.one() { let text = unsafe { // safe because: - // 1. we just checked raw.len == 1 - // 2. we don't actually care if it's utf8, we just want to + // 1. we don't actually care if it's utf8, we just want to // compare the bytes with the "case" normalized. If it's not // utf8, then the byte comparison will fail, and we'll return // None. No big deal. - str::from_utf8_unchecked(raw.get_unchecked(0)) + str::from_utf8_unchecked(line) }; if UniCase(text) == EXPECT_CONTINUE { Ok(Expect::Continue) diff --git a/src/header/common/host.rs b/src/header/common/host.rs index 2d1f7e78e4..e1be6793e2 100644 --- a/src/header/common/host.rs +++ b/src/header/common/host.rs @@ -1,4 +1,4 @@ -use header::{Header}; +use header::{Header, Raw}; use std::fmt; use std::str::FromStr; use header::parsing::from_one_raw_str; @@ -49,7 +49,7 @@ impl Header for Host { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { from_one_raw_str(raw) } @@ -98,14 +98,14 @@ mod tests { #[test] fn test_host() { - let host = Header::parse_header([b"foo.com".to_vec()].as_ref()); + let host = Header::parse_header(&vec![b"foo.com".to_vec()].into()); assert_eq!(host.ok(), Some(Host { hostname: "foo.com".to_owned(), port: None })); - let host = Header::parse_header([b"foo.com:8080".to_vec()].as_ref()); + let host = Header::parse_header(&vec![b"foo.com:8080".to_vec()].into()); assert_eq!(host.ok(), Some(Host { hostname: "foo.com".to_owned(), port: Some(8080) diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs index 734845df48..a2a37d6f3b 100644 --- a/src/header/common/if_none_match.rs +++ b/src/header/common/if_none_match.rs @@ -67,10 +67,10 @@ mod tests { fn test_if_none_match() { let mut if_none_match: ::Result; - if_none_match = Header::parse_header([b"*".to_vec()].as_ref()); + if_none_match = Header::parse_header(&b"*".as_ref().into()); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - if_none_match = Header::parse_header([b"\"foobar\", W/\"weak-etag\"".to_vec()].as_ref()); + if_none_match = Header::parse_header(&b"\"foobar\", W/\"weak-etag\"".as_ref().into()); let mut entities: Vec = Vec::new(); let foobar_etag = EntityTag::new(false, "foobar".to_owned()); let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs index 5b14e5d7b1..29ce248df3 100644 --- a/src/header/common/if_range.rs +++ b/src/header/common/if_range.rs @@ -1,5 +1,5 @@ use std::fmt::{self, Display}; -use header::{self, Header, EntityTag, HttpDate}; +use header::{self, Header, Raw, EntityTag, HttpDate}; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// @@ -58,7 +58,7 @@ impl Header for IfRange { static NAME: &'static str = "If-Range"; NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let etag: ::Result = header::parsing::from_one_raw_str(raw); if let Ok(etag) = etag { return Ok(IfRange::EntityTag(etag)); diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index d621e6072d..8d7d6ff427 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -72,15 +72,16 @@ macro_rules! bench_header( #[bench] fn bench_parse(b: &mut Bencher) { - let val = $value; + let val = $value.into(); b.iter(|| { - let _: $ty = Header::parse_header(&val[..]).unwrap(); + let _: $ty = Header::parse_header(&val).unwrap(); }); } #[bench] fn bench_format(b: &mut Bencher) { - let val: $ty = Header::parse_header(&$value[..]).unwrap(); + let raw = $value.into(); + let val: $ty = Header::parse_header(&raw).unwrap(); let fmt = ::header::HeaderFormatter(&val); b.iter(|| { format!("{}", fmt); @@ -137,7 +138,8 @@ macro_rules! test_header { use std::ascii::AsciiExt; let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let value = HeaderField::parse_header(&a[..]); + let a = a.into(); + let value = HeaderField::parse_header(&a); let result = format!("{}", value.unwrap()); let expected = String::from_utf8(raw[0].to_vec()).unwrap(); let result_cmp: Vec = result @@ -157,7 +159,8 @@ macro_rules! test_header { #[test] fn $id() { let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let val = HeaderField::parse_header(&a[..]); + let a = a.into(); + let val = HeaderField::parse_header(&a); let typed: Option = $typed; // Test parsing assert_eq!(val.ok(), typed); @@ -195,9 +198,8 @@ macro_rules! __hyper_generate_header_serialization { where D: ::serde::Deserializer { let string_representation: String = try!(::serde::Deserialize::deserialize(deserializer)); - Ok($crate::header::Header::parse_header(&[ - string_representation.into_bytes() - ]).unwrap()) + let raw = string_representation.into_bytes().into(); + Ok($crate::header::Header::parse_header(&raw).unwrap()) } } } @@ -221,7 +223,7 @@ macro_rules! header { static NAME: &'static str = $n; NAME } - fn parse_header(raw: &[Vec]) -> $crate::Result { + fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { $crate::header::parsing::from_comma_delimited(raw).map($id) } fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { @@ -248,7 +250,7 @@ macro_rules! header { static NAME: &'static str = $n; NAME } - fn parse_header(raw: &[Vec]) -> $crate::Result { + fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { $crate::header::parsing::from_comma_delimited(raw).map($id) } fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { @@ -274,7 +276,7 @@ macro_rules! header { static NAME: &'static str = $n; NAME } - fn parse_header(raw: &[Vec]) -> $crate::Result { + fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { $crate::header::parsing::from_one_raw_str(raw).map($id) } fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { @@ -303,10 +305,10 @@ macro_rules! header { static NAME: &'static str = $n; NAME } - fn parse_header(raw: &[Vec]) -> $crate::Result { + fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { // FIXME: Return None if no item is in $id::Only if raw.len() == 1 { - if raw[0] == b"*" { + if &raw[0] == b"*" { return Ok($id::Any) } } diff --git a/src/header/common/origin.rs b/src/header/common/origin.rs index 32a8d4b4eb..0310d1de60 100644 --- a/src/header/common/origin.rs +++ b/src/header/common/origin.rs @@ -1,4 +1,4 @@ -use header::{Header, Host}; +use header::{Header, Raw, Host}; use std::fmt; use std::str::FromStr; use header::parsing::from_one_raw_str; @@ -57,7 +57,7 @@ impl Header for Origin { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { from_one_raw_str(raw) } @@ -100,10 +100,10 @@ mod tests { #[test] fn test_origin() { - let origin = Header::parse_header([b"http://foo.com".to_vec()].as_ref()); + let origin = Header::parse_header(&vec![b"http://foo.com".to_vec()].into()); assert_eq!(origin.ok(), Some(Origin::new("http", "foo.com", None))); - let origin = Header::parse_header([b"https://foo.com:443".to_vec()].as_ref()); + let origin = Header::parse_header(&vec![b"https://foo.com:443".to_vec()].into()); assert_eq!(origin.ok(), Some(Origin::new("https", "foo.com", Some(443)))); } } diff --git a/src/header/common/pragma.rs b/src/header/common/pragma.rs index 9e6d0504df..7ba347f9e6 100644 --- a/src/header/common/pragma.rs +++ b/src/header/common/pragma.rs @@ -1,7 +1,7 @@ use std::fmt; use std::ascii::AsciiExt; -use header::{Header, parsing}; +use header::{Header, Raw, parsing}; /// The `Pragma` header defined by HTTP/1.0. /// @@ -44,7 +44,7 @@ impl Header for Pragma { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { parsing::from_one_raw_str(raw).and_then(|s: String| { let slice = &s.to_ascii_lowercase()[..]; match slice { @@ -64,12 +64,12 @@ impl Header for Pragma { #[test] fn test_parse_header() { - let a: Pragma = Header::parse_header([b"no-cache".to_vec()].as_ref()).unwrap(); + let a: Pragma = Header::parse_header(&"no-cache".into()).unwrap(); let b = Pragma::NoCache; assert_eq!(a, b); - let c: Pragma = Header::parse_header([b"FoObar".to_vec()].as_ref()).unwrap(); + let c: Pragma = Header::parse_header(&"FoObar".into()).unwrap(); let d = Pragma::Ext("FoObar".to_owned()); assert_eq!(c, d); - let e: ::Result = Header::parse_header([b"".to_vec()].as_ref()); + let e: ::Result = Header::parse_header(&"".into()); assert_eq!(e.ok(), None); } diff --git a/src/header/common/prefer.rs b/src/header/common/prefer.rs index b960bfd6fc..5e14e1f22f 100644 --- a/src/header/common/prefer.rs +++ b/src/header/common/prefer.rs @@ -1,6 +1,6 @@ use std::fmt; use std::str::FromStr; -use header::{Header}; +use header::{Header, Raw}; use header::parsing::{from_comma_delimited, fmt_comma_delimited}; /// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) @@ -56,7 +56,7 @@ impl Header for Prefer { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let preferences = try!(from_comma_delimited(raw)); if !preferences.is_empty() { Ok(Prefer(preferences)) @@ -159,14 +159,14 @@ mod tests { #[test] fn test_parse_multiple_headers() { - let prefer = Header::parse_header(&[b"respond-async, return=representation".to_vec()]); + let prefer = Header::parse_header(&"respond-async, return=representation".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync, Preference::ReturnRepresentation]))) } #[test] fn test_parse_argument() { - let prefer = Header::parse_header(&[b"wait=100, handling=leniant, respond-async".to_vec()]); + let prefer = Header::parse_header(&"wait=100, handling=leniant, respond-async".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100), Preference::HandlingLeniant, Preference::RespondAsync]))) @@ -174,14 +174,14 @@ mod tests { #[test] fn test_parse_quote_form() { - let prefer = Header::parse_header(&[b"wait=\"200\", handling=\"strict\"".to_vec()]); + let prefer = Header::parse_header(&"wait=\"200\", handling=\"strict\"".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200), Preference::HandlingStrict]))) } #[test] fn test_parse_extension() { - let prefer = Header::parse_header(&[b"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".to_vec()]); + let prefer = Header::parse_header(&"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![ Preference::Extension("foo".to_owned(), "".to_owned(), vec![]), Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]), @@ -192,7 +192,7 @@ mod tests { #[test] fn test_fail_with_args() { - let prefer: ::Result = Header::parse_header(&[b"respond-async; foo=bar".to_vec()]); + let prefer: ::Result = Header::parse_header(&"respond-async; foo=bar".into()); assert_eq!(prefer.ok(), None); } } diff --git a/src/header/common/preference_applied.rs b/src/header/common/preference_applied.rs index 2d6ef4fdc5..9c07f0d73f 100644 --- a/src/header/common/preference_applied.rs +++ b/src/header/common/preference_applied.rs @@ -1,5 +1,5 @@ use std::fmt; -use header::{Header, Preference}; +use header::{Header, Raw, Preference}; use header::parsing::{from_comma_delimited, fmt_comma_delimited}; /// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) @@ -54,7 +54,7 @@ impl Header for PreferenceApplied { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let preferences = try!(from_comma_delimited(raw)); if !preferences.is_empty() { Ok(PreferenceApplied(preferences)) diff --git a/src/header/common/range.rs b/src/header/common/range.rs index c18c19a641..3ad6c100bf 100644 --- a/src/header/common/range.rs +++ b/src/header/common/range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display}; use std::str::FromStr; -use header::Header; -use header::parsing::{from_one_raw_str, from_comma_delimited}; +use header::{Header, Raw}; +use header::parsing::{from_one_raw_str}; /// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) /// @@ -130,15 +130,11 @@ impl FromStr for Range { match (iter.next(), iter.next()) { (Some("bytes"), Some(ranges)) => { - match from_comma_delimited(&[ranges]) { - Ok(ranges) => { - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - }, - Err(_) => Err(::Error::Header) + let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { + return Err(::Error::Header); } + Ok(Range::Bytes(ranges)) } (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) @@ -173,6 +169,16 @@ impl FromStr for ByteRangeSpec { } } +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y) + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + impl Header for Range { fn header_name() -> &'static str { @@ -180,7 +186,7 @@ impl Header for Range { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { from_one_raw_str(raw) } @@ -192,29 +198,29 @@ impl Header for Range { #[test] fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&[b"bytes=1-100".to_vec()]).unwrap(); - let r2: Range = Header::parse_header(&[b"bytes=1-100,-".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&[b"bytes=1-100,200-".to_vec()]).unwrap(); - let r2: Range = Header::parse_header(&[b"bytes= 1-100 , 101-xxx, 200- ".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); let r3 = Range::Bytes( vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] ); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&[b"bytes=1-100,-100".to_vec()]).unwrap(); - let r2: Range = Header::parse_header(&[b"bytes=1-100, ,,-100".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); let r3 = Range::Bytes( vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100)] ); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&[b"custom=1-100,-100".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); @@ -222,40 +228,40 @@ fn test_parse_bytes_range_valid() { #[test] fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&[b"custom=1-100,-100".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&[b"custom=abcd".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&[b"custom=xxx-yyy".to_vec()]).unwrap(); + let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&[b"bytes=1-a,-".to_vec()]); + let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"bytes=1-2-3".to_vec()]); + let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"abc".to_vec()]); + let r: ::Result = Header::parse_header(&"abc".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"bytes=1-100=".to_vec()]); + let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"bytes=".to_vec()]); + let r: ::Result = Header::parse_header(&"bytes=".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"custom=".to_vec()]); + let r: ::Result = Header::parse_header(&"custom=".into()); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&[b"=1-100".to_vec()]); + let r: ::Result = Header::parse_header(&"=1-100".into()); assert_eq!(r.ok(), None); } diff --git a/src/header/common/referrer_policy.rs b/src/header/common/referrer_policy.rs index 7bce9a11f0..154ba90c79 100644 --- a/src/header/common/referrer_policy.rs +++ b/src/header/common/referrer_policy.rs @@ -1,7 +1,7 @@ use std::fmt; use std::ascii::AsciiExt; -use header::{Header, parsing}; +use header::{Header, Raw, parsing}; /// `Referrer-Policy` header, part of /// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) @@ -52,7 +52,7 @@ impl Header for ReferrerPolicy { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { use self::ReferrerPolicy::*; parsing::from_one_raw_str(raw).and_then(|s: String| { let slice = &s.to_ascii_lowercase()[..]; @@ -84,9 +84,9 @@ impl Header for ReferrerPolicy { #[test] fn test_parse_header() { - let a: ReferrerPolicy = Header::parse_header([b"origin".to_vec()].as_ref()).unwrap(); + let a: ReferrerPolicy = Header::parse_header(&"origin".into()).unwrap(); let b = ReferrerPolicy::Origin; assert_eq!(a, b); - let e: ::Result = Header::parse_header([b"foobar".to_vec()].as_ref()); + let e: ::Result = Header::parse_header(&"foobar".into()); assert!(e.is_err()); } diff --git a/src/header/common/set_cookie.rs b/src/header/common/set_cookie.rs index c08a865a24..67471e7d61 100644 --- a/src/header/common/set_cookie.rs +++ b/src/header/common/set_cookie.rs @@ -1,4 +1,4 @@ -use header::{Header, CookiePair, CookieJar}; +use header::{Header, Raw, CookiePair, CookieJar}; use std::fmt::{self, Display}; use std::str::from_utf8; @@ -88,7 +88,7 @@ impl Header for SetCookie { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { let mut set_cookies = Vec::with_capacity(raw.len()); for set_cookies_raw in raw { if let Ok(s) = from_utf8(&set_cookies_raw[..]) { @@ -136,7 +136,7 @@ impl SetCookie { #[test] fn test_parse() { - let h = Header::parse_header(&[b"foo=bar; HttpOnly".to_vec()][..]); + let h = Header::parse_header(&"foo=bar; HttpOnly".into()); let mut c1 = CookiePair::new("foo".to_owned(), "bar".to_owned()); c1.httponly = true; diff --git a/src/header/common/strict_transport_security.rs b/src/header/common/strict_transport_security.rs index 8023f225d6..491ffaf660 100644 --- a/src/header/common/strict_transport_security.rs +++ b/src/header/common/strict_transport_security.rs @@ -3,7 +3,7 @@ use std::str::{self, FromStr}; use unicase::UniCase; -use header::{Header, parsing}; +use header::{Header, Raw, parsing}; /// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) /// @@ -125,7 +125,7 @@ impl Header for StrictTransportSecurity { NAME } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { parsing::from_one_raw_str(raw) } @@ -145,49 +145,49 @@ mod tests { #[test] fn test_parse_max_age() { - let h = Header::parse_header(&[b"max-age=31536000".to_vec()][..]); + let h = Header::parse_header(&"max-age=31536000".into()); assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); } #[test] fn test_parse_max_age_no_value() { - let h: ::Result = Header::parse_header(&[b"max-age".to_vec()][..]); + let h: ::Result = Header::parse_header(&"max-age".into()); assert!(h.is_err()); } #[test] fn test_parse_quoted_max_age() { - let h = Header::parse_header(&[b"max-age=\"31536000\"".to_vec()][..]); + let h = Header::parse_header(&"max-age=\"31536000\"".into()); assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); } #[test] fn test_parse_spaces_max_age() { - let h = Header::parse_header(&[b"max-age = 31536000".to_vec()][..]); + let h = Header::parse_header(&"max-age = 31536000".into()); assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); } #[test] fn test_parse_include_subdomains() { - let h = Header::parse_header(&[b"max-age=15768000 ; includeSubDomains".to_vec()][..]); + let h = Header::parse_header(&"max-age=15768000 ; includeSubDomains".into()); assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 })); } #[test] fn test_parse_no_max_age() { - let h: ::Result = Header::parse_header(&[b"includeSubDomains".to_vec()][..]); + let h: ::Result = Header::parse_header(&"includeSubDomains".into()); assert!(h.is_err()); } #[test] fn test_parse_max_age_nan() { - let h: ::Result = Header::parse_header(&[b"max-age = derp".to_vec()][..]); + let h: ::Result = Header::parse_header(&"max-age = derp".into()); assert!(h.is_err()); } #[test] fn test_parse_duplicate_directives() { - assert!(StrictTransportSecurity::parse_header(&[b"max-age=100; max-age=5; max-age=0".to_vec()][..]).is_err()); + assert!(StrictTransportSecurity::parse_header(&"max-age=100; max-age=5; max-age=0".into()).is_err()); } } diff --git a/src/header/common/upgrade.rs b/src/header/common/upgrade.rs index 7f2a613210..782ca018fd 100644 --- a/src/header/common/upgrade.rs +++ b/src/header/common/upgrade.rs @@ -68,7 +68,7 @@ header! { Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); #[test] fn test3() { - let x: ::Result = Header::parse_header(&[b"WEbSOCKet".to_vec()]); + let x: ::Result = Header::parse_header(&"WEbSOCKet".into()); assert_eq!(x.ok(), Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); } } diff --git a/src/header/common/vary.rs b/src/header/common/vary.rs index d9113e9046..9f3d05548e 100644 --- a/src/header/common/vary.rs +++ b/src/header/common/vary.rs @@ -54,10 +54,10 @@ header! { fn test2() { let mut vary: ::Result; - vary = Header::parse_header([b"*".to_vec()].as_ref()); + vary = Header::parse_header(&"*".into()); assert_eq!(vary.ok(), Some(Vary::Any)); - vary = Header::parse_header([b"etag,cookie,allow".to_vec()].as_ref()); + vary = Header::parse_header(&"etag,cookie,allow".into()); assert_eq!(vary.ok(), Some(Vary::Items(vec!["eTag".parse().unwrap(), "cookIE".parse().unwrap(), "AlLOw".parse().unwrap(),]))); diff --git a/src/header/internals/item.rs b/src/header/internals/item.rs index 03ed46a6bc..bf9ed44638 100644 --- a/src/header/internals/item.rs +++ b/src/header/internals/item.rs @@ -4,18 +4,18 @@ use std::fmt; use std::str::from_utf8; use super::cell::{OptCell, PtrMapCell}; -use header::{Header}; +use header::{Header, Raw}; #[derive(Clone)] pub struct Item { - raw: OptCell>>, + raw: OptCell, typed: PtrMapCell
} impl Item { #[inline] - pub fn new_raw(data: Vec>) -> Item { + pub fn new_raw(data: Raw) -> Item { Item { raw: OptCell::new(Some(data)), typed: PtrMapCell::new(), @@ -33,23 +33,22 @@ impl Item { } #[inline] - pub fn mut_raw(&mut self) -> &mut Vec> { + pub fn mut_raw(&mut self) -> &mut Raw { self.typed = PtrMapCell::new(); unsafe { self.raw.get_mut() } } - pub fn raw(&self) -> &[Vec] { + pub fn raw(&self) -> &Raw { if let Some(ref raw) = *self.raw { - return &raw[..]; + return raw; } - let raw = vec![unsafe { self.typed.one() }.to_string().into_bytes()]; + let raw = unsafe { self.typed.one() }.to_string().into_bytes().into(); self.raw.set(raw); - let raw = self.raw.as_ref().unwrap(); - &raw[..] + self.raw.as_ref().unwrap() } pub fn typed(&self) -> Option<&H> { @@ -86,10 +85,8 @@ impl Item { } #[inline] -fn parse(raw: &Vec>) -> - ::Result> { - Header::parse_header(&raw[..]).map(|h: H| { - // FIXME: Use Type ascription +fn parse(raw: &Raw) -> ::Result> { + H::parse_header(raw).map(|h| { let h: Box
= Box::new(h); h }) diff --git a/src/header/mod.rs b/src/header/mod.rs index 96eaa17b62..cdabf888a6 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -41,7 +41,7 @@ //! //! ``` //! use std::fmt; -//! use hyper::header::Header; +//! use hyper::header::{Header, Raw}; //! //! #[derive(Debug, Clone, Copy)] //! struct Dnt(bool); @@ -51,7 +51,7 @@ //! "DNT" //! } //! -//! fn parse_header(raw: &[Vec]) -> hyper::Result { +//! fn parse_header(raw: &Raw) -> hyper::Result { //! if raw.len() == 1 { //! let line = &raw[0]; //! if line.len() == 1 { @@ -94,9 +94,11 @@ use serde::ser; pub use self::shared::*; pub use self::common::*; +pub use self::raw::Raw; mod common; mod internals; +mod raw; mod shared; pub mod parsing; @@ -117,7 +119,7 @@ pub trait Header: HeaderClone + Any + GetType + Send + Sync { /// it's not necessarily the case that a Header is *allowed* to have more /// than one field value. If that's the case, you **should** return `None` /// if `raw.len() > 1`. - fn parse_header(raw: &[Vec]) -> ::Result where Self: Sized; + fn parse_header(raw: &Raw) -> ::Result where Self: Sized; /// Format a header to be output into a TcpStream. /// /// This method is not allowed to introduce an Err not produced @@ -199,7 +201,6 @@ fn header_name() -> &'static str { /// A map of header fields on requests and responses. #[derive(Clone)] pub struct Headers { - //data: HashMap data: VecMap, } @@ -270,13 +271,17 @@ impl Headers { for header in raw { trace!("raw header: {:?}={:?}", header.name, &header.value[..]); let name = HeaderName(UniCase(maybe_literal(header.name))); - let mut item = match headers.data.entry(name) { - Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])), - Entry::Occupied(entry) => entry.into_mut() - }; let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count(); let value = &header.value[.. header.value.len() - trim]; - item.mut_raw().push(value.to_vec()); + + match headers.data.entry(name) { + Entry::Vacant(entry) => { + entry.insert(Item::new_raw(self::raw::parsed(value))); + } + Entry::Occupied(entry) => { + entry.into_mut().mut_raw().push(value); + } + }; } Ok(headers) } @@ -290,46 +295,6 @@ impl Headers { Item::new_typed(Box::new(value))); } - /// Access the raw value of a header. - /// - /// Prefer to use the typed getters instead. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # let mut headers = Headers::new(); - /// let raw_content_type = headers.get_raw("content-type"); - /// ``` - pub fn get_raw(&self, name: &str) -> Option<&[Vec]> { - self.data - .get(&HeaderName(UniCase(Cow::Borrowed(unsafe { mem::transmute::<&str, &str>(name) })))) - .map(Item::raw) - } - - /// Set the raw value of a header, bypassing any typed headers. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # let mut headers = Headers::new(); - /// headers.set_raw("content-length", vec![b"5".to_vec()]); - /// ``` - pub fn set_raw> + fmt::Debug>(&mut self, name: K, - value: Vec>) { - trace!("Headers.set_raw( {:?}, {:?} )", name, value); - self.data.insert(HeaderName(UniCase(name.into())), Item::new_raw(value)); - } - - /// Remove a header set by set_raw - pub fn remove_raw(&mut self, name: &str) { - trace!("Headers.remove_raw( {:?} )", name); - self.data.remove( - &HeaderName(UniCase(Cow::Borrowed(unsafe { mem::transmute::<&str, &str>(name) }))) - ); - } - /// Get a reference to the header field's value, if it exists. pub fn get(&self) -> Option<&H> { self.data.get(&HeaderName(UniCase(Cow::Borrowed(header_name::())))) @@ -379,6 +344,54 @@ impl Headers { pub fn clear(&mut self) { self.data.clear() } + + /// Access the raw value of a header. + /// + /// Prefer to use the typed getters instead. + /// + /// Example: + /// + /// ``` + /// # use hyper::header::Headers; + /// # let mut headers = Headers::new(); + /// # headers.set_raw("content-type", "text/plain"); + /// let raw = headers.get_raw("content-type").unwrap(); + /// assert_eq!(raw, "text/plain"); + /// ``` + pub fn get_raw(&self, name: &str) -> Option<&Raw> { + self.data + .get(&HeaderName(UniCase(Cow::Borrowed(unsafe { mem::transmute::<&str, &str>(name) })))) + .map(Item::raw) + } + + /// Set the raw value of a header, bypassing any typed headers. + /// + /// Example: + /// + /// ``` + /// # use hyper::header::Headers; + /// # let mut headers = Headers::new(); + /// headers.set_raw("content-length", b"1".as_ref()); + /// headers.set_raw("content-length", "2"); + /// headers.set_raw("content-length", "3".to_string()); + /// headers.set_raw("content-length", vec![vec![b'4']]); + /// ``` + pub fn set_raw>, V: Into>(&mut self, name: K, value: V) { + let name = name.into(); + let value = value.into(); + trace!("Headers.set_raw( {:?}, {:?} )", name, value); + self.data.insert(HeaderName(UniCase(name)), Item::new_raw(value)); + } + + /// Remove a header by name. + pub fn remove_raw(&mut self, name: &str) { + trace!("Headers.remove_raw( {:?} )", name); + self.data.remove( + &HeaderName(UniCase(Cow::Borrowed(unsafe { mem::transmute::<&str, &str>(name) }))) + ); + } + + } impl PartialEq for Headers { @@ -584,7 +597,7 @@ mod tests { use mime::Mime; use mime::TopLevel::Text; use mime::SubLevel::Plain; - use super::{Headers, Header, ContentLength, ContentType, + use super::{Headers, Header, Raw, ContentLength, ContentType, Accept, Host, qitem}; use httparse; @@ -622,7 +635,7 @@ mod tests { #[test] fn test_content_type() { - let content_type = Header::parse_header([b"text/plain".to_vec()].as_ref()); + let content_type = Header::parse_header(&b"text/plain".as_ref().into()); assert_eq!(content_type.ok(), Some(ContentType(Mime(Text, Plain, vec![])))); } @@ -631,11 +644,11 @@ mod tests { let text_plain = qitem(Mime(Text, Plain, vec![])); let application_vendor = "application/vnd.github.v3.full+json; q=0.5".parse().unwrap(); - let accept = Header::parse_header([b"text/plain".to_vec()].as_ref()); + let accept = Header::parse_header(&b"text/plain".as_ref().into()); assert_eq!(accept.ok(), Some(Accept(vec![text_plain.clone()]))); - let bytevec = [b"application/vnd.github.v3.full+json; q=0.5, text/plain".to_vec()]; - let accept = Header::parse_header(bytevec.as_ref()); + let bytevec = b"application/vnd.github.v3.full+json; q=0.5, text/plain".as_ref().into(); + let accept = Header::parse_header(&bytevec); assert_eq!(accept.ok(), Some(Accept(vec![application_vendor, text_plain]))); } @@ -646,20 +659,15 @@ mod tests { fn header_name() -> &'static str { "content-length" } - fn parse_header(raw: &[Vec]) -> ::Result { + fn parse_header(raw: &Raw) -> ::Result { use std::str::from_utf8; use std::str::FromStr; - if raw.len() != 1 { - return Err(::Error::Header); - } - // we JUST checked that raw.len() == 1, so raw[0] WILL exist. - match match from_utf8(unsafe { &raw.get_unchecked(0)[..] }) { - Ok(s) => FromStr::from_str(s).ok(), - Err(_) => None - }.map(|u| CrazyLength(Some(false), u)) { - Some(x) => Ok(x), - None => Err(::Error::Header), + if let Some(line) = raw.one() { + let s = try!(from_utf8(line).map(|s| FromStr::from_str(s).map_err(|_| ::Error::Header))); + s.map(|u| CrazyLength(Some(false), u)) + } else { + Err(::Error::Header) } } diff --git a/src/header/parsing.rs b/src/header/parsing.rs index fc8b96498d..e3c4c07022 100644 --- a/src/header/parsing.rs +++ b/src/header/parsing.rs @@ -6,13 +6,17 @@ use std::str::FromStr; use std::fmt::{self, Display}; use url::percent_encoding; +use header::Raw; use header::shared::Charset; /// Reads a single raw string when parsing a header. -pub fn from_one_raw_str(raw: &[Vec]) -> ::Result { - if raw.len() != 1 || unsafe { raw.get_unchecked(0) } == b"" { return Err(::Error::Header) } - // we JUST checked that raw.len() == 1, so raw[0] WILL exist. - from_raw_str( unsafe { raw.get_unchecked(0) }) +pub fn from_one_raw_str(raw: &Raw) -> ::Result { + if let Some(line) = raw.one() { + if !line.is_empty() { + return from_raw_str(line) + } + } + Err(::Error::Header) } /// Reads a raw string into a value. @@ -23,7 +27,7 @@ pub fn from_raw_str(raw: &[u8]) -> ::Result { /// Reads a comma-delimited raw header into a Vec. #[inline] -pub fn from_comma_delimited>(raw: &[S]) -> ::Result> { +pub fn from_comma_delimited(raw: &Raw) -> ::Result> { let mut result = Vec::new(); for s in raw { let s = try!(str::from_utf8(s.as_ref())); diff --git a/src/header/raw.rs b/src/header/raw.rs new file mode 100644 index 0000000000..39735a2f03 --- /dev/null +++ b/src/header/raw.rs @@ -0,0 +1,229 @@ +use std::borrow::Cow; +use std::fmt; + +/// A raw header value. +#[derive(Clone, PartialEq, Eq)] +pub struct Raw(Lines); + +impl Raw { + /// Returns the amount of lines. + #[inline] + pub fn len(&self) -> usize { + match self.0 { + Lines::One(..) => 1, + Lines::Many(ref lines) => lines.len() + } + } + + /// Returns the line if there is only 1. + #[inline] + pub fn one(&self) -> Option<&[u8]> { + match self.0 { + Lines::One(ref line) => Some(line), + Lines::Many(ref lines) if lines.len() == 1 => Some(&lines[0]), + _ => None + } + } + + /// Iterate the lines of raw bytes. + #[inline] + pub fn iter(&self) -> RawLines { + RawLines { + inner: match self.0 { + Lines::One(ref line) => unsafe { + ::std::slice::from_raw_parts(line, 1) + }.iter(), + Lines::Many(ref lines) => lines.iter() + } + } + } + + /// Append a line to this `Raw` header value. + pub fn push(&mut self, val: &[u8]) { + let lines = ::std::mem::replace(&mut self.0, Lines::Many(Vec::new())); + match lines { + Lines::One(line) => { + self.0 = Lines::Many(vec![line, maybe_literal(val.into())]); + } + Lines::Many(mut lines) => { + lines.push(maybe_literal(val.into())); + self.0 = Lines::Many(lines); + } + } + } +} + +#[derive(Clone, PartialEq, Eq)] +enum Lines { + One(Line), + Many(Vec) +} + +type Line = Cow<'static, [u8]>; + +fn eq, B: AsRef<[u8]>>(a: &[A], b: &[B]) -> bool { + if a.len() != b.len() { + false + } else { + for (a, b) in a.iter().zip(b.iter()) { + if a.as_ref() != b.as_ref() { + return false + } + } + true + } +} + +impl PartialEq<[Vec]> for Raw { + fn eq(&self, bytes: &[Vec]) -> bool { + match self.0 { + Lines::One(ref line) => eq(&[line], bytes), + Lines::Many(ref lines) => eq(lines, bytes) + } + } +} + +impl PartialEq<[u8]> for Raw { + fn eq(&self, bytes: &[u8]) -> bool { + match self.0 { + Lines::One(ref line) => line.as_ref() == bytes, + Lines::Many(..) => false + } + } +} + +impl PartialEq for Raw { + fn eq(&self, s: &str) -> bool { + match self.0 { + Lines::One(ref line) => line.as_ref() == s.as_bytes(), + Lines::Many(..) => false + } + } +} + +impl From>> for Raw { + #[inline] + fn from(val: Vec>) -> Raw { + Raw(Lines::Many( + val.into_iter() + .map(|vec| maybe_literal(vec.into())) + .collect() + )) + } +} + +impl From for Raw { + #[inline] + fn from(val: String) -> Raw { + let vec: Vec = val.into(); + vec.into() + } +} + +impl From> for Raw { + #[inline] + fn from(val: Vec) -> Raw { + Raw(Lines::One(Cow::Owned(val))) + } +} + +impl From<&'static str> for Raw { + fn from(val: &'static str) -> Raw { + Raw(Lines::One(Cow::Borrowed(val.as_bytes()))) + } +} + +impl From<&'static [u8]> for Raw { + fn from(val: &'static [u8]) -> Raw { + Raw(Lines::One(Cow::Borrowed(val))) + } +} + +pub fn parsed(val: &[u8]) -> Raw { + Raw(Lines::One(maybe_literal(val.into()))) +} + +impl fmt::Debug for Raw { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Lines::One(ref line) => fmt::Debug::fmt(&[line], f), + Lines::Many(ref lines) => fmt::Debug::fmt(lines, f) + } + } +} + +impl ::std::ops::Index for Raw { + type Output = [u8]; + fn index(&self, idx: usize) -> &[u8] { + match self.0 { + Lines::One(ref line) => if idx == 0 { + line.as_ref() + } else { + panic!("index out of bounds: {}", idx) + }, + Lines::Many(ref lines) => lines[idx].as_ref() + } + } +} + +macro_rules! literals { + ($($len:expr => $($value:expr),+;)+) => ( + fn maybe_literal<'a>(s: Cow<'a, [u8]>) -> Cow<'static, [u8]> { + match s.len() { + $($len => { + $( + if s.as_ref() == $value { + return Cow::Borrowed($value); + } + )+ + })+ + + _ => () + } + + Cow::Owned(s.into_owned()) + } + + #[test] + fn test_literal_lens() { + $( + $({ + let s = $value; + assert!(s.len() == $len, "{:?} has len of {}, listed as {}", s, s.len(), $len); + })+ + )+ + } + ); +} + +literals! { + 1 => b"*", b"0"; + 3 => b"*/*"; + 4 => b"gzip"; + 5 => b"close"; + 7 => b"chunked"; + 10 => b"keep-alive"; +} + +impl<'a> IntoIterator for &'a Raw { + type IntoIter = RawLines<'a>; + type Item = &'a [u8]; + + fn into_iter(self) -> RawLines<'a> { + self.iter() + } +} + +#[derive(Debug)] +pub struct RawLines<'a> { + inner: ::std::slice::Iter<'a, Cow<'static, [u8]>> +} + +impl<'a> Iterator for RawLines<'a> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option<&'a [u8]> { + self.inner.next().map(AsRef::as_ref) + } +}