Skip to content

Commit cbfe882

Browse files
committed
feat(header): change Cookie to be map-like
The `Cookie` header now has `get` and `append` 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 `append` methods. Closes #1145
1 parent c3466be commit cbfe882

File tree

2 files changed

+160
-20
lines changed

2 files changed

+160
-20
lines changed

src/header/common/cookie.rs

+155-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
use header::{Header, Raw};
2-
use std::fmt::{self, Display};
1+
use std::borrow::Cow;
2+
use std::fmt;
33
use std::str::from_utf8;
44

5+
use header::{Header, Raw};
6+
use header::internals::VecMap;
7+
58
/// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4)
69
///
710
/// If the user agent does attach a Cookie header field to an HTTP
@@ -20,17 +23,53 @@ use std::str::from_utf8;
2023
/// use hyper::header::{Headers, Cookie};
2124
///
2225
/// let mut headers = Headers::new();
26+
/// let mut cookie = Cookie::new();
27+
/// cookie.append("foo", "bar");
2328
///
24-
/// headers.set(
25-
/// Cookie(vec![
26-
/// String::from("foo=bar")
27-
/// ])
28-
/// );
29+
/// assert_eq!(cookie.get("foo"), Some("bar"));
30+
///
31+
/// headers.set(cookie);
2932
/// ```
30-
#[derive(Clone, PartialEq, Debug)]
31-
pub struct Cookie(pub Vec<String>);
33+
#[derive(Clone)]
34+
pub struct Cookie(VecMap<Cow<'static, str>, Cow<'static, str>>);
3235

33-
__hyper__deref!(Cookie => Vec<String>);
36+
impl Cookie {
37+
/// Creates a new `Cookie` header.
38+
pub fn new() -> Cookie {
39+
Cookie(VecMap::with_capacity(0))
40+
}
41+
42+
/// Append a name and value for the `Cookie`.
43+
///
44+
/// # Note
45+
///
46+
/// Cookies are allowed to set a name with a
47+
/// a value multiple times. For example:
48+
///
49+
/// ```
50+
/// use hyper::header::Cookie;
51+
/// let mut cookie = Cookie::new();
52+
/// cookie.append("foo", "bar");
53+
/// cookie.append("foo", "quux");
54+
/// assert_eq!(cookie.to_string(), "foo=bar; foo=quux");
55+
pub fn append<K, V>(&mut self, key: K, value: V)
56+
where K: Into<Cow<'static, str>>,
57+
V: Into<Cow<'static, str>>,
58+
{
59+
self.0.append(key.into(), value.into());
60+
}
61+
62+
/// Get a value for the name, if it exists.
63+
///
64+
/// # Note
65+
///
66+
/// Only returns the first instance found. To access
67+
/// any other values associated with the name, parse
68+
/// the `str` representation.
69+
pub fn get(&self, key: &str) -> Option<&str> {
70+
self.0.get(key).map(AsRef::as_ref)
71+
}
72+
}
3473

