1use std::fmt;
2use std::str::FromStr;
3use crate::header::{Header, HeaderFormat};
4use crate::header::parsing::{from_comma_delimited, fmt_comma_delimited};
5
6#[derive(PartialEq, Clone, Debug)]
48pub struct CacheControl(pub Vec<CacheDirective>);
49
50__hyper__deref!(CacheControl => Vec<CacheDirective>);
51
52impl Header for CacheControl {
53 fn header_name() -> &'static str {
54 "Cache-Control"
55 }
56
57 fn parse_header(raw: &[Vec<u8>]) -> crate::Result<CacheControl> {
58 let directives = r#try!(from_comma_delimited(raw));
59 if !directives.is_empty() {
60 Ok(CacheControl(directives))
61 } else {
62 Err(crate::Error::Header)
63 }
64 }
65}
66
67impl HeaderFormat for CacheControl {
68 fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 fmt::Display::fmt(self, f)
70 }
71}
72
73impl fmt::Display for CacheControl {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 fmt_comma_delimited(f, &self[..])
76 }
77}
78
79#[derive(PartialEq, Clone, Debug)]
81pub enum CacheDirective {
82 NoCache,
84 NoStore,
86 NoTransform,
88 OnlyIfCached,
90
91 MaxAge(u32),
94 MaxStale(u32),
96 MinFresh(u32),
98
99 MustRevalidate,
102 Public,
104 Private,
106 ProxyRevalidate,
108 SMaxAge(u32),
110
111 Extension(String, Option<String>)
113}
114
115impl fmt::Display for CacheDirective {
116 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117 use self::CacheDirective::*;
118 fmt::Display::fmt(match *self {
119 NoCache => "no-cache",
120 NoStore => "no-store",
121 NoTransform => "no-transform",
122 OnlyIfCached => "only-if-cached",
123
124 MaxAge(secs) => return write!(f, "max-age={}", secs),
125 MaxStale(secs) => return write!(f, "max-stale={}", secs),
126 MinFresh(secs) => return write!(f, "min-fresh={}", secs),
127
128 MustRevalidate => "must-revalidate",
129 Public => "public",
130 Private => "private",
131 ProxyRevalidate => "proxy-revalidate",
132 SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
133
134 Extension(ref name, None) => &name[..],
135 Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg),
136
137 }, f)
138 }
139}
140
141impl FromStr for CacheDirective {
142 type Err = Option<<u32 as FromStr>::Err>;
143 fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
144 use self::CacheDirective::*;
145 match s {
146 "no-cache" => Ok(NoCache),
147 "no-store" => Ok(NoStore),
148 "no-transform" => Ok(NoTransform),
149 "only-if-cached" => Ok(OnlyIfCached),
150 "must-revalidate" => Ok(MustRevalidate),
151 "public" => Ok(Public),
152 "private" => Ok(Private),
153 "proxy-revalidate" => Ok(ProxyRevalidate),
154 "" => Err(None),
155 _ => match s.find('=') {
156 Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) {
157 ("max-age" , secs) => secs.parse().map(MaxAge).map_err(Some),
158 ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
159 ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
160 ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
161 (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned())))
162 },
163 Some(_) => Err(None),
164 None => Ok(Extension(s.to_owned(), None))
165 }
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::header::Header;
173 use super::*;
174
175 #[test]
176 fn test_parse_multiple_headers() {
177 let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]);
178 assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache,
179 CacheDirective::Private])))
180 }
181
182 #[test]
183 fn test_parse_argument() {
184 let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]);
185 assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100),
186 CacheDirective::Private])))
187 }
188
189 #[test]
190 fn test_parse_quote_form() {
191 let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]);
192 assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)])))
193 }
194
195 #[test]
196 fn test_parse_extension() {
197 let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]);
198 assert_eq!(cache.ok(), Some(CacheControl(vec![
199 CacheDirective::Extension("foo".to_owned(), None),
200 CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))])))
201 }
202
203 #[test]
204 fn test_parse_bad_syntax() {
205 let cache: crate::Result<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
206 assert_eq!(cache.ok(), None)
207 }
208}
209
210bench_header!(normal,
211 CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] });