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))
}
pub async fn etag_handler(
req: HttpRequest,
path: web::Path<String>,
config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
let etag = path.into_inner();
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() {
let etag_quoted = format!("\"{}\"", etag);
if if_none_match_values.contains(&etag)
|| if_none_match_values.contains(&etag_quoted)
|| if_none_match_values.contains(&"*".to_string())
{
return Ok(HttpResponse::NotModified()
.append_header(("ETag", format!("\"{}\"", etag)))
.finish());
}
}
else if !if_match_values.is_empty() {
let etag_quoted = format!("\"{}\"", etag);
if !if_match_values.contains(&etag)
&& !if_match_values.contains(&etag_quoted)
&& !if_match_values.contains(&"*".to_string())
{
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", format!("\"{}\"", etag)))
.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());
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
}