cogo_http/header/common/
cache_control.rs

1use std::fmt;
2use std::str::FromStr;
3use crate::header::{Header, HeaderFormat};
4use crate::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 cogo_http::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 cogo_http::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)]
48pub struct CacheControl(pub Vec<CacheDirective>);
49
50__hyper__deref!(CacheControl => Vec<CacheDirective>);
51
52impl Header for CacheControl {
53    fn header_name() -> &'static str {
54        "Cache-Control"
55    }
56
57    fn parse_header(raw: &[Vec<u8>]) -> crate::Result<CacheControl> {
58        let directives = r#try!(from_comma_delimited(raw));
59        if !directives.is_empty() {
60            Ok(CacheControl(directives))
61        } else {
62            Err(crate::Error::Header)
63        }
64    }
65}
66
67impl HeaderFormat for CacheControl {
68    fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        fmt::Display::fmt(self, f)
70    }
71}
72
73impl fmt::Display for CacheControl {
74    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)]
81pub 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
115impl fmt::Display for CacheDirective {
116    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
141impl FromStr for CacheDirective {
142    type Err = 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)]
171mod tests {
172    use crate::header::Header;
173    use super::*;
174
175    #[test]
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]
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]
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]
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]
204    fn test_parse_bad_syntax() {
205        let cache: crate::Result<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
206        assert_eq!(cache.ok(), None)
207    }
208}
209
210bench_header!(normal,
211    CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] });