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