1use std::fmt::{self, Display, Formatter};
2use std::str::{from_utf8, FromStr};
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use crate::StatusCode;
6use crate::{bail, ensure, format_err};
7
8const IMF_FIXDATE_LENGTH: usize = 29;
9const RFC850_MAX_LENGTH: usize = 23;
10const ASCTIME_LENGTH: usize = 24;
11
12const YEAR_9999_SECONDS: u64 = 253402300800;
13const SECONDS_IN_DAY: u64 = 86400;
14const SECONDS_IN_HOUR: u64 = 3600;
15
16#[derive(Copy, Clone, Debug, Eq)]
20pub(crate) struct HttpDate {
21 second: u8,
23 minute: u8,
25 hour: u8,
27 day: u8,
29 month: u8,
31 year: u16,
33 week_day: u8,
35}
36
37pub(crate) fn parse_http_date(s: &str) -> crate::Result<SystemTime> {
43 s.parse::<HttpDate>().map(|d| d.into()).map_err(|mut e| {
44 e.set_status(StatusCode::BadRequest);
45 e
46 })
47}
48
49pub(crate) fn fmt_http_date(d: SystemTime) -> String {
53 format!("{}", HttpDate::from(d))
54}
55
56impl HttpDate {
57 fn is_valid(self) -> bool {
58 self.second < 60
59 && self.minute < 60
60 && self.hour < 24
61 && self.day > 0
62 && self.day < 32
63 && self.month > 0
64 && self.month <= 12
65 && self.year >= 1970
66 && self.year <= 9999
67 && self.week_day >= 1
68 && self.week_day < 8
69 }
70}
71
72fn parse_imf_fixdate(s: &[u8]) -> crate::Result<HttpDate> {
73 if s.len() != IMF_FIXDATE_LENGTH
75 || &s[25..] != b" GMT"
76 || s[16] != b' '
77 || s[19] != b':'
78 || s[22] != b':'
79 {
80 bail!("Date time not in imf fixdate format");
81 }
82 Ok(HttpDate {
83 second: from_utf8(&s[23..25])?.parse()?,
84 minute: from_utf8(&s[20..22])?.parse()?,
85 hour: from_utf8(&s[17..19])?.parse()?,
86 day: from_utf8(&s[5..7])?.parse()?,
87 month: match &s[7..12] {
88 b" Jan " => 1,
89 b" Feb " => 2,
90 b" Mar " => 3,
91 b" Apr " => 4,
92 b" May " => 5,
93 b" Jun " => 6,
94 b" Jul " => 7,
95 b" Aug " => 8,
96 b" Sep " => 9,
97 b" Oct " => 10,
98 b" Nov " => 11,
99 b" Dec " => 12,
100 _ => bail!("Invalid Month"),
101 },
102 year: from_utf8(&s[12..16])?.parse()?,
103 week_day: match &s[..5] {
104 b"Mon, " => 1,
105 b"Tue, " => 2,
106 b"Wed, " => 3,
107 b"Thu, " => 4,
108 b"Fri, " => 5,
109 b"Sat, " => 6,
110 b"Sun, " => 7,
111 _ => bail!("Invalid Day"),
112 },
113 })
114}
115
116fn parse_rfc850_date(s: &[u8]) -> crate::Result<HttpDate> {
117 ensure!(
119 s.len() >= RFC850_MAX_LENGTH,
120 "Date time not in rfc850 format"
121 );
122
123 fn week_day<'a>(s: &'a [u8], week_day: u8, name: &'static [u8]) -> Option<(u8, &'a [u8])> {
124 if &s[0..name.len()] == name {
125 return Some((week_day, &s[name.len()..]));
126 }
127 None
128 }
129 let (week_day, s) = week_day(s, 1, b"Monday, ")
130 .or_else(|| week_day(s, 2, b"Tuesday, "))
131 .or_else(|| week_day(s, 3, b"Wednesday, "))
132 .or_else(|| week_day(s, 4, b"Thursday, "))
133 .or_else(|| week_day(s, 5, b"Friday, "))
134 .or_else(|| week_day(s, 6, b"Saturday, "))
135 .or_else(|| week_day(s, 7, b"Sunday, "))
136 .ok_or_else(|| format_err!("Invalid day"))?;
137 if s.len() != 22 || s[12] != b':' || s[15] != b':' || &s[18..22] != b" GMT" {
138 bail!("Date time not in rfc950 fmt");
139 }
140 let mut year = from_utf8(&s[7..9])?.parse::<u16>()?;
141 if year < 70 {
142 year += 2000;
143 } else {
144 year += 1900;
145 }
146 Ok(HttpDate {
147 second: from_utf8(&s[16..18])?.parse()?,
148 minute: from_utf8(&s[13..15])?.parse()?,
149 hour: from_utf8(&s[10..12])?.parse()?,
150 day: from_utf8(&s[0..2])?.parse()?,
151 month: match &s[2..7] {
152 b"-Jan-" => 1,
153 b"-Feb-" => 2,
154 b"-Mar-" => 3,
155 b"-Apr-" => 4,
156 b"-May-" => 5,
157 b"-Jun-" => 6,
158 b"-Jul-" => 7,
159 b"-Aug-" => 8,
160 b"-Sep-" => 9,
161 b"-Oct-" => 10,
162 b"-Nov-" => 11,
163 b"-Dec-" => 12,
164 _ => bail!("Invalid month"),
165 },
166 year,
167 week_day,
168 })
169}
170
171fn parse_asctime(s: &[u8]) -> crate::Result<HttpDate> {
172 if s.len() != ASCTIME_LENGTH || s[10] != b' ' || s[13] != b':' || s[16] != b':' || s[19] != b' '
174 {
175 bail!("Date time not in asctime format");
176 }
177 Ok(HttpDate {
178 second: from_utf8(&s[17..19])?.parse()?,
179 minute: from_utf8(&s[14..16])?.parse()?,
180 hour: from_utf8(&s[11..13])?.parse()?,
181 day: {
182 let x = &s[8..10];
183 from_utf8(if x[0] == b' ' { &x[1..2] } else { x })?.parse()?
184 },
185 month: match &s[4..8] {
186 b"Jan " => 1,
187 b"Feb " => 2,
188 b"Mar " => 3,
189 b"Apr " => 4,
190 b"May " => 5,
191 b"Jun " => 6,
192 b"Jul " => 7,
193 b"Aug " => 8,
194 b"Sep " => 9,
195 b"Oct " => 10,
196 b"Nov " => 11,
197 b"Dec " => 12,
198 _ => bail!("Invalid month"),
199 },
200 year: from_utf8(&s[20..24])?.parse()?,
201 week_day: match &s[0..4] {
202 b"Mon " => 1,
203 b"Tue " => 2,
204 b"Wed " => 3,
205 b"Thu " => 4,
206 b"Fri " => 5,
207 b"Sat " => 6,
208 b"Sun " => 7,
209 _ => bail!("Invalid day"),
210 },
211 })
212}
213
214impl From<SystemTime> for HttpDate {
215 fn from(system_time: SystemTime) -> Self {
216 let dur = system_time
217 .duration_since(UNIX_EPOCH)
218 .expect("all times should be after the epoch");
219 let secs_since_epoch = dur.as_secs();
220
221 if secs_since_epoch >= YEAR_9999_SECONDS {
222 panic!("date must be before year 9999");
224 }
225
226 const LEAPOCH: i64 = 11017;
228 const DAYS_PER_400Y: i64 = 365 * 400 + 97;
229 const DAYS_PER_100Y: i64 = 365 * 100 + 24;
230 const DAYS_PER_4Y: i64 = 365 * 4 + 1;
231
232 let days = (secs_since_epoch / SECONDS_IN_DAY) as i64 - LEAPOCH;
233 let secs_of_day = secs_since_epoch % SECONDS_IN_DAY;
234
235 let mut qc_cycles = days / DAYS_PER_400Y;
236 let mut remdays = days % DAYS_PER_400Y;
237
238 if remdays < 0 {
239 remdays += DAYS_PER_400Y;
240 qc_cycles -= 1;
241 }
242
243 let mut c_cycles = remdays / DAYS_PER_100Y;
244 if c_cycles == 4 {
245 c_cycles -= 1;
246 }
247 remdays -= c_cycles * DAYS_PER_100Y;
248
249 let mut q_cycles = remdays / DAYS_PER_4Y;
250 if q_cycles == 25 {
251 q_cycles -= 1;
252 }
253 remdays -= q_cycles * DAYS_PER_4Y;
254
255 let mut remyears = remdays / 365;
256 if remyears == 4 {
257 remyears -= 1;
258 }
259 remdays -= remyears * 365;
260
261 let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
262
263 let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
264 let mut month = 0;
265 for month_len in months.iter() {
266 month += 1;
267 if remdays < *month_len {
268 break;
269 }
270 remdays -= *month_len;
271 }
272 let mday = remdays + 1;
273 let month = if month + 2 > 12 {
274 year += 1;
275 month - 10
276 } else {
277 month + 2
278 };
279
280 let mut week_day = (3 + days) % 7;
281 if week_day <= 0 {
282 week_day += 7
283 };
284
285 HttpDate {
286 second: (secs_of_day % 60) as u8,
287 minute: ((secs_of_day % SECONDS_IN_HOUR) / 60) as u8,
288 hour: (secs_of_day / SECONDS_IN_HOUR) as u8,
289 day: mday as u8,
290 month: month as u8,
291 year: year as u16,
292 week_day: week_day as u8,
293 }
294 }
295}
296
297impl From<HttpDate> for SystemTime {
298 fn from(http_date: HttpDate) -> Self {
299 let leap_years = ((http_date.year - 1) - 1968) / 4 - ((http_date.year - 1) - 1900) / 100
300 + ((http_date.year - 1) - 1600) / 400;
301 let mut ydays = match http_date.month {
302 1 => 0,
303 2 => 31,
304 3 => 59,
305 4 => 90,
306 5 => 120,
307 6 => 151,
308 7 => 181,
309 8 => 212,
310 9 => 243,
311 10 => 273,
312 11 => 304,
313 12 => 334,
314 _ => unreachable!(),
315 } + http_date.day as u64
316 - 1;
317 if is_leap_year(http_date.year) && http_date.month > 2 {
318 ydays += 1;
319 }
320 let days = (http_date.year as u64 - 1970) * 365 + leap_years as u64 + ydays;
321 UNIX_EPOCH
322 + Duration::from_secs(
323 http_date.second as u64
324 + http_date.minute as u64 * 60
325 + http_date.hour as u64 * SECONDS_IN_HOUR
326 + days * SECONDS_IN_DAY,
327 )
328 }
329}
330
331impl FromStr for HttpDate {
332 type Err = crate::Error;
333
334 fn from_str(s: &str) -> Result<Self, Self::Err> {
335 ensure!(s.is_ascii(), "String slice is not valid ASCII");
336 let x = s.trim().as_bytes();
337 let date = parse_imf_fixdate(x)
338 .or_else(|_| parse_rfc850_date(x))
339 .or_else(|_| parse_asctime(x))?;
340 ensure!(date.is_valid(), "Invalid date time");
341 Ok(date)
342 }
343}
344
345impl Display for HttpDate {
346 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
347 let week_day = match self.week_day {
348 1 => b"Mon",
349 2 => b"Tue",
350 3 => b"Wed",
351 4 => b"Thu",
352 5 => b"Fri",
353 6 => b"Sat",
354 7 => b"Sun",
355 _ => unreachable!(),
356 };
357 let month = match self.month {
358 1 => b"Jan",
359 2 => b"Feb",
360 3 => b"Mar",
361 4 => b"Apr",
362 5 => b"May",
363 6 => b"Jun",
364 7 => b"Jul",
365 8 => b"Aug",
366 9 => b"Sep",
367 10 => b"Oct",
368 11 => b"Nov",
369 12 => b"Dec",
370 _ => unreachable!(),
371 };
372 let mut buf: [u8; 29] = [
373 b' ', b' ', b' ', b',', b' ', b'0', b'0', b' ', b' ', b' ', b' ', b' ', b'0', b'0',
375 b'0', b'0', b' ', b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b'G', b'M',
376 b'T',
377 ];
378 buf[0] = week_day[0];
379 buf[1] = week_day[1];
380 buf[2] = week_day[2];
381 buf[5] = b'0' + (self.day / 10) as u8;
382 buf[6] = b'0' + (self.day % 10) as u8;
383 buf[8] = month[0];
384 buf[9] = month[1];
385 buf[10] = month[2];
386 buf[12] = b'0' + (self.year / 1000) as u8;
387 buf[13] = b'0' + (self.year / 100 % 10) as u8;
388 buf[14] = b'0' + (self.year / 10 % 10) as u8;
389 buf[15] = b'0' + (self.year % 10) as u8;
390 buf[17] = b'0' + (self.hour / 10) as u8;
391 buf[18] = b'0' + (self.hour % 10) as u8;
392 buf[20] = b'0' + (self.minute / 10) as u8;
393 buf[21] = b'0' + (self.minute % 10) as u8;
394 buf[23] = b'0' + (self.second / 10) as u8;
395 buf[24] = b'0' + (self.second % 10) as u8;
396 f.write_str(from_utf8(&buf[..]).unwrap())
397 }
398}
399
400impl PartialEq for HttpDate {
401 fn eq(&self, other: &HttpDate) -> bool {
402 SystemTime::from(*self) == SystemTime::from(*other)
403 }
404}
405
406impl PartialOrd for HttpDate {
407 fn partial_cmp(&self, other: &HttpDate) -> Option<std::cmp::Ordering> {
408 SystemTime::from(*self).partial_cmp(&SystemTime::from(*other))
409 }
410}
411
412fn is_leap_year(year: u16) -> bool {
413 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
414}
415
416#[cfg(test)]
417mod tests {
418 use std::time::{Duration, UNIX_EPOCH};
419
420 use super::{fmt_http_date, parse_http_date, HttpDate, SECONDS_IN_DAY, SECONDS_IN_HOUR};
421
422 #[test]
423 fn test_rfc_example() {
424 let d = UNIX_EPOCH + Duration::from_secs(784111777);
425 assert_eq!(
426 d,
427 parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").expect("#1")
428 );
429 assert_eq!(
430 d,
431 parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT").expect("#2")
432 );
433 assert_eq!(d, parse_http_date("Sun Nov 6 08:49:37 1994").expect("#3"));
434 }
435
436 #[test]
437 fn test2() {
438 let d = UNIX_EPOCH + Duration::from_secs(1475419451);
439 assert_eq!(
440 d,
441 parse_http_date("Sun, 02 Oct 2016 14:44:11 GMT").expect("#1")
442 );
443 assert!(parse_http_date("Sun Nov 10 08:00:00 1000").is_err());
444 assert!(parse_http_date("Sun Nov 10 08*00:00 2000").is_err());
445 assert!(parse_http_date("Sunday, 06-Nov-94 08+49:37 GMT").is_err());
446 }
447
448 #[test]
449 fn test3() {
450 let mut d = UNIX_EPOCH;
451 assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 00:00:00 GMT").unwrap());
452 d += Duration::from_secs(SECONDS_IN_HOUR);
453 assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 01:00:00 GMT").unwrap());
454 d += Duration::from_secs(SECONDS_IN_DAY);
455 assert_eq!(d, parse_http_date("Fri, 02 Jan 1970 01:00:00 GMT").unwrap());
456 d += Duration::from_secs(2592000);
457 assert_eq!(d, parse_http_date("Sun, 01 Feb 1970 01:00:00 GMT").unwrap());
458 d += Duration::from_secs(2592000);
459 assert_eq!(d, parse_http_date("Tue, 03 Mar 1970 01:00:00 GMT").unwrap());
460 d += Duration::from_secs(31536005);
461 assert_eq!(d, parse_http_date("Wed, 03 Mar 1971 01:00:05 GMT").unwrap());
462 d += Duration::from_secs(15552000);
463 assert_eq!(d, parse_http_date("Mon, 30 Aug 1971 01:00:05 GMT").unwrap());
464 d += Duration::from_secs(6048000);
465 assert_eq!(d, parse_http_date("Mon, 08 Nov 1971 01:00:05 GMT").unwrap());
466 d += Duration::from_secs(864000000);
467 assert_eq!(d, parse_http_date("Fri, 26 Mar 1999 01:00:05 GMT").unwrap());
468 }
469
470 #[test]
471 fn test_fmt() {
472 let d = UNIX_EPOCH;
473 assert_eq!(fmt_http_date(d), "Thu, 01 Jan 1970 00:00:00 GMT");
474 let d = UNIX_EPOCH + Duration::from_secs(1475419451);
475 assert_eq!(fmt_http_date(d), "Sun, 02 Oct 2016 14:44:11 GMT");
476 }
477
478 #[test]
479 fn size_of() {
480 assert_eq!(::std::mem::size_of::<HttpDate>(), 8);
481 }
482}