hyper_old_types/header/common/
cache_control.rs

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