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 = i32::MAX as u32;
47pub const DELTA_SECONDS_OVERFLOW_DURATION: Duration =
48 Duration::from_secs(DELTA_SECONDS_OVERFLOW_VALUE as u64);
49
50pub type DirectiveKey = String;
52
53#[derive(Debug)]
55pub struct DirectiveValue(pub Vec<u8>);
56
57impl AsRef<[u8]> for DirectiveValue {
58 fn as_ref(&self) -> &[u8] {
59 &self.0
60 }
61}
62
63impl DirectiveValue {
64 pub fn parse_as_bytes(&self) -> &[u8] {
66 self.0
67 .strip_prefix(b"\"")
68 .and_then(|bytes| bytes.strip_suffix(b"\""))
69 .unwrap_or(&self.0[..])
70 }
71
72 pub fn parse_as_str(&self) -> Result<&str> {
74 str::from_utf8(self.parse_as_bytes()).or_else(|e| {
75 Error::e_because(ErrorType::InternalError, "could not parse value as utf8", e)
76 })
77 }
78
79 pub fn parse_as_delta_seconds(&self) -> Result<u32> {
83 match self.parse_as_str()?.parse::<u32>() {
84 Ok(value) => Ok(value),
85 Err(e) => {
86 if e.kind() == &IntErrorKind::PosOverflow {
88 Ok(DELTA_SECONDS_OVERFLOW_VALUE)
89 } else {
90 Error::e_because(ErrorType::InternalError, "could not parse value as u32", e)
91 }
92 }
93 }
94 }
95}
96
97pub type DirectiveMap = IndexMap<DirectiveKey, Option<DirectiveValue>>;
99
100#[derive(Debug)]
102pub struct CacheControl {
103 pub directives: DirectiveMap,
105}
106
107#[derive(Debug, PartialEq, Eq)]
109pub enum Cacheable {
110 Yes,
112 No,
114 Default,
116}
117
118pub struct ListValueIter<'a>(slice::Split<'a, u8, fn(&u8) -> bool>);
120
121impl<'a> ListValueIter<'a> {
122 pub fn from(value: &'a DirectiveValue) -> Self {
123 ListValueIter(value.parse_as_bytes().split(|byte| byte == &b','))
124 }
125}
126
127fn trim_ows(bytes: &[u8]) -> &[u8] {
130 fn not_ows(b: &u8) -> bool {
131 b != &b'\x20' && b != &b'\x09'
132 }
133 let head = bytes.iter().position(not_ows).unwrap_or(0);
135 let tail = bytes
136 .iter()
137 .rposition(not_ows)
138 .map(|rpos| rpos + 1)
139 .unwrap_or(head);
140 &bytes[head..tail]
141}
142
143impl<'a> Iterator for ListValueIter<'a> {
144 type Item = &'a [u8];
145
146 fn next(&mut self) -> Option<Self::Item> {
147 Some(trim_ows(self.0.next()?))
148 }
149}
150
151static RE_CACHE_DIRECTIVE: Lazy<Regex> =
161 Lazy::new(|| {
167 Regex::new(r#"(?-u)(?:^|(?:\s*[,;]\s*))([^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+)(?:=((?:[^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+|(?:"(?:[^"\\]|\\.)*"))))?"#).unwrap()
168 });
169
170impl CacheControl {
171 fn from_headers(headers: http::header::GetAll<HeaderValue>) -> Option<Self> {
179 let mut directives = IndexMap::new();
180 for line in headers {
182 for captures in RE_CACHE_DIRECTIVE.captures_iter(line.as_bytes()) {
183 let key = captures.get(1).and_then(|cap| {
186 str::from_utf8(cap.as_bytes())
187 .ok()
188 .map(|token| token.to_lowercase())
189 });
190 if key.is_none() {
191 continue;
192 }
193 let value = captures
196 .get(2)
197 .map(|cap| DirectiveValue(cap.as_bytes().to_vec()));
198 directives.insert(key.unwrap(), value);
199 }
200 }
201 Some(CacheControl { directives })
202 }
203
204 pub fn from_headers_named(header_name: &str, headers: &http::HeaderMap) -> Option<Self> {
206 if !headers.contains_key(header_name) {
207 return None;
208 }
209
210 Self::from_headers(headers.get_all(header_name))
211 }
212
213 pub fn from_req_headers_named(header_name: &str, req_header: &ReqHeader) -> Option<Self> {
215 Self::from_headers_named(header_name, &req_header.headers)
216 }
217
218 pub fn from_req_headers(req_header: &ReqHeader) -> Option<Self> {
220 Self::from_req_headers_named("cache-control", req_header)
221 }
222
223 pub fn from_resp_headers_named(header_name: &str, resp_header: &RespHeader) -> Option<Self> {
225 Self::from_headers_named(header_name, &resp_header.headers)
226 }
227
228 pub fn from_resp_headers(resp_header: &RespHeader) -> Option<Self> {
230 Self::from_resp_headers_named("cache-control", resp_header)
231 }
232
233 pub fn has_key(&self, key: &str) -> bool {
235 self.directives.contains_key(key)
236 }
237
238 pub fn public(&self) -> bool {
240 self.has_key("public")
241 }
242
243 fn has_key_without_value(&self, key: &str) -> bool {
245 matches!(self.directives.get(key), Some(None))
246 }
247
248 pub fn private(&self) -> bool {
255 self.has_key_without_value("private")
256 }
257
258 fn get_field_names(&self, key: &str) -> Option<ListValueIter> {
259 if let Some(Some(value)) = self.directives.get(key) {
260 Some(ListValueIter::from(value))
261 } else {
262 None
263 }
264 }
265
266 pub fn private_field_names(&self) -> Option<ListValueIter> {
268 self.get_field_names("private")
269 }
270
271 pub fn no_cache(&self) -> bool {
273 self.has_key_without_value("no-cache")
274 }
275
276 pub fn no_cache_field_names(&self) -> Option<ListValueIter> {
278 self.get_field_names("no-cache")
279 }
280
281 pub fn no_store(&self) -> bool {
283 self.has_key("no-store")
284 }
285
286 fn parse_delta_seconds(&self, key: &str) -> Result<Option<u32>> {
287 if let Some(Some(dir_value)) = self.directives.get(key) {
288 Ok(Some(dir_value.parse_as_delta_seconds()?))
289 } else {
290 Ok(None)
291 }
292 }
293
294 pub fn max_age(&self) -> Result<Option<u32>> {
296 self.parse_delta_seconds("max-age")
297 }
298
299 pub fn s_maxage(&self) -> Result<Option<u32>> {
301 self.parse_delta_seconds("s-maxage")
302 }
303
304 pub fn stale_while_revalidate(&self) -> Result<Option<u32>> {
306 self.parse_delta_seconds("stale-while-revalidate")
307 }
308
309 pub fn stale_if_error(&self) -> Result<Option<u32>> {
311 self.parse_delta_seconds("stale-if-error")
312 }
313
314 pub fn must_revalidate(&self) -> bool {
316 self.has_key("must-revalidate")
317 }
318
319 pub fn proxy_revalidate(&self) -> bool {
321 self.has_key("proxy-revalidate")
322 }
323
324 pub fn only_if_cached(&self) -> bool {
326 self.has_key("only-if-cached")
327 }
328}
329
330impl InterpretCacheControl for CacheControl {
331 fn is_cacheable(&self) -> Cacheable {
332 if self.no_store() || self.private() {
333 return Cacheable::No;
334 }
335 if self.has_key("s-maxage") || self.has_key("max-age") || self.public() {
336 return Cacheable::Yes;
337 }
338 Cacheable::Default
339 }
340
341 fn allow_caching_authorized_req(&self) -> bool {
342 self.must_revalidate() || self.public() || self.has_key("s-maxage")
346 }
347
348 fn fresh_duration(&self) -> Option<Duration> {
349 if self.no_cache() {
350 return Some(Duration::ZERO);
352 }
353 match self.s_maxage() {
354 Ok(Some(duration)) => Some(Duration::from_secs(duration as u64)),
355 Ok(None) => match self.max_age() {
357 Ok(Some(duration)) => Some(Duration::from_secs(duration as u64)),
358 _ => None,
359 },
360 _ => None,
361 }
362 }
363
364 fn serve_stale_while_revalidate_duration(&self) -> Option<Duration> {
365 if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
368 return Some(Duration::ZERO);
369 }
370 self.stale_while_revalidate()
371 .unwrap_or(None)
372 .map(|secs| Duration::from_secs(secs as u64))
373 }
374
375 fn serve_stale_if_error_duration(&self) -> Option<Duration> {
376 if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
377 return Some(Duration::ZERO);
378 }
379 self.stale_if_error()
380 .unwrap_or(None)
381 .map(|secs| Duration::from_secs(secs as u64))
382 }
383
384 fn strip_private_headers(&self, resp_header: &mut ResponseHeader) {
386 fn strip_listed_headers(resp: &mut ResponseHeader, field_names: ListValueIter) {
387 for name in field_names {
388 if let Ok(header) = HeaderName::from_bytes(name) {
389 resp.remove_header(&header);
390 }
391 }
392 }
393
394 if let Some(headers) = self.private_field_names() {
395 strip_listed_headers(resp_header, headers);
396 }
397 if let Some(headers) = self.no_cache_field_names() {
403 strip_listed_headers(resp_header, headers);
404 }
405 }
406}
407
408pub trait InterpretCacheControl {
415 fn is_cacheable(&self) -> Cacheable;
421
422 fn allow_caching_authorized_req(&self) -> bool;
425
426 fn fresh_duration(&self) -> Option<Duration>;
431
432 fn serve_stale_while_revalidate_duration(&self) -> Option<Duration>;
441
442 fn serve_stale_if_error_duration(&self) -> Option<Duration>;
451
452 fn strip_private_headers(&self, resp_header: &mut ResponseHeader);
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use http::header::CACHE_CONTROL;
461 use http::{request, response};
462
463 fn build_response(cc_key: HeaderName, cc_value: &str) -> response::Parts {
464 let (parts, _) = response::Builder::new()
465 .header(cc_key, cc_value)
466 .body(())
467 .unwrap()
468 .into_parts();
469 parts
470 }
471
472 #[test]
473 fn test_simple_cache_control() {
474 let resp = build_response(CACHE_CONTROL, "public, max-age=10000");
475 let cc = CacheControl::from_resp_headers(&resp).unwrap();
476 assert!(cc.public());
477 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
478 }
479
480 #[test]
481 fn test_private_cache_control() {
482 let resp = build_response(CACHE_CONTROL, "private");
483 let cc = CacheControl::from_resp_headers(&resp).unwrap();
484
485 assert!(cc.private());
486 assert!(cc.max_age().unwrap().is_none());
487 }
488
489 #[test]
490 fn test_directives_across_header_lines() {
491 let (parts, _) = response::Builder::new()
492 .header(CACHE_CONTROL, "public,")
493 .header("cache-Control", "max-age=10000")
494 .body(())
495 .unwrap()
496 .into_parts();
497 let cc = CacheControl::from_resp_headers(&parts).unwrap();
498
499 assert!(cc.public());
500 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
501 }
502
503 #[test]
504 fn test_recognizes_semicolons_as_delimiters() {
505 let resp = build_response(CACHE_CONTROL, "public; max-age=0");
506 let cc = CacheControl::from_resp_headers(&resp).unwrap();
507
508 assert!(cc.public());
509 assert_eq!(cc.max_age().unwrap().unwrap(), 0);
510 }
511
512 #[test]
513 fn test_unknown_directives() {
514 let resp = build_response(CACHE_CONTROL, "public,random1=random2, rand3=\"\"");
515 let cc = CacheControl::from_resp_headers(&resp).unwrap();
516 let mut directive_iter = cc.directives.iter();
517
518 let first = directive_iter.next().unwrap();
519 assert_eq!(first.0, &"public");
520 assert!(first.1.is_none());
521
522 let second = directive_iter.next().unwrap();
523 assert_eq!(second.0, &"random1");
524 assert_eq!(second.1.as_ref().unwrap().0, "random2".as_bytes());
525
526 let third = directive_iter.next().unwrap();
527 assert_eq!(third.0, &"rand3");
528 assert_eq!(third.1.as_ref().unwrap().0, "\"\"".as_bytes());
529
530 assert!(directive_iter.next().is_none());
531 }
532
533 #[test]
534 fn test_case_insensitive_directive_keys() {
535 let resp = build_response(
536 CACHE_CONTROL,
537 "Public=\"something\", mAx-AGe=\"10000\", foo=cRaZyCaSe, bAr=\"inQuotes\"",
538 );
539 let cc = CacheControl::from_resp_headers(&resp).unwrap();
540
541 assert!(cc.public());
542 assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
543
544 let mut directive_iter = cc.directives.iter();
545 let first = directive_iter.next().unwrap();
546 assert_eq!(first.0, &"public");
547 assert_eq!(first.1.as_ref().unwrap().0, "\"something\"".as_bytes());
548
549 let second = directive_iter.next().unwrap();
550 assert_eq!(second.0, &"max-age");
551 assert_eq!(second.1.as_ref().unwrap().0, "\"10000\"".as_bytes());
552
553 let third = directive_iter.next().unwrap();
555 assert_eq!(third.0, &"foo");
556 assert_eq!(third.1.as_ref().unwrap().0, "cRaZyCaSe".as_bytes());
557
558 let fourth = directive_iter.next().unwrap();
559 assert_eq!(fourth.0, &"bar");
560 assert_eq!(fourth.1.as_ref().unwrap().0, "\"inQuotes\"".as_bytes());
561
562 assert!(directive_iter.next().is_none());
563 }
564
565 #[test]
566 fn test_non_ascii() {
567 let resp = build_response(CACHE_CONTROL, "püblic=💖, max-age=\"💯\"");
568 let cc = CacheControl::from_resp_headers(&resp).unwrap();
569
570 assert!(!cc.public());
572 assert_eq!(
573 cc.max_age().unwrap_err().context.unwrap().to_string(),
574 "could not parse value as u32"
575 );
576
577 let mut directive_iter = cc.directives.iter();
578 let first = directive_iter.next().unwrap();
579 assert_eq!(first.0, &"püblic");
580 assert_eq!(first.1.as_ref().unwrap().0, "💖".as_bytes());
581
582 let second = directive_iter.next().unwrap();
583 assert_eq!(second.0, &"max-age");
584 assert_eq!(second.1.as_ref().unwrap().0, "\"💯\"".as_bytes());
585
586 assert!(directive_iter.next().is_none());
587 }
588
589 #[test]
590 fn test_non_utf8_key() {
591 let mut resp = response::Builder::new().body(()).unwrap();
592 resp.headers_mut().insert(
593 CACHE_CONTROL,
594 HeaderValue::from_bytes(b"bar\xFF=\"baz\", a=b").unwrap(),
595 );
596 let (parts, _) = resp.into_parts();
597 let cc = CacheControl::from_resp_headers(&parts).unwrap();
598
599 let mut directive_iter = cc.directives.iter();
601 let first = directive_iter.next().unwrap();
602 assert_eq!(first.0, &"a");
603 assert_eq!(first.1.as_ref().unwrap().0, "b".as_bytes());
604
605 assert!(directive_iter.next().is_none());
606 }
607
608 #[test]
609 fn test_non_utf8_value() {
610 let mut resp = response::Builder::new().body(()).unwrap();
612 resp.headers_mut().insert(
613 CACHE_CONTROL,
614 HeaderValue::from_bytes(b"max-age=ba\xFFr, bar=\"baz\xFF\", a=b").unwrap(),
615 );
616 let (parts, _) = resp.into_parts();
617 let cc = CacheControl::from_resp_headers(&parts).unwrap();
618
619 assert_eq!(
620 cc.max_age().unwrap_err().context.unwrap().to_string(),
621 "could not parse value as utf8"
622 );
623
624 let mut directive_iter = cc.directives.iter();
625
626 let first = directive_iter.next().unwrap();
627 assert_eq!(first.0, &"max-age");
628 assert_eq!(first.1.as_ref().unwrap().0, b"ba\xFFr");
629
630 let second = directive_iter.next().unwrap();
631 assert_eq!(second.0, &"bar");
632 assert_eq!(second.1.as_ref().unwrap().0, b"\"baz\xFF\"");
633
634 let third = directive_iter.next().unwrap();
635 assert_eq!(third.0, &"a");
636 assert_eq!(third.1.as_ref().unwrap().0, "b".as_bytes());
637
638 assert!(directive_iter.next().is_none());
639 }
640
641 #[test]
642 fn test_age_overflow() {
643 let resp = build_response(
644 CACHE_CONTROL,
645 "max-age=-99999999999999999999999999, s-maxage=99999999999999999999999999",
646 );
647 let cc = CacheControl::from_resp_headers(&resp).unwrap();
648
649 assert_eq!(
650 cc.s_maxage().unwrap().unwrap(),
651 DELTA_SECONDS_OVERFLOW_VALUE
652 );
653 assert_eq!(
655 cc.max_age().unwrap_err().context.unwrap().to_string(),
656 "could not parse value as u32"
657 );
658 }
659
660 #[test]
661 fn test_fresh_sec() {
662 let resp = build_response(CACHE_CONTROL, "");
663 let cc = CacheControl::from_resp_headers(&resp).unwrap();
664 assert!(cc.fresh_duration().is_none());
665
666 let resp = build_response(CACHE_CONTROL, "max-age=12345");
667 let cc = CacheControl::from_resp_headers(&resp).unwrap();
668 assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(12345));
669
670 let resp = build_response(CACHE_CONTROL, "max-age=99999,s-maxage=123");
671 let cc = CacheControl::from_resp_headers(&resp).unwrap();
672 assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(123));
674 }
675
676 #[test]
677 fn test_cacheability() {
678 let resp = build_response(CACHE_CONTROL, "");
679 let cc = CacheControl::from_resp_headers(&resp).unwrap();
680 assert_eq!(cc.is_cacheable(), Cacheable::Default);
681
682 let resp = build_response(CACHE_CONTROL, "private, max-age=12345");
684 let cc = CacheControl::from_resp_headers(&resp).unwrap();
685 assert_eq!(cc.is_cacheable(), Cacheable::No);
686
687 let resp = build_response(CACHE_CONTROL, "no-store, max-age=12345");
688 let cc = CacheControl::from_resp_headers(&resp).unwrap();
689 assert_eq!(cc.is_cacheable(), Cacheable::No);
690
691 let resp = build_response(CACHE_CONTROL, "public");
693 let cc = CacheControl::from_resp_headers(&resp).unwrap();
694 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
695
696 let resp = build_response(CACHE_CONTROL, "max-age=0");
697 let cc = CacheControl::from_resp_headers(&resp).unwrap();
698 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
699 }
700
701 #[test]
702 fn test_no_cache() {
703 let resp = build_response(CACHE_CONTROL, "no-cache, max-age=12345");
704 let cc = CacheControl::from_resp_headers(&resp).unwrap();
705 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
706 assert_eq!(cc.fresh_duration().unwrap(), Duration::ZERO);
707 }
708
709 #[test]
710 fn test_no_cache_field_names() {
711 let resp = build_response(CACHE_CONTROL, "no-cache=\"set-cookie\", max-age=12345");
712 let cc = CacheControl::from_resp_headers(&resp).unwrap();
713 assert!(!cc.private());
714 assert_eq!(cc.is_cacheable(), Cacheable::Yes);
715 assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(12345));
716 let mut field_names = cc.no_cache_field_names().unwrap();
717 assert_eq!(
718 str::from_utf8(field_names.next().unwrap()).unwrap(),
719 "set-cookie"
720 );
721 assert!(field_names.next().is_none());
722
723 let mut resp = response::Builder::new().body(()).unwrap();
724 resp.headers_mut().insert(
725 CACHE_CONTROL,
726 HeaderValue::from_bytes(
727 b"private=\"\", no-cache=\"a\xFF, set-cookie, Baz\x09 , c,d ,, \"",
728 )
729 .unwrap(),
730 );
731 let (parts, _) = resp.into_parts();
732 let cc = CacheControl::from_resp_headers(&parts).unwrap();
733 let mut field_names = cc.private_field_names().unwrap();
734 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
735 assert!(field_names.next().is_none());
736 let mut field_names = cc.no_cache_field_names().unwrap();
737 assert!(str::from_utf8(field_names.next().unwrap()).is_err());
738 assert_eq!(
739 str::from_utf8(field_names.next().unwrap()).unwrap(),
740 "set-cookie"
741 );
742 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "Baz");
743 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "c");
744 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "d");
745 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
746 assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
747 assert!(field_names.next().is_none());
748 }
749
750 #[test]
751 fn test_strip_private_headers() {
752 let mut resp = ResponseHeader::build(200, None).unwrap();
753 resp.append_header(
754 CACHE_CONTROL,
755 "no-cache=\"x-private-header\", max-age=12345",
756 )
757 .unwrap();
758 resp.append_header("X-Private-Header", "dropped").unwrap();
759
760 let cc = CacheControl::from_resp_headers(&resp).unwrap();
761 cc.strip_private_headers(&mut resp);
762 assert!(!resp.headers.contains_key("X-Private-Header"));
763 }
764
765 #[test]
766 fn test_stale_while_revalidate() {
767 let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-while-revalidate=5");
768 let cc = CacheControl::from_resp_headers(&resp).unwrap();
769 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 5);
770 assert_eq!(
771 cc.serve_stale_while_revalidate_duration().unwrap(),
772 Duration::from_secs(5)
773 );
774 assert!(cc.serve_stale_if_error_duration().is_none());
775 }
776
777 #[test]
778 fn test_stale_if_error() {
779 let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-if-error=3600");
780 let cc = CacheControl::from_resp_headers(&resp).unwrap();
781 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 3600);
782 assert_eq!(
783 cc.serve_stale_if_error_duration().unwrap(),
784 Duration::from_secs(3600)
785 );
786 assert!(cc.serve_stale_while_revalidate_duration().is_none());
787 }
788
789 #[test]
790 fn test_must_revalidate() {
791 let resp = build_response(
792 CACHE_CONTROL,
793 "max-age=12345, stale-while-revalidate=60, stale-if-error=30, must-revalidate",
794 );
795 let cc = CacheControl::from_resp_headers(&resp).unwrap();
796 assert!(cc.must_revalidate());
797 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
798 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
799 assert_eq!(
800 cc.serve_stale_while_revalidate_duration().unwrap(),
801 Duration::ZERO
802 );
803 assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
804 }
805
806 #[test]
807 fn test_proxy_revalidate() {
808 let resp = build_response(
809 CACHE_CONTROL,
810 "max-age=12345, stale-while-revalidate=60, stale-if-error=30, proxy-revalidate",
811 );
812 let cc = CacheControl::from_resp_headers(&resp).unwrap();
813 assert!(cc.proxy_revalidate());
814 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
815 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
816 assert_eq!(
817 cc.serve_stale_while_revalidate_duration().unwrap(),
818 Duration::ZERO
819 );
820 assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
821 }
822
823 #[test]
824 fn test_s_maxage_stale() {
825 let resp = build_response(
826 CACHE_CONTROL,
827 "s-maxage=0, stale-while-revalidate=60, stale-if-error=30",
828 );
829 let cc = CacheControl::from_resp_headers(&resp).unwrap();
830 assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
831 assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
832 assert_eq!(
833 cc.serve_stale_while_revalidate_duration().unwrap(),
834 Duration::ZERO
835 );
836 assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
837 }
838
839 #[test]
840 fn test_authorized_request() {
841 let resp = build_response(CACHE_CONTROL, "max-age=10");
842 let cc = CacheControl::from_resp_headers(&resp).unwrap();
843 assert!(!cc.allow_caching_authorized_req());
844
845 let resp = build_response(CACHE_CONTROL, "s-maxage=10");
846 let cc = CacheControl::from_resp_headers(&resp).unwrap();
847 assert!(cc.allow_caching_authorized_req());
848
849 let resp = build_response(CACHE_CONTROL, "public");
850 let cc = CacheControl::from_resp_headers(&resp).unwrap();
851 assert!(cc.allow_caching_authorized_req());
852
853 let resp = build_response(CACHE_CONTROL, "must-revalidate, max-age=0");
854 let cc = CacheControl::from_resp_headers(&resp).unwrap();
855 assert!(cc.allow_caching_authorized_req());
856
857 let resp = build_response(CACHE_CONTROL, "");
858 let cc = CacheControl::from_resp_headers(&resp).unwrap();
859 assert!(!cc.allow_caching_authorized_req());
860 }
861
862 fn build_request(cc_key: HeaderName, cc_value: &str) -> request::Parts {
863 let (parts, _) = request::Builder::new()
864 .header(cc_key, cc_value)
865 .body(())
866 .unwrap()
867 .into_parts();
868 parts
869 }
870
871 #[test]
872 fn test_request_only_if_cached() {
873 let req = build_request(CACHE_CONTROL, "only-if-cached=1");
874 let cc = CacheControl::from_req_headers(&req).unwrap();
875 assert!(cc.only_if_cached())
876 }
877}