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