Skip to main content

pingora_cache/
cache_control.rs

1// Copyright 2026 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        let value = self.directives.get(key)?.as_ref()?;
260        Some(ListValueIter::from(value))
261    }
262
263    /// Get the values of `private=`
264    pub fn private_field_names(&self) -> Option<ListValueIter<'_>> {
265        self.get_field_names("private")
266    }
267
268    /// Whether the standalone `no-cache` exists in the cache control
269    pub fn no_cache(&self) -> bool {
270        self.has_key_without_value("no-cache")
271    }
272
273    /// Get the values of `no-cache=`
274    pub fn no_cache_field_names(&self) -> Option<ListValueIter<'_>> {
275        self.get_field_names("no-cache")
276    }
277
278    /// Whether `no-store` exists.
279    pub fn no_store(&self) -> bool {
280        self.has_key("no-store")
281    }
282
283    fn parse_delta_seconds(&self, key: &str) -> Result<Option<u32>> {
284        if let Some(Some(dir_value)) = self.directives.get(key) {
285            Ok(Some(dir_value.parse_as_delta_seconds()?))
286        } else {
287            Ok(None)
288        }
289    }
290
291    /// Return the `max-age` seconds
292    pub fn max_age(&self) -> Result<Option<u32>> {
293        self.parse_delta_seconds("max-age")
294    }
295
296    /// Return the `s-maxage` seconds
297    pub fn s_maxage(&self) -> Result<Option<u32>> {
298        self.parse_delta_seconds("s-maxage")
299    }
300
301    /// Return the `stale-while-revalidate` seconds
302    pub fn stale_while_revalidate(&self) -> Result<Option<u32>> {
303        self.parse_delta_seconds("stale-while-revalidate")
304    }
305
306    /// Return the `stale-if-error` seconds
307    pub fn stale_if_error(&self) -> Result<Option<u32>> {
308        self.parse_delta_seconds("stale-if-error")
309    }
310
311    /// Whether `must-revalidate` exists.
312    pub fn must_revalidate(&self) -> bool {
313        self.has_key("must-revalidate")
314    }
315
316    /// Whether `proxy-revalidate` exists.
317    pub fn proxy_revalidate(&self) -> bool {
318        self.has_key("proxy-revalidate")
319    }
320
321    /// Whether `only-if-cached` exists.
322    pub fn only_if_cached(&self) -> bool {
323        self.has_key("only-if-cached")
324    }
325}
326
327impl InterpretCacheControl for CacheControl {
328    fn is_cacheable(&self) -> Cacheable {
329        if self.no_store() || self.private() {
330            return Cacheable::No;
331        }
332        if self.has_key("s-maxage") || self.has_key("max-age") || self.public() {
333            return Cacheable::Yes;
334        }
335        Cacheable::Default
336    }
337
338    fn allow_caching_authorized_req(&self) -> bool {
339        // RFC 7234 https://datatracker.ietf.org/doc/html/rfc7234#section-3
340        // "MUST NOT" store requests with Authorization header
341        // unless response contains one of these directives
342        self.must_revalidate() || self.public() || self.has_key("s-maxage")
343    }
344
345    fn fresh_duration(&self) -> Option<Duration> {
346        if self.no_cache() {
347            // always treated as stale
348            return Some(Duration::ZERO);
349        }
350        let seconds = self
351            .s_maxage()
352            .ok()?
353            // s-maxage not present
354            .or_else(|| self.max_age().unwrap_or(None))
355            .map(|duration| Duration::from_secs(duration as u64))?;
356        Some(seconds)
357    }
358
359    fn serve_stale_while_revalidate_duration(&self) -> Option<Duration> {
360        // RFC 7234: these directives forbid serving stale.
361        // https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.4
362        if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
363            return Some(Duration::ZERO);
364        }
365        self.stale_while_revalidate()
366            .unwrap_or(None)
367            .map(|secs| Duration::from_secs(secs as u64))
368    }
369
370    fn serve_stale_if_error_duration(&self) -> Option<Duration> {
371        if self.must_revalidate() || self.proxy_revalidate() || self.has_key("s-maxage") {
372            return Some(Duration::ZERO);
373        }
374        self.stale_if_error()
375            .unwrap_or(None)
376            .map(|secs| Duration::from_secs(secs as u64))
377    }
378
379    // Strip header names listed in `private` or `no-cache` directives from a response.
380    fn strip_private_headers(&self, resp_header: &mut ResponseHeader) {
381        fn strip_listed_headers(resp: &mut ResponseHeader, field_names: ListValueIter) {
382            for name in field_names {
383                if let Ok(header) = HeaderName::from_bytes(name) {
384                    resp.remove_header(&header);
385                }
386            }
387        }
388
389        if let Some(headers) = self.private_field_names() {
390            strip_listed_headers(resp_header, headers);
391        }
392        // We interpret `no-cache` the same way as `private`,
393        // though technically it has a less restrictive requirement
394        // ("MUST NOT be sent in the response to a subsequent request
395        // without successful revalidation with the origin server").
396        // https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.2.2
397        if let Some(headers) = self.no_cache_field_names() {
398            strip_listed_headers(resp_header, headers);
399        }
400    }
401}
402
403/// `InterpretCacheControl` provides a meaningful interface to the parsed `CacheControl`.
404/// These functions actually interpret the parsed cache-control directives to return
405/// the freshness or other cache meta values that cache-control is signaling.
406///
407/// By default `CacheControl` implements an RFC-7234 compliant reading that assumes it is being
408/// used with a shared (proxy) cache.
409pub trait InterpretCacheControl {
410    /// Does cache-control specify this response is cacheable?
411    ///
412    /// Note that an RFC-7234 compliant cacheability check must also
413    /// check if the request contained the Authorization header and
414    /// `allow_caching_authorized_req`.
415    fn is_cacheable(&self) -> Cacheable;
416
417    /// Does this cache-control allow caching a response to
418    /// a request with the Authorization header?
419    fn allow_caching_authorized_req(&self) -> bool;
420
421    /// Returns freshness ttl specified in cache-control
422    ///
423    /// - `Some(_)` indicates cache-control specifies a valid ttl. Some(Duration::ZERO) = always stale.
424    /// - `None` means cache-control did not specify a valid ttl.
425    fn fresh_duration(&self) -> Option<Duration>;
426
427    /// Returns stale-while-revalidate ttl,
428    ///
429    /// The result should consider all the relevant cache directives, not just SWR header itself.
430    ///
431    /// Some(0) means serving such stale is disallowed by directive like `must-revalidate`
432    /// or `stale-while-revalidater=0`.
433    ///
434    /// `None` indicates no SWR ttl was specified.
435    fn serve_stale_while_revalidate_duration(&self) -> Option<Duration>;
436
437    /// Returns stale-if-error ttl,
438    ///
439    /// The result should consider all the relevant cache directives, not just SIE header itself.
440    ///
441    /// Some(0) means serving such stale is disallowed by directive like `must-revalidate`
442    /// or `stale-if-error=0`.
443    ///
444    /// `None` indicates no SIE ttl was specified.
445    fn serve_stale_if_error_duration(&self) -> Option<Duration>;
446
447    /// Strip header names listed in `private` or `no-cache` directives from a response,
448    /// usually prior to storing that response in cache.
449    fn strip_private_headers(&self, resp_header: &mut ResponseHeader);
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455    use http::header::CACHE_CONTROL;
456    use http::{request, response};
457
458    fn build_response(cc_key: HeaderName, cc_value: &str) -> response::Parts {
459        let (parts, _) = response::Builder::new()
460            .header(cc_key, cc_value)
461            .body(())
462            .unwrap()
463            .into_parts();
464        parts
465    }
466
467    #[test]
468    fn test_simple_cache_control() {
469        let resp = build_response(CACHE_CONTROL, "public, max-age=10000");
470        let cc = CacheControl::from_resp_headers(&resp).unwrap();
471        assert!(cc.public());
472        assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
473    }
474
475    #[test]
476    fn test_private_cache_control() {
477        let resp = build_response(CACHE_CONTROL, "private");
478        let cc = CacheControl::from_resp_headers(&resp).unwrap();
479
480        assert!(cc.private());
481        assert!(cc.max_age().unwrap().is_none());
482    }
483
484    #[test]
485    fn test_directives_across_header_lines() {
486        let (parts, _) = response::Builder::new()
487            .header(CACHE_CONTROL, "public,")
488            .header("cache-Control", "max-age=10000")
489            .body(())
490            .unwrap()
491            .into_parts();
492        let cc = CacheControl::from_resp_headers(&parts).unwrap();
493
494        assert!(cc.public());
495        assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
496    }
497
498    #[test]
499    fn test_recognizes_semicolons_as_delimiters() {
500        let resp = build_response(CACHE_CONTROL, "public; max-age=0");
501        let cc = CacheControl::from_resp_headers(&resp).unwrap();
502
503        assert!(cc.public());
504        assert_eq!(cc.max_age().unwrap().unwrap(), 0);
505    }
506
507    #[test]
508    fn test_unknown_directives() {
509        let resp = build_response(CACHE_CONTROL, "public,random1=random2, rand3=\"\"");
510        let cc = CacheControl::from_resp_headers(&resp).unwrap();
511        let mut directive_iter = cc.directives.iter();
512
513        let first = directive_iter.next().unwrap();
514        assert_eq!(first.0, &"public");
515        assert!(first.1.is_none());
516
517        let second = directive_iter.next().unwrap();
518        assert_eq!(second.0, &"random1");
519        assert_eq!(second.1.as_ref().unwrap().0, "random2".as_bytes());
520
521        let third = directive_iter.next().unwrap();
522        assert_eq!(third.0, &"rand3");
523        assert_eq!(third.1.as_ref().unwrap().0, "\"\"".as_bytes());
524
525        assert!(directive_iter.next().is_none());
526    }
527
528    #[test]
529    fn test_case_insensitive_directive_keys() {
530        let resp = build_response(
531            CACHE_CONTROL,
532            "Public=\"something\", mAx-AGe=\"10000\", foo=cRaZyCaSe, bAr=\"inQuotes\"",
533        );
534        let cc = CacheControl::from_resp_headers(&resp).unwrap();
535
536        assert!(cc.public());
537        assert_eq!(cc.max_age().unwrap().unwrap(), 10000);
538
539        let mut directive_iter = cc.directives.iter();
540        let first = directive_iter.next().unwrap();
541        assert_eq!(first.0, &"public");
542        assert_eq!(first.1.as_ref().unwrap().0, "\"something\"".as_bytes());
543
544        let second = directive_iter.next().unwrap();
545        assert_eq!(second.0, &"max-age");
546        assert_eq!(second.1.as_ref().unwrap().0, "\"10000\"".as_bytes());
547
548        // values are still stored with casing
549        let third = directive_iter.next().unwrap();
550        assert_eq!(third.0, &"foo");
551        assert_eq!(third.1.as_ref().unwrap().0, "cRaZyCaSe".as_bytes());
552
553        let fourth = directive_iter.next().unwrap();
554        assert_eq!(fourth.0, &"bar");
555        assert_eq!(fourth.1.as_ref().unwrap().0, "\"inQuotes\"".as_bytes());
556
557        assert!(directive_iter.next().is_none());
558    }
559
560    #[test]
561    fn test_non_ascii() {
562        let resp = build_response(CACHE_CONTROL, "püblic=💖, max-age=\"💯\"");
563        let cc = CacheControl::from_resp_headers(&resp).unwrap();
564
565        // Not considered valid registered directive keys / values
566        assert!(!cc.public());
567        assert_eq!(
568            cc.max_age().unwrap_err().context.unwrap().to_string(),
569            "could not parse value as u32"
570        );
571
572        let mut directive_iter = cc.directives.iter();
573        let first = directive_iter.next().unwrap();
574        assert_eq!(first.0, &"püblic");
575        assert_eq!(first.1.as_ref().unwrap().0, "💖".as_bytes());
576
577        let second = directive_iter.next().unwrap();
578        assert_eq!(second.0, &"max-age");
579        assert_eq!(second.1.as_ref().unwrap().0, "\"💯\"".as_bytes());
580
581        assert!(directive_iter.next().is_none());
582    }
583
584    #[test]
585    fn test_non_utf8_key() {
586        let mut resp = response::Builder::new().body(()).unwrap();
587        resp.headers_mut().insert(
588            CACHE_CONTROL,
589            HeaderValue::from_bytes(b"bar\xFF=\"baz\", a=b").unwrap(),
590        );
591        let (parts, _) = resp.into_parts();
592        let cc = CacheControl::from_resp_headers(&parts).unwrap();
593
594        // invalid bytes for key
595        let mut directive_iter = cc.directives.iter();
596        let first = directive_iter.next().unwrap();
597        assert_eq!(first.0, &"a");
598        assert_eq!(first.1.as_ref().unwrap().0, "b".as_bytes());
599
600        assert!(directive_iter.next().is_none());
601    }
602
603    #[test]
604    fn test_non_utf8_value() {
605        // RFC 7230: 0xFF is part of obs-text and is officially considered a valid octet in quoted-strings
606        let mut resp = response::Builder::new().body(()).unwrap();
607        resp.headers_mut().insert(
608            CACHE_CONTROL,
609            HeaderValue::from_bytes(b"max-age=ba\xFFr, bar=\"baz\xFF\", a=b").unwrap(),
610        );
611        let (parts, _) = resp.into_parts();
612        let cc = CacheControl::from_resp_headers(&parts).unwrap();
613
614        assert_eq!(
615            cc.max_age().unwrap_err().context.unwrap().to_string(),
616            "could not parse value as utf8"
617        );
618
619        let mut directive_iter = cc.directives.iter();
620
621        let first = directive_iter.next().unwrap();
622        assert_eq!(first.0, &"max-age");
623        assert_eq!(first.1.as_ref().unwrap().0, b"ba\xFFr");
624
625        let second = directive_iter.next().unwrap();
626        assert_eq!(second.0, &"bar");
627        assert_eq!(second.1.as_ref().unwrap().0, b"\"baz\xFF\"");
628
629        let third = directive_iter.next().unwrap();
630        assert_eq!(third.0, &"a");
631        assert_eq!(third.1.as_ref().unwrap().0, "b".as_bytes());
632
633        assert!(directive_iter.next().is_none());
634    }
635
636    #[test]
637    fn test_age_overflow() {
638        let resp = build_response(
639            CACHE_CONTROL,
640            "max-age=-99999999999999999999999999, s-maxage=99999999999999999999999999",
641        );
642        let cc = CacheControl::from_resp_headers(&resp).unwrap();
643
644        assert_eq!(
645            cc.s_maxage().unwrap().unwrap(),
646            DELTA_SECONDS_OVERFLOW_VALUE
647        );
648        // negative ages still result in errors even with overflow handling
649        assert_eq!(
650            cc.max_age().unwrap_err().context.unwrap().to_string(),
651            "could not parse value as u32"
652        );
653    }
654
655    #[test]
656    fn test_fresh_sec() {
657        let resp = build_response(CACHE_CONTROL, "");
658        let cc = CacheControl::from_resp_headers(&resp).unwrap();
659        assert!(cc.fresh_duration().is_none());
660
661        let resp = build_response(CACHE_CONTROL, "max-age=12345");
662        let cc = CacheControl::from_resp_headers(&resp).unwrap();
663        assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(12345));
664
665        let resp = build_response(CACHE_CONTROL, "max-age=99999,s-maxage=123");
666        let cc = CacheControl::from_resp_headers(&resp).unwrap();
667        // prefer s-maxage over max-age
668        assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(123));
669    }
670
671    #[test]
672    fn test_cacheability() {
673        let resp = build_response(CACHE_CONTROL, "");
674        let cc = CacheControl::from_resp_headers(&resp).unwrap();
675        assert_eq!(cc.is_cacheable(), Cacheable::Default);
676
677        // uncacheable
678        let resp = build_response(CACHE_CONTROL, "private, max-age=12345");
679        let cc = CacheControl::from_resp_headers(&resp).unwrap();
680        assert_eq!(cc.is_cacheable(), Cacheable::No);
681
682        let resp = build_response(CACHE_CONTROL, "no-store, max-age=12345");
683        let cc = CacheControl::from_resp_headers(&resp).unwrap();
684        assert_eq!(cc.is_cacheable(), Cacheable::No);
685
686        // cacheable
687        let resp = build_response(CACHE_CONTROL, "public");
688        let cc = CacheControl::from_resp_headers(&resp).unwrap();
689        assert_eq!(cc.is_cacheable(), Cacheable::Yes);
690
691        let resp = build_response(CACHE_CONTROL, "max-age=0");
692        let cc = CacheControl::from_resp_headers(&resp).unwrap();
693        assert_eq!(cc.is_cacheable(), Cacheable::Yes);
694    }
695
696    #[test]
697    fn test_no_cache() {
698        let resp = build_response(CACHE_CONTROL, "no-cache, max-age=12345");
699        let cc = CacheControl::from_resp_headers(&resp).unwrap();
700        assert_eq!(cc.is_cacheable(), Cacheable::Yes);
701        assert_eq!(cc.fresh_duration().unwrap(), Duration::ZERO);
702    }
703
704    #[test]
705    fn test_no_cache_field_names() {
706        let resp = build_response(CACHE_CONTROL, "no-cache=\"set-cookie\", max-age=12345");
707        let cc = CacheControl::from_resp_headers(&resp).unwrap();
708        assert!(!cc.private());
709        assert_eq!(cc.is_cacheable(), Cacheable::Yes);
710        assert_eq!(cc.fresh_duration().unwrap(), Duration::from_secs(12345));
711        let mut field_names = cc.no_cache_field_names().unwrap();
712        assert_eq!(
713            str::from_utf8(field_names.next().unwrap()).unwrap(),
714            "set-cookie"
715        );
716        assert!(field_names.next().is_none());
717
718        let mut resp = response::Builder::new().body(()).unwrap();
719        resp.headers_mut().insert(
720            CACHE_CONTROL,
721            HeaderValue::from_bytes(
722                b"private=\"\", no-cache=\"a\xFF, set-cookie, Baz\x09 , c,d  ,, \"",
723            )
724            .unwrap(),
725        );
726        let (parts, _) = resp.into_parts();
727        let cc = CacheControl::from_resp_headers(&parts).unwrap();
728        let mut field_names = cc.private_field_names().unwrap();
729        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
730        assert!(field_names.next().is_none());
731        let mut field_names = cc.no_cache_field_names().unwrap();
732        assert!(str::from_utf8(field_names.next().unwrap()).is_err());
733        assert_eq!(
734            str::from_utf8(field_names.next().unwrap()).unwrap(),
735            "set-cookie"
736        );
737        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "Baz");
738        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "c");
739        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "d");
740        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
741        assert_eq!(str::from_utf8(field_names.next().unwrap()).unwrap(), "");
742        assert!(field_names.next().is_none());
743    }
744
745    #[test]
746    fn test_strip_private_headers() {
747        let mut resp = ResponseHeader::build(200, None).unwrap();
748        resp.append_header(
749            CACHE_CONTROL,
750            "no-cache=\"x-private-header\", max-age=12345",
751        )
752        .unwrap();
753        resp.append_header("X-Private-Header", "dropped").unwrap();
754
755        let cc = CacheControl::from_resp_headers(&resp).unwrap();
756        cc.strip_private_headers(&mut resp);
757        assert!(!resp.headers.contains_key("X-Private-Header"));
758    }
759
760    #[test]
761    fn test_stale_while_revalidate() {
762        let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-while-revalidate=5");
763        let cc = CacheControl::from_resp_headers(&resp).unwrap();
764        assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 5);
765        assert_eq!(
766            cc.serve_stale_while_revalidate_duration().unwrap(),
767            Duration::from_secs(5)
768        );
769        assert!(cc.serve_stale_if_error_duration().is_none());
770    }
771
772    #[test]
773    fn test_stale_if_error() {
774        let resp = build_response(CACHE_CONTROL, "max-age=12345, stale-if-error=3600");
775        let cc = CacheControl::from_resp_headers(&resp).unwrap();
776        assert_eq!(cc.stale_if_error().unwrap().unwrap(), 3600);
777        assert_eq!(
778            cc.serve_stale_if_error_duration().unwrap(),
779            Duration::from_secs(3600)
780        );
781        assert!(cc.serve_stale_while_revalidate_duration().is_none());
782    }
783
784    #[test]
785    fn test_must_revalidate() {
786        let resp = build_response(
787            CACHE_CONTROL,
788            "max-age=12345, stale-while-revalidate=60, stale-if-error=30, must-revalidate",
789        );
790        let cc = CacheControl::from_resp_headers(&resp).unwrap();
791        assert!(cc.must_revalidate());
792        assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
793        assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
794        assert_eq!(
795            cc.serve_stale_while_revalidate_duration().unwrap(),
796            Duration::ZERO
797        );
798        assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
799    }
800
801    #[test]
802    fn test_proxy_revalidate() {
803        let resp = build_response(
804            CACHE_CONTROL,
805            "max-age=12345, stale-while-revalidate=60, stale-if-error=30, proxy-revalidate",
806        );
807        let cc = CacheControl::from_resp_headers(&resp).unwrap();
808        assert!(cc.proxy_revalidate());
809        assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
810        assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
811        assert_eq!(
812            cc.serve_stale_while_revalidate_duration().unwrap(),
813            Duration::ZERO
814        );
815        assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
816    }
817
818    #[test]
819    fn test_s_maxage_stale() {
820        let resp = build_response(
821            CACHE_CONTROL,
822            "s-maxage=0, stale-while-revalidate=60, stale-if-error=30",
823        );
824        let cc = CacheControl::from_resp_headers(&resp).unwrap();
825        assert_eq!(cc.stale_while_revalidate().unwrap().unwrap(), 60);
826        assert_eq!(cc.stale_if_error().unwrap().unwrap(), 30);
827        assert_eq!(
828            cc.serve_stale_while_revalidate_duration().unwrap(),
829            Duration::ZERO
830        );
831        assert_eq!(cc.serve_stale_if_error_duration().unwrap(), Duration::ZERO);
832    }
833
834    #[test]
835    fn test_authorized_request() {
836        let resp = build_response(CACHE_CONTROL, "max-age=10");
837        let cc = CacheControl::from_resp_headers(&resp).unwrap();
838        assert!(!cc.allow_caching_authorized_req());
839
840        let resp = build_response(CACHE_CONTROL, "s-maxage=10");
841        let cc = CacheControl::from_resp_headers(&resp).unwrap();
842        assert!(cc.allow_caching_authorized_req());
843
844        let resp = build_response(CACHE_CONTROL, "public");
845        let cc = CacheControl::from_resp_headers(&resp).unwrap();
846        assert!(cc.allow_caching_authorized_req());
847
848        let resp = build_response(CACHE_CONTROL, "must-revalidate, max-age=0");
849        let cc = CacheControl::from_resp_headers(&resp).unwrap();
850        assert!(cc.allow_caching_authorized_req());
851
852        let resp = build_response(CACHE_CONTROL, "");
853        let cc = CacheControl::from_resp_headers(&resp).unwrap();
854        assert!(!cc.allow_caching_authorized_req());
855    }
856
857    fn build_request(cc_key: HeaderName, cc_value: &str) -> request::Parts {
858        let (parts, _) = request::Builder::new()
859            .header(cc_key, cc_value)
860            .body(())
861            .unwrap()
862            .into_parts();
863        parts
864    }
865
866    #[test]
867    fn test_request_only_if_cached() {
868        let req = build_request(CACHE_CONTROL, "only-if-cached=1");
869        let cc = CacheControl::from_req_headers(&req).unwrap();
870        assert!(cc.only_if_cached())
871    }
872}