1use std::fmt;
2use std::iter::FromIterator;
3use std::str::FromStr;
4use std::time::Duration;
5
6use util::{self, csv, Seconds};
7use {HeaderValue};
8
9#[derive(PartialEq, Clone, Debug)]
38pub struct CacheControl {
39 flags: Flags,
40 max_age: Option<Seconds>,
41 max_stale: Option<Seconds>,
42 min_fresh: Option<Seconds>,
43 s_max_age: Option<Seconds>,
44}
45
46bitflags! {
47 struct Flags: u32 {
48 const NO_CACHE = 0b00000001;
49 const NO_STORE = 0b00000010;
50 const NO_TRANSFORM = 0b00000100;
51 const ONLY_IF_CACHED = 0b00001000;
52 const MUST_REVALIDATE = 0b00010000;
53 const PUBLIC = 0b00100000;
54 const PRIVATE = 0b01000000;
55 const PROXY_REVALIDATE = 0b10000000;
56 }
57}
58
59impl CacheControl {
60 pub fn new() -> Self {
62 CacheControl {
63 flags: Flags::empty(),
64 max_age: None,
65 max_stale: None,
66 min_fresh: None,
67 s_max_age: None,
68 }
69 }
70
71 pub fn no_cache(&self) -> bool {
75 self.flags.contains(Flags::NO_CACHE)
76 }
77
78 pub fn no_store(&self) -> bool {
80 self.flags.contains(Flags::NO_STORE)
81 }
82
83 pub fn no_transform(&self) -> bool {
85 self.flags.contains(Flags::NO_TRANSFORM)
86 }
87
88 pub fn only_if_cached(&self) -> bool {
90 self.flags.contains(Flags::ONLY_IF_CACHED)
91 }
92
93 pub fn public(&self) -> bool {
95 self.flags.contains(Flags::PUBLIC)
96 }
97
98 pub fn private(&self) -> bool {
100 self.flags.contains(Flags::PRIVATE)
101 }
102
103 pub fn max_age(&self) -> Option<Duration> {
105 self.max_age.map(Into::into)
106 }
107
108 pub fn max_stale(&self) -> Option<Duration> {
110 self.max_stale.map(Into::into)
111 }
112
113
114 pub fn min_fresh(&self) -> Option<Duration> {
116 self.min_fresh.map(Into::into)
117 }
118
119 pub fn s_max_age(&self) -> Option<Duration> {
121 self.s_max_age.map(Into::into)
122 }
123
124 pub fn with_no_cache(mut self) -> Self {
128 self.flags.insert(Flags::NO_CACHE);
129 self
130 }
131
132 pub fn with_no_store(mut self) -> Self {
134 self.flags.insert(Flags::NO_STORE);
135 self
136 }
137
138 pub fn with_no_transform(mut self) -> Self {
140 self.flags.insert(Flags::NO_TRANSFORM);
141 self
142 }
143
144 pub fn with_only_if_cached(mut self) -> Self {
146 self.flags.insert(Flags::ONLY_IF_CACHED);
147 self
148 }
149
150 pub fn with_private(mut self) -> Self {
152 self.flags.insert(Flags::PRIVATE);
153 self
154 }
155
156 pub fn with_public(mut self) -> Self {
158 self.flags.insert(Flags::PUBLIC);
159 self
160 }
161
162 pub fn with_max_age(mut self, seconds: Duration) -> Self {
164 self.max_age = Some(seconds.into());
165 self
166 }
167
168 pub fn with_max_stale(mut self, seconds: Duration) -> Self {
170 self.max_stale = Some(seconds.into());
171 self
172 }
173
174 pub fn with_min_fresh(mut self, seconds: Duration) -> Self {
176 self.min_fresh = Some(seconds.into());
177 self
178 }
179
180 pub fn with_s_max_age(mut self, seconds: Duration) -> Self {
182 self.s_max_age = Some(seconds.into());
183 self
184 }
185}
186
187impl ::Header for CacheControl {
188 const NAME: &'static ::HeaderName = &::http::header::CACHE_CONTROL;
189
190 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
191 csv::from_comma_delimited(values)
192 .map(|FromIter(cc)| cc)
193
194 }
195
196 fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
197 values.extend(::std::iter::once(util::fmt(Fmt(self))));
198 }
199}
200
201struct FromIter(CacheControl);
203
204impl FromIterator<KnownDirective> for FromIter {
205 fn from_iter<I>(iter: I) -> Self
206 where
207 I: IntoIterator<Item=KnownDirective>,
208 {
209 let mut cc = CacheControl::new();
210
211 let iter = iter
213 .into_iter()
214 .filter_map(|dir| match dir {
215 KnownDirective::Known(dir) => Some(dir),
216 KnownDirective::Unknown => None,
217 });
218
219 for directive in iter {
220 match directive {
221 Directive::NoCache => {
222 cc.flags.insert(Flags::NO_CACHE);
223 },
224 Directive::NoStore => {
225 cc.flags.insert(Flags::NO_STORE);
226 },
227 Directive::NoTransform => {
228 cc.flags.insert(Flags::NO_TRANSFORM);
229 },
230 Directive::OnlyIfCached => {
231 cc.flags.insert(Flags::ONLY_IF_CACHED);
232 },
233 Directive::MustRevalidate => {
234 cc.flags.insert(Flags::MUST_REVALIDATE);
235 },
236 Directive::Public => {
237 cc.flags.insert(Flags::PUBLIC);
238 },
239 Directive::Private => {
240 cc.flags.insert(Flags::PRIVATE);
241 },
242 Directive::ProxyRevalidate => {
243 cc.flags.insert(Flags::PROXY_REVALIDATE);
244 },
245 Directive::MaxAge(secs) => {
246 cc.max_age = Some(Duration::from_secs(secs.into()).into());
247 },
248 Directive::MaxStale(secs) => {
249 cc.max_stale = Some(Duration::from_secs(secs.into()).into());
250 },
251 Directive::MinFresh(secs) => {
252 cc.min_fresh = Some(Duration::from_secs(secs.into()).into());
253 },
254 Directive::SMaxAge(secs) => {
255 cc.s_max_age = Some(Duration::from_secs(secs.into()).into());
256 },
257 }
258 }
259
260 FromIter(cc)
261 }
262}
263
264struct Fmt<'a>(&'a CacheControl);
265
266impl<'a> fmt::Display for Fmt<'a> {
267 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268 let if_flag = |f: Flags, dir: Directive| {
269 if self.0.flags.contains(f) {
270 Some(dir)
271 } else {
272 None
273 }
274 };
275
276 let slice = &[
277 if_flag(Flags::NO_CACHE, Directive::NoCache),
278 if_flag(Flags::NO_STORE, Directive::NoStore),
279 if_flag(Flags::NO_TRANSFORM, Directive::NoTransform),
280 if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached),
281 if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate),
282 if_flag(Flags::PUBLIC, Directive::Public),
283 if_flag(Flags::PRIVATE, Directive::Private),
284 if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate),
285 self.0.max_age.as_ref().map(|s| Directive::MaxAge(s.as_u64())),
286 self.0.max_stale.as_ref().map(|s| Directive::MaxStale(s.as_u64())),
287 self.0.min_fresh.as_ref().map(|s| Directive::MinFresh(s.as_u64())),
288 self.0.s_max_age.as_ref().map(|s| Directive::SMaxAge(s.as_u64())),
289 ];
290
291 let iter = slice
292 .iter()
293 .filter_map(|o| *o);
294
295 csv::fmt_comma_delimited(f, iter)
296 }
297}
298
299#[derive(Clone, Copy)]
300enum KnownDirective {
301 Known(Directive),
302 Unknown,
303}
304
305#[derive(Clone, Copy)]
306enum Directive {
307 NoCache,
308 NoStore,
309 NoTransform,
310 OnlyIfCached,
311
312 MaxAge(u64),
314 MaxStale(u64),
315 MinFresh(u64),
316
317 MustRevalidate,
319 Public,
320 Private,
321 ProxyRevalidate,
322 SMaxAge(u64),
323}
324
325impl fmt::Display for Directive {
326 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
327 fmt::Display::fmt(match *self {
328 Directive::NoCache => "no-cache",
329 Directive::NoStore => "no-store",
330 Directive::NoTransform => "no-transform",
331 Directive::OnlyIfCached => "only-if-cached",
332
333 Directive::MaxAge(secs) => return write!(f, "max-age={}", secs),
334 Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs),
335 Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs),
336
337 Directive::MustRevalidate => "must-revalidate",
338 Directive::Public => "public",
339 Directive::Private => "private",
340 Directive::ProxyRevalidate => "proxy-revalidate",
341 Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
342 }, f)
343 }
344}
345
346impl FromStr for KnownDirective {
347 type Err = ();
348 fn from_str(s: &str) -> Result<Self, Self::Err> {
349 Ok(KnownDirective::Known(match s {
350 "no-cache" => Directive::NoCache,
351 "no-store" => Directive::NoStore,
352 "no-transform" => Directive::NoTransform,
353 "only-if-cached" => Directive::OnlyIfCached,
354 "must-revalidate" => Directive::MustRevalidate,
355 "public" => Directive::Public,
356 "private" => Directive::Private,
357 "proxy-revalidate" => Directive::ProxyRevalidate,
358 "" => return Err(()),
359 _ => match s.find('=') {
360 Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) {
361 ("max-age" , secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?,
362 ("max-stale", secs) => secs.parse().map(Directive::MaxStale).map_err(|_| ())?,
363 ("min-fresh", secs) => secs.parse().map(Directive::MinFresh).map_err(|_| ())?,
364 ("s-maxage", secs) => secs.parse().map(Directive::SMaxAge).map_err(|_| ())?,
365 _unknown => return Ok(KnownDirective::Unknown),
366 },
367 Some(_) | None => return Ok(KnownDirective::Unknown),
368 }
369 }))
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use super::super::{test_decode, test_encode};
377
378 #[test]
379 fn test_parse_multiple_headers() {
380 assert_eq!(
381 test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(),
382 CacheControl::new()
383 .with_no_cache()
384 .with_private(),
385 );
386 }
387
388 #[test]
389 fn test_parse_argument() {
390 assert_eq!(
391 test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(),
392 CacheControl::new()
393 .with_max_age(Duration::from_secs(100))
394 .with_private(),
395 );
396 }
397
398 #[test]
399 fn test_parse_quote_form() {
400 assert_eq!(
401 test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(),
402 CacheControl::new()
403 .with_max_age(Duration::from_secs(200)),
404 );
405 }
406
407 #[test]
408 fn test_parse_extension() {
409 assert_eq!(
410 test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(),
411 CacheControl::new()
412 .with_no_cache(),
413 "unknown extensions are ignored but shouldn't fail parsing",
414 );
415 }
416
417 #[test]
418 fn test_parse_bad_syntax() {
419 assert_eq!(
420 test_decode::<CacheControl>(&["max-age=lolz"]),
421 None,
422 );
423 }
424
425 #[test]
426 fn encode_one_flag_directive() {
427 let cc = CacheControl::new()
428 .with_no_cache();
429
430 let headers = test_encode(cc);
431 assert_eq!(headers["cache-control"], "no-cache");
432 }
433
434 #[test]
435 fn encode_one_param_directive() {
436 let cc = CacheControl::new()
437 .with_max_age(Duration::from_secs(300));
438
439 let headers = test_encode(cc);
440 assert_eq!(headers["cache-control"], "max-age=300");
441 }
442
443 #[test]
444 fn encode_two_directive() {
445 let headers = test_encode(
446 CacheControl::new()
447 .with_no_cache()
448 .with_private()
449 );
450 assert_eq!(headers["cache-control"], "no-cache, private");
451
452 let headers = test_encode(
453 CacheControl::new()
454 .with_no_cache()
455 .with_max_age(Duration::from_secs(100))
456 );
457 assert_eq!(headers["cache-control"], "no-cache, max-age=100");
458 }
459}
460