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    error::{ErrorKind, ParseError, Result, WriteError},
21    limits::Limits,
22    num::parse_u32_saturating,
23    scan::control_kind,
24    text::{Word, is_token, parse_word, skip_ows, token_len, trim_ows},
25    write::{Sink, reserve, write_decimal_u64},
26};
27
28/// One directive: a name and an optional argument.
29///
30/// # Examples
31///
32/// ```
33/// use parse_net::{cache, Limits};
34///
35/// let d = cache::parse(b"max-age=60", &Limits::DEFAULT)?.iter().next().expect("one directive");
36/// assert_eq!(d.name, b"max-age");
37/// assert!(d.value.is_some_and(|v| v.eq_bytes(b"60")));
38/// # Ok::<(), parse_net::ParseError>(())
39/// ```
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub struct Directive<'a> {
42    /// The name as sent (compare it ASCII case-insensitively).
43    pub name: &'a [u8],
44    /// The argument, token or quoted.
45    pub value: Option<Word<'a>>,
46}
47
48/// Skips SP and HTAB.
49#[inline]
50fn skip_ws(mut s: &[u8]) -> &[u8] {
51    while let [b' ' | b'\t', rest @ ..] = s {
52        s = rest;
53    }
54    s
55}
56
57/// One directive at the front of `s`: `token [ "=" ( token / quoted-string ) ]`.
58/// Returns it and what follows; error offsets are relative to `s`.
59fn directive(s: &[u8]) -> Result<(Directive<'_>, &[u8])> {
60    let n = token_len(s);
61    if n == 0 {
62        return Err(bad(s, 0));
63    }
64    let (name, after) = s.split_at(n);
65    let [b'=', value @ ..] = after else {
66        return Ok((Directive { name, value: None }, after));
67    };
68    let (w, used) = parse_word(value).map_err(|x| match x.kind() {
69        ErrorKind::Empty => bad(s, n + 1),
70        _ => x.shifted(n + 1),
71    })?;
72    Ok((
73        Directive {
74            name,
75            value: Some(w),
76        },
77        value.get(used..).unwrap_or(&[]),
78    ))
79}
80
81/// How much of the list limits a field has used, over all of its lines.
82#[derive(Default)]
83struct Budget {
84    count: usize,
85    empty: usize,
86}
87
88/// Validates a `Cache-Control` value in one pass over `v` (OWS already
89/// trimmed), calling `f` for each directive and adding to `budget`. Empty
90/// list elements are skipped up to `max_empty_elements`. The cursor is a
91/// shrinking slice; offsets are computed only for an error.
92fn walk<'a>(
93    v: &'a [u8],
94    limits: &Limits,
95    budget: &mut Budget,
96    mut f: impl FnMut(Directive<'a>),
97) -> Result<()> {
98    let at = |rest: &[u8]| v.len() - rest.len();
99    let mut rest = v;
100    let mut after_comma = false;
101    loop {
102        rest = skip_ws(rest);
103        match rest {
104            [] if !after_comma => return Ok(()),
105            [] | [b',', ..] => {
106                budget.empty += 1;
107                if budget.empty > usize::from(limits.empty_elements) {
108                    return Err(ParseError::new(ErrorKind::TooManyEmptyElements, at(rest)));
109                }
110                let [_, tail @ ..] = rest else {
111                    return Ok(());
112                };
113                rest = tail;
114                after_comma = true;
115                continue;
116            }
117            _ => {}
118        }
119        let (d, tail) = directive(rest).map_err(|e| e.shifted(at(rest)))?;
120        budget.count += 1;
121        if budget.count > usize::from(limits.list_members) {
122            return Err(ParseError::new(ErrorKind::TooManyMembers, at(rest)));
123        }
124        f(d);
125        rest = skip_ws(tail);
126        match rest {
127            [] => return Ok(()),
128            [b',', tail @ ..] => {
129                rest = tail;
130                after_comma = true;
131            }
132            _ => return Err(bad(rest, 0).shifted(at(rest))),
133        }
134    }
135}
136
137/// Runs [`walk`] over a whole field value, offsets relative to it.
138fn walk_value<'a>(
139    value: &'a [u8],
140    limits: &Limits,
141    budget: &mut Budget,
142    f: impl FnMut(Directive<'a>),
143) -> Result<&'a [u8]> {
144    let lead = skip_ows(value);
145    let v = trim_ows(value);
146    walk(v, limits, budget, f).map_err(|e| e.shifted(lead))?;
147    Ok(v)
148}
149
150fn bad(e: &[u8], at: usize) -> ParseError {
151    match e.get(at) {
152        Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
153        _ => ParseError::new(ErrorKind::NotToken, at),
154    }
155}
156
157/// Validated directives.
158///
159/// # Examples
160///
161/// ```
162/// use parse_net::{cache, Limits};
163///
164/// let d = cache::parse(b"no-store, max-age=0", &Limits::DEFAULT)?;
165/// assert_eq!(d.len(), 2);
166/// assert!(d.iter().any(|x| x.name.eq_ignore_ascii_case(b"NO-STORE")));
167/// # Ok::<(), parse_net::ParseError>(())
168/// ```
169#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
170pub struct Directives<'a> {
171    /// The validated value, OWS trimmed.
172    text: &'a [u8],
173    count: usize,
174}
175
176impl<'a> Directives<'a> {
177    /// The directives, in order.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use parse_net::{cache, Limits};
183    ///
184    /// let d = cache::parse(b"public, max-age=60", &Limits::DEFAULT)?;
185    /// let has_value: Vec<bool> = d.iter().map(|x| x.value.is_some()).collect();
186    /// assert_eq!(has_value, [false, true]);
187    /// # Ok::<(), parse_net::ParseError>(())
188    /// ```
189    pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
190        DirectiveIter { s: self.text }
191    }
192
193    /// The number of directives.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use parse_net::{cache, Limits};
199    ///
200    /// assert_eq!(cache::parse(b"no-cache, no-store, x-ext=1", &Limits::DEFAULT)?.len(), 3);
201    /// # Ok::<(), parse_net::ParseError>(())
202    /// ```
203    #[must_use]
204    pub const fn len(&self) -> usize {
205        self.count
206    }
207
208    /// Whether there are no directives.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// use parse_net::{cache, Limits};
214    ///
215    /// assert!(cache::parse(b"", &Limits::DEFAULT)?.is_empty());
216    /// assert!(!cache::parse(b"no-store", &Limits::DEFAULT)?.is_empty());
217    /// # Ok::<(), parse_net::ParseError>(())
218    /// ```
219    #[must_use]
220    pub const fn is_empty(&self) -> bool {
221        self.count == 0
222    }
223}
224
225/// The directives of a validated [`Directives`].
226struct DirectiveIter<'a> {
227    s: &'a [u8],
228}
229
230impl<'a> Iterator for DirectiveIter<'a> {
231    type Item = Directive<'a>;
232
233    fn next(&mut self) -> Option<Directive<'a>> {
234        let mut rest = self.s;
235        while let [b' ' | b'\t' | b',', tail @ ..] = rest {
236            rest = tail;
237        }
238        let (d, tail) = directive(rest).ok()?;
239        self.s = tail;
240        Some(d)
241    }
242}
243
244/// Parses `#( token [ "=" ( token / quoted-string ) ] )`, keeping every
245/// directive raw.
246///
247/// # Errors
248///
249/// The list errors; [`ErrorKind::NotToken`] for a malformed directive; the
250/// quoted-string errors.
251///
252/// # Examples
253///
254/// ```
255/// use parse_net::{cache, Limits};
256///
257/// let d = cache::parse(b"no-cache=\"Set-Cookie\", x-y", &Limits::DEFAULT)?;
258/// let names: Vec<&[u8]> = d.iter().map(|x| x.name).collect();
259/// assert_eq!(names, [&b"no-cache"[..], b"x-y"]);
260/// # Ok::<(), parse_net::ParseError>(())
261/// ```
262pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
263    let mut budget = Budget::default();
264    let text = walk_value(value, limits, &mut budget, |_| {})?;
265    let count = budget.count;
266    Ok(Directives { text, count })
267}
268
269/// The response directives (RFC 9111 section 5.2.2 and the extensions).
270///
271/// Unknown directives are counted in `unknown` and available raw through
272/// [`parse`]. A directive given twice keeps its first value and sets
273/// `duplicates` (RFC 9111 section 4.2.1); an argument that does not fit
274/// (`max-age=60s`) leaves the field `None` and sets `invalid_values`: treat
275/// such a response as stale. Conflicting directives (`private, public`) are
276/// both reported.
277///
278/// # Examples
279///
280/// ```
281/// let cc = parse_net::response_cache_control(b"max-age=60, s-maxage=60s, immutable")?;
282/// assert_eq!(cc.max_age, Some(60));
283/// assert!(cc.immutable);
284/// assert!(cc.s_maxage.is_none() && cc.invalid_values);
285/// # Ok::<(), parse_net::ParseError>(())
286/// ```
287#[non_exhaustive]
288#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
289pub struct ResponseDirectives {
290    /// `max-age`, saturating at 2147483648.
291    pub max_age: Option<u32>,
292    /// `s-maxage`.
293    pub s_maxage: Option<u32>,
294    /// `no-cache` (with or without a field list).
295    pub no_cache: bool,
296    /// `no-store`.
297    pub no_store: bool,
298    /// `private` (with or without a field list).
299    pub private: bool,
300    /// `public`.
301    pub public: bool,
302    /// `must-revalidate`.
303    pub must_revalidate: bool,
304    /// `proxy-revalidate`.
305    pub proxy_revalidate: bool,
306    /// `must-understand`.
307    pub must_understand: bool,
308    /// `no-transform`.
309    pub no_transform: bool,
310    /// `immutable` (RFC 8246).
311    pub immutable: bool,
312    /// `stale-while-revalidate` (RFC 5861).
313    pub stale_while_revalidate: Option<u32>,
314    /// `stale-if-error` (RFC 5861).
315    pub stale_if_error: Option<u32>,
316    /// A known directive appeared more than once.
317    pub duplicates: bool,
318    /// A known directive had an argument that does not fit its grammar.
319    pub invalid_values: bool,
320    /// The number of extension directives (saturating).
321    pub unknown: u16,
322}
323
324/// The request directives (RFC 9111 section 5.2.1).
325///
326/// # Examples
327///
328/// ```
329/// let req = parse_net::request_cache_control(b"no-store, min-fresh=30, max-age=1, max-age=2")?;
330/// assert!(req.no_store);
331/// assert_eq!((req.min_fresh, req.max_age, req.duplicates), (Some(30), Some(1), true));
332/// # Ok::<(), parse_net::ParseError>(())
333/// ```
334#[non_exhaustive]
335#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
336pub struct RequestDirectives {
337    /// `max-age`.
338    pub max_age: Option<u32>,
339    /// `max-stale`: `Some(None)` without a value (any staleness).
340    pub max_stale: Option<Option<u32>>,
341    /// `min-fresh`.
342    pub min_fresh: Option<u32>,
343    /// `no-cache`.
344    pub no_cache: bool,
345    /// `no-store`.
346    pub no_store: bool,
347    /// `no-transform`.
348    pub no_transform: bool,
349    /// `only-if-cached`.
350    pub only_if_cached: bool,
351    /// `stale-if-error` (RFC 5861).
352    pub stale_if_error: Option<u32>,
353    /// A known directive appeared more than once.
354    pub duplicates: bool,
355    /// A known directive had an argument that does not fit its grammar.
356    pub invalid_values: bool,
357    /// The number of extension directives (saturating).
358    pub unknown: u16,
359}
360
361/// Reads a `delta-seconds` argument, token or quoted (RFC 9111 section 5.2:
362/// recipients accept both forms).
363fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
364    // Senders must not quote delta-seconds (RFC 9111 section 5.2), but
365    // recipients read both forms; a quoted pair is read through the
366    // unescaping iterator, so no buffer is needed.
367    let n = match value {
368        Some(Word::Token(t)) => parse_u32_saturating(t).ok(),
369        Some(Word::Quoted(q)) => match q.as_plain() {
370            Some(p) => parse_u32_saturating(p).ok(),
371            None => {
372                let mut n: Option<u32> = None;
373                for b in q.bytes() {
374                    if !b.is_ascii_digit() {
375                        n = None;
376                        break;
377                    }
378                    n = Some(
379                        n.unwrap_or(0)
380                            .saturating_mul(10)
381                            .saturating_add(u32::from(b - b'0'))
382                            .min(crate::num::DELTA_SECONDS_MAX),
383                    );
384                }
385                n
386            }
387        },
388        None => None,
389    };
390    if n.is_none() {
391        *invalid = true;
392    }
393    n
394}
395
396/// A flag directive: no argument expected; one (other than a field list for
397/// `no-cache` and `private`) marks the value invalid.
398fn flag(
399    slot: &mut bool,
400    seen: &mut bool,
401    dup: &mut bool,
402    value: Option<Word<'_>>,
403    invalid: &mut bool,
404    list_ok: bool,
405) {
406    if *seen {
407        *dup = true;
408        return;
409    }
410    *seen = true;
411    *slot = true;
412    if value.is_some() && !list_ok {
413        *invalid = true;
414    }
415}
416
417/// The first value of a numeric directive; a repeat sets `dup`.
418fn number(
419    slot: &mut Option<u32>,
420    seen: &mut bool,
421    dup: &mut bool,
422    value: Option<Word<'_>>,
423    invalid: &mut bool,
424) {
425    if *seen {
426        *dup = true;
427        return;
428    }
429    *seen = true;
430    *slot = seconds(value, invalid);
431}
432
433/// Whether the token `n` equals `lower`, a lowercase name of letters and
434/// `-`, ignoring ASCII case. Among token bytes, `b | 0x20` maps exactly `A-Z`
435/// onto `a-z` and leaves `-` alone (the only other byte it maps onto `-` is
436/// CR, which is not a token byte), so one OR per byte suffices. The length
437/// is a constant, so the bytes are compared as two to three overlapping
438/// words, OR-ing `0x20` into each byte of a word at once.
439#[inline]
440fn is_name<const N: usize>(n: &[u8], lower: &[u8; N]) -> bool {
441    let Ok(a) = <&[u8; N]>::try_from(n) else {
442        return false;
443    };
444    if N < 4 {
445        return a.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p);
446    }
447    if N < 8 {
448        let w = |s: &[u8; N], at: usize| {
449            s.get(at..at + 4)
450                .and_then(|c| <[u8; 4]>::try_from(c).ok())
451                .map_or(0, u32::from_le_bytes)
452        };
453        return w(a, 0) | 0x2020_2020 == w(lower, 0)
454            && w(a, N - 4) | 0x2020_2020 == w(lower, N - 4);
455    }
456    let w = |s: &[u8; N], at: usize| {
457        s.get(at..at + 8)
458            .and_then(|c| <[u8; 8]>::try_from(c).ok())
459            .map_or(0, u64::from_le_bytes)
460    };
461    let mut at = 0;
462    loop {
463        let i = at.min(N - 8);
464        if w(a, i) | 0x2020_2020_2020_2020 != w(lower, i) {
465            return false;
466        }
467        at += 8;
468        if at >= N {
469            return true;
470        }
471    }
472}
473
474/// The index of a response directive name, dispatched on length first.
475fn response_slot(n: &[u8]) -> Option<usize> {
476    match n.len() {
477        6 if is_name(n, b"public") => Some(5),
478        7 if is_name(n, b"max-age") => Some(0),
479        7 if is_name(n, b"private") => Some(4),
480        8 if is_name(n, b"s-maxage") => Some(1),
481        8 if is_name(n, b"no-cache") => Some(2),
482        8 if is_name(n, b"no-store") => Some(3),
483        9 if is_name(n, b"immutable") => Some(10),
484        12 if is_name(n, b"no-transform") => Some(9),
485        14 if is_name(n, b"stale-if-error") => Some(12),
486        15 if is_name(n, b"must-revalidate") => Some(6),
487        15 if is_name(n, b"must-understand") => Some(8),
488        16 if is_name(n, b"proxy-revalidate") => Some(7),
489        22 if is_name(n, b"stale-while-revalidate") => Some(11),
490        _ => None,
491    }
492}
493
494/// Builds [`ResponseDirectives`] one directive at a time.
495#[derive(Default)]
496struct ResponseBuilder {
497    r: ResponseDirectives,
498    seen: [bool; 13],
499}
500
501impl ResponseBuilder {
502    fn push(&mut self, d: Directive<'_>) {
503        let r = &mut self.r;
504        let Some(slot) = response_slot(d.name) else {
505            r.unknown = r.unknown.saturating_add(1);
506            return;
507        };
508        let Some(seen) = self.seen.get_mut(slot) else {
509            return;
510        };
511        let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
512        match slot {
513            0 => number(&mut r.max_age, seen, dup, d.value, inv),
514            1 => number(&mut r.s_maxage, seen, dup, d.value, inv),
515            2 => flag(&mut r.no_cache, seen, dup, d.value, inv, true),
516            3 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
517            4 => flag(&mut r.private, seen, dup, d.value, inv, true),
518            5 => flag(&mut r.public, seen, dup, d.value, inv, false),
519            6 => flag(&mut r.must_revalidate, seen, dup, d.value, inv, false),
520            7 => flag(&mut r.proxy_revalidate, seen, dup, d.value, inv, false),
521            8 => flag(&mut r.must_understand, seen, dup, d.value, inv, false),
522            9 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
523            10 => flag(&mut r.immutable, seen, dup, d.value, inv, false),
524            11 => number(&mut r.stale_while_revalidate, seen, dup, d.value, inv),
525            _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
526        }
527    }
528}
529
530/// The index of a request directive name, dispatched on length first.
531fn request_slot(n: &[u8]) -> Option<usize> {
532    match n.len() {
533        7 if is_name(n, b"max-age") => Some(0),
534        8 if is_name(n, b"no-cache") => Some(3),
535        8 if is_name(n, b"no-store") => Some(4),
536        9 if is_name(n, b"max-stale") => Some(1),
537        9 if is_name(n, b"min-fresh") => Some(2),
538        12 if is_name(n, b"no-transform") => Some(5),
539        14 if is_name(n, b"only-if-cached") => Some(6),
540        14 if is_name(n, b"stale-if-error") => Some(7),
541        _ => None,
542    }
543}
544
545/// Builds [`RequestDirectives`] one directive at a time.
546#[derive(Default)]
547struct RequestBuilder {
548    r: RequestDirectives,
549    seen: [bool; 8],
550}
551
552impl RequestBuilder {
553    fn push(&mut self, d: Directive<'_>) {
554        let r = &mut self.r;
555        let Some(slot) = request_slot(d.name) else {
556            r.unknown = r.unknown.saturating_add(1);
557            return;
558        };
559        let Some(seen) = self.seen.get_mut(slot) else {
560            return;
561        };
562        let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
563        match slot {
564            0 => number(&mut r.max_age, seen, dup, d.value, inv),
565            1 => {
566                if *seen {
567                    *dup = true;
568                } else {
569                    *seen = true;
570                    // A bare `max-stale` means any staleness; an argument that
571                    // cannot be read must not widen to that.
572                    r.max_stale = match d.value {
573                        None => Some(None),
574                        v => seconds(v, inv).map(Some),
575                    };
576                }
577            }
578            2 => number(&mut r.min_fresh, seen, dup, d.value, inv),
579            3 => flag(&mut r.no_cache, seen, dup, d.value, inv, false),
580            4 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
581            5 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
582            6 => flag(&mut r.only_if_cached, seen, dup, d.value, inv, false),
583            _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
584        }
585    }
586}
587
588/// Reads a response `Cache-Control` value.
589///
590/// # Errors
591///
592/// As [`parse`]: only structural errors; unknown directives and bad
593/// arguments are reported in the struct.
594///
595/// # Examples
596///
597/// ```
598/// let cc = parse_net::response_cache_control(b"max-age=99999999999999999999, private, public")?;
599/// assert_eq!(cc.max_age, Some(2_147_483_648));
600/// assert!(cc.private && cc.public);
601/// # Ok::<(), parse_net::ParseError>(())
602/// ```
603pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
604    let mut b = ResponseBuilder::default();
605    let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
606        b.push(d)
607    })?;
608    Ok(b.r)
609}
610
611/// Reads a request `Cache-Control` value.
612///
613/// # Errors
614///
615/// As [`parse`].
616///
617/// # Examples
618///
619/// ```
620/// let req = parse_net::request_cache_control(b"max-stale, no-cache")?;
621/// assert_eq!(req.max_stale, Some(None));
622/// assert!(req.no_cache);
623/// # Ok::<(), parse_net::ParseError>(())
624/// ```
625pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
626    let mut b = RequestBuilder::default();
627    let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
628        b.push(d)
629    })?;
630    Ok(b.r)
631}
632
633/// Reads several response `Cache-Control` field lines as one.
634///
635/// # Errors
636///
637/// As [`parse`], with [`ParseError::line`] set.
638///
639/// # Examples
640///
641/// ```
642/// use parse_net::cache;
643///
644/// let lines: [&[u8]; 2] = [b"public", b"max-age=300"];
645/// let cc = cache::response_lines(lines)?;
646/// assert!(cc.public);
647/// assert_eq!(cc.max_age, Some(300));
648/// # Ok::<(), parse_net::ParseError>(())
649/// ```
650pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
651where
652    I: IntoIterator<Item = &'a [u8]>,
653    I::IntoIter: Clone,
654{
655    let (mut b, mut budget) = (ResponseBuilder::default(), Budget::default());
656    for (n, line) in lines.into_iter().enumerate() {
657        let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
658            .map_err(|e| e.on_line(n))?;
659    }
660    Ok(b.r)
661}
662
663/// Reads several request `Cache-Control` field lines as one.
664///
665/// # Errors
666///
667/// As [`parse`], with [`ParseError::line`] set.
668///
669/// # Examples
670///
671/// ```
672/// use parse_net::cache;
673///
674/// let lines: [&[u8]; 2] = [b"no-cache", b"max-stale=60"];
675/// let req = cache::request_lines(lines)?;
676/// assert!(req.no_cache);
677/// assert_eq!(req.max_stale, Some(Some(60)));
678/// # Ok::<(), parse_net::ParseError>(())
679/// ```
680pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
681where
682    I: IntoIterator<Item = &'a [u8]>,
683    I::IntoIter: Clone,
684{
685    let (mut b, mut budget) = (RequestBuilder::default(), Budget::default());
686    for (n, line) in lines.into_iter().enumerate() {
687        let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
688            .map_err(|e| e.on_line(n))?;
689    }
690    Ok(b.r)
691}
692
693/// Writes `Cache-Control` directives, separated by `, `.
694///
695/// Delta-seconds are always written as tokens (senders must not quote
696/// them); field lists are always quoted.
697///
698/// # Examples
699///
700/// ```
701/// use parse_net::{cache::Writer, SliceSink};
702///
703/// let mut buf = [0u8; 64];
704/// let mut out = SliceSink::new(&mut buf);
705/// Writer::new(&mut out).flag("no-store")?.seconds("max-age", 0)?;
706/// assert_eq!(out.written(), b"no-store, max-age=0");
707/// # Ok::<(), parse_net::WriteError>(())
708/// ```
709pub struct Writer<'s, S: Sink> {
710    out: &'s mut S,
711    first: bool,
712}
713
714impl<'s, S: Sink> Writer<'s, S> {
715    /// A writer over `out`.
716    ///
717    /// # Examples
718    ///
719    /// ```
720    /// use parse_net::{cache::Writer, SliceSink};
721    ///
722    /// let mut buf = [0u8; 16];
723    /// let mut out = SliceSink::new(&mut buf);
724    /// let mut w = Writer::new(&mut out);
725    /// w.flag("public")?;
726    /// assert_eq!(out.written(), b"public");
727    /// # Ok::<(), parse_net::WriteError>(())
728    /// ```
729    pub fn new(out: &'s mut S) -> Self {
730        Self { out, first: true }
731    }
732
733    fn separator(&self) -> &'static [u8] {
734        if self.first { b"" } else { b", " }
735    }
736
737    /// Writes a directive with no argument (`no-store`).
738    ///
739    /// # Errors
740    ///
741    /// [`WriteError::NotToken`]; [`WriteError::Full`] (nothing written for
742    /// this directive when the sink reports its room).
743    ///
744    /// # Examples
745    ///
746    /// ```
747    /// use parse_net::{cache::Writer, SliceSink, WriteError};
748    ///
749    /// let mut buf = [0u8; 64];
750    /// let mut out = SliceSink::new(&mut buf);
751    /// Writer::new(&mut out).flag("no-store")?.flag("no-transform")?;
752    /// assert_eq!(out.written(), b"no-store, no-transform");
753    /// assert!(matches!(Writer::new(&mut out).flag("no store"), Err(WriteError::NotToken)));
754    /// # Ok::<(), parse_net::WriteError>(())
755    /// ```
756    pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
757        if !is_token(name.as_bytes()) {
758            return Err(WriteError::NotToken);
759        }
760        let sep = self.separator();
761        reserve(self.out, sep.len() + name.len())?;
762        self.out.put(sep)?;
763        self.out.put(name.as_bytes())?;
764        self.first = false;
765        Ok(self)
766    }
767
768    /// Writes `name=secs` (`max-age=60`).
769    ///
770    /// # Errors
771    ///
772    /// As [`flag`](Self::flag).
773    ///
774    /// # Examples
775    ///
776    /// ```
777    /// use parse_net::{cache::Writer, SliceSink};
778    ///
779    /// let mut buf = [0u8; 64];
780    /// let mut out = SliceSink::new(&mut buf);
781    /// Writer::new(&mut out).seconds("max-age", 3600)?.seconds("stale-while-revalidate", 30)?;
782    /// assert_eq!(out.written(), b"max-age=3600, stale-while-revalidate=30");
783    /// # Ok::<(), parse_net::WriteError>(())
784    /// ```
785    pub fn seconds(
786        &mut self,
787        name: &str,
788        secs: u32,
789    ) -> core::result::Result<&mut Self, WriteError> {
790        if !is_token(name.as_bytes()) {
791            return Err(WriteError::NotToken);
792        }
793        let mut digits = crate::write::CountSink::new();
794        write_decimal_u64(u64::from(secs), &mut digits)?;
795        let sep = self.separator();
796        reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
797        self.out.put(sep)?;
798        self.out.put(name.as_bytes())?;
799        self.out.put(b"=")?;
800        write_decimal_u64(u64::from(secs), self.out)?;
801        self.first = false;
802        Ok(self)
803    }
804
805    /// Writes `name="field, field"` (`no-cache="Set-Cookie"`).
806    ///
807    /// # Errors
808    ///
809    /// [`WriteError::NotToken`] for the name or a field name, and as
810    /// [`flag`](Self::flag).
811    ///
812    /// # Examples
813    ///
814    /// ```
815    /// use parse_net::{cache::Writer, SliceSink};
816    ///
817    /// let mut buf = [0u8; 64];
818    /// let mut out = SliceSink::new(&mut buf);
819    /// Writer::new(&mut out).fields("no-cache", &["Set-Cookie", "Authorization"]).expect("room");
820    /// assert_eq!(out.written(), b"no-cache=\"Set-Cookie, Authorization\"");
821    /// ```
822    pub fn fields(
823        &mut self,
824        name: &str,
825        fields: &[&str],
826    ) -> core::result::Result<&mut Self, WriteError> {
827        if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
828            return Err(WriteError::NotToken);
829        }
830        let sep = self.separator();
831        let list_len: usize =
832            fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
833        reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
834        self.out.put(sep)?;
835        self.out.put(name.as_bytes())?;
836        self.out.put(b"=\"")?;
837        for (i, f) in fields.iter().enumerate() {
838            if i > 0 {
839                self.out.put(b", ")?;
840            }
841            self.out.put(f.as_bytes())?;
842        }
843        self.out.put(b"\"")?;
844        self.first = false;
845        Ok(self)
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852
853    #[test]
854    fn test_response_directives_cases() {
855        let r = |v: &[u8]| response_cache_control(v).unwrap();
856        assert_eq!(r(b"max-age=0").max_age, Some(0));
857        assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
858        let bad = r(b"max-age=60s");
859        assert_eq!((bad.max_age, bad.invalid_values), (None, true));
860        assert_eq!(
861            r(b"max-age=99999999999999999999").max_age,
862            Some(2_147_483_648)
863        );
864        let dup = r(b"max-age=1, max-age=2");
865        assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
866        let nc = r(b"no-cache=\"Set-Cookie\"");
867        assert!(nc.no_cache && !nc.invalid_values);
868        let both = r(b"private, public");
869        assert!(both.private && both.public);
870        assert_eq!(r(b"x-unknown, y=1").unknown, 2);
871        assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
872        assert_eq!(
873            r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
874            Some(60)
875        );
876        assert!(r(b"no-store=1").invalid_values);
877        assert!(r(b"max-age").invalid_values);
878        assert_eq!(r(b"").unknown, 0);
879    }
880
881    #[test]
882    fn test_request_directives_cases() {
883        let q = |v: &[u8]| request_cache_control(v).unwrap();
884        assert_eq!(q(b"max-stale").max_stale, Some(None));
885        assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
886        assert!(q(b"only-if-cached").only_if_cached);
887        assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
888        assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
889    }
890
891    #[test]
892    fn test_cache_structure_errors() {
893        let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
894        assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
895        assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
896        assert_eq!(k(b"=5"), ErrorKind::NotToken);
897        assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
898        assert_eq!(k(b"a=\x01"), ErrorKind::Control);
899    }
900
901    #[test]
902    fn test_lines() {
903        let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
904        let r = response_lines(lines).unwrap();
905        assert!(r.public && r.max_age == Some(5));
906        let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
907        assert_eq!(response_lines(bad).unwrap_err().line(), 1);
908        let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
909        assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
910    }
911
912    #[test]
913    fn test_writer_round_trip_and_refusals() {
914        let mut buf = [0u8; 128];
915        let mut out = crate::SliceSink::new(&mut buf);
916        Writer::new(&mut out)
917            .flag("public")
918            .and_then(|w| w.seconds("max-age", u32::MAX))
919            .and_then(|w| w.fields("private", &["X-A"]))
920            .map(|_| ())
921            .unwrap();
922        let r = response_cache_control(out.written()).unwrap();
923        assert!(r.public && r.private);
924        assert_eq!(r.max_age, Some(2_147_483_648));
925        let mut w = Writer::new(&mut out);
926        assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
927        assert!(matches!(
928            w.fields("no-cache", &["a b"]),
929            Err(WriteError::NotToken)
930        ));
931        let mut small = [0u8; 3];
932        let mut out = crate::SliceSink::new(&mut small);
933        assert!(matches!(
934            Writer::new(&mut out).flag("public"),
935            Err(WriteError::Full)
936        ));
937        assert!(out.is_empty());
938    }
939
940    /// The word compare of `is_name` against the per-byte rule it replaces,
941    /// for every directive name with each byte set to every value in turn,
942    /// and for names one byte short or long.
943    #[test]
944    fn test_is_name_matches_bytewise() {
945        fn check<const N: usize>(lower: &[u8; N]) {
946            let reference =
947                |n: &[u8]| n.len() == N && n.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p);
948            let mut n = *lower;
949            for i in 0..N {
950                for b in 0..=255u8 {
951                    n[i] = b;
952                    assert_eq!(is_name(&n, lower), reference(&n), "{lower:?} at {i} = {b}");
953                }
954                n[i] = lower[i].to_ascii_uppercase();
955                assert!(is_name(&n, lower));
956            }
957            assert!(!is_name(&lower[..N - 1], lower));
958            let mut long = lower.to_vec();
959            long.push(b'a');
960            assert!(!is_name(&long, lower));
961        }
962        check(b"abc");
963        check(b"public");
964        check(b"max-age");
965        check(b"private");
966        check(b"s-maxage");
967        check(b"no-cache");
968        check(b"no-store");
969        check(b"immutable");
970        check(b"max-stale");
971        check(b"min-fresh");
972        check(b"no-transform");
973        check(b"only-if-cached");
974        check(b"stale-if-error");
975        check(b"must-revalidate");
976        check(b"must-understand");
977        check(b"proxy-revalidate");
978        check(b"stale-while-revalidate");
979    }
980}