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