use std::collections::HashMap;
use std::net::IpAddr;
const SENSITIVE_HEADERS: &[&str] = &[
"authorization",
"cookie",
"set-cookie",
"x-api-key",
"x-auth-token",
"x-csrf-token",
"x-xsrf-token",
"proxy-authorization",
];
pub fn filter_headers(headers: &HashMap<String, String>) -> HashMap<String, String> {
headers
.iter()
.filter(|(key, _)| !is_sensitive_header(key))
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
pub fn is_sensitive_header(name: &str) -> bool {
let name_lower = name.to_lowercase();
SENSITIVE_HEADERS.contains(&name_lower.as_str())
}
pub fn extract_client_ip(headers: &HashMap<String, String>) -> Option<IpAddr> {
if let Some(forwarded) = headers.get("x-forwarded-for").or_else(|| headers.get("X-Forwarded-For")) {
if let Some(first_ip) = forwarded.split(',').next() {
if let Ok(ip) = first_ip.trim().parse() {
return Some(ip);
}
}
}
if let Some(real_ip) = headers.get("x-real-ip").or_else(|| headers.get("X-Real-IP")) {
if let Ok(ip) = real_ip.trim().parse() {
return Some(ip);
}
}
if let Some(cf_ip) = headers.get("cf-connecting-ip").or_else(|| headers.get("CF-Connecting-IP")) {
if let Ok(ip) = cf_ip.trim().parse() {
return Some(ip);
}
}
if let Some(true_ip) = headers.get("true-client-ip").or_else(|| headers.get("True-Client-IP")) {
if let Ok(ip) = true_ip.trim().parse() {
return Some(ip);
}
}
None
}
pub fn build_url(scheme: &str, host: &str, path: &str, query: Option<&str>) -> String {
let mut url = format!("{}://{}{}", scheme, host, path);
if let Some(q) = query {
if !q.is_empty() {
url.push('?');
url.push_str(q);
}
}
url
}
pub fn normalize_method(method: &str) -> String {
method.to_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_headers() {
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("Authorization".to_string(), "Bearer secret".to_string());
headers.insert("X-Request-Id".to_string(), "abc123".to_string());
let filtered = filter_headers(&headers);
assert_eq!(filtered.len(), 2);
assert!(filtered.contains_key("Content-Type"));
assert!(filtered.contains_key("X-Request-Id"));
assert!(!filtered.contains_key("Authorization"));
}
#[test]
fn test_is_sensitive_header() {
assert!(is_sensitive_header("authorization"));
assert!(is_sensitive_header("Authorization"));
assert!(is_sensitive_header("AUTHORIZATION"));
assert!(is_sensitive_header("cookie"));
assert!(is_sensitive_header("x-api-key"));
assert!(!is_sensitive_header("content-type"));
assert!(!is_sensitive_header("x-request-id"));
}
#[test]
fn test_extract_client_ip() {
let mut headers = HashMap::new();
headers.insert("X-Forwarded-For".to_string(), "192.168.1.1, 10.0.0.1".to_string());
let ip = extract_client_ip(&headers);
assert_eq!(ip, Some("192.168.1.1".parse().unwrap()));
}
#[test]
fn test_extract_client_ip_real_ip() {
let mut headers = HashMap::new();
headers.insert("X-Real-IP".to_string(), "10.0.0.50".to_string());
let ip = extract_client_ip(&headers);
assert_eq!(ip, Some("10.0.0.50".parse().unwrap()));
}
#[test]
fn test_build_url() {
assert_eq!(
build_url("https", "example.com", "/api/users", Some("page=1")),
"https://example.com/api/users?page=1"
);
assert_eq!(
build_url("http", "localhost:8080", "/health", None),
"http://localhost:8080/health"
);
}
}