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
67#[cfg(feature = "percent-encode")]
68fn name_val_decoded(
69 name: &str,
70 val: &str
71) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
72 let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
73 let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
74
75 if let (&Cow::Borrowed(_), &Cow::Borrowed(_)) = (&decoded_name, &decoded_value) {
76 Ok(None)
77 } else {
78 let name = CookieStr::Concrete(Cow::Owned(decoded_name.into()));
79 let val = CookieStr::Concrete(Cow::Owned(decoded_value.into()));
80 Ok(Some((name, val)))
81 }
82}
83
84#[cfg(not(feature = "percent-encode"))]
85fn name_val_decoded(
86 _: &str,
87 _: &str
88) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
89 unreachable!("This function should never be called with 'percent-encode' disabled!")
90}
91
92fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
97 let mut attributes = s.split(';');
98
99 let key_value = attributes.next().expect("first str::split().next() returns Some");
101 let (name, value) = match key_value.find('=') {
102 Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()),
103 None => return Err(ParseError::MissingPair)
104 };
105
106 if name.is_empty() {
107 return Err(ParseError::EmptyName);
108 }
109
110 let indexed_names = |s, name, value| {
112 let name = CookieStr::indexed(name, s).expect("name sub");
113 let value = CookieStr::indexed(value, s).expect("value sub");
114 (name, value)
115 };
116
117 let (name, value) = if decode {
120 match name_val_decoded(name, value)? {
121 Some((name, value)) => (name, value),
122 None => indexed_names(s, name, value)
123 }
124 } else {
125 indexed_names(s, name, value)
126 };
127
128 let mut cookie: Cookie<'c> = Cookie {
129 name, value,
130 cookie_string: None,
131 expires: None,
132 max_age: None,
133 domain: None,
134 path: None,
135 secure: None,
136 http_only: None,
137 same_site: None,
138 partitioned: None,
139 };
140
141 for attr in attributes {
142 let (key, value) = match attr.find('=') {
143 Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
144 None => (attr.trim(), None),
145 };
146
147 match (&*key.to_ascii_lowercase(), value) {
148 ("secure", _) => cookie.secure = Some(true),
149 ("httponly", _) => cookie.http_only = Some(true),
150 ("max-age", Some(mut v)) => cookie.max_age = {
151 let is_negative = v.starts_with('-');
152 if is_negative {
153 v = &v[1..];
154 }
155
156 if !v.chars().all(|d| d.is_digit(10)) {
157 continue
158 }
159
160 if is_negative {
163 Some(Duration::ZERO)
164 } else {
165 Some(v.parse::<i64>()
166 .map(Duration::seconds)
167 .unwrap_or_else(|_| Duration::seconds(i64::max_value())))
168 }
169 },
170 ("domain", Some(d)) if !d.is_empty() => {
171 cookie.domain = Some(CookieStr::indexed(d, s).expect("domain sub"));
172 },
173 ("path", Some(v)) => {
174 cookie.path = Some(CookieStr::indexed(v, s).expect("path sub"));
175 },
176 ("samesite", Some(v)) => {
177 if v.eq_ignore_ascii_case("strict") {
178 cookie.same_site = Some(SameSite::Strict);
179 } else if v.eq_ignore_ascii_case("lax") {
180 cookie.same_site = Some(SameSite::Lax);
181 } else if v.eq_ignore_ascii_case("none") {
182 cookie.same_site = Some(SameSite::None);
183 } else {
184 }
190 }
191 ("partitioned", _) => cookie.partitioned = Some(true),
192 ("expires", Some(v)) => {
193 let tm = parse_date(v, &FMT1)
194 .or_else(|_| parse_date(v, &FMT2))
195 .or_else(|_| parse_date(v, &FMT3))
196 .or_else(|_| parse_date(v, &FMT4));
197 if let Ok(time) = tm {
200 cookie.expires = Some(time.into())
201 }
202 }
203 _ => {
204 }
209 }
210 }
211
212 Ok(cookie)
213}
214
215pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
216 where S: Into<Cow<'c, str>>
217{
218 let s = cow.into();
219 let mut cookie = parse_inner(&s, decode)?;
220 cookie.cookie_string = Some(s);
221 Ok(cookie)
222}
223
224pub(crate) fn parse_date(s: &str, format: &impl Parsable) -> Result<OffsetDateTime, time::Error> {
225 let mut date = format.parse(s.as_bytes())?;
227 if let Some(y) = date.year().or_else(|| date.year_last_two().map(|v| v as i32)) {
228 let offset = match y {
229 0..=68 => 2000,
230 69..=99 => 1900,
231 _ => 0,
232 };
233
234 date.set_year(y + offset);
235 }
236
237 Ok(PrimitiveDateTime::try_from(date)?.assume_utc())
238}
239
240#[cfg(test)]
241mod tests {
242 use super::parse_date;
243 use crate::{Cookie, SameSite};
244 use time::Duration;
245
246 macro_rules! assert_eq_parse {
247 ($string:expr, $expected:expr) => (
248 let cookie = match Cookie::parse($string) {
249 Ok(cookie) => cookie,
250 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
251 };
252
253 assert_eq!(cookie, $expected);
254 )
255 }
256
257 macro_rules! assert_ne_parse {
258 ($string:expr, $expected:expr) => (
259 let cookie = match Cookie::parse($string) {
260 Ok(cookie) => cookie,
261 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
262 };
263
264 assert_ne!(cookie, $expected);
265 )
266 }
267
268 #[test]
269 fn parse_same_site() {
270 let expected = Cookie::build(("foo", "bar")).same_site(SameSite::Lax);
271 assert_eq_parse!("foo=bar; SameSite=Lax", expected);
272 assert_eq_parse!("foo=bar; SameSite=lax", expected);
273 assert_eq_parse!("foo=bar; SameSite=LAX", expected);
274 assert_eq_parse!("foo=bar; samesite=Lax", expected);
275 assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
276
277 let expected = Cookie::build(("foo", "bar")).same_site(SameSite::Strict);
278 assert_eq_parse!("foo=bar; SameSite=Strict", expected);
279 assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
280 assert_eq_parse!("foo=bar; SameSite=strict", expected);
281 assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
282 assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
283
284 let expected = Cookie::build(("foo", "bar")).same_site(SameSite::None);
285 assert_eq_parse!("foo=bar; SameSite=None", expected);
286 assert_eq_parse!("foo=bar; SameSITE=none", expected);
287 assert_eq_parse!("foo=bar; SameSite=NOne", expected);
288 assert_eq_parse!("foo=bar; SameSite=nOne", expected);
289 }
290
291 #[test]
292 fn parse() {
293 assert!(Cookie::parse("bar").is_err());
294 assert!(Cookie::parse("=bar").is_err());
295 assert!(Cookie::parse(" =bar").is_err());
296 assert!(Cookie::parse("foo=").is_ok());
297
298 let expected = Cookie::new("foo", "bar=baz");
299 assert_eq_parse!("foo=bar=baz", expected);
300
301 let expected = Cookie::new("foo", "\"\"bar\"\"");
302 assert_eq_parse!("foo=\"\"bar\"\"", expected);
303
304 let expected = Cookie::new("foo", "\"bar");
305 assert_eq_parse!("foo= \"bar", expected);
306 assert_eq_parse!("foo=\"bar ", expected);
307 assert_ne_parse!("foo=\"\"bar\"", expected);
308 assert_ne_parse!("foo=\"\"bar \"", expected);
309 assert_ne_parse!("foo=\"\"bar \" ", expected);
310
311 let expected = Cookie::new("foo", "bar\"");
312 assert_eq_parse!("foo=bar\"", expected);
313 assert_ne_parse!("foo=\"bar\"\"", expected);
314 assert_ne_parse!("foo=\" bar\"\"", expected);
315 assert_ne_parse!("foo=\" bar\" \" ", expected);
316
317 let expected = Cookie::build(("foo", "bar")).partitioned(true).build();
318 assert_eq_parse!("foo=bar; partitioned", expected);
319 assert_eq_parse!("foo=bar; Partitioned", expected);
320 assert_eq_parse!("foo=bar; PARTITIONED", expected);
321
322 let mut expected = Cookie::new("foo", "bar");
323 assert_eq_parse!("foo=bar", expected);
324 assert_eq_parse!("foo = bar", expected);
325 assert_eq_parse!(" foo=bar ", expected);
326 assert_eq_parse!(" foo=bar ;Domain=", expected);
327 assert_eq_parse!(" foo=bar ;Domain= ", expected);
328 assert_eq_parse!(" foo=bar ;Ignored", expected);
329 assert_ne_parse!("foo=\"bar\"", expected);
330 assert_ne_parse!(" foo=\"bar \" ", expected);
331
332 let mut unexpected = Cookie::build(("foo", "bar")).http_only(false).build();
333 assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
334 assert_ne_parse!(" foo=bar; httponly", unexpected);
335
336 expected.set_http_only(true);
337 assert_eq_parse!(" foo=bar ;HttpOnly", expected);
338 assert_eq_parse!(" foo=bar ;httponly", expected);
339 assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
340 assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
341
342 expected.set_secure(true);
343 assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
344 assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
345
346 unexpected.set_http_only(true);
347 unexpected.set_secure(true);
348 assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
349 assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
350 assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
351
352 unexpected.set_secure(false);
353 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
354 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
355 assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
356
357 expected.set_max_age(Duration::ZERO);
358 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
359 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
360 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
361 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
362
363 expected.set_max_age(Duration::minutes(1));
364 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
365 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
366
367 expected.set_max_age(Duration::seconds(4));
368 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
369 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
370
371 unexpected.set_secure(true);
372 unexpected.set_max_age(Duration::minutes(1));
373 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
374 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
375 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
376 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
377 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
378
379 expected.set_path("/");
380 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
381 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
382
383 expected.set_path("/foo");
384 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
385 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
386 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
387 assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
388
389 unexpected.set_max_age(Duration::seconds(4));
390 unexpected.set_path("/bar");
391 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
392 assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
393
394 expected.set_domain("www.foo.com");
395 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
396 Domain=www.foo.com", expected);
397
398 expected.set_domain("foo.com");
399 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
400 Domain=foo.com", expected);
401 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
402 Domain=FOO.COM", expected);
403
404 expected.set_domain(".foo.com");
405 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
406 Domain=.foo.com", expected);
407 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
408 Domain=.FOO.COM", expected);
409
410 unexpected.set_path("/foo");
411 unexpected.set_domain("bar.com");
412 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
413 Domain=foo.com", unexpected);
414 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
415 Domain=FOO.COM", unexpected);
416
417 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
418 let expires = parse_date(time_str, &super::FMT1).unwrap();
419 expected.set_expires(expires);
420 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
421 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", expected);
422
423 unexpected.set_domain("foo.com");
424 let bad_expires = parse_date(time_str, &super::FMT1).unwrap();
425 expected.set_expires(bad_expires);
426 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
427 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", unexpected);
428 }
429
430 #[test]
431 fn parse_abbreviated_years() {
432 let cookie_str = "foo=bar; expires=Thu, 10-Sep-20 20:00:00 GMT";
433 let cookie = Cookie::parse(cookie_str).unwrap();
434 assert_eq!(cookie.expires_datetime().unwrap().year(), 2020);
435
436 let cookie_str = "foo=bar; expires=Thu, 10-Sep-68 20:00:00 GMT";
437 let cookie = Cookie::parse(cookie_str).unwrap();
438 assert_eq!(cookie.expires_datetime().unwrap().year(), 2068);
439
440 let cookie_str = "foo=bar; expires=Thu, 10-Sep-69 20:00:00 GMT";
441 let cookie = Cookie::parse(cookie_str).unwrap();
442 assert_eq!(cookie.expires_datetime().unwrap().year(), 1969);
443
444 let cookie_str = "foo=bar; expires=Thu, 10-Sep-99 20:00:00 GMT";
445 let cookie = Cookie::parse(cookie_str).unwrap();
446 assert_eq!(cookie.expires_datetime().unwrap().year(), 1999);
447
448 let cookie_str = "foo=bar; expires=Thu, 10-Sep-2069 20:00:00 GMT";
449 let cookie = Cookie::parse(cookie_str).unwrap();
450 assert_eq!(cookie.expires_datetime().unwrap().year(), 2069);
451 }
452
453 #[test]
454 fn parse_variant_date_fmts() {
455 let cookie_str = "foo=bar; expires=Sun, 06 Nov 1994 08:49:37 GMT";
456 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
457
458 let cookie_str = "foo=bar; expires=Sunday, 06-Nov-94 08:49:37 GMT";
459 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
460
461 let cookie_str = "foo=bar; expires=Sun Nov 6 08:49:37 1994";
462 Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
463 }
464
465 #[test]
466 fn parse_very_large_max_ages() {
467 let mut expected = Cookie::build(("foo", "bar"))
468 .max_age(Duration::seconds(i64::max_value()))
469 .build();
470
471 let string = format!("foo=bar; Max-Age={}", 1u128 << 100);
472 assert_eq_parse!(&string, expected);
473
474 expected.set_max_age(Duration::seconds(0));
475 assert_eq_parse!("foo=bar; Max-Age=-129", expected);
476
477 let string = format!("foo=bar; Max-Age=-{}", 1u128 << 100);
478 assert_eq_parse!(&string, expected);
479
480 let string = format!("foo=bar; Max-Age=-{}", i64::max_value());
481 assert_eq_parse!(&string, expected);
482
483 let string = format!("foo=bar; Max-Age={}", i64::max_value());
484 expected.set_max_age(Duration::seconds(i64::max_value()));
485 assert_eq_parse!(&string, expected);
486 }
487
488 #[test]
489 fn odd_characters() {
490 let expected = Cookie::new("foo", "b%2Fr");
491 assert_eq_parse!("foo=b%2Fr", expected);
492 }
493
494 #[test]
495 #[cfg(feature = "percent-encode")]
496 fn odd_characters_encoded() {
497 let expected = Cookie::new("foo", "b/r");
498 let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
499 Ok(cookie) => cookie,
500 Err(e) => panic!("Failed to parse: {:?}", e)
501 };
502
503 assert_eq!(cookie, expected);
504 }
505
506 #[test]
507 fn do_not_panic_on_large_max_ages() {
508 let max_seconds = Duration::MAX.whole_seconds();
509 let expected = Cookie::build(("foo", "bar"))
510 .max_age(Duration::seconds(max_seconds));
511
512 let too_many_seconds = (max_seconds as u64) + 1;
513 assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", too_many_seconds), expected);
514 }
515}