Skip to main content

parse_net/
param.rs

1//! Parameters: `*( OWS ";" OWS [ parameter ] )` (RFC 9110 section 5.6.6).
2//!
3//! # Examples
4//!
5//! ```
6//! use parse_net::{param, Limits};
7//!
8//! let (p, used) = param::parse(b"; charset=utf-8;format=\"flowed\" , next", &Limits::DEFAULT)?;
9//! assert_eq!(used, 31);
10//! assert!(p.get("Charset").is_some_and(|v| v.eq_bytes(b"utf-8")));
11//! assert_eq!(format!("{p:?}"), "Params([\"charset\", \"format\"])");
12//! # Ok::<(), parse_net::ParseError>(())
13//! ```
14
15use core::fmt;
16
17use crate::{
18    class,
19    error::{ErrorKind, Result, err},
20    limits::{DuplicatePolicy, Limits},
21    scan::control_kind,
22    text::{Word, is_ows, parse_quoted, token_len, validated_word},
23};
24
25/// One parameter: a name and a value.
26///
27/// # Examples
28///
29/// ```
30/// use parse_net::{param, Limits, Param, Word};
31///
32/// let (p, _) = param::parse(b";charset=utf-8", &Limits::DEFAULT)?;
33/// assert_eq!(p.iter().next(), Some(Param { name: b"charset", value: Word::Token(b"utf-8") }));
34/// # Ok::<(), parse_net::ParseError>(())
35/// ```
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub struct Param<'a> {
38    /// The name as sent (compare it ASCII case-insensitively).
39    pub name: &'a [u8],
40    /// The value.
41    pub value: Word<'a>,
42}
43
44/// The most parameters one element may have when repeated names are
45/// refused: RFC 9110 parameters under [`DuplicatePolicy::Refuse`],
46/// authentication parameters, and MIME parameters. Their name offsets are
47/// kept on the stack, so a repeat is found by comparing names in place,
48/// never by reading the parameters again (D-072). Without it, the check for
49/// a repeat reads every earlier parameter, values included, and a field of
50/// many parameters costs the square of its length.
51///
52/// # Examples
53///
54/// ```
55/// use parse_net::{param, DuplicatePolicy, ErrorKind, Limits};
56///
57/// let many: String = (0..65).map(|i| format!(";p{i}=v")).collect::<Vec<_>>().concat();
58/// let wide = Limits::DEFAULT.max_params(1000);
59/// assert_eq!(param::parse(many.as_bytes(), &wide).unwrap_err().kind(), ErrorKind::TooManyMembers);
60/// // `First` and `Last` do not look for repeats, so `max_params` applies.
61/// let last = wide.duplicate_params(DuplicatePolicy::Last);
62/// assert_eq!(param::parse(many.as_bytes(), &last)?.0.len(), 65);
63/// assert_eq!(param::MAX_CHECKED_PARAMS, 64);
64/// # Ok::<(), parse_net::ParseError>(())
65/// ```
66pub const MAX_CHECKED_PARAMS: usize = 64;
67
68/// How many name offsets [`Names`] keeps inline. Nearly every list fits, and
69/// the rest of the table is then never written.
70const INLINE_NAMES: usize = 8;
71
72/// The names already seen in one parameter list, for the repeat check: their
73/// offsets in the list's text, compared in place (D-072). The caller keeps
74/// at most [`MAX_CHECKED_PARAMS`] names. `rest` stays `None` until a list
75/// passes [`INLINE_NAMES`], so a short list does not pay to zero the table.
76pub(crate) struct Names {
77    first: [usize; INLINE_NAMES],
78    rest: Option<[usize; MAX_CHECKED_PARAMS - INLINE_NAMES]>,
79    len: usize,
80    seen: u64,
81}
82
83impl Names {
84    #[inline]
85    pub(crate) const fn new() -> Self {
86        Self {
87            first: [0; INLINE_NAMES],
88            rest: None,
89            len: 0,
90            seen: 0,
91        }
92    }
93
94    /// Whether `name`, at `off` in `text`, repeats a recorded name (ASCII
95    /// case-insensitive); records it when it does not. `in_name` is the
96    /// names' byte class: a recorded name ends at the first byte outside it.
97    #[inline]
98    pub(crate) fn repeats(
99        &mut self,
100        text: &[u8],
101        off: usize,
102        name: &[u8],
103        in_name: fn(u8) -> bool,
104    ) -> bool {
105        let bit = name_bit(name);
106        if self.seen & bit == 0 {
107            if let Some(slot) = self.first.get_mut(self.len) {
108                self.seen |= bit;
109                *slot = off;
110                self.len += 1;
111                return false;
112            }
113        }
114        self.repeats_slow(text, off, name, in_name, bit)
115    }
116
117    /// [`repeats`](Self::repeats) when the filter cannot rule a repeat out,
118    /// or the list has passed [`INLINE_NAMES`].
119    #[cold]
120    fn repeats_slow(
121        &mut self,
122        text: &[u8],
123        off: usize,
124        name: &[u8],
125        in_name: fn(u8) -> bool,
126        bit: u64,
127    ) -> bool {
128        if self.seen & bit != 0 {
129            let l = name.len();
130            let same = |&o: &usize| {
131                text.get(o + l).is_none_or(|&b| !in_name(b))
132                    && text
133                        .get(o..o + l)
134                        .is_some_and(|e| e.eq_ignore_ascii_case(name))
135            };
136            let inline = self.first.get(..self.len.min(INLINE_NAMES));
137            let spilled = self.len.saturating_sub(INLINE_NAMES);
138            let rest = self.rest.as_ref().and_then(|r| r.get(..spilled));
139            if inline.unwrap_or(&[]).iter().any(same) || rest.unwrap_or(&[]).iter().any(same) {
140                return true;
141            }
142        }
143        self.seen |= bit;
144        let slot = match self.len.checked_sub(INLINE_NAMES) {
145            None => self.first.get_mut(self.len),
146            Some(i) => self
147                .rest
148                .get_or_insert([0; MAX_CHECKED_PARAMS - INLINE_NAMES])
149                .get_mut(i),
150        };
151        if let Some(slot) = slot {
152            *slot = off;
153            self.len += 1;
154        }
155        false
156    }
157}
158
159/// A small filter over parameter names, so the table of earlier names is
160/// searched only when a repeat could exist.
161#[inline]
162fn name_bit(name: &[u8]) -> u64 {
163    let first = name.first().map_or(0, u8::to_ascii_lowercase);
164    let last = name.last().map_or(0, u8::to_ascii_lowercase);
165    let h = (name.len() as u64)
166        .wrapping_mul(31)
167        .wrapping_add(u64::from(first))
168        .wrapping_mul(31)
169        .wrapping_add(u64::from(last));
170    1u64 << (h & 63)
171}
172
173/// Parses parameters at the start of `s`.
174///
175/// `s` starts at the first `;` (optionally preceded by OWS) or is empty.
176/// Parsing continues as long as the grammar does; the view and the number of
177/// bytes used are returned (OWS after the last parameter is not used), and
178/// whatever follows is the caller's to check. Empty parameters (`;;`, a
179/// trailing `;`) are allowed, as the grammar says. Whitespace around `=` is
180/// not (BWS is not part of this grammar).
181///
182/// # Errors
183///
184/// [`ErrorKind::NotToken`] for a bad name; [`ErrorKind::MissingValue`] for a
185/// name with no `=` or no value; [`ErrorKind::InvalidByte`] for whitespace
186/// around `=`; the quoted-string errors; [`ErrorKind::DuplicateParameter`]
187/// under [`DuplicatePolicy::Refuse`]; [`ErrorKind::TooManyMembers`] beyond [`Limits::max_params`], and under
188/// [`DuplicatePolicy::Refuse`] beyond [`MAX_CHECKED_PARAMS`] as well.
189///
190/// # Examples
191///
192/// ```
193/// use parse_net::{param, ErrorKind, Limits};
194///
195/// let dup = param::parse(b";a=1;A=2", &Limits::DEFAULT).unwrap_err();
196/// assert_eq!((dup.kind(), dup.offset()), (ErrorKind::DuplicateParameter, 5));
197/// assert_eq!(param::parse(b";a =1", &Limits::DEFAULT).unwrap_err().kind(), ErrorKind::InvalidByte);
198/// ```
199pub fn parse<'a>(s: &'a [u8], limits: &Limits) -> Result<(Params<'a>, usize)> {
200    let policy = limits.duplicate_params;
201    let refuse = policy == DuplicatePolicy::Refuse;
202    let max = if refuse {
203        usize::from(limits.params).min(MAX_CHECKED_PARAMS)
204    } else {
205        usize::from(limits.params)
206    };
207    // The cursor is a shrinking slice; an offset is computed only for an
208    // error or the result. `done` follows the last complete parameter.
209    let at = |rest: &[u8]| s.len() - rest.len();
210    let (mut done, mut count) = (s, 0usize);
211    let mut names = Names::new();
212    while let [b';', after @ ..] = skip_ws(done) {
213        let r = skip_ws(after);
214        let n = token_len(r);
215        if n == 0 {
216            match r.first() {
217                None | Some(b';') => {
218                    done = r;
219                    continue;
220                }
221                // OWS after an empty parameter followed by a list separator
222                // belongs to the caller.
223                Some(b',') => {
224                    done = after;
225                    break;
226                }
227                Some(&b) => {
228                    let kind = if crate::scan::is_control(b) {
229                        control_kind(b)
230                    } else {
231                        ErrorKind::NotToken
232                    };
233                    return err(kind, at(r));
234                }
235            }
236        }
237        let name_at = at(r);
238        let (name, r) = r.split_at(n);
239        let value = match r {
240            [b'=', value @ ..] => value,
241            [b, ..] if is_ows(*b) => {
242                let kind = if skip_ws(r).first() == Some(&b'=') {
243                    ErrorKind::InvalidByte
244                } else {
245                    ErrorKind::MissingValue
246                };
247                return err(kind, at(r));
248            }
249            [b'"', ..] => return err(ErrorKind::MissingValue, at(r)),
250            [b, ..] if *b != b';' && *b != b',' && class::is_field_text(*b) => {
251                return err(ErrorKind::NotToken, at(r));
252            }
253            [b, ..] if !class::is_field_text(*b) => return err(control_kind(*b), at(r)),
254            _ => return err(ErrorKind::MissingValue, at(r)),
255        };
256        let used = match value {
257            [b, ..] if is_ows(*b) => return err(ErrorKind::InvalidByte, at(value)),
258            [] | [b';' | b',', ..] => return err(ErrorKind::MissingValue, at(value)),
259            [b'"', ..] => parse_quoted(value).map_err(|e| e.shifted(at(value)))?.1,
260            [b, ..] => match token_len(value) {
261                0 => return err(control_or_invalid(*b), at(value)),
262                n => n,
263            },
264        };
265        count += 1;
266        if count > max {
267            return err(ErrorKind::TooManyMembers, name_at);
268        }
269        if refuse && names.repeats(s, name_at, name, class::is_tchar) {
270            return err(ErrorKind::DuplicateParameter, name_at);
271        }
272        done = value.get(used..).unwrap_or(&[]);
273    }
274    let used = at(done);
275    Ok((
276        Params {
277            text: s.get(..used).unwrap_or(&[]),
278            count: u32::try_from(count).unwrap_or(u32::MAX),
279            policy,
280        },
281        used,
282    ))
283}
284
285/// Skips SP and HTAB.
286#[inline]
287fn skip_ws(s: &[u8]) -> &[u8] {
288    match s {
289        [b' ' | b'\t', ..] => {
290            let n = s
291                .iter()
292                .position(|&b| b != b' ' && b != b'\t')
293                .unwrap_or(s.len());
294            s.get(n..).unwrap_or(&[])
295        }
296        _ => s,
297    }
298}
299
300#[inline]
301const fn control_or_invalid(b: u8) -> ErrorKind {
302    if crate::scan::is_control(b) {
303        control_kind(b)
304    } else {
305        ErrorKind::InvalidByte
306    }
307}
308
309/// Validated parameters. Iterating them cannot fail.
310///
311/// `Debug` prints the parameter names only: values can be secrets.
312///
313/// # Examples
314///
315/// ```
316/// use parse_net::{param, Limits};
317///
318/// let (p, used) = param::parse(b";q=1;level=\"2\"", &Limits::DEFAULT)?;
319/// assert_eq!((p.len(), used), (2, 14));
320/// assert!(p.get("level").is_some_and(|v| v.eq_bytes(b"2")));
321/// # Ok::<(), parse_net::ParseError>(())
322/// ```
323#[derive(Clone, Copy, PartialEq, Eq, Hash)]
324pub struct Params<'a> {
325    pub(crate) text: &'a [u8],
326    pub(crate) count: u32,
327    pub(crate) policy: DuplicatePolicy,
328}
329
330impl<'a> Params<'a> {
331    /// Every parameter, in order (with `First` or `Last`, duplicates
332    /// included).
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use parse_net::{param, Limits};
338    ///
339    /// let (p, _) = param::parse(b";a=1; B=2", &Limits::DEFAULT)?;
340    /// let names: Vec<&[u8]> = p.iter().map(|p| p.name).collect();
341    /// assert_eq!(names, [&b"a"[..], b"B"]);
342    /// # Ok::<(), parse_net::ParseError>(())
343    /// ```
344    #[must_use]
345    pub fn iter(&self) -> ParamIter<'a> {
346        ParamIter { rest: self.text }
347    }
348
349    /// The value of the parameter named `name` (ASCII case-insensitive): the
350    /// first match, or the last under [`DuplicatePolicy::Last`].
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use parse_net::{param, Limits};
356    ///
357    /// let (p, _) = param::parse(b";charset=UTF-8", &Limits::DEFAULT)?;
358    /// assert!(p.get("CHARSET").is_some_and(|v| v.eq_bytes(b"UTF-8")));
359    /// assert!(p.get("q").is_none());
360    /// # Ok::<(), parse_net::ParseError>(())
361    /// ```
362    #[must_use]
363    pub fn get(&self, name: &str) -> Option<Word<'a>> {
364        let name = name.as_bytes();
365        let last = self.policy == DuplicatePolicy::Last;
366        let mut found = None;
367        // Validated text: OWS ";" OWS [ name "=" word ] ... When `tail` starts
368        // with a token byte it starts a parameter name, which ends at its
369        // first `=`; so `name` followed by `=` there is that whole name when
370        // `name` holds no `=`. (An empty parameter starts `tail` with `;`.)
371        // No other byte is checked again.
372        let mut rest = self.text;
373        while let [b';', after @ ..] = skip_ws(rest) {
374            let tail = skip_ws(after);
375            let value = match tail.split_at_checked(name.len()) {
376                // An exact compare first: names are usually sent in the case
377                // asked for.
378                Some((head, [b'=', value @ ..]))
379                    if (head == name || head.eq_ignore_ascii_case(name))
380                        && head.first().is_some_and(|&b| class::is_tchar(b))
381                        && !name.contains(&b'=') =>
382                {
383                    let (word, used) = validated_word(value);
384                    if !last {
385                        return Some(word);
386                    }
387                    found = Some(word);
388                    rest = value.get(used..).unwrap_or(&[]);
389                    continue;
390                }
391                _ => match token_len(tail) {
392                    0 => {
393                        rest = tail;
394                        continue;
395                    }
396                    n => tail.get(n + 1..).unwrap_or(&[]),
397                },
398            };
399            rest = value.get(validated_word(value).1..).unwrap_or(&[]);
400        }
401        found
402    }
403
404    /// The parameters before the first one named `name` (ASCII
405    /// case-insensitive), and that parameter; all of them and `None` when no
406    /// parameter has the name. Used to split media-type parameters from
407    /// `q` and the `accept-ext` after it.
408    pub(crate) fn split_at_name(&self, name: &[u8]) -> (Params<'a>, Option<Param<'a>>) {
409        for (index, p) in self.iter().enumerate() {
410            if p.name.eq_ignore_ascii_case(name) {
411                // `p.name` points into `self.text`; the distance is its offset.
412                let off = (p.name.as_ptr() as usize).saturating_sub(self.text.as_ptr() as usize);
413                let head = self.text.get(..off).unwrap_or(&[]);
414                let semi = head.iter().rposition(|&b| b == b';').unwrap_or(0);
415                let mut text = self.text.get(..semi).unwrap_or(&[]);
416                while let [rest @ .., b' ' | b'\t'] = text {
417                    text = rest;
418                }
419                let before = Params {
420                    text,
421                    count: u32::try_from(index).unwrap_or(u32::MAX),
422                    policy: self.policy,
423                };
424                return (before, Some(p));
425            }
426        }
427        (*self, None)
428    }
429
430    /// The number of parameters.
431    ///
432    /// # Examples
433    ///
434    /// ```
435    /// use parse_net::{param, Limits};
436    ///
437    /// assert_eq!(param::parse(b";a=1;;b=2;", &Limits::DEFAULT)?.0.len(), 2);
438    /// # Ok::<(), parse_net::ParseError>(())
439    /// ```
440    #[must_use]
441    pub const fn len(&self) -> usize {
442        self.count as usize
443    }
444
445    /// Whether there are no parameters.
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// use parse_net::{param, Limits};
451    ///
452    /// assert!(param::parse(b"", &Limits::DEFAULT)?.0.is_empty());
453    /// assert!(!param::parse(b";a=1", &Limits::DEFAULT)?.0.is_empty());
454    /// # Ok::<(), parse_net::ParseError>(())
455    /// ```
456    #[must_use]
457    pub const fn is_empty(&self) -> bool {
458        self.count == 0
459    }
460
461    /// The validated text, from the first `;` to the end of the last
462    /// parameter.
463    ///
464    /// # Examples
465    ///
466    /// ```
467    /// use parse_net::{param, Limits};
468    ///
469    /// let (p, _) = param::parse(b";a=1 ;b=2 , next", &Limits::DEFAULT)?;
470    /// assert_eq!(p.raw(), b";a=1 ;b=2");
471    /// # Ok::<(), parse_net::ParseError>(())
472    /// ```
473    #[must_use]
474    pub const fn raw(&self) -> &'a [u8] {
475        self.text
476    }
477}
478
479impl fmt::Debug for Params<'_> {
480    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481        f.write_str("Params(")?;
482        f.debug_list()
483            .entries(
484                self.iter()
485                    .map(|p| core::str::from_utf8(p.name).unwrap_or("?")),
486            )
487            .finish()?;
488        f.write_str(")")
489    }
490}
491
492/// The parameters of a validated [`Params`].
493///
494/// # Examples
495///
496/// ```
497/// use parse_net::{param, Limits};
498///
499/// let (p, _) = param::parse(b";a=1;b=2", &Limits::DEFAULT)?;
500/// let mut it = p.iter();
501/// assert_eq!(it.next().map(|p| p.name), Some(&b"a"[..]));
502/// assert_eq!(it.next().map(|p| p.name), Some(&b"b"[..]));
503/// assert!(it.next().is_none());
504/// # Ok::<(), parse_net::ParseError>(())
505/// ```
506#[derive(Clone, Debug)]
507pub struct ParamIter<'a> {
508    rest: &'a [u8],
509}
510
511impl<'a> Iterator for ParamIter<'a> {
512    type Item = Param<'a>;
513
514    fn next(&mut self) -> Option<Param<'a>> {
515        loop {
516            // Validated text: OWS ";" OWS [ name "=" word ] ...
517            let [b';', after @ ..] = skip_ws(self.rest) else {
518                self.rest = &[];
519                return None;
520            };
521            let tail = skip_ws(after);
522            let n = token_len(tail);
523            let Some((name, [_, value_text @ ..])) = (n > 0).then(|| tail.split_at(n)) else {
524                self.rest = tail;
525                continue;
526            };
527            let (value, used) = validated_word(value_text);
528            self.rest = value_text.get(used..).unwrap_or(&[]);
529            return Some(Param { name, value });
530        }
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use std::{format, string::String, vec, vec::Vec};
537
538    use proptest::prelude::{Just, Strategy, prop, prop_assert_eq, prop_oneof};
539
540    use super::*;
541
542    fn p(s: &[u8]) -> Result<(Params<'_>, usize)> {
543        parse(s, &Limits::DEFAULT)
544    }
545
546    fn names<'a>(ps: &Params<'a>) -> Vec<&'a [u8]> {
547        ps.iter().map(|x| x.name).collect()
548    }
549
550    #[test]
551    fn test_params_edge_cases() {
552        assert_eq!(p(b"").unwrap().1, 0);
553        assert!(p(b";").unwrap().0.is_empty());
554        assert_eq!(p(b";").unwrap().1, 1);
555        assert_eq!(p(b";;").unwrap().1, 2);
556        let (ps, used) = p(b"; a=b").unwrap();
557        assert_eq!((used, names(&ps)), (5, [&b"a"[..]].to_vec()));
558        assert_eq!(p(b";a=").unwrap_err().kind(), ErrorKind::MissingValue);
559        assert_eq!(p(b";a").unwrap_err().kind(), ErrorKind::MissingValue);
560        assert_eq!(p(b";a;b=c").unwrap_err().kind(), ErrorKind::MissingValue);
561        assert_eq!(p(b";a =b").unwrap_err().kind(), ErrorKind::InvalidByte);
562        assert_eq!(p(b";a= b").unwrap_err().kind(), ErrorKind::InvalidByte);
563        assert_eq!(p(b";@=b").unwrap_err().kind(), ErrorKind::NotToken);
564        assert_eq!(p(b";a@=b").unwrap_err().kind(), ErrorKind::NotToken);
565        assert_eq!(p(b";a=\"\"").unwrap().0.get("a").map(|w| w.len()), Some(0));
566        assert!(
567            p(b";a=\"x\\\"y\"")
568                .unwrap()
569                .0
570                .get("a")
571                .unwrap()
572                .eq_bytes(b"x\"y")
573        );
574        assert_eq!(
575            p(b";a=\"x").unwrap_err().kind(),
576            ErrorKind::UnterminatedQuote
577        );
578        assert_eq!(p(b";a=b\rc").unwrap().1, 4);
579        assert_eq!(p(b";a=\x01").unwrap_err().kind(), ErrorKind::Control);
580    }
581
582    #[test]
583    fn test_params_stop_at_list_separator_and_trailing_ows() {
584        let (ps, used) = p(b";a=1 , b").unwrap();
585        assert_eq!((used, ps.len()), (4, 1));
586        let (_, used) = p(b";a=1; ,b").unwrap();
587        assert_eq!(used, 5);
588        let (_, used) = p(b" ;a=1 x").unwrap();
589        assert_eq!(used, 5);
590    }
591
592    #[test]
593    fn test_params_duplicate_refused_case_insensitive() {
594        let e = p(b";A=b;a=c").unwrap_err();
595        assert_eq!((e.kind(), e.offset()), (ErrorKind::DuplicateParameter, 5));
596        assert!(p(b";ab=1;ba=2;aa=3;bb=4").is_ok());
597    }
598
599    #[test]
600    fn test_params_first_and_last_policies() {
601        let first = Limits::DEFAULT.duplicate_params(DuplicatePolicy::First);
602        let last = Limits::DEFAULT.duplicate_params(DuplicatePolicy::Last);
603        let (a, _) = parse(b";a=1;A=2", &first).unwrap();
604        let (b, _) = parse(b";a=1;A=2", &last).unwrap();
605        assert!(a.get("a").unwrap().eq_bytes(b"1"));
606        assert!(b.get("a").unwrap().eq_bytes(b"2"));
607        assert_eq!(a.len(), 2);
608    }
609
610    #[test]
611    fn test_params_count_limit() {
612        let mut v = Vec::new();
613        for i in 0..257 {
614            v.extend_from_slice(format!(";p{i}=v").as_bytes());
615        }
616        let cut = v.len() - format!(";p{}=v", 256).len();
617        // `First` and `Last` read up to `max_params` (256 by default).
618        for policy in [DuplicatePolicy::First, DuplicatePolicy::Last] {
619            let l = Limits::DEFAULT.duplicate_params(policy);
620            assert_eq!(parse(&v[..cut], &l).unwrap().0.len(), 256);
621            assert_eq!(parse(&v, &l).unwrap_err().kind(), ErrorKind::TooManyMembers);
622        }
623        // `Refuse` stops at the size of its name table, whatever the limit.
624        let upto = |n: usize| {
625            (0..n)
626                .map(|i| format!(";p{i}=v"))
627                .collect::<Vec<_>>()
628                .concat()
629        };
630        assert_eq!(p(upto(MAX_CHECKED_PARAMS).as_bytes()).unwrap().0.len(), 64);
631        let over = upto(MAX_CHECKED_PARAMS + 1);
632        let e = p(over.as_bytes()).unwrap_err();
633        let last = over.rfind(';').unwrap() + 1;
634        assert_eq!((e.kind(), e.offset()), (ErrorKind::TooManyMembers, last));
635        let wide = Limits::DEFAULT.max_params(u16::MAX);
636        assert_eq!(
637            parse(over.as_bytes(), &wide).unwrap_err().kind(),
638            ErrorKind::TooManyMembers
639        );
640        assert_eq!(
641            parse(upto(3).as_bytes(), &Limits::DEFAULT.max_params(2))
642                .unwrap_err()
643                .kind(),
644            ErrorKind::TooManyMembers
645        );
646    }
647
648    #[test]
649    fn test_params_duplicate_found_in_every_table_slot() {
650        // A repeat of each of the 64 names is found, in either case, and a
651        // name that is a prefix or an extension of an earlier one is not a
652        // repeat.
653        let base = (0..63)
654            .map(|i| format!(";p{i}=\"v;p{i}=w\""))
655            .collect::<Vec<_>>()
656            .concat();
657        for i in 0..63 {
658            let dup = format!("{base};P{i}=x");
659            let e = p(dup.as_bytes()).unwrap_err();
660            assert_eq!(
661                (e.kind(), e.offset()),
662                (ErrorKind::DuplicateParameter, base.len() + 1),
663                "{i}"
664            );
665        }
666        for other in ["p", "p1x", "xp1", "p100"] {
667            assert!(p(format!("{base};{other}=x").as_bytes()).is_ok(), "{other}");
668        }
669    }
670
671    #[test]
672    fn test_params_debug_prints_names_only() {
673        let (ps, _) = p(b";user=alice;token=\"s3cr3t\"").unwrap();
674        let d = format!("{ps:?}");
675        assert_eq!(d, "Params([\"user\", \"token\"])");
676        assert!(!d.contains("s3cr3t") && !d.contains("alice"));
677    }
678
679    #[test]
680    fn test_params_iter_skips_empty_parameters() {
681        let (ps, _) = p(b";; a=1 ;;b=\"x;y\";").unwrap();
682        assert_eq!(names(&ps), [&b"a"[..], b"b"]);
683        assert!(ps.get("b").unwrap().eq_bytes(b"x;y"));
684        assert_eq!(ps.raw(), b";; a=1 ;;b=\"x;y\";");
685    }
686
687    #[test]
688    fn test_params_get_matches_whole_names_only() {
689        // Found by `test_iter_and_get_agree_with_checked_reads`: an empty
690        // parameter must not let `;a` match across the second `;`.
691        let (ps, _) = p(b";;a=\"\"").unwrap();
692        assert!(ps.get(";a").is_none());
693        assert!(ps.get("a").is_some_and(|v| v.eq_bytes(b"")));
694        let (ps, _) = p(b";a=b;c=d").unwrap();
695        for name in ["a=b", "a=b;c", "", ";", "=", "a;", " a"] {
696            assert!(ps.get(name).is_none(), "{name}");
697        }
698        assert!(ps.get("C").is_some_and(|v| v.eq_bytes(b"d")));
699    }
700
701    /// The 0.6 walker, kept as the reference the slice-cursor walker is
702    /// compared with (`test_parse_agrees_with_reference`).
703    mod reference {
704        use super::super::*;
705        use crate::text::{parse_word, skip_ows};
706
707        pub(super) fn parse<'a>(s: &'a [u8], limits: &Limits) -> Result<(Params<'a>, usize)> {
708            let policy = limits.duplicate_params;
709            let mut count: usize = 0;
710            let mut used = 0usize;
711            let mut seen: u64 = 0;
712            loop {
713                let ows = skip_ows(s.get(used..).unwrap_or(&[]));
714                let semi = used + ows;
715                if s.get(semi) != Some(&b';') {
716                    break;
717                }
718                let mut i = semi + 1;
719                i += skip_ows(s.get(i..).unwrap_or(&[]));
720                match s.get(i) {
721                    None | Some(b';') => {
722                        used = i;
723                        continue;
724                    }
725                    Some(&b) if !class::is_tchar(b) => {
726                        // OWS after an empty parameter followed by a list separator
727                        // or the end belongs to the caller.
728                        if b == b',' {
729                            used = semi + 1;
730                            break;
731                        }
732                        return err(
733                            if crate::scan::is_control(b) {
734                                control_kind(b)
735                            } else {
736                                ErrorKind::NotToken
737                            },
738                            i,
739                        );
740                    }
741                    Some(_) => {}
742                }
743                let name_start = i;
744                let name_len = s
745                    .get(i..)
746                    .unwrap_or(&[])
747                    .iter()
748                    .take_while(|&&b| class::is_tchar(b))
749                    .count();
750                let name = s.get(name_start..name_start + name_len).unwrap_or(&[]);
751                i += name_len;
752                match s.get(i) {
753                    Some(b'=') => i += 1,
754                    Some(&b) if is_ows(b) => {
755                        let after = i + skip_ows(s.get(i..).unwrap_or(&[]));
756                        if s.get(after) == Some(&b'=') {
757                            return err(ErrorKind::InvalidByte, i);
758                        }
759                        return err(ErrorKind::MissingValue, i);
760                    }
761                    Some(b'"') => return err(ErrorKind::MissingValue, i),
762                    Some(&b) if b != b';' && b != b',' && class::is_field_text(b) => {
763                        return err(ErrorKind::NotToken, i);
764                    }
765                    Some(&b) if !class::is_field_text(b) => return err(control_kind(b), i),
766                    _ => return err(ErrorKind::MissingValue, i),
767                }
768                let value_start = i;
769                match s.get(i) {
770                    Some(&b) if is_ows(b) => return err(ErrorKind::InvalidByte, i),
771                    None | Some(b';' | b',') => return err(ErrorKind::MissingValue, i),
772                    _ => {}
773                }
774                let rest = s.get(i..).unwrap_or(&[]);
775                let (_, n) = parse_word(rest).map_err(|e| match e.kind() {
776                    ErrorKind::Empty => crate::error::ParseError::new(
777                        control_or_invalid(rest.first().copied().unwrap_or(0)),
778                        value_start,
779                    ),
780                    _ => e.shifted(value_start),
781                })?;
782                i += n;
783                count += 1;
784                if count > usize::from(limits.params) {
785                    return err(ErrorKind::TooManyMembers, name_start);
786                }
787                if policy == DuplicatePolicy::Refuse {
788                    let bit = name_bit(name);
789                    if seen & bit != 0 {
790                        let earlier = Params {
791                            text: s.get(..used).unwrap_or(&[]),
792                            count: 0,
793                            policy,
794                        };
795                        if earlier.iter().any(|p| p.name.eq_ignore_ascii_case(name)) {
796                            return err(ErrorKind::DuplicateParameter, name_start);
797                        }
798                    }
799                    seen |= bit;
800                }
801                used = i;
802            }
803            Ok((
804                Params {
805                    text: s.get(..used).unwrap_or(&[]),
806                    count: u32::try_from(count).unwrap_or(u32::MAX),
807                    policy,
808                },
809                used,
810            ))
811        }
812    }
813
814    /// Parameter text built from the pieces the grammar turns on.
815    fn param_text() -> impl Strategy<Value = Vec<u8>> {
816        let piece = prop_oneof![
817            Just(b";".to_vec()),
818            Just(b" ".to_vec()),
819            Just(b"\t".to_vec()),
820            Just(b"=".to_vec()),
821            Just(b",".to_vec()),
822            Just(b"\"a\\\"b;c=d\"".to_vec()),
823            Just(b"\"\\".to_vec()),
824            Just(b"\r".to_vec()),
825            Just(b"\x7f".to_vec()),
826            "[a-cA-C]{1,3}".prop_map(String::into_bytes),
827            "[0-9]{1,3}".prop_map(String::into_bytes),
828            "[(){}@/]".prop_map(String::into_bytes),
829            "; ?[a-cA-C]{1,2}=[0-9a-c]{1,2}".prop_map(String::into_bytes),
830            "; ?[a-c]=\"[a-c;,= ]{0,3}\"".prop_map(String::into_bytes),
831        ];
832        prop::collection::vec(piece, 0..16).prop_map(|v| v.concat())
833    }
834
835    fn outcome(
836        r: Result<(Params<'_>, usize)>,
837    ) -> core::result::Result<(Vec<u8>, usize, usize), (ErrorKind, usize)> {
838        r.map(|(p, used)| (p.raw().to_vec(), p.len(), used))
839            .map_err(|e| (e.kind(), e.offset()))
840    }
841
842    /// Re-reads validated text with the checked public word parser.
843    fn reference_iter(raw: &[u8]) -> Vec<Param<'_>> {
844        let mut out = Vec::new();
845        let mut i = 0;
846        loop {
847            i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
848            if raw.get(i) != Some(&b';') {
849                return out;
850            }
851            i += 1;
852            i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
853            let Ok((name, n)) = crate::text::parse_token(raw.get(i..).unwrap_or(&[])) else {
854                continue;
855            };
856            i += n + 1;
857            let (value, n) = crate::text::parse_word(raw.get(i..).unwrap_or(&[])).unwrap();
858            i += n;
859            out.push(Param { name, value });
860        }
861    }
862
863    proptest::proptest! {
864        #![proptest_config(proptest::prelude::ProptestConfig::with_cases(4096))]
865
866        #[test]
867        fn test_parse_agrees_with_reference(v in param_text()) {
868            for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
869                let limits = Limits::DEFAULT.duplicate_params(policy);
870                prop_assert_eq!(outcome(parse(&v, &limits)), outcome(reference::parse(&v, &limits)));
871            }
872        }
873
874        #[test]
875        fn test_iter_and_get_agree_with_checked_reads(v in param_text(), name in "[a-cA-C=; \"]{0,4}") {
876            for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
877                let limits = Limits::DEFAULT.duplicate_params(policy);
878                let Ok((p, _)) = parse(&v, &limits) else { continue };
879                let all: Vec<Param<'_>> = p.iter().collect();
880                prop_assert_eq!(&all, &reference_iter(p.raw()));
881                prop_assert_eq!(all.len(), p.len());
882                let mut matching = all.iter().filter(|x| x.name.eq_ignore_ascii_case(name.as_bytes()));
883                let expected = if policy == DuplicatePolicy::Last { matching.next_back() } else { matching.next() };
884                prop_assert_eq!(p.get(&name), expected.map(|x| x.value));
885                for x in &all {
886                    let n = core::str::from_utf8(x.name).unwrap();
887                    let mut same = all.iter().filter(|y| y.name.eq_ignore_ascii_case(x.name));
888                    let expected = if policy == DuplicatePolicy::Last { same.next_back() } else { same.next() };
889                    prop_assert_eq!(p.get(n), expected.map(|y| y.value));
890                }
891            }
892        }
893    }
894}