pingora_cache/
cache_control.rs

1// Copyright 2025 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Functions and utilities to help parse Cache-Control headers
16
17use 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
29/// The max delta-second per [RFC 9111](https://datatracker.ietf.org/doc/html/rfc9111#section-1.2.2)
30// "If a cache receives a delta-seconds value
31// greater than the greatest integer it can represent, or if any of its
32// subsequent calculations overflows, the cache MUST consider the value
33// to be 2147483648 (2^31) or the greatest positive integer it can
34// conveniently represent.
35//
36//    |  *Note:* The value 2147483648 is here for historical reasons,
37//    |  represents infinity (over 68 years), and does not need to be
38//    |  stored in binary form; an implementation could produce it as a
39//    |  string if any overflow occurs, even if the calculations are
40//    |  performed with an arithmetic type incapable of directly
41//    |  representing that number.  What matters here is that an
42//    |  overflow be detected and not treated as a negative value in
43//    |  later calculations."
44//
45// We choose to use i32::MAX for our overflow value to stick to the letter of the RFC.
46pub 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
50/// Cache control directive key type
51pub type DirectiveKey = String;
52
53/// Cache control directive value type
54#[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    /// A [DirectiveValue] without quotes (`"`).
65    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    /// A [DirectiveValue] without quotes (`"`) as `str`.
73    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    /// Parse the [DirectiveValue] as delta seconds
80    ///
81    /// `"`s are ignored. The value is capped to [DELTA_SECONDS_OVERFLOW_VALUE].
82    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                // delta-seconds expect to handle positive overflow gracefully
87                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
97/// An ordered map to store cache control key value pairs.
98pub type DirectiveMap = IndexMap<DirectiveKey, Option<DirectiveValue>>;
99
100/// Parsed Cache-Control directives
101#[derive(Debug)]
102pub struct CacheControl {
103    /// The parsed directives
104    pub directives: DirectiveMap,
105}
106
107/// Cacheability calculated from cache control.
108#[derive(Debug, PartialEq, Eq)]
109pub enum Cacheable {
110    /// Cacheable
111    Yes,
112    /// Not cacheable
113    No,
114    /// No directive found for explicit cacheability
115    Default,
116}
117
118/// An iter over all the cache control directives
119pub 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
127// https://datatracker.ietf.org/doc/html/rfc9110#name-whitespace
128// optional whitespace OWS = *(SP / HTAB); SP = 0x20, HTAB = 0x09
129fn trim_ows(bytes: &[u8]) -> &[u8] {
130    fn not_ows(b: &u8) -> bool {
131        b != &b'\x20' && b != &b'\x09'
132    }
133    // find first non-OWS char from front (head) and from end (tail)
134    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
151// Originally from https://github.com/hapijs/wreck which has the following comments:
152// Cache-Control   = 1#cache-directive
153// cache-directive = token [ "=" ( token / quoted-string ) ]
154// token           = [^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+
155// quoted-string   = "(?:[^"\\]|\\.)*"
156//
157// note the `token` implementation excludes disallowed ASCII ranges
158// and disallowed delimiters: https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2
159// though it does not forbid `obs-text`: %x80-FF
160static RE_CACHE_DIRECTIVE: Lazy<Regex> =
161    // to break our version down further:
162    // `(?-u)`: unicode support disabled, which puts the regex into "ASCII compatible mode" for specifying literal bytes like \x7F: https://docs.rs/regex/1.10.4/regex/bytes/index.html#syntax
163    // `(?:^|(?:\s*[,;]\s*)`: allow either , or ; as a delimiter
164    // `([^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+)`: token (directive name capture group)
165    // `(?:=((?:[^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+|(?:"(?:[^"\\]|\\.)*"))))`: token OR quoted-string (directive value capture-group)
166    Lazy::new(|| {
167        Regex::new(r#"(?-u)(?:^|(?:\s*[,;]\s*))([^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+)(?:=((?:[^\x00-\x20\(\)<>@,;:\\"/\[\]\?=\{\}\x7F]+|(?:"(?:[^"\\]|\\.)*"))))?"#).unwrap()
168    });
169
170impl CacheControl {
171    // Our parsing strategy is more permissive than the RFC in a few ways:
172    // - Allows semicolons as delimiters (in addition to commas). See the regex above.
173    // - Allows octets outside of visible ASCII in `token`s, and in later RFCs, octets outside of
174    //   the `quoted-string` range: https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2
175    //   See the regex above.
176    // - Doesn't require no-value for "boolean directives," such as must-revalidate
177    // - Allows quoted-string format for numeric values.
178    fn from_headers(headers: http::header::GetAll<HeaderValue>) -> Option<Self> {
179        let mut directives = IndexMap::new();
180        // should iterate in header line insertion order
181        for line in headers {
182            for captures in RE_CACHE_DIRECTIVE.captures_iter(line.as_bytes()) {
183                // directive key
184                // header values don't have to be utf-8, but we store keys as strings for case-insensitive hashing
185                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                // directive value
194                // match token or quoted-string
195                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    /// Parse from the given header name in `headers`
205    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    /// Parse from the given header name in the [ReqHeader]
214    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    /// Parse `Cache-Control` header name from the [ReqHeader]
219    pub fn from_req_headers(req_header: &ReqHeader) -> Option<Self> {
220        Self::from_req_headers_named("cache-control", req_header)
221    }
222
223    /// Parse from the given header name in the [RespHeader]
224    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    /// Parse `Cache-Control` header name from the [RespHeader]
229    pub fn from_resp_headers(resp_header: &RespHeader) -> Option<Self> {
230        Self::from_resp_headers_named("cache-control", resp_header)
231    }
232
233    /// Whether the given directive is in the cache control.
234    pub fn has_key(&self, key: &str) -> bool {
235        self.directives.contains_key(key)
236    }
237
238    /// Whether the `public` directive is in the cache control.
239    pub fn public(&self) -> bool {
240        self.has_key("public")
241    }
242
243    /// Whether the given directive exists, and it has no value.
244    fn has_key_without_value(&self, key: &str) -> bool {
245        matches!(self.directives.get(key), Some(None))
246    }
247
248    /// Whether the standalone `private` exists in the cache control
249    // RFC 7234: using the #field-name versions of `private`
250    // means a shared cache "MUST NOT store the specified field-name(s),
251    // whereas it MAY store the remainder of the response."
252    // It must be a boolean form (no value) to apply to the whole response.
253    // https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.2.6
254    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    /// Get the values of `private=`
267    pub fn private_field_names(&self) -> Option<ListValueIter> {
268        self.get_field_names("private")
269    }
270
271    /// Whether the standalone `no-cache` exists in the cache control
272    pub fn no_cache(&self) -> bool {
273        self.has_key_without_value("no-cache")
274    }
275
276    /// Get the values of `no-cache=`
277    pub fn no_cache_field_names(&self) -> Option<ListValueIter> {
278        self.get_field_names("no-cache")
279    }
280
281    /// Whether `no-store` exists.
282    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    /// Return the `max-age` seconds
295    pub fn max_age(&self) -> Result<Option<u32>> {
296        self.parse_delta_seconds("max-age")
297    }
298
299    /// Return the `s-maxage` seconds
300    pub fn s_maxage(&self) -> Result<Option<u32>> {
301        self.parse_delta_seconds("s-maxage")
302    }
303
304    /// Return the `stale-while-revalidate` seconds
305    pub fn stale_while_revalidate(&self) -> Result<Option<u32>> {
306        self.parse_delta_seconds("stale-while-revalidate")
307    }
308
309    /// Return the `stale-if-error` seconds
310    pub fn stale_if_error(&self) -> Result<Option<u32>> {
311        self.parse_delta_seconds("stale-if-error")
312    }
313
314    /// Whether `must-revalidate` exists.
315    pub fn must_revalidate(&self) -> bool {
316        self.has_key("must-revalidate")
317    }
318
319    /// Whether `proxy-revalidate` exists.
320    pub fn proxy_revalidate(&self) -> bool {
321        self.has_key("proxy-revalidate")
322    }
323
324    /// Whether `only-if-cached` exists.
325    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        // RFC 7234 https://datatracker.ietf.org/doc/html/rfc7234#section-3
343        // "MUST NOT" store requests with Authorization header
344        // unless response contains one of these directives
345        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            // always treated as stale
351            return Some(Duration::ZERO);
352        }
353        match self.s_maxage() {
354            Ok(Some(duration)) => Some(Duration::from_secs(duration as u64)),
355            // s-maxage not present
356            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        // RFC 7234: these directives forbid serving stale.
366        // https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.4
367        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    // Strip header names listed in `private` or `no-cache` directives from a response.
385    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        // We interpret `no-cache` the same way as `private`,
398        // though technically it has a less restrictive requirement
399        // ("MUST NOT be sent in the response to a subsequent request
400        // without successful revalidation with the origin server").
401        // https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.2.2
402        if let Some(headers) = self.no_cache_field_names() {
403            strip_listed_headers(resp_header, headers);
404        }
405    }
406}
407
408/// `InterpretCacheControl` provides a meaningful interface to the parsed `CacheControl`.
409/// These functions actually interpret the parsed cache-control directives to return
410/// the freshness or other cache meta values that cache-control is signaling.
411///
412/// By default `CacheControl` implements an RFC-7234 compliant reading that assumes it is being
413/// used with a shared (proxy) cache.
414pub trait InterpretCacheControl {
415    /// Does cache-control specify this response is cacheable?
416    ///
417    /// Note that an RFC-7234 compliant cacheability check must also
418    /// check if the request contained the Authorization header and
419    /// `allow_caching_authorized_req`.
420    fn is_cacheable(&self) -> Cacheable;
421
422    /// Does this cache-control allow caching a response to
423    /// a request with the Authorization header?
424    fn allow_caching_authorized_req(&self) -> bool;
425
426    /// Returns freshness ttl specified in cache-control
427    ///
428    /// - `Some(_)` indicates cache-control specifies a valid ttl. Some(Duration::ZERO) = always stale.
429    /// - `None` means cache-control did not specify a valid ttl.
430    fn fresh_duration(&self) -> Option<Duration>;
431
432    /// Returns stale-while-revalidate ttl,
433    ///
434    /// The result should consider all the relevant cache directives, not just SWR header itself.
435    ///
436    /// Some(0) means serving such stale is disallowed by directive like `must-revalidate`
437    /// or `stale-while-revalidater=0`.
438    ///
439    /// `None` indicates no SWR ttl was specified.
440    fn serve_stale_while_revalidate_duration(&self) -> Option<Duration>;
441
442    /// Returns stale-if-error ttl,
443    ///
444    /// The result should consider all the relevant cache directives, not just SIE header itself.
445    ///
446    /// Some(0) means serving such stale is disallowed by directive like `must-revalidate`
447    /// or `stale-if-error=0`.
448    ///
449    /// `None` indicates no SIE ttl was specified.
450    fn serve_stale_if_error_duration(&self) -> Option<Duration>;
451
452    /// Strip header names listed in `private` or `no-cache` directives from a response,
453    /// usually prior to storing that response in cache.
454    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        // values are still stored with casing
554        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        // Not considered valid registered directive keys / values
571        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        // invalid bytes for key
600        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        // RFC 7230: 0xFF is part of obs-text and is officially considered a valid octet in quoted-strings
611        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        // negative ages still result in errors even with overflow handling
654        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        // prefer s-maxage over max-age
673        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        // uncacheable
683        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        // cacheable
692        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}