1use std::borrow::Cow;
2use std::error::Error;
3use std::convert::{From, TryFrom};
4use std::str::Utf8Error;
5use std::fmt;
6
7#[allow(unused_imports, deprecated)]
8use std::ascii::AsciiExt;
9
10#[cfg(feature = "percent-encode")]
11use percent_encoding::percent_decode;
12use time::{PrimitiveDateTime, Duration, OffsetDateTime};
13use time::{parsing::Parsable, macros::format_description, format_description::FormatItem};
14
15use crate::{Cookie, SameSite, CookieStr};
16
17pub static FMT1: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day] [month repr:short] [year padding:none] [hour]:[minute]:[second] GMT");
20pub static FMT2: &[FormatItem<'_>] = format_description!("[weekday], [day]-[month repr:short]-[year repr:last_two] [hour]:[minute]:[second] GMT");
21pub static FMT3: &[FormatItem<'_>] = format_description!("[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none]");
22pub static FMT4: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day]-[month repr:short]-[year padding:none] [hour]:[minute]:[second] GMT");
23
24#[derive(Debug, PartialEq, Eq, Clone, Copy)]
26#[non_exhaustive]
27pub enum ParseError {
28 MissingPair,
30 EmptyName,
32 Utf8Error(Utf8Error),
34}
35
36impl ParseError {
37 pub fn as_str(&self) -> &'static str {
39 match *self {
40 ParseError::MissingPair => "the cookie is missing a name/value pair",
41 ParseError::EmptyName => "the cookie's name is empty",
42 ParseError::Utf8Error(_) => {
43 "decoding the cookie's name or value resulted in invalid UTF-8"
44 }
45 }
46 }
47}
48
49impl fmt::Display for ParseError {
50 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51 write!(f, "{}", self.as_str())
52 }
53}
54
55impl From<Utf8Error> for ParseError {
56 fn from(error: Utf8Error) -> ParseError {
57 ParseError::Utf8Error(error)
58 }
59}
60
61impl Error for ParseError {
62 fn description(&self) -> &str {
63 self.as_str()
64 }
65}
66
67fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
68 let haystack_start = haystack.as_ptr() as usize;
69 let needle_start = needle.as_ptr() as usize;
70
71 if needle_start < haystack_start {
72 return None;
73 }
74
75 if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
76 return None;
77 }
78
79 let start = needle_start - haystack_start;
80 let end = start + needle.len();
81 Some((start, end))
82}
83
84#[cfg(feature = "percent-encode")]
85fn name_val_decoded(
86 name: &str,
87 val: &str
88) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
89 let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
90 let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
91
92 if let (&Cow::Borrowed(_), &Cow::Borrowed(_)) = (&decoded_name, &decoded_value) {
93 Ok(None)
94 } else {
95 let name = CookieStr::Concrete(Cow::Owned(decoded_name.into()));
96 let val = CookieStr::Concrete(Cow::Owned(decoded_value.into()));
97 Ok(Some((name, val)))
98 }
99}
100
101#[cfg(not(feature = "percent-encode"))]
102fn name_val_decoded(
103 _: &str,
104 _: &str
105) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
106 unreachable!("This function should never be called with 'percent-encode' disabled!")
107}
108
109fn trim_quotes(s: &str) -> &str {
110 if s.len() < 2 {
111 return s;
112 }
113
114 match (s.chars().next(), s.chars().last()) {
115 (Some('"'), Some('"')) => &s[1..(s.len() - 1)],
116 _ => s
117 }
118}
119
120fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
125 let mut attributes = s.split(';');
126
127 let key_value = attributes.next().expect("first str::split().next() returns Some");
129 let (name, value) = match key_value.find('=') {
130 Some(i) => {
131 let (key, value) = (key_value[..i].trim(), key_value[(i + 1)..].trim());
132 (key, trim_quotes(value).trim())
133 },
134 None => return Err(ParseError::MissingPair)
135 };
136
137 if name.is_empty() {
138 return Err(ParseError::EmptyName);
139 }
140
141 let indexed_names = |s, name, value| {
143 let name_indexes = indexes_of(name, s).expect("name sub");
144 let value_indexes = indexes_of(value, s).expect("value sub");
145 let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
146 let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
147 (name, value)
148 };
149
150 let (name, value) = if decode {
153 match name_val_decoded(name, value)? {
154 Some((name, value)) => (name, value),
155 None => indexed_names(s, name, value)
156 }
157 } else {
158 indexed_names(s, name, value)
159 };
160
161 let mut cookie: Cookie<'c> = Cookie {
162 name, value,
163 cookie_string: None,
164 expires: None,
165 max_age: None,
166 domain: None,
167 path: None,
168 secure: None,
169 http_only: None,
170 same_site: None
171 };
172
173 for attr in attributes {
174 let (key, value) = match attr.find('=') {
175 Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
176 None => (attr.trim(), None),
177 };
178
179 match (&*key.to_ascii_lowercase(), value) {
180 ("secure", _) => cookie.secure = Some(true),
181 ("httponly", _) => cookie.http_only = Some(true),
182 ("max-age", Some(mut v)) => cookie.max_age = {
183 let is_negative = v.starts_with('-');
184 if is_negative {
185 v = &v[1..];
186 }
187
188 if !v.chars().all(|d| d.is_digit(10)) {
189 continue
190 }
191
192 if is_negative {
195 Some(Duration::ZERO)
196 } else {
197 Some(v.parse::<i64>()
198 .map(Duration::seconds)
199 .unwrap_or_else(|_| Duration::seconds(i64::max_value())))
200 }
201 },
202 ("domain", Some(mut domain)) if !domain.is_empty() => {
203 if domain.starts_with('.') {
204 domain = &domain[1..];
205 }
206
207 let (i, j) = indexes_of(domain, s).expect("domain sub");
208 cookie.domain = Some(CookieStr::Indexed(i, j));
209 }
210 ("path", Some(v)) => {
211 let (i, j) = indexes_of(v, s).expect("path sub");
212 cookie.path = Some(CookieStr::Indexed(i, j));
213 }
214 ("samesite", Some(v)) => {
215 if v.eq_ignore_ascii_case("strict") {
216 cookie.same_site = Some(SameSite::Strict);
217 } else if v.eq_ignore_ascii_case("lax") {
218 cookie.same_site = Some(SameSite::Lax);
219 } else if v.eq_ignore_ascii_case("none") {
220 cookie.same_site = Some(SameSite::None);
221 } else {
222 }
228 }
229 ("expires", Some(v)) => {
230 let tm = parse_date(v, &FMT1)
231 .or_else(|_| parse_date(v, &FMT2))
232 .or_else(|_| parse_date(v, &FMT3))
233 .or_else(|_| parse_date(v, &FMT4));
234 if let Ok(time) = tm {
237 cookie.expires = Some(time.into())
238 }
239 }
240 _ => {
241 }
246 }
247 }
248
249 Ok(cookie)
250}
251
252pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
253 where S: Into<Cow<'c, str>>
254{
255 let s = cow.into();
256 let mut cookie = parse_inner(&s, decode)?;
257 cookie.cookie_string = Some(s);
258 Ok(cookie)
259}
260
261pub(crate) fn parse_date(s: &str, format: &impl Parsable) -> Result<OffsetDateTime, time::Error> {
262 let mut date = format.parse(s.as_bytes())?;
264 if let Some(y) = date.year().or_else(|| date.year_last_two().map(|v| v as i32)) {
265 let offset = match y {
266 0..=68 => 2000,
267 69..=99 => 1900,
268 _ => 0,
269 };
270
271 date.set_year(y + offset);
272 }
273
274 Ok(PrimitiveDateTime::try_from(date)?.assume_utc())
275}
276
277#[cfg(test)]
278mod tests {
279 use super::parse_date;
280 use crate::{Cookie, SameSite};
281 use time::Duration;
282
283 macro_rules! assert_eq_parse {
284 ($string:expr, $expected:expr) => (
285 let cookie = match Cookie::parse($string) {
286 Ok(cookie) => cookie,
287 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
288 };
289
290 assert_eq!(cookie, $expected);
291 )
292 }
293
294 macro_rules! assert_ne_parse {
295 ($string:expr, $expected:expr) => (
296 let cookie = match Cookie::parse($string) {
297 Ok(cookie) => cookie,
298 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
299 };
300
301 assert_ne!(cookie, $expected);
302 )
303 }
304
305 #[test]
306 fn parse_same_site() {
307 let expected = Cookie::build("foo", "bar")
308 .same_site(SameSite::Lax)
309 .finish();
310
311 assert_eq_parse!("foo=bar; SameSite=Lax", expected);
312 assert_eq_parse!("foo=bar; SameSite=lax", expected);
313 assert_eq_parse!("foo=bar; SameSite=LAX", expected);
314 assert_eq_parse!("foo=bar; samesite=Lax", expected);
315 assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
316
317 let expected = Cookie::build("foo", "bar")
318 .same_site(SameSite::Strict)
319 .finish();
320
321 assert_eq_parse!("foo=bar; SameSite=Strict", expected);
322 assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
323 assert_eq_parse!("foo=bar; SameSite=strict", expected);
324 assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
325 assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
326
327 let expected = Cookie::build("foo", "bar")
328 .same_site(SameSite::None)
329 .finish();
330
331 assert_eq_parse!("foo=bar; SameSite=None", expected);
332 assert_eq_parse!("foo=bar; SameSITE=none", expected);
333 assert_eq_parse!("foo=bar; SameSite=NOne", expected);
334 assert_eq_parse!("foo=bar; SameSite=nOne", expected);
335 }
336
337 #[test]
338 fn parse() {
339 assert!(Cookie::parse("bar").is_err());
340 assert!(Cookie::parse("=bar").is_err());
341 assert!(Cookie::parse(" =bar").is_err());
342 assert!(Cookie::parse("foo=").is_ok());
343
344 let expected = Cookie::build("foo", "bar=baz").finish();
345 assert_eq_parse!("foo=bar=baz", expected);
346
347 let expected = Cookie::build("foo", "\"bar\"").finish();
348 assert_eq_parse!("foo=\"\"bar\"\"", expected);
349
350 let expected = Cookie::build("foo", "\"bar").finish();
351 assert_eq_parse!("foo= \"bar", expected);
352 assert_eq_parse!("foo=\"bar ", expected);
353 assert_eq_parse!("foo=\"\"bar\"", expected);
354 assert_eq_parse!("foo=\"\"bar \"", expected);
355 assert_eq_parse!("foo=\"\"bar \" ", expected);
356
357 let expected = Cookie::build("foo", "bar\"").finish();
358 assert_eq_parse!("foo=bar\"", expected);
359 assert_eq_parse!("foo=\"bar\"\"", expected);
360 assert_eq_parse!("foo=\" bar\"\"", expected);
361 assert_eq_parse!("foo=\" bar\" \" ", expected);
362
363 let mut expected = Cookie::build("foo", "bar").finish();
364 assert_eq_parse!("foo=bar", expected);
365 assert_eq_parse!("foo = bar", expected);
366 assert_eq_parse!("foo=\"bar\"", expected);
367 assert_eq_parse!(" foo=bar ", expected);
368 assert_eq_parse!(" foo=\"bar \" ", expected);
369 assert_eq_parse!(" foo=bar ;Domain=", expected);
370 assert_eq_parse!(" foo=bar ;Domain= ", expected);
371 assert_eq_parse!(" foo=bar ;Ignored", expected);
372
373 let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
374 assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
375 assert_ne_parse!(" foo=bar; httponly", unexpected);
376
377 expected.set_http_only(true);
378 assert_eq_parse!(" foo=bar ;HttpOnly", expected);
379 assert_eq_parse!(" foo=bar ;httponly", expected);
380 assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
381 assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
382
383 expected.set_secure(true);
384 assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
385 assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
386
387 unexpected.set_http_only(true);
388 unexpected.set_secure(true);
389 assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
390 assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
391 assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
392
393 unexpected.set_secure(false);
394 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
395 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
396 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
397
398 expected.set_max_age(Duration::ZERO);
399 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
400 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
401 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
402 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
403
404 expected.set_max_age(Duration::minutes(1));
405 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
406 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
407
408 expected.set_max_age(Duration::seconds(4));
409 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
410 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
411
412 unexpected.set_secure(true);
413 unexpected.set_max_age(Duration::minutes(1));
414 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
415 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
416 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
417 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
418 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
419
420 expected.set_path("/");
421 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
422 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
423
424 expected.set_path("/foo");
425 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
426 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
427 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
428 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
429
430 unexpected.set_max_age(Duration::seconds(4));
431 unexpected.set_path("/bar");
432 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
433 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
434
435 expected.set_domain("www.foo.com");
436 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
437 Domain=www.foo.com", expected);
438
439 expected.set_domain("foo.com");
440 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
441 Domain=foo.com", expected);
442 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
443 Domain=FOO.COM", expected);
444
445 unexpected.set_path("/foo");
446 unexpected.set_domain("bar.com");
447 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
448 Domain=foo.com", unexpected);
449 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
450 Domain=FOO.COM", unexpected);
451
452 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
453 let expires = parse_date(time_str, &super::FMT1).unwrap();
454 expected.set_expires(expires);
455 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
456 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", expected);
457
458 unexpected.set_domain("foo.com");
459 let bad_expires = parse_date(time_str, &super::FMT1).unwrap();
460 expected.set_expires(bad_expires);
461 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
462 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", unexpected);
463 }
464
465 #[test]
466 fn parse_abbreviated_years() {
467 let cookie_str = "foo=bar; expires=Thu, 10-Sep-20 20:00:00 GMT";
468 let cookie = Cookie::parse(cookie_str).unwrap();
469 assert_eq!(cookie.expires_datetime().unwrap().year(), 2020);
470
471 let cookie_str = "foo=bar; expires=Thu, 10-Sep-68 20:00:00 GMT";
472 let cookie = Cookie::parse(cookie_str).unwrap();
473 assert_eq!(cookie.expires_datetime().unwrap().year(), 2068);
474
475 let cookie_str = "foo=bar; expires=Thu, 10-Sep-69 20:00:00 GMT";
476 let cookie = Cookie::parse(cookie_str).unwrap();
477 assert_eq!(cookie.expires_datetime().unwrap().year(), 1969);
478
479 let cookie_str = "foo=bar; expires=Thu, 10-Sep-99 20:00:00 GMT";
480 let cookie = Cookie::parse(cookie_str).unwrap();
481 assert_eq!(cookie.expires_datetime().unwrap().year(), 1999);
482
483 let cookie_str = "foo=bar; expires=Thu, 10-Sep-2069 20:00:00 GMT";
484 let cookie = Cookie::parse(cookie_str).unwrap();
485 assert_eq!(cookie.expires_datetime().unwrap().year(), 2069);
486 }
487
488 #[test]
489 fn parse_variant_date_fmts() {
490 let cookie_str = "foo=bar; expires=Sun, 06 Nov 1994 08:49:37 GMT";
491 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
492
493 let cookie_str = "foo=bar; expires=Sunday, 06-Nov-94 08:49:37 GMT";
494 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
495
496 let cookie_str = "foo=bar; expires=Sun Nov 6 08:49:37 1994";
497 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
498 }
499
500 #[test]
501 fn parse_very_large_max_ages() {
502 let mut expected = Cookie::build("foo", "bar")
503 .max_age(Duration::seconds(i64::max_value()))
504 .finish();
505
506 let string = format!("foo=bar; Max-Age={}", 1u128 << 100);
507 assert_eq_parse!(&string, expected);
508
509 expected.set_max_age(Duration::seconds(0));
510 assert_eq_parse!("foo=bar; Max-Age=-129", expected);
511
512 let string = format!("foo=bar; Max-Age=-{}", 1u128 << 100);
513 assert_eq_parse!(&string, expected);
514
515 let string = format!("foo=bar; Max-Age=-{}", i64::max_value());
516 assert_eq_parse!(&string, expected);
517
518 let string = format!("foo=bar; Max-Age={}", i64::max_value());
519 expected.set_max_age(Duration::seconds(i64::max_value()));
520 assert_eq_parse!(&string, expected);
521 }
522
523 #[test]
524 fn odd_characters() {
525 let expected = Cookie::new("foo", "b%2Fr");
526 assert_eq_parse!("foo=b%2Fr", expected);
527 }
528
529 #[test]
530 #[cfg(feature = "percent-encode")]
531 fn odd_characters_encoded() {
532 let expected = Cookie::new("foo", "b/r");
533 let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
534 Ok(cookie) => cookie,
535 Err(e) => panic!("Failed to parse: {:?}", e)
536 };
537
538 assert_eq!(cookie, expected);
539 }
540
541 #[test]
542 fn do_not_panic_on_large_max_ages() {
543 let max_seconds = Duration::MAX.whole_seconds();
544 let expected = Cookie::build("foo", "bar")
545 .max_age(Duration::seconds(max_seconds))
546 .finish();
547 let too_many_seconds = (max_seconds as u64) + 1;
548 assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", too_many_seconds), expected);
549 }
550}