use core::fmt;
use crate::{
class,
error::{ErrorKind, Result, err},
limits::{DuplicatePolicy, Limits},
scan::control_kind,
text::{Word, is_ows, parse_quoted, token_len, validated_word},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Param<'a> {
pub name: &'a [u8],
pub value: Word<'a>,
}
pub const MAX_CHECKED_PARAMS: usize = 64;
const INLINE_NAMES: usize = 8;
pub(crate) struct Names {
first: [usize; INLINE_NAMES],
rest: Option<[usize; MAX_CHECKED_PARAMS - INLINE_NAMES]>,
len: usize,
seen: u64,
}
impl Names {
#[inline]
pub(crate) const fn new() -> Self {
Self {
first: [0; INLINE_NAMES],
rest: None,
len: 0,
seen: 0,
}
}
#[inline]
pub(crate) fn repeats(
&mut self,
text: &[u8],
off: usize,
name: &[u8],
in_name: fn(u8) -> bool,
) -> bool {
let bit = name_bit(name);
if self.seen & bit == 0 {
if let Some(slot) = self.first.get_mut(self.len) {
self.seen |= bit;
*slot = off;
self.len += 1;
return false;
}
}
self.repeats_slow(text, off, name, in_name, bit)
}
#[cold]
fn repeats_slow(
&mut self,
text: &[u8],
off: usize,
name: &[u8],
in_name: fn(u8) -> bool,
bit: u64,
) -> bool {
if self.seen & bit != 0 {
let l = name.len();
let same = |&o: &usize| {
text.get(o + l).is_none_or(|&b| !in_name(b))
&& text
.get(o..o + l)
.is_some_and(|e| e.eq_ignore_ascii_case(name))
};
let inline = self.first.get(..self.len.min(INLINE_NAMES));
let spilled = self.len.saturating_sub(INLINE_NAMES);
let rest = self.rest.as_ref().and_then(|r| r.get(..spilled));
if inline.unwrap_or(&[]).iter().any(same) || rest.unwrap_or(&[]).iter().any(same) {
return true;
}
}
self.seen |= bit;
let slot = match self.len.checked_sub(INLINE_NAMES) {
None => self.first.get_mut(self.len),
Some(i) => self
.rest
.get_or_insert([0; MAX_CHECKED_PARAMS - INLINE_NAMES])
.get_mut(i),
};
if let Some(slot) = slot {
*slot = off;
self.len += 1;
}
false
}
}
#[inline]
fn name_bit(name: &[u8]) -> u64 {
let first = name.first().map_or(0, u8::to_ascii_lowercase);
let last = name.last().map_or(0, u8::to_ascii_lowercase);
let h = (name.len() as u64)
.wrapping_mul(31)
.wrapping_add(u64::from(first))
.wrapping_mul(31)
.wrapping_add(u64::from(last));
1u64 << (h & 63)
}
pub fn parse<'a>(s: &'a [u8], limits: &Limits) -> Result<(Params<'a>, usize)> {
walk::<false>(s, limits)
}
pub fn parse_optional<'a>(s: &'a [u8], limits: &Limits) -> Result<(OptionalParams<'a>, usize)> {
let (p, used) = walk::<true>(s, limits)?;
Ok((
OptionalParams {
text: p.text,
count: p.count,
policy: p.policy,
},
used,
))
}
#[inline]
fn walk<'a, const OPTIONAL: bool>(s: &'a [u8], limits: &Limits) -> Result<(Params<'a>, usize)> {
let policy = limits.duplicate_params;
let refuse = policy == DuplicatePolicy::Refuse;
let max = if refuse {
usize::from(limits.params).min(MAX_CHECKED_PARAMS)
} else {
usize::from(limits.params)
};
let at = |rest: &[u8]| s.len() - rest.len();
let (mut done, mut count) = (s, 0usize);
let mut names = Names::new();
while let [b';', after @ ..] = skip_ws(done) {
let r = skip_ws(after);
let n = token_len(r);
if n == 0 {
match r.first() {
None | Some(b';') => {
done = r;
continue;
}
Some(b',') => {
done = after;
break;
}
Some(&b) => {
let kind = if crate::scan::is_control(b) {
control_kind(b)
} else {
ErrorKind::NotToken
};
return err(kind, at(r));
}
}
}
let name_at = at(r);
let (name, r) = r.split_at(n);
let bare = OPTIONAL
&& match r {
[] | [b';' | b',', ..] => true,
[b, ..] if is_ows(*b) => skip_ws(r).first() != Some(&b'='),
_ => false,
};
let next = if bare {
r
} else {
let value = match r {
[b'=', value @ ..] => value,
[b, ..] if is_ows(*b) => {
let kind = if skip_ws(r).first() == Some(&b'=') {
ErrorKind::InvalidByte
} else {
ErrorKind::MissingValue
};
return err(kind, at(r));
}
[b'"', ..] => return err(ErrorKind::MissingValue, at(r)),
[b, ..] if *b != b';' && *b != b',' && class::is_field_text(*b) => {
return err(ErrorKind::NotToken, at(r));
}
[b, ..] if !class::is_field_text(*b) => return err(control_kind(*b), at(r)),
_ => return err(ErrorKind::MissingValue, at(r)),
};
let used = match value {
[b, ..] if is_ows(*b) => return err(ErrorKind::InvalidByte, at(value)),
[] | [b';' | b',', ..] => return err(ErrorKind::MissingValue, at(value)),
[b'"', ..] => parse_quoted(value).map_err(|e| e.shifted(at(value)))?.1,
[b, ..] => match token_len(value) {
0 => return err(control_or_invalid(*b), at(value)),
n => n,
},
};
value.get(used..).unwrap_or(&[])
};
count += 1;
if count > max {
return err(ErrorKind::TooManyMembers, name_at);
}
if refuse && names.repeats(s, name_at, name, class::is_tchar) {
return err(ErrorKind::DuplicateParameter, name_at);
}
done = next;
}
let used = at(done);
Ok((
Params {
text: s.get(..used).unwrap_or(&[]),
count: u32::try_from(count).unwrap_or(u32::MAX),
policy,
},
used,
))
}
#[inline]
fn skip_ws(s: &[u8]) -> &[u8] {
match s {
[b' ' | b'\t', ..] => {
let n = s
.iter()
.position(|&b| b != b' ' && b != b'\t')
.unwrap_or(s.len());
s.get(n..).unwrap_or(&[])
}
_ => s,
}
}
#[inline]
const fn control_or_invalid(b: u8) -> ErrorKind {
if crate::scan::is_control(b) {
control_kind(b)
} else {
ErrorKind::InvalidByte
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Params<'a> {
pub(crate) text: &'a [u8],
pub(crate) count: u32,
pub(crate) policy: DuplicatePolicy,
}
impl<'a> Params<'a> {
#[must_use]
pub fn iter(&self) -> ParamIter<'a> {
ParamIter { rest: self.text }
}
#[must_use]
pub fn get(&self, name: &str) -> Option<Word<'a>> {
let name = name.as_bytes();
let last = self.policy == DuplicatePolicy::Last;
let mut found = None;
let mut rest = self.text;
while let [b';', after @ ..] = skip_ws(rest) {
let tail = skip_ws(after);
let value = match tail.split_at_checked(name.len()) {
Some((head, [b'=', value @ ..]))
if (head == name || head.eq_ignore_ascii_case(name))
&& head.first().is_some_and(|&b| class::is_tchar(b))
&& !name.contains(&b'=') =>
{
let (word, used) = validated_word(value);
if !last {
return Some(word);
}
found = Some(word);
rest = value.get(used..).unwrap_or(&[]);
continue;
}
_ => match token_len(tail) {
0 => {
rest = tail;
continue;
}
n => tail.get(n + 1..).unwrap_or(&[]),
},
};
rest = value.get(validated_word(value).1..).unwrap_or(&[]);
}
found
}
pub(crate) fn split_at_name(&self, name: &[u8]) -> (Params<'a>, Option<Param<'a>>) {
for (index, p) in self.iter().enumerate() {
if p.name.eq_ignore_ascii_case(name) {
let off = (p.name.as_ptr() as usize).saturating_sub(self.text.as_ptr() as usize);
let head = self.text.get(..off).unwrap_or(&[]);
let semi = head.iter().rposition(|&b| b == b';').unwrap_or(0);
let mut text = self.text.get(..semi).unwrap_or(&[]);
while let [rest @ .., b' ' | b'\t'] = text {
text = rest;
}
let before = Params {
text,
count: u32::try_from(index).unwrap_or(u32::MAX),
policy: self.policy,
};
return (before, Some(p));
}
}
(*self, None)
}
#[must_use]
pub const fn len(&self) -> usize {
self.count as usize
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub const fn raw(&self) -> &'a [u8] {
self.text
}
}
impl fmt::Debug for Params<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Params(")?;
f.debug_list()
.entries(
self.iter()
.map(|p| core::str::from_utf8(p.name).unwrap_or("?")),
)
.finish()?;
f.write_str(")")
}
}
#[derive(Clone, Debug)]
pub struct ParamIter<'a> {
rest: &'a [u8],
}
impl<'a> Iterator for ParamIter<'a> {
type Item = Param<'a>;
fn next(&mut self) -> Option<Param<'a>> {
loop {
let [b';', after @ ..] = skip_ws(self.rest) else {
self.rest = &[];
return None;
};
let tail = skip_ws(after);
let n = token_len(tail);
let Some((name, [_, value_text @ ..])) = (n > 0).then(|| tail.split_at(n)) else {
self.rest = tail;
continue;
};
let (value, used) = validated_word(value_text);
self.rest = value_text.get(used..).unwrap_or(&[]);
return Some(Param { name, value });
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OptionalParam<'a> {
pub name: &'a [u8],
pub value: Option<Word<'a>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct OptionalParams<'a> {
text: &'a [u8],
count: u32,
policy: DuplicatePolicy,
}
impl<'a> OptionalParams<'a> {
#[must_use]
pub fn iter(&self) -> OptionalParamIter<'a> {
OptionalParamIter { rest: self.text }
}
#[must_use]
pub fn find(&self, name: &str) -> Option<OptionalParam<'a>> {
let name = name.as_bytes();
let mut matching = self.iter().filter(|p| p.name.eq_ignore_ascii_case(name));
if self.policy == DuplicatePolicy::Last {
matching.last()
} else {
matching.next()
}
}
#[must_use]
pub fn contains(&self, name: &str) -> bool {
let name = name.as_bytes();
self.iter().any(|p| p.name.eq_ignore_ascii_case(name))
}
#[must_use]
pub const fn len(&self) -> usize {
self.count as usize
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub const fn raw(&self) -> &'a [u8] {
self.text
}
}
impl fmt::Debug for OptionalParams<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("OptionalParams(")?;
f.debug_list()
.entries(
self.iter()
.map(|p| core::str::from_utf8(p.name).unwrap_or("?")),
)
.finish()?;
f.write_str(")")
}
}
#[derive(Clone, Debug)]
pub struct OptionalParamIter<'a> {
rest: &'a [u8],
}
impl<'a> Iterator for OptionalParamIter<'a> {
type Item = OptionalParam<'a>;
fn next(&mut self) -> Option<OptionalParam<'a>> {
loop {
let [b';', after @ ..] = skip_ws(self.rest) else {
self.rest = &[];
return None;
};
let tail = skip_ws(after);
let n = token_len(tail);
if n == 0 {
self.rest = tail;
continue;
}
let (name, r) = tail.split_at(n);
let [b'=', value_text @ ..] = r else {
self.rest = r;
return Some(OptionalParam { name, value: None });
};
let (value, used) = validated_word(value_text);
self.rest = value_text.get(used..).unwrap_or(&[]);
return Some(OptionalParam {
name,
value: Some(value),
});
}
}
}
#[cfg(test)]
mod tests {
use std::{format, string::String, vec, vec::Vec};
use proptest::prelude::{Just, Strategy, prop, prop_assert_eq, prop_oneof};
use super::*;
fn p(s: &[u8]) -> Result<(Params<'_>, usize)> {
parse(s, &Limits::DEFAULT)
}
fn names<'a>(ps: &Params<'a>) -> Vec<&'a [u8]> {
ps.iter().map(|x| x.name).collect()
}
#[test]
fn test_params_edge_cases() {
assert_eq!(p(b"").unwrap().1, 0);
assert!(p(b";").unwrap().0.is_empty());
assert_eq!(p(b";").unwrap().1, 1);
assert_eq!(p(b";;").unwrap().1, 2);
let (ps, used) = p(b"; a=b").unwrap();
assert_eq!((used, names(&ps)), (5, [&b"a"[..]].to_vec()));
assert_eq!(p(b";a=").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(p(b";a").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(p(b";a;b=c").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(p(b";a =b").unwrap_err().kind(), ErrorKind::InvalidByte);
assert_eq!(p(b";a= b").unwrap_err().kind(), ErrorKind::InvalidByte);
assert_eq!(p(b";@=b").unwrap_err().kind(), ErrorKind::NotToken);
assert_eq!(p(b";a@=b").unwrap_err().kind(), ErrorKind::NotToken);
assert_eq!(p(b";a=\"\"").unwrap().0.get("a").map(|w| w.len()), Some(0));
assert!(
p(b";a=\"x\\\"y\"")
.unwrap()
.0
.get("a")
.unwrap()
.eq_bytes(b"x\"y")
);
assert_eq!(
p(b";a=\"x").unwrap_err().kind(),
ErrorKind::UnterminatedQuote
);
assert_eq!(p(b";a=b\rc").unwrap().1, 4);
assert_eq!(p(b";a=\x01").unwrap_err().kind(), ErrorKind::Control);
}
#[test]
fn test_params_stop_at_list_separator_and_trailing_ows() {
let (ps, used) = p(b";a=1 , b").unwrap();
assert_eq!((used, ps.len()), (4, 1));
let (_, used) = p(b";a=1; ,b").unwrap();
assert_eq!(used, 5);
let (_, used) = p(b" ;a=1 x").unwrap();
assert_eq!(used, 5);
}
#[test]
fn test_params_duplicate_refused_case_insensitive() {
let e = p(b";A=b;a=c").unwrap_err();
assert_eq!((e.kind(), e.offset()), (ErrorKind::DuplicateParameter, 5));
assert!(p(b";ab=1;ba=2;aa=3;bb=4").is_ok());
}
#[test]
fn test_params_first_and_last_policies() {
let first = Limits::DEFAULT.duplicate_params(DuplicatePolicy::First);
let last = Limits::DEFAULT.duplicate_params(DuplicatePolicy::Last);
let (a, _) = parse(b";a=1;A=2", &first).unwrap();
let (b, _) = parse(b";a=1;A=2", &last).unwrap();
assert!(a.get("a").unwrap().eq_bytes(b"1"));
assert!(b.get("a").unwrap().eq_bytes(b"2"));
assert_eq!(a.len(), 2);
}
#[test]
fn test_params_count_limit() {
let mut v = Vec::new();
for i in 0..257 {
v.extend_from_slice(format!(";p{i}=v").as_bytes());
}
let cut = v.len() - format!(";p{}=v", 256).len();
for policy in [DuplicatePolicy::First, DuplicatePolicy::Last] {
let l = Limits::DEFAULT.duplicate_params(policy);
assert_eq!(parse(&v[..cut], &l).unwrap().0.len(), 256);
assert_eq!(parse(&v, &l).unwrap_err().kind(), ErrorKind::TooManyMembers);
}
let upto = |n: usize| {
(0..n)
.map(|i| format!(";p{i}=v"))
.collect::<Vec<_>>()
.concat()
};
assert_eq!(p(upto(MAX_CHECKED_PARAMS).as_bytes()).unwrap().0.len(), 64);
let over = upto(MAX_CHECKED_PARAMS + 1);
let e = p(over.as_bytes()).unwrap_err();
let last = over.rfind(';').unwrap() + 1;
assert_eq!((e.kind(), e.offset()), (ErrorKind::TooManyMembers, last));
let wide = Limits::DEFAULT.max_params(u16::MAX);
assert_eq!(
parse(over.as_bytes(), &wide).unwrap_err().kind(),
ErrorKind::TooManyMembers
);
assert_eq!(
parse(upto(3).as_bytes(), &Limits::DEFAULT.max_params(2))
.unwrap_err()
.kind(),
ErrorKind::TooManyMembers
);
}
#[test]
fn test_params_duplicate_found_in_every_table_slot() {
let base = (0..63)
.map(|i| format!(";p{i}=\"v;p{i}=w\""))
.collect::<Vec<_>>()
.concat();
for i in 0..63 {
let dup = format!("{base};P{i}=x");
let e = p(dup.as_bytes()).unwrap_err();
assert_eq!(
(e.kind(), e.offset()),
(ErrorKind::DuplicateParameter, base.len() + 1),
"{i}"
);
}
for other in ["p", "p1x", "xp1", "p100"] {
assert!(p(format!("{base};{other}=x").as_bytes()).is_ok(), "{other}");
}
}
#[test]
fn test_params_debug_prints_names_only() {
let (ps, _) = p(b";user=alice;token=\"s3cr3t\"").unwrap();
let d = format!("{ps:?}");
assert_eq!(d, "Params([\"user\", \"token\"])");
assert!(!d.contains("s3cr3t") && !d.contains("alice"));
}
#[test]
fn test_params_iter_skips_empty_parameters() {
let (ps, _) = p(b";; a=1 ;;b=\"x;y\";").unwrap();
assert_eq!(names(&ps), [&b"a"[..], b"b"]);
assert!(ps.get("b").unwrap().eq_bytes(b"x;y"));
assert_eq!(ps.raw(), b";; a=1 ;;b=\"x;y\";");
}
#[test]
fn test_params_get_matches_whole_names_only() {
let (ps, _) = p(b";;a=\"\"").unwrap();
assert!(ps.get(";a").is_none());
assert!(ps.get("a").is_some_and(|v| v.eq_bytes(b"")));
let (ps, _) = p(b";a=b;c=d").unwrap();
for name in ["a=b", "a=b;c", "", ";", "=", "a;", " a"] {
assert!(ps.get(name).is_none(), "{name}");
}
assert!(ps.get("C").is_some_and(|v| v.eq_bytes(b"d")));
}
fn po(s: &[u8]) -> Result<(OptionalParams<'_>, usize)> {
parse_optional(s, &Limits::DEFAULT)
}
fn values<'a>(ps: &OptionalParams<'a>) -> Vec<(&'a [u8], Option<&'a [u8]>)> {
ps.iter()
.map(|x| (x.name, x.value.map(|v| v.as_plain().unwrap())))
.collect()
}
#[test]
fn test_optional_params_permessage_deflate_offers() {
let (ps, used) = po(b"; client_max_window_bits").unwrap();
assert_eq!(
(used, values(&ps)),
(24, [(&b"client_max_window_bits"[..], None)].to_vec())
);
let (ps, used) =
po(b"; server_no_context_takeover; client_max_window_bits=15 , permessage-deflate")
.unwrap();
assert_eq!(used, 55);
assert_eq!(
values(&ps),
[
(&b"server_no_context_takeover"[..], None),
(&b"client_max_window_bits"[..], Some(&b"15"[..])),
]
);
let (ps, _) = po(b";client_max_window_bits=\"10\"").unwrap();
assert!(
ps.find("client_max_window_bits")
.unwrap()
.value
.unwrap()
.eq_bytes(b"10")
);
}
#[test]
fn test_optional_params_edge_cases() {
assert_eq!(po(b"").unwrap().1, 0);
assert_eq!(po(b";;").unwrap().1, 2);
assert_eq!(values(&po(b";a").unwrap().0), [(&b"a"[..], None)]);
assert_eq!(
values(&po(b";a;b").unwrap().0),
[(&b"a"[..], None), (b"b", None)]
);
assert_eq!(po(b";a ;b").unwrap().1, 5);
assert_eq!(po(b";a , b").unwrap().1, 2);
assert_eq!(po(b";a b").unwrap().1, 2);
assert_eq!(po(b";a\t").unwrap().1, 2);
assert_eq!(po(b";a,").unwrap().1, 2);
assert_eq!(po(b";a=").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(po(b";a=;b").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(po(b";a=,").unwrap_err().kind(), ErrorKind::MissingValue);
let e = po(b";a =1").unwrap_err();
assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidByte, 2));
assert_eq!(po(b";a= 1").unwrap_err().kind(), ErrorKind::InvalidByte);
assert_eq!(po(b";a\t=1").unwrap_err().kind(), ErrorKind::InvalidByte);
assert_eq!(po(b";a\"x\"").unwrap_err().kind(), ErrorKind::MissingValue);
assert_eq!(po(b";a@").unwrap_err().kind(), ErrorKind::NotToken);
assert_eq!(po(b";a\x01").unwrap_err().kind(), ErrorKind::Control);
assert_eq!(po(b";a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
assert_eq!(po(b";@").unwrap_err().kind(), ErrorKind::NotToken);
assert_eq!(
po(b";a=\"x").unwrap_err().kind(),
ErrorKind::UnterminatedQuote
);
}
#[test]
fn test_optional_params_duplicates_and_limits() {
for dup in [&b";a;A"[..], b";a=1;a", b";a;a=1", b";a; b; A=\"x\""] {
let e = po(dup).unwrap_err();
assert_eq!(e.kind(), ErrorKind::DuplicateParameter, "{dup:?}");
}
let last = Limits::DEFAULT.duplicate_params(DuplicatePolicy::Last);
let first = Limits::DEFAULT.duplicate_params(DuplicatePolicy::First);
let (l, _) = parse_optional(b";a;A=2", &last).unwrap();
let (f, _) = parse_optional(b";a;A=2", &first).unwrap();
assert!(l.find("a").unwrap().value.unwrap().eq_bytes(b"2"));
assert!(f.find("a").unwrap().value.is_none());
assert_eq!(f.len(), 2);
let upto = |n: usize| {
(0..n)
.map(|i| format!(";p{i}"))
.collect::<Vec<_>>()
.concat()
};
assert_eq!(po(upto(MAX_CHECKED_PARAMS).as_bytes()).unwrap().0.len(), 64);
let over = upto(MAX_CHECKED_PARAMS + 1);
let e = po(over.as_bytes()).unwrap_err();
assert_eq!(
(e.kind(), e.offset()),
(ErrorKind::TooManyMembers, over.rfind(';').unwrap() + 1)
);
let bare = upto(257);
assert_eq!(
parse_optional(bare.as_bytes(), &last).unwrap_err().kind(),
ErrorKind::TooManyMembers
);
}
#[test]
fn test_optional_params_lookups_and_debug() {
let (ps, _) = po(b";; a ;b=\"x;y\";token=\"s3cr3t\";").unwrap();
assert_eq!(ps.raw(), b";; a ;b=\"x;y\";token=\"s3cr3t\";");
assert_eq!(ps.find("A").map(|x| x.value), Some(None));
assert!(ps.find("b").unwrap().value.unwrap().eq_bytes(b"x;y"));
assert!(ps.contains("TOKEN") && !ps.contains("y") && !ps.contains(""));
let d = format!("{ps:?}");
assert_eq!(d, "OptionalParams([\"a\", \"b\", \"token\"])");
assert!(!d.contains("s3cr3t"));
}
mod reference {
use super::super::*;
use crate::text::{parse_word, skip_ows};
pub(super) fn parse<'a>(s: &'a [u8], limits: &Limits) -> Result<(Params<'a>, usize)> {
let policy = limits.duplicate_params;
let mut count: usize = 0;
let mut used = 0usize;
let mut seen: u64 = 0;
loop {
let ows = skip_ows(s.get(used..).unwrap_or(&[]));
let semi = used + ows;
if s.get(semi) != Some(&b';') {
break;
}
let mut i = semi + 1;
i += skip_ows(s.get(i..).unwrap_or(&[]));
match s.get(i) {
None | Some(b';') => {
used = i;
continue;
}
Some(&b) if !class::is_tchar(b) => {
if b == b',' {
used = semi + 1;
break;
}
return err(
if crate::scan::is_control(b) {
control_kind(b)
} else {
ErrorKind::NotToken
},
i,
);
}
Some(_) => {}
}
let name_start = i;
let name_len = s
.get(i..)
.unwrap_or(&[])
.iter()
.take_while(|&&b| class::is_tchar(b))
.count();
let name = s.get(name_start..name_start + name_len).unwrap_or(&[]);
i += name_len;
match s.get(i) {
Some(b'=') => i += 1,
Some(&b) if is_ows(b) => {
let after = i + skip_ows(s.get(i..).unwrap_or(&[]));
if s.get(after) == Some(&b'=') {
return err(ErrorKind::InvalidByte, i);
}
return err(ErrorKind::MissingValue, i);
}
Some(b'"') => return err(ErrorKind::MissingValue, i),
Some(&b) if b != b';' && b != b',' && class::is_field_text(b) => {
return err(ErrorKind::NotToken, i);
}
Some(&b) if !class::is_field_text(b) => return err(control_kind(b), i),
_ => return err(ErrorKind::MissingValue, i),
}
let value_start = i;
match s.get(i) {
Some(&b) if is_ows(b) => return err(ErrorKind::InvalidByte, i),
None | Some(b';' | b',') => return err(ErrorKind::MissingValue, i),
_ => {}
}
let rest = s.get(i..).unwrap_or(&[]);
let (_, n) = parse_word(rest).map_err(|e| match e.kind() {
ErrorKind::Empty => crate::error::ParseError::new(
control_or_invalid(rest.first().copied().unwrap_or(0)),
value_start,
),
_ => e.shifted(value_start),
})?;
i += n;
count += 1;
if count > usize::from(limits.params) {
return err(ErrorKind::TooManyMembers, name_start);
}
if policy == DuplicatePolicy::Refuse {
let bit = name_bit(name);
if seen & bit != 0 {
let earlier = Params {
text: s.get(..used).unwrap_or(&[]),
count: 0,
policy,
};
if earlier.iter().any(|p| p.name.eq_ignore_ascii_case(name)) {
return err(ErrorKind::DuplicateParameter, name_start);
}
}
seen |= bit;
}
used = i;
}
Ok((
Params {
text: s.get(..used).unwrap_or(&[]),
count: u32::try_from(count).unwrap_or(u32::MAX),
policy,
},
used,
))
}
pub(super) fn parse_optional(
s: &[u8],
limits: &Limits,
) -> Result<(std::vec::Vec<u8>, usize, usize)> {
let refuse = limits.duplicate_params == DuplicatePolicy::Refuse;
let max = if refuse {
usize::from(limits.params).min(MAX_CHECKED_PARAMS)
} else {
usize::from(limits.params)
};
let mut seen: std::vec::Vec<&[u8]> = std::vec::Vec::new();
let mut used = 0usize;
loop {
let semi = used + skip_ows(s.get(used..).unwrap_or(&[]));
if s.get(semi) != Some(&b';') {
break;
}
let mut i = semi + 1;
i += skip_ows(s.get(i..).unwrap_or(&[]));
let Ok((name, n)) = crate::text::parse_token(s.get(i..).unwrap_or(&[])) else {
match s.get(i) {
None | Some(b';') => used = i,
Some(b',') => {
used = semi + 1;
break;
}
Some(&b) if crate::scan::is_control(b) => return err(control_kind(b), i),
Some(_) => return err(ErrorKind::NotToken, i),
}
continue;
};
let name_start = i;
i += n;
match s.get(i) {
Some(b'=') => {
i += 1;
match s.get(i) {
Some(&b) if is_ows(b) => return err(ErrorKind::InvalidByte, i),
None | Some(b';' | b',') => return err(ErrorKind::MissingValue, i),
_ => {}
}
let rest = s.get(i..).unwrap_or(&[]);
let (_, n) = parse_word(rest).map_err(|e| match e.kind() {
ErrorKind::Empty => crate::error::ParseError::new(
control_or_invalid(rest.first().copied().unwrap_or(0)),
i,
),
_ => e.shifted(i),
})?;
i += n;
}
None | Some(b';' | b',') => {}
Some(&b) if is_ows(b) => {
let after = i + skip_ows(s.get(i..).unwrap_or(&[]));
if s.get(after) == Some(&b'=') {
return err(ErrorKind::InvalidByte, i);
}
}
Some(b'"') => return err(ErrorKind::MissingValue, i),
Some(&b) if class::is_field_text(b) => return err(ErrorKind::NotToken, i),
Some(&b) => return err(control_kind(b), i),
}
if seen.len() + 1 > max {
return err(ErrorKind::TooManyMembers, name_start);
}
if refuse && seen.iter().any(|e| e.eq_ignore_ascii_case(name)) {
return err(ErrorKind::DuplicateParameter, name_start);
}
seen.push(name);
used = i;
}
Ok((s.get(..used).unwrap_or(&[]).to_vec(), seen.len(), used))
}
}
fn reference_optional_iter(raw: &[u8]) -> Vec<OptionalParam<'_>> {
let mut out = Vec::new();
let mut i = 0;
loop {
i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
if raw.get(i) != Some(&b';') {
return out;
}
i += 1;
i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
let Ok((name, n)) = crate::text::parse_token(raw.get(i..).unwrap_or(&[])) else {
continue;
};
i += n;
let value = if raw.get(i) == Some(&b'=') {
let (value, n) = crate::text::parse_word(raw.get(i + 1..).unwrap_or(&[])).unwrap();
i += 1 + n;
Some(value)
} else {
None
};
out.push(OptionalParam { name, value });
}
}
fn param_text() -> impl Strategy<Value = Vec<u8>> {
let piece = prop_oneof![
Just(b";".to_vec()),
Just(b" ".to_vec()),
Just(b"\t".to_vec()),
Just(b"=".to_vec()),
Just(b",".to_vec()),
Just(b"\"a\\\"b;c=d\"".to_vec()),
Just(b"\"\\".to_vec()),
Just(b"\r".to_vec()),
Just(b"\x7f".to_vec()),
"[a-cA-C]{1,3}".prop_map(String::into_bytes),
"[0-9]{1,3}".prop_map(String::into_bytes),
"[(){}@/]".prop_map(String::into_bytes),
"; ?[a-cA-C]{1,2}=[0-9a-c]{1,2}".prop_map(String::into_bytes),
"; ?[a-c]=\"[a-c;,= ]{0,3}\"".prop_map(String::into_bytes),
];
prop::collection::vec(piece, 0..16).prop_map(|v| v.concat())
}
fn outcome(
r: Result<(Params<'_>, usize)>,
) -> core::result::Result<(Vec<u8>, usize, usize), (ErrorKind, usize)> {
r.map(|(p, used)| (p.raw().to_vec(), p.len(), used))
.map_err(|e| (e.kind(), e.offset()))
}
fn reference_iter(raw: &[u8]) -> Vec<Param<'_>> {
let mut out = Vec::new();
let mut i = 0;
loop {
i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
if raw.get(i) != Some(&b';') {
return out;
}
i += 1;
i += crate::text::skip_ows(raw.get(i..).unwrap_or(&[]));
let Ok((name, n)) = crate::text::parse_token(raw.get(i..).unwrap_or(&[])) else {
continue;
};
i += n + 1;
let (value, n) = crate::text::parse_word(raw.get(i..).unwrap_or(&[])).unwrap();
i += n;
out.push(Param { name, value });
}
}
proptest::proptest! {
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(4096))]
#[test]
fn test_parse_agrees_with_reference(v in param_text()) {
for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
let limits = Limits::DEFAULT.duplicate_params(policy);
prop_assert_eq!(outcome(parse(&v, &limits)), outcome(reference::parse(&v, &limits)));
}
}
#[test]
fn test_iter_and_get_agree_with_checked_reads(v in param_text(), name in "[a-cA-C=; \"]{0,4}") {
for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
let limits = Limits::DEFAULT.duplicate_params(policy);
let Ok((p, _)) = parse(&v, &limits) else { continue };
let all: Vec<Param<'_>> = p.iter().collect();
prop_assert_eq!(&all, &reference_iter(p.raw()));
prop_assert_eq!(all.len(), p.len());
let mut matching = all.iter().filter(|x| x.name.eq_ignore_ascii_case(name.as_bytes()));
let expected = if policy == DuplicatePolicy::Last { matching.next_back() } else { matching.next() };
prop_assert_eq!(p.get(&name), expected.map(|x| x.value));
for x in &all {
let n = core::str::from_utf8(x.name).unwrap();
let mut same = all.iter().filter(|y| y.name.eq_ignore_ascii_case(x.name));
let expected = if policy == DuplicatePolicy::Last { same.next_back() } else { same.next() };
prop_assert_eq!(p.get(n), expected.map(|y| y.value));
}
}
}
#[test]
fn test_parse_optional_agrees_with_reference(v in param_text()) {
for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
let limits = Limits::DEFAULT.duplicate_params(policy);
let got = parse_optional(&v, &limits)
.map(|(p, used)| (p.raw().to_vec(), p.len(), used))
.map_err(|e| (e.kind(), e.offset()));
let want = reference::parse_optional(&v, &limits).map_err(|e| (e.kind(), e.offset()));
prop_assert_eq!(got, want);
}
}
#[test]
fn test_parse_optional_extends_parse(v in param_text()) {
for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
let limits = Limits::DEFAULT.duplicate_params(policy);
let opt = parse_optional(&v, &limits);
match parse(&v, &limits) {
Ok((p, used)) => {
let (o, o_used) = opt.unwrap();
prop_assert_eq!((o.raw(), o.len(), o_used), (p.raw(), p.len(), used));
let with: Vec<OptionalParam<'_>> = p.iter()
.map(|x| OptionalParam { name: x.name, value: Some(x.value) })
.collect();
prop_assert_eq!(o.iter().collect::<Vec<_>>(), with);
}
Err(e) if e.kind() != ErrorKind::MissingValue => {
let o = opt.unwrap_err();
prop_assert_eq!((o.kind(), o.offset()), (e.kind(), e.offset()));
}
Err(_) => {}
}
}
}
#[test]
fn test_optional_iter_and_find_agree_with_checked_reads(v in param_text(), name in "[a-cA-C=; \"]{0,4}") {
for policy in [DuplicatePolicy::Refuse, DuplicatePolicy::First, DuplicatePolicy::Last] {
let limits = Limits::DEFAULT.duplicate_params(policy);
let Ok((p, _)) = parse_optional(&v, &limits) else { continue };
let all: Vec<OptionalParam<'_>> = p.iter().collect();
prop_assert_eq!(&all, &reference_optional_iter(p.raw()));
prop_assert_eq!(all.len(), p.len());
let mut matching = all.iter().filter(|x| x.name.eq_ignore_ascii_case(name.as_bytes()));
let expected = if policy == DuplicatePolicy::Last { matching.next_back() } else { matching.next() };
prop_assert_eq!(p.find(&name), expected.copied());
prop_assert_eq!(p.contains(&name), expected.is_some());
}
}
}
}