1 use std::fmt;
2 use std::iter::FromIterator;
3 use std::str::FromStr;
4 use std::time::Duration;
5 
6 use util::{self, csv, Seconds};
7 use HeaderValue;
8 
9 /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
10 ///
11 /// The `Cache-Control` header field is used to specify directives for
12 /// caches along the request/response chain.  Such cache directives are
13 /// unidirectional in that the presence of a directive in a request does
14 /// not imply that the same directive is to be given in the response.
15 ///
16 /// ## ABNF
17 ///
18 /// ```text
19 /// Cache-Control   = 1#cache-directive
20 /// cache-directive = token [ "=" ( token / quoted-string ) ]
21 /// ```
22 ///
23 /// ## Example values
24 ///
25 /// * `no-cache`
26 /// * `private, community="UCI"`
27 /// * `max-age=30`
28 ///
29 /// # Example
30 ///
31 /// ```
32 /// # extern crate headers;
33 /// use headers::CacheControl;
34 ///
35 /// let cc = CacheControl::new();
36 /// ```
37 #[derive(PartialEq, Clone, Debug)]
38 pub 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 
46 bitflags! {
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 
59 impl CacheControl {
60     /// Construct a new empty `CacheControl` header.
new() -> Self61     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     // getters
72 
73     /// Check if the `no-cache` directive is set.
no_cache(&self) -> bool74     pub fn no_cache(&self) -> bool {
75         self.flags.contains(Flags::NO_CACHE)
76     }
77 
78     /// Check if the `no-store` directive is set.
no_store(&self) -> bool79     pub fn no_store(&self) -> bool {
80         self.flags.contains(Flags::NO_STORE)
81     }
82 
83     /// Check if the `no-transform` directive is set.
no_transform(&self) -> bool84     pub fn no_transform(&self) -> bool {
85         self.flags.contains(Flags::NO_TRANSFORM)
86     }
87 
88     /// Check if the `only-if-cached` directive is set.
only_if_cached(&self) -> bool89     pub fn only_if_cached(&self) -> bool {
90         self.flags.contains(Flags::ONLY_IF_CACHED)
91     }
92 
93     /// Check if the `public` directive is set.
public(&self) -> bool94     pub fn public(&self) -> bool {
95         self.flags.contains(Flags::PUBLIC)
96     }
97 
98     /// Check if the `private` directive is set.
private(&self) -> bool99     pub fn private(&self) -> bool {
100         self.flags.contains(Flags::PRIVATE)
101     }
102 
103     /// Get the value of the `max-age` directive if set.
max_age(&self) -> Option<Duration>104     pub fn max_age(&self) -> Option<Duration> {
105         self.max_age.map(Into::into)
106     }
107 
108     /// Get the value of the `max-stale` directive if set.
max_stale(&self) -> Option<Duration>109     pub fn max_stale(&self) -> Option<Duration> {
110         self.max_stale.map(Into::into)
111     }
112 
113     /// Get the value of the `min-fresh` directive if set.
min_fresh(&self) -> Option<Duration>114     pub fn min_fresh(&self) -> Option<Duration> {
115         self.min_fresh.map(Into::into)
116     }
117 
118     /// Get the value of the `s-maxage` directive if set.
s_max_age(&self) -> Option<Duration>119     pub fn s_max_age(&self) -> Option<Duration> {
120         self.s_max_age.map(Into::into)
121     }
122 
123     // setters
124 
125     /// Set the `no-cache` directive.
with_no_cache(mut self) -> Self126     pub fn with_no_cache(mut self) -> Self {
127         self.flags.insert(Flags::NO_CACHE);
128         self
129     }
130 
131     /// Set the `no-store` directive.
with_no_store(mut self) -> Self132     pub fn with_no_store(mut self) -> Self {
133         self.flags.insert(Flags::NO_STORE);
134         self
135     }
136 
137     /// Set the `no-transform` directive.
with_no_transform(mut self) -> Self138     pub fn with_no_transform(mut self) -> Self {
139         self.flags.insert(Flags::NO_TRANSFORM);
140         self
141     }
142 
143     /// Set the `only-if-cached` directive.
with_only_if_cached(mut self) -> Self144     pub fn with_only_if_cached(mut self) -> Self {
145         self.flags.insert(Flags::ONLY_IF_CACHED);
146         self
147     }
148 
149     /// Set the `private` directive.
with_private(mut self) -> Self150     pub fn with_private(mut self) -> Self {
151         self.flags.insert(Flags::PRIVATE);
152         self
153     }
154 
155     /// Set the `public` directive.
with_public(mut self) -> Self156     pub fn with_public(mut self) -> Self {
157         self.flags.insert(Flags::PUBLIC);
158         self
159     }
160 
161     /// Set the `max-age` directive.
with_max_age(mut self, seconds: Duration) -> Self162     pub fn with_max_age(mut self, seconds: Duration) -> Self {
163         self.max_age = Some(seconds.into());
164         self
165     }
166 
167     /// Set the `max-stale` directive.
with_max_stale(mut self, seconds: Duration) -> Self168     pub fn with_max_stale(mut self, seconds: Duration) -> Self {
169         self.max_stale = Some(seconds.into());
170         self
171     }
172 
173     /// Set the `min-fresh` directive.
with_min_fresh(mut self, seconds: Duration) -> Self174     pub fn with_min_fresh(mut self, seconds: Duration) -> Self {
175         self.min_fresh = Some(seconds.into());
176         self
177     }
178 
179     /// Set the `s-maxage` directive.
with_s_max_age(mut self, seconds: Duration) -> Self180     pub fn with_s_max_age(mut self, seconds: Duration) -> Self {
181         self.s_max_age = Some(seconds.into());
182         self
183     }
184 }
185 
186 impl ::Header for CacheControl {
name() -> &'static ::HeaderName187     fn name() -> &'static ::HeaderName {
188         &::http::header::CACHE_CONTROL
189     }
190 
decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error>191     fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
192         csv::from_comma_delimited(values).map(|FromIter(cc)| cc)
193     }
194 
encode<E: Extend<::HeaderValue>>(&self, values: &mut E)195     fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
196         values.extend(::std::iter::once(util::fmt(Fmt(self))));
197     }
198 }
199 
200 // Adapter to be used in Header::decode
201 struct FromIter(CacheControl);
202 
203 impl FromIterator<KnownDirective> for FromIter {
from_iter<I>(iter: I) -> Self where I: IntoIterator<Item = KnownDirective>,204     fn from_iter<I>(iter: I) -> Self
205     where
206         I: IntoIterator<Item = KnownDirective>,
207     {
208         let mut cc = CacheControl::new();
209 
210         // ignore all unknown directives
211         let iter = iter.into_iter().filter_map(|dir| match dir {
212             KnownDirective::Known(dir) => Some(dir),
213             KnownDirective::Unknown => None,
214         });
215 
216         for directive in iter {
217             match directive {
218                 Directive::NoCache => {
219                     cc.flags.insert(Flags::NO_CACHE);
220                 }
221                 Directive::NoStore => {
222                     cc.flags.insert(Flags::NO_STORE);
223                 }
224                 Directive::NoTransform => {
225                     cc.flags.insert(Flags::NO_TRANSFORM);
226                 }
227                 Directive::OnlyIfCached => {
228                     cc.flags.insert(Flags::ONLY_IF_CACHED);
229                 }
230                 Directive::MustRevalidate => {
231                     cc.flags.insert(Flags::MUST_REVALIDATE);
232                 }
233                 Directive::Public => {
234                     cc.flags.insert(Flags::PUBLIC);
235                 }
236                 Directive::Private => {
237                     cc.flags.insert(Flags::PRIVATE);
238                 }
239                 Directive::ProxyRevalidate => {
240                     cc.flags.insert(Flags::PROXY_REVALIDATE);
241                 }
242                 Directive::MaxAge(secs) => {
243                     cc.max_age = Some(Duration::from_secs(secs.into()).into());
244                 }
245                 Directive::MaxStale(secs) => {
246                     cc.max_stale = Some(Duration::from_secs(secs.into()).into());
247                 }
248                 Directive::MinFresh(secs) => {
249                     cc.min_fresh = Some(Duration::from_secs(secs.into()).into());
250                 }
251                 Directive::SMaxAge(secs) => {
252                     cc.s_max_age = Some(Duration::from_secs(secs.into()).into());
253                 }
254             }
255         }
256 
257         FromIter(cc)
258     }
259 }
260 
261 struct Fmt<'a>(&'a CacheControl);
262 
263 impl<'a> fmt::Display for Fmt<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result264     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265         let if_flag = |f: Flags, dir: Directive| {
266             if self.0.flags.contains(f) {
267                 Some(dir)
268             } else {
269                 None
270             }
271         };
272 
273         let slice = &[
274             if_flag(Flags::NO_CACHE, Directive::NoCache),
275             if_flag(Flags::NO_STORE, Directive::NoStore),
276             if_flag(Flags::NO_TRANSFORM, Directive::NoTransform),
277             if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached),
278             if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate),
279             if_flag(Flags::PUBLIC, Directive::Public),
280             if_flag(Flags::PRIVATE, Directive::Private),
281             if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate),
282             self.0
283                 .max_age
284                 .as_ref()
285                 .map(|s| Directive::MaxAge(s.as_u64())),
286             self.0
287                 .max_stale
288                 .as_ref()
289                 .map(|s| Directive::MaxStale(s.as_u64())),
290             self.0
291                 .min_fresh
292                 .as_ref()
293                 .map(|s| Directive::MinFresh(s.as_u64())),
294             self.0
295                 .s_max_age
296                 .as_ref()
297                 .map(|s| Directive::SMaxAge(s.as_u64())),
298         ];
299 
300         let iter = slice.iter().filter_map(|o| *o);
301 
302         csv::fmt_comma_delimited(f, iter)
303     }
304 }
305 
306 #[derive(Clone, Copy)]
307 enum KnownDirective {
308     Known(Directive),
309     Unknown,
310 }
311 
312 #[derive(Clone, Copy)]
313 enum Directive {
314     NoCache,
315     NoStore,
316     NoTransform,
317     OnlyIfCached,
318 
319     // request directives
320     MaxAge(u64),
321     MaxStale(u64),
322     MinFresh(u64),
323 
324     // response directives
325     MustRevalidate,
326     Public,
327     Private,
328     ProxyRevalidate,
329     SMaxAge(u64),
330 }
331 
332 impl fmt::Display for Directive {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result333     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334         fmt::Display::fmt(
335             match *self {
336                 Directive::NoCache => "no-cache",
337                 Directive::NoStore => "no-store",
338                 Directive::NoTransform => "no-transform",
339                 Directive::OnlyIfCached => "only-if-cached",
340 
341                 Directive::MaxAge(secs) => return write!(f, "max-age={}", secs),
342                 Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs),
343                 Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs),
344 
345                 Directive::MustRevalidate => "must-revalidate",
346                 Directive::Public => "public",
347                 Directive::Private => "private",
348                 Directive::ProxyRevalidate => "proxy-revalidate",
349                 Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
350             },
351             f,
352         )
353     }
354 }
355 
356 impl FromStr for KnownDirective {
357     type Err = ();
from_str(s: &str) -> Result<Self, Self::Err>358     fn from_str(s: &str) -> Result<Self, Self::Err> {
359         Ok(KnownDirective::Known(match s {
360             "no-cache" => Directive::NoCache,
361             "no-store" => Directive::NoStore,
362             "no-transform" => Directive::NoTransform,
363             "only-if-cached" => Directive::OnlyIfCached,
364             "must-revalidate" => Directive::MustRevalidate,
365             "public" => Directive::Public,
366             "private" => Directive::Private,
367             "proxy-revalidate" => Directive::ProxyRevalidate,
368             "" => return Err(()),
369             _ => match s.find('=') {
370                 Some(idx) if idx + 1 < s.len() => {
371                     match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
372                         ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?,
373                         ("max-stale", secs) => {
374                             secs.parse().map(Directive::MaxStale).map_err(|_| ())?
375                         }
376                         ("min-fresh", secs) => {
377                             secs.parse().map(Directive::MinFresh).map_err(|_| ())?
378                         }
379                         ("s-maxage", secs) => {
380                             secs.parse().map(Directive::SMaxAge).map_err(|_| ())?
381                         }
382                         _unknown => return Ok(KnownDirective::Unknown),
383                     }
384                 }
385                 Some(_) | None => return Ok(KnownDirective::Unknown),
386             },
387         }))
388     }
389 }
390 
391 #[cfg(test)]
392 mod tests {
393     use super::super::{test_decode, test_encode};
394     use super::*;
395 
396     #[test]
test_parse_multiple_headers()397     fn test_parse_multiple_headers() {
398         assert_eq!(
399             test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(),
400             CacheControl::new().with_no_cache().with_private(),
401         );
402     }
403 
404     #[test]
test_parse_argument()405     fn test_parse_argument() {
406         assert_eq!(
407             test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(),
408             CacheControl::new()
409                 .with_max_age(Duration::from_secs(100))
410                 .with_private(),
411         );
412     }
413 
414     #[test]
test_parse_quote_form()415     fn test_parse_quote_form() {
416         assert_eq!(
417             test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(),
418             CacheControl::new().with_max_age(Duration::from_secs(200)),
419         );
420     }
421 
422     #[test]
test_parse_extension()423     fn test_parse_extension() {
424         assert_eq!(
425             test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(),
426             CacheControl::new().with_no_cache(),
427             "unknown extensions are ignored but shouldn't fail parsing",
428         );
429     }
430 
431     #[test]
test_parse_bad_syntax()432     fn test_parse_bad_syntax() {
433         assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None,);
434     }
435 
436     #[test]
encode_one_flag_directive()437     fn encode_one_flag_directive() {
438         let cc = CacheControl::new().with_no_cache();
439 
440         let headers = test_encode(cc);
441         assert_eq!(headers["cache-control"], "no-cache");
442     }
443 
444     #[test]
encode_one_param_directive()445     fn encode_one_param_directive() {
446         let cc = CacheControl::new().with_max_age(Duration::from_secs(300));
447 
448         let headers = test_encode(cc);
449         assert_eq!(headers["cache-control"], "max-age=300");
450     }
451 
452     #[test]
encode_two_directive()453     fn encode_two_directive() {
454         let headers = test_encode(CacheControl::new().with_no_cache().with_private());
455         assert_eq!(headers["cache-control"], "no-cache, private");
456 
457         let headers = test_encode(
458             CacheControl::new()
459                 .with_no_cache()
460                 .with_max_age(Duration::from_secs(100)),
461         );
462         assert_eq!(headers["cache-control"], "no-cache, max-age=100");
463     }
464 }
465