Skip to content

Commit

Permalink
feat(header): introduce header::Raw (#869)
Browse files Browse the repository at this point in the history
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<Raw>`, which allows for more ergnomic method calls.

BREAKING CHANGE: `Header::parse_header` now receives `&Raw`, instead of
  a `&[Vec<u8>]`. `Raw` provides several methods to ease using it, but
  may require some changes to existing code.
  • Loading branch information
seanmonstar committed Jul 23, 2016
1 parent d67dbc6 commit 50ccdaa
Show file tree
Hide file tree
Showing 27 changed files with 489 additions and 245 deletions.
11 changes: 5 additions & 6 deletions src/header/common/access_control_allow_credentials.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -46,16 +46,15 @@ impl Header for AccessControlAllowCredentials {
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<AccessControlAllowCredentials> {
if raw.len() == 1 {
fn parse_header(raw: &Raw) -> ::Result<AccessControlAllowCredentials> {
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);
Expand Down
21 changes: 11 additions & 10 deletions src/header/common/access_control_allow_origin.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -59,16 +60,16 @@ impl Header for AccessControlAllowOrigin {
"Access-Control-Allow-Origin"
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<AccessControlAllowOrigin> {
if raw.len() != 1 {
return Err(::Error::Header)
fn parse_header(raw: &Raw) -> ::Result<AccessControlAllowOrigin> {
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 {
Expand Down
40 changes: 20 additions & 20 deletions src/header/common/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
///
Expand Down Expand Up @@ -77,25 +77,26 @@ impl<S: Scheme + Any> Header for Authorization<S> where <S as FromStr>::Err: 'st
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<Authorization<S>> {
if raw.len() != 1 {
return Err(::Error::Header);
}
let header = try!(from_utf8(unsafe { &raw.get_unchecked(0)[..] }));
if let Some(scheme) = <S as Scheme>::scheme() {
if header.starts_with(scheme) && header.len() > scheme.len() + 1 {
match header[scheme.len() + 1..].parse::<S>().map(Authorization) {
fn parse_header(raw: &Raw) -> ::Result<Authorization<S>> {
if let Some(line) = raw.one() {
let header = try!(from_utf8(line));
if let Some(scheme) = <S as Scheme>::scheme() {
if header.starts_with(scheme) && header.len() > scheme.len() + 1 {
match header[scheme.len() + 1..].parse::<S>().map(Authorization) {
Ok(h) => Ok(h),
Err(_) => Err(::Error::Header)
}
} else {
Err(::Error::Header)
}
} else {
match header.parse::<S>().map(Authorization) {
Ok(h) => Ok(h),
Err(_) => Err(::Error::Header)
}
} else {
Err(::Error::Header)
}
} else {
match header.parse::<S>().map(Authorization) {
Ok(h) => Ok(h),
Err(_) => Err(::Error::Header)
}
Err(::Error::Header)
}
}

Expand Down Expand Up @@ -231,8 +232,7 @@ mod tests {

#[test]
fn test_raw_auth_parse() {
let header: Authorization<String> = Header::parse_header(
&[b"foo bar baz".to_vec()]).unwrap();
let header: Authorization<String> = Header::parse_header(&b"foo bar baz".as_ref().into()).unwrap();
assert_eq!(header.0, "foo bar baz");
}

Expand All @@ -256,15 +256,15 @@ mod tests {
#[test]
fn test_basic_auth_parse() {
let auth: Authorization<Basic> = 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()));
}

#[test]
fn test_basic_auth_parse_no_password() {
let auth: Authorization<Basic> = 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()));
}
Expand All @@ -282,7 +282,7 @@ mod tests {
#[test]
fn test_bearer_auth_parse() {
let auth: Authorization<Bearer> = Header::parse_header(
&[b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()]).unwrap();
&b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into()).unwrap();
assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG");
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/header/common/cache_control.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -55,7 +55,7 @@ impl Header for CacheControl {
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl> {
fn parse_header(raw: &Raw) -> ::Result<CacheControl> {
let directives = try!(from_comma_delimited(raw));
if !directives.is_empty() {
Ok(CacheControl(directives))
Expand Down Expand Up @@ -167,35 +167,35 @@ 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()))])))
}

#[test]
fn test_parse_bad_syntax() {
let cache: ::Result<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
let cache: ::Result<CacheControl> = Header::parse_header(&vec![b"foo=".to_vec()].into());
assert_eq!(cache.ok(), None)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/header/common/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ mod tests {
use unicase::UniCase;

fn parse_option(header: Vec<u8>) -> 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
}

Expand Down
32 changes: 16 additions & 16 deletions src/header/common/content_disposition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -94,7 +94,7 @@ impl Header for ContentDisposition {
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<ContentDisposition> {
fn parse_header(raw: &Raw) -> ::Result<ContentDisposition> {
parsing::from_one_raw_str(raw).and_then(|s: String| {
let mut sections = s.split(';');
let disposition = match sections.next() {
Expand Down Expand Up @@ -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![
Expand All @@ -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![
Expand All @@ -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![
Expand All @@ -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);
}
Expand Down
9 changes: 4 additions & 5 deletions src/header/common/content_length.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -40,12 +40,11 @@ impl Header for ContentLength {
static NAME: &'static str = "Content-Length";
NAME
}
fn parse_header(raw: &[Vec<u8>]) -> ::Result<ContentLength> {
fn parse_header(raw: &Raw) -> ::Result<ContentLength> {
// 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) {
Expand Down Expand Up @@ -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");
}
Expand Down
6 changes: 3 additions & 3 deletions src/header/common/cookie.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -43,7 +43,7 @@ impl Header for Cookie {
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<Cookie> {
fn parse_header(raw: &Raw) -> ::Result<Cookie> {
let mut cookies = Vec::with_capacity(raw.len());
for cookies_raw in raw.iter() {
let cookies_str = try!(from_utf8(&cookies_raw[..]));
Expand Down Expand Up @@ -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])));
Expand Down
11 changes: 5 additions & 6 deletions src/header/common/expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::str;

use unicase::UniCase;

use header::{Header};
use header::{Header, Raw};

/// The `Expect` header.
///
Expand Down Expand Up @@ -34,16 +34,15 @@ impl Header for Expect {
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<Expect> {
if raw.len() == 1 {
fn parse_header(raw: &Raw) -> ::Result<Expect> {
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)
Expand Down
Loading

0 comments on commit 50ccdaa

Please sign in to comment.