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