1use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use crate::cookie::Cookie;
7use crate::request::Request;
8use crate::response::ResponseWriter;
9use crate::status;
10
11pub use crate::mime::detect_content_type;
16
17pub fn parse_time(s: &str) -> Option<SystemTime> {
28 for fmt in &[
30 "%a, %d %b %Y %H:%M:%S GMT", "%A, %d-%b-%y %H:%M:%S GMT", "%a %b %e %H:%M:%S %Y", ] {
34 if let Some(t) = parse_http_date(s, fmt) {
36 return Some(t);
37 }
38 }
39 None
40}
41
42fn parse_http_date(s: &str, _fmt: &str) -> Option<SystemTime> {
45 let s = s.trim();
48 let rest = if let Some(i) = s.find(", ") { &s[i + 2..] } else { s };
50
51 let parts: Vec<&str> = rest.split_whitespace().collect();
52 if parts.len() < 4 { return None; }
53
54 let day: u64 = parts[0].parse().ok()?;
55 let mon: u64 = month_num(parts[1])?;
56 let year: u64 = parts[2].parse().ok()?;
57 let time_parts: Vec<&str> = parts[3].split(':').collect();
58 if time_parts.len() < 3 { return None; }
59 let hour: u64 = time_parts[0].parse().ok()?;
60 let min: u64 = time_parts[1].parse().ok()?;
61 let sec: u64 = time_parts[2].parse().ok()?;
62
63 let days_since_epoch = days_from_civil(year as i64, mon, day);
65 let secs = days_since_epoch as u64 * 86400 + hour * 3600 + min * 60 + sec;
66 Some(UNIX_EPOCH + Duration::from_secs(secs))
67}
68
69fn month_num(s: &str) -> Option<u64> {
70 match s {
71 "Jan" | "January" => Some(1), "Feb" | "February" => Some(2),
72 "Mar" | "March" => Some(3), "Apr" | "April" => Some(4),
73 "May" => Some(5), "Jun" | "June" => Some(6),
74 "Jul" | "July" => Some(7), "Aug" | "August" => Some(8),
75 "Sep" | "September"=> Some(9), "Oct" | "October" => Some(10),
76 "Nov" | "November" => Some(11), "Dec" | "December" => Some(12),
77 _ => None,
78 }
79}
80
81fn days_from_civil(y: i64, m: u64, d: u64) -> i64 {
83 let m = m as i64;
84 let d = d as i64;
85 let y = if m <= 2 { y - 1 } else { y };
86 let era = if y >= 0 { y } else { y - 399 } / 400;
87 let yoe = y - era * 400;
88 let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + d - 1;
89 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
90 era * 146097 + doe - 719468
91}
92
93pub fn set_cookie(w: &mut dyn ResponseWriter, cookie: &Cookie) {
100 w.header().add("Set-Cookie", cookie.to_set_cookie_header());
101}
102
103pub fn error(w: &mut dyn ResponseWriter, message: &str, code: u16) {
110 w.header().set("Content-Type", "text/plain; charset=utf-8");
111 w.header().set("X-Content-Type-Options", "nosniff");
112 w.write_header(code);
113 let _ = w.write(message.as_bytes());
114 let _ = w.write(b"\n");
115}
116
117pub fn not_found(w: &mut dyn ResponseWriter, _r: &Request) {
120 error(w, "404 page not found", status::NOT_FOUND);
121}
122
123pub fn redirect(w: &mut dyn ResponseWriter, _r: &Request, url: &str, code: u16) {
126 w.header().set("Location", url);
127 w.header().set("Content-Type", "text/html; charset=utf-8");
128 w.write_header(code);
129 let body = format!(
130 "<a href=\"{url}\">{text}</a>.\n",
131 url = html_escape(url),
132 text = status::status_text(code),
133 );
134 let _ = w.write(body.as_bytes());
135}
136
137fn html_escape(s: &str) -> String {
138 s.replace('&', "&")
139 .replace('<', "<")
140 .replace('>', ">")
141 .replace('"', """)
142}
143
144pub fn canonical_header_key(s: &str) -> String {
150 crate::header::Header::canonical_key(s)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn canonical_key() {
159 assert_eq!(canonical_header_key("content-type"), "Content-Type");
160 }
161
162 #[test]
163 fn parse_time_rfc1123() {
164 let t = parse_time("Thu, 01 Jan 1970 00:00:00 GMT");
165 assert_eq!(t, Some(UNIX_EPOCH));
166 }
167
168 #[test]
169 fn days_from_civil_epoch() {
170 assert_eq!(days_from_civil(1970, 1, 1), 0);
171 }
172}