use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::cookie::Cookie;
use crate::request::Request;
use crate::response::ResponseWriter;
use crate::status;
pub use crate::mime::detect_content_type;
pub fn parse_time(s: &str) -> Option<SystemTime> {
for fmt in &[
"%a, %d %b %Y %H:%M:%S GMT", "%A, %d-%b-%y %H:%M:%S GMT", "%a %b %e %H:%M:%S %Y", ] {
if let Some(t) = parse_http_date(s, fmt) {
return Some(t);
}
}
None
}
fn parse_http_date(s: &str, _fmt: &str) -> Option<SystemTime> {
let s = s.trim();
let rest = if let Some(i) = s.find(", ") { &s[i + 2..] } else { s };
let parts: Vec<&str> = rest.split_whitespace().collect();
if parts.len() < 4 { return None; }
let day: u64 = parts[0].parse().ok()?;
let mon: u64 = month_num(parts[1])?;
let year: u64 = parts[2].parse().ok()?;
let time_parts: Vec<&str> = parts[3].split(':').collect();
if time_parts.len() < 3 { return None; }
let hour: u64 = time_parts[0].parse().ok()?;
let min: u64 = time_parts[1].parse().ok()?;
let sec: u64 = time_parts[2].parse().ok()?;
let days_since_epoch = days_from_civil(year as i64, mon, day);
let secs = days_since_epoch as u64 * 86400 + hour * 3600 + min * 60 + sec;
Some(UNIX_EPOCH + Duration::from_secs(secs))
}
fn month_num(s: &str) -> Option<u64> {
match s {
"Jan" | "January" => Some(1), "Feb" | "February" => Some(2),
"Mar" | "March" => Some(3), "Apr" | "April" => Some(4),
"May" => Some(5), "Jun" | "June" => Some(6),
"Jul" | "July" => Some(7), "Aug" | "August" => Some(8),
"Sep" | "September"=> Some(9), "Oct" | "October" => Some(10),
"Nov" | "November" => Some(11), "Dec" | "December" => Some(12),
_ => None,
}
}
fn days_from_civil(y: i64, m: u64, d: u64) -> i64 {
let m = m as i64;
let d = d as i64;
let y = if m <= 2 { y - 1 } else { y };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
pub fn set_cookie(w: &mut dyn ResponseWriter, cookie: &Cookie) {
w.header().add("Set-Cookie", cookie.to_set_cookie_header());
}
pub fn error(w: &mut dyn ResponseWriter, message: &str, code: u16) {
w.header().set("Content-Type", "text/plain; charset=utf-8");
w.header().set("X-Content-Type-Options", "nosniff");
w.write_header(code);
let _ = w.write(message.as_bytes());
let _ = w.write(b"\n");
}
pub fn not_found(w: &mut dyn ResponseWriter, _r: &Request) {
error(w, "404 page not found", status::NOT_FOUND);
}
pub fn redirect(w: &mut dyn ResponseWriter, _r: &Request, url: &str, code: u16) {
w.header().set("Location", url);
w.header().set("Content-Type", "text/html; charset=utf-8");
w.write_header(code);
let body = format!(
"<a href=\"{url}\">{text}</a>.\n",
url = html_escape(url),
text = status::status_text(code),
);
let _ = w.write(body.as_bytes());
}
fn html_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
pub fn canonical_header_key(s: &str) -> String {
crate::header::Header::canonical_key(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_key() {
assert_eq!(canonical_header_key("content-type"), "Content-Type");
}
#[test]
fn parse_time_rfc1123() {
let t = parse_time("Thu, 01 Jan 1970 00:00:00 GMT");
assert_eq!(t, Some(UNIX_EPOCH));
}
#[test]
fn days_from_civil_epoch() {
assert_eq!(days_from_civil(1970, 1, 1), 0);
}
}