3574
impl Header for Cookie {
3675
fn header_name() -> &'static str {
@@ -39,16 +78,22 @@ impl Header for Cookie {
3978
}
4079

4180
fn parse_header(raw: &Raw) -> ::Result<Cookie> {
42-
let mut cookies = Vec::with_capacity(raw.len());
81+
let mut vec_map = VecMap::with_capacity(raw.len());
4382
for cookies_raw in raw.iter() {
4483
let cookies_str = try!(from_utf8(&cookies_raw[..]));
4584
for cookie_str in cookies_str.split(';') {
46-
cookies.push(cookie_str.trim().to_owned())
85+
let mut key_val = cookie_str.splitn(2, '=');
86+
let key_val = (key_val.next(), key_val.next());
87+
if let (Some(key), Some(val)) = key_val {
88+
vec_map.insert(key.trim().to_owned().into(), val.trim().to_owned().into());
89+
} else {
90+
return Err(::Error::Header);
91+
}
4792
}
4893
}
4994

50-
if !cookies.is_empty() {
51-
Ok(Cookie(cookies))
95+
if vec_map.len() != 0 {
96+
Ok(Cookie(vec_map))
5297
} else {
5398
Err(::Error::Header)
5499
}
@@ -59,16 +104,106 @@ impl Header for Cookie {
59104
}
60105
}
61106

107+
impl PartialEq for Cookie {
108+
fn eq(&self, other: &Cookie) -> bool {
109+
if self.0.len() == other.0.len() {
110+
for &(ref k, ref v) in self.0.iter() {
111+
if other.get(k) != Some(v) {
112+
return false;
113+
}
114+
}
115+
true
116+
} else {
117+
false
118+
}
119+
}
120+
}
121+
122+
impl fmt::Debug for Cookie {
123+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124+
f.debug_map()
125+
.entries(self.0.iter().map(|&(ref k, ref v)| (k, v)))
126+
.finish()
127+
}
128+
}
129+
62130
impl fmt::Display for Cookie {
63131
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64-
let cookies = &self.0;
65-
for (i, cookie) in cookies.iter().enumerate() {
66-
if i != 0 {
67-
try!(f.write_str("; "));
68-
}
69-
try!(Display::fmt(&cookie, f));
132+
let mut iter = self.0.iter();
133+
if let Some(&(ref key, ref val)) = iter.next() {
134+
try!(write!(f, "{}={}", key, val));
135+
}
136+
for &(ref key, ref val) in iter {
137+
try!(write!(f, "; {}={}", key, val));
70138
}
71139
Ok(())
140+
}
141+
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use ::header::Header;
146+
use super::Cookie;
147+
148+
#[test]
149+
fn test_set_and_get() {
150+
let mut cookie = Cookie::new();
151+
cookie.append("foo", "bar");
152+
cookie.append(String::from("dyn"), String::from("amic"));
153+
154+
assert_eq!(cookie.get("foo"), Some("bar"));
155+
assert_eq!(cookie.get("dyn"), Some("amic"));
156+
assert!(cookie.get("nope").is_none());
157+
158+
cookie.append("foo", "notbar");
159+
assert_eq!(cookie.get("foo"), Some("bar"));
160+
}
161+
162+
#[test]
163+
fn test_eq() {
164+
let mut cookie = Cookie::new();
165+
let mut cookie2 = Cookie::new();
166+
167+
// empty is equal
168+
assert_eq!(cookie, cookie2);
169+
170+
// left has more params
171+
cookie.append("foo", "bar");
172+
assert!(cookie != cookie2);
173+
174+
// same len, different params
175+
cookie2.append("bar", "foo");
176+
assert!(cookie != cookie2);
177+
178+
179+
// right has more params, and matching KV
180+
cookie2.append("foo", "bar");
181+
assert!(cookie != cookie2);
182+
183+
// same params, different order
184+
cookie.append("bar", "foo");
185+
assert_eq!(cookie, cookie2);
186+
}
187+
188+
#[test]
189+
fn test_parse() {
190+
let mut cookie = Cookie::new();
191+
192+
let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap();
193+
cookie.append("foo", "bar");
194+
assert_eq!(cookie, parsed);
195+
196+
let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap();
197+
cookie.append("baz", "quux");
198+
assert_eq!(cookie, parsed);
199+
200+
let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap();
201+
assert_eq!(cookie, parsed);
202+
203+
let parsed = Cookie::parse_header(&vec![b"foo = bar".to_vec(),b"baz= quux ".to_vec()].into()).unwrap();
204+
assert_eq!(cookie, parsed);
205+
206+
Cookie::parse_header(&b"foo;bar=baz;quux".to_vec().into()).unwrap_err();
72207

73208
}
74209
}

src/header/internals/vec_map.rs

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ impl<K: PartialEq, V> VecMap<K, V> {
1919
}
2020
}
2121

22+
#[inline]
23+
pub fn append(&mut self, key: K, value: V) {
24+
self.vec.push((key, value));
25+
}
26+
2227
#[inline]
2328
pub fn entry(&mut self, key: K) -> Entry<K, V> {
2429
match self.find(&key) {

0 commit comments

Comments
 (0)