rama_http/layer/remove_header/
mod.rs1use rama_core::telemetry::tracing;
6use rama_http_headers::{Connection, HeaderMapExt};
7use rama_utils::str::{any_submatch_ignore_ascii_case, starts_with_ignore_ascii_case};
8
9use crate::{HeaderMap, HeaderName, HeaderValue, header};
10
11pub mod request;
12pub mod response;
13
14#[doc(inline)]
15pub use self::{
16 request::{RemoveRequestHeader, RemoveRequestHeaderLayer},
17 response::{RemoveResponseHeader, RemoveResponseHeaderLayer},
18};
19
20fn remove_headers_by_prefix(headers: &mut HeaderMap, prefix: &str) {
21 let keys: Vec<_> = headers
22 .keys()
23 .filter(|key| starts_with_ignore_ascii_case(key, prefix))
25 .cloned()
26 .collect();
27
28 for key in keys {
29 headers.remove(key);
30 }
31}
32
33fn remove_headers_by_exact_name(headers: &mut HeaderMap, name: &HeaderName) {
34 headers.remove(name);
35}
36
37pub fn remove_hop_by_hop_request_headers(headers: &mut HeaderMap) {
45 while let Some(c) = headers.typed_get::<Connection>() {
46 for header in c.iter_headers() {
47 while headers.remove(header).is_some() {
48 tracing::trace!(
49 "removed hop-by-hop request header listed in Connection header for name: {header}"
50 );
51 }
52 }
53 _ = headers.remove(header::CONNECTION);
54 }
55 for header in [
56 &header::CONNECTION,
57 &header::PROXY_CONNECTION,
58 &header::PROXY_AUTHORIZATION,
59 &header::TE,
60 &header::TRAILER,
61 &header::TRANSFER_ENCODING,
62 &header::UPGRADE,
63 &header::X_FORWARDED_FOR,
64 &header::X_FORWARDED_HOST,
65 &header::X_FORWARDED_PROTO,
66 &header::FORWARDED,
67 &header::VIA,
68 &header::CF_CONNECTING_IP,
69 &header::X_REAL_IP,
70 &header::X_CLIENT_IP,
71 &header::CLIENT_IP,
72 &header::TRUE_CLIENT_IP,
73 ] {
74 while headers.remove(header).is_some() {
75 tracing::trace!("removed hop-by-hop request header for name: {header}");
76 }
77 }
78}
79
80pub fn remove_hop_by_hop_response_headers(headers: &mut HeaderMap) {
88 while let Some(c) = headers.typed_get::<Connection>() {
89 for header in c.iter_headers() {
90 while headers.remove(header).is_some() {
91 tracing::trace!(
92 "removed hop-by-hop response header listed in Connection header for name: {header}"
93 );
94 }
95 }
96 _ = headers.remove(header::CONNECTION);
97 }
98 for header in [
99 &header::CONNECTION,
100 &header::KEEP_ALIVE,
101 &header::PROXY_AUTHENTICATE,
102 &header::TRAILER,
103 &header::TRANSFER_ENCODING,
104 &header::UPGRADE,
105 ] {
106 while headers.remove(header).is_some() {
107 tracing::trace!("removed hop-by-hop response header for name: {header}");
108 }
109 }
110}
111
112pub fn remove_illegal_h2_request_headers(headers: &mut HeaderMap) {
121 while let Some(c) = headers.typed_get::<Connection>() {
122 for header in c.iter_headers() {
123 while headers.remove(header).is_some() {
124 tracing::trace!(
125 header = %header,
126 "removed connection-specific request header listed in Connection header for name"
127 );
128 }
129 }
130 _ = headers.remove(header::CONNECTION);
131 }
132 for header in [
133 &header::CONNECTION,
134 &header::PROXY_CONNECTION,
135 &header::KEEP_ALIVE,
136 &header::TRANSFER_ENCODING,
137 &header::UPGRADE,
138 &header::SEC_WEBSOCKET_KEY,
139 &header::HOST,
140 ] {
141 while headers.remove(header).is_some() {
142 tracing::trace!(
143 header = %header,
144 "removed illegal (~http1) header from h2 request for name"
145 );
146 }
147 }
148
149 let te_is_legal = headers
152 .get_all(header::TE)
153 .iter()
154 .all(|v| v.as_bytes().trim_ascii().eq_ignore_ascii_case(b"trailers"));
155 if !te_is_legal {
156 while headers.remove(header::TE).is_some() {
157 tracing::trace!(
158 "removed illegal TE header (only `TE: trailers` is valid) from h2 request"
159 );
160 }
161 }
162}
163
164pub fn remove_illegal_h2_response_headers(headers: &mut HeaderMap) {
177 while let Some(c) = headers.typed_get::<Connection>() {
178 for header in c.iter_headers() {
179 while headers.remove(header).is_some() {
180 tracing::trace!(
181 header = %header,
182 "removed connection-specific response header listed in Connection header for name"
183 );
184 }
185 }
186 _ = headers.remove(header::CONNECTION);
187 }
188 for header in [
189 &header::CONNECTION,
190 &header::PROXY_CONNECTION,
191 &header::KEEP_ALIVE,
192 &header::TRANSFER_ENCODING,
193 &header::UPGRADE,
194 ] {
195 while headers.remove(header).is_some() {
196 tracing::trace!(
197 header = %header,
198 "removed illegal (~http1) header from h2 response for name"
199 );
200 }
201 }
202}
203
204pub fn remove_sensitive_request_headers(headers: &mut HeaderMap) {
214 for header in [
215 &header::AUTHORIZATION,
216 &header::PROXY_AUTHORIZATION,
217 &header::COOKIE,
218 ] {
219 while headers.remove(header).is_some() {
220 tracing::trace!("removed sensitive request header for name: {header}");
221 }
222 }
223 remove_headers_if(
224 headers,
225 |name, _value| is_sensitive_header_name(name),
226 "sensitive request header",
227 );
228}
229
230pub fn remove_sensitive_response_headers(headers: &mut HeaderMap) {
238 for header in [&header::SET_COOKIE] {
239 while headers.remove(header).is_some() {
240 tracing::trace!("removed sensitive response header for name: {header}");
241 }
242 }
243}
244
245pub fn remove_payload_metadata_headers(headers: &mut HeaderMap) {
253 for header in [
254 &header::CONTENT_ENCODING,
255 &header::TRANSFER_ENCODING,
256 &header::ACCEPT_RANGES,
257 &header::CONTENT_LENGTH,
258 ] {
259 while headers.remove(header).is_some() {
260 tracing::trace!("removed payload header for name: {header}");
261 }
262 }
263}
264
265pub fn remove_cache_validation_request_headers(headers: &mut HeaderMap) {
276 for header in [
277 &header::IF_NONE_MATCH,
278 &header::IF_MODIFIED_SINCE,
279 &header::IF_MATCH,
280 &header::IF_UNMODIFIED_SINCE,
281 &header::IF_RANGE,
282 &header::RANGE,
283 ] {
284 while headers.remove(header).is_some() {
285 tracing::trace!("removed cache validation request header for name: {header}");
286 }
287 }
288}
289
290pub fn remove_cache_validation_response_headers(headers: &mut HeaderMap) {
300 for header in [
301 &header::ETAG,
302 &header::LAST_MODIFIED,
303 &header::ACCEPT_RANGES,
304 &header::CONTENT_RANGE,
305 ] {
306 while headers.remove(header).is_some() {
307 tracing::trace!("removed cache validation response header for name: {header}");
308 }
309 }
310}
311
312pub fn remove_cache_policy_headers(headers: &mut HeaderMap) {
321 for header in [
322 &header::CACHE_CONTROL,
323 &header::PRAGMA,
324 &header::EXPIRES,
325 &header::AGE,
326 &header::WARNING,
327 ] {
328 while headers.remove(header).is_some() {
329 tracing::trace!("removed cache policy header for name: {header}");
330 }
331 }
332}
333
334#[inline(always)]
335fn is_sensitive_header_name(name: &HeaderName) -> bool {
336 any_submatch_ignore_ascii_case(
337 name.as_str(),
338 ["api-key", "auth-token", "access-token", "security-token"],
339 )
340}
341
342fn remove_headers_if<F>(headers: &mut HeaderMap, mut remove: F, log_context: &str)
343where
344 F: FnMut(&HeaderName, &HeaderValue) -> bool,
345{
346 loop {
347 let name_to_remove: Option<HeaderName> = headers
348 .iter()
349 .find_map(|(name, value)| remove(name, value).then(|| name.clone()));
350
351 let Some(name) = name_to_remove else { break };
352
353 while headers.remove(&name).is_some() {
354 tracing::trace!("{log_context}: removed header: {name}");
355 }
356 }
357}