1 use std::fmt;
2 use std::str::FromStr;
3 use header::{Header, HeaderFormat};
4 use header::parsing::{from_comma_delimited, fmt_comma_delimited};
5 
6 /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
7 ///
8 /// The `Cache-Control` header field is used to specify directives for
9 /// caches along the request/response chain.  Such cache directives are
10 /// unidirectional in that the presence of a directive in a request does
11 /// not imply that the same directive is to be given in the response.
12 ///
13 /// # ABNF
14 /// ```plain
15 /// Cache-Control   = 1#cache-directive
16 /// cache-directive = token [ "=" ( token / quoted-string ) ]
17 /// ```
18 ///
19 /// # Example values
20 /// * `no-cache`
21 /// * `private, community="UCI"`
22 /// * `max-age=30`
23 ///
24 /// # Examples
25 /// ```
26 /// use hyper::header::{Headers, CacheControl, CacheDirective};
27 ///
28 /// let mut headers = Headers::new();
29 /// headers.set(
30 ///     CacheControl(vec![CacheDirective::MaxAge(86400u32)])
31 /// );
32 /// ```
33 /// ```
34 /// use hyper::header::{Headers, CacheControl, CacheDirective};
35 ///
36 /// let mut headers = Headers::new();
37 /// headers.set(
38 ///     CacheControl(vec![
39 ///         CacheDirective::NoCache,
40 ///         CacheDirective::Private,
41 ///         CacheDirective::MaxAge(360u32),
42 ///         CacheDirective::Extension("foo".to_owned(),
43 ///                                   Some("bar".to_owned())),
44 ///     ])
45 /// );
46 /// ```
47 #[derive(PartialEq, Clone, Debug)]
48 pub struct CacheControl(pub Vec<CacheDirective>);
49 
50 __hyper__deref!(CacheControl => Vec<CacheDirective>);
51 
52 impl Header for CacheControl {
header_name() -> &'static str53     fn header_name() -> &'static str {
54         "Cache-Control"
55     }
56 
parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl>57     fn parse_header(raw: &[Vec<u8>]) -> ::Result<CacheControl> {
58         let directives = try!(from_comma_delimited(raw));
59         if !directives.is_empty() {
60             Ok(CacheControl(directives))
61         } else {
62             Err(::Error::Header)
63         }
64     }
65 }
66 
67 impl HeaderFormat for CacheControl {
fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result68     fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
69         fmt::Display::fmt(self, f)
70     }
71 }
72 
73 impl fmt::Display for CacheControl {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result74     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75         fmt_comma_delimited(f, &self[..])
76     }
77 }
78 
79 /// `CacheControl` contains a list of these directives.
80 #[derive(PartialEq, Clone, Debug)]
81 pub enum CacheDirective {
82     /// "no-cache"
83     NoCache,
84     /// "no-store"
85     NoStore,
86     /// "no-transform"
87     NoTransform,
88     /// "only-if-cached"
89     OnlyIfCached,
90 
91     // request directives
92     /// "max-age=delta"
93     MaxAge(u32),
94     /// "max-stale=delta"
95     MaxStale(u32),
96     /// "min-fresh=delta"
97     MinFresh(u32),
98 
99     // response directives
100     /// "must-revalidate"
101     MustRevalidate,
102     /// "public"
103     Public,
104     /// "private"
105     Private,
106     /// "proxy-revalidate"
107     ProxyRevalidate,
108     /// "s-maxage=delta"
109     SMaxAge(u32),
110 
111     /// Extension directives. Optionally include an argument.
112     Extension(String, Option<String>)
113 }
114 
115 impl fmt::Display for CacheDirective {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result116     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117         use self::CacheDirective::*;
118         fmt::Display::fmt(match *self {
119             NoCache => "no-cache",
120             NoStore => "no-store",
121             NoTransform => "no-transform",
122             OnlyIfCached => "only-if-cached",
123 
124             MaxAge(secs) => return write!(f, "max-age={}", secs),
125             MaxStale(secs) => return write!(f, "max-stale={}", secs),
126             MinFresh(secs) => return write!(f, "min-fresh={}", secs),
127 
128             MustRevalidate => "must-revalidate",
129             Public => "public",
130             Private => "private",
131             ProxyRevalidate => "proxy-revalidate",
132             SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
133 
134             Extension(ref name, None) => &name[..],
135             Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg),
136 
137         }, f)
138     }
139 }
140 
141 impl FromStr for CacheDirective {
142     type Err = Option<<u32 as FromStr>::Err>;
from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>>143     fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
144         use self::CacheDirective::*;
145         match s {
146             "no-cache" => Ok(NoCache),
147             "no-store" => Ok(NoStore),
148             "no-transform" => Ok(NoTransform),
149             "only-if-cached" => Ok(OnlyIfCached),
150             "must-revalidate" => Ok(MustRevalidate),
151             "public" => Ok(Public),
152             "private" => Ok(Private),
153             "proxy-revalidate" => Ok(ProxyRevalidate),
154             "" => Err(None),
155             _ => match s.find('=') {
156                 Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) {
157                     ("max-age" , secs) => secs.parse().map(MaxAge).map_err(Some),
158                     ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
159                     ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
160                     ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
161                     (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned())))
162                 },
163                 Some(_) => Err(None),
164                 None => Ok(Extension(s.to_owned(), None))
165             }
166         }
167     }
168 }
169 
170 #[cfg(test)]
171 mod tests {
172     use header::Header;
173     use super::*;
174 
175     #[test]
test_parse_multiple_headers()176     fn test_parse_multiple_headers() {
177         let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]);
178         assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache,
179                                                  CacheDirective::Private])))
180     }
181 
182     #[test]
test_parse_argument()183     fn test_parse_argument() {
184         let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]);
185         assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100),
186                                                  CacheDirective::Private])))
187     }
188 
189     #[test]
test_parse_quote_form()190     fn test_parse_quote_form() {
191         let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]);
192         assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)])))
193     }
194 
195     #[test]
test_parse_extension()196     fn test_parse_extension() {
197         let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]);
198         assert_eq!(cache.ok(), Some(CacheControl(vec![
199             CacheDirective::Extension("foo".to_owned(), None),
200             CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))])))
201     }
202 
203     #[test]
test_parse_bad_syntax()204     fn test_parse_bad_syntax() {
205         let cache: ::Result<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
206         assert_eq!(cache.ok(), None)
207     }
208 }
209 
210 bench_header!(normal,
211     CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] });
212