actori_http/header/common/
cache_control.rs

1use std::fmt::{self, Write};
2use std::str::FromStr;
3
4use http::header;
5
6use crate::header::{
7    fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer,
8};
9
10/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
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/// # Examples
31/// ```rust
32/// use actori_http::Response;
33/// use actori_http::http::header::{CacheControl, CacheDirective};
34///
35/// let mut builder = Response::Ok();
36/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
37/// ```
38///
39/// ```rust
40/// use actori_http::Response;
41/// use actori_http::http::header::{CacheControl, CacheDirective};
42///
43/// let mut builder = Response::Ok();
44/// builder.set(CacheControl(vec![
45///     CacheDirective::NoCache,
46///     CacheDirective::Private,
47///     CacheDirective::MaxAge(360u32),
48///     CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())),
49/// ]));
50/// ```
51#[derive(PartialEq, Clone, Debug)]
52pub struct CacheControl(pub Vec<CacheDirective>);
53
54__hyper__deref!(CacheControl => Vec<CacheDirective>);
55
56//TODO: this could just be the header! macro
57impl Header for CacheControl {
58    fn name() -> header::HeaderName {
59        header::CACHE_CONTROL
60    }
61
62    #[inline]
63    fn parse<T>(msg: &T) -> Result<Self, crate::error::ParseError>
64    where
65        T: crate::HttpMessage,
66    {
67        let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?;
68        if !directives.is_empty() {
69            Ok(CacheControl(directives))
70        } else {
71            Err(crate::error::ParseError::Header)
72        }
73    }
74}
75
76impl fmt::Display for CacheControl {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        fmt_comma_delimited(f, &self[..])
79    }
80}
81
82impl IntoHeaderValue for CacheControl {
83    type Error = header::InvalidHeaderValue;
84
85    fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
86        let mut writer = Writer::new();
87        let _ = write!(&mut writer, "{}", self);
88        header::HeaderValue::from_maybe_shared(writer.take())
89    }
90}
91
92/// `CacheControl` contains a list of these directives.
93#[derive(PartialEq, Clone, Debug)]
94pub enum CacheDirective {
95    /// "no-cache"
96    NoCache,
97    /// "no-store"
98    NoStore,
99    /// "no-transform"
100    NoTransform,
101    /// "only-if-cached"
102    OnlyIfCached,
103
104    // request directives
105    /// "max-age=delta"
106    MaxAge(u32),
107    /// "max-stale=delta"
108    MaxStale(u32),
109    /// "min-fresh=delta"
110    MinFresh(u32),
111
112    // response directives
113    /// "must-revalidate"
114    MustRevalidate,
115    /// "public"
116    Public,
117    /// "private"
118    Private,
119    /// "proxy-revalidate"
120    ProxyRevalidate,
121    /// "s-maxage=delta"
122    SMaxAge(u32),
123
124    /// Extension directives. Optionally include an argument.
125    Extension(String, Option<String>),
126}
127
128impl fmt::Display for CacheDirective {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        use self::CacheDirective::*;
131        fmt::Display::fmt(
132            match *self {
133                NoCache => "no-cache",
134                NoStore => "no-store",
135                NoTransform => "no-transform",
136                OnlyIfCached => "only-if-cached",
137
138                MaxAge(secs) => return write!(f, "max-age={}", secs),
139                MaxStale(secs) => return write!(f, "max-stale={}", secs),
140                MinFresh(secs) => return write!(f, "min-fresh={}", secs),
141
142                MustRevalidate => "must-revalidate",
143                Public => "public",
144                Private => "private",
145                ProxyRevalidate => "proxy-revalidate",
146                SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
147
148                Extension(ref name, None) => &name[..],
149                Extension(ref name, Some(ref arg)) => {
150                    return write!(f, "{}={}", name, arg);
151                }
152            },
153            f,
154        )
155    }
156}
157
158impl FromStr for CacheDirective {
159    type Err = Option<<u32 as FromStr>::Err>;
160    fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
161        use self::CacheDirective::*;
162        match s {
163            "no-cache" => Ok(NoCache),
164            "no-store" => Ok(NoStore),
165            "no-transform" => Ok(NoTransform),
166            "only-if-cached" => Ok(OnlyIfCached),
167            "must-revalidate" => Ok(MustRevalidate),
168            "public" => Ok(Public),
169            "private" => Ok(Private),
170            "proxy-revalidate" => Ok(ProxyRevalidate),
171            "" => Err(None),
172            _ => match s.find('=') {
173                Some(idx) if idx + 1 < s.len() => {
174                    match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
175                        ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some),
176                        ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
177                        ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
178                        ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
179                        (left, right) => {
180                            Ok(Extension(left.to_owned(), Some(right.to_owned())))
181                        }
182                    }
183                }
184                Some(_) => Err(None),
185                None => Ok(Extension(s.to_owned(), None)),
186            },
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::header::Header;
195    use crate::test::TestRequest;
196
197    #[test]
198    fn test_parse_multiple_headers() {
199        let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
200            .finish();
201        let cache = Header::parse(&req);
202        assert_eq!(
203            cache.ok(),
204            Some(CacheControl(vec![
205                CacheDirective::NoCache,
206                CacheDirective::Private,
207            ]))
208        )
209    }
210
211    #[test]
212    fn test_parse_argument() {
213        let req =
214            TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
215                .finish();
216        let cache = Header::parse(&req);
217        assert_eq!(
218            cache.ok(),
219            Some(CacheControl(vec![
220                CacheDirective::MaxAge(100),
221                CacheDirective::Private,
222            ]))
223        )
224    }
225
226    #[test]
227    fn test_parse_quote_form() {
228        let req =
229            TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
230        let cache = Header::parse(&req);
231        assert_eq!(
232            cache.ok(),
233            Some(CacheControl(vec![CacheDirective::MaxAge(200)]))
234        )
235    }
236
237    #[test]
238    fn test_parse_extension() {
239        let req =
240            TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
241        let cache = Header::parse(&req);
242        assert_eq!(
243            cache.ok(),
244            Some(CacheControl(vec![
245                CacheDirective::Extension("foo".to_owned(), None),
246                CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())),
247            ]))
248        )
249    }
250
251    #[test]
252    fn test_parse_bad_syntax() {
253        let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
254        let cache: Result<CacheControl, _> = Header::parse(&req);
255        assert_eq!(cache.ok(), None)
256    }
257}