use super::*;
use crate::handlers::utils::{generate_etag, http_date, parse_multi_value_header};
use serde_json::Value;
use std::collections::BTreeMap;
pub async fn cache_handler(req: HttpRequest, config: web::Data<AppConfig>) -> Result<HttpResponse> {
let if_modified_since = req.headers().get("If-Modified-Since");
let if_none_match = req.headers().get("If-None-Match");
let last_modified = http_date();
let etag = generate_etag();
if if_modified_since.is_some() || if_none_match.is_some() {
Ok(HttpResponse::NotModified()
.append_header(("Last-Modified", last_modified))
.append_header(("ETag", etag))
.finish())
} else {
let mut request_info = extract_request_info(&req, None, &config.exclude_headers);
fix_request_info_url(&req, &mut request_info);
Ok(HttpResponse::Ok()
.append_header(("Last-Modified", last_modified))
.append_header(("ETag", etag))
.json(request_info))
}
}
pub async fn cache_control_handler(
req: HttpRequest,
path: web::Path<u32>,
config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
let seconds = path.into_inner();
let mut request_info = extract_request_info(&req, None, &config.exclude_headers);
fix_request_info_url(&req, &mut request_info);
Ok(HttpResponse::Ok()
.append_header(("Cache-Control", format!("public, max-age={}", seconds)))
.json(request_info))
}
fn etag_matches(candidate: &str, canonical: &str) -> bool {
if candidate == "*" {
return true;
}
candidate
.strip_prefix("W/")
.unwrap_or(candidate)
.trim_matches('"')
== canonical
}
pub async fn etag_handler(
req: HttpRequest,
path: web::Path<String>,
config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
let raw_etag = path.into_inner();
let is_weak = raw_etag.starts_with("W/");
let canonical = raw_etag.strip_prefix("W/").unwrap_or(&raw_etag);
let canonical = canonical.trim_matches('"');
let etag_header = if is_weak {
format!("W/\"{}\"", canonical)
} else {
format!("\"{}\"", canonical)
};
let if_none_match_values = parse_multi_value_header(req.headers().get("If-None-Match"));
let if_match_values = parse_multi_value_header(req.headers().get("If-Match"));
if !if_none_match_values.is_empty() {
if if_none_match_values
.iter()
.any(|v| etag_matches(v, canonical))
{
return Ok(HttpResponse::NotModified()
.append_header(("ETag", etag_header.clone()))
.finish());
}
} else if !if_match_values.is_empty()
&& !if_match_values.iter().any(|v| etag_matches(v, canonical))
{
return Ok(HttpResponse::PreconditionFailed().finish());
}
let mut request_info = extract_request_info(&req, None, &config.exclude_headers);
fix_request_info_url(&req, &mut request_info);
Ok(HttpResponse::Ok()
.append_header(("ETag", etag_header))
.json(request_info))
}
fn parse_multi_value_query_string(query_string: &str) -> BTreeMap<String, Vec<String>> {
let mut params: BTreeMap<String, Vec<String>> = BTreeMap::new();
if query_string.is_empty() {
return params;
}
for pair in query_string.split('&') {
if let Some((key, value)) = pair.split_once('=') {
let decoded_key = urlencoding::decode(key)
.unwrap_or_else(|_| key.into())
.to_string();
let decoded_value = urlencoding::decode(value)
.unwrap_or_else(|_| value.into())
.to_string();
params.entry(decoded_key).or_default().push(decoded_value);
} else if !pair.is_empty() {
let decoded_key = urlencoding::decode(pair)
.unwrap_or_else(|_| pair.into())
.to_string();
params.entry(decoded_key).or_default().push(String::new());
}
}
params
}
pub async fn response_headers_get_handler(
req: HttpRequest,
_query: web::Query<HashMap<String, String>>, ) -> Result<HttpResponse> {
let multi_value_params = parse_multi_value_query_string(req.query_string());
if let Some(body) = multi_value_params.get("body").and_then(|v| v.last()) {
let status = multi_value_params
.get("status")
.and_then(|v| v.last())
.and_then(|s| s.parse::<u16>().ok())
.and_then(|c| StatusCode::from_u16(c).ok())
.unwrap_or(StatusCode::OK);
let mut builder = HttpResponse::build(status);
for (key, values) in &multi_value_params {
if matches!(key.as_str(), "body" | "status") {
continue;
}
for value in values {
builder.append_header((key.as_str(), value.as_str()));
}
}
return Ok(builder.body(body.clone()));
}
let mut iteration_count = 0;
const MAX_ITERATIONS: usize = 10;
loop {
let mut response_builder = HttpResponse::Ok();
for (key, values) in &multi_value_params {
for value in values {
response_builder.append_header((key.as_str(), value.as_str()));
}
}
let mut headers_map: BTreeMap<String, Value> = BTreeMap::new();
for (key, values) in &multi_value_params {
if values.len() == 1 {
headers_map.insert(key.clone(), Value::String(values[0].clone()));
} else {
headers_map.insert(
key.clone(),
Value::Array(values.iter().map(|v| Value::String(v.clone())).collect()),
);
}
}
headers_map.insert(
"Content-Type".to_string(),
Value::String("application/json".to_string()),
);
let json_string = serde_json::to_string(&headers_map)?;
let content_length = json_string.len().to_string();
headers_map.insert(
"Content-Length".to_string(),
Value::String(content_length.clone()),
);
response_builder.append_header(("Content-Length", content_length));
let _current_json = serde_json::to_string(&headers_map)?;
iteration_count += 1;
if iteration_count >= 2 || iteration_count >= MAX_ITERATIONS {
return Ok(response_builder.json(headers_map));
}
}
}
pub async fn response_headers_post_handler(
req: HttpRequest,
_query: web::Query<HashMap<String, String>>, _body: String,
) -> Result<HttpResponse> {
response_headers_get_handler(req, _query).await
}