1use super::*;
18
19use http::header::HeaderName;
20use http::HeaderValue;
21use indexmap::IndexMap;
22use once_cell::sync::Lazy;
23use pingora_error::{Error, ErrorType};
24use regex::bytes::Regex;
25use std::num::IntErrorKind;
26use std::slice;
27use std::str;
28
29pub const DELTA_SECONDS_OVERFLOW_VALUE: u32 = 2147483648;
36
37pub type DirectiveKey = String;
39
40#[derive(Debug)]
42pub struct DirectiveValue(pub Vec<u8>);
43
44impl AsRef<[u8]> for DirectiveValue {
45 fn as_ref(&self) -> &[u8] {
46 &self.0
47 }
48}
49
50impl DirectiveValue {
51 pub fn parse_as_bytes(&self) -> &[u8] {
53 self.0
54 .strip_prefix(b"\"")
55 .and_then(|bytes| bytes.strip_suffix(b"\""))
56 .unwrap_or(&self.0[..])
57 }
58
59 pub fn parse_as_str(&self) -> Result<&str> {
61 str::from_utf8(self.parse_as_bytes()).or_else(|e| {
62 Error::e_because(ErrorType::InternalError, "could not parse value as utf8", e)
63 })
64 }
65
66 pub fn parse_as_delta_seconds(&self) -> Result<u32> {
70 match self.parse_as_str()?.parse::<u32>() {
71 Ok(value) => Ok(value),
72 Err(e) => {
73 if e.kind() == &IntErrorKind::PosOverflow {
75 Ok(DELTA_SECONDS_OVERFLOW_VALUE)
76 } else {
77 Error::e_because(ErrorType::InternalError, "could not parse value as u32", e)
78 }
79 }
80 }
81 }
82}
83
84pub type DirectiveMap = IndexMap<DirectiveKey, Option<DirectiveValue>>;
86
87#[derive(Debug)]
89pub struct CacheControl {
90 pub directives: DirectiveMap,
92}
93
94#[derive(Debug, PartialEq, Eq)]
96pub enum Cacheable {
97 Yes,
99 No,
101 Default,
103}
104
105pub struct ListValueIter<'a>(slice::Split<'a, u8, fn(&u8) -> bool>);
107
108impl<'a> ListValueIter<'a> {
109 pub fn from(value: &'a DirectiveValue) -> Self {
110 ListValueIter(value.parse_as_bytes().split(|byte| byte == &b','))
111 }
112}
113
114fn trim_ows(bytes: &[u8]) -> &[u8] {
117 fn not_ows(b: &u8) -> bool {
118 b != &b'\x20' && b != &b'\x09'
119 }
120 let head = bytes.iter().position(not_ows).unwrap_or(0);
122 let tail = bytes
123 .iter()
124 .rposition(not_ows)
125 .map(|rpos| rpos + 1)
126 .unwrap_or(head);
127 &bytes[head..tail]
128}
129
130impl<'a> Iterator for ListValueIter<'a> {
131 type Item = &'a [u8];
132
133 fn next(&mut self) -> Option<Self::Item> {
134 Some(trim_ows(self.0.next()?))
135 }
136}
137
138static RE_CACHE_DIRECTIVE: Lazy<Regex> =
148 Lazy::new(|| {
154 Regex::new(r#"(?-u)(?:^|(?:\s*[,;]\s*))([^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+)(?:=((?:[^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+|(?:"(?:[^"\\]|\\.)*"))))?"#).unwrap()
155 });
156
157impl CacheControl {
158 fn from_headers(headers: http::header::GetAll<HeaderValue>) -> Option<Self> {
166 let mut directives = IndexMap::new();
167 for line in headers {
169 for captures in RE_CACHE_DIRECTIVE.captures_iter(line.as_bytes()) {
170 let key = captures.get(1).and_then(|cap| {
173 str::from_utf8(cap.as_bytes())
174 .ok()
175 .map(|token| token.to_lowercase())
176 });
177 if key.is_none() {
178 continue;
179 }
180 let value = captures
183 .get(2)
184 .map(|cap| DirectiveValue(cap.as_bytes().to_vec()));
185 directives.insert(key.unwrap(), value);
186 }
187 }
188 Some(CacheControl { directives })
189 }
190
191 pub fn from_headers_named(header_name: &str, headers: &http::HeaderMap) -> Option<Self> {
193 if !headers.contains_key(header_name) {
194 return None;
195 }
196
197 Self::from_headers(headers.get_all(header_name))
198 }
199
200 pub fn from_req_headers_named(header_name: &str, req_header: &ReqHeader) -> Option<Self> {
202 Self::from_headers_named(header_name, &req_header.headers)
203 }
204
205 pub fn from_req_headers(req_header: &ReqHeader) -> Option<Self> {
207 Self::from_req_headers_named("cache-control", req_header)
208 }
209
210 pub fn from_resp_headers_named(header_name: &str, resp_header: &RespHeader) -> Option<Self> {
212 Self::from_headers_named(header_name, &resp_header.headers)
213 }
214
215 pub fn from_resp_headers(resp_header: &RespHeader) -> Option<Self> {
217 Self::from_resp_headers_named("cache-control", resp_header)
218 }
219
220 pub fn has_key(&self, key: &str) -> bool {
222 self.directives.contains_key(key)
223 }
224
225 pub fn public(&self) -> bool {
227 self.has_key("public")
228 }
229
230 fn has_key_without_value(&self, key: &str) -> bool {
232 matches!(self.directives.get(key), Some(None))
233 }
234
235 pub fn private(&self) -> bool {
242 self.has_key_without_value("private")
243 }
244
245 fn get_field_names(&self, key: &str) -> Option<ListValueIter> {
246 if let Some(Some(value)) = self.directives.get(key) {
247 Some(ListValueIter::from(value))
248 } else {
249 None
250 }
251 }
252
253 pub fn private_field_names(&self) -> Option<ListValueIter> {
255 self.get_field_names("private")
256 }
257
258 pub fn no_cache(&self) -> bool {
260 self.has_key_without_value("no-cache")
261 }
262
263 pub fn no_cache_field_names(&self) -> Option<ListValueIter> {
265 self.get_field_names("no-cache")
266 }
267
268 pub fn no_store(&self) -> bool {
270 self.has_key("no-store")
271 }
272
273 fn parse_delta_seconds(&self, key: &str) -> Result<Option<u32>> {
274 if let Some(Some(dir_value)) = self.directives.get(key) {
275 Ok(Some(dir_value.parse_as_delta_seconds()?))
276 } else {
277 Ok(None)
278 }
279 }
280
281 pub fn max_age(&self) -> Result<Option<u32>> {
283 self.parse_delta_seconds("max-age")
284 }
285
286 pub fn s_maxage(&self) -> Result<Option<u32>> {
288 self.parse_delta_seconds("s-maxage")
289 }
290
291 pub fn stale_while_revalidate(&self) -> Result<Option<u32>> {
293 self.parse_delta_seconds("stale-while-revalidate")
294 }
295
296 pub fn stale_if_error(&self) -> Result<Option<u32>> {
298 self.parse_delta_seconds("stale-if-error")
299 }
300
301 pub fn must_revalidate(&self) -> bool {
303 self.has_key("must-revalidate")
304 }
305
306 pub fn proxy_revalidate(&self) -> bool {
308 self.has_key("proxy-revalidate")
309 }
310
311 pub fn only_if_cached(&self) -> bool {
313 self.has_key("only-if-cached")
314 }
315}
316
317impl InterpretCacheControl for CacheControl {
318 fn is_cacheable(&self) -> Cacheable {
319 if self.no_store() || self.private() {
320 return Cacheable::No;
321 }
322 if self.has_key("s-maxage") || self.has_key("max-age") || self.public() {
323 return Cacheable::Yes;
324 }
325 Cacheable::Default
326 }
327
328 fn allow_caching_authorized_req(&self) -> bool {
329 self.must_revalidate() || self.public() || self.has_key("s-maxage")
333 }
334
335 fn fresh_sec(&self) -> Option<u32> {
336 if self.no_cache() {
337 return Some(0);
339 }
340 match self.s_maxage() {
341 Ok(Some(seconds)) => Some(seconds),
342 Ok(None) => match self.max_age() {
344 Ok(Some(seconds)) => Some(seconds),
345 _ => None,
346 },
347 _ => None,
348 }
349 }
350
351 fn serve_stale_while_revalidate_sec(&self) -> Option<u32> {
352 if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
355 return Some(0);
356 }
357 self.stale_while_revalidate().unwrap_or(None)
358 }
359
360 fn serve_stale_if_error_sec(&self) -> Option<u32> {
361 if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
362 return Some(0);
363 }
364 self.stale_if_error().unwrap_or(None)
365 }
366
367 fn strip_private_headers(&self, resp_header: &mut ResponseHeader) {
369 fn strip_listed_headers(resp: &mut ResponseHeader, field_names: ListValueIter) {
370 for name in field_names {
371 if let Ok(header) = HeaderName::from_bytes(name) {
372 resp.remove_header(&header);
373 }
374 }
375 }
376
377 if let Some(headers) = self.private_field_names() {
378 strip_listed_headers(resp_header, headers);
379 }
380 if let Some(headers) = self.no_cache_field_names() {
386 strip_listed_headers(resp_header, headers);
387 }
388 }
389}
390
391pub trait InterpretCacheControl {
398 fn is_cacheable(&self) -> Cacheable;
404
405 fn allow_caching_authorized_req(&self) -> bool;
408
409 fn fresh_sec(&self) -> Option<u32>;
414
415 fn serve_stale_while_revalidate_sec(&self) -> Option<u32>;
424
425 fn serve_stale_if_error_sec(&self) -> Option<u32>;
434
435 fn strip_private_headers(&self, resp_header: &mut ResponseHeader);
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use http::header::CACHE_CONTROL;
444 use http::{request, response};
445
446 fn build_response(cc_key: HeaderName, cc_value: &str) -> response::Parts {
447 let (parts, _) = response::Builder::new()
448 .header(cc_key, cc_value)
449 .body(())
450 .unwrap()
451 .into_parts();
452 parts
453 }
454
455 #[test]
456 fn test_simple_cache_control() {
457 let resp = build_response(CACHE_CONTROL, "public, max-age=10000");
458 let cc = CacheControl::from_resp_headers(&resp).unwrap();
459 assert!(cc.public());
460 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
461 }
462
463 #[test]
464 fn test_private_cache_control() {
465 let resp = build_response(CACHE_CONTROL, "private");
466 let cc = CacheControl::from_resp_headers(&resp).unwrap();
467
468 assert!(cc.private());
469 assert!(cc.max_age().unwrap().is_none());
470 }
471
472 #[test]
473 fn test_directives_across_header_lines() {
474 let (parts, _) = response::Builder::new()
475 .header(CACHE_CONTROL, "public,")
476 .header("cache-Control", "max-age=10000")
477 .body(())
478 .unwrap()
479 .into_parts();
480 let cc = CacheControl::from_resp_headers(&parts).unwrap();
481
482 assert!(cc.public());
483 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
484 }
485
486 #[test]
487 fn test_recognizes_semicolons_as_delimiters() {
488 let resp = build_response(CACHE_CONTROL, "public; max-age=0");
489 let cc = CacheControl::from_resp_headers(&resp).unwrap();
490
491 assert!(cc.public());
492 assert_eq!(cc.max_age().unwrap().unwrap(), 0);
493 }
494
495 #[test]
496 fn test_unknown_directives() {
497 let resp = build_response(CACHE_CONTROL, "public,random1=random2, rand3=\"\"");
498 let cc = CacheControl::from_resp_headers(&resp).unwrap();
499 let mut directive_iter = cc.directives.iter();
500
501 let first = directive_iter.next().unwrap();
502 assert_eq!(first.0, &"public");
503 assert!(first.1.is_none());
504
505 let second = directive_iter.next().unwrap();
506 assert_eq!(second.0, &"random1");
507 assert_eq!(second.1.as_ref().unwrap().0, "random2".as_bytes());
508
509 let third = directive_iter.next().unwrap();
510 assert_eq!(third.0, &"rand3");
511 assert_eq!(third.1.as_ref().unwrap().0, "\"\"".as_bytes());
512
513 assert!(directive_iter.next().is_none());
514 }
515
516 #[test]
517 fn test_case_insensitive_directive_keys() {
518 let resp = build_response(
519 CACHE_CONTROL,
520 "Public=\"something\", mAx-AGe=\"10000\", foo=cRaZyCaSe, bAr=\"inQuotes\"",
521 );
522 let cc = CacheControl::from_resp_headers(&resp).unwrap();
523
524 assert!(cc.public());
525 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
526
527 let mut directive_iter = cc.directives.iter();
528 let first = directive_iter.next().unwrap();
529 assert_eq!(first.0, &"public");
530 assert_eq!(first.1.as_ref().unwrap().0, "\"something\"".as_bytes());
531
532 let second = directive_iter.next().unwrap();
533 assert_eq!(second.0, &"max-age");
534 assert_eq!(second.1.as_ref().unwrap().0, "\"10000\"".as_bytes());
535
536 let third = directive_iter.next().unwrap();
538 assert_eq!(third.0, &"foo");
539 assert_eq!(third.1.as_ref().unwrap().0, "cRaZyCaSe".as_bytes());
540
541 let fourth = directive_iter.next().unwrap();
542 assert_eq!(fourth.0, &"bar");
543 assert_eq!(fourth.1.as_ref().unwrap().0, "\"inQuotes\"".as_bytes());
544
545 assert!(directive_iter.next().is_none());
546 }
547
548 #[test]
549 fn test_non_ascii() {
550 let resp = build_response(CACHE_CONTROL, "püblic=💖, max-age=\"💯\"");
551 let cc = CacheControl::from_resp_headers(&resp).unwrap();
552
553 assert!(!cc.public());
555 assert_eq!(
556 cc.max_age().unwrap_err().context.unwrap().to_string(),
557 "could not parse value as u32"
558 );
559
560 let mut directive_iter = cc.directives.iter();
561 let first = directive_iter.next().unwrap();
562 assert_eq!(first.0, &"püblic");
563 assert_eq!(first.1.as_ref().unwrap().0, "💖".as_bytes());
564
565 let second = directive_iter.next().unwrap();
566 assert_eq!(second.0, &"max-age");
567 assert_eq!(second.1.as_ref().unwrap().0, "\"💯\"".as_bytes());
568
569 assert!(directive_iter.next().is_none());
570 }
571
572 #[test]
573 fn test_non_utf8_key() {
574 let mut resp = response::Builder::new().body(()).unwrap();
575 resp.headers_mut().insert(
576 CACHE_CONTROL,
577 HeaderValue::from_bytes(b"bar\xFF=\"baz\", a=b").unwrap(),
578 );
579 let (parts, _) = resp.into_parts();
580 let cc = CacheControl::from_resp_headers(&parts).unwrap();
581
582 let mut directive_iter = cc.directives.iter();
584 let first = directive_iter.next().unwrap();
585 assert_eq!(first.0, &"a");
586 assert_eq!(first.1.as_ref().unwrap().0, "b".as_bytes());
587
588 assert!(directive_iter.next().is_none());
589 }
590
591 #[test]
592 fn test_non_utf8_value() {
593 let mut resp = response::Builder::new().body(()).unwrap();
595 resp.headers_mut().insert(
596 CACHE_CONTROL,
597 HeaderValue::from_bytes(b"max-age=ba\xFFr, bar=\"baz\xFF\", a=b").unwrap(),
598 );
599 let (parts, _) = resp.into_parts();
600 let cc = CacheControl::from_resp_headers(&parts).unwrap();
601
602 assert_eq!(
603 cc.max_age().unwrap_err().context.unwrap().to_string(),
604 "could not parse value as utf8"
605 );
606
607 let mut directive_iter = cc.directives.iter();
608
609 let first = directive_iter.next().unwrap();
610 assert_eq!(first.0, &"max-age");
611 assert_eq!(first.1.as_ref().unwrap().0, b"ba\xFFr");
612
613 let second = directive_iter.next().unwrap();
614 assert_eq!(second.0, &"bar");
615 assert_eq!(second.1.as_ref().unwrap().0, b"\"baz\xFF\"");
616
617 let third = directive_iter.next().unwrap();
618 assert_eq!(third.0, &"a");
619 assert_eq!(third.1.as_ref().unwrap().0, "b".as_bytes());
620
621 assert!(directive_iter.next().is_none());
622 }
623
624 #[test]
625 fn test_age_overflow() {
626 let resp = build_response(
627 CACHE_CONTROL,
628 "max-age=-99999999999999999999999999, s-maxage=99999999999999999999999999",
629 );
630 let cc = CacheControl::from_resp_headers(&resp).unwrap();
631
632 assert_eq!(
633 cc.s_maxage().unwrap().unwrap(),
634 DELTA_SECONDS_OVERFLOW_VALUE
635 );
636 assert_eq!(
638 cc.max_age().unwrap_err().context.unwrap().to_string(),
639 "could not parse value as u32"
640 );
641 }
642
643 #[test]
644 fn test_fresh_sec() {
645 let resp = build_response(CACHE_CONTROL, "");
646 let cc = CacheControl::from_resp_headers(&resp).unwrap();
647 assert!(cc.fresh_sec().is_none());
648
649 let resp = build_response(CACHE_CONTROL, "max-age=12345");
650 let cc = CacheControl::from_resp_headers(&resp).unwrap();
651 assert_eq!(cc.fresh_sec().unwrap(), 12345);
652
653 let resp = build_response(CACHE_CONTROL, "max-age=99999,s-maxage=123");
654 let cc = CacheControl::from_resp_headers(&resp).unwrap();
655 assert_eq!(cc.fresh_sec().unwrap(), 123);
657 }
658
659 #[test]
660 fn test_cacheability() {
661 let resp = build_response(CACHE_CONTROL, "");
662 let cc = CacheControl::from_resp_headers(&resp).unwrap();
663 assert_eq!(cc.is_cacheable(), Cacheable::Default);
664
665 let resp = build_response(CACHE_CONTROL, "private, max-age=12345");
667 let cc = CacheControl::from_resp_headers(&resp).unwrap();
668 assert_eq!(cc.is_cacheable(), Cacheable::No);
669
670 let resp = build_response(CACHE_CONTROL, "no-store, max-age=12345");
671 let cc = CacheControl::from_resp_headers(&resp).unwrap();
672 assert_eq!(cc.is_cacheable(), Cacheable::No);
673
674 let resp = build_response(CACHE_CONTROL, "public");
676 let cc = CacheControl::from_resp_headers(&resp).unwrap();
677 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
678
679 let resp = build_response(CACHE_CONTROL, "max-age=0");
680 let cc = CacheControl::from_resp_headers(&resp).unwrap();
681 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
682 }
683
684 #[test]
685 fn test_no_cache() {
686 let resp = build_response(CACHE_CONTROL, "no-cache, max-age=12345");
687 let cc = CacheControl::from_resp_headers(&resp).unwrap();
688 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
689 assert_eq!(cc.fresh_sec().unwrap(), 0);
690 }
691
692 #[test]
693 fn test_no_cache_field_names() {
694 let resp = build_response(CACHE_CONTROL, "no-cache=\"set-cookie\", max-age=12345");
695 let cc = CacheControl::from_resp_headers(&resp).unwrap();
696 assert!(!cc.private());
697 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
698 assert_eq!(cc.fresh_sec().unwrap(), 12345);
699 let mut field_names = cc.no_cache_field_names().unwrap();
700 assert_eq!(
701 str::from_utf8(field_names.next().unwrap()).unwrap(),
702 "set-cookie"
703 );
704 assert!(field_names.next().is_none());
705
706 let mut resp = response::Builder::new().body(()).unwrap();
707 resp.headers_mut().insert(
708 CACHE_CONTROL,
709 HeaderValue::from_bytes(
710 b"private=\"\", no-cache=\"a\xFF, set-cookie, Baz\x09 , c,d ,, \"",
711 )
712 .unwrap(),
713 );
714 let (parts, _) = resp.into_parts();
715 let cc = CacheControl::from_resp_headers(&parts).unwrap();
716 let mut field_names = cc.private_field_names().unwrap();
717 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
718 assert!(field_names.next().is_none());
719 let mut field_names = cc.no_cache_field_names().unwrap();
720 assert!(str::from_utf8(field_names.next().unwrap()).is_err());
721 assert_eq!(
722 str::from_utf8(field_names.next().unwrap()).unwrap(),
723 "set-cookie"
724 );
725 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "Baz");
726 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "c");
727 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "d");
728 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
729 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
730 assert!(field_names.next().is_none());
731 }
732
733 #[test]
734 fn test_strip_private_headers() {
735 let mut resp = ResponseHeader::build(200, None).unwrap();
736 resp.append_header(
737 CACHE_CONTROL,
738 "no-cache=\"x-private-header\", max-age=12345",
739 )
740 .unwrap();
741 resp.append_header("X-Private-Header", "dropped").unwrap();
742
743 let cc = CacheControl::from_resp_headers(&resp).unwrap();
744 cc.strip_private_headers(&mut resp);
745 assert!(!resp.headers.contains_key("X-Private-Header"));
746 }
747
748 #[test]
749 fn test_stale_while_revalidate() {
750 let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-while-revalidate=5");
751 let cc = CacheControl::from_resp_headers(&resp).unwrap();
752 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 5);
753 assert_eq!(cc.serve_stale_while_revalidate_sec().unwrap(), 5);
754 assert!(cc.serve_stale_if_error_sec().is_none());
755 }
756
757 #[test]
758 fn test_stale_if_error() {
759 let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-if-error=3600");
760 let cc = CacheControl::from_resp_headers(&resp).unwrap();
761 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 3600);
762 assert_eq!(cc.serve_stale_if_error_sec().unwrap(), 3600);
763 assert!(cc.serve_stale_while_revalidate_sec().is_none());
764 }
765
766 #[test]
767 fn test_must_revalidate() {
768 let resp = build_response(
769 CACHE_CONTROL,
770 "max-age=12345, stale-while-revalidate=60, stale-if-error=30, must-revalidate",
771 );
772 let cc = CacheControl::from_resp_headers(&resp).unwrap();
773 assert!(cc.must_revalidate());
774 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
775 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
776 assert_eq!(cc.serve_stale_while_revalidate_sec().unwrap(), 0);
777 assert_eq!(cc.serve_stale_if_error_sec().unwrap(), 0);
778 }
779
780 #[test]
781 fn test_proxy_revalidate() {
782 let resp = build_response(
783 CACHE_CONTROL,
784 "max-age=12345, stale-while-revalidate=60, stale-if-error=30, proxy-revalidate",
785 );
786 let cc = CacheControl::from_resp_headers(&resp).unwrap();
787 assert!(cc.proxy_revalidate());
788 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
789 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
790 assert_eq!(cc.serve_stale_while_revalidate_sec().unwrap(), 0);
791 assert_eq!(cc.serve_stale_if_error_sec().unwrap(), 0);
792 }
793
794 #[test]
795 fn test_s_maxage_stale() {
796 let resp = build_response(
797 CACHE_CONTROL,
798 "s-maxage=0, stale-while-revalidate=60, stale-if-error=30",
799 );
800 let cc = CacheControl::from_resp_headers(&resp).unwrap();
801 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
802 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
803 assert_eq!(cc.serve_stale_while_revalidate_sec().unwrap(), 0);
804 assert_eq!(cc.serve_stale_if_error_sec().unwrap(), 0);
805 }
806
807 #[test]
808 fn test_authorized_request() {
809 let resp = build_response(CACHE_CONTROL, "max-age=10");
810 let cc = CacheControl::from_resp_headers(&resp).unwrap();
811 assert!(!cc.allow_caching_authorized_req());
812
813 let resp = build_response(CACHE_CONTROL, "s-maxage=10");
814 let cc = CacheControl::from_resp_headers(&resp).unwrap();
815 assert!(cc.allow_caching_authorized_req());
816
817 let resp = build_response(CACHE_CONTROL, "public");
818 let cc = CacheControl::from_resp_headers(&resp).unwrap();
819 assert!(cc.allow_caching_authorized_req());
820
821 let resp = build_response(CACHE_CONTROL, "must-revalidate, max-age=0");
822 let cc = CacheControl::from_resp_headers(&resp).unwrap();
823 assert!(cc.allow_caching_authorized_req());
824
825 let resp = build_response(CACHE_CONTROL, "");
826 let cc = CacheControl::from_resp_headers(&resp).unwrap();
827 assert!(!cc.allow_caching_authorized_req());
828 }
829
830 fn build_request(cc_key: HeaderName, cc_value: &str) -> request::Parts {
831 let (parts, _) = request::Builder::new()
832 .header(cc_key, cc_value)
833 .body(())
834 .unwrap()
835 .into_parts();
836 parts
837 }
838
839 #[test]
840 fn test_request_only_if_cached() {
841 let req = build_request(CACHE_CONTROL, "only-if-cached=1");
842 let cc = CacheControl::from_req_headers(&req).unwrap();
843 assert!(cc.only_if_cached())
844 }
845}