1 use std::fmt; 2 use std::str::FromStr; 3 use header::{Header, Raw}; 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 /// 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)] 51 pub struct CacheControl(pub Vec<CacheDirective>); 52 53 __hyper__deref!(CacheControl => Vec<CacheDirective>); 54 55 //TODO: this could just be the header! macro 56 impl Header for CacheControl { header_name() -> &'static str57 fn header_name() -> &'static str { 58 static NAME: &'static str = "Cache-Control"; 59 NAME 60 } 61 parse_header(raw: &Raw) -> ::Result<CacheControl>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 fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result71 fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 72 f.fmt_line(self) 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 /// `CacheControl` contains a list of these directives. 83 #[derive(PartialEq, Clone, Debug)] 84 pub 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 118 impl fmt::Display for CacheDirective { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result119 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 144 impl FromStr for CacheDirective { 145 type Err = Option<<u32 as FromStr>::Err>; from_str(s: &str) -> Result<CacheDirective, 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)] 174 mod tests { 175 use header::Header; 176 use super::*; 177 178 #[test] test_parse_multiple_headers()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] test_parse_argument()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] test_parse_quote_form()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] test_parse_extension()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] test_parse_bad_syntax()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 213 bench_header!(normal, 214 CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] }); 215