1 use std::cmp;
2 use std::fmt::{self, Display, Formatter};
3 use std::num::ParseIntError;
4 use std::str::{from_utf8_unchecked, FromStr};
5 use std::time::{Duration, SystemTime, UNIX_EPOCH};
6 
7 use crate::Error;
8 
9 /// HTTP timestamp type.
10 ///
11 /// Parse using `FromStr` impl.
12 /// Format using the `Display` trait.
13 /// Convert timestamp into/from `SytemTime` to use.
14 /// Supports comparsion and sorting.
15 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
16 pub struct HttpDate {
17     /// 0...59
18     sec: u8,
19     /// 0...59
20     min: u8,
21     /// 0...23
22     hour: u8,
23     /// 1...31
24     day: u8,
25     /// 1...12
26     mon: u8,
27     /// 1970...9999
28     year: u16,
29     /// 1...7
30     wday: u8,
31 }
32 
33 impl HttpDate {
is_valid(&self) -> bool34     fn is_valid(&self) -> bool {
35         self.sec < 60
36             && self.min < 60
37             && self.hour < 24
38             && self.day > 0
39             && self.day < 32
40             && self.mon > 0
41             && self.mon <= 12
42             && self.year >= 1970
43             && self.year <= 9999
44             && &HttpDate::from(SystemTime::from(*self)) == self
45     }
46 }
47 
48 impl From<SystemTime> for HttpDate {
from(v: SystemTime) -> HttpDate49     fn from(v: SystemTime) -> HttpDate {
50         let dur = v
51             .duration_since(UNIX_EPOCH)
52             .expect("all times should be after the epoch");
53         let secs_since_epoch = dur.as_secs();
54 
55         if secs_since_epoch >= 253402300800 {
56             // year 9999
57             panic!("date must be before year 9999");
58         }
59 
60         /* 2000-03-01 (mod 400 year, immediately after feb29 */
61         const LEAPOCH: i64 = 11017;
62         const DAYS_PER_400Y: i64 = 365 * 400 + 97;
63         const DAYS_PER_100Y: i64 = 365 * 100 + 24;
64         const DAYS_PER_4Y: i64 = 365 * 4 + 1;
65 
66         let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
67         let secs_of_day = secs_since_epoch % 86400;
68 
69         let mut qc_cycles = days / DAYS_PER_400Y;
70         let mut remdays = days % DAYS_PER_400Y;
71 
72         if remdays < 0 {
73             remdays += DAYS_PER_400Y;
74             qc_cycles -= 1;
75         }
76 
77         let mut c_cycles = remdays / DAYS_PER_100Y;
78         if c_cycles == 4 {
79             c_cycles -= 1;
80         }
81         remdays -= c_cycles * DAYS_PER_100Y;
82 
83         let mut q_cycles = remdays / DAYS_PER_4Y;
84         if q_cycles == 25 {
85             q_cycles -= 1;
86         }
87         remdays -= q_cycles * DAYS_PER_4Y;
88 
89         let mut remyears = remdays / 365;
90         if remyears == 4 {
91             remyears -= 1;
92         }
93         remdays -= remyears * 365;
94 
95         let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
96 
97         let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
98         let mut mon = 0;
99         for mon_len in months.iter() {
100             mon += 1;
101             if remdays < *mon_len {
102                 break;
103             }
104             remdays -= *mon_len;
105         }
106         let mday = remdays + 1;
107         let mon = if mon + 2 > 12 {
108             year += 1;
109             mon - 10
110         } else {
111             mon + 2
112         };
113 
114         let mut wday = (3 + days) % 7;
115         if wday <= 0 {
116             wday += 7
117         };
118 
119         HttpDate {
120             sec: (secs_of_day % 60) as u8,
121             min: ((secs_of_day % 3600) / 60) as u8,
122             hour: (secs_of_day / 3600) as u8,
123             day: mday as u8,
124             mon: mon as u8,
125             year: year as u16,
126             wday: wday as u8,
127         }
128     }
129 }
130 
131 impl From<HttpDate> for SystemTime {
from(v: HttpDate) -> SystemTime132     fn from(v: HttpDate) -> SystemTime {
133         let leap_years =
134             ((v.year - 1) - 1968) / 4 - ((v.year - 1) - 1900) / 100 + ((v.year - 1) - 1600) / 400;
135         let mut ydays = match v.mon {
136             1 => 0,
137             2 => 31,
138             3 => 59,
139             4 => 90,
140             5 => 120,
141             6 => 151,
142             7 => 181,
143             8 => 212,
144             9 => 243,
145             10 => 273,
146             11 => 304,
147             12 => 334,
148             _ => unreachable!(),
149         } + v.day as u64
150             - 1;
151         if is_leap_year(v.year) && v.mon > 2 {
152             ydays += 1;
153         }
154         let days = (v.year as u64 - 1970) * 365 + leap_years as u64 + ydays;
155         UNIX_EPOCH
156             + Duration::from_secs(
157                 v.sec as u64 + v.min as u64 * 60 + v.hour as u64 * 3600 + days * 86400,
158             )
159     }
160 }
161 
162 impl FromStr for HttpDate {
163     type Err = Error;
164 
from_str(s: &str) -> Result<HttpDate, Error>165     fn from_str(s: &str) -> Result<HttpDate, Error> {
166         if !s.is_ascii() {
167             return Err(Error(()));
168         }
169         let x = s.trim().as_bytes();
170         let date = parse_imf_fixdate(x)
171             .or_else(|_| parse_rfc850_date(x))
172             .or_else(|_| parse_asctime(x))?;
173         if !date.is_valid() {
174             return Err(Error(()));
175         }
176         Ok(date)
177     }
178 }
179 
180 impl Display for HttpDate {
fmt(&self, f: &mut Formatter) -> fmt::Result181     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
182         let wday = match self.wday {
183             1 => b"Mon",
184             2 => b"Tue",
185             3 => b"Wed",
186             4 => b"Thu",
187             5 => b"Fri",
188             6 => b"Sat",
189             7 => b"Sun",
190             _ => unreachable!(),
191         };
192         let mon = match self.mon {
193             1 => b"Jan",
194             2 => b"Feb",
195             3 => b"Mar",
196             4 => b"Apr",
197             5 => b"May",
198             6 => b"Jun",
199             7 => b"Jul",
200             8 => b"Aug",
201             9 => b"Sep",
202             10 => b"Oct",
203             11 => b"Nov",
204             12 => b"Dec",
205             _ => unreachable!(),
206         };
207         let mut buf: [u8; 29] = [
208             // Too long to write as: b"Thu, 01 Jan 1970 00:00:00 GMT"
209             b' ', b' ', b' ', b',', b' ', b'0', b'0', b' ', b' ', b' ', b' ', b' ', b'0', b'0',
210             b'0', b'0', b' ', b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b'G', b'M',
211             b'T',
212         ];
213         buf[0] = wday[0];
214         buf[1] = wday[1];
215         buf[2] = wday[2];
216         buf[5] = b'0' + (self.day / 10) as u8;
217         buf[6] = b'0' + (self.day % 10) as u8;
218         buf[8] = mon[0];
219         buf[9] = mon[1];
220         buf[10] = mon[2];
221         buf[12] = b'0' + (self.year / 1000) as u8;
222         buf[13] = b'0' + (self.year / 100 % 10) as u8;
223         buf[14] = b'0' + (self.year / 10 % 10) as u8;
224         buf[15] = b'0' + (self.year % 10) as u8;
225         buf[17] = b'0' + (self.hour / 10) as u8;
226         buf[18] = b'0' + (self.hour % 10) as u8;
227         buf[20] = b'0' + (self.min / 10) as u8;
228         buf[21] = b'0' + (self.min % 10) as u8;
229         buf[23] = b'0' + (self.sec / 10) as u8;
230         buf[24] = b'0' + (self.sec % 10) as u8;
231         f.write_str(unsafe { from_utf8_unchecked(&buf[..]) })
232     }
233 }
234 
235 impl Ord for HttpDate {
cmp(&self, other: &HttpDate) -> cmp::Ordering236     fn cmp(&self, other: &HttpDate) -> cmp::Ordering {
237         SystemTime::from(*self).cmp(&SystemTime::from(*other))
238     }
239 }
240 
241 impl PartialOrd for HttpDate {
partial_cmp(&self, other: &HttpDate) -> Option<cmp::Ordering>242     fn partial_cmp(&self, other: &HttpDate) -> Option<cmp::Ordering> {
243         Some(self.cmp(other))
244     }
245 }
246 
247 /// Convert &[u8] to &str with zero checks.
248 ///
249 /// For internal use only.
250 /// Intended to be used with ASCII-only strings.
conv(s: &[u8]) -> &str251 fn conv(s: &[u8]) -> &str {
252     unsafe { from_utf8_unchecked(s) }
253 }
254 
cvt_err(_: ParseIntError) -> Error255 fn cvt_err(_: ParseIntError) -> Error {
256     Error(())
257 }
parse_imf_fixdate(s: &[u8]) -> Result<HttpDate, Error>258 fn parse_imf_fixdate(s: &[u8]) -> Result<HttpDate, Error> {
259     // Example: `Sun, 06 Nov 1994 08:49:37 GMT`
260     if s.len() != 29 || &s[25..] != b" GMT" || s[16] != b' ' || s[19] != b':' || s[22] != b':' {
261         return Err(Error(()));
262     }
263     Ok(HttpDate {
264         sec: conv(&s[23..25]).parse().map_err(cvt_err)?,
265         min: conv(&s[20..22]).parse().map_err(cvt_err)?,
266         hour: conv(&s[17..19]).parse().map_err(cvt_err)?,
267         day: conv(&s[5..7]).parse().map_err(cvt_err)?,
268         mon: match &s[7..12] {
269             b" Jan " => 1,
270             b" Feb " => 2,
271             b" Mar " => 3,
272             b" Apr " => 4,
273             b" May " => 5,
274             b" Jun " => 6,
275             b" Jul " => 7,
276             b" Aug " => 8,
277             b" Sep " => 9,
278             b" Oct " => 10,
279             b" Nov " => 11,
280             b" Dec " => 12,
281             _ => return Err(Error(())),
282         },
283         year: conv(&s[12..16]).parse().map_err(cvt_err)?,
284         wday: match &s[..5] {
285             b"Mon, " => 1,
286             b"Tue, " => 2,
287             b"Wed, " => 3,
288             b"Thu, " => 4,
289             b"Fri, " => 5,
290             b"Sat, " => 6,
291             b"Sun, " => 7,
292             _ => return Err(Error(())),
293         },
294     })
295 }
296 
parse_rfc850_date(s: &[u8]) -> Result<HttpDate, Error>297 fn parse_rfc850_date(s: &[u8]) -> Result<HttpDate, Error> {
298     // Example: `Sunday, 06-Nov-94 08:49:37 GMT`
299     if s.len() < 23 {
300         return Err(Error(()));
301     }
302 
303     fn wday<'a>(s: &'a [u8], wday: u8, name: &'static [u8]) -> Option<(u8, &'a [u8])> {
304         if &s[0..name.len()] == name {
305             return Some((wday, &s[name.len()..]));
306         }
307         None
308     }
309     let (wday, s) = wday(s, 1, b"Monday, ")
310         .or_else(|| wday(s, 2, b"Tuesday, "))
311         .or_else(|| wday(s, 3, b"Wednesday, "))
312         .or_else(|| wday(s, 4, b"Thursday, "))
313         .or_else(|| wday(s, 5, b"Friday, "))
314         .or_else(|| wday(s, 6, b"Saturday, "))
315         .or_else(|| wday(s, 7, b"Sunday, "))
316         .ok_or(Error(()))?;
317     if s.len() != 22 || s[12] != b':' || s[15] != b':' || &s[18..22] != b" GMT" {
318         return Err(Error(()));
319     }
320     let mut year = conv(&s[7..9]).parse::<u16>().map_err(cvt_err)?;
321     if year < 70 {
322         year += 2000;
323     } else {
324         year += 1900;
325     }
326     Ok(HttpDate {
327         sec: conv(&s[16..18]).parse().map_err(cvt_err)?,
328         min: conv(&s[13..15]).parse().map_err(cvt_err)?,
329         hour: conv(&s[10..12]).parse().map_err(cvt_err)?,
330         day: conv(&s[0..2]).parse().map_err(cvt_err)?,
331         mon: match &s[2..7] {
332             b"-Jan-" => 1,
333             b"-Feb-" => 2,
334             b"-Mar-" => 3,
335             b"-Apr-" => 4,
336             b"-May-" => 5,
337             b"-Jun-" => 6,
338             b"-Jul-" => 7,
339             b"-Aug-" => 8,
340             b"-Sep-" => 9,
341             b"-Oct-" => 10,
342             b"-Nov-" => 11,
343             b"-Dec-" => 12,
344             _ => return Err(Error(())),
345         },
346         year,
347         wday,
348     })
349 }
350 
parse_asctime(s: &[u8]) -> Result<HttpDate, Error>351 fn parse_asctime(s: &[u8]) -> Result<HttpDate, Error> {
352     // Example: `Sun Nov  6 08:49:37 1994`
353     if s.len() != 24 || s[10] != b' ' || s[13] != b':' || s[16] != b':' || s[19] != b' ' {
354         return Err(Error(()));
355     }
356     Ok(HttpDate {
357         sec: conv(&s[17..19]).parse().map_err(cvt_err)?,
358         min: conv(&s[14..16]).parse().map_err(cvt_err)?,
359         hour: conv(&s[11..13]).parse().map_err(cvt_err)?,
360         day: {
361             let x = &s[8..10];
362             conv(if x[0] == b' ' { &x[1..2] } else { x })
363                 .parse()
364                 .map_err(cvt_err)?
365         },
366         mon: match &s[4..8] {
367             b"Jan " => 1,
368             b"Feb " => 2,
369             b"Mar " => 3,
370             b"Apr " => 4,
371             b"May " => 5,
372             b"Jun " => 6,
373             b"Jul " => 7,
374             b"Aug " => 8,
375             b"Sep " => 9,
376             b"Oct " => 10,
377             b"Nov " => 11,
378             b"Dec " => 12,
379             _ => return Err(Error(())),
380         },
381         year: conv(&s[20..24]).parse().map_err(cvt_err)?,
382         wday: match &s[0..4] {
383             b"Mon " => 1,
384             b"Tue " => 2,
385             b"Wed " => 3,
386             b"Thu " => 4,
387             b"Fri " => 5,
388             b"Sat " => 6,
389             b"Sun " => 7,
390             _ => return Err(Error(())),
391         },
392     })
393 }
394 
is_leap_year(y: u16) -> bool395 fn is_leap_year(y: u16) -> bool {
396     y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
397 }
398