headers/common/
cache_control.rs

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/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
10/// with extensions in [RFC8246](https://www.rfc-editor.org/rfc/rfc8246)
11///
12/// The `Cache-Control` header field is used to specify directives for
13/// caches along the request/response chain.  Such cache directives are
14/// unidirectional in that the presence of a directive in a request does
15/// not imply that the same directive is to be given in the response.
16///
17/// ## ABNF
18///
19/// ```text
20/// Cache-Control   = 1#cache-directive
21/// cache-directive = token [ "=" ( token / quoted-string ) ]
22/// ```
23///
24/// ## Example values
25///
26/// * `no-cache`
27/// * `private, community="UCI"`
28/// * `max-age=30`
29///
30/// # Example
31///
32/// ```
33/// # extern crate headers;
34/// use headers::CacheControl;
35///
36/// let cc = CacheControl::new();
37/// ```
38#[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    /// Construct a new empty `CacheControl` header.
79    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    // getters
90
91    /// Check if the `no-cache` directive is set.
92    pub fn no_cache(&self) -> bool {
93        self.flags.contains(Flags::NO_CACHE)
94    }
95
96    /// Check if the `no-store` directive is set.
97    pub fn no_store(&self) -> bool {
98        self.flags.contains(Flags::NO_STORE)
99    }
100
101    /// Check if the `no-transform` directive is set.
102    pub fn no_transform(&self) -> bool {
103        self.flags.contains(Flags::NO_TRANSFORM)
104    }
105
106    /// Check if the `only-if-cached` directive is set.
107    pub fn only_if_cached(&self) -> bool {
108        self.flags.contains(Flags::ONLY_IF_CACHED)
109    }
110
111    /// Check if the `public` directive is set.
112    pub fn public(&self) -> bool {
113        self.flags.contains(Flags::PUBLIC)
114    }
115
116    /// Check if the `private` directive is set.
117    pub fn private(&self) -> bool {
118        self.flags.contains(Flags::PRIVATE)
119    }
120
121    /// Check if the `immutable` directive is set.
122    pub fn immutable(&self) -> bool {
123        self.flags.contains(Flags::IMMUTABLE)
124    }
125    /// Check if the `must_understand` directive is set.
126    pub fn must_understand(&self) -> bool {
127        self.flags.contains(Flags::MUST_UNDERSTAND)
128    }
129
130    /// Get the value of the `max-age` directive if set.
131    pub fn max_age(&self) -> Option<Duration> {
132        self.max_age.map(Into::into)
133    }
134
135    /// Get the value of the `max-stale` directive if set.
136    pub fn max_stale(&self) -> Option<Duration> {
137        self.max_stale.map(Into::into)
138    }
139
140    /// Get the value of the `min-fresh` directive if set.
141    pub fn min_fresh(&self) -> Option<Duration> {
142        self.min_fresh.map(Into::into)
143    }
144
145    /// Get the value of the `s-maxage` directive if set.
146    pub fn s_max_age(&self) -> Option<Duration> {
147        self.s_max_age.map(Into::into)
148    }
149
150    // setters
151
152    /// Set the `no-cache` directive.
153    pub fn with_no_cache(mut self) -> Self {
154        self.flags.insert(Flags::NO_CACHE);
155        self
156    }
157
158    /// Set the `no-store` directive.
159    pub fn with_no_store(mut self) -> Self {
160        self.flags.insert(Flags::NO_STORE);
161        self
162    }
163
164    /// Set the `no-transform` directive.
165    pub fn with_no_transform(mut self) -> Self {
166        self.flags.insert(Flags::NO_TRANSFORM);
167        self
168    }
169
170    /// Set the `only-if-cached` directive.
171    pub fn with_only_if_cached(mut self) -> Self {
172        self.flags.insert(Flags::ONLY_IF_CACHED);
173        self
174    }
175
176    /// Set the `private` directive.
177    pub fn with_private(mut self) -> Self {
178        self.flags.insert(Flags::PRIVATE);
179        self
180    }
181
182    /// Set the `public` directive.
183    pub fn with_public(mut self) -> Self {
184        self.flags.insert(Flags::PUBLIC);
185        self
186    }
187
188    /// Set the `immutable` directive.
189    pub fn with_immutable(mut self) -> Self {
190        self.flags.insert(Flags::IMMUTABLE);
191        self
192    }
193
194    /// Set the `must_understand` directive.
195    pub fn with_must_understand(mut self) -> Self {
196        self.flags.insert(Flags::MUST_UNDERSTAND);
197        self
198    }
199    /// Set the `max-age` directive.
200    pub fn with_max_age(mut self, duration: Duration) -> Self {
201        self.max_age = Some(duration.into());
202        self
203    }
204
205    /// Set the `max-stale` directive.
206    pub fn with_max_stale(mut self, duration: Duration) -> Self {
207        self.max_stale = Some(duration.into());
208        self
209    }
210
211    /// Set the `min-fresh` directive.
212    pub fn with_min_fresh(mut self, duration: Duration) -> Self {
213        self.min_fresh = Some(duration.into());
214        self
215    }
216
217    /// Set the `s-maxage` directive.
218    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
238// Adapter to be used in Header::decode
239struct 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        // ignore all unknown directives
249        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    // request directives
366    MaxAge(u64),
367    MaxStale(u64),
368    MinFresh(u64),
369
370    // response directives
371    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}