actori_http/header/shared/
httpdate.rs

1use std::fmt::{self, Display};
2use std::io::Write;
3use std::str::FromStr;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use bytes::{buf::BufMutExt, BytesMut};
7use http::header::{HeaderValue, InvalidHeaderValue};
8
9use crate::error::ParseError;
10use crate::header::IntoHeaderValue;
11
12/// A timestamp with HTTP formatting and parsing
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub struct HttpDate(time::Tm);
15
16impl FromStr for HttpDate {
17    type Err = ParseError;
18
19    fn from_str(s: &str) -> Result<HttpDate, ParseError> {
20        match time::strptime(s, "%a, %d %b %Y %T %Z")
21            .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z"))
22            .or_else(|_| time::strptime(s, "%c"))
23        {
24            Ok(t) => Ok(HttpDate(t)),
25            Err(_) => Err(ParseError::Header),
26        }
27    }
28}
29
30impl Display for HttpDate {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
33    }
34}
35
36impl From<time::Tm> for HttpDate {
37    fn from(tm: time::Tm) -> HttpDate {
38        HttpDate(tm)
39    }
40}
41
42impl From<SystemTime> for HttpDate {
43    fn from(sys: SystemTime) -> HttpDate {
44        let tmspec = match sys.duration_since(UNIX_EPOCH) {
45            Ok(dur) => {
46                time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
47            }
48            Err(err) => {
49                let neg = err.duration();
50                time::Timespec::new(
51                    -(neg.as_secs() as i64),
52                    -(neg.subsec_nanos() as i32),
53                )
54            }
55        };
56        HttpDate(time::at_utc(tmspec))
57    }
58}
59
60impl IntoHeaderValue for HttpDate {
61    type Error = InvalidHeaderValue;
62
63    fn try_into(self) -> Result<HeaderValue, Self::Error> {
64        let mut wrt = BytesMut::with_capacity(29).writer();
65        write!(wrt, "{}", self.0.rfc822()).unwrap();
66        HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
67    }
68}
69
70impl From<HttpDate> for SystemTime {
71    fn from(date: HttpDate) -> SystemTime {
72        let spec = date.0.to_timespec();
73        if spec.sec >= 0 {
74            UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
75        } else {
76            UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::HttpDate;
84    use time::Tm;
85
86    const NOV_07: HttpDate = HttpDate(Tm {
87        tm_nsec: 0,
88        tm_sec: 37,
89        tm_min: 48,
90        tm_hour: 8,
91        tm_mday: 7,
92        tm_mon: 10,
93        tm_year: 94,
94        tm_wday: 0,
95        tm_isdst: 0,
96        tm_yday: 0,
97        tm_utcoff: 0,
98    });
99
100    #[test]
101    fn test_date() {
102        assert_eq!(
103            "Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
104            NOV_07
105        );
106        assert_eq!(
107            "Sunday, 07-Nov-94 08:48:37 GMT"
108                .parse::<HttpDate>()
109                .unwrap(),
110            NOV_07
111        );
112        assert_eq!(
113            "Sun Nov  7 08:48:37 1994".parse::<HttpDate>().unwrap(),
114            NOV_07
115        );
116        assert!("this-is-no-date".parse::<HttpDate>().is_err());
117    }
118}