1use std::borrow::Cow;
2use std::cmp;
3use std::convert::From;
4use std::error::Error;
5use std::fmt;
6use std::str::Utf8Error;
7
8use chrono::Duration;
9use percent_encoding::percent_decode;
10
11use super::{Cookie, CookieStr, SameSite};
12
13#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub enum ParseError {
16 MissingPair,
18 EmptyName,
20 Utf8Error(Utf8Error),
22 #[doc(hidden)]
25 __Nonexhasutive,
26}
27
28impl ParseError {
29 pub fn as_str(&self) -> &'static str {
31 match *self {
32 ParseError::MissingPair => "the cookie is missing a name/value pair",
33 ParseError::EmptyName => "the cookie's name is empty",
34 ParseError::Utf8Error(_) => {
35 "decoding the cookie's name or value resulted in invalid UTF-8"
36 }
37 ParseError::__Nonexhasutive => unreachable!("__Nonexhasutive ParseError"),
38 }
39 }
40}
41
42impl fmt::Display for ParseError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}", self.as_str())
45 }
46}
47
48impl From<Utf8Error> for ParseError {
49 fn from(error: Utf8Error) -> ParseError {
50 ParseError::Utf8Error(error)
51 }
52}
53
54impl Error for ParseError {}
55
56fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
57 let haystack_start = haystack.as_ptr() as usize;
58 let needle_start = needle.as_ptr() as usize;
59
60 if needle_start < haystack_start {
61 return None;
62 }
63
64 if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
65 return None;
66 }
67
68 let start = needle_start - haystack_start;
69 let end = start + needle.len();
70 Some((start, end))
71}
72
73fn name_val_decoded(
74 name: &str,
75 val: &str,
76) -> Result<(CookieStr, CookieStr), ParseError> {
77 let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
78 let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
79 let name = CookieStr::Concrete(Cow::Owned(decoded_name.into_owned()));
80 let val = CookieStr::Concrete(Cow::Owned(decoded_value.into_owned()));
81
82 Ok((name, val))
83}
84
85fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
90 let mut attributes = s.split(';');
91 let key_value = match attributes.next() {
92 Some(s) => s,
93 _ => panic!(),
94 };
95
96 let (name, value) = match key_value.find('=') {
98 Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()),
99 None => return Err(ParseError::MissingPair),
100 };
101
102 if name.is_empty() {
103 return Err(ParseError::EmptyName);
104 }
105
106 let (name, value) = if decode {
109 name_val_decoded(name, value)?
110 } else {
111 let name_indexes = indexes_of(name, s).expect("name sub");
112 let value_indexes = indexes_of(value, s).expect("value sub");
113 let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
114 let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
115
116 (name, value)
117 };
118
119 let mut cookie = Cookie {
120 name,
121 value,
122 cookie_string: None,
123 expires: None,
124 max_age: None,
125 domain: None,
126 path: None,
127 secure: None,
128 http_only: None,
129 same_site: None,
130 };
131
132 for attr in attributes {
133 let (key, value) = match attr.find('=') {
134 Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
135 None => (attr.trim(), None),
136 };
137
138 match (&*key.to_ascii_lowercase(), value) {
139 ("secure", _) => cookie.secure = Some(true),
140 ("httponly", _) => cookie.http_only = Some(true),
141 ("max-age", Some(v)) => {
142 cookie.max_age = match v.parse() {
146 Ok(val) if val <= 0 => Some(Duration::zero()),
147 Ok(val) => {
148 let val = cmp::min(val, Duration::max_value().num_seconds());
151 Some(Duration::seconds(val))
152 }
153 Err(_) => continue,
154 };
155 }
156 ("domain", Some(mut domain)) if !domain.is_empty() => {
157 if domain.starts_with('.') {
158 domain = &domain[1..];
159 }
160
161 let (i, j) = indexes_of(domain, s).expect("domain sub");
162 cookie.domain = Some(CookieStr::Indexed(i, j));
163 }
164 ("path", Some(v)) => {
165 let (i, j) = indexes_of(v, s).expect("path sub");
166 cookie.path = Some(CookieStr::Indexed(i, j));
167 }
168 ("samesite", Some(v)) => {
169 if v.eq_ignore_ascii_case("strict") {
170 cookie.same_site = Some(SameSite::Strict);
171 } else if v.eq_ignore_ascii_case("lax") {
172 cookie.same_site = Some(SameSite::Lax);
173 } else {
174 }
180 }
181 ("expires", Some(v)) => {
182 let tm = time::strptime(v, "%a, %d %b %Y %H:%M:%S %Z")
186 .or_else(|_| time::strptime(v, "%A, %d-%b-%y %H:%M:%S %Z"))
187 .or_else(|_| time::strptime(v, "%a, %d-%b-%Y %H:%M:%S %Z"))
188 .or_else(|_| time::strptime(v, "%a %b %d %H:%M:%S %Y"));
189
190 if let Ok(time) = tm {
191 cookie.expires = Some(time)
192 }
193 }
194 _ => {
195 }
200 }
201 }
202
203 Ok(cookie)
204}
205
206pub fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
207where
208 S: Into<Cow<'c, str>>,
209{
210 let s = cow.into();
211 let mut cookie = parse_inner(&s, decode)?;
212 cookie.cookie_string = Some(s);
213 Ok(cookie)
214}
215
216#[cfg(test)]
217mod tests {
218 use super::{Cookie, SameSite};
219 use chrono::Duration;
220 use time::strptime;
221
222 macro_rules! assert_eq_parse {
223 ($string:expr, $expected:expr) => {
224 let cookie = match Cookie::parse($string) {
225 Ok(cookie) => cookie,
226 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
227 };
228
229 assert_eq!(cookie, $expected);
230 };
231 }
232
233 macro_rules! assert_ne_parse {
234 ($string:expr, $expected:expr) => {
235 let cookie = match Cookie::parse($string) {
236 Ok(cookie) => cookie,
237 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
238 };
239
240 assert_ne!(cookie, $expected);
241 };
242 }
243
244 #[test]
245 fn parse_same_site() {
246 let expected = Cookie::build("foo", "bar")
247 .same_site(SameSite::Lax)
248 .finish();
249
250 assert_eq_parse!("foo=bar; SameSite=Lax", expected);
251 assert_eq_parse!("foo=bar; SameSite=lax", expected);
252 assert_eq_parse!("foo=bar; SameSite=LAX", expected);
253 assert_eq_parse!("foo=bar; samesite=Lax", expected);
254 assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
255
256 let expected = Cookie::build("foo", "bar")
257 .same_site(SameSite::Strict)
258 .finish();
259
260 assert_eq_parse!("foo=bar; SameSite=Strict", expected);
261 assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
262 assert_eq_parse!("foo=bar; SameSite=strict", expected);
263 assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
264 assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
265 }
266
267 #[test]
268 fn parse() {
269 assert!(Cookie::parse("bar").is_err());
270 assert!(Cookie::parse("=bar").is_err());
271 assert!(Cookie::parse(" =bar").is_err());
272 assert!(Cookie::parse("foo=").is_ok());
273
274 let expected = Cookie::build("foo", "bar=baz").finish();
275 assert_eq_parse!("foo=bar=baz", expected);
276
277 let mut expected = Cookie::build("foo", "bar").finish();
278 assert_eq_parse!("foo=bar", expected);
279 assert_eq_parse!("foo = bar", expected);
280 assert_eq_parse!(" foo=bar ", expected);
281 assert_eq_parse!(" foo=bar ;Domain=", expected);
282 assert_eq_parse!(" foo=bar ;Domain= ", expected);
283 assert_eq_parse!(" foo=bar ;Ignored", expected);
284
285 let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
286 assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
287 assert_ne_parse!(" foo=bar; httponly", unexpected);
288
289 expected.set_http_only(true);
290 assert_eq_parse!(" foo=bar ;HttpOnly", expected);
291 assert_eq_parse!(" foo=bar ;httponly", expected);
292 assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
293 assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
294
295 expected.set_secure(true);
296 assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
297 assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
298
299 unexpected.set_http_only(true);
300 unexpected.set_secure(true);
301 assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
302 assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
303 assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
304
305 unexpected.set_secure(false);
306 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
307 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
308 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
309
310 expected.set_max_age(Duration::zero());
311 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
312 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
313 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
314 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
315
316 expected.set_max_age(Duration::minutes(1));
317 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
318 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
319
320 expected.set_max_age(Duration::seconds(4));
321 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
322 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
323
324 unexpected.set_secure(true);
325 unexpected.set_max_age(Duration::minutes(1));
326 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
327 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
328 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
329 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
330 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
331
332 expected.set_path("/");
333 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
334 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
335
336 expected.set_path("/foo");
337 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
338 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
339 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
340 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
341
342 unexpected.set_max_age(Duration::seconds(4));
343 unexpected.set_path("/bar");
344 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
345 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
346
347 expected.set_domain("www.foo.com");
348 assert_eq_parse!(
349 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
350 Domain=www.foo.com",
351 expected
352 );
353
354 expected.set_domain("foo.com");
355 assert_eq_parse!(
356 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
357 Domain=foo.com",
358 expected
359 );
360 assert_eq_parse!(
361 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
362 Domain=FOO.COM",
363 expected
364 );
365
366 unexpected.set_path("/foo");
367 unexpected.set_domain("bar.com");
368 assert_ne_parse!(
369 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
370 Domain=foo.com",
371 unexpected
372 );
373 assert_ne_parse!(
374 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
375 Domain=FOO.COM",
376 unexpected
377 );
378
379 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
380 let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap();
381 expected.set_expires(expires);
382 assert_eq_parse!(
383 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
384 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
385 expected
386 );
387
388 unexpected.set_domain("foo.com");
389 let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap();
390 expected.set_expires(bad_expires);
391 assert_ne_parse!(
392 " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
393 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
394 unexpected
395 );
396 }
397
398 #[test]
399 fn odd_characters() {
400 let expected = Cookie::new("foo", "b%2Fr");
401 assert_eq_parse!("foo=b%2Fr", expected);
402 }
403
404 #[test]
405 fn odd_characters_encoded() {
406 let expected = Cookie::new("foo", "b/r");
407 let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
408 Ok(cookie) => cookie,
409 Err(e) => panic!("Failed to parse: {:?}", e),
410 };
411
412 assert_eq!(cookie, expected);
413 }
414
415 #[test]
416 fn do_not_panic_on_large_max_ages() {
417 let max_seconds = Duration::max_value().num_seconds();
418 let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish();
419 assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
420 }
421}