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<String>) -> Result<Cookie, Error> {
7 Self::parse_inner(string.into(), |name, value| {
8 Ok((Cow::Borrowed(name), Cow::Borrowed(value)))
9 })
10 }
11
12 #[cfg(feature = "percent-encode")]
13 pub fn parse_cookie_encoded(string: impl Into<String>) -> Result<Cookie, Error> {
14 use crate::cookie::encoding;
15
16 Self::parse_inner(string.into(), encoding::decode_name_value)
17 }
18
19 fn parse_inner(
20 mut string: String,
21 callback: impl for<'a> Fn(&'a str, &'a str) -> crate::Result<(Cow<'a, str>, Cow<'a, str>)>,
22 ) -> Result<Cookie, Error> {
23 let mut parts = SplitMut::new(&mut string);
24
25 let name_value = parts.next().expect("First split always returns something");
26
27 let Some(index) = name_value.find('=') else {
30 return Err(Error::EqualsNotFound);
31 };
32
33 let name = name_value[..index].trim();
36 let mut value = name_value[(index + 1)..].trim();
37
38 if name.is_empty() {
40 return Err(Error::NameEmpty);
41 } else if !is_token(name) {
42 return Err(Error::InvalidName);
43 }
44
45 value = trim_quotes(value);
47
48 if let Some(invalid_char) = find_invalid_cookie_value(value) {
49 return Err(Error::InvalidValue(invalid_char));
50 }
51
52 let (name, value) = callback(name, value)?;
53
54 let name = TinyStr::from_cow_ref(name, parts.ptr);
55 let value = TinyStr::from_cow_ref(value, parts.ptr);
56
57 let mut cookie = Cookie::new_inner(name, value);
58 cookie.raw_value = Some(string);
59 Ok(cookie)
60 }
61}
62
63struct SplitMut<'s> {
64 haystack: Option<&'s mut str>,
65 ptr: *const u8,
66}
67
68impl<'s> SplitMut<'s> {
69 pub fn new(haystack: &'s mut str) -> SplitMut<'s> {
70 SplitMut {
71 ptr: haystack.as_ptr(),
72 haystack: Some(haystack),
73 }
74 }
75}
76
77impl<'s> Iterator for SplitMut<'s> {
78 type Item = &'s mut str;
79
80 fn next(&mut self) -> Option<Self::Item> {
81 let remainder = self.haystack.take()?;
82
83 match remainder.find(';') {
84 Some(index) => {
85 let (current, rest) = remainder.split_at_mut(index);
86
87 if !rest.is_empty() {
88 self.haystack = Some(&mut rest[1..]);
89 } else {
90 self.haystack = Some(&mut rest[..]);
91 }
92
93 Some(current)
94 }
95 None => Some(remainder),
96 }
97 }
98}
99
100#[inline]
101fn invalid_cookie_value_char(val: &char) -> bool {
102 match val {
103 ' ' | '"' | ',' | ';' | '\\' => true,
104 control if control.is_ascii_control() => true,
105 _ => false,
106 }
107}
108
109pub fn trim_quotes(value: &str) -> &str {
110 if value.len() > 1 && value.starts_with('"') && value.ends_with('"') {
111 &value[1..(value.len() - 1)]
112 } else {
113 value
114 }
115}
116
117#[inline]
118pub fn is_token(val: &str) -> bool {
119 val.chars().all(|c| match c {
120 '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|'
121 | '~' => true,
122 c if c.is_alphanumeric() => true,
123 _ => false,
124 })
125}
126
127#[inline]
128pub fn find_invalid_cookie_value(val: &str) -> Option<char> {
129 val.chars().find(invalid_cookie_value_char)
130}