cookie/
parse.rs

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/// Enum corresponding to a parsing error.
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18#[non_exhaustive]
19pub enum ParseError {
20    /// The cookie did not contain a name/value pair.
21    MissingPair,
22    /// The cookie's name was empty.
23    EmptyName,
24    /// Decoding the cookie's name or value resulted in invalid UTF-8.
25    Utf8Error(Utf8Error),
26}
27
28impl ParseError {
29    /// Returns a description of this error as a string
30    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
112// This function does the real parsing but _does not_ set the `cookie_string` in
113// the returned cookie object. This only exists so that the borrow to `s` is
114// returned at the end of the call, allowing the `cookie_string` field to be
115// set in the outer `parse` function.
116fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
117    let mut attributes = s.split(';');
118
119    // Determine the name = val.
120    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    // If there is nothing to decode, or we're not decoding, use indexes.
134    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    // Create a cookie with all of the defaults. We'll fill things in while we
143    // iterate through the parameters below.
144    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                // From RFC 6265 5.2.2: neg values indicate that the earliest
185                // expiration should be used, so set the max age to 0 seconds.
186                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                    // We do nothing here, for now. When/if the `SameSite`
215                    // attribute becomes standard, the spec says that we should
216                    // ignore this cookie, i.e, fail to parse it, when an
217                    // invalid value is passed in. The draft is at
218                    // http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
219                }
220            }
221            ("expires", Some(v)) => {
222                // Try strptime with three date formats according to
223                // http://tools.ietf.org/html/rfc2616#section-3.3.1. Try
224                // additional ones as encountered in the real world.
225                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                // We're going to be permissive here. If we have no idea what
236                // this is, then it's something nonstandard. We're not going to
237                // store it (because it's not compliant), but we're also not
238                // going to emit an error.
239            }
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        // Handle malformed "abbreviated" dates like Chromium. See cookie#162.
259        .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}