use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use http::HeaderMap;
use http::StatusCode;
use tako_rs_core::body::TakoBody;
use tako_rs_core::types::Response;
use super::date::format_http_date;
use super::date::parse_http_date;
pub fn evaluate_conditional(
request_headers: &HeaderMap,
etag: Option<&str>,
last_modified: Option<SystemTime>,
) -> Option<Response> {
if let Some(req) = request_headers.get(http::header::IF_MATCH) {
let req = req.to_str().unwrap_or("");
let satisfied = match etag {
Some(e) => etag_match(req, e, true),
None => req.trim() == "*",
};
if !satisfied {
return Some(precondition_failed());
}
}
if let (Some(req), Some(ts)) = (
request_headers.get(http::header::IF_UNMODIFIED_SINCE),
last_modified,
) && let Ok(req) = req.to_str()
&& let Some(req_ts) = parse_http_date(req)
&& let Ok(file_ts) = ts.duration_since(UNIX_EPOCH)
&& file_ts.as_secs() > req_ts
{
return Some(precondition_failed());
}
if let (Some(req), Some(etag)) = (request_headers.get(http::header::IF_NONE_MATCH), etag) {
let req = req.to_str().unwrap_or("");
if etag_match(req, etag, false) {
return Some(not_modified(etag, last_modified));
}
}
if request_headers.get(http::header::IF_NONE_MATCH).is_none()
&& let (Some(req), Some(ts)) = (
request_headers.get(http::header::IF_MODIFIED_SINCE),
last_modified,
)
&& let Ok(req) = req.to_str()
&& let Some(req_ts) = parse_http_date(req)
&& let Ok(file_ts) = ts.duration_since(UNIX_EPOCH)
&& file_ts.as_secs() <= req_ts
{
return Some(not_modified(etag.unwrap_or(""), Some(ts)));
}
None
}
fn precondition_failed() -> Response {
http::Response::builder()
.status(StatusCode::PRECONDITION_FAILED)
.body(TakoBody::empty())
.expect("valid 412 response")
}
fn not_modified(etag: &str, last_modified: Option<SystemTime>) -> Response {
let mut builder = http::Response::builder().status(StatusCode::NOT_MODIFIED);
if !etag.is_empty() {
builder = builder.header(http::header::ETAG, etag);
}
if let Some(ts) = last_modified
&& let Ok(s) = ts.duration_since(UNIX_EPOCH)
{
builder = builder.header(http::header::LAST_MODIFIED, format_http_date(s.as_secs()));
}
builder.body(TakoBody::empty()).expect("valid 304 response")
}
fn etag_match(header: &str, value: &str, strong_only: bool) -> bool {
if header.trim() == "*" {
return true;
}
if strong_only && value.starts_with("W/") {
return false;
}
for raw in header.split(',') {
let raw = raw.trim();
if strong_only && raw.starts_with("W/") {
continue;
}
let raw = raw.strip_prefix("W/").unwrap_or(raw);
let raw = raw.trim_matches('"');
if raw == value {
return true;
}
}
false
}