1use crate::cookie_domain::CookieDomain;
2use crate::cookie_expiration::CookieExpiration;
3use crate::cookie_path::CookiePath;
4
5use crate::utils::{is_http_scheme, is_secure};
6use cookie::{Cookie as RawCookie, CookieBuilder as RawCookieBuilder, ParseError};
7#[cfg(feature = "serde")]
8use serde_derive::{Deserialize, Serialize};
9use std::borrow::Cow;
10use std::convert::TryFrom;
11use std::fmt;
12use std::ops::Deref;
13use time;
14use url::Url;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Error {
18 NonHttpScheme,
21 NonRelativeScheme,
24 DomainMismatch,
26 Expired,
28 Parse,
30 #[cfg(feature = "public_suffix")]
31 PublicSuffix,
34 UnspecifiedDomain,
36}
37
38impl std::error::Error for Error {}
39
40impl fmt::Display for Error {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(
43 f,
44 "{}",
45 match *self {
46 Error::NonHttpScheme =>
47 "request-uri is not an http scheme but HttpOnly attribute set",
48 Error::NonRelativeScheme => {
49 "request-uri is not a relative scheme; cannot determine host"
50 }
51 Error::DomainMismatch => "request-uri does not domain-match the cookie",
52 Error::Expired => "attempted to utilize an Expired Cookie",
53 Error::Parse => "unable to parse string as cookie::Cookie",
54 #[cfg(feature = "public_suffix")]
55 Error::PublicSuffix => "domain-attribute value is a public suffix",
56 Error::UnspecifiedDomain => "domain-attribute is not specified",
57 }
58 )
59 }
60}
61
62impl From<ParseError> for Error {
64 fn from(_: ParseError) -> Error {
65 Error::Parse
66 }
67}
68
69pub type CookieResult<'a> = Result<Cookie<'a>, Error>;
70
71#[derive(PartialEq, Clone, Debug)]
73#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
74pub struct Cookie<'a> {
75 #[cfg_attr(feature = "serde", serde(serialize_with = "serde_raw_cookie::serialize"))]
77 #[cfg_attr(feature = "serde", serde(deserialize_with = "serde_raw_cookie::deserialize"))]
78 raw_cookie: RawCookie<'a>,
79 pub path: CookiePath,
83 pub domain: CookieDomain,
87 pub expires: CookieExpiration,
94}
95
96#[cfg(feature = "serde")]
97mod serde_raw_cookie {
98 use cookie::Cookie as RawCookie;
99 use serde::de::Error;
100 use serde::de::Unexpected;
101 use serde::{Deserialize, Deserializer, Serialize, Serializer};
102 use std::str::FromStr;
103
104 pub fn serialize<S>(cookie: &RawCookie<'_>, serializer: S) -> Result<S::Ok, S::Error>
105 where
106 S: Serializer,
107 {
108 cookie.to_string().serialize(serializer)
109 }
110
111 pub fn deserialize<'a, D>(deserializer: D) -> Result<RawCookie<'static>, D::Error>
112 where
113 D: Deserializer<'a>,
114 {
115 let cookie = String::deserialize(deserializer)?;
116 match RawCookie::from_str(&cookie) {
117 Ok(cookie) => Ok(cookie),
118 Err(_) => Err(D::Error::invalid_value(
119 Unexpected::Str(&cookie),
120 &"a cookie string",
121 )),
122 }
123 }
124}
125
126impl<'a> Cookie<'a> {
127 pub fn matches(&self, request_url: &Url) -> bool {
129 self.path.matches(request_url)
130 && self.domain.matches(request_url)
131 && (!self.raw_cookie.secure().unwrap_or(false) || is_secure(request_url))
132 && (!self.raw_cookie.http_only().unwrap_or(false) || is_http_scheme(request_url))
133 }
134
135 pub fn is_persistent(&self) -> bool {
137 match self.expires {
138 CookieExpiration::AtUtc(_) => true,
139 CookieExpiration::SessionEnd => false,
140 }
141 }
142
143 pub fn expire(&mut self) {
145 self.expires = CookieExpiration::from(0u64);
146 }
147
148 pub fn is_expired(&self) -> bool {
150 self.expires.is_expired()
151 }
152
153 pub fn expires_by(&self, utc_tm: &time::OffsetDateTime) -> bool {
155 self.expires.expires_by(utc_tm)
156 }
157
158 pub fn parse<S>(cookie_str: S, request_url: &Url) -> CookieResult<'a>
160 where
161 S: Into<Cow<'a, str>>,
162 {
163 Cookie::try_from_raw_cookie(&RawCookie::parse(cookie_str)?, request_url)
164 }
165
166 pub fn try_from_raw_cookie(raw_cookie: &RawCookie<'a>, request_url: &Url) -> CookieResult<'a> {
169 if raw_cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) {
170 return Err(Error::NonHttpScheme);
174 }
175
176 let domain = match CookieDomain::try_from(raw_cookie) {
177 Ok(d @ CookieDomain::Suffix(_)) => {
179 if !d.matches(request_url) {
180 Err(Error::DomainMismatch)
184 } else {
185 Ok(d)
189 }
190 }
191 Err(_) => Err(Error::Parse),
192 _ => CookieDomain::host_only(request_url),
196 }?;
197
198 let path = raw_cookie
199 .path()
200 .as_ref()
201 .and_then(|p| CookiePath::parse(p))
202 .unwrap_or_else(|| CookiePath::default_path(request_url));
203
204 let expires = if let Some(max_age) = raw_cookie.max_age() {
207 CookieExpiration::from(max_age)
208 } else if let Some(expiration) = raw_cookie.expires() {
209 CookieExpiration::from(expiration)
210 } else {
211 CookieExpiration::SessionEnd
212 };
213
214 Ok(Cookie {
215 raw_cookie: raw_cookie.clone(),
216 path,
217 expires,
218 domain,
219 })
220 }
221
222 pub fn into_owned(self) -> Cookie<'static> {
223 Cookie {
224 raw_cookie: self.raw_cookie.into_owned(),
225 path: self.path,
226 domain: self.domain,
227 expires: self.expires,
228 }
229 }
230}
231
232impl<'a> Deref for Cookie<'a> {
233 type Target = RawCookie<'a>;
234 fn deref(&self) -> &Self::Target {
235 &self.raw_cookie
236 }
237}
238
239impl<'a> From<Cookie<'a>> for RawCookie<'a> {
240 fn from(cookie: Cookie<'a>) -> RawCookie<'static> {
241 let mut builder =
242 RawCookieBuilder::new(cookie.name().to_owned(), cookie.value().to_owned());
243
244 match cookie.expires {
246 CookieExpiration::AtUtc(utc_tm) => {
247 builder = builder.expires(utc_tm);
248 }
249 CookieExpiration::SessionEnd => {}
250 }
251
252 if cookie.path.is_from_path_attr() {
253 builder = builder.path(String::from(cookie.path));
254 }
255
256 if let CookieDomain::Suffix(s) = cookie.domain {
257 builder = builder.domain(s);
258 }
259
260 builder.build()
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::Cookie;
267 use crate::cookie_domain::CookieDomain;
268 use crate::cookie_expiration::CookieExpiration;
269 use cookie::Cookie as RawCookie;
270 use time::{Duration, OffsetDateTime};
271 use url::Url;
272
273 use crate::utils::test as test_utils;
274
275 fn cmp_domain(cookie: &str, url: &str, exp: CookieDomain) {
276 let ua = test_utils::make_cookie(cookie, url, None, None);
277 assert!(ua.domain == exp, "\n{:?}", ua);
278 }
279
280 #[test]
281 fn no_domain() {
282 let url = test_utils::url("http://example.com/foo/bar");
283 cmp_domain(
284 "cookie1=value1",
285 "http://example.com/foo/bar",
286 CookieDomain::host_only(&url).expect("unable to parse domain"),
287 );
288 }
289
290 #[test]
294 fn empty_domain() {
295 let url = test_utils::url("http://example.com/foo/bar");
296 cmp_domain(
297 "cookie1=value1; Domain=",
298 "http://example.com/foo/bar",
299 CookieDomain::host_only(&url).expect("unable to parse domain"),
300 );
301 }
302
303 #[test]
304 fn mismatched_domain() {
305 let ua = Cookie::parse(
306 "cookie1=value1; Domain=notmydomain.com",
307 &test_utils::url("http://example.com/foo/bar"),
308 );
309 assert!(ua.is_err(), "{:?}", ua);
310 }
311
312 #[test]
313 fn domains() {
314 fn domain_from(domain: &str, request_url: &str, is_some: bool) {
315 let cookie_str = format!("cookie1=value1; Domain={}", domain);
316 let raw_cookie = RawCookie::parse(cookie_str).unwrap();
317 let cookie = Cookie::try_from_raw_cookie(&raw_cookie, &test_utils::url(request_url));
318 assert_eq!(is_some, cookie.is_ok())
319 }
320 domain_from("example.com", "http://foo.example.com", true);
327 domain_from(".example.com", "http://foo.example.com", true);
328 domain_from("foo.example.com", "http://foo.example.com", true);
329 domain_from(".foo.example.com", "http://foo.example.com", true);
330
331 domain_from("oo.example.com", "http://foo.example.com", false);
332 domain_from("myexample.com", "http://foo.example.com", false);
333 domain_from("bar.example.com", "http://foo.example.com", false);
334 domain_from("baz.foo.example.com", "http://foo.example.com", false);
335 }
336
337 #[test]
338 fn httponly() {
339 let c = RawCookie::parse("cookie1=value1; HttpOnly").unwrap();
340 let url = Url::parse("ftp://example.com/foo/bar").unwrap();
341 let ua = Cookie::try_from_raw_cookie(&c, &url);
342 assert!(ua.is_err(), "{:?}", ua);
343 }
344
345 #[test]
346 fn identical_domain() {
347 cmp_domain(
348 "cookie1=value1; Domain=example.com",
349 "http://example.com/foo/bar",
350 CookieDomain::Suffix(String::from("example.com")),
351 );
352 }
353
354 #[test]
355 fn identical_domain_leading_dot() {
356 cmp_domain(
357 "cookie1=value1; Domain=.example.com",
358 "http://example.com/foo/bar",
359 CookieDomain::Suffix(String::from("example.com")),
360 );
361 }
362
363 #[test]
364 fn identical_domain_two_leading_dots() {
365 cmp_domain(
366 "cookie1=value1; Domain=..example.com",
367 "http://..example.com/foo/bar",
368 CookieDomain::Suffix(String::from(".example.com")),
369 );
370 }
371
372 #[test]
373 fn upper_case_domain() {
374 cmp_domain(
375 "cookie1=value1; Domain=EXAMPLE.com",
376 "http://example.com/foo/bar",
377 CookieDomain::Suffix(String::from("example.com")),
378 );
379 }
380
381 fn cmp_path(cookie: &str, url: &str, exp: &str) {
382 let ua = test_utils::make_cookie(cookie, url, None, None);
383 assert!(String::from(ua.path.clone()) == exp, "\n{:?}", ua);
384 }
385
386 #[test]
387 fn no_path() {
388 cmp_path("cookie1=value1", "http://example.com/foo/bar/", "/foo/bar");
390 cmp_path("cookie1=value1", "http://example.com/foo/bar", "/foo");
391 cmp_path("cookie1=value1", "http://example.com/foo", "/");
392 cmp_path("cookie1=value1", "http://example.com/", "/");
393 cmp_path("cookie1=value1", "http://example.com", "/");
394 }
395
396 #[test]
397 fn empty_path() {
398 cmp_path(
400 "cookie1=value1; Path=",
401 "http://example.com/foo/bar/",
402 "/foo/bar",
403 );
404 cmp_path(
405 "cookie1=value1; Path=",
406 "http://example.com/foo/bar",
407 "/foo",
408 );
409 cmp_path("cookie1=value1; Path=", "http://example.com/foo", "/");
410 cmp_path("cookie1=value1; Path=", "http://example.com/", "/");
411 cmp_path("cookie1=value1; Path=", "http://example.com", "/");
412 }
413
414 #[test]
415 fn invalid_path() {
416 cmp_path(
418 "cookie1=value1; Path=baz",
419 "http://example.com/foo/bar/",
420 "/foo/bar",
421 );
422 cmp_path(
423 "cookie1=value1; Path=baz",
424 "http://example.com/foo/bar",
425 "/foo",
426 );
427 cmp_path("cookie1=value1; Path=baz", "http://example.com/foo", "/");
428 cmp_path("cookie1=value1; Path=baz", "http://example.com/", "/");
429 cmp_path("cookie1=value1; Path=baz", "http://example.com", "/");
430 }
431
432 #[test]
433 fn path() {
434 cmp_path(
436 "cookie1=value1; Path=/baz",
437 "http://example.com/foo/bar/",
438 "/baz",
439 );
440 cmp_path(
443 "cookie1=value1; Path=/baz/",
444 "http://example.com/foo/bar/",
445 "/baz/",
446 );
447 }
448
449 #[inline]
451 fn in_days(days: i64) -> OffsetDateTime {
452 OffsetDateTime::now_utc() + Duration::days(days)
453 }
454 #[inline]
455 fn in_minutes(mins: i64) -> OffsetDateTime {
456 OffsetDateTime::now_utc() + Duration::minutes(mins)
457 }
458
459 #[test]
460 fn max_age_bounds() {
461 let ua = test_utils::make_cookie(
462 "cookie1=value1",
463 "http://example.com/foo/bar",
464 None,
465 Some(9223372036854776),
466 );
467 assert!(match ua.expires {
468 CookieExpiration::AtUtc(_) => true,
469 _ => false,
470 });
471 }
472
473 #[test]
474 fn max_age() {
475 let ua = test_utils::make_cookie(
476 "cookie1=value1",
477 "http://example.com/foo/bar",
478 None,
479 Some(60),
480 );
481 assert!(!ua.is_expired());
482 assert!(ua.expires_by(&in_minutes(2)));
483 }
484
485 #[test]
486 fn expired() {
487 let ua = test_utils::make_cookie(
488 "cookie1=value1",
489 "http://example.com/foo/bar",
490 None,
491 Some(0u64),
492 );
493 assert!(ua.is_expired());
494 assert!(ua.expires_by(&in_days(-1)));
495 let ua = test_utils::make_cookie(
496 "cookie1=value1; Max-Age=0",
497 "http://example.com/foo/bar",
498 None,
499 None,
500 );
501 assert!(ua.is_expired());
502 assert!(ua.expires_by(&in_days(-1)));
503 let ua = test_utils::make_cookie(
504 "cookie1=value1; Max-Age=-1",
505 "http://example.com/foo/bar",
506 None,
507 None,
508 );
509 assert!(ua.is_expired());
510 assert!(ua.expires_by(&in_days(-1)));
511 }
512
513 #[test]
514 fn session_end() {
515 let ua =
516 test_utils::make_cookie("cookie1=value1", "http://example.com/foo/bar", None, None);
517 assert!(match ua.expires {
518 CookieExpiration::SessionEnd => true,
519 _ => false,
520 });
521 assert!(!ua.is_expired());
522 assert!(!ua.expires_by(&in_days(1)));
523 assert!(!ua.expires_by(&in_days(-1)));
524 }
525
526 #[test]
527 fn expires_tmrw_at_utc() {
528 let ua = test_utils::make_cookie(
529 "cookie1=value1",
530 "http://example.com/foo/bar",
531 Some(in_days(1)),
532 None,
533 );
534 assert!(!ua.is_expired());
535 assert!(ua.expires_by(&in_days(2)));
536 }
537
538 #[test]
539 fn expired_yest_at_utc() {
540 let ua = test_utils::make_cookie(
541 "cookie1=value1",
542 "http://example.com/foo/bar",
543 Some(in_days(-1)),
544 None,
545 );
546 assert!(ua.is_expired());
547 assert!(!ua.expires_by(&in_days(-2)));
548 }
549
550 #[test]
551 fn is_persistent() {
552 let ua =
553 test_utils::make_cookie("cookie1=value1", "http://example.com/foo/bar", None, None);
554 assert!(!ua.is_persistent()); let ua = test_utils::make_cookie(
556 "cookie1=value1",
557 "http://example.com/foo/bar",
558 Some(in_days(1)),
559 None,
560 );
561 assert!(ua.is_persistent()); let ua = test_utils::make_cookie(
563 "cookie1=value1",
564 "http://example.com/foo/bar",
565 Some(in_days(1)),
566 Some(60),
567 );
568 assert!(ua.is_persistent()); }
570
571 #[test]
572 fn max_age_overrides_expires() {
573 let ua = test_utils::make_cookie(
576 "cookie1=value1",
577 "http://example.com/foo/bar",
578 Some(in_days(-1)),
579 Some(60),
580 );
581 assert!(!ua.is_expired());
582 assert!(ua.expires_by(&in_minutes(2)));
583 }
584
585 #[test]
594 fn matches() {
595 fn do_match(exp: bool, cookie: &str, src_url: &str, request_url: Option<&str>) {
596 let ua = test_utils::make_cookie(cookie, src_url, None, None);
597 let request_url = request_url.unwrap_or(src_url);
598 assert!(
599 exp == ua.matches(&Url::parse(request_url).unwrap()),
600 "\n>> {:?}\nshould{}match\n>> {:?}\n",
601 ua,
602 if exp { " " } else { " NOT " },
603 request_url
604 );
605 }
606 fn is_match(cookie: &str, url: &str, request_url: Option<&str>) {
607 do_match(true, cookie, url, request_url);
608 }
609 fn is_mismatch(cookie: &str, url: &str, request_url: Option<&str>) {
610 do_match(false, cookie, url, request_url);
611 }
612
613 is_match("cookie1=value1", "http://example.com/foo/bar", None);
615 is_mismatch(
617 "cookie1=value1",
618 "http://example.com/bus/baz/",
619 Some("http://example.com/foo/bar"),
620 );
621 is_mismatch(
622 "cookie1=value1; Path=/bus/baz",
623 "http://example.com/foo/bar",
624 None,
625 );
626 is_match(
629 "cookie1=value1",
630 "http://example.com/foo/bar",
631 Some("http://example.com/foo/bar"),
632 );
633 is_match(
634 "cookie1=value1; Path=/foo/",
635 "http://example.com/foo/bar",
636 None,
637 );
638 is_mismatch(
642 "cookie1=value1",
643 "http://example.com/fo/",
644 Some("http://example.com/foo/bar"),
645 );
646 is_mismatch(
647 "cookie1=value1; Path=/fo",
648 "http://example.com/foo/bar",
649 None,
650 );
651 is_match(
655 "cookie1=value1",
656 "http://example.com/foo/",
657 Some("http://example.com/foo/bar"),
658 );
659 is_match(
660 "cookie1=value1; Path=/foo",
661 "http://example.com/foo/bar",
662 None,
663 );
664 is_match(
666 "cookie1=value1; Path=/",
667 "http://example.com/foo/bar",
668 Some("http://example.com/bus/baz"),
669 );
670 is_mismatch(
672 "cookie1=value1",
673 "http://example.com/foo/",
674 Some("http://notmydomain.com/foo/bar"),
675 );
676 is_mismatch(
677 "cookie1=value1; Domain=example.com",
678 "http://foo.example.com/foo/",
679 Some("http://notmydomain.com/foo/bar"),
680 );
681 is_match(
683 "cookie1=value1; Secure",
684 "http://example.com/foo/bar",
685 Some("https://example.com/foo/bar"),
686 );
687 is_mismatch(
689 "cookie1=value1; Secure",
690 "http://example.com/foo/bar",
691 Some("http://example.com/foo/bar"),
692 );
693 is_match(
695 "cookie1=value1",
696 "http://example.com/foo/bar",
697 Some("ftp://example.com/foo/bar"),
698 );
699 is_match(
701 "cookie1=value1; HttpOnly",
702 "http://example.com/foo/bar",
703 Some("http://example.com/foo/bar"),
704 );
705 is_match(
706 "cookie1=value1; HttpOnly",
707 "http://example.com/foo/bar",
708 Some("HTTP://example.com/foo/bar"),
709 );
710 is_match(
711 "cookie1=value1; HttpOnly",
712 "http://example.com/foo/bar",
713 Some("https://example.com/foo/bar"),
714 );
715 is_mismatch(
717 "cookie1=value1; HttpOnly",
718 "http://example.com/foo/bar",
719 Some("ftp://example.com/foo/bar"),
720 );
721 is_mismatch(
722 "cookie1=value1; HttpOnly",
723 "http://example.com/foo/bar",
724 Some("data:nonrelativescheme"),
725 );
726 }
727}
728
729#[cfg(all(test, feature = "serde_json"))]
730mod serde_json_tests {
731 use crate::cookie::Cookie;
732 use crate::cookie_expiration::CookieExpiration;
733 use crate::utils::test as test_utils;
734 use crate::utils::test::*;
735 use serde_json::json;
736 use time;
737
738 fn encode_decode(c: &Cookie<'_>, expected: serde_json::Value) {
739 let encoded = serde_json::to_value(c).unwrap();
740 assert_eq!(
741 expected,
742 encoded,
743 "\nexpected: '{}'\n encoded: '{}'",
744 expected.to_string(),
745 encoded.to_string()
746 );
747 let decoded: Cookie<'_> = serde_json::from_value(encoded).unwrap();
748 assert_eq!(
749 *c,
750 decoded,
751 "\nexpected: '{}'\n decoded: '{}'",
752 c.to_string(),
753 decoded.to_string()
754 );
755 }
756
757 #[test]
758 fn serde() {
759 encode_decode(
760 &test_utils::make_cookie("cookie1=value1", "http://example.com/foo/bar", None, None),
761 json!({
762 "raw_cookie": "cookie1=value1",
763 "path": ["/foo", false],
764 "domain": { "HostOnly": "example.com" },
765 "expires": "SessionEnd"
766 }),
767 );
768
769 encode_decode(
770 &test_utils::make_cookie(
771 "cookie2=value2; Domain=example.com",
772 "http://foo.example.com/foo/bar",
773 None,
774 None,
775 ),
776 json!({
777 "raw_cookie": "cookie2=value2; Domain=example.com",
778 "path": ["/foo", false],
779 "domain": { "Suffix": "example.com" },
780 "expires": "SessionEnd"
781 }),
782 );
783
784 encode_decode(
785 &test_utils::make_cookie(
786 "cookie3=value3; Path=/foo/bar",
787 "http://foo.example.com/foo",
788 None,
789 None,
790 ),
791 json!({
792 "raw_cookie": "cookie3=value3; Path=/foo/bar",
793 "path": ["/foo/bar", true],
794 "domain": { "HostOnly": "foo.example.com" },
795 "expires": "SessionEnd",
796 }),
797 );
798
799 let at_utc = time::macros::date!(2015 - 08 - 11)
800 .with_time(time::macros::time!(16:41:42))
801 .assume_utc();
802 encode_decode(
803 &test_utils::make_cookie(
804 "cookie4=value4",
805 "http://example.com/foo/bar",
806 Some(at_utc),
807 None,
808 ),
809 json!({
810 "raw_cookie": "cookie4=value4; Expires=Tue, 11 Aug 2015 16:41:42 GMT",
811 "path": ["/foo", false],
812 "domain": { "HostOnly": "example.com" },
813 "expires": { "AtUtc": at_utc.format(crate::rfc3339_fmt::RFC3339_FORMAT).unwrap().to_string() },
814 }),
815 );
816
817 let expires = test_utils::make_cookie(
818 "cookie5=value5",
819 "http://example.com/foo/bar",
820 Some(in_minutes(10)),
821 None,
822 );
823 let utc_tm = match expires.expires {
824 CookieExpiration::AtUtc(ref utc_tm) => utc_tm,
825 CookieExpiration::SessionEnd => unreachable!(),
826 };
827
828 let utc_formatted = utc_tm
829 .format(&time::format_description::well_known::Rfc2822)
830 .unwrap()
831 .to_string()
832 .replace("+0000", "GMT");
833 let raw_cookie_value = format!("cookie5=value5; Expires={utc_formatted}");
834
835 encode_decode(
836 &expires,
837 json!({
838 "raw_cookie": raw_cookie_value,
839 "path":["/foo", false],
840 "domain": { "HostOnly": "example.com" },
841 "expires": { "AtUtc": utc_tm.format(crate::rfc3339_fmt::RFC3339_FORMAT).unwrap().to_string() },
842 }),
843 );
844 dbg!(&at_utc);
845 let max_age = test_utils::make_cookie(
846 "cookie6=value6",
847 "http://example.com/foo/bar",
848 Some(at_utc),
849 Some(10),
850 );
851 dbg!(&max_age);
852 let utc_tm = match max_age.expires {
853 CookieExpiration::AtUtc(ref utc_tm) => time::OffsetDateTime::parse(
854 &utc_tm.format(crate::rfc3339_fmt::RFC3339_FORMAT).unwrap(),
855 &time::format_description::well_known::Rfc3339,
856 )
857 .expect("could not re-parse time"),
858 CookieExpiration::SessionEnd => unreachable!(),
859 };
860 dbg!(&utc_tm);
861 encode_decode(
862 &max_age,
863 json!({
864 "raw_cookie": "cookie6=value6; Max-Age=10; Expires=Tue, 11 Aug 2015 16:41:42 GMT",
865 "path":["/foo", false],
866 "domain": { "HostOnly": "example.com" },
867 "expires": { "AtUtc": utc_tm.format(crate::rfc3339_fmt::RFC3339_FORMAT).unwrap().to_string() },
868 }),
869 );
870
871 let max_age = test_utils::make_cookie(
872 "cookie7=value7",
873 "http://example.com/foo/bar",
874 None,
875 Some(10),
876 );
877 let utc_tm = match max_age.expires {
878 CookieExpiration::AtUtc(ref utc_tm) => utc_tm,
879 CookieExpiration::SessionEnd => unreachable!(),
880 };
881 encode_decode(
882 &max_age,
883 json!({
884 "raw_cookie": "cookie7=value7; Max-Age=10",
885 "path":["/foo", false],
886 "domain": { "HostOnly": "example.com" },
887 "expires": { "AtUtc": utc_tm.format(crate::rfc3339_fmt::RFC3339_FORMAT).unwrap().to_string() },
888 }),
889 );
890 }
891}