use rama_core::telemetry::tracing;
use rama_http_headers::{Connection, HeaderMapExt};
use rama_utils::str::{any_submatch_ignore_ascii_case, starts_with_ignore_ascii_case};
use crate::{HeaderMap, HeaderName, HeaderValue, header};
pub mod request;
pub mod response;
#[doc(inline)]
pub use self::{
request::{RemoveRequestHeader, RemoveRequestHeaderLayer},
response::{RemoveResponseHeader, RemoveResponseHeaderLayer},
};
fn remove_headers_by_prefix(headers: &mut HeaderMap, prefix: &str) {
let keys: Vec<_> = headers
.keys()
.filter(|key| starts_with_ignore_ascii_case(key, prefix))
.cloned()
.collect();
for key in keys {
headers.remove(key);
}
}
fn remove_headers_by_exact_name(headers: &mut HeaderMap, name: &HeaderName) {
headers.remove(name);
}
pub fn remove_hop_by_hop_request_headers(headers: &mut HeaderMap) {
while let Some(c) = headers.typed_get::<Connection>() {
for header in c.iter_headers() {
while headers.remove(header).is_some() {
tracing::trace!(
"removed hop-by-hop request header listed in Connection header for name: {header}"
);
}
}
_ = headers.remove(header::CONNECTION);
}
for header in [
&header::CONNECTION,
&header::PROXY_CONNECTION,
&header::PROXY_AUTHORIZATION,
&header::TE,
&header::TRAILER,
&header::TRANSFER_ENCODING,
&header::UPGRADE,
&header::X_FORWARDED_FOR,
&header::X_FORWARDED_HOST,
&header::X_FORWARDED_PROTO,
&header::FORWARDED,
&header::VIA,
&header::CF_CONNECTING_IP,
&header::X_REAL_IP,
&header::X_CLIENT_IP,
&header::CLIENT_IP,
&header::TRUE_CLIENT_IP,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed hop-by-hop request header for name: {header}");
}
}
}
pub fn remove_hop_by_hop_response_headers(headers: &mut HeaderMap) {
while let Some(c) = headers.typed_get::<Connection>() {
for header in c.iter_headers() {
while headers.remove(header).is_some() {
tracing::trace!(
"removed hop-by-hop response header listed in Connection header for name: {header}"
);
}
}
_ = headers.remove(header::CONNECTION);
}
for header in [
&header::CONNECTION,
&header::KEEP_ALIVE,
&header::PROXY_AUTHENTICATE,
&header::TRAILER,
&header::TRANSFER_ENCODING,
&header::UPGRADE,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed hop-by-hop response header for name: {header}");
}
}
}
pub fn remove_illegal_h2_request_headers(headers: &mut HeaderMap) {
while let Some(c) = headers.typed_get::<Connection>() {
for header in c.iter_headers() {
while headers.remove(header).is_some() {
tracing::trace!(
header = %header,
"removed connection-specific request header listed in Connection header for name"
);
}
}
_ = headers.remove(header::CONNECTION);
}
for header in [
&header::CONNECTION,
&header::PROXY_CONNECTION,
&header::KEEP_ALIVE,
&header::TRANSFER_ENCODING,
&header::UPGRADE,
&header::SEC_WEBSOCKET_KEY,
&header::HOST,
] {
while headers.remove(header).is_some() {
tracing::trace!(
header = %header,
"removed illegal (~http1) header from h2 request for name"
);
}
}
let te_is_legal = headers
.get_all(header::TE)
.iter()
.all(|v| v.as_bytes().trim_ascii().eq_ignore_ascii_case(b"trailers"));
if !te_is_legal {
while headers.remove(header::TE).is_some() {
tracing::trace!(
"removed illegal TE header (only `TE: trailers` is valid) from h2 request"
);
}
}
}
pub fn remove_illegal_h2_response_headers(headers: &mut HeaderMap) {
while let Some(c) = headers.typed_get::<Connection>() {
for header in c.iter_headers() {
while headers.remove(header).is_some() {
tracing::trace!(
header = %header,
"removed connection-specific response header listed in Connection header for name"
);
}
}
_ = headers.remove(header::CONNECTION);
}
for header in [
&header::CONNECTION,
&header::PROXY_CONNECTION,
&header::KEEP_ALIVE,
&header::TRANSFER_ENCODING,
&header::UPGRADE,
] {
while headers.remove(header).is_some() {
tracing::trace!(
header = %header,
"removed illegal (~http1) header from h2 response for name"
);
}
}
}
pub fn remove_sensitive_request_headers(headers: &mut HeaderMap) {
for header in [
&header::AUTHORIZATION,
&header::PROXY_AUTHORIZATION,
&header::COOKIE,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed sensitive request header for name: {header}");
}
}
remove_headers_if(
headers,
|name, _value| is_sensitive_header_name(name),
"sensitive request header",
);
}
pub fn remove_sensitive_response_headers(headers: &mut HeaderMap) {
for header in [&header::SET_COOKIE] {
while headers.remove(header).is_some() {
tracing::trace!("removed sensitive response header for name: {header}");
}
}
}
pub fn remove_payload_metadata_headers(headers: &mut HeaderMap) {
for header in [
&header::CONTENT_ENCODING,
&header::TRANSFER_ENCODING,
&header::ACCEPT_RANGES,
&header::CONTENT_LENGTH,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed payload header for name: {header}");
}
}
}
pub fn remove_cache_validation_request_headers(headers: &mut HeaderMap) {
for header in [
&header::IF_NONE_MATCH,
&header::IF_MODIFIED_SINCE,
&header::IF_MATCH,
&header::IF_UNMODIFIED_SINCE,
&header::IF_RANGE,
&header::RANGE,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed cache validation request header for name: {header}");
}
}
}
pub fn remove_cache_validation_response_headers(headers: &mut HeaderMap) {
for header in [
&header::ETAG,
&header::LAST_MODIFIED,
&header::ACCEPT_RANGES,
&header::CONTENT_RANGE,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed cache validation response header for name: {header}");
}
}
}
pub fn remove_cache_policy_headers(headers: &mut HeaderMap) {
for header in [
&header::CACHE_CONTROL,
&header::PRAGMA,
&header::EXPIRES,
&header::AGE,
&header::WARNING,
] {
while headers.remove(header).is_some() {
tracing::trace!("removed cache policy header for name: {header}");
}
}
}
#[inline(always)]
fn is_sensitive_header_name(name: &HeaderName) -> bool {
any_submatch_ignore_ascii_case(
name.as_str(),
["api-key", "auth-token", "access-token", "security-token"],
)
}
fn remove_headers_if<F>(headers: &mut HeaderMap, mut remove: F, log_context: &str)
where
F: FnMut(&HeaderName, &HeaderValue) -> bool,
{
loop {
let name_to_remove: Option<HeaderName> = headers
.iter()
.find_map(|(name, value)| remove(name, value).then(|| name.clone()));
let Some(name) = name_to_remove else { break };
while headers.remove(&name).is_some() {
tracing::trace!("{log_context}: removed header: {name}");
}
}
}