Skip to content

Commit f182f53

Browse files
committed
feat(headers): add CacheControl header
1 parent 0297147 commit f182f53

File tree

4 files changed

+179
-5
lines changed

4 files changed

+179
-5
lines changed

src/header/common/cache_control.rs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use std::fmt;
2+
use std::str::FromStr;
3+
use header::{Header, HeaderFormat};
4+
use super::util::{from_one_comma_delimited, fmt_comma_delimited};
5+
6+
/// The Cache-Control header.
7+
#[deriving(PartialEq, Clone, Show)]
8+
pub struct CacheControl(pub Vec<CacheDirective>);
9+
10+
deref!(CacheControl -> Vec<CacheDirective>)
11+
12+
impl Header for CacheControl {
13+
fn header_name(_: Option<CacheControl>) -> &'static str {
14+
"Cache-Control"
15+
}
16+
17+
fn parse_header(raw: &[Vec<u8>]) -> Option<CacheControl> {
18+
let directives = raw.iter()
19+
.filter_map(|line| from_one_comma_delimited(line[]))
20+
.collect::<Vec<Vec<CacheDirective>>>()
21+
.concat_vec();
22+
if directives.len() > 0 {
23+
Some(CacheControl(directives))
24+
} else {
25+
None
26+
}
27+
}
28+
}
29+
30+
impl HeaderFormat for CacheControl {
31+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
32+
fmt_comma_delimited(fmt, self[])
33+
}
34+
}
35+
36+
/// CacheControl contains a list of these directives.
37+
#[deriving(PartialEq, Clone)]
38+
pub enum CacheDirective {
39+
/// "no-cache"
40+
NoCache,
41+
/// "no-store"
42+
NoStore,
43+
/// "no-transform"
44+
NoTransform,
45+
/// "only-if-cached"
46+
OnlyIfCached,
47+
48+
// request directives
49+
/// "max-age=delta"
50+
MaxAge(uint),
51+
/// "max-stale=delta"
52+
MaxStale(uint),
53+
/// "min-fresh=delta"
54+
MinFresh(uint),
55+
56+
// response directives
57+
/// "must-revalidate"
58+
MustRevalidate,
59+
/// "public"
60+
Public,
61+
/// "private"
62+
Private,
63+
/// "proxy-revalidate"
64+
ProxyRevalidate,
65+
/// "s-maxage=delta"
66+
SMaxAge(uint),
67+
68+
/// Extension directives. Optionally include an argument.
69+
Extension(String, Option<String>)
70+
}
71+
72+
impl fmt::Show for CacheDirective {
73+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74+
use self::CacheDirective::*;
75+
match *self {
76+
NoCache => "no-cache",
77+
NoStore => "no-store",
78+
NoTransform => "no-transform",
79+
OnlyIfCached => "only-if-cached",
80+
81+
MaxAge(secs) => return write!(f, "max-age={}", secs),
82+
MaxStale(secs) => return write!(f, "max-stale={}", secs),
83+
MinFresh(secs) => return write!(f, "min-fresh={}", secs),
84+
85+
MustRevalidate => "must-revalidate",
86+
Public => "public",
87+
Private => "private",
88+
ProxyRevalidate => "proxy-revalidate",
89+
SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
90+
91+
Extension(ref name, None) => name[],
92+
Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg),
93+
94+
}.fmt(f)
95+
}
96+
}
97+
98+
impl FromStr for CacheDirective {
99+
fn from_str(s: &str) -> Option<CacheDirective> {
100+
use self::CacheDirective::*;
101+
match s {
102+
"no-cache" => Some(NoCache),
103+
"no-store" => Some(NoStore),
104+
"no-transform" => Some(NoTransform),
105+
"only-if-cached" => Some(OnlyIfCached),
106+
"must-revalidate" => Some(MustRevalidate),
107+
"public" => Some(Public),
108+
"private" => Some(Private),
109+
"proxy-revalidate" => Some(ProxyRevalidate),
110+
"" => None,
111+
_ => match s.find('=') {
112+
Some(idx) if idx+1 < s.len() => match (s[..idx], s[idx+1..].trim_chars('"')) {
113+
("max-age" , secs) => from_str::<uint>(secs).map(MaxAge),
114+
("max-stale", secs) => from_str::<uint>(secs).map(MaxStale),
115+
("min-fresh", secs) => from_str::<uint>(secs).map(MinFresh),
116+
("s-maxage", secs) => from_str::<uint>(secs).map(SMaxAge),
117+
(left, right) => Some(Extension(left.into_string(), Some(right.into_string())))
118+
},
119+
Some(_) => None,
120+
None => Some(Extension(s.into_string(), None))
121+
}
122+
}
123+
}
124+
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use header::Header;
129+
use super::*;
130+
131+
#[test]
132+
fn test_parse_multiple_headers() {
133+
let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]);
134+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::NoCache,
135+
CacheDirective::Private])))
136+
}
137+
138+
#[test]
139+
fn test_parse_argument() {
140+
let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]);
141+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(100),
142+
CacheDirective::Private])))
143+
}
144+
145+
#[test]
146+
fn test_parse_quote_form() {
147+
let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]);
148+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(200)])))
149+
}
150+
151+
#[test]
152+
fn test_parse_extension() {
153+
let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]);
154+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::Extension("foo".to_string(), None),
155+
CacheDirective::Extension("bar".to_string(), Some("baz".to_string()))])))
156+
}
157+
158+
#[test]
159+
fn test_parse_bad_syntax() {
160+
let cache: Option<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
161+
assert_eq!(cache, None)
162+
}
163+
}
164+
165+
bench_header!(normal, CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] })

