Skip to content

Commit

Permalink
feat(header): change Cookie to be map-like
Browse files Browse the repository at this point in the history
The `Cookie` header now has `get` and `set` methods, to tree the list of
cookies as a map.

BREAKING CHANGE: The `Cookie` header is no longer a wrapper over a
  `Vec<String>`. It must accessed via its `get` and `set` methods.

Closes #1145
  • Loading branch information
seanmonstar committed Apr 26, 2017
1 parent 8d6d9a2 commit b648f19
Showing 1 changed file with 137 additions and 20 deletions.
157 changes: 137 additions & 20 deletions src/header/common/cookie.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use header::{Header, Raw};
use std::fmt::{self, Display};
use std::borrow::Cow;
use std::fmt;
use std::str::from_utf8;

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

/// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4)
///
/// If the user agent does attach a Cookie header field to an HTTP
Expand All @@ -20,17 +23,35 @@ use std::str::from_utf8;
/// use hyper::header::{Headers, Cookie};
///
/// let mut headers = Headers::new();
/// let mut cookie = Cookie::new();
/// cookie.set("foo", "bar");
///
/// headers.set(
/// Cookie(vec![
/// String::from("foo=bar")
/// ])
/// );
/// assert_eq!(cookie.get("foo"), Some("bar"));
///
/// headers.set(cookie);
/// ```
#[derive(Clone, PartialEq, Debug)]
pub struct Cookie(pub Vec<String>);
#[derive(Clone)]
pub struct Cookie(VecMap<Cow<'static, str>, Cow<'static, str>>);

__hyper__deref!(Cookie => Vec<String>);
impl Cookie {
/// Creates a new `Cookie` header.
pub fn new() -> Cookie {
Cookie(VecMap::with_capacity(0))
}

/// Set a name and value for the `Cookie`.
pub fn set<K, V>(&mut self, key: K, value: V)
where K: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
self.0.insert(key.into(), value.into());
}

/// Get a value for the name, if it exists.
pub fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).map(AsRef::as_ref)
}
}

impl Header for Cookie {
fn header_name() -> &'static str {
Expand All @@ -39,16 +60,22 @@ impl Header for Cookie {
}

fn parse_header(raw: &Raw) -> ::Result<Cookie> {
let mut cookies = Vec::with_capacity(raw.len());
let mut vec_map = VecMap::with_capacity(raw.len());
for cookies_raw in raw.iter() {
let cookies_str = try!(from_utf8(&cookies_raw[..]));
for cookie_str in cookies_str.split(';') {
cookies.push(cookie_str.trim().to_owned())
let mut key_val = cookie_str.splitn(2, '=');
let key_val = (key_val.next(), key_val.next());
if let (Some(key), Some(val)) = key_val {
vec_map.insert(key.trim().to_owned().into(), val.trim().to_owned().into());
} else {
return Err(::Error::Header);
}
}
}

if !cookies.is_empty() {
Ok(Cookie(cookies))
if !vec_map.is_empty() {
Ok(Cookie(vec_map))
} else {
Err(::Error::Header)
}
Expand All @@ -59,16 +86,106 @@ impl Header for Cookie {
}
}

impl PartialEq for Cookie {
fn eq(&self, other: &Cookie) -> bool {
if self.0.len() == other.0.len() {
for &(ref k, ref v) in self.0.iter() {
if other.get(k) != Some(v) {
return false;
}
}
true
} else {
false
}
}
}

impl fmt::Debug for Cookie {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(self.0.iter().map(|&(ref k, ref v)| (k, v)))
.finish()
}
}

impl fmt::Display for Cookie {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let cookies = &self.0;
for (i, cookie) in cookies.iter().enumerate() {
if i != 0 {
try!(f.write_str("; "));
}
try!(Display::fmt(&cookie, f));
let mut iter = self.0.iter();
if let Some(&(ref key, ref val)) = iter.next() {
try!(write!(f, "{}={}", key, val));
}
for &(ref key, ref val) in iter {
try!(write!(f, "; {}={}", key, val));
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use ::header::Header;
use super::Cookie;

#[test]
fn test_set_and_get() {
let mut cookie = Cookie::new();
cookie.set("foo", "bar");
cookie.set(String::from("dyn"), String::from("amic"));

assert_eq!(cookie.get("foo"), Some("bar"));
assert_eq!(cookie.get("dyn"), Some("amic"));
assert!(cookie.get("nope").is_none());

cookie.set("foo", "notbar");
assert_eq!(cookie.get("foo"), Some("notbar"));
}

#[test]
fn test_eq() {
let mut cookie = Cookie::new();
let mut cookie2 = Cookie::new();

// empty is equal
assert_eq!(cookie, cookie2);

// left has more params
cookie.set("foo", "bar");
assert_ne!(cookie, cookie2);

// same len, different params
cookie2.set("bar", "foo");
assert_ne!(cookie, cookie2);


// right has more params, and matching KV
cookie2.set("foo", "bar");
assert_ne!(cookie, cookie2);

// same params, different order
cookie.set("bar", "foo");
assert_eq!(cookie, cookie2);
}

#[test]
fn test_parse() {
let mut cookie = Cookie::new();

let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap();
cookie.set("foo", "bar");
assert_eq!(cookie, parsed);

let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap();
cookie.set("baz", "quux");
assert_eq!(cookie, parsed);

let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap();
assert_eq!(cookie, parsed);

let parsed = Cookie::parse_header(&vec![b"foo = bar".to_vec(),b"baz= quux ".to_vec()].into()).unwrap();
assert_eq!(cookie, parsed);

Cookie::parse_header(&b"foo=bar=baz=quux".to_vec().into()).unwrap_err();

}
}
Expand Down

0 comments on commit b648f19

Please sign in to comment.