cookie_store/
cookie_expiration.rs

1use std;
2
3#[cfg(feature = "serde")]
4use serde_derive::{Deserialize, Serialize};
5use time::{self, OffsetDateTime};
6
7/// When a given `Cookie` expires
8#[derive(Eq, Clone, Debug)]
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10pub enum CookieExpiration {
11    /// `Cookie` expires at the given UTC time, as set from either the Max-Age
12    /// or Expires attribute of a Set-Cookie header
13    #[cfg_attr(feature = "serde", serde(with = "crate::rfc3339_fmt"))]
14    AtUtc(OffsetDateTime),
15    /// `Cookie` expires at the end of the current `Session`; this means the cookie
16    /// is not persistent
17    SessionEnd,
18}
19
20// We directly impl `PartialEq` as the cookie Expires attribute does not include nanosecond precision
21impl std::cmp::PartialEq for CookieExpiration {
22    fn eq(&self, other: &Self) -> bool {
23        match (self, other) {
24            (CookieExpiration::SessionEnd, CookieExpiration::SessionEnd) => true,
25            (CookieExpiration::AtUtc(this_offset), CookieExpiration::AtUtc(other_offset)) => {
26                // All instances should already be UTC offset
27                this_offset.date() == other_offset.date()
28                    && this_offset.time().hour() == other_offset.time().hour()
29                    && this_offset.time().minute() == other_offset.time().minute()
30                    && this_offset.time().second() == other_offset.time().second()
31            }
32            _ => false,
33        }
34    }
35}
36
37impl CookieExpiration {
38    /// Indicates if the `Cookie` is expired as of *now*.
39    pub fn is_expired(&self) -> bool {
40        self.expires_by(&time::OffsetDateTime::now_utc())
41    }
42
43    /// Indicates if the `Cookie` expires as of `utc_tm`.
44    pub fn expires_by(&self, utc_tm: &time::OffsetDateTime) -> bool {
45        match *self {
46            CookieExpiration::AtUtc(ref expire_tm) => *expire_tm <= *utc_tm,
47            CookieExpiration::SessionEnd => false,
48        }
49    }
50}
51
52const MAX_RFC3339: time::OffsetDateTime = time::macros::date!(9999 - 12 - 31)
53    .with_time(time::macros::time!(23:59:59))
54    .assume_utc();
55impl From<u64> for CookieExpiration {
56    fn from(max_age: u64) -> CookieExpiration {
57        // make sure we don't trigger a panic! in Duration by restricting the seconds
58        // to the max
59        CookieExpiration::from(time::Duration::seconds(std::cmp::min(
60            time::Duration::MAX.whole_seconds() as u64,
61            max_age,
62        ) as i64))
63    }
64}
65
66impl From<time::OffsetDateTime> for CookieExpiration {
67    fn from(utc_tm: OffsetDateTime) -> CookieExpiration {
68        CookieExpiration::AtUtc(utc_tm.min(MAX_RFC3339))
69    }
70}
71
72impl From<cookie::Expiration> for CookieExpiration {
73    fn from(expiration: cookie::Expiration) -> CookieExpiration {
74        match expiration {
75            cookie::Expiration::DateTime(offset) => CookieExpiration::AtUtc(offset),
76            cookie::Expiration::Session => CookieExpiration::SessionEnd,
77        }
78    }
79}
80
81impl From<time::Duration> for CookieExpiration {
82    fn from(duration: time::Duration) -> Self {
83        // If delta-seconds is less than or equal to zero (0), let expiry-time
84        //    be the earliest representable date and time.  Otherwise, let the
85        //    expiry-time be the current date and time plus delta-seconds seconds.
86        let utc_tm = if duration.is_zero() {
87            time::OffsetDateTime::UNIX_EPOCH
88        } else {
89            let now_utc = time::OffsetDateTime::now_utc();
90            let d = (MAX_RFC3339 - now_utc).min(duration);
91            now_utc + d
92        };
93        CookieExpiration::from(utc_tm)
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::CookieExpiration;
100    use time;
101
102    use crate::utils::test::*;
103
104    #[test]
105    fn max_age_bounds() {
106        match CookieExpiration::from(time::Duration::MAX.whole_seconds() as u64 + 1) {
107            CookieExpiration::AtUtc(_) => assert!(true),
108            _ => assert!(false),
109        }
110    }
111
112    #[test]
113    fn expired() {
114        let ma = CookieExpiration::from(0u64); // Max-Age<=0 indicates the cookie is expired
115        assert!(ma.is_expired());
116        assert!(ma.expires_by(&in_days(-1)));
117    }
118
119    #[test]
120    fn max_age() {
121        let ma = CookieExpiration::from(60u64);
122        assert!(!ma.is_expired());
123        assert!(ma.expires_by(&in_minutes(2)));
124    }
125
126    #[test]
127    fn session_end() {
128        // SessionEnd never "expires"; lives until end of session
129        let se = CookieExpiration::SessionEnd;
130        assert!(!se.is_expired());
131        assert!(!se.expires_by(&in_days(1)));
132        assert!(!se.expires_by(&in_days(-1)));
133    }
134
135    #[test]
136    fn at_utc() {
137        {
138            let expire_tmrw = CookieExpiration::from(in_days(1));
139            assert!(!expire_tmrw.is_expired());
140            assert!(expire_tmrw.expires_by(&in_days(2)));
141        }
142        {
143            let expired_yest = CookieExpiration::from(in_days(-1));
144            assert!(expired_yest.is_expired());
145            assert!(!expired_yest.expires_by(&in_days(-2)));
146        }
147    }
148}