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