cookie_monster/cookie/
parse.rs1use std::borrow::Cow;
2
3use crate::{Cookie, error::Error, util::TinyStr};
4
5impl Cookie {
6 pub fn parse_cookie(string: impl Into<Box<str>>) -> crate::Result<Cookie> {
14 Self::parse_inner(string.into(), |name, value| {
15 Ok((Cow::Borrowed(name), Cow::Borrowed(value)))
16 })
17 }
18
19 #[cfg(feature = "percent-encode")]
27 pub fn parse_cookie_encoded(string: impl Into<Box<str>>) -> crate::Result<Cookie> {
28 use crate::cookie::encoding;
29
30 Self::parse_inner(string.into(), encoding::decode_name_value)
31 }
32
33 fn parse_inner(
34 mut string: Box<str>,
35 callback: impl for<'a> Fn(&'a str, &'a str) -> crate::Result<(Cow<'a, str>, Cow<'a, str>)>,
36 ) -> Result<Cookie, Error> {
37 let mut parts = SplitMut::new(&mut string);
38
39 let name_value = parts.next().expect("First split always returns something");
40
41 let Some(index) = name_value.find('=') else {
44 return Err(Error::EqualsNotFound);
45 };
46
47 let name = name_value[..index].trim();
50 let mut value = name_value[(index + 1)..].trim();
51
52 if name.is_empty() {
54 return Err(Error::NameEmpty);
55 } else if let Some(token) = invalid_token(name) {
56 return Err(Error::InvalidName(token));
57 }
58
59 value = trim_quotes(value);
61
62 if let Some(invalid_char) = find_invalid_cookie_value(value) {
63 return Err(Error::InvalidValue(invalid_char));
64 }
65
66 let (name, value) = callback(name, value)?;
68
69 let (prefix, name) = crate::cookie::prefix::split_prefix(name);
71
72 let name = TinyStr::from_cow_ref(name, parts.ptr);
73 let value = TinyStr::from_cow_ref(value, parts.ptr);
74
75 let mut cookie = Cookie::new_inner(name, value);
76 cookie.prefix = prefix;
77 cookie.raw_value = Some(string);
78 Ok(cookie)
79 }
80}
81
82struct SplitMut<'s> {
83 haystack: Option<&'s mut str>,
84 ptr: *const u8,
85}
86
87impl<'s> SplitMut<'s> {
88 pub fn new(haystack: &'s mut str) -> SplitMut<'s> {
89 SplitMut {
90 ptr: haystack.as_ptr(),
91 haystack: Some(haystack),
92 }
93 }
94}
95
96impl<'s> Iterator for SplitMut<'s> {
97 type Item = &'s mut str;
98
99 fn next(&mut self) -> Option<Self::Item> {
100 let remainder = self.haystack.take()?;
101
102 match remainder.find(';') {
103 Some(index) => {
104 let (current, rest) = remainder.split_at_mut(index);
105
106 if !rest.is_empty() {
107 self.haystack = Some(&mut rest[1..]);
108 } else {
109 self.haystack = Some(&mut rest[..]);
110 }
111
112 Some(current)
113 }
114 None => Some(remainder),
115 }
116 }
117}
118
119#[inline]
120fn invalid_cookie_value_char(val: &char) -> bool {
121 match val {
122 ' ' | '"' | ',' | ';' | '\\' => true,
123 control if control.is_ascii_control() => true,
124 _ => false,
125 }
126}
127
128pub fn trim_quotes(value: &str) -> &str {
129 if value.len() > 1 && value.starts_with('"') && value.ends_with('"') {
130 &value[1..(value.len() - 1)]
131 } else {
132 value
133 }
134}
135
136#[inline]
137pub fn invalid_token(val: &str) -> Option<char> {
138 val.chars().find(|c| match c {
139 '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|'
140 | '~' => false,
141 c if c.is_alphanumeric() => false,
142 _ => true,
143 })
144}
145
146#[inline]
147pub fn find_invalid_cookie_value(val: &str) -> Option<char> {
148 val.chars().find(invalid_cookie_value_char)
149}