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 name = TinyStr::from_cow_ref(name, parts.ptr);
70 let value = TinyStr::from_cow_ref(value, parts.ptr);
71
72 let mut cookie = Cookie::new_inner(name, value);
73 cookie.raw_value = Some(string);
74 Ok(cookie)
75 }
76}
77
78struct SplitMut<'s> {
79 haystack: Option<&'s mut str>,
80 ptr: *const u8,
81}
82
83impl<'s> SplitMut<'s> {
84 pub fn new(haystack: &'s mut str) -> SplitMut<'s> {
85 SplitMut {
86 ptr: haystack.as_ptr(),
87 haystack: Some(haystack),
88 }
89 }
90}
91
92impl<'s> Iterator for SplitMut<'s> {
93 type Item = &'s mut str;
94
95 fn next(&mut self) -> Option<Self::Item> {
96 let remainder = self.haystack.take()?;
97
98 match remainder.find(';') {
99 Some(index) => {
100 let (current, rest) = remainder.split_at_mut(index);
101
102 if !rest.is_empty() {
103 self.haystack = Some(&mut rest[1..]);
104 } else {
105 self.haystack = Some(&mut rest[..]);
106 }
107
108 Some(current)
109 }
110 None => Some(remainder),
111 }
112 }
113}
114
115#[inline]
116fn invalid_cookie_value_char(val: &char) -> bool {
117 match val {
118 ' ' | '"' | ',' | ';' | '\\' => true,
119 control if control.is_ascii_control() => true,
120 _ => false,
121 }
122}
123
124pub fn trim_quotes(value: &str) -> &str {
125 if value.len() > 1 && value.starts_with('"') && value.ends_with('"') {
126 &value[1..(value.len() - 1)]
127 } else {
128 value
129 }
130}
131
132#[inline]
133pub fn invalid_token(val: &str) -> Option<char> {
134 val.chars().find(|c| match c {
135 '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|'
136 | '~' => false,
137 c if c.is_alphanumeric() => false,
138 _ => true,
139 })
140}
141
142#[inline]
143pub fn find_invalid_cookie_value(val: &str) -> Option<char> {
144 val.chars().find(invalid_cookie_value_char)
145}