use rfc_6265::grammar::{is_cookie_name_bytes, is_ws};
use crate::report::PairIssue;
#[inline]
pub(crate) fn trim_ws(mut bytes: &[u8]) -> &[u8] {
while let [first, rest @ ..] = bytes
&& is_ws(*first)
{
bytes = rest;
}
while let [rest @ .., last] = bytes
&& is_ws(*last)
{
bytes = rest;
}
bytes
}
pub(crate) fn split_checked_pair(segment: &[u8]) -> Result<(&str, &[u8]), PairIssue<'_>> {
let Some(eq) = segment.iter().position(|&b| b == b'=') else {
return Err(PairIssue::MissingEquals { segment });
};
let (raw_name, raw_value) = (&segment[..eq], &segment[eq + 1..]);
let name = trim_ws(raw_name);
if !is_cookie_name_bytes(name) {
#[cfg(feature = "tracing")]
tracing::debug!(
name = %String::from_utf8_lossy(name).escape_debug(),
"ignoring a cookie pair with an empty or non-token name"
);
return Err(PairIssue::InvalidName { name });
}
match std::str::from_utf8(name) {
Ok(name) => Ok((name, raw_value)),
Err(_) => Err(PairIssue::InvalidName { name }),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammar::is_ws_char;
#[test]
fn trim_ws_matches_the_str_form_and_only_touches_sp_htab() {
for s in ["", " ", "\t \t", " a ", "a", "\ta\t", " a b ", " = "] {
assert_eq!(
trim_ws(s.as_bytes()),
s.trim_matches(is_ws_char).as_bytes(),
"{s:?}"
);
}
for s in ["\ra\r", "\na\n", "\x0ca\x0c", "\x0ba\x0b"] {
assert_eq!(trim_ws(s.as_bytes()), s.as_bytes(), "{s:?}");
}
}
#[test]
fn split_checked_pair_takes_the_first_equals_and_gates_the_name() {
assert_eq!(split_checked_pair(b" n =v=w"), Ok(("n", &b"v=w"[..])));
assert_eq!(split_checked_pair(b"n= v "), Ok(("n", &b" v "[..])));
for bad in &[&b"novalue"[..], b""] {
assert_eq!(
split_checked_pair(bad),
Err(PairIssue::MissingEquals { segment: bad }),
"{bad:?}"
);
}
for (bad, name) in [
(&b"=v"[..], &b""[..]),
(b" \t=v", b""),
(b"a b=v", b"a b"),
(b"a;b=v", b"a;b"),
(b"caf\xc3\xa9=v", b"caf\xc3\xa9"),
(b"a\xffb=v", b"a\xffb"),
] {
assert_eq!(
split_checked_pair(bad),
Err(PairIssue::InvalidName { name }),
"{bad:?}"
);
}
}
}