1 use std::fmt; 2 use std::iter::FromIterator; 3 use std::str::FromStr; 4 use std::time::Duration; 5 6 use util::{self, csv, Seconds}; 7 use HeaderValue; 8 9 /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) 10 /// 11 /// The `Cache-Control` header field is used to specify directives for 12 /// caches along the request/response chain. Such cache directives are 13 /// unidirectional in that the presence of a directive in a request does 14 /// not imply that the same directive is to be given in the response. 15 /// 16 /// ## ABNF 17 /// 18 /// ```text 19 /// Cache-Control = 1#cache-directive 20 /// cache-directive = token [ "=" ( token / quoted-string ) ] 21 /// ``` 22 /// 23 /// ## Example values 24 /// 25 /// * `no-cache` 26 /// * `private, community="UCI"` 27 /// * `max-age=30` 28 /// 29 /// # Example 30 /// 31 /// ``` 32 /// # extern crate headers; 33 /// use headers::CacheControl; 34 /// 35 /// let cc = CacheControl::new(); 36 /// ``` 37 #[derive(PartialEq, Clone, Debug)] 38 pub struct CacheControl { 39 flags: Flags, 40 max_age: Option<Seconds>, 41 max_stale: Option<Seconds>, 42 min_fresh: Option<Seconds>, 43 s_max_age: Option<Seconds>, 44 } 45 46 bitflags! { 47 struct Flags: u32 { 48 const NO_CACHE = 0b00000001; 49 const NO_STORE = 0b00000010; 50 const NO_TRANSFORM = 0b00000100; 51 const ONLY_IF_CACHED = 0b00001000; 52 const MUST_REVALIDATE = 0b00010000; 53 const PUBLIC = 0b00100000; 54 const PRIVATE = 0b01000000; 55 const PROXY_REVALIDATE = 0b10000000; 56 } 57 } 58 59 impl CacheControl { 60 /// Construct a new empty `CacheControl` header. new() -> Self61 pub fn new() -> Self { 62 CacheControl { 63 flags: Flags::empty(), 64 max_age: None, 65 max_stale: None, 66 min_fresh: None, 67 s_max_age: None, 68 } 69 } 70 71 // getters 72 73 /// Check if the `no-cache` directive is set. no_cache(&self) -> bool74 pub fn no_cache(&self) -> bool { 75 self.flags.contains(Flags::NO_CACHE) 76 } 77 78 /// Check if the `no-store` directive is set. no_store(&self) -> bool79 pub fn no_store(&self) -> bool { 80 self.flags.contains(Flags::NO_STORE) 81 } 82 83 /// Check if the `no-transform` directive is set. no_transform(&self) -> bool84 pub fn no_transform(&self) -> bool { 85 self.flags.contains(Flags::NO_TRANSFORM) 86 } 87 88 /// Check if the `only-if-cached` directive is set. only_if_cached(&self) -> bool89 pub fn only_if_cached(&self) -> bool { 90 self.flags.contains(Flags::ONLY_IF_CACHED) 91 } 92 93 /// Check if the `public` directive is set. public(&self) -> bool94 pub fn public(&self) -> bool { 95 self.flags.contains(Flags::PUBLIC) 96 } 97 98 /// Check if the `private` directive is set. private(&self) -> bool99 pub fn private(&self) -> bool { 100 self.flags.contains(Flags::PRIVATE) 101 } 102 103 /// Get the value of the `max-age` directive if set. max_age(&self) -> Option<Duration>104 pub fn max_age(&self) -> Option<Duration> { 105 self.max_age.map(Into::into) 106 } 107 108 /// Get the value of the `max-stale` directive if set. max_stale(&self) -> Option<Duration>109 pub fn max_stale(&self) -> Option<Duration> { 110 self.max_stale.map(Into::into) 111 } 112 113 /// Get the value of the `min-fresh` directive if set. min_fresh(&self) -> Option<Duration>114 pub fn min_fresh(&self) -> Option<Duration> { 115 self.min_fresh.map(Into::into) 116 } 117 118 /// Get the value of the `s-maxage` directive if set. s_max_age(&self) -> Option<Duration>119 pub fn s_max_age(&self) -> Option<Duration> { 120 self.s_max_age.map(Into::into) 121 } 122 123 // setters 124 125 /// Set the `no-cache` directive. with_no_cache(mut self) -> Self126 pub fn with_no_cache(mut self) -> Self { 127 self.flags.insert(Flags::NO_CACHE); 128 self 129 } 130 131 /// Set the `no-store` directive. with_no_store(mut self) -> Self132 pub fn with_no_store(mut self) -> Self { 133 self.flags.insert(Flags::NO_STORE); 134 self 135 } 136 137 /// Set the `no-transform` directive. with_no_transform(mut self) -> Self138 pub fn with_no_transform(mut self) -> Self { 139 self.flags.insert(Flags::NO_TRANSFORM); 140 self 141 } 142 143 /// Set the `only-if-cached` directive. with_only_if_cached(mut self) -> Self144 pub fn with_only_if_cached(mut self) -> Self { 145 self.flags.insert(Flags::ONLY_IF_CACHED); 146 self 147 } 148 149 /// Set the `private` directive. with_private(mut self) -> Self150 pub fn with_private(mut self) -> Self { 151 self.flags.insert(Flags::PRIVATE); 152 self 153 } 154 155 /// Set the `public` directive. with_public(mut self) -> Self156 pub fn with_public(mut self) -> Self { 157 self.flags.insert(Flags::PUBLIC); 158 self 159 } 160 161 /// Set the `max-age` directive. with_max_age(mut self, seconds: Duration) -> Self162 pub fn with_max_age(mut self, seconds: Duration) -> Self { 163 self.max_age = Some(seconds.into()); 164 self 165 } 166 167 /// Set the `max-stale` directive. with_max_stale(mut self, seconds: Duration) -> Self168 pub fn with_max_stale(mut self, seconds: Duration) -> Self { 169 self.max_stale = Some(seconds.into()); 170 self 171 } 172 173 /// Set the `min-fresh` directive. with_min_fresh(mut self, seconds: Duration) -> Self174 pub fn with_min_fresh(mut self, seconds: Duration) -> Self { 175 self.min_fresh = Some(seconds.into()); 176 self 177 } 178 179 /// Set the `s-maxage` directive. with_s_max_age(mut self, seconds: Duration) -> Self180 pub fn with_s_max_age(mut self, seconds: Duration) -> Self { 181 self.s_max_age = Some(seconds.into()); 182 self 183 } 184 } 185 186 impl ::Header for CacheControl { name() -> &'static ::HeaderName187 fn name() -> &'static ::HeaderName { 188 &::http::header::CACHE_CONTROL 189 } 190 decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error>191 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { 192 csv::from_comma_delimited(values).map(|FromIter(cc)| cc) 193 } 194 encode<E: Extend<::HeaderValue>>(&self, values: &mut E)195 fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { 196 values.extend(::std::iter::once(util::fmt(Fmt(self)))); 197 } 198 } 199 200 // Adapter to be used in Header::decode 201 struct FromIter(CacheControl); 202 203 impl FromIterator<KnownDirective> for FromIter { from_iter<I>(iter: I) -> Self where I: IntoIterator<Item = KnownDirective>,204 fn from_iter<I>(iter: I) -> Self 205 where 206 I: IntoIterator<Item = KnownDirective>, 207 { 208 let mut cc = CacheControl::new(); 209 210 // ignore all unknown directives 211 let iter = iter.into_iter().filter_map(|dir| match dir { 212 KnownDirective::Known(dir) => Some(dir), 213 KnownDirective::Unknown => None, 214 }); 215 216 for directive in iter { 217 match directive { 218 Directive::NoCache => { 219 cc.flags.insert(Flags::NO_CACHE); 220 } 221 Directive::NoStore => { 222 cc.flags.insert(Flags::NO_STORE); 223 } 224 Directive::NoTransform => { 225 cc.flags.insert(Flags::NO_TRANSFORM); 226 } 227 Directive::OnlyIfCached => { 228 cc.flags.insert(Flags::ONLY_IF_CACHED); 229 } 230 Directive::MustRevalidate => { 231 cc.flags.insert(Flags::MUST_REVALIDATE); 232 } 233 Directive::Public => { 234 cc.flags.insert(Flags::PUBLIC); 235 } 236 Directive::Private => { 237 cc.flags.insert(Flags::PRIVATE); 238 } 239 Directive::ProxyRevalidate => { 240 cc.flags.insert(Flags::PROXY_REVALIDATE); 241 } 242 Directive::MaxAge(secs) => { 243 cc.max_age = Some(Duration::from_secs(secs.into()).into()); 244 } 245 Directive::MaxStale(secs) => { 246 cc.max_stale = Some(Duration::from_secs(secs.into()).into()); 247 } 248 Directive::MinFresh(secs) => { 249 cc.min_fresh = Some(Duration::from_secs(secs.into()).into()); 250 } 251 Directive::SMaxAge(secs) => { 252 cc.s_max_age = Some(Duration::from_secs(secs.into()).into()); 253 } 254 } 255 } 256 257 FromIter(cc) 258 } 259 } 260 261 struct Fmt<'a>(&'a CacheControl); 262 263 impl<'a> fmt::Display for Fmt<'a> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 265 let if_flag = |f: Flags, dir: Directive| { 266 if self.0.flags.contains(f) { 267 Some(dir) 268 } else { 269 None 270 } 271 }; 272 273 let slice = &[ 274 if_flag(Flags::NO_CACHE, Directive::NoCache), 275 if_flag(Flags::NO_STORE, Directive::NoStore), 276 if_flag(Flags::NO_TRANSFORM, Directive::NoTransform), 277 if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached), 278 if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate), 279 if_flag(Flags::PUBLIC, Directive::Public), 280 if_flag(Flags::PRIVATE, Directive::Private), 281 if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate), 282 self.0 283 .max_age 284 .as_ref() 285 .map(|s| Directive::MaxAge(s.as_u64())), 286 self.0 287 .max_stale 288 .as_ref() 289 .map(|s| Directive::MaxStale(s.as_u64())), 290 self.0 291 .min_fresh 292 .as_ref() 293 .map(|s| Directive::MinFresh(s.as_u64())), 294 self.0 295 .s_max_age 296 .as_ref() 297 .map(|s| Directive::SMaxAge(s.as_u64())), 298 ]; 299 300 let iter = slice.iter().filter_map(|o| *o); 301 302 csv::fmt_comma_delimited(f, iter) 303 } 304 } 305 306 #[derive(Clone, Copy)] 307 enum KnownDirective { 308 Known(Directive), 309 Unknown, 310 } 311 312 #[derive(Clone, Copy)] 313 enum Directive { 314 NoCache, 315 NoStore, 316 NoTransform, 317 OnlyIfCached, 318 319 // request directives 320 MaxAge(u64), 321 MaxStale(u64), 322 MinFresh(u64), 323 324 // response directives 325 MustRevalidate, 326 Public, 327 Private, 328 ProxyRevalidate, 329 SMaxAge(u64), 330 } 331 332 impl fmt::Display for Directive { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result333 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 334 fmt::Display::fmt( 335 match *self { 336 Directive::NoCache => "no-cache", 337 Directive::NoStore => "no-store", 338 Directive::NoTransform => "no-transform", 339 Directive::OnlyIfCached => "only-if-cached", 340 341 Directive::MaxAge(secs) => return write!(f, "max-age={}", secs), 342 Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs), 343 Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs), 344 345 Directive::MustRevalidate => "must-revalidate", 346 Directive::Public => "public", 347 Directive::Private => "private", 348 Directive::ProxyRevalidate => "proxy-revalidate", 349 Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs), 350 }, 351 f, 352 ) 353 } 354 } 355 356 impl FromStr for KnownDirective { 357 type Err = (); from_str(s: &str) -> Result<Self, Self::Err>358 fn from_str(s: &str) -> Result<Self, Self::Err> { 359 Ok(KnownDirective::Known(match s { 360 "no-cache" => Directive::NoCache, 361 "no-store" => Directive::NoStore, 362 "no-transform" => Directive::NoTransform, 363 "only-if-cached" => Directive::OnlyIfCached, 364 "must-revalidate" => Directive::MustRevalidate, 365 "public" => Directive::Public, 366 "private" => Directive::Private, 367 "proxy-revalidate" => Directive::ProxyRevalidate, 368 "" => return Err(()), 369 _ => match s.find('=') { 370 Some(idx) if idx + 1 < s.len() => { 371 match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { 372 ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?, 373 ("max-stale", secs) => { 374 secs.parse().map(Directive::MaxStale).map_err(|_| ())? 375 } 376 ("min-fresh", secs) => { 377 secs.parse().map(Directive::MinFresh).map_err(|_| ())? 378 } 379 ("s-maxage", secs) => { 380 secs.parse().map(Directive::SMaxAge).map_err(|_| ())? 381 } 382 _unknown => return Ok(KnownDirective::Unknown), 383 } 384 } 385 Some(_) | None => return Ok(KnownDirective::Unknown), 386 }, 387 })) 388 } 389 } 390 391 #[cfg(test)] 392 mod tests { 393 use super::super::{test_decode, test_encode}; 394 use super::*; 395 396 #[test] test_parse_multiple_headers()397 fn test_parse_multiple_headers() { 398 assert_eq!( 399 test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(), 400 CacheControl::new().with_no_cache().with_private(), 401 ); 402 } 403 404 #[test] test_parse_argument()405 fn test_parse_argument() { 406 assert_eq!( 407 test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(), 408 CacheControl::new() 409 .with_max_age(Duration::from_secs(100)) 410 .with_private(), 411 ); 412 } 413 414 #[test] test_parse_quote_form()415 fn test_parse_quote_form() { 416 assert_eq!( 417 test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(), 418 CacheControl::new().with_max_age(Duration::from_secs(200)), 419 ); 420 } 421 422 #[test] test_parse_extension()423 fn test_parse_extension() { 424 assert_eq!( 425 test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(), 426 CacheControl::new().with_no_cache(), 427 "unknown extensions are ignored but shouldn't fail parsing", 428 ); 429 } 430 431 #[test] test_parse_bad_syntax()432 fn test_parse_bad_syntax() { 433 assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None,); 434 } 435 436 #[test] encode_one_flag_directive()437 fn encode_one_flag_directive() { 438 let cc = CacheControl::new().with_no_cache(); 439 440 let headers = test_encode(cc); 441 assert_eq!(headers["cache-control"], "no-cache"); 442 } 443 444 #[test] encode_one_param_directive()445 fn encode_one_param_directive() { 446 let cc = CacheControl::new().with_max_age(Duration::from_secs(300)); 447 448 let headers = test_encode(cc); 449 assert_eq!(headers["cache-control"], "max-age=300"); 450 } 451 452 #[test] encode_two_directive()453 fn encode_two_directive() { 454 let headers = test_encode(CacheControl::new().with_no_cache().with_private()); 455 assert_eq!(headers["cache-control"], "no-cache, private"); 456 457 let headers = test_encode( 458 CacheControl::new() 459 .with_no_cache() 460 .with_max_age(Duration::from_secs(100)), 461 ); 462 assert_eq!(headers["cache-control"], "no-cache, max-age=100"); 463 } 464 } 465