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)]
39pub struct CacheControl {
40 flags: Flags,
41 max_age: Option<Seconds>,
42 max_stale: Option<Seconds>,
43 min_fresh: Option<Seconds>,
44 s_max_age: Option<Seconds>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48struct Flags {
49 bits: u64,
50}
51
52impl Flags {
53 const NO_CACHE: Self = Self { bits: 0b000000001 };
54 const NO_STORE: Self = Self { bits: 0b000000010 };
55 const NO_TRANSFORM: Self = Self { bits: 0b000000100 };
56 const ONLY_IF_CACHED: Self = Self { bits: 0b000001000 };
57 const MUST_REVALIDATE: Self = Self { bits: 0b000010000 };
58 const PUBLIC: Self = Self { bits: 0b000100000 };
59 const PRIVATE: Self = Self { bits: 0b001000000 };
60 const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 };
61 const IMMUTABLE: Self = Self { bits: 0b100000000 };
62 const MUST_UNDERSTAND: Self = Self { bits: 0b1000000000 };
63
64 fn empty() -> Self {
65 Self { bits: 0 }
66 }
67
68 fn contains(&self, flag: Self) -> bool {
69 (self.bits & flag.bits) != 0
70 }
71
72 fn insert(&mut self, flag: Self) {
73 self.bits |= flag.bits;
74 }
75}
76
77impl CacheControl {
78 pub fn new() -> Self {
80 CacheControl {
81 flags: Flags::empty(),
82 max_age: None,
83 max_stale: None,
84 min_fresh: None,
85 s_max_age: None,
86 }
87 }
88
89 pub fn no_cache(&self) -> bool {
93 self.flags.contains(Flags::NO_CACHE)
94 }
95
96 pub fn no_store(&self) -> bool {
98 self.flags.contains(Flags::NO_STORE)
99 }
100
101 pub fn no_transform(&self) -> bool {
103 self.flags.contains(Flags::NO_TRANSFORM)
104 }
105
106 pub fn only_if_cached(&self) -> bool {
108 self.flags.contains(Flags::ONLY_IF_CACHED)
109 }
110
111 pub fn public(&self) -> bool {
113 self.flags.contains(Flags::PUBLIC)
114 }
115
116 pub fn private(&self) -> bool {
118 self.flags.contains(Flags::PRIVATE)
119 }
120
121 pub fn immutable(&self) -> bool {
123 self.flags.contains(Flags::IMMUTABLE)
124 }
125 pub fn must_understand(&self) -> bool {
127 self.flags.contains(Flags::MUST_UNDERSTAND)
128 }
129
130 pub fn max_age(&self) -> Option<Duration> {
132 self.max_age.map(Into::into)
133 }
134
135 pub fn max_stale(&self) -> Option<Duration> {
137 self.max_stale.map(Into::into)
138 }
139
140 pub fn min_fresh(&self) -> Option<Duration> {
142 self.min_fresh.map(Into::into)
143 }
144
145 pub fn s_max_age(&self) -> Option<Duration> {
147 self.s_max_age.map(Into::into)
148 }
149
150 pub fn with_no_cache(mut self) -> Self {
154 self.flags.insert(Flags::NO_CACHE);
155 self
156 }
157
158 pub fn with_no_store(mut self) -> Self {
160 self.flags.insert(Flags::NO_STORE);
161 self
162 }
163
164 pub fn with_no_transform(mut self) -> Self {
166 self.flags.insert(Flags::NO_TRANSFORM);
167 self
168 }
169
170 pub fn with_only_if_cached(mut self) -> Self {
172 self.flags.insert(Flags::ONLY_IF_CACHED);
173 self
174 }
175
176 pub fn with_private(mut self) -> Self {
178 self.flags.insert(Flags::PRIVATE);
179 self
180 }
181
182 pub fn with_public(mut self) -> Self {
184 self.flags.insert(Flags::PUBLIC);
185 self
186 }
187
188 pub fn with_immutable(mut self) -> Self {
190 self.flags.insert(Flags::IMMUTABLE);
191 self
192 }
193
194 pub fn with_must_understand(mut self) -> Self {
196 self.flags.insert(Flags::MUST_UNDERSTAND);
197 self
198 }
199 pub fn with_max_age(mut self, duration: Duration) -> Self {
201 self.max_age = Some(duration.into());
202 self
203 }
204
205 pub fn with_max_stale(mut self, duration: Duration) -> Self {
207 self.max_stale = Some(duration.into());
208 self
209 }
210
211 pub fn with_min_fresh(mut self, duration: Duration) -> Self {
213 self.min_fresh = Some(duration.into());
214 self
215 }
216
217 pub fn with_s_max_age(mut self, duration: Duration) -> Self {
219 self.s_max_age = Some(duration.into());
220 self
221 }
222}
223
224impl ::Header for CacheControl {
225 fn name() -> &'static ::HeaderName {
226 &::http::header::CACHE_CONTROL
227 }
228
229 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
230 csv::from_comma_delimited(values).map(|FromIter(cc)| cc)
231 }
232
233 fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
234 values.extend(::std::iter::once(util::fmt(Fmt(self))));
235 }
236}
237
238struct FromIter(CacheControl);
240
241impl FromIterator<KnownDirective> for FromIter {
242 fn from_iter<I>(iter: I) -> Self
243 where
244 I: IntoIterator<Item = KnownDirective>,
245 {
246 let mut cc = CacheControl::new();
247
248 let iter = iter.into_iter().filter_map(|dir| match dir {
250 KnownDirective::Known(dir) => Some(dir),
251 KnownDirective::Unknown => None,
252 });
253
254 for directive in iter {
255 match directive {
256 Directive::NoCache => {
257 cc.flags.insert(Flags::NO_CACHE);
258 }
259 Directive::NoStore => {
260 cc.flags.insert(Flags::NO_STORE);
261 }
262 Directive::NoTransform => {
263 cc.flags.insert(Flags::NO_TRANSFORM);
264 }
265 Directive::OnlyIfCached => {
266 cc.flags.insert(Flags::ONLY_IF_CACHED);
267 }
268 Directive::MustRevalidate => {
269 cc.flags.insert(Flags::MUST_REVALIDATE);
270 }
271 Directive::MustUnderstand => {
272 cc.flags.insert(Flags::MUST_UNDERSTAND);
273 }
274 Directive::Public => {
275 cc.flags.insert(Flags::PUBLIC);
276 }
277 Directive::Private => {
278 cc.flags.insert(Flags::PRIVATE);
279 }
280 Directive::Immutable => {
281 cc.flags.insert(Flags::IMMUTABLE);
282 }
283 Directive::ProxyRevalidate => {
284 cc.flags.insert(Flags::PROXY_REVALIDATE);
285 }
286 Directive::MaxAge(secs) => {
287 cc.max_age = Some(Duration::from_secs(secs.into()).into());
288 }
289 Directive::MaxStale(secs) => {
290 cc.max_stale = Some(Duration::from_secs(secs.into()).into());
291 }
292 Directive::MinFresh(secs) => {
293 cc.min_fresh = Some(Duration::from_secs(secs.into()).into());
294 }
295 Directive::SMaxAge(secs) => {
296 cc.s_max_age = Some(Duration::from_secs(secs.into()).into());
297 }
298 }
299 }
300
301 FromIter(cc)
302 }
303}
304
305struct Fmt<'a>(&'a CacheControl);
306
307impl<'a> fmt::Display for Fmt<'a> {
308 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
309 let if_flag = |f: Flags, dir: Directive| {
310 if self.0.flags.contains(f) {
311 Some(dir)
312 } else {
313 None
314 }
315 };
316
317 let slice = &[
318 if_flag(Flags::NO_CACHE, Directive::NoCache),
319 if_flag(Flags::NO_STORE, Directive::NoStore),
320 if_flag(Flags::NO_TRANSFORM, Directive::NoTransform),
321 if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached),
322 if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate),
323 if_flag(Flags::PUBLIC, Directive::Public),
324 if_flag(Flags::PRIVATE, Directive::Private),
325 if_flag(Flags::IMMUTABLE, Directive::Immutable),
326 if_flag(Flags::MUST_UNDERSTAND, Directive::MustUnderstand),
327 if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate),
328 self.0
329 .max_age
330 .as_ref()
331 .map(|s| Directive::MaxAge(s.as_u64())),
332 self.0
333 .max_stale
334 .as_ref()
335 .map(|s| Directive::MaxStale(s.as_u64())),
336 self.0
337 .min_fresh
338 .as_ref()
339 .map(|s| Directive::MinFresh(s.as_u64())),
340 self.0
341 .s_max_age
342 .as_ref()
343 .map(|s| Directive::SMaxAge(s.as_u64())),
344 ];
345
346 let iter = slice.iter().filter_map(|o| *o);
347
348 csv::fmt_comma_delimited(f, iter)
349 }
350}
351
352#[derive(Clone, Copy)]
353enum KnownDirective {
354 Known(Directive),
355 Unknown,
356}
357
358#[derive(Clone, Copy)]
359enum Directive {
360 NoCache,
361 NoStore,
362 NoTransform,
363 OnlyIfCached,
364
365 MaxAge(u64),
367 MaxStale(u64),
368 MinFresh(u64),
369
370 MustRevalidate,
372 MustUnderstand,
373 Public,
374 Private,
375 Immutable,
376 ProxyRevalidate,
377 SMaxAge(u64),
378}
379
380impl fmt::Display for Directive {
381 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
382 fmt::Display::fmt(
383 match *self {
384 Directive::NoCache => "no-cache",
385 Directive::NoStore => "no-store",
386 Directive::NoTransform => "no-transform",
387 Directive::OnlyIfCached => "only-if-cached",
388
389 Directive::MaxAge(secs) => return write!(f, "max-age={}", secs),
390 Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs),
391 Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs),
392
393 Directive::MustRevalidate => "must-revalidate",
394 Directive::MustUnderstand => "must-understand",
395 Directive::Public => "public",
396 Directive::Private => "private",
397 Directive::Immutable => "immutable",
398 Directive::ProxyRevalidate => "proxy-revalidate",
399 Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
400 },
401 f,
402 )
403 }
404}
405
406impl FromStr for KnownDirective {
407 type Err = ();
408 fn from_str(s: &str) -> Result<Self, Self::Err> {
409 Ok(KnownDirective::Known(match s {
410 "no-cache" => Directive::NoCache,
411 "no-store" => Directive::NoStore,
412 "no-transform" => Directive::NoTransform,
413 "only-if-cached" => Directive::OnlyIfCached,
414 "must-revalidate" => Directive::MustRevalidate,
415 "public" => Directive::Public,
416 "private" => Directive::Private,
417 "immutable" => Directive::Immutable,
418 "must-understand" => Directive::MustUnderstand,
419 "proxy-revalidate" => Directive::ProxyRevalidate,
420 "" => return Err(()),
421 _ => match s.find('=') {
422 Some(idx) if idx + 1 < s.len() => {
423 match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
424 ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?,
425 ("max-stale", secs) => {
426 secs.parse().map(Directive::MaxStale).map_err(|_| ())?
427 }
428 ("min-fresh", secs) => {
429 secs.parse().map(Directive::MinFresh).map_err(|_| ())?
430 }
431 ("s-maxage", secs) => {
432 secs.parse().map(Directive::SMaxAge).map_err(|_| ())?
433 }
434 _unknown => return Ok(KnownDirective::Unknown),
435 }
436 }
437 Some(_) | None => return Ok(KnownDirective::Unknown),
438 },
439 }))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::super::{test_decode, test_encode};
446 use super::*;
447
448 #[test]
449 fn test_parse_multiple_headers() {
450 assert_eq!(
451 test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(),
452 CacheControl::new().with_no_cache().with_private(),
453 );
454 }
455
456 #[test]
457 fn test_parse_argument() {
458 assert_eq!(
459 test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(),
460 CacheControl::new()
461 .with_max_age(Duration::from_secs(100))
462 .with_private(),
463 );
464 }
465
466 #[test]
467 fn test_parse_quote_form() {
468 assert_eq!(
469 test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(),
470 CacheControl::new().with_max_age(Duration::from_secs(200)),
471 );
472 }
473
474 #[test]
475 fn test_parse_extension() {
476 assert_eq!(
477 test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(),
478 CacheControl::new().with_no_cache(),
479 "unknown extensions are ignored but shouldn't fail parsing",
480 );
481 }
482
483 #[test]
484 fn test_immutable() {
485 let cc = CacheControl::new().with_immutable();
486 let headers = test_encode(cc.clone());
487 assert_eq!(headers["cache-control"], "immutable");
488 assert_eq!(test_decode::<CacheControl>(&["immutable"]).unwrap(), cc);
489 assert!(cc.immutable());
490 }
491
492 #[test]
493 fn test_must_understand() {
494 let cc = CacheControl::new().with_must_understand();
495 let headers = test_encode(cc.clone());
496 assert_eq!(headers["cache-control"], "must-understand");
497 assert_eq!(
498 test_decode::<CacheControl>(&["must-understand"]).unwrap(),
499 cc
500 );
501 assert!(cc.must_understand());
502 }
503
504 #[test]
505 fn test_parse_bad_syntax() {
506 assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None);
507 }
508
509 #[test]
510 fn encode_one_flag_directive() {
511 let cc = CacheControl::new().with_no_cache();
512
513 let headers = test_encode(cc);
514 assert_eq!(headers["cache-control"], "no-cache");
515 }
516
517 #[test]
518 fn encode_one_param_directive() {
519 let cc = CacheControl::new().with_max_age(Duration::from_secs(300));
520
521 let headers = test_encode(cc);
522 assert_eq!(headers["cache-control"], "max-age=300");
523 }
524
525 #[test]
526 fn encode_two_directive() {
527 let headers = test_encode(CacheControl::new().with_no_cache().with_private());
528 assert_eq!(headers["cache-control"], "no-cache, private");
529
530 let headers = test_encode(
531 CacheControl::new()
532 .with_no_cache()
533 .with_max_age(Duration::from_secs(100)),
534 );
535 assert_eq!(headers["cache-control"], "no-cache, max-age=100");
536 }
537}