1 use std::fmt::{self, Write}; 2 use std::str::FromStr; 3 4 use http::header; 5 6 use crate::header::{ 7 fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, 8 }; 9 10 /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) 11 /// 12 /// The `Cache-Control` header field is used to specify directives for 13 /// caches along the request/response chain. Such cache directives are 14 /// unidirectional in that the presence of a directive in a request does 15 /// not imply that the same directive is to be given in the response. 16 /// 17 /// # ABNF 18 /// 19 /// ```text 20 /// Cache-Control = 1#cache-directive 21 /// cache-directive = token [ "=" ( token / quoted-string ) ] 22 /// ``` 23 /// 24 /// # Example values 25 /// 26 /// * `no-cache` 27 /// * `private, community="UCI"` 28 /// * `max-age=30` 29 /// 30 /// # Examples 31 /// ```rust 32 /// use actix_http::Response; 33 /// use actix_http::http::header::{CacheControl, CacheDirective}; 34 /// 35 /// let mut builder = Response::Ok(); 36 /// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); 37 /// ``` 38 /// 39 /// ```rust 40 /// use actix_http::Response; 41 /// use actix_http::http::header::{CacheControl, CacheDirective}; 42 /// 43 /// let mut builder = Response::Ok(); 44 /// builder.set(CacheControl(vec![ 45 /// CacheDirective::NoCache, 46 /// CacheDirective::Private, 47 /// CacheDirective::MaxAge(360u32), 48 /// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), 49 /// ])); 50 /// ``` 51 #[derive(PartialEq, Clone, Debug)] 52 pub struct CacheControl(pub Vec<CacheDirective>); 53 54 __hyper__deref!(CacheControl => Vec<CacheDirective>); 55 56 //TODO: this could just be the header! macro 57 impl Header for CacheControl { name() -> header::HeaderName58 fn name() -> header::HeaderName { 59 header::CACHE_CONTROL 60 } 61 62 #[inline] parse<T>(msg: &T) -> Result<Self, crate::error::ParseError> where T: crate::HttpMessage,63 fn parse<T>(msg: &T) -> Result<Self, crate::error::ParseError> 64 where 65 T: crate::HttpMessage, 66 { 67 let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?; 68 if !directives.is_empty() { 69 Ok(CacheControl(directives)) 70 } else { 71 Err(crate::error::ParseError::Header) 72 } 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 impl IntoHeaderValue for CacheControl { 83 type Error = header::InvalidHeaderValue; 84 try_into(self) -> Result<header::HeaderValue, Self::Error>85 fn try_into(self) -> Result<header::HeaderValue, Self::Error> { 86 let mut writer = Writer::new(); 87 let _ = write!(&mut writer, "{}", self); 88 header::HeaderValue::from_maybe_shared(writer.take()) 89 } 90 } 91 92 /// `CacheControl` contains a list of these directives. 93 #[derive(PartialEq, Clone, Debug)] 94 pub enum CacheDirective { 95 /// "no-cache" 96 NoCache, 97 /// "no-store" 98 NoStore, 99 /// "no-transform" 100 NoTransform, 101 /// "only-if-cached" 102 OnlyIfCached, 103 104 // request directives 105 /// "max-age=delta" 106 MaxAge(u32), 107 /// "max-stale=delta" 108 MaxStale(u32), 109 /// "min-fresh=delta" 110 MinFresh(u32), 111 112 // response directives 113 /// "must-revalidate" 114 MustRevalidate, 115 /// "public" 116 Public, 117 /// "private" 118 Private, 119 /// "proxy-revalidate" 120 ProxyRevalidate, 121 /// "s-maxage=delta" 122 SMaxAge(u32), 123 124 /// Extension directives. Optionally include an argument. 125 Extension(String, Option<String>), 126 } 127 128 impl fmt::Display for CacheDirective { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 130 use self::CacheDirective::*; 131 fmt::Display::fmt( 132 match *self { 133 NoCache => "no-cache", 134 NoStore => "no-store", 135 NoTransform => "no-transform", 136 OnlyIfCached => "only-if-cached", 137 138 MaxAge(secs) => return write!(f, "max-age={}", secs), 139 MaxStale(secs) => return write!(f, "max-stale={}", secs), 140 MinFresh(secs) => return write!(f, "min-fresh={}", secs), 141 142 MustRevalidate => "must-revalidate", 143 Public => "public", 144 Private => "private", 145 ProxyRevalidate => "proxy-revalidate", 146 SMaxAge(secs) => return write!(f, "s-maxage={}", secs), 147 148 Extension(ref name, None) => &name[..], 149 Extension(ref name, Some(ref arg)) => { 150 return write!(f, "{}={}", name, arg); 151 } 152 }, 153 f, 154 ) 155 } 156 } 157 158 impl FromStr for CacheDirective { 159 type Err = Option<<u32 as FromStr>::Err>; from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>>160 fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> { 161 use self::CacheDirective::*; 162 match s { 163 "no-cache" => Ok(NoCache), 164 "no-store" => Ok(NoStore), 165 "no-transform" => Ok(NoTransform), 166 "only-if-cached" => Ok(OnlyIfCached), 167 "must-revalidate" => Ok(MustRevalidate), 168 "public" => Ok(Public), 169 "private" => Ok(Private), 170 "proxy-revalidate" => Ok(ProxyRevalidate), 171 "" => Err(None), 172 _ => match s.find('=') { 173 Some(idx) if idx + 1 < s.len() => { 174 match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { 175 ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), 176 ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), 177 ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), 178 ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), 179 (left, right) => { 180 Ok(Extension(left.to_owned(), Some(right.to_owned()))) 181 } 182 } 183 } 184 Some(_) => Err(None), 185 None => Ok(Extension(s.to_owned(), None)), 186 }, 187 } 188 } 189 } 190 191 #[cfg(test)] 192 mod tests { 193 use super::*; 194 use crate::header::Header; 195 use crate::test::TestRequest; 196 197 #[test] test_parse_multiple_headers()198 fn test_parse_multiple_headers() { 199 let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") 200 .finish(); 201 let cache = Header::parse(&req); 202 assert_eq!( 203 cache.ok(), 204 Some(CacheControl(vec![ 205 CacheDirective::NoCache, 206 CacheDirective::Private, 207 ])) 208 ) 209 } 210 211 #[test] test_parse_argument()212 fn test_parse_argument() { 213 let req = 214 TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") 215 .finish(); 216 let cache = Header::parse(&req); 217 assert_eq!( 218 cache.ok(), 219 Some(CacheControl(vec![ 220 CacheDirective::MaxAge(100), 221 CacheDirective::Private, 222 ])) 223 ) 224 } 225 226 #[test] test_parse_quote_form()227 fn test_parse_quote_form() { 228 let req = 229 TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); 230 let cache = Header::parse(&req); 231 assert_eq!( 232 cache.ok(), 233 Some(CacheControl(vec![CacheDirective::MaxAge(200)])) 234 ) 235 } 236 237 #[test] test_parse_extension()238 fn test_parse_extension() { 239 let req = 240 TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); 241 let cache = Header::parse(&req); 242 assert_eq!( 243 cache.ok(), 244 Some(CacheControl(vec![ 245 CacheDirective::Extension("foo".to_owned(), None), 246 CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), 247 ])) 248 ) 249 } 250 251 #[test] test_parse_bad_syntax()252 fn test_parse_bad_syntax() { 253 let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); 254 let cache: Result<CacheControl, _> = Header::parse(&req); 255 assert_eq!(cache.ok(), None) 256 } 257 } 258