use axum::body::Body;
use axum::http::{HeaderMap, HeaderName, Response, StatusCode};
use serde_json::{Map, Value, json};
use crate::configuration::BOOTSTRAP_CLIENT_TOKEN_HEADER;
use crate::error::CliError;
pub(super) fn observable_headers(headers: &HeaderMap) -> Map<String, Value> {
let mut output = Map::new();
for (name, value) in headers {
if should_record_header(name, headers)
&& let Ok(value) = value.to_str()
{
output.insert(name.as_str().to_string(), json!(value));
}
}
output
}
pub(super) fn response_headers(headers: &HeaderMap) -> HeaderMap {
let mut output = HeaderMap::new();
for (name, value) in headers {
if !is_hop_by_hop(name)
&& !named_by_connection_header(name, headers)
&& name != http::header::CONTENT_LENGTH
{
output.append(name.clone(), value.clone());
}
}
output
}
pub(super) fn build_response(
status: StatusCode,
headers: HeaderMap,
body: Body,
) -> Result<Response<Body>, CliError> {
let mut builder = Response::builder().status(status);
for (name, value) in &headers {
builder = builder.header(name, value);
}
Ok(builder.body(body)?)
}
pub(super) fn should_forward_request_header(name: &HeaderName, headers: &HeaderMap) -> bool {
!is_hop_by_hop(name)
&& !named_by_connection_header(name, headers)
&& name != http::header::HOST
&& name != http::header::CONTENT_LENGTH
&& name.as_str() != BOOTSTRAP_CLIENT_TOKEN_HEADER
&& name.as_str() != crate::provider_auth::TRANSPARENT_PROXY_CREDENTIAL_HEADER
&& name != http::header::ACCEPT_ENCODING
}
pub(super) fn should_record_header(name: &HeaderName, headers: &HeaderMap) -> bool {
should_forward_request_header(name, headers)
&& name != http::header::AUTHORIZATION
&& name != http::header::COOKIE
&& name.as_str() != "x-api-key"
&& name.as_str() != "api-key"
&& name.as_str() != "anthropic-api-key"
}
fn named_by_connection_header(name: &HeaderName, headers: &HeaderMap) -> bool {
headers
.get_all(http::header::CONNECTION)
.iter()
.filter_map(|value| value.to_str().ok())
.flat_map(|value| value.split(','))
.any(|token| token.trim().eq_ignore_ascii_case(name.as_str()))
}
pub(super) fn is_hop_by_hop(name: &HeaderName) -> bool {
matches!(
name.as_str(),
"connection"
| "keep-alive"
| "proxy-authenticate"
| "proxy-authorization"
| "te"
| "trailer"
| "transfer-encoding"
| "upgrade"
)
}