1use std::borrow::Cow;
2use std::error::Error;
3use std::str::Utf8Error;
4use std::fmt;
5use std::convert::From;
6
7#[allow(unused_imports, deprecated)]
8use std::ascii::AsciiExt;
9
10#[cfg(feature = "percent-encode")]
11use percent_encoding::percent_decode;
12use time::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset, Date};
13
14use crate::{Cookie, SameSite, CookieStr};
15
16#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18#[non_exhaustive]
19pub enum ParseError {
20 MissingPair,
22 EmptyName,
24 Utf8Error(Utf8Error),
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 }
38 }
39}
40
41impl fmt::Display for ParseError {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 write!(f, "{}", self.as_str())
44 }
45}
46
47impl From<Utf8Error> for ParseError {
48 fn from(error: Utf8Error) -> ParseError {
49 ParseError::Utf8Error(error)
50 }
51}
52
53impl Error for ParseError {
54 fn description(&self) -> &str {
55 self.as_str()
56 }
57}
58
59fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
60 let haystack_start = haystack.as_ptr() as usize;
61 let needle_start = needle.as_ptr() as usize;
62
63 if needle_start < haystack_start {
64 return None;
65 }
66
67 if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
68 return None;
69 }
70
71 let start = needle_start - haystack_start;
72 let end = start + needle.len();
73 Some((start, end))
74}
75
76#[cfg(feature = "percent-encode")]
77fn name_val_decoded(
78 name: &str,
79 val: &str
80) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
81 let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
82 let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
83
84 if let (&Cow::Borrowed(_), &Cow::Borrowed(_)) = (&decoded_name, &decoded_value) {
85 Ok(None)
86 } else {
87 let name = CookieStr::Concrete(Cow::Owned(decoded_name.into()));
88 let val = CookieStr::Concrete(Cow::Owned(decoded_value.into()));
89 Ok(Some((name, val)))
90 }
91}
92
93#[cfg(not(feature = "percent-encode"))]
94fn name_val_decoded(
95 _: &str,
96 _: &str
97) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
98 unreachable!("This function should never be called with 'percent-encode' disabled!")
99}
100
101fn trim_quotes(s: &str) -> &str {
102 if s.len() < 2 {
103 return s;
104 }
105
106 match (s.chars().next(), s.chars().last()) {
107 (Some('"'), Some('"')) => &s[1..(s.len() - 1)],
108 _ => s
109 }
110}
111
112fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
117 let mut attributes = s.split(';');
118
119 let key_value = attributes.next().expect("first str::split().next() returns Some");
121 let (name, value) = match key_value.find('=') {
122 Some(i) => {
123 let (key, value) = (key_value[..i].trim(), key_value[(i + 1)..].trim());
124 (key, trim_quotes(value).trim())
125 },
126 None => return Err(ParseError::MissingPair)
127 };
128
129 if name.is_empty() {
130 return Err(ParseError::EmptyName);
131 }
132
133 let indexed_names = |s, name, value| {
135 let name_indexes = indexes_of(name, s).expect("name sub");
136 let value_indexes = indexes_of(value, s).expect("value sub");
137 let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
138 let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
139 (name, value)
140 };
141
142 let (name, value) = if decode {
145 match name_val_decoded(name, value)? {
146 Some((name, value)) => (name, value),
147 None => indexed_names(s, name, value)
148 }
149 } else {
150 indexed_names(s, name, value)
151 };
152
153 let mut cookie: Cookie<'c> = Cookie {
154 name, value,
155 cookie_string: None,
156 expires: None,
157 max_age: None,
158 domain: None,
159 path: None,
160 secure: None,
161 http_only: None,
162 same_site: None
163 };
164
165 for attr in attributes {
166 let (key, value) = match attr.find('=') {
167 Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
168 None => (attr.trim(), None),
169 };
170
171 match (&*key.to_ascii_lowercase(), value) {
172 ("secure", _) => cookie.secure = Some(true),
173 ("httponly", _) => cookie.http_only = Some(true),
174 ("max-age", Some(mut v)) => cookie.max_age = {
175 let is_negative = v.starts_with('-');
176 if is_negative {
177 v = &v[1..];
178 }
179
180 if !v.chars().all(|d| d.is_digit(10)) {
181 continue
182 }
183
184 if is_negative {
187 Some(Duration::zero())
188 } else {
189 Some(v.parse::<i64>()
190 .map(Duration::seconds)
191 .unwrap_or_else(|_| Duration::seconds(i64::max_value())))
192 }
193 },
194 ("domain", Some(mut domain)) if !domain.is_empty() => {
195 if domain.starts_with('.') {
196 domain = &domain[1..];
197 }
198
199 let (i, j) = indexes_of(domain, s).expect("domain sub");
200 cookie.domain = Some(CookieStr::Indexed(i, j));
201 }
202 ("path", Some(v)) => {
203 let (i, j) = indexes_of(v, s).expect("path sub");
204 cookie.path = Some(CookieStr::Indexed(i, j));
205 }
206 ("samesite", Some(v)) => {
207 if v.eq_ignore_ascii_case("strict") {
208 cookie.same_site = Some(SameSite::Strict);
209 } else if v.eq_ignore_ascii_case("lax") {
210 cookie.same_site = Some(SameSite::Lax);
211 } else if v.eq_ignore_ascii_case("none") {
212 cookie.same_site = Some(SameSite::None);
213 } else {
214 }
220 }
221 ("expires", Some(v)) => {
222 let tm = parse_gmt_date(v, "%a, %d %b %Y %H:%M:%S GMT")
226 .or_else(|_| parse_gmt_date(v, "%A, %d-%b-%y %H:%M:%S GMT"))
227 .or_else(|_| parse_gmt_date(v, "%a, %d-%b-%Y %H:%M:%S GMT"))
228 .or_else(|_| parse_gmt_date(v, "%a %b %d %H:%M:%S %Y"));
229
230 if let Ok(time) = tm {
231 cookie.expires = Some(time.into())
232 }
233 }
234 _ => {
235 }
240 }
241 }
242
243 Ok(cookie)
244}
245
246pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
247 where S: Into<Cow<'c, str>>
248{
249 let s = cow.into();
250 let mut cookie = parse_inner(&s, decode)?;
251 cookie.cookie_string = Some(s);
252 Ok(cookie)
253}
254
255pub(crate) fn parse_gmt_date(s: &str, format: &str) -> Result<OffsetDateTime, time::ParseError> {
256 PrimitiveDateTime::parse(s, format)
257 .map(|t| t.assume_utc().to_offset(UtcOffset::UTC))
258 .map(|date| {
260 let offset = match date.year() {
261 0..=68 => 2000,
262 69..=99 => 1900,
263 _ => return date,
264 };
265
266 let new_date = Date::try_from_ymd(date.year() + offset, date.month(), date.day());
267 PrimitiveDateTime::new(new_date.expect("date from date"), date.time()).assume_utc()
268 })
269}
270
271#[cfg(test)]
272mod tests {
273 use crate::{Cookie, SameSite};
274 use super::parse_gmt_date;
275 use ::time::Duration;
276
277 macro_rules! assert_eq_parse {
278 ($string:expr, $expected:expr) => (
279 let cookie = match Cookie::parse($string) {
280 Ok(cookie) => cookie,
281 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
282 };
283
284 assert_eq!(cookie, $expected);
285 )
286 }
287
288 macro_rules! assert_ne_parse {
289 ($string:expr, $expected:expr) => (
290 let cookie = match Cookie::parse($string) {
291 Ok(cookie) => cookie,
292 Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
293 };
294
295 assert_ne!(cookie, $expected);
296 )
297 }
298
299 #[test]
300 fn parse_same_site() {
301 let expected = Cookie::build("foo", "bar")
302 .same_site(SameSite::Lax)
303 .finish();
304
305 assert_eq_parse!("foo=bar; SameSite=Lax", expected);
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
311 let expected = Cookie::build("foo", "bar")
312 .same_site(SameSite::Strict)
313 .finish();
314
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 assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
319 assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
320
321 let expected = Cookie::build("foo", "bar")
322 .same_site(SameSite::None)
323 .finish();
324
325 assert_eq_parse!("foo=bar; SameSite=None", expected);
326 assert_eq_parse!("foo=bar; SameSITE=none", expected);
327 assert_eq_parse!("foo=bar; SameSite=NOne", expected);
328 assert_eq_parse!("foo=bar; SameSite=nOne", expected);
329 }
330
331 #[test]
332 fn parse() {
333 assert!(Cookie::parse("bar").is_err());
334 assert!(Cookie::parse("=bar").is_err());
335 assert!(Cookie::parse(" =bar").is_err());
336 assert!(Cookie::parse("foo=").is_ok());
337
338 let expected = Cookie::build("foo", "bar=baz").finish();
339 assert_eq_parse!("foo=bar=baz", expected);
340
341 let expected = Cookie::build("foo", "\"bar\"").finish();
342 assert_eq_parse!("foo=\"\"bar\"\"", expected);
343
344 let expected = Cookie::build("foo", "\"bar").finish();
345 assert_eq_parse!("foo= \"bar", expected);
346 assert_eq_parse!("foo=\"bar ", expected);
347 assert_eq_parse!("foo=\"\"bar\"", expected);
348 assert_eq_parse!("foo=\"\"bar \"", expected);
349 assert_eq_parse!("foo=\"\"bar \" ", expected);
350
351 let expected = Cookie::build("foo", "bar\"").finish();
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 mut 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 assert_eq_parse!(" foo=\"bar \" ", expected);
363 assert_eq_parse!(" foo=bar ;Domain=", expected);
364 assert_eq_parse!(" foo=bar ;Domain= ", expected);
365 assert_eq_parse!(" foo=bar ;Ignored", expected);
366
367 let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
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 unexpected.set_path("/foo");
440 unexpected.set_domain("bar.com");
441 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
442 Domain=foo.com", unexpected);
443 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
444 Domain=FOO.COM", unexpected);
445
446 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
447 let expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%M:%S GMT").unwrap();
448 expected.set_expires(expires);
449 assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
450 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", expected);
451
452 unexpected.set_domain("foo.com");
453 let bad_expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%S:%M GMT").unwrap();
454 expected.set_expires(bad_expires);
455 assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
456 Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", unexpected);
457 }
458
459 #[test]
460 fn parse_abbreviated_years() {
461 let cookie_str = "foo=bar; expires=Thu, 10-Sep-20 20:00:00 GMT";
462 let cookie = Cookie::parse(cookie_str).unwrap();
463 assert_eq!(cookie.expires_datetime().unwrap().year(), 2020);
464
465 let cookie_str = "foo=bar; expires=Thu, 10-Sep-68 20:00:00 GMT";
466 let cookie = Cookie::parse(cookie_str).unwrap();
467 assert_eq!(cookie.expires_datetime().unwrap().year(), 2068);
468
469 let cookie_str = "foo=bar; expires=Thu, 10-Sep-69 20:00:00 GMT";
470 let cookie = Cookie::parse(cookie_str).unwrap();
471 assert_eq!(cookie.expires_datetime().unwrap().year(), 1969);
472
473 let cookie_str = "foo=bar; expires=Thu, 10-Sep-99 20:00:00 GMT";
474 let cookie = Cookie::parse(cookie_str).unwrap();
475 assert_eq!(cookie.expires_datetime().unwrap().year(), 1999);
476 }
477
478 #[test]
479 fn parse_very_large_max_ages() {
480 let mut expected = Cookie::build("foo", "bar")
481 .max_age(Duration::seconds(i64::max_value()))
482 .finish();
483
484 let string = format!("foo=bar; Max-Age={}", 1u128 << 100);
485 assert_eq_parse!(&string, expected);
486
487 expected.set_max_age(Duration::seconds(0));
488 assert_eq_parse!("foo=bar; Max-Age=-129", expected);
489
490 let string = format!("foo=bar; Max-Age=-{}", 1u128 << 100);
491 assert_eq_parse!(&string, expected);
492
493 let string = format!("foo=bar; Max-Age=-{}", i64::max_value());
494 assert_eq_parse!(&string, expected);
495
496 let string = format!("foo=bar; Max-Age={}", i64::max_value());
497 expected.set_max_age(Duration::seconds(i64::max_value()));
498 assert_eq_parse!(&string, expected);
499 }
500
501 #[test]
502 fn odd_characters() {
503 let expected = Cookie::new("foo", "b%2Fr");
504 assert_eq_parse!("foo=b%2Fr", expected);
505 }
506
507 #[test]
508 #[cfg(feature = "percent-encode")]
509 fn odd_characters_encoded() {
510 let expected = Cookie::new("foo", "b/r");
511 let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
512 Ok(cookie) => cookie,
513 Err(e) => panic!("Failed to parse: {:?}", e)
514 };
515
516 assert_eq!(cookie, expected);
517 }
518
519 #[test]
520 fn do_not_panic_on_large_max_ages() {
521 let max_seconds = Duration::max_value().whole_seconds();
522 let expected = Cookie::build("foo", "bar")
523 .max_age(Duration::seconds(max_seconds))
524 .finish();
525 let too_many_seconds = (max_seconds as u64) + 1;
526 assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", too_many_seconds), expected);
527 }
528}