1 use std::fmt::{self, Write};
2 use std::str::FromStr;
3 
4 use http::header;
5 
6 use 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 actix_http::Response;
33 /// use actix_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 actix_http::Response;
41 /// use actix_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)]
52 pub struct CacheControl(pub Vec<CacheDirective>);
53 
54 __hyper__deref!(CacheControl => Vec<CacheDirective>);
55 
56 //TODO: this could just be the header! macro
57 impl Header for CacheControl {
name() -> header::HeaderName58     fn name() -> header::HeaderName {
59         header::CACHE_CONTROL
60     }
61 
62     #[inline]
parse<T>(msg: &T) -> Result<Self, crate::error::ParseError> where T: crate::HttpMessage,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 
76 impl fmt::Display for CacheControl {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result77     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78         fmt_comma_delimited(f, &self[..])
79     }
80 }
81 
82 impl IntoHeaderValue for CacheControl {
83     type Error = header::InvalidHeaderValue;
84 
try_into(self) -> Result<header::HeaderValue, Self::Error>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)]
94 pub 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 
128 impl fmt::Display for CacheDirective {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result129     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 
158 impl FromStr for CacheDirective {
159     type Err = Option<<u32 as FromStr>::Err>;
from_str(s: &str) -> Result<CacheDirective, 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)]
192 mod tests {
193     use super::*;
194     use crate::header::Header;
195     use crate::test::TestRequest;
196 
197     #[test]
test_parse_multiple_headers()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]
test_parse_argument()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]
test_parse_quote_form()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]
test_parse_extension()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]
test_parse_bad_syntax()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 }
258