cookie_rs/cookie/
parse.rs1use std::borrow::Cow;
23use std::time::Duration;
24
25use super::Cookie;
26use super::SameSite;
27use crate::StringPrison;
28
29pub use self::error::*;
30
31pub mod error;
32
33impl<'a> Cookie<'a> {
34 pub fn parse<V: Into<Cow<'a, str>>>(value: V) -> Result<Self, ParseError> {
54 Self::inner_parse(value.into(), false)
55 }
56
57 pub fn parse_strict<V: Into<Cow<'a, str>>>(value: V) -> Result<Self, ParseError> {
75 Self::inner_parse(value.into(), true)
76 }
77
78 pub(crate) fn inner_parse(value: Cow<'a, str>, strict: bool) -> Result<Self, ParseError> {
79 let prison = StringPrison::new(value);
80
81 let str = unsafe { prison.get() };
83
84 let mut cookie = parse_cookie(str, strict)?;
85 cookie.prison = Some(prison);
86
87 Ok(cookie)
88 }
89}
90
91fn parse_cookie(str: &str, strict: bool) -> Result<Cookie<'_>, ParseError> {
92 let mut attributes = str.split(';');
93
94 let (name, value) = attributes
95 .next()
96 .expect("Missing any attributes")
97 .split_once('=')
98 .ok_or(MissingPair::NameValue)?;
99
100 let (name, value) = (name.trim(), value.trim());
101
102 if name.is_empty() {
103 return Err(ParseError::EmptyName);
104 }
105
106 #[cfg(not(feature = "percent-encoding"))]
107 let mut cookie = Cookie::new(name, value);
108 #[cfg(feature = "percent-encoding")]
109 let mut cookie = Cookie::new(
110 name,
111 percent_encoding::percent_decode_str(value)
112 .decode_utf8()
113 .map_err(|_| ParseError::ParseDecodeError)?,
114 );
115
116 for attribute in attributes {
117 let mut pair = attribute.splitn(2, '=');
118
119 let (name, value) = (
120 pair.next().expect("missing any attribute name").trim(),
121 pair.next().map(|v| v.trim()),
122 );
123
124 match value {
125 domain if name.eq_ignore_ascii_case("Domain") => {
126 cookie.set_domain(domain.ok_or(MissingPair::Domain)?)
127 }
128 expires if name.eq_ignore_ascii_case("Expires") => {
129 cookie.set_expires(expires.ok_or(MissingPair::Expires)?)
130 }
131 _ if name.eq_ignore_ascii_case("HttpOnly") => cookie.set_http_only(true),
132 max_age if name.eq_ignore_ascii_case("Max-Age") => {
133 let secs: i64 = max_age.ok_or(MissingPair::MaxAge)?.parse()?;
134 cookie.set_max_age(Duration::from_secs(secs.max(0) as u64))
135 }
136 _ if name.eq_ignore_ascii_case("Partitioned") => cookie.set_partitioned(true),
137 path if name.eq_ignore_ascii_case("Path") => {
138 cookie.set_path(path.ok_or(MissingPair::Path)?)
139 }
140 _ if name.eq_ignore_ascii_case("Secure") => cookie.set_secure(true),
141 same_site if name.eq_ignore_ascii_case("SameSite") => {
142 cookie.set_same_site(same_site.ok_or(MissingPair::SameSite)?.parse()?)
143 }
144 _ if strict => return Err(ParseError::UnknownAttribute(name.to_owned())),
145 _ => continue,
146 }
147 }
148
149 Ok(cookie)
150}
151
152impl std::str::FromStr for SameSite {
153 type Err = ParseSameSiteError;
154
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 if s.eq_ignore_ascii_case("strict") {
157 Ok(Self::Strict)
158 } else if s.eq_ignore_ascii_case("lax") {
159 Ok(Self::Lax)
160 } else if s.eq_ignore_ascii_case("none") {
161 Ok(Self::None)
162 } else {
163 Err(ParseSameSiteError::UnknownValue(s.to_owned()))
164 }
165 }
166}