Skip to main content

parse_net/
cache.rs

1//! `Cache-Control` (RFC 9111 section 5.2, with `immutable` from RFC 8246 and
2//! `stale-while-revalidate` and `stale-if-error` from RFC 5861).
3//!
4//! # Examples
5//!
6//! ```
7//! use parse_net::{self as pn, cache::Writer};
8//!
9//! let cc = pn::response_cache_control(b"public, max-age=\"60\", MAX-AGE=5, x-ext=1")?;
10//! assert_eq!((cc.public, cc.max_age, cc.duplicates, cc.unknown), (true, Some(60), true, 1));
11//!
12//! let mut buf = [0u8; 64];
13//! let mut out = pn::SliceSink::new(&mut buf);
14//! Writer::new(&mut out).flag("public").and_then(|w| w.seconds("max-age", 60)).expect("room");
15//! assert_eq!(out.written(), b"public, max-age=60");
16//! # Ok::<(), pn::ParseError>(())
17//! ```
18
19use crate::{
20    class,
21    error::{ErrorKind, ParseError, Result, WriteError},
22    limits::Limits,
23    list,
24    num::parse_u32_saturating,
25    scan::control_kind,
26    text::{Word, is_token, parse_word},
27    write::{Sink, reserve, write_decimal_u64},
28};
29
30/// One directive: a name and an optional argument.
31///
32/// # Examples
33///
34/// ```
35/// use parse_net::{cache, Limits};
36///
37/// let d = cache::parse(b"max-age=60", &Limits::DEFAULT)?.iter().next().expect("one directive");
38/// assert_eq!(d.name, b"max-age");
39/// assert!(d.value.is_some_and(|v| v.eq_bytes(b"60")));
40/// # Ok::<(), parse_net::ParseError>(())
41/// ```
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43pub struct Directive<'a> {
44    /// The name as sent (compare it ASCII case-insensitively).
45    pub name: &'a [u8],
46    /// The argument, token or quoted.
47    pub value: Option<Word<'a>>,
48}
49
50/// Parses one `token [ "=" ( token / quoted-string ) ]`.
51fn directive(e: &[u8]) -> Result<Directive<'_>> {
52    let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
53    if n == 0 {
54        return Err(bad(e, 0));
55    }
56    let name = e.get(..n).unwrap_or(&[]);
57    match e.get(n) {
58        None => Ok(Directive { name, value: None }),
59        Some(b'=') => {
60            let rest = e.get(n + 1..).unwrap_or(&[]);
61            let (w, used) = parse_word(rest).map_err(|x| match x.kind() {
62                ErrorKind::Empty => bad(e, n + 1),
63                _ => x.shifted(n + 1),
64            })?;
65            if n + 1 + used != e.len() {
66                return Err(bad(e, n + 1 + used));
67            }
68            Ok(Directive {
69                name,
70                value: Some(w),
71            })
72        }
73        Some(_) => Err(bad(e, n)),
74    }
75}
76
77fn bad(e: &[u8], at: usize) -> ParseError {
78    match e.get(at) {
79        Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
80        _ => ParseError::new(ErrorKind::NotToken, at),
81    }
82}
83
84/// Validated directives.
85///
86/// # Examples
87///
88/// ```
89/// use parse_net::{cache, Limits};
90///
91/// let d = cache::parse(b"no-store, max-age=0", &Limits::DEFAULT)?;
92/// assert_eq!(d.len(), 2);
93/// assert!(d.iter().any(|x| x.name.eq_ignore_ascii_case(b"NO-STORE")));
94/// # Ok::<(), parse_net::ParseError>(())
95/// ```
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
97pub struct Directives<'a> {
98    list: list::List<'a>,
99}
100
101impl<'a> Directives<'a> {
102    /// The directives, in order.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use parse_net::{cache, Limits};
108    ///
109    /// let d = cache::parse(b"public, max-age=60", &Limits::DEFAULT)?;
110    /// let has_value: Vec<bool> = d.iter().map(|x| x.value.is_some()).collect();
111    /// assert_eq!(has_value, [false, true]);
112    /// # Ok::<(), parse_net::ParseError>(())
113    /// ```
114    pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
115        self.list.iter().filter_map(|e| directive(e).ok())
116    }
117
118    /// The number of directives.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use parse_net::{cache, Limits};
124    ///
125    /// assert_eq!(cache::parse(b"no-cache, no-store, x-ext=1", &Limits::DEFAULT)?.len(), 3);
126    /// # Ok::<(), parse_net::ParseError>(())
127    /// ```
128    #[must_use]
129    pub const fn len(&self) -> usize {
130        self.list.len()
131    }
132
133    /// Whether there are no directives.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use parse_net::{cache, Limits};
139    ///
140    /// assert!(cache::parse(b"", &Limits::DEFAULT)?.is_empty());
141    /// assert!(!cache::parse(b"no-store", &Limits::DEFAULT)?.is_empty());
142    /// # Ok::<(), parse_net::ParseError>(())
143    /// ```
144    #[must_use]
145    pub const fn is_empty(&self) -> bool {
146        self.list.is_empty()
147    }
148}
149
150/// Parses `#( token [ "=" ( token / quoted-string ) ] )`, keeping every
151/// directive raw.
152///
153/// # Errors
154///
155/// The list errors; [`ErrorKind::NotToken`] for a malformed directive; the
156/// quoted-string errors.
157///
158/// # Examples
159///
160/// ```
161/// use parse_net::{cache, Limits};
162///
163/// let d = cache::parse(b"no-cache=\"Set-Cookie\", x-y", &Limits::DEFAULT)?;
164/// let names: Vec<&[u8]> = d.iter().map(|x| x.name).collect();
165/// assert_eq!(names, [&b"no-cache"[..], b"x-y"]);
166/// # Ok::<(), parse_net::ParseError>(())
167/// ```
168pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
169    let l = list::parse(value, limits)?;
170    for e in l.iter() {
171        let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
172        directive(e).map(|_| ()).map_err(|x| x.shifted(at))?;
173    }
174    Ok(Directives { list: l })
175}
176
177/// The response directives (RFC 9111 section 5.2.2 and the extensions).
178///
179/// Unknown directives are counted in `unknown` and available raw through
180/// [`parse`]. A directive given twice keeps its first value and sets
181/// `duplicates` (RFC 9111 section 4.2.1); an argument that does not fit
182/// (`max-age=60s`) leaves the field `None` and sets `invalid_values`: treat
183/// such a response as stale. Conflicting directives (`private, public`) are
184/// both reported.
185///
186/// # Examples
187///
188/// ```
189/// let cc = parse_net::response_cache_control(b"max-age=60, s-maxage=60s, immutable")?;
190/// assert_eq!(cc.max_age, Some(60));
191/// assert!(cc.immutable);
192/// assert!(cc.s_maxage.is_none() && cc.invalid_values);
193/// # Ok::<(), parse_net::ParseError>(())
194/// ```
195#[non_exhaustive]
196#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
197pub struct ResponseDirectives {
198    /// `max-age`, saturating at 2147483648.
199    pub max_age: Option<u32>,
200    /// `s-maxage`.
201    pub s_maxage: Option<u32>,
202    /// `no-cache` (with or without a field list).
203    pub no_cache: bool,
204    /// `no-store`.
205    pub no_store: bool,
206    /// `private` (with or without a field list).
207    pub private: bool,
208    /// `public`.
209    pub public: bool,
210    /// `must-revalidate`.
211    pub must_revalidate: bool,
212    /// `proxy-revalidate`.
213    pub proxy_revalidate: bool,
214    /// `must-understand`.
215    pub must_understand: bool,
216    /// `no-transform`.
217    pub no_transform: bool,
218    /// `immutable` (RFC 8246).
219    pub immutable: bool,
220    /// `stale-while-revalidate` (RFC 5861).
221    pub stale_while_revalidate: Option<u32>,
222    /// `stale-if-error` (RFC 5861).
223    pub stale_if_error: Option<u32>,
224    /// A known directive appeared more than once.
225    pub duplicates: bool,
226    /// A known directive had an argument that does not fit its grammar.
227    pub invalid_values: bool,
228    /// The number of extension directives (saturating).
229    pub unknown: u16,
230}
231
232/// The request directives (RFC 9111 section 5.2.1).
233///
234/// # Examples
235///
236/// ```
237/// let req = parse_net::request_cache_control(b"no-store, min-fresh=30, max-age=1, max-age=2")?;
238/// assert!(req.no_store);
239/// assert_eq!((req.min_fresh, req.max_age, req.duplicates), (Some(30), Some(1), true));
240/// # Ok::<(), parse_net::ParseError>(())
241/// ```
242#[non_exhaustive]
243#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
244pub struct RequestDirectives {
245    /// `max-age`.
246    pub max_age: Option<u32>,
247    /// `max-stale`: `Some(None)` without a value (any staleness).
248    pub max_stale: Option<Option<u32>>,
249    /// `min-fresh`.
250    pub min_fresh: Option<u32>,
251    /// `no-cache`.
252    pub no_cache: bool,
253    /// `no-store`.
254    pub no_store: bool,
255    /// `no-transform`.
256    pub no_transform: bool,
257    /// `only-if-cached`.
258    pub only_if_cached: bool,
259    /// `stale-if-error` (RFC 5861).
260    pub stale_if_error: Option<u32>,
261    /// A known directive appeared more than once.
262    pub duplicates: bool,
263    /// A known directive had an argument that does not fit its grammar.
264    pub invalid_values: bool,
265    /// The number of extension directives (saturating).
266    pub unknown: u16,
267}
268
269/// Reads a `delta-seconds` argument, token or quoted (RFC 9111 section 5.2:
270/// recipients accept both forms).
271fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
272    let mut buf = [0u8; 32];
273    let digits = match value {
274        Some(Word::Token(t)) => t,
275        Some(q @ Word::Quoted(_)) => match q.unescape_into(&mut buf) {
276            Ok(d) => d,
277            Err(_) => {
278                *invalid = true;
279                return None;
280            }
281        },
282        None => {
283            *invalid = true;
284            return None;
285        }
286    };
287    match parse_u32_saturating(digits) {
288        Ok(n) => Some(n),
289        Err(_) => {
290            *invalid = true;
291            None
292        }
293    }
294}
295
296/// A flag directive: no argument expected; one (other than a field list for
297/// `no-cache` and `private`) marks the value invalid.
298fn flag(
299    slot: &mut bool,
300    seen: &mut bool,
301    dup: &mut bool,
302    value: Option<Word<'_>>,
303    invalid: &mut bool,
304    list_ok: bool,
305) {
306    if *seen {
307        *dup = true;
308        return;
309    }
310    *seen = true;
311    *slot = true;
312    if value.is_some() && !list_ok {
313        *invalid = true;
314    }
315}
316
317/// The first value of a numeric directive; a repeat sets `dup`.
318fn number(
319    slot: &mut Option<u32>,
320    seen: &mut bool,
321    dup: &mut bool,
322    value: Option<Word<'_>>,
323    invalid: &mut bool,
324) {
325    if *seen {
326        *dup = true;
327        return;
328    }
329    *seen = true;
330    *slot = seconds(value, invalid);
331}
332
333fn response_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> ResponseDirectives {
334    let mut r = ResponseDirectives::default();
335    let mut seen = [false; 13];
336    for d in directives {
337        let n = d.name;
338        let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
339        let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
340        match () {
341            () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
342            () if is(b"s-maxage") => number(&mut r.s_maxage, &mut seen[1], dup, d.value, inv),
343            () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[2], dup, d.value, inv, true),
344            () if is(b"no-store") => flag(&mut r.no_store, &mut seen[3], dup, d.value, inv, false),
345            () if is(b"private") => flag(&mut r.private, &mut seen[4], dup, d.value, inv, true),
346            () if is(b"public") => flag(&mut r.public, &mut seen[5], dup, d.value, inv, false),
347            () if is(b"must-revalidate") => flag(
348                &mut r.must_revalidate,
349                &mut seen[6],
350                dup,
351                d.value,
352                inv,
353                false,
354            ),
355            () if is(b"proxy-revalidate") => flag(
356                &mut r.proxy_revalidate,
357                &mut seen[7],
358                dup,
359                d.value,
360                inv,
361                false,
362            ),
363            () if is(b"must-understand") => flag(
364                &mut r.must_understand,
365                &mut seen[8],
366                dup,
367                d.value,
368                inv,
369                false,
370            ),
371            () if is(b"no-transform") => {
372                flag(&mut r.no_transform, &mut seen[9], dup, d.value, inv, false)
373            }
374            () if is(b"immutable") => {
375                flag(&mut r.immutable, &mut seen[10], dup, d.value, inv, false)
376            }
377            () if is(b"stale-while-revalidate") => {
378                number(
379                    &mut r.stale_while_revalidate,
380                    &mut seen[11],
381                    dup,
382                    d.value,
383                    inv,
384                );
385            }
386            () if is(b"stale-if-error") => {
387                number(&mut r.stale_if_error, &mut seen[12], dup, d.value, inv)
388            }
389            () => r.unknown = r.unknown.saturating_add(1),
390        }
391    }
392    r
393}
394
395fn request_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> RequestDirectives {
396    let mut r = RequestDirectives::default();
397    let mut seen = [false; 8];
398    for d in directives {
399        let n = d.name;
400        let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
401        let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
402        match () {
403            () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
404            () if is(b"max-stale") => {
405                if seen[1] {
406                    *dup = true;
407                } else {
408                    seen[1] = true;
409                    r.max_stale = Some(match d.value {
410                        None => None,
411                        v => seconds(v, inv),
412                    });
413                }
414            }
415            () if is(b"min-fresh") => number(&mut r.min_fresh, &mut seen[2], dup, d.value, inv),
416            () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[3], dup, d.value, inv, false),
417            () if is(b"no-store") => flag(&mut r.no_store, &mut seen[4], dup, d.value, inv, false),
418            () if is(b"no-transform") => {
419                flag(&mut r.no_transform, &mut seen[5], dup, d.value, inv, false)
420            }
421            () if is(b"only-if-cached") => flag(
422                &mut r.only_if_cached,
423                &mut seen[6],
424                dup,
425                d.value,
426                inv,
427                false,
428            ),
429            () if is(b"stale-if-error") => {
430                number(&mut r.stale_if_error, &mut seen[7], dup, d.value, inv)
431            }
432            () => r.unknown = r.unknown.saturating_add(1),
433        }
434    }
435    r
436}
437
438/// Reads a response `Cache-Control` value.
439///
440/// # Errors
441///
442/// As [`parse`]: only structural errors; unknown directives and bad
443/// arguments are reported in the struct.
444///
445/// # Examples
446///
447/// ```
448/// let cc = parse_net::response_cache_control(b"max-age=99999999999999999999, private, public")?;
449/// assert_eq!(cc.max_age, Some(2_147_483_648));
450/// assert!(cc.private && cc.public);
451/// # Ok::<(), parse_net::ParseError>(())
452/// ```
453pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
454    Ok(response_from(parse(value, &Limits::DEFAULT)?.iter()))
455}
456
457/// Reads a request `Cache-Control` value.
458///
459/// # Errors
460///
461/// As [`parse`].
462///
463/// # Examples
464///
465/// ```
466/// let req = parse_net::request_cache_control(b"max-stale, no-cache")?;
467/// assert_eq!(req.max_stale, Some(None));
468/// assert!(req.no_cache);
469/// # Ok::<(), parse_net::ParseError>(())
470/// ```
471pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
472    Ok(request_from(parse(value, &Limits::DEFAULT)?.iter()))
473}
474
475/// Reads several response `Cache-Control` field lines as one.
476///
477/// # Errors
478///
479/// As [`parse`], with [`ParseError::line`] set.
480///
481/// # Examples
482///
483/// ```
484/// use parse_net::cache;
485///
486/// let lines: [&[u8]; 2] = [b"public", b"max-age=300"];
487/// let cc = cache::response_lines(lines)?;
488/// assert!(cc.public);
489/// assert_eq!(cc.max_age, Some(300));
490/// # Ok::<(), parse_net::ParseError>(())
491/// ```
492pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
493where
494    I: IntoIterator<Item = &'a [u8]>,
495    I::IntoIter: Clone,
496{
497    let lines = lines.into_iter();
498    for (n, line) in lines.clone().enumerate() {
499        parse(line, &Limits::DEFAULT)
500            .map(|_| ())
501            .map_err(|e| e.on_line(n))?;
502    }
503    Ok(response_from(lines.flat_map(|l| {
504        list::parse(l, &Limits::DEFAULT)
505            .into_iter()
506            .flat_map(|x| x.iter())
507            .filter_map(|e| directive(e).ok())
508    })))
509}
510
511/// Reads several request `Cache-Control` field lines as one.
512///
513/// # Errors
514///
515/// As [`parse`], with [`ParseError::line`] set.
516///
517/// # Examples
518///
519/// ```
520/// use parse_net::cache;
521///
522/// let lines: [&[u8]; 2] = [b"no-cache", b"max-stale=60"];
523/// let req = cache::request_lines(lines)?;
524/// assert!(req.no_cache);
525/// assert_eq!(req.max_stale, Some(Some(60)));
526/// # Ok::<(), parse_net::ParseError>(())
527/// ```
528pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
529where
530    I: IntoIterator<Item = &'a [u8]>,
531    I::IntoIter: Clone,
532{
533    let lines = lines.into_iter();
534    for (n, line) in lines.clone().enumerate() {
535        parse(line, &Limits::DEFAULT)
536            .map(|_| ())
537            .map_err(|e| e.on_line(n))?;
538    }
539    Ok(request_from(lines.flat_map(|l| {
540        list::parse(l, &Limits::DEFAULT)
541            .into_iter()
542            .flat_map(|x| x.iter())
543            .filter_map(|e| directive(e).ok())
544    })))
545}
546
547/// Writes `Cache-Control` directives, separated by `, `.
548///
549/// Delta-seconds are always written as tokens (senders must not quote
550/// them); field lists are always quoted.
551///
552/// # Examples
553///
554/// ```
555/// use parse_net::{cache::Writer, SliceSink};
556///
557/// let mut buf = [0u8; 64];
558/// let mut out = SliceSink::new(&mut buf);
559/// Writer::new(&mut out).flag("no-store")?.seconds("max-age", 0)?;
560/// assert_eq!(out.written(), b"no-store, max-age=0");
561/// # Ok::<(), parse_net::WriteError>(())
562/// ```
563pub struct Writer<'s, S: Sink> {
564    out: &'s mut S,
565    first: bool,
566}
567
568impl<'s, S: Sink> Writer<'s, S> {
569    /// A writer over `out`.
570    ///
571    /// # Examples
572    ///
573    /// ```
574    /// use parse_net::{cache::Writer, SliceSink};
575    ///
576    /// let mut buf = [0u8; 16];
577    /// let mut out = SliceSink::new(&mut buf);
578    /// let mut w = Writer::new(&mut out);
579    /// w.flag("public")?;
580    /// assert_eq!(out.written(), b"public");
581    /// # Ok::<(), parse_net::WriteError>(())
582    /// ```
583    pub fn new(out: &'s mut S) -> Self {
584        Self { out, first: true }
585    }
586
587    fn separator(&self) -> &'static [u8] {
588        if self.first { b"" } else { b", " }
589    }
590
591    /// Writes a directive with no argument (`no-store`).
592    ///
593    /// # Errors
594    ///
595    /// [`WriteError::NotToken`]; [`WriteError::Full`] (nothing written for
596    /// this directive when the sink reports its room).
597    ///
598    /// # Examples
599    ///
600    /// ```
601    /// use parse_net::{cache::Writer, SliceSink, WriteError};
602    ///
603    /// let mut buf = [0u8; 64];
604    /// let mut out = SliceSink::new(&mut buf);
605    /// Writer::new(&mut out).flag("no-store")?.flag("no-transform")?;
606    /// assert_eq!(out.written(), b"no-store, no-transform");
607    /// assert!(matches!(Writer::new(&mut out).flag("no store"), Err(WriteError::NotToken)));
608    /// # Ok::<(), parse_net::WriteError>(())
609    /// ```
610    pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
611        if !is_token(name.as_bytes()) {
612            return Err(WriteError::NotToken);
613        }
614        let sep = self.separator();
615        reserve(self.out, sep.len() + name.len())?;
616        self.out.put(sep)?;
617        self.out.put(name.as_bytes())?;
618        self.first = false;
619        Ok(self)
620    }
621
622    /// Writes `name=secs` (`max-age=60`).
623    ///
624    /// # Errors
625    ///
626    /// As [`flag`](Self::flag).
627    ///
628    /// # Examples
629    ///
630    /// ```
631    /// use parse_net::{cache::Writer, SliceSink};
632    ///
633    /// let mut buf = [0u8; 64];
634    /// let mut out = SliceSink::new(&mut buf);
635    /// Writer::new(&mut out).seconds("max-age", 3600)?.seconds("stale-while-revalidate", 30)?;
636    /// assert_eq!(out.written(), b"max-age=3600, stale-while-revalidate=30");
637    /// # Ok::<(), parse_net::WriteError>(())
638    /// ```
639    pub fn seconds(
640        &mut self,
641        name: &str,
642        secs: u32,
643    ) -> core::result::Result<&mut Self, WriteError> {
644        if !is_token(name.as_bytes()) {
645            return Err(WriteError::NotToken);
646        }
647        let mut digits = crate::write::CountSink::new();
648        write_decimal_u64(u64::from(secs), &mut digits)?;
649        let sep = self.separator();
650        reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
651        self.out.put(sep)?;
652        self.out.put(name.as_bytes())?;
653        self.out.put(b"=")?;
654        write_decimal_u64(u64::from(secs), self.out)?;
655        self.first = false;
656        Ok(self)
657    }
658
659    /// Writes `name="field, field"` (`no-cache="Set-Cookie"`).
660    ///
661    /// # Errors
662    ///
663    /// [`WriteError::NotToken`] for the name or a field name, and as
664    /// [`flag`](Self::flag).
665    ///
666    /// # Examples
667    ///
668    /// ```
669    /// use parse_net::{cache::Writer, SliceSink};
670    ///
671    /// let mut buf = [0u8; 64];
672    /// let mut out = SliceSink::new(&mut buf);
673    /// Writer::new(&mut out).fields("no-cache", &["Set-Cookie", "Authorization"]).expect("room");
674    /// assert_eq!(out.written(), b"no-cache=\"Set-Cookie, Authorization\"");
675    /// ```
676    pub fn fields(
677        &mut self,
678        name: &str,
679        fields: &[&str],
680    ) -> core::result::Result<&mut Self, WriteError> {
681        if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
682            return Err(WriteError::NotToken);
683        }
684        let sep = self.separator();
685        let list_len: usize =
686            fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
687        reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
688        self.out.put(sep)?;
689        self.out.put(name.as_bytes())?;
690        self.out.put(b"=\"")?;
691        for (i, f) in fields.iter().enumerate() {
692            if i > 0 {
693                self.out.put(b", ")?;
694            }
695            self.out.put(f.as_bytes())?;
696        }
697        self.out.put(b"\"")?;
698        self.first = false;
699        Ok(self)
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706
707    #[test]
708    fn test_response_directives_cases() {
709        let r = |v: &[u8]| response_cache_control(v).unwrap();
710        assert_eq!(r(b"max-age=0").max_age, Some(0));
711        assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
712        let bad = r(b"max-age=60s");
713        assert_eq!((bad.max_age, bad.invalid_values), (None, true));
714        assert_eq!(
715            r(b"max-age=99999999999999999999").max_age,
716            Some(2_147_483_648)
717        );
718        let dup = r(b"max-age=1, max-age=2");
719        assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
720        let nc = r(b"no-cache=\"Set-Cookie\"");
721        assert!(nc.no_cache && !nc.invalid_values);
722        let both = r(b"private, public");
723        assert!(both.private && both.public);
724        assert_eq!(r(b"x-unknown, y=1").unknown, 2);
725        assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
726        assert_eq!(
727            r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
728            Some(60)
729        );
730        assert!(r(b"no-store=1").invalid_values);
731        assert!(r(b"max-age").invalid_values);
732        assert_eq!(r(b"").unknown, 0);
733    }
734
735    #[test]
736    fn test_request_directives_cases() {
737        let q = |v: &[u8]| request_cache_control(v).unwrap();
738        assert_eq!(q(b"max-stale").max_stale, Some(None));
739        assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
740        assert!(q(b"only-if-cached").only_if_cached);
741        assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
742        assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
743    }
744
745    #[test]
746    fn test_cache_structure_errors() {
747        let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
748        assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
749        assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
750        assert_eq!(k(b"=5"), ErrorKind::NotToken);
751        assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
752        assert_eq!(k(b"a=\x01"), ErrorKind::Control);
753    }
754
755    #[test]
756    fn test_lines() {
757        let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
758        let r = response_lines(lines).unwrap();
759        assert!(r.public && r.max_age == Some(5));
760        let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
761        assert_eq!(response_lines(bad).unwrap_err().line(), 1);
762        let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
763        assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
764    }
765
766    #[test]
767    fn test_writer_round_trip_and_refusals() {
768        let mut buf = [0u8; 128];
769        let mut out = crate::SliceSink::new(&mut buf);
770        Writer::new(&mut out)
771            .flag("public")
772            .and_then(|w| w.seconds("max-age", u32::MAX))
773            .and_then(|w| w.fields("private", &["X-A"]))
774            .map(|_| ())
775            .unwrap();
776        let r = response_cache_control(out.written()).unwrap();
777        assert!(r.public && r.private);
778        assert_eq!(r.max_age, Some(2_147_483_648));
779        let mut w = Writer::new(&mut out);
780        assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
781        assert!(matches!(
782            w.fields("no-cache", &["a b"]),
783            Err(WriteError::NotToken)
784        ));
785        let mut small = [0u8; 3];
786        let mut out = crate::SliceSink::new(&mut small);
787        assert!(matches!(
788            Writer::new(&mut out).flag("public"),
789            Err(WriteError::Full)
790        ));
791        assert!(out.is_empty());
792    }
793}