src/header/common/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
99
pub use self::accept::Accept;
1010
pub use self::authorization::Authorization;
11+
pub use self::cache_control::CacheControl;
1112
pub use self::cookie::Cookies;
1213
pub use self::connection::Connection;
1314
pub use self::content_length::ContentLength;
@@ -83,6 +84,9 @@ pub mod accept;
8384
/// Exposes the Authorization header.
8485
pub mod authorization;
8586

87+
/// Exposes the CacheControl header.
88+
pub mod cache_control;
89+
8690
/// Exposes the Cookie header.
8791
pub mod cookie;
8892

src/header/common/transfer_encoding.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,13 @@ impl Header for TransferEncoding {
7676
}
7777

7878
fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> {
79-
from_comma_delimited(raw).map(|vec| TransferEncoding(vec))
79+
from_comma_delimited(raw).map(TransferEncoding)
8080
}
8181
}
8282

8383
impl HeaderFormat for TransferEncoding {
8484
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
85-
let TransferEncoding(ref parts) = *self;
86-
fmt_comma_delimited(fmt, parts[])
85+
fmt_comma_delimited(fmt, self[])
8786
}
8887
}
8988

src/header/common/util.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ pub fn from_one_raw_str<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> {
1616
}
1717
}
1818

19-
/// Reads a comma-delimited raw string into a Vec.
19+
/// Reads a comma-delimited raw header into a Vec.
20+
#[inline]
2021
pub fn from_comma_delimited<T: FromStr>(raw: &[Vec<u8>]) -> Option<Vec<T>> {
2122
if raw.len() != 1 {
2223
return None;
2324
}
2425
// we JUST checked that raw.len() == 1, so raw[0] WILL exist.
25-
match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) {
26+
from_one_comma_delimited(unsafe { raw.as_slice().unsafe_get(0).as_slice() })
27+
}
28+
29+
/// Reads a comma-delimited raw string into a Vec.
30+
pub fn from_one_comma_delimited<T: FromStr>(raw: &[u8]) -> Option<Vec<T>> {
31+
match from_utf8(raw) {
2632
Some(s) => {
2733
Some(s.as_slice()
2834
.split([',', ' '].as_slice())

0 commit comments

Comments
 (0)