use std::borrow::Cow;
use crate::{Cookie, error::Error, util::TinyStr};
impl Cookie {
pub fn parse_cookie(string: impl Into<Box<str>>) -> crate::Result<Cookie> {
Self::parse_inner(string.into(), |name, value| {
Ok((Cow::Borrowed(name), Cow::Borrowed(value)))
})
}
#[cfg(feature = "percent-encode")]
pub fn parse_cookie_encoded(string: impl Into<Box<str>>) -> crate::Result<Cookie> {
use crate::cookie::encoding;
Self::parse_inner(string.into(), encoding::decode_name_value)
}
fn parse_inner(
mut string: Box<str>,
callback: impl for<'a> Fn(&'a str, &'a str) -> crate::Result<(Cow<'a, str>, Cow<'a, str>)>,
) -> Result<Cookie, Error> {
let mut parts = SplitMut::new(&mut string);
let name_value = parts.next().expect("First split always returns something");
let Some(index) = name_value.find('=') else {
return Err(Error::EqualsNotFound);
};
let name = name_value[..index].trim();
let mut value = name_value[(index + 1)..].trim();
if name.is_empty() {
return Err(Error::NameEmpty);
} else if let Some(token) = invalid_token(name) {
return Err(Error::InvalidName(token));
}
value = trim_quotes(value);
if let Some(invalid_char) = find_invalid_cookie_value(value) {
return Err(Error::InvalidValue(invalid_char));
}
let (name, value) = callback(name, value)?;
let (prefix, name) = crate::cookie::prefix::split_prefix(name);
let name = TinyStr::from_cow_ref(name, parts.ptr);
let value = TinyStr::from_cow_ref(value, parts.ptr);
let mut cookie = Cookie::new_inner(name, value);
cookie.prefix = prefix;
cookie.raw_value = Some(string);
Ok(cookie)
}
}
struct SplitMut<'s> {
haystack: Option<&'s mut str>,
ptr: *const u8,
}
impl<'s> SplitMut<'s> {
pub fn new(haystack: &'s mut str) -> SplitMut<'s> {
SplitMut {
ptr: haystack.as_ptr(),
haystack: Some(haystack),
}
}
}
impl<'s> Iterator for SplitMut<'s> {
type Item = &'s mut str;
fn next(&mut self) -> Option<Self::Item> {
let remainder = self.haystack.take()?;
match remainder.find(';') {
Some(index) => {
let (current, rest) = remainder.split_at_mut(index);
if !rest.is_empty() {
self.haystack = Some(&mut rest[1..]);
} else {
self.haystack = Some(&mut rest[..]);
}
Some(current)
}
None => Some(remainder),
}
}
}
#[inline]
fn invalid_cookie_value_char(val: &char) -> bool {
match val {
' ' | '"' | ',' | ';' | '\\' => true,
control if control.is_ascii_control() => true,
_ => false,
}
}
pub fn trim_quotes(value: &str) -> &str {
if value.len() > 1 && value.starts_with('"') && value.ends_with('"') {
&value[1..(value.len() - 1)]
} else {
value
}
}
#[inline]
pub fn invalid_token(val: &str) -> Option<char> {
val.chars().find(|c| match c {
'!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|'
| '~' => false,
c if c.is_alphanumeric() => false,
_ => true,
})
}
#[inline]
pub fn find_invalid_cookie_value(val: &str) -> Option<char> {
val.chars().find(invalid_cookie_value_char)
}