Skip to main content

pingap_core/
http_header.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Import necessary modules and types from supervisors and external crates.
16use super::{Ctx, get_hostname};
17use ahash::AHashSet;
18use arc_swap::ArcSwapOption;
19use bytes::BytesMut;
20use http::header;
21use http::{HeaderName, HeaderValue};
22use ipnet::IpNet;
23use pingora::http::RequestHeader;
24use pingora::proxy::Session;
25use snafu::{ResultExt, Snafu};
26use std::borrow::Cow;
27use std::net::{IpAddr, SocketAddr};
28use std::str::FromStr;
29use std::sync::Arc;
30use std::sync::atomic::{AtomicBool, Ordering};
31
32// Define string constants for commonly used HTTP header names.
33const HTTP_HEADER_X_FORWARDED_FOR: &str = "x-forwarded-for";
34const HTTP_HEADER_X_REAL_IP: &str = "x-real-ip";
35
36// Define byte slice constants for special variable tags used in header value processing.
37// These are matched against the raw bytes of a header value.
38pub const HOST_NAME_TAG: &[u8] = b"$hostname";
39const HOST_TAG: &[u8] = b"$host";
40const SCHEME_TAG: &[u8] = b"$scheme";
41const REMOTE_ADDR_TAG: &[u8] = b"$remote_addr";
42const REMOTE_PORT_TAG: &[u8] = b"$remote_port";
43const SERVER_ADDR_TAG: &[u8] = b"$server_addr";
44const SERVER_PORT_TAG: &[u8] = b"$server_port";
45const PROXY_ADD_FORWARDED_TAG: &[u8] = b"$proxy_add_x_forwarded_for";
46const UPSTREAM_ADDR_TAG: &[u8] = b"$upstream_addr";
47const JA4_TAG: &[u8] = b"$ja4";
48const CLIENT_IP_TAG: &[u8] = b"$client_ip";
49const FORWARDED_PROTO_TAG: &[u8] = b"$forwarded_proto";
50const FORWARDED_HOST_TAG: &[u8] = b"$forwarded_host";
51const FORWARDED_PORT_TAG: &[u8] = b"$forwarded_port";
52const TLS_CLIENT_SUBJECT_TAG: &[u8] = b"$tls_client_subject";
53const TLS_CLIENT_FINGERPRINT_TAG: &[u8] = b"$tls_client_fingerprint";
54const TLS_CLIENT_SERIAL_TAG: &[u8] = b"$tls_client_serial";
55const TLS_CLIENT_VERIFIED_TAG: &[u8] = b"$tls_client_verified";
56
57// Define static HeaderValues for HTTP and HTTPS schemes to avoid re-creation.
58static SCHEME_HTTPS: HeaderValue = HeaderValue::from_static("https");
59static SCHEME_HTTP: HeaderValue = HeaderValue::from_static("http");
60
61/// Defines the custom error types for this module using the snafu crate.
62#[derive(Debug, Snafu)]
63pub enum Error {
64    /// Error for when a string cannot be parsed into a valid HeaderValue.
65    #[snafu(display("invalid header value: {value} - {source}"))]
66    InvalidHeaderValue {
67        value: String,
68        source: header::InvalidHeaderValue,
69    },
70    /// Error for when a string cannot be parsed into a valid HeaderName.
71    #[snafu(display("invalid header name: {value} - {source}"))]
72    InvalidHeaderName {
73        value: String,
74        source: header::InvalidHeaderName,
75    },
76}
77/// A convenient type alias for `Result` with the module's `Error` type.
78type Result<T, E = Error> = std::result::Result<T, E>;
79
80/// A type alias for a tuple representing an HTTP header.
81pub type HttpHeader = (HeaderName, HeaderValue);
82
83/// Gets the request host by checking the URI first, then falling back to the "Host" header.
84///
85/// This function follows the common practice of prioritizing the host from the absolute URI
86/// (e.g., in `GET http://example.com/path HTTP/1.1`) over the `Host` header field.
87pub fn get_host(header: &RequestHeader) -> Option<&str> {
88    get_request_host(header).map(strip_root_label)
89}
90
91/// The host of the request as it was sent, less its port: what
92/// [`get_host`] gives before it drops the root label.
93///
94/// The cache key is made of this one. The request goes to the upstream
95/// with the `Host` it came with, and an upstream that answers
96/// `example.com.` differently from `example.com` - it does not know the
97/// name, or writes it into a redirect - must not have that answer stored
98/// as the answer for the other.
99pub fn get_request_host(header: &RequestHeader) -> Option<&str> {
100    // First, try to get the host directly from the parsed URI.
101    // http2 will always have a host in the uri
102    match header.uri.host() {
103        Some(host) => Some(host),
104        // If not in the URI, fall back to the "Host" header.
105        None => header
106            .headers
107            .get(http::header::HOST)
108            // Convert the header value to a string slice.
109            .and_then(|value| value.to_str().ok())
110            .map(strip_port),
111    }
112}
113
114/// `example.com.` is `example.com` with the root label written out, and
115/// names the same host. Matched as it came, it missed every location
116/// configured for the host and fell through to the catch-all, past the
117/// plugins that guard the host, while the upstream served it as that host.
118#[inline]
119pub fn strip_root_label(host: &str) -> &str {
120    match host.strip_suffix('.') {
121        Some(name) if !name.is_empty() => name,
122        _ => host,
123    }
124}
125
126/// Puts the cookies of a request into one `Cookie` field.
127///
128/// HTTP/2 lets a client send them as several fields (RFC 9113 8.2.3), and
129/// everything that reads a cookie reads the first field: a token in the
130/// second was not found by `jwt`, a sticky cookie not by the traffic
131/// split, a `{~name}` not by the access log. An HTTP/1.1 upstream is owed
132/// the single field anyway. A request with one field, or none, is left as
133/// it is.
134pub fn merge_cookie_headers(header: &mut RequestHeader) {
135    let mut values = header.headers.get_all(http::header::COOKIE).iter();
136    let (Some(first), Some(second)) = (values.next(), values.next()) else {
137        return;
138    };
139    let mut merged = Vec::with_capacity(
140        first.len() + second.len() + 2 + values.size_hint().0 * 16,
141    );
142    merged.extend_from_slice(first.as_bytes());
143    for value in std::iter::once(second).chain(values) {
144        merged.extend_from_slice(b"; ");
145        merged.extend_from_slice(value.as_bytes());
146    }
147    if let Ok(value) = HeaderValue::from_bytes(&merged) {
148        let _ = header.insert_header(http::header::COOKIE, value);
149    }
150}
151
152/// Drops the `:port` suffix of a `Host` value. An IPv6 literal keeps its
153/// brackets, and the colons inside them are not a port separator:
154/// `[::1]:8080` is `[::1]`, the same form `Uri::host` reports.
155fn strip_port(host: &str) -> &str {
156    if host.starts_with('[') {
157        return match host.find(']') {
158            Some(end) => &host[..=end],
159            None => host,
160        };
161    }
162    host.split(':').next().unwrap_or(host)
163}
164
165/// Converts a single string in "name: value" format into an `HttpHeader` tuple.
166///
167/// This is a utility function for parsing header configurations. It trims whitespace
168/// from both the name and the value.
169pub fn convert_header(value: &str) -> Result<Option<HttpHeader>> {
170    // `split_once` is an efficient way to split the string into two parts at the first colon.
171    value
172        .split_once(':')
173        // If a colon exists, map the key and value parts.
174        .map(|(k, v)| {
175            // Parse the trimmed key into a HeaderName, wrapping errors.
176            let name = HeaderName::from_str(k.trim())
177                .context(InvalidHeaderNameSnafu { value: k })?;
178            // Parse the trimmed value into a HeaderValue, wrapping errors.
179            let value = HeaderValue::from_str(v.trim())
180                .context(InvalidHeaderValueSnafu { value: v })?;
181            // If both parsing steps succeed, return the header tuple.
182            Ok(Some((name, value)))
183        })
184        // If `split_once` returns None (no colon), default to `Ok(None)`.
185        .unwrap_or(Ok(None))
186}
187
188/// Converts a slice of strings into a `Vec` of `HttpHeader`s.
189///
190/// This function iterates over a list of header strings and uses `convert_header`
191/// on each, collecting the valid results into a vector.
192pub fn convert_headers(header_values: &[String]) -> Result<Vec<HttpHeader>> {
193    header_values
194        .iter()
195        // `filter_map` is used to iterate, convert, and filter out `None` results elegantly.
196        // `transpose` flips `Option<Result<T>>` to `Result<Option<T>>`, which is what `filter_map` expects.
197        .filter_map(|item| convert_header(item).transpose())
198        // `collect` gathers the `Result<HttpHeader>` items. If any item is an `Err`, `collect` will return that `Err`.
199        .collect()
200}
201
202// Define common, pre-built HTTP headers as static constants for reuse and performance.
203pub static HTTP_HEADER_NO_STORE: HttpHeader = (
204    header::CACHE_CONTROL,
205    HeaderValue::from_static("private, no-store"),
206);
207pub static HTTP_HEADER_NO_CACHE: HttpHeader = (
208    header::CACHE_CONTROL,
209    HeaderValue::from_static("private, no-cache"),
210);
211pub static HTTP_HEADER_CONTENT_JSON: HttpHeader = (
212    header::CONTENT_TYPE,
213    HeaderValue::from_static("application/json; charset=utf-8"),
214);
215pub static HTTP_HEADER_CONTENT_HTML: HttpHeader = (
216    header::CONTENT_TYPE,
217    HeaderValue::from_static("text/html; charset=utf-8"),
218);
219pub static HTTP_HEADER_CONTENT_TEXT: HttpHeader = (
220    header::CONTENT_TYPE,
221    HeaderValue::from_static("text/plain; charset=utf-8"),
222);
223pub static HTTP_HEADER_TRANSFER_CHUNKED: HttpHeader = (
224    header::TRANSFER_ENCODING,
225    HeaderValue::from_static("chunked"),
226);
227pub static HTTP_HEADER_NAME_X_REQUEST_ID: HeaderName =
228    HeaderName::from_static("x-request-id");
229
230/// Resolves a header value that cannot change for the life of the process -
231/// `$hostname` and `$<ENV_VAR>` - so it can be stored as a plain value when
232/// the configuration is loaded instead of being looked up on every request
233/// (`std::env::var` takes a process-wide lock and allocates each time).
234/// Values that depend on the request (`$host`, `$http_*`, `:key`, ...) and
235/// plain values come back unchanged, as does a `$NAME` whose variable is not
236/// set, so the request path treats it exactly as before.
237pub fn resolve_static_header_value(value: HeaderValue) -> HeaderValue {
238    let buf = value.as_bytes();
239    if buf.first() != Some(&b'$') {
240        return value;
241    }
242    if buf == HOST_NAME_TAG {
243        return HeaderValue::from_str(get_hostname()).unwrap_or(value);
244    }
245    let request_tags: [&[u8]; 17] = [
246        HOST_TAG,
247        SCHEME_TAG,
248        REMOTE_ADDR_TAG,
249        REMOTE_PORT_TAG,
250        SERVER_ADDR_TAG,
251        SERVER_PORT_TAG,
252        PROXY_ADD_FORWARDED_TAG,
253        UPSTREAM_ADDR_TAG,
254        JA4_TAG,
255        CLIENT_IP_TAG,
256        FORWARDED_PROTO_TAG,
257        FORWARDED_HOST_TAG,
258        FORWARDED_PORT_TAG,
259        TLS_CLIENT_SUBJECT_TAG,
260        TLS_CLIENT_FINGERPRINT_TAG,
261        TLS_CLIENT_SERIAL_TAG,
262        TLS_CLIENT_VERIFIED_TAG,
263    ];
264    if request_tags.contains(&buf) || buf.starts_with(b"$http_") {
265        return value;
266    }
267    let Ok(name) = std::str::from_utf8(&buf[1..]) else {
268        return value;
269    };
270    match std::env::var(name) {
271        Ok(env_value) => HeaderValue::from_str(&env_value).unwrap_or(value),
272        Err(_) => value,
273    }
274}
275
276/// One field of the client's certificate as a header value: empty when
277/// the client showed none, or the field can not be a header value.
278fn tls_client_value(
279    ctx: &Ctx,
280    field: impl Fn(&crate::TlsClientCert) -> &str,
281) -> HeaderValue {
282    ctx.conn
283        .tls_client_cert
284        .as_deref()
285        .and_then(|cert| HeaderValue::from_str(field(cert)).ok())
286        .unwrap_or_else(|| HeaderValue::from_static(""))
287}
288
289/// Processes a `HeaderValue` that may contain a special dynamic variable (e.g., `$host`).
290/// It replaces the variable with its corresponding runtime value.
291#[inline]
292pub fn convert_header_value(
293    value: &HeaderValue,
294    session: &Session,
295    ctx: &Ctx,
296) -> Option<HeaderValue> {
297    // Work with the raw byte representation of the header value for efficient matching.
298    let buf = value.as_bytes();
299
300    // Perform a quick check for the special variable prefix ('$' or ':') to exit early
301    // for normal header values, which is the most common case.
302    if buf.is_empty() || !(buf[0] == b'$' || buf[0] == b':') {
303        return None;
304    }
305
306    // A helper closure to reduce boilerplate when converting a string slice to a HeaderValue.
307    let to_header_value = |s: &str| HeaderValue::from_str(s).ok();
308
309    // Match the entire byte slice against the predefined variable tags.
310    match buf {
311        HOST_TAG => get_host(session.req_header()).and_then(to_header_value),
312        SCHEME_TAG => Some(if ctx.conn.tls_version.is_some() {
313            SCHEME_HTTPS.clone()
314        } else {
315            SCHEME_HTTP.clone()
316        }),
317        HOST_NAME_TAG => to_header_value(get_hostname()),
318        REMOTE_ADDR_TAG => {
319            ctx.conn.remote_addr.as_deref().and_then(to_header_value)
320        },
321        REMOTE_PORT_TAG => ctx.conn.remote_port.and_then(|p| {
322            // Use `itoa` to format the integer directly into a valid header value
323            // without creating an intermediate `String`.
324            HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
325        }),
326        SERVER_ADDR_TAG => {
327            ctx.conn.server_addr.as_deref().and_then(to_header_value)
328        },
329        SERVER_PORT_TAG => ctx.conn.server_port.and_then(|p| {
330            HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
331        }),
332        UPSTREAM_ADDR_TAG => {
333            if !ctx.upstream.address.is_empty() {
334                to_header_value(&ctx.upstream.address)
335            } else {
336                None
337            }
338        },
339        JA4_TAG => ctx
340            .conn
341            .ja4
342            .as_deref()
343            .and_then(|fingerprint| to_header_value(fingerprint.ja4())),
344        // The address of the client, as far as it can be vouched for:
345        // what a trusted proxy says of it, and the peer's own where there
346        // is none - or no list of them, in which case a forwarded header
347        // is only what the request claims.
348        CLIENT_IP_TAG => {
349            if has_trusted_proxies() {
350                match ctx.conn.client_ip.as_deref() {
351                    Some(client_ip) => to_header_value(client_ip),
352                    None => to_header_value(&get_client_ip(session)),
353                }
354            } else {
355                ctx.conn.remote_addr.as_deref().and_then(to_header_value)
356            }
357        },
358        // The scheme and the host the client used, which behind a proxy
359        // that ends TLS are not the ones this connection has: what a
360        // trusted proxy says of them, and this connection's otherwise.
361        FORWARDED_PROTO_TAG => Some(if client_used_https(session, ctx) {
362            SCHEME_HTTPS.clone()
363        } else {
364            SCHEME_HTTP.clone()
365        }),
366        // Only what reads as the name of a host, with or without a port:
367        // it goes into a header the upstream may build links from.
368        FORWARDED_HOST_TAG => {
369            forwarded_by_trusted_proxy(session, &HTTP_HEADER_X_FORWARDED_HOST)
370                .filter(|host| is_host_name(host))
371                .and_then(to_header_value)
372                .or_else(|| {
373                    get_host(session.req_header()).and_then(to_header_value)
374                })
375        },
376        // The certificate the client showed in the handshake, on a server
377        // that asks for one. Without a certificate the value is empty and
378        // not missing: the header is set all the same, and what the
379        // client sent under that name does not reach the upstream as if
380        // the proxy had vouched for it.
381        TLS_CLIENT_SUBJECT_TAG => {
382            Some(tls_client_value(ctx, |cert| cert.subject.as_str()))
383        },
384        TLS_CLIENT_FINGERPRINT_TAG => {
385            Some(tls_client_value(ctx, |cert| cert.fingerprint.as_str()))
386        },
387        TLS_CLIENT_SERIAL_TAG => {
388            Some(tls_client_value(ctx, |cert| cert.serial.as_str()))
389        },
390        TLS_CLIENT_VERIFIED_TAG => Some(HeaderValue::from_static(
391            if ctx.conn.tls_client_cert.is_some() {
392                "true"
393            } else {
394                "false"
395            },
396        )),
397        // The port that goes with the scheme above: the one the proxy
398        // names, or the default of the scheme it names. Without either it
399        // is the port of this listener, as it has always been.
400        FORWARDED_PORT_TAG => {
401            let forwarded = forwarded_by_trusted_proxy(
402                session,
403                &HTTP_HEADER_X_FORWARDED_PORT,
404            )
405            .and_then(|port| port.parse::<u16>().ok())
406            .filter(|port| *port > 0)
407            .or_else(|| {
408                forwarded_scheme(session)
409                    .map(|https| if https { 443 } else { 80 })
410            })
411            .or(ctx.conn.server_port);
412            forwarded.and_then(|port| {
413                HeaderValue::from_str(itoa::Buffer::new().format(port)).ok()
414            })
415        },
416        PROXY_ADD_FORWARDED_TAG => {
417            ctx.conn.remote_addr.as_deref().and_then(|remote_addr| {
418                // Build the new `x-forwarded-for` value efficiently using `BytesMut` to avoid `format!`.
419                // Every line of the request goes in, in order: a proxy in
420                // front may add its entry as a line of its own, and with
421                // only the first line kept that entry - the client's real
422                // address - was lost, leaving what the client wrote.
423                let existing = session
424                    .req_header()
425                    .headers
426                    .get_all(HTTP_HEADER_X_FORWARDED_FOR);
427                let capacity = existing
428                    .iter()
429                    .map(|v| v.as_bytes().len() + 2)
430                    .sum::<usize>()
431                    + remote_addr.len();
432                let mut value_buf = BytesMut::with_capacity(capacity);
433                for value in existing.iter() {
434                    value_buf.extend_from_slice(value.as_bytes());
435                    value_buf.extend_from_slice(b", ");
436                }
437                value_buf.extend_from_slice(remote_addr.as_bytes());
438                HeaderValue::from_bytes(&value_buf).ok()
439            })
440        },
441        // If no predefined tag matches, it might be a different type of variable (e.g., `$http_...`).
442        _ => handle_special_headers(buf, session, ctx),
443    }
444}
445
446/// A helper function to handle more complex or less common special header variables.
447/// This function is called as a fallback from `convert_header_value`.
448#[inline]
449fn handle_special_headers(
450    buf: &[u8],
451    session: &Session,
452    ctx: &Ctx,
453) -> Option<HeaderValue> {
454    // Handle variables that reference other request headers, like `$http_user_agent`.
455    if let Some(name) = buf.strip_prefix(b"$http_") {
456        let key = std::str::from_utf8(name).ok()?;
457        // nginx spelling: `$http_user_agent` names `User-Agent`. Header names
458        // never contain underscores, so mapping them is unambiguous.
459        if key.contains('_') {
460            return session.get_header(key.replace('_', "-")).cloned();
461        }
462        return session.get_header(key).cloned();
463    }
464    // Handle variables that reference environment variables, like `$PATH`.
465    if buf.starts_with(b"$") {
466        let var_name = std::str::from_utf8(&buf[1..]).ok()?;
467        // Look up the environment variable and convert its value to a HeaderValue.
468        return std::env::var(var_name)
469            .ok()
470            .and_then(|v| HeaderValue::from_str(&v).ok());
471    }
472    // Handle variables that reference fields in the `Ctx` struct, like `:connection_id`.
473    if buf.starts_with(b":") {
474        let key = std::str::from_utf8(&buf[1..]).ok()?;
475        // Use `append_log_value` to get the string representation of the context field.
476        let mut value = BytesMut::with_capacity(20);
477        ctx.append_log_value(&mut value, key);
478        if !value.is_empty() {
479            // Convert the resulting bytes to a HeaderValue.
480            return HeaderValue::from_bytes(&value).ok();
481        }
482    }
483    // If no pattern matches, return None.
484    None
485}
486
487/// Gets the remote address (IP and port) from the session.
488///
489/// An IPv4 client of a dual-stack listener (`[::]:80`) has the address
490/// `::ffff:1.2.3.4`. It is reported as `1.2.3.4`: left in the mapped form
491/// it matched no IPv4 entry of an allow or deny list, no IPv4 trusted
492/// proxy, and no country.
493pub fn get_remote_addr(session: &Session) -> Option<(String, u16)> {
494    session
495        .client_addr()
496        // Ensure the address is an IP address (v4 or v6).
497        .and_then(|addr| addr.as_inet())
498        // Map it to a tuple of (String, u16).
499        .map(|addr| (addr.ip().to_canonical().to_string(), addr.port()))
500}
501
502/// Parsed trusted downstream proxy addresses: individual IPs plus CIDR
503/// networks. Kept small and local since it is only used by `get_client_ip`.
504struct TrustedProxies {
505    nets: Vec<IpNet>,
506    ips: AHashSet<IpAddr>,
507}
508
509impl TrustedProxies {
510    /// Parses a list of IPs / CIDR ranges. Invalid entries are ignored (a
511    /// mistyped proxy simply fails closed: its forwarded headers are dropped).
512    fn parse(values: &[String]) -> Self {
513        let mut nets = Vec::new();
514        let mut ips = AHashSet::new();
515        for item in values {
516            if let Ok(net) = IpNet::from_str(item) {
517                nets.push(net);
518            } else if let Ok(ip) = IpAddr::from_str(item) {
519                ips.insert(ip.to_canonical());
520            }
521        }
522        Self { nets, ips }
523    }
524
525    /// Returns true if `peer` is one of the trusted proxies.
526    fn contains(&self, peer: IpAddr) -> bool {
527        let peer = peer.to_canonical();
528        self.ips.contains(&peer)
529            || self.nets.iter().any(|net| net.contains(&peer))
530    }
531}
532
533// Trusted downstream proxies. When configured, the forwarded client-IP headers
534// (`X-Forwarded-For` / `X-Real-IP`) are only honoured for connections whose
535// direct TCP peer is one of these addresses; a client connecting directly must
536// not be able to spoof its IP for IP-based access control, rate limiting, etc.
537// A cheap atomic flag keeps the common "not configured" path to one load;
538// the configured path reads the list through an `ArcSwap`, so a reload never
539// makes a request wait on a lock.
540static TRUSTED_PROXIES_ENABLED: AtomicBool = AtomicBool::new(false);
541static TRUSTED_PROXIES: ArcSwapOption<TrustedProxies> =
542    ArcSwapOption::const_empty();
543
544static HTTP_HEADER_X_FORWARDED_PROTO: HeaderName =
545    HeaderName::from_static("x-forwarded-proto");
546static HTTP_HEADER_X_FORWARDED_HOST: HeaderName =
547    HeaderName::from_static("x-forwarded-host");
548static HTTP_HEADER_X_FORWARDED_PORT: HeaderName =
549    HeaderName::from_static("x-forwarded-port");
550
551/// Whether the connection of the request comes from one of the trusted
552/// proxies. Never without a list of them: there is then nobody whose word
553/// about a client could be taken.
554pub fn peer_is_trusted_proxy(session: &Session) -> bool {
555    if !has_trusted_proxies() {
556        return false;
557    }
558    let Some(peer) = session
559        .client_addr()
560        .and_then(|addr| addr.as_inet())
561        .map(|addr| addr.ip())
562    else {
563        return false;
564    };
565    TRUSTED_PROXIES
566        .load()
567        .as_ref()
568        .is_some_and(|trusted| trusted.contains(peer))
569}
570
571/// The first entry of a forwarded header, over all of its lines: the
572/// one the proxy nearest to the client wrote, in a chain of proxies that
573/// each add theirs. `https, http` is a client on https and a hop inside
574/// the chain on plain http.
575fn first_forwarded_entry<'a>(
576    values: impl Iterator<Item = &'a HeaderValue>,
577) -> Option<&'a str> {
578    values
579        .filter_map(|value| value.to_str().ok())
580        .flat_map(|value| value.split(','))
581        .map(str::trim)
582        .find(|entry| !entry.is_empty())
583}
584
585/// What a trusted proxy says under `name`: the first entry of the header,
586/// when the request came through one and has it. From anyone else the
587/// header is something the client wrote.
588///
589/// The proxy has to write the header itself and not pass on what it was
590/// sent: one that only appends, or leaves the client's in place, hands on
591/// the client's word under its own name.
592fn forwarded_by_trusted_proxy<'a>(
593    session: &'a Session,
594    name: &HeaderName,
595) -> Option<&'a str> {
596    let entry = first_forwarded_entry(
597        session.req_header().headers.get_all(name).iter(),
598    )?;
599    peer_is_trusted_proxy(session).then_some(entry)
600}
601
602/// The scheme a trusted proxy says the client used: `Some(true)` for
603/// https, `Some(false)` for http, `None` when there is no proxy to say or
604/// it says something else.
605fn forwarded_scheme(session: &Session) -> Option<bool> {
606    let scheme =
607        forwarded_by_trusted_proxy(session, &HTTP_HEADER_X_FORWARDED_PROTO)?;
608    if scheme.eq_ignore_ascii_case("https") {
609        Some(true)
610    } else if scheme.eq_ignore_ascii_case("http") {
611        Some(false)
612    } else {
613        None
614    }
615}
616
617/// Whether the client reached the site over https, as far as that can be
618/// vouched for: what a trusted proxy says of it, and otherwise whether
619/// this connection is one.
620pub fn client_used_https(session: &Session, ctx: &Ctx) -> bool {
621    forwarded_scheme(session).unwrap_or(ctx.conn.tls_version.is_some())
622}
623
624/// Whether `value` reads as a host, with or without a port, and nothing
625/// more: no user in front of it, no path behind it.
626fn is_host_name(value: &str) -> bool {
627    value
628        .parse::<http::uri::Authority>()
629        .is_ok_and(|authority| {
630            !authority.host().is_empty() && !authority.as_str().contains('@')
631        })
632}
633
634/// Sets the trusted downstream proxy addresses (individual IPs or CIDR ranges).
635///
636/// When a non-empty list is configured, forwarded headers are only trusted for
637/// connections coming directly from one of these addresses. Passing `None` or
638/// an empty list restores the default behaviour of trusting forwarded headers
639/// unconditionally (backwards compatible). Safe to call repeatedly on reload.
640pub fn set_trusted_proxies(proxies: &Option<Vec<String>>) {
641    let parsed = match proxies {
642        Some(list) if !list.is_empty() => Some(TrustedProxies::parse(list)),
643        _ => None,
644    };
645    let enabled = parsed.is_some();
646    TRUSTED_PROXIES.store(parsed.map(Arc::new));
647    // Set last: a request that sees the flag also sees the list.
648    TRUSTED_PROXIES_ENABLED.store(enabled, Ordering::Relaxed);
649}
650
651/// An `X-Forwarded-For` entry as an address. Besides the plain form some
652/// proxies write `ip:port` or `[v6]:port`.
653fn parse_forwarded_ip(value: &str) -> Option<IpAddr> {
654    let ip = if let Ok(ip) = IpAddr::from_str(value) {
655        ip
656    } else if let Ok(addr) = SocketAddr::from_str(value) {
657        addr.ip()
658    } else {
659        value
660            .strip_prefix('[')
661            .and_then(|value| value.strip_suffix(']'))
662            .and_then(|value| IpAddr::from_str(value).ok())?
663    };
664    // `::ffff:1.2.3.4` is `1.2.3.4`, see `get_remote_addr`.
665    Some(ip.to_canonical())
666}
667
668/// The client address out of the `X-Forwarded-For` lines of a request that
669/// came through a trusted proxy.
670///
671/// Each proxy appends the address it received the request from, so the list
672/// is read from the right: entries that are trusted proxies themselves are
673/// skipped, and the first one that is not is the client. Everything further
674/// left was written by that client and proves nothing - taking the first
675/// entry, as this used to, let `X-Forwarded-For: 6.6.6.6` through a trusted
676/// proxy pick the address the access rules saw.
677///
678/// When every entry is a trusted proxy the request started at one of them,
679/// and the left-most is returned.
680fn forwarded_client_ip<'a>(
681    values: impl DoubleEndedIterator<Item = &'a HeaderValue>,
682    trusted: &TrustedProxies,
683) -> Option<String> {
684    let mut first_proxy = None;
685    for value in values.rev() {
686        // Entry by entry on the bytes, never the line as text. A line is
687        // text only when all of it is, and its left end is the client's to
688        // write: one byte there that is not ASCII would discard the line,
689        // and with it the address the proxy appended on the right.
690        for item in value.as_bytes().rsplit(|b| *b == b',') {
691            let item = item.trim_ascii();
692            if item.is_empty() {
693                continue;
694            }
695            // Not text, so not an address and not a trusted proxy.
696            let Ok(item) = std::str::from_utf8(item) else {
697                return Some(String::from_utf8_lossy(item).into_owned());
698            };
699            match parse_forwarded_ip(item) {
700                Some(ip) if trusted.contains(ip) => first_proxy = Some(ip),
701                Some(ip) => return Some(ip.to_string()),
702                None => return Some(item.to_string()),
703            }
704        }
705    }
706    first_proxy.map(|ip| ip.to_string())
707}
708
709/// Whether `basic.trusted_proxies` is set.
710#[inline]
711pub fn has_trusted_proxies() -> bool {
712    TRUSTED_PROXIES_ENABLED.load(Ordering::Relaxed)
713}
714
715/// Whether `ip` is one of `basic.trusted_proxies`: an address whose word
716/// about a client is taken, before there is a request to ask it of (the
717/// PROXY protocol header of a connection). Never without a list.
718pub fn is_trusted_proxy(ip: IpAddr) -> bool {
719    if !has_trusted_proxies() {
720        return false;
721    }
722    TRUSTED_PROXIES
723        .load()
724        .as_ref()
725        .is_some_and(|trusted| trusted.contains(ip.to_canonical()))
726}
727
728/// The address to check a request by when the check grants something to the
729/// few, such as the allow list of the cache's `PURGE`.
730///
731/// With trusted proxies configured this is the client ip, which a client
732/// can not choose then. Without them the forwarded headers are simply what
733/// the request says, so the address is the peer's own: a list that allows
734/// `127.0.0.1` used to let in anyone who sent `X-Forwarded-For: 127.0.0.1`.
735#[inline]
736pub fn ensure_verified_client_ip<'a>(
737    session: &Session,
738    ctx: &'a mut Ctx,
739) -> &'a str {
740    if has_trusted_proxies() {
741        return ensure_client_ip(session, ctx);
742    }
743    ctx.conn.remote_addr.as_deref().unwrap_or_default()
744}
745
746/// Ensures `ctx.conn.client_ip` is populated and returns a borrowed reference.
747///
748/// Prefer this on the request path over calling [`get_client_ip`] repeatedly —
749/// the first call allocates once and subsequent callers reuse the cached value.
750#[inline]
751pub fn ensure_client_ip<'a>(session: &Session, ctx: &'a mut Ctx) -> &'a str {
752    if ctx.conn.client_ip.is_none() {
753        ctx.conn.client_ip = Some(get_client_ip(session));
754    }
755    // Just inserted or already present — never None after the block above.
756    ctx.conn.client_ip.as_deref().unwrap_or_default()
757}
758
759/// Gets the client's IP address.
760///
761/// When trusted proxies are configured, `X-Forwarded-For` / `X-Real-IP` are
762/// only honoured if the direct TCP peer is a trusted proxy; otherwise the
763/// peer's own address is returned. Through a trusted proxy the address is
764/// the right-most `X-Forwarded-For` entry that is not a trusted proxy (see
765/// [`forwarded_client_ip`]), then `X-Real-IP`, then the peer.
766///
767/// When no trusted proxies are configured the lookup order is:
768/// 1. `X-Forwarded-For` (taking the first IP in the list)
769/// 2. `X-Real-IP`
770/// 3. The remote address of the direct TCP connection
771pub fn get_client_ip(session: &Session) -> String {
772    // When trusted proxies are configured, a direct (untrusted) peer's
773    // forwarded headers must be ignored to prevent client-IP spoofing.
774    if TRUSTED_PROXIES_ENABLED.load(Ordering::Relaxed) {
775        // Compare the address itself; formatting it only to parse it back
776        // would cost an allocation per request.
777        let Some(peer) = session
778            .client_addr()
779            .and_then(|addr| addr.as_inet())
780            .map(|addr| addr.ip().to_canonical())
781        else {
782            return String::new();
783        };
784        let trusted = TRUSTED_PROXIES.load();
785        let Some(trusted) =
786            trusted.as_ref().filter(|trusted| trusted.contains(peer))
787        else {
788            return peer.to_string();
789        };
790        let headers = &session.req_header().headers;
791        if let Some(ip) = forwarded_client_ip(
792            headers.get_all(HTTP_HEADER_X_FORWARDED_FOR).iter(),
793            trusted,
794        ) {
795            return ip;
796        }
797        if let Some(ip) = headers
798            .get(HTTP_HEADER_X_REAL_IP)
799            .and_then(|value| value.to_str().ok())
800            .map(|value| value.trim())
801            .filter(|value| !value.is_empty())
802        {
803            return ip.to_string();
804        }
805        return peer.to_string();
806    }
807    // 1. Check `X-Forwarded-For`.
808    if let Some(value) = session.get_header(HTTP_HEADER_X_FORWARDED_FOR) {
809        // Efficiently take the first IP without creating an intermediate Vec.
810        if let Ok(s) = value.to_str()
811            && let Some(ip) = s.split(',').next()
812        {
813            let trimmed_ip = ip.trim();
814            if !trimmed_ip.is_empty() {
815                return trimmed_ip.to_string();
816            }
817        }
818    }
819    // 2. Check `X-Real-IP`.
820    if let Some(value) = session.get_header(HTTP_HEADER_X_REAL_IP) {
821        return value.to_str().unwrap_or_default().to_string();
822    }
823    // 3. Fall back to the direct connection's remote address.
824    if let Some((addr, _)) = get_remote_addr(session) {
825        return addr;
826    }
827    // If all checks fail, return an empty string.
828    "".to_string()
829}
830
831/// A convenient helper to get a header value as a `&str` from a `RequestHeader`.
832pub fn get_req_header_value<'a>(
833    req_header: &'a RequestHeader,
834    key: &str,
835) -> Option<&'a str> {
836    // Get the header by its key.
837    if let Some(value) = req_header.headers.get(key) {
838        // Try to convert it to a string slice. Fails if the value is not valid UTF-8.
839        if let Ok(value) = value.to_str() {
840            return Some(value);
841        }
842    }
843    None
844}
845
846/// Parses the "Cookie" header to find the value of a specific cookie.
847pub fn get_cookie_value<'a>(
848    req_header: &'a RequestHeader,
849    cookie_name: &str,
850) -> Option<&'a str> {
851    // First, get the entire "Cookie" header string. The '?' operator will short-circuit if it's not present.
852    get_req_header_value(req_header, "cookie")?
853        // Split the string into individual cookies.
854        .split(';')
855        // `find_map` is an efficient way to find the first cookie that matches our criteria.
856        .find_map(|item| {
857            // This chained logic attempts to quickly find a match.
858            // It's more complex to handle cases like "key=value" vs "key=" correctly.
859            item.trim()
860                .strip_prefix(cookie_name)?
861                .strip_prefix('=')
862                .or_else(|| {
863                    // Fallback logic to ensure the cookie name is an exact match.
864                    let (k, v) = item.split_once('=')?;
865                    if k.trim() == cookie_name {
866                        Some(v.trim())
867                    } else {
868                        None
869                    }
870                })
871        })
872}
873
874/// Gets the value of a specific query parameter from the request URI.
875pub fn get_query_value<'a>(
876    req_header: &'a RequestHeader,
877    name: &str,
878) -> Option<&'a str> {
879    // Get the query string from the URI, exiting if it doesn't exist.
880    req_header
881        .uri
882        .query()?
883        // Split the query string into key-value pairs.
884        .split('&')
885        // `find_map` efficiently searches for the first pair where the key matches.
886        .find_map(|item| {
887            // Split the pair into key and value.
888            let (k, v) = item.split_once('=')?;
889            // If the key matches, return the value.
890            if k == name { Some(v) } else { None }
891        })
892}
893
894/// The value of a query parameter as the client meant it, with its
895/// percent-encoding decoded. `+` stays a plus: that it stands for a space
896/// is a rule of html forms, not of urls. A value that does not decode to
897/// text is returned as it came.
898pub fn decode_query_value(raw: &str) -> Cow<'_, str> {
899    if !raw.contains('%') {
900        return Cow::Borrowed(raw);
901    }
902    urlencoding::decode(raw).unwrap_or(Cow::Borrowed(raw))
903}
904
905/// Removes a specific query parameter from the request header's URI.
906///
907/// This function modifies the `req_header` in place.
908pub fn remove_query_from_header(
909    req_header: &mut RequestHeader,
910    name: &str,
911) -> Result<(), http::Error> {
912    // If there is no query string, there is nothing to do.
913    let Some(query_str) = req_header.uri.query() else {
914        return Ok(());
915    };
916
917    // Pre-allocate a String with enough capacity to hold the new query string,
918    // which is a performance optimization to avoid reallocations.
919    let mut new_query = String::with_capacity(query_str.len());
920
921    // Iterate over each key-value pair in the original query string.
922    for item in query_str.split('&') {
923        // Get the key part of the pair.
924        let key = item.split('=').next().unwrap_or(item);
925
926        // If the key is not the one we want to remove, keep the item.
927        if key != name {
928            // If the new query string is not empty, add a separator first.
929            if !new_query.is_empty() {
930                new_query.push('&');
931            }
932            // Append the original "key=value" slice, which is allocation-free.
933            new_query.push_str(item);
934        }
935    }
936
937    // The path as it was, with what is left of the query.
938    let path = req_header.uri.path();
939    let mut path_and_query =
940        String::with_capacity(path.len() + 1 + new_query.len());
941    path_and_query.push_str(path);
942    if !new_query.is_empty() {
943        path_and_query.push('?');
944        path_and_query.push_str(&new_query);
945    }
946
947    set_path_and_query(req_header, &path_and_query)
948}
949
950/// Whether `path` has anything for `normalize_path` to do: a `%`, a path
951/// parameter (`;`), a backslash, an empty segment (`//`), or a segment that
952/// is `.` or `..`.
953fn path_needs_normalizing(path: &[u8]) -> bool {
954    let mut after_slash = false;
955    for (index, byte) in path.iter().enumerate() {
956        match byte {
957            b'%' | b';' | b'\\' => return true,
958            b'/' if after_slash => return true,
959            b'.' if after_slash => {
960                let rest = &path[index + 1..];
961                let rest = rest.strip_prefix(b".").unwrap_or(rest);
962                if rest.first().is_none_or(|next| *next == b'/') {
963                    return true;
964                }
965            },
966            _ => {},
967        }
968        after_slash = *byte == b'/';
969    }
970    false
971}
972
973/// The path of a request in the form it is matched by: percent-encoding
974/// decoded, path parameters left out, a backslash taken for a slash, `.`
975/// and `..` segments resolved, repeated slashes merged.
976///
977/// Which location a request belongs to is decided on this form, because it
978/// is the form most upstreams go on to serve. Matching the path as it was
979/// sent let `/%61dmin`, `//admin` and `/public/../admin` slip past a
980/// location for `/admin`, and whatever plugins guard it, on their way to
981/// an upstream that reads all three as `/admin`. Only the matching uses
982/// it; the request is forwarded as it came.
983///
984/// Two of these are what some upstreams read and others do not. A servlet
985/// container drops the `;name=value` of a segment before it looks at the
986/// path, so to Tomcat `/public/..;/admin` and `/api;v=1/admin` are
987/// `/admin` and `/api/admin`; IIS takes `\` for `/`. An upstream that does
988/// neither has nothing at such a path, so reading it the way the others do
989/// costs it nothing, and the ones that do are no longer reached past the
990/// location that was meant to stand in front of them.
991///
992/// A path with nothing to change, which is nearly every path, is returned
993/// as it is.
994pub fn normalize_path(path: &str) -> Cow<'_, str> {
995    match normalized_path_bytes(path) {
996        None => Cow::Borrowed(path),
997        Some(normalized) => {
998            Cow::Owned(String::from_utf8_lossy(&normalized).into_owned())
999        },
1000    }
1001}
1002
1003/// [`normalize_path`] in a form that can be sent on: the same path, with
1004/// what a uri cannot carry as it is - a space, a `?` that was `%3F`, a
1005/// byte that is not ASCII - percent-encoded again.
1006///
1007/// This is the path a location's `rewrite` falls back on. The rule used
1008/// to be matched against the path as it was sent and nothing else, while
1009/// the location had been chosen by the normalized one: `/%75sers/x`
1010/// reached the location for `/users` and then slipped past its
1011/// `^/users/(.*)$ /acme/$1`, to arrive at the upstream as `/users/x`
1012/// without the prefix; `/users/../other` took the prefix and left it again
1013/// one segment later.
1014///
1015/// It is not the path to rewrite every request by: it says less than the
1016/// path that was sent. `%2F` has become a separator, `;jsessionid=...` is
1017/// gone, `//` is one slash. See `Location::rewrite` for when it is used.
1018///
1019/// A path with nothing to change is returned as it is, like there.
1020pub fn canonical_path(path: &str) -> Cow<'_, str> {
1021    let Some(normalized) = normalized_path_bytes(path) else {
1022        return Cow::Borrowed(path);
1023    };
1024    let mut encoded = String::with_capacity(normalized.len() + 8);
1025    for byte in normalized {
1026        // What a path may hold unencoded (RFC 3986 `pchar`, and the `/`
1027        // between segments).
1028        let plain = byte.is_ascii_alphanumeric()
1029            || matches!(
1030                byte,
1031                b'/' | b'-'
1032                    | b'.'
1033                    | b'_'
1034                    | b'~'
1035                    | b'!'
1036                    | b'$'
1037                    | b'&'
1038                    | b'\''
1039                    | b'('
1040                    | b')'
1041                    | b'*'
1042                    | b'+'
1043                    | b','
1044                    | b';'
1045                    | b'='
1046                    | b':'
1047                    | b'@'
1048            );
1049        if plain {
1050            encoded.push(byte as char);
1051        } else {
1052            encoded.push('%');
1053            encoded.push(char::from(b"0123456789ABCDEF"[(byte >> 4) as usize]));
1054            encoded.push(char::from(b"0123456789ABCDEF"[(byte & 15) as usize]));
1055        }
1056    }
1057    Cow::Owned(encoded)
1058}
1059
1060/// Whether `path` has a `.` or `..` segment, read the way
1061/// [`normalize_path`] reads it: `%2e%2e`, `..;x`, and the ones that only
1062/// show once `%2F` or `\` is taken for a separator (`a%2F..%2Fb`).
1063///
1064/// A path without one cannot leave the directory it names under any of
1065/// those readings, since each of them finds its segments among the ones
1066/// found here. That is what makes such a path safe to rewrite as it was
1067/// sent.
1068pub fn has_dot_segments(path: &str) -> bool {
1069    normalized_path(path).is_some_and(|(_, dot_segments)| dot_segments)
1070}
1071
1072/// The bytes of the normalized path, `None` when the path is in that form
1073/// already - which is nearly every path.
1074fn normalized_path_bytes(path: &str) -> Option<Vec<u8>> {
1075    normalized_path(path).map(|(bytes, _)| bytes)
1076}
1077
1078/// [`normalized_path_bytes`], and whether a `.` or `..` segment was
1079/// resolved on the way.
1080fn normalized_path(path: &str) -> Option<(Vec<u8>, bool)> {
1081    let bytes = path.as_bytes();
1082    if !path.starts_with('/') || !path_needs_normalizing(bytes) {
1083        return None;
1084    }
1085    let hex = |byte: Option<&u8>| {
1086        byte.and_then(|byte| (*byte as char).to_digit(16))
1087            .map(|value| value as u8)
1088    };
1089    let mut decoded = Vec::with_capacity(bytes.len());
1090    let mut index = 0;
1091    while index < bytes.len() {
1092        if bytes[index] == b'%'
1093            && let (Some(high), Some(low)) =
1094                (hex(bytes.get(index + 1)), hex(bytes.get(index + 2)))
1095        {
1096            decoded.push(high << 4 | low);
1097            index += 3;
1098        } else {
1099            decoded.push(bytes[index]);
1100            index += 1;
1101        }
1102    }
1103
1104    let mut segments: Vec<&[u8]> = vec![];
1105    let mut dot_segments = false;
1106    // Whether the path ends in a directory, `/a/` as well as `/a/.`.
1107    let mut trailing_slash = false;
1108    for segment in decoded.split(|byte| matches!(byte, b'/' | b'\\')) {
1109        trailing_slash = true;
1110        // The name of the segment, without its parameters.
1111        let name = segment
1112            .split(|byte| *byte == b';')
1113            .next()
1114            .unwrap_or(segment);
1115        match name {
1116            b"" => {},
1117            b"." => dot_segments = true,
1118            b".." => {
1119                dot_segments = true;
1120                segments.pop();
1121            },
1122            _ => {
1123                segments.push(name);
1124                trailing_slash = false;
1125            },
1126        }
1127    }
1128    let mut normalized = Vec::with_capacity(decoded.len());
1129    for segment in segments.iter() {
1130        normalized.push(b'/');
1131        normalized.extend_from_slice(segment);
1132    }
1133    if trailing_slash || normalized.is_empty() {
1134        normalized.push(b'/');
1135    }
1136    Some((normalized, dot_segments))
1137}
1138
1139/// Replaces the path and query of the request, and nothing else.
1140///
1141/// An HTTP/2 request carries its host in the uri (`:authority`) and usually
1142/// has no `Host` header. Setting a uri parsed from the path alone took the
1143/// host away with it: the upstream request went out without a `Host`, and
1144/// whatever asked for the request's host afterwards - the cache key, the
1145/// access log - found none.
1146pub fn set_path_and_query(
1147    req_header: &mut RequestHeader,
1148    path_and_query: &str,
1149) -> Result<(), http::Error> {
1150    if req_header.uri.authority().is_none() {
1151        req_header.set_uri(http::Uri::from_str(path_and_query)?);
1152        return Ok(());
1153    }
1154    let path_and_query = http::uri::PathAndQuery::from_str(path_and_query)?;
1155    let mut parts = req_header.uri.clone().into_parts();
1156    parts.path_and_query = Some(path_and_query);
1157    // Scheme and authority are those of a uri that was valid already. One
1158    // that has an authority and no scheme takes no path at all: that is an
1159    // error like any other, not a path that was set.
1160    req_header.set_uri(http::Uri::from_parts(parts)?);
1161
1162    Ok(())
1163}
1164
1165/// Takes `names` out of what the `Connection` header of a request
1166/// nominates, for the headers the proxy itself has put on the request.
1167///
1168/// A header named in `Connection` is one its sender calls hop-by-hop, and
1169/// it is removed from the request that goes to the upstream. For a header
1170/// of the client's own that is as it should be. For one a plugin set - the
1171/// address of the client, what the auth service said of it - it let the
1172/// client decide that the upstream does not get it: `Connection: X-User-Id`
1173/// was enough. Only `Host` and the `X-Forwarded-*` headers were safe from
1174/// that.
1175///
1176/// The rest of the header stays as it is: `keep-alive`, `upgrade` and what
1177/// else the client nominated.
1178pub fn protect_from_connection_header(
1179    req: &mut RequestHeader,
1180    names: &[HeaderName],
1181) {
1182    if names.is_empty() || !req.headers.contains_key(header::CONNECTION) {
1183        return;
1184    }
1185    let nominated = |token: &str| {
1186        names
1187            .iter()
1188            .any(|name| name.as_str().eq_ignore_ascii_case(token))
1189    };
1190    let mut kept = vec![];
1191    let mut removed = false;
1192    for value in req.headers.get_all(header::CONNECTION).iter() {
1193        // A value that is not text has no name in it to take out.
1194        let Ok(value) = value.to_str() else {
1195            return;
1196        };
1197        for token in value.split(',').map(str::trim) {
1198            if token.is_empty() {
1199                continue;
1200            }
1201            if nominated(token) {
1202                removed = true;
1203            } else {
1204                kept.push(token);
1205            }
1206        }
1207    }
1208    if !removed {
1209        return;
1210    }
1211    let kept = kept.join(", ");
1212    req.remove_header(&header::CONNECTION);
1213    if !kept.is_empty() {
1214        let _ = req.insert_header(header::CONNECTION, kept);
1215    }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220    use super::*;
1221    use crate::{
1222        ConnectionInfo, TlsClientCert, UpstreamInfo, new_test_session,
1223    };
1224    use pretty_assertions::assert_eq;
1225
1226    /// Regression: a client could name a header the proxy set in its
1227    /// `Connection`, and the upstream never got it.
1228    #[test]
1229    fn test_protect_from_connection_header() {
1230        let protected = [
1231            HeaderName::from_static("x-real-ip"),
1232            HeaderName::from_static("x-user-id"),
1233        ];
1234        let after = |connection: &[&str]| {
1235            let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1236            for value in connection {
1237                req.append_header(header::CONNECTION, *value).unwrap();
1238            }
1239            protect_from_connection_header(&mut req, &protected);
1240            req.headers
1241                .get_all(header::CONNECTION)
1242                .iter()
1243                .map(|value| value.to_str().unwrap().to_string())
1244                .collect::<Vec<_>>()
1245        };
1246        // Taken out, in whatever case and among whatever else.
1247        assert_eq!(Vec::<String>::new(), after(&["X-Real-IP"]));
1248        assert_eq!(vec!["keep-alive"], after(&["keep-alive, x-real-ip"]));
1249        assert_eq!(
1250            vec!["keep-alive, upgrade, X-Other"],
1251            after(&[" keep-alive ,X-USER-ID,upgrade", "x-real-ip, X-Other"])
1252        );
1253        // Nothing of ours in it: left exactly as it came.
1254        assert_eq!(
1255            vec!["keep-alive , Upgrade"],
1256            after(&["keep-alive , Upgrade"])
1257        );
1258        assert_eq!(vec!["close", "X-Other"], after(&["close", "X-Other"]));
1259        assert_eq!(Vec::<String>::new(), after(&[]));
1260        // A name that only begins like one of ours is another name.
1261        assert_eq!(vec!["x-real-ip-2"], after(&["x-real-ip-2"]));
1262    }
1263
1264    #[test]
1265    fn test_convert_headers() {
1266        let headers = convert_headers(&[
1267            "Content-Type: application/octet-stream".to_string(),
1268            "X-Server: $hostname".to_string(),
1269            "X-User: $USER".to_string(),
1270        ])
1271        .unwrap();
1272        assert_eq!(3, headers.len());
1273        assert_eq!("content-type", headers[0].0.to_string());
1274        assert_eq!("application/octet-stream", headers[0].1.to_str().unwrap());
1275        assert_eq!("x-server", headers[1].0.to_string());
1276        assert_eq!(false, headers[1].1.to_str().unwrap().is_empty());
1277        assert_eq!("x-user", headers[2].0.to_string());
1278        assert_eq!(false, headers[2].1.to_str().unwrap().is_empty());
1279    }
1280
1281    #[test]
1282    fn test_static_value() {
1283        assert_eq!(
1284            "cache-control: private, no-store",
1285            format!(
1286                "{}: {}",
1287                HTTP_HEADER_NO_STORE.0.to_string(),
1288                HTTP_HEADER_NO_STORE.1.to_str().unwrap_or_default()
1289            )
1290        );
1291
1292        assert_eq!(
1293            "cache-control: private, no-cache",
1294            format!(
1295                "{}: {}",
1296                HTTP_HEADER_NO_CACHE.0.to_string(),
1297                HTTP_HEADER_NO_CACHE.1.to_str().unwrap_or_default()
1298            )
1299        );
1300
1301        assert_eq!(
1302            "content-type: application/json; charset=utf-8",
1303            format!(
1304                "{}: {}",
1305                HTTP_HEADER_CONTENT_JSON.0.to_string(),
1306                HTTP_HEADER_CONTENT_JSON.1.to_str().unwrap_or_default()
1307            )
1308        );
1309
1310        assert_eq!(
1311            "content-type: text/html; charset=utf-8",
1312            format!(
1313                "{}: {}",
1314                HTTP_HEADER_CONTENT_HTML.0.to_string(),
1315                HTTP_HEADER_CONTENT_HTML.1.to_str().unwrap_or_default()
1316            )
1317        );
1318
1319        assert_eq!(
1320            "transfer-encoding: chunked",
1321            format!(
1322                "{}: {}",
1323                HTTP_HEADER_TRANSFER_CHUNKED.0.to_string(),
1324                HTTP_HEADER_TRANSFER_CHUNKED.1.to_str().unwrap_or_default()
1325            )
1326        );
1327
1328        assert_eq!("x-request-id", HTTP_HEADER_NAME_X_REQUEST_ID.to_string());
1329
1330        assert_eq!(
1331            "content-type: text/plain; charset=utf-8",
1332            format!(
1333                "{}: {}",
1334                HTTP_HEADER_CONTENT_TEXT.0.to_string(),
1335                HTTP_HEADER_CONTENT_TEXT.1.to_str().unwrap_or_default()
1336            )
1337        );
1338    }
1339
1340    /// The certificate of a client as header values: its fields with one,
1341    /// and empty - not left as the variable is written - without.
1342    #[tokio::test]
1343    async fn test_tls_client_header_values() {
1344        let session = new_test_session(&["Host: pingap.io"], "/").await;
1345        let value = |ctx: &Ctx, tag: &str| {
1346            convert_header_value(
1347                &HeaderValue::from_str(tag).unwrap(),
1348                &session,
1349                ctx,
1350            )
1351            .map(|value| value.to_str().unwrap().to_string())
1352        };
1353        let mut ctx = Ctx::default();
1354        for tag in [
1355            "$tls_client_subject",
1356            "$tls_client_fingerprint",
1357            "$tls_client_serial",
1358        ] {
1359            assert_eq!(Some("".to_string()), value(&ctx, tag), "{tag}");
1360        }
1361        assert_eq!(
1362            Some("false".to_string()),
1363            value(&ctx, "$tls_client_verified")
1364        );
1365
1366        ctx.conn.tls_client_cert = Some(std::sync::Arc::new(TlsClientCert {
1367            subject: "O=Example, CN=device-42".to_string(),
1368            fingerprint: "b545db7a".to_string(),
1369            serial: "3429".to_string(),
1370        }));
1371        assert_eq!(
1372            Some("O=Example, CN=device-42".to_string()),
1373            value(&ctx, "$tls_client_subject")
1374        );
1375        assert_eq!(
1376            Some("b545db7a".to_string()),
1377            value(&ctx, "$tls_client_fingerprint")
1378        );
1379        assert_eq!(Some("3429".to_string()), value(&ctx, "$tls_client_serial"));
1380        assert_eq!(
1381            Some("true".to_string()),
1382            value(&ctx, "$tls_client_verified")
1383        );
1384        // Not an environment variable to look up when the configuration
1385        // is read: it is the request's.
1386        let tag = HeaderValue::from_static("$tls_client_subject");
1387        assert_eq!(tag.clone(), resolve_static_header_value(tag));
1388    }
1389
1390    #[tokio::test]
1391    async fn test_convert_header_value() {
1392        let session =
1393            new_test_session(&["Host: pingap.io"], "/vicanso/pingap?size=1")
1394                .await;
1395        let default_state = Ctx {
1396            upstream: UpstreamInfo {
1397                address: "10.1.1.3:4123".to_string(),
1398                ..Default::default()
1399            },
1400            conn: ConnectionInfo {
1401                id: 102,
1402                remote_addr: Some("10.1.1.1".to_string()),
1403                remote_port: Some(6000),
1404                server_addr: Some("10.1.1.2".to_string()),
1405                server_port: Some(6001),
1406                tls_version: Some("tls1.3".into()),
1407                ..Default::default()
1408            },
1409            ..Default::default()
1410        };
1411
1412        let value = convert_header_value(
1413            &HeaderValue::from_str("$host").unwrap(),
1414            &session,
1415            &Ctx {
1416                ..Default::default()
1417            },
1418        );
1419        assert_eq!(true, value.is_some());
1420        assert_eq!("pingap.io", value.unwrap().to_str().unwrap());
1421
1422        let value = convert_header_value(
1423            &HeaderValue::from_str("$scheme").unwrap(),
1424            &session,
1425            &Ctx {
1426                ..Default::default()
1427            },
1428        );
1429        assert_eq!(true, value.is_some());
1430        assert_eq!("http", value.unwrap().to_str().unwrap());
1431        let value = convert_header_value(
1432            &HeaderValue::from_str("$scheme").unwrap(),
1433            &session,
1434            &default_state,
1435        );
1436        assert_eq!(true, value.is_some());
1437        assert_eq!("https", value.unwrap().to_str().unwrap());
1438
1439        let value = convert_header_value(
1440            &HeaderValue::from_str("$remote_addr").unwrap(),
1441            &session,
1442            &default_state,
1443        );
1444        assert_eq!(true, value.is_some());
1445        assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
1446
1447        let value = convert_header_value(
1448            &HeaderValue::from_str("$remote_port").unwrap(),
1449            &session,
1450            &default_state,
1451        );
1452        assert_eq!(true, value.is_some());
1453        assert_eq!("6000", value.unwrap().to_str().unwrap());
1454
1455        let value = convert_header_value(
1456            &HeaderValue::from_str("$server_addr").unwrap(),
1457            &session,
1458            &default_state,
1459        );
1460        assert_eq!(true, value.is_some());
1461        assert_eq!("10.1.1.2", value.unwrap().to_str().unwrap());
1462
1463        let value = convert_header_value(
1464            &HeaderValue::from_str("$server_port").unwrap(),
1465            &session,
1466            &default_state,
1467        );
1468        assert_eq!(true, value.is_some());
1469        assert_eq!("6001", value.unwrap().to_str().unwrap());
1470
1471        let value = convert_header_value(
1472            &HeaderValue::from_str("$upstream_addr").unwrap(),
1473            &session,
1474            &default_state,
1475        );
1476        assert_eq!(true, value.is_some());
1477        assert_eq!("10.1.1.3:4123", value.unwrap().to_str().unwrap());
1478
1479        let value = convert_header_value(
1480            &HeaderValue::from_str(":connection_id").unwrap(),
1481            &session,
1482            &default_state,
1483        );
1484        assert_eq!(true, value.is_some());
1485        assert_eq!("102", value.unwrap().to_str().unwrap());
1486
1487        let session = new_test_session(
1488            &["X-Forwarded-For: 1.1.1.1, 2.2.2.2"],
1489            "/vicanso/pingap?size=1",
1490        )
1491        .await;
1492        let value = convert_header_value(
1493            &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
1494            &session,
1495            &Ctx {
1496                conn: ConnectionInfo {
1497                    remote_addr: Some("10.1.1.1".to_string()),
1498                    ..Default::default()
1499                },
1500                ..Default::default()
1501            },
1502        );
1503        assert_eq!(true, value.is_some());
1504        assert_eq!(
1505            "1.1.1.1, 2.2.2.2, 10.1.1.1",
1506            value.unwrap().to_str().unwrap()
1507        );
1508
1509        let session = new_test_session(&[""], "/vicanso/pingap?size=1").await;
1510        let value = convert_header_value(
1511            &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
1512            &session,
1513            &Ctx {
1514                conn: ConnectionInfo {
1515                    remote_addr: Some("10.1.1.1".to_string()),
1516                    ..Default::default()
1517                },
1518                ..Default::default()
1519            },
1520        );
1521        assert_eq!(true, value.is_some());
1522        assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
1523
1524        let session = new_test_session(&[""], "/vicanso/pingap?size=1").await;
1525        let value = convert_header_value(
1526            &HeaderValue::from_str("$upstream_addr").unwrap(),
1527            &session,
1528            &Ctx {
1529                upstream: UpstreamInfo {
1530                    address: "10.1.1.1:8001".to_string(),
1531                    ..Default::default()
1532                },
1533                ..Default::default()
1534            },
1535        );
1536        assert_eq!(true, value.is_some());
1537        assert_eq!("10.1.1.1:8001", value.unwrap().to_str().unwrap());
1538
1539        // `$ja4` and the log-field form `:ja4` read the connection's
1540        // fingerprint; without one there is nothing to set.
1541        let session = new_test_session(&[""], "/vicanso/pingap?size=1").await;
1542        let mut ctx = Ctx::default();
1543        for tag in ["$ja4", ":ja4"] {
1544            let value = convert_header_value(
1545                &HeaderValue::from_str(tag).unwrap(),
1546                &session,
1547                &ctx,
1548            );
1549            assert_eq!(true, value.is_none(), "{tag}");
1550        }
1551        ctx.conn.ja4 = Some(std::sync::Arc::new(
1552            crate::Ja4Fingerprint::from_client_hello(
1553                &crate::ja4::testing::spec_example_body(),
1554            )
1555            .unwrap(),
1556        ));
1557        for tag in ["$ja4", ":ja4"] {
1558            let value = convert_header_value(
1559                &HeaderValue::from_str(tag).unwrap(),
1560                &session,
1561                &ctx,
1562            );
1563            assert_eq!(
1564                "t13d1516h2_8daaf6152771_e5627efa2ab1",
1565                value.unwrap().to_str().unwrap(),
1566                "{tag}"
1567            );
1568        }
1569        // Never resolved as an environment variable at load time.
1570        assert_eq!(
1571            "$ja4",
1572            resolve_static_header_value(HeaderValue::from_static("$ja4"))
1573        );
1574
1575        let session = new_test_session(
1576            &["Origin: https://github.com"],
1577            "/vicanso/pingap?size=1",
1578        )
1579        .await;
1580        let value = convert_header_value(
1581            &HeaderValue::from_str("$http_origin").unwrap(),
1582            &session,
1583            &Ctx::default(),
1584        );
1585        assert_eq!(true, value.is_some());
1586        assert_eq!("https://github.com", value.unwrap().to_str().unwrap());
1587
1588        let session = new_test_session(
1589            &["Origin: https://github.com"],
1590            "/vicanso/pingap?size=1",
1591        )
1592        .await;
1593        let value = convert_header_value(
1594            &HeaderValue::from_str("$hostname").unwrap(),
1595            &session,
1596            &Ctx::default(),
1597        );
1598        assert_eq!(true, value.is_some());
1599
1600        let session = new_test_session(
1601            &["Origin: https://github.com"],
1602            "/vicanso/pingap?size=1",
1603        )
1604        .await;
1605        let value = convert_header_value(
1606            &HeaderValue::from_str("$HOME").unwrap(),
1607            &session,
1608            &Ctx::default(),
1609        );
1610        assert_eq!(true, value.is_some());
1611
1612        let session = new_test_session(
1613            &["Origin: https://github.com"],
1614            "/vicanso/pingap?size=1",
1615        )
1616        .await;
1617        let value = convert_header_value(
1618            &HeaderValue::from_str("UUID").unwrap(),
1619            &session,
1620            &Ctx::default(),
1621        );
1622        assert_eq!(false, value.is_some());
1623    }
1624
1625    #[tokio::test]
1626    async fn test_get_host() {
1627        let session =
1628            new_test_session(&["Host: pingap.io"], "/vicanso/pingap?size=1")
1629                .await;
1630        assert_eq!(get_host(session.req_header()), Some("pingap.io"));
1631    }
1632
1633    #[test]
1634    fn test_remove_query_from_header() {
1635        let mut req =
1636            RequestHeader::build("GET", b"/?apikey=123", None).unwrap();
1637        remove_query_from_header(&mut req, "apikey").unwrap();
1638        assert_eq!("/", req.uri.to_string());
1639
1640        let mut req =
1641            RequestHeader::build("GET", b"/?apikey=123&name=pingap", None)
1642                .unwrap();
1643        remove_query_from_header(&mut req, "apikey").unwrap();
1644        assert_eq!("/?name=pingap", req.uri.to_string());
1645    }
1646
1647    #[tokio::test]
1648    async fn test_get_client_ip() {
1649        let session = new_test_session(
1650            &["X-Forwarded-For:192.168.1.1"],
1651            "/vicanso/pingap?size=1",
1652        )
1653        .await;
1654        assert_eq!(get_client_ip(&session), "192.168.1.1");
1655
1656        let session = new_test_session(
1657            &["X-Real-Ip:192.168.1.2"],
1658            "/vicanso/pingap?size=1",
1659        )
1660        .await;
1661        assert_eq!(get_client_ip(&session), "192.168.1.2");
1662
1663        // What is passed on about the client is what can be vouched for.
1664        // Without trusted proxies that is this connection: its peer, its
1665        // scheme and the host it asked for, whatever the request claims
1666        // in the headers a proxy would set.
1667        let claims = [
1668            "Host: pingap.io",
1669            "X-Forwarded-For: 192.168.1.1",
1670            "X-Forwarded-Proto: https",
1671            "X-Forwarded-Host: other.example",
1672        ];
1673        let session = new_test_session(&claims, "/").await;
1674        let mut ctx = Ctx::default();
1675        ctx.conn.remote_addr = Some("10.1.1.1".to_string());
1676        let resolve = |name: &'static str, session: &Session, ctx: &Ctx| {
1677            convert_header_value(&HeaderValue::from_static(name), session, ctx)
1678                .map(|value| value.to_str().unwrap().to_string())
1679        };
1680        assert_eq!(false, peer_is_trusted_proxy(&session));
1681        ctx.conn.server_port = Some(8080);
1682        for (name, expected) in [
1683            ("$client_ip", "10.1.1.1"),
1684            ("$forwarded_proto", "http"),
1685            ("$forwarded_host", "pingap.io"),
1686            ("$forwarded_port", "8080"),
1687        ] {
1688            assert_eq!(
1689                Some(expected.to_string()),
1690                resolve(name, &session, &ctx),
1691                "{name}"
1692            );
1693        }
1694        ctx.conn.tls_version = Some(Cow::Borrowed("TLSv1.3"));
1695        assert_eq!(
1696            Some("https".to_string()),
1697            resolve("$forwarded_proto", &session, &ctx)
1698        );
1699        // They are of the request, not of the environment: left for the
1700        // request path when the configuration is loaded.
1701        for name in [
1702            "$client_ip",
1703            "$forwarded_proto",
1704            "$forwarded_host",
1705            "$forwarded_port",
1706        ] {
1707            assert_eq!(
1708                name,
1709                resolve_static_header_value(HeaderValue::from_static(name))
1710            );
1711        }
1712
1713        // Of a header a chain of proxies each added to, the first entry
1714        // is the client's end of it, whether they wrote one line or
1715        // several.
1716        let entry = |lines: &[&'static str]| {
1717            let values: Vec<HeaderValue> = lines
1718                .iter()
1719                .map(|line| HeaderValue::from_static(line))
1720                .collect();
1721            first_forwarded_entry(values.iter()).map(str::to_string)
1722        };
1723        assert_eq!(Some("https".to_string()), entry(&["https, http"]));
1724        assert_eq!(Some("https".to_string()), entry(&[" , https", "http"]));
1725        assert_eq!(Some("https".to_string()), entry(&["https", "http"]));
1726        assert_eq!(None, entry(&[]));
1727        assert_eq!(None, entry(&[" , "]));
1728        // What is passed on as a host has to read as one.
1729        for host in ["shop.example.com", "shop.example.com:8443", "[::1]:80"] {
1730            assert_eq!(true, is_host_name(host), "{host}");
1731        }
1732        for host in [
1733            "",
1734            "user@shop.example.com",
1735            "shop.example.com/path",
1736            "shop example.com",
1737            "evil.example\r\nx: 1",
1738        ] {
1739            assert_eq!(false, is_host_name(host), "{host}");
1740        }
1741
1742        // With trusted proxies configured, a forwarded header from an untrusted
1743        // direct peer (the mock session has no trusted peer address) must be
1744        // ignored instead of being taken at face value.
1745        set_trusted_proxies(&Some(vec!["10.0.0.0/8".to_string()]));
1746        let session = new_test_session(&claims, "/").await;
1747        assert_eq!(false, peer_is_trusted_proxy(&session));
1748        ctx.conn.tls_version = None;
1749        assert_eq!(
1750            Some("http".to_string()),
1751            resolve("$forwarded_proto", &session, &ctx)
1752        );
1753        assert_eq!(
1754            Some("pingap.io".to_string()),
1755            resolve("$forwarded_host", &session, &ctx)
1756        );
1757        // The client ip that was worked out for the request is the one.
1758        ctx.conn.client_ip = Some("203.0.113.9".to_string());
1759        assert_eq!(
1760            Some("203.0.113.9".to_string()),
1761            resolve("$client_ip", &session, &ctx)
1762        );
1763        let session = new_test_session(
1764            &["X-Forwarded-For:192.168.1.1"],
1765            "/vicanso/pingap?size=1",
1766        )
1767        .await;
1768        assert_ne!(get_client_ip(&session), "192.168.1.1");
1769
1770        // Restoring the default trusts forwarded headers again.
1771        set_trusted_proxies(&None);
1772        let session = new_test_session(
1773            &["X-Forwarded-For:192.168.1.1"],
1774            "/vicanso/pingap?size=1",
1775        )
1776        .await;
1777        assert_eq!(get_client_ip(&session), "192.168.1.1");
1778    }
1779
1780    #[test]
1781    fn test_forwarded_client_ip() {
1782        let trusted = TrustedProxies::parse(&[
1783            "10.0.0.0/8".to_string(),
1784            "192.168.1.1".to_string(),
1785            "fd00::/8".to_string(),
1786        ]);
1787        let client_ip = |lines: &[&str]| {
1788            let values: Vec<HeaderValue> = lines
1789                .iter()
1790                .map(|line| HeaderValue::from_str(line).unwrap())
1791                .collect();
1792            forwarded_client_ip(values.iter(), &trusted)
1793        };
1794        let ip = |value: &str| Some(value.to_string());
1795
1796        assert_eq!(ip("9.9.9.9"), client_ip(&["9.9.9.9"]));
1797        // Regression: what the client wrote comes first, the address the
1798        // trusted proxy saw comes last. The first one used to win.
1799        assert_eq!(ip("9.9.9.9"), client_ip(&["6.6.6.6, 9.9.9.9"]));
1800        assert_eq!(ip("9.9.9.9"), client_ip(&["6.6.6.6,10.1.1.1 , 9.9.9.9"]));
1801        // A chain of trusted proxies is skipped from the right.
1802        assert_eq!(
1803            ip("9.9.9.9"),
1804            client_ip(&["6.6.6.6, 9.9.9.9, 10.0.0.2, 192.168.1.1"])
1805        );
1806        assert_eq!(ip("2001:db8::1"), client_ip(&["2001:db8::1, fd00::2"]));
1807        // Several header lines read as one list.
1808        assert_eq!(
1809            ip("9.9.9.9"),
1810            client_ip(&["6.6.6.6", "9.9.9.9", "10.0.0.2"])
1811        );
1812        assert_eq!(ip("9.9.9.9"), client_ip(&["6.6.6.6, 9.9.9.9", "10.0.0.2"]));
1813        // The request started at a trusted proxy: the left-most of them.
1814        assert_eq!(ip("10.0.0.3"), client_ip(&["10.0.0.3, 10.0.0.2"]));
1815        // With a port, as some proxies write it.
1816        assert_eq!(ip("9.9.9.9"), client_ip(&["9.9.9.9:4321, 10.0.0.2:80"]));
1817        assert_eq!(
1818            ip("2001:db8::1"),
1819            client_ip(&["[2001:db8::1]:4321, [fd00::2]"])
1820        );
1821        // Not an address: not a trusted proxy either, so that is the answer
1822        // and nothing to its left is looked at.
1823        assert_eq!(ip("unknown"), client_ip(&["6.6.6.6, unknown, 10.0.0.2"]));
1824        assert_eq!(None, client_ip(&[]));
1825        assert_eq!(None, client_ip(&[" , "]));
1826
1827        // Regression: the IPv4-mapped form is the IPv4 address, as a
1828        // trusted proxy and as a client.
1829        assert_eq!(
1830            ip("9.9.9.9"),
1831            client_ip(&["6.6.6.6, ::ffff:9.9.9.9, ::ffff:10.0.0.2"])
1832        );
1833        assert_eq!(true, trusted.contains("::ffff:10.1.2.3".parse().unwrap()));
1834        assert_eq!(
1835            true,
1836            trusted.contains("::ffff:192.168.1.1".parse().unwrap())
1837        );
1838        assert_eq!(false, trusted.contains("::ffff:9.9.9.9".parse().unwrap()));
1839        let mapped = TrustedProxies::parse(&["::ffff:172.16.0.1".to_string()]);
1840        assert_eq!(true, mapped.contains("172.16.0.1".parse().unwrap()));
1841
1842        // Regression: a byte that is not ASCII, written by the client at
1843        // the front of the line the proxy appends to. The whole line used
1844        // to be dropped, leaving the client's own `X-Real-IP` or the
1845        // proxy's address as the answer.
1846        let line = HeaderValue::from_bytes(b"\xff\xfe, 9.9.9.9").unwrap();
1847        assert_eq!(
1848            ip("9.9.9.9"),
1849            forwarded_client_ip([&line].into_iter(), &trusted)
1850        );
1851        let line = HeaderValue::from_bytes(b"6.6.6.6, \xff, 9.9.9.9, 10.0.0.2")
1852            .unwrap();
1853        assert_eq!(
1854            ip("9.9.9.9"),
1855            forwarded_client_ip([&line].into_iter(), &trusted)
1856        );
1857    }
1858
1859    #[tokio::test]
1860    async fn test_get_header_value() {
1861        let session =
1862            new_test_session(&["Host: pingap.io"], "/vicanso/pingap?size=1")
1863                .await;
1864        assert_eq!(
1865            get_req_header_value(session.req_header(), "Host"),
1866            Some("pingap.io")
1867        );
1868    }
1869
1870    #[tokio::test]
1871    async fn test_get_cookie_value() {
1872        let session = new_test_session(
1873            &["Cookie: name=pingap"],
1874            "/vicanso/pingap?size=1",
1875        )
1876        .await;
1877        assert_eq!(
1878            get_cookie_value(session.req_header(), "name"),
1879            Some("pingap")
1880        );
1881    }
1882
1883    #[tokio::test]
1884    async fn test_get_query_value() {
1885        let session = new_test_session(
1886            &["X-Forwarded-For:192.168.1.1"],
1887            "/vicanso/pingap?size=1",
1888        )
1889        .await;
1890        assert_eq!(get_query_value(session.req_header(), "size"), Some("1"));
1891    }
1892
1893    /// Tests `convert_header` with edge cases like empty strings,
1894    /// strings without colons, and invalid header names/values.
1895    #[test]
1896    fn test_convert_header_edge_cases() {
1897        // Empty string should result in Ok(None).
1898        assert!(convert_header("").unwrap().is_none());
1899        // String without a colon should result in Ok(None).
1900        assert!(convert_header("no-colon").unwrap().is_none());
1901        // Invalid header name should result in an error.
1902        assert!(convert_header("Invalid Name: value").is_err());
1903        // Invalid header value (with newline) should result in an error.
1904        assert!(convert_header("Valid-Name: invalid\r\nvalue").is_err());
1905    }
1906
1907    /// Tests `get_host` logic with different request formats.
1908    #[test]
1909    fn test_get_host_variants() {
1910        // Case 1: Host is in the URI authority.
1911        let uri_string = "http://user:pass@authority.com/path";
1912
1913        // 使用 .parse() 或 from_str 来创建 Uri
1914        let uri = http::Uri::from_str(uri_string).unwrap();
1915        let mut req_with_authority =
1916            RequestHeader::build("GET", b"/path", None).unwrap();
1917        req_with_authority.set_uri(uri);
1918        assert_eq!(get_host(&req_with_authority), Some("authority.com"));
1919
1920        // Case 2: Host is in the "Host" header.
1921        let mut req_with_host_header =
1922            RequestHeader::build("GET", b"/path", None).unwrap();
1923        req_with_host_header
1924            .insert_header("Host", "header-host.com:8080")
1925            .unwrap();
1926        assert_eq!(get_host(&req_with_host_header), Some("header-host.com"));
1927
1928        // Case 3: No host information available.
1929        let req_no_host = RequestHeader::build("GET", b"/path", None).unwrap();
1930        assert_eq!(get_host(&req_no_host), None);
1931
1932        // Case 4: IPv6 literals keep their brackets and lose only the port.
1933        for (host, expected) in [
1934            ("[::1]:8080", "[::1]"),
1935            ("[::1]", "[::1]"),
1936            ("[2001:db8::1]:443", "[2001:db8::1]"),
1937            ("example.com", "example.com"),
1938            ("example.com:8080", "example.com"),
1939        ] {
1940            let mut req = RequestHeader::build("GET", b"/path", None).unwrap();
1941            req.insert_header("Host", host).unwrap();
1942            assert_eq!(get_host(&req), Some(expected), "{host}");
1943        }
1944    }
1945
1946    /// Regression: `example.com.` is `example.com` with the root label
1947    /// written out. Matched as it came it missed the locations of the host
1948    /// and went to the catch-all, past the plugins guarding the host.
1949    #[test]
1950    fn test_get_host_drops_the_root_label() {
1951        for (host, expected) in [
1952            ("admin.example.com.", "admin.example.com"),
1953            ("admin.example.com.:8443", "admin.example.com"),
1954            ("Admin.Example.com.", "Admin.Example.com"),
1955            ("admin.example.com", "admin.example.com"),
1956            // Only the one label: this is another, odd, name.
1957            ("admin.example.com..", "admin.example.com."),
1958            (".", "."),
1959        ] {
1960            let mut req = RequestHeader::build("GET", b"/path", None).unwrap();
1961            req.insert_header("Host", host).unwrap();
1962            assert_eq!(get_host(&req), Some(expected), "{host}");
1963        }
1964        // The `:authority` of HTTP/2 as well.
1965        let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1966        req.set_uri(http::Uri::from_static("https://admin.example.com./a"));
1967        assert_eq!(get_host(&req), Some("admin.example.com"));
1968        // As it was sent, which is what the cache key is made of.
1969        assert_eq!(get_request_host(&req), Some("admin.example.com."));
1970    }
1971
1972    /// The request is routed as `example.com` and goes to the upstream as
1973    /// `example.com.`. What the upstream answers to that name is not
1974    /// stored as the answer for the other.
1975    #[test]
1976    fn test_cache_key_keeps_the_host_as_sent() {
1977        let key = |host: &str| {
1978            let mut req = RequestHeader::build("GET", b"/a", None).unwrap();
1979            req.insert_header("Host", host).unwrap();
1980            let ctx = Ctx {
1981                cache: Some(Default::default()),
1982                ..Default::default()
1983            };
1984            format!("{:?}", crate::get_cache_key(&ctx, "GET", &req))
1985        };
1986        assert_ne!(key("example.com"), key("example.com."));
1987        assert_eq!(key("example.com"), key("EXAMPLE.com:8080"));
1988    }
1989
1990    /// Regression: HTTP/2 lets a client send its cookies as several
1991    /// fields, and every reader of a cookie looked at the first.
1992    #[test]
1993    fn test_merge_cookie_headers() {
1994        let cookies = |values: &[&str]| {
1995            let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1996            for value in values {
1997                req.append_header("cookie", *value).unwrap();
1998            }
1999            merge_cookie_headers(&mut req);
2000            let merged: Vec<String> = req
2001                .headers
2002                .get_all(http::header::COOKIE)
2003                .iter()
2004                .map(|value| value.to_str().unwrap().to_string())
2005                .collect();
2006            let c = get_cookie_value(&req, "c").map(|v| v.to_string());
2007            (merged, c)
2008        };
2009        assert_eq!(
2010            (vec!["a=1; b=2; c=3".to_string()], Some("3".to_string())),
2011            cookies(&["a=1", "b=2", "c=3"])
2012        );
2013        assert_eq!(
2014            (vec!["a=1; b=2; c=3".to_string()], Some("3".to_string())),
2015            cookies(&["a=1; b=2", "c=3"])
2016        );
2017        // One field, or none, is left as it is.
2018        assert_eq!(
2019            (vec!["a=1; c=3".to_string()], Some("3".to_string())),
2020            cookies(&["a=1; c=3"])
2021        );
2022        assert_eq!((vec![], None), cookies(&[]));
2023    }
2024
2025    /// Regression: only the first `X-Forwarded-For` line was carried over.
2026    /// A proxy in front that adds its entry as a line of its own - the
2027    /// address it saw the client at - lost that entry, and what was left
2028    /// was the line the client had written itself.
2029    #[tokio::test]
2030    async fn test_proxy_add_x_forwarded_for_keeps_every_line() {
2031        let session = new_test_session(
2032            &["X-Forwarded-For: 6.6.6.6", "X-Forwarded-For: 10.0.0.9"],
2033            "/",
2034        )
2035        .await;
2036        let value = convert_header_value(
2037            &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
2038            &session,
2039            &Ctx {
2040                conn: ConnectionInfo {
2041                    remote_addr: Some("10.0.0.1".to_string()),
2042                    ..Default::default()
2043                },
2044                ..Default::default()
2045            },
2046        );
2047        assert_eq!(
2048            "6.6.6.6, 10.0.0.9, 10.0.0.1",
2049            value.unwrap().to_str().unwrap()
2050        );
2051    }
2052
2053    #[test]
2054    fn test_resolve_static_header_value() {
2055        let resolve = |value: &str| {
2056            resolve_static_header_value(HeaderValue::from_str(value).unwrap())
2057        };
2058        assert_eq!(get_hostname(), resolve("$hostname").to_str().unwrap());
2059        let home = std::env::var("HOME").unwrap();
2060        assert_eq!(home, resolve("$HOME").to_str().unwrap());
2061        // Request-time variables, plain values and unset variables are left
2062        // for the request path.
2063        for value in [
2064            "$host",
2065            "$remote_addr",
2066            "$proxy_add_x_forwarded_for",
2067            "$http_origin",
2068            ":connection_id",
2069            "plain",
2070            "$PINGAP_TEST_UNSET_VARIABLE",
2071        ] {
2072            assert_eq!(value, resolve(value).to_str().unwrap());
2073        }
2074    }
2075
2076    /// `$http_<name>` accepts the nginx spelling with underscores.
2077    #[tokio::test]
2078    async fn test_http_variable_with_underscores() {
2079        let session = new_test_session(
2080            &["User-Agent: pingap/1", "X-Env-Name: prod"],
2081            "/",
2082        )
2083        .await;
2084        for (variable, expected) in [
2085            ("$http_user_agent", Some("pingap/1")),
2086            ("$http_user-agent", Some("pingap/1")),
2087            ("$http_x_env_name", Some("prod")),
2088            ("$http_missing_header", None),
2089        ] {
2090            let value = convert_header_value(
2091                &HeaderValue::from_str(variable).unwrap(),
2092                &session,
2093                &Ctx::default(),
2094            );
2095            assert_eq!(
2096                expected,
2097                value.as_ref().and_then(|v| v.to_str().ok()),
2098                "{variable}"
2099            );
2100        }
2101    }
2102
2103    /// Tests `get_cookie_value` with multiple cookies and edge cases.
2104    #[test]
2105    fn test_get_cookie_value_advanced() {
2106        let mut req = RequestHeader::build("GET", b"/", None).unwrap();
2107        req.insert_header("Cookie", "id=123; session=abc; theme=dark")
2108            .unwrap();
2109
2110        assert_eq!(get_cookie_value(&req, "session"), Some("abc"));
2111        assert_eq!(get_cookie_value(&req, "id"), Some("123"));
2112        assert_eq!(get_cookie_value(&req, "theme"), Some("dark"));
2113        // Test for a non-existent cookie.
2114        assert_eq!(get_cookie_value(&req, "lang"), None);
2115        // Test for a cookie name that is a prefix of another.
2116        assert_eq!(get_cookie_value(&req, "the"), None);
2117    }
2118
2119    /// The normalized path as it can be sent on: what `normalize_path`
2120    /// gives, with whatever a uri cannot carry encoded again.
2121    #[test]
2122    fn test_canonical_path() {
2123        // Nothing to change: the very same string.
2124        for path in ["/", "/users/x", "/a/b.c/d", "relative"] {
2125            assert_eq!(
2126                true,
2127                matches!(canonical_path(path), Cow::Borrowed(_)),
2128                "{path}"
2129            );
2130        }
2131        for (path, expected) in [
2132            ("/%75sers/x", "/users/x"),
2133            ("/users/../other", "/other"),
2134            ("//users/./x/", "/users/x/"),
2135            ("/users;v=1/x", "/users/x"),
2136            ("/a%20b", "/a%20b"),
2137            ("/a%3fb", "/a%3Fb"),
2138            ("/a%23b", "/a%23b"),
2139            ("/a%2fb", "/a/b"),
2140            ("/100%25", "/100%25"),
2141            // decoded once, not twice
2142            ("/%2561", "/%2561"),
2143            ("/caf%c3%a9", "/caf%C3%A9"), // spellchecker:disable-line
2144            // not text, and still the same bytes
2145            ("/%ff%00", "/%FF%00"),
2146        ] {
2147            assert_eq!(expected, canonical_path(path), "{path}");
2148        }
2149    }
2150
2151    #[test]
2152    fn test_has_dot_segments() {
2153        for path in [
2154            "/a/../b",
2155            "/a/./b",
2156            "/a/..",
2157            "/a/%2e%2e/b",
2158            "/a/%2E./b",
2159            "/a/..;x=1/b",
2160            "/a%2F..%2Fb",
2161            "/a%5c..%5cb",
2162            "/a\\..\\b",
2163            "/a/.;/b",
2164        ] {
2165            assert_eq!(true, has_dot_segments(path), "{path}");
2166        }
2167        for path in [
2168            "/",
2169            "/a/b",
2170            "/a..b/c",
2171            "/a/..b/c..",
2172            "/a/.../b",
2173            "/a%2Fb",
2174            "/a;x=../b",
2175            "/a/b;jsessionid=1",
2176            "//a//b",
2177            "/%2e%2ea/b",
2178        ] {
2179            assert_eq!(false, has_dot_segments(path), "{path}");
2180        }
2181    }
2182
2183    #[test]
2184    fn test_normalize_path() {
2185        // Nothing to do: the very same string comes back.
2186        for path in [
2187            "/",
2188            "/admin",
2189            "/admin/",
2190            "/a/b.c/d",
2191            "/.well-known/acme-challenge/token",
2192            "/.env",
2193            "/a/..b/c",
2194            "/a/b../c",
2195            "/a/...",
2196            "*",
2197            "",
2198        ] {
2199            assert_eq!(
2200                true,
2201                matches!(normalize_path(path), Cow::Borrowed(value) if value == path),
2202                "{path}"
2203            );
2204        }
2205        for (path, expected) in [
2206            // what a location for `/admin` used to miss
2207            ("/%61dmin", "/admin"),
2208            ("/%61dmin/users", "/admin/users"),
2209            ("//admin", "/admin"),
2210            ("/./admin", "/admin"),
2211            ("/public/../admin", "/admin"),
2212            ("/public/%2e%2e/admin", "/admin"),
2213            ("/public/%2E%2E%2Fadmin", "/admin"),
2214            ("/public/..%2fadmin/x", "/admin/x"),
2215            // above the root is the root
2216            ("/../../admin", "/admin"),
2217            ("/..", "/"),
2218            ("/.", "/"),
2219            ("//", "/"),
2220            // the end of the path keeps its shape
2221            ("/admin//", "/admin/"),
2222            ("/admin/.", "/admin/"),
2223            ("/admin/x/..", "/admin/"),
2224            ("/admin/x/../", "/admin/"),
2225            // decoded once, not until nothing is left to decode
2226            ("/%2561dmin", "/%61dmin"),
2227            // not an escape: left alone
2228            ("/100%", "/100%"),
2229            ("/a%zzb", "/a%zzb"),
2230            ("/a%4", "/a%4"),
2231            ("/%E6%96%87%E6%A1%A3/menu", "/文档/menu"),
2232            ("/a%20b", "/a b"),
2233            // Regression: a path parameter is no part of the name of its
2234            // segment. A servlet container reads these as the path on the
2235            // right, and a location for it was walked past.
2236            ("/public/..;/admin", "/admin"),
2237            ("/public/..;x=1/admin", "/admin"),
2238            ("/public/.;/admin", "/public/admin"),
2239            ("/public/%2e%2e%3b/admin", "/admin"),
2240            ("/api;v=1/admin", "/api/admin"),
2241            ("/admin;jsessionid=A1/users", "/admin/users"),
2242            ("/login;jsessionid=A1", "/login"),
2243            ("/a/;x/b", "/a/b"),
2244            ("/a/b;x/", "/a/b/"),
2245            ("/a/;x", "/a/"),
2246            // Regression: so is a backslash a separator to some.
2247            ("/public\\..\\admin", "/admin"),
2248            ("/public/..%5cadmin", "/admin"),
2249            ("/a\\b", "/a/b"),
2250            ("/a\\\\b\\", "/a/b/"),
2251        ] {
2252            assert_eq!(expected, normalize_path(path), "{path}");
2253        }
2254        // Bytes that are no text still give a path to match against.
2255        assert_eq!("/\u{fffd}/x", normalize_path("/%ff/x"));
2256    }
2257
2258    /// Regression: an HTTP/2 request has its host in the uri. Replacing the
2259    /// path, or dropping a query parameter, used to drop the host too.
2260    #[test]
2261    fn test_path_and_query_changes_keep_the_authority() {
2262        let mut req = RequestHeader::build("GET", b"/", None).unwrap();
2263        req.set_uri(http::Uri::from_static(
2264            "https://example.com/api/users?apikey=1&page=2",
2265        ));
2266        remove_query_from_header(&mut req, "apikey").unwrap();
2267        assert_eq!("https://example.com/api/users?page=2", req.uri.to_string());
2268        assert_eq!(Some("example.com"), get_host(&req));
2269
2270        set_path_and_query(&mut req, "/users?page=2").unwrap();
2271        assert_eq!("https://example.com/users?page=2", req.uri.to_string());
2272        set_path_and_query(&mut req, "/").unwrap();
2273        assert_eq!("https://example.com/", req.uri.to_string());
2274        // Not a path: the uri is left as it is.
2275        assert_eq!(true, set_path_and_query(&mut req, "/a b").is_err());
2276        assert_eq!("https://example.com/", req.uri.to_string());
2277
2278        // HTTP/1.1: the uri is the path and stays one.
2279        let mut req = RequestHeader::build("GET", b"/api?a=1", None).unwrap();
2280        set_path_and_query(&mut req, "/v2/api?a=1").unwrap();
2281        assert_eq!("/v2/api?a=1", req.uri.to_string());
2282    }
2283
2284    #[test]
2285    fn test_remove_query_from_header_variants() {
2286        // Case 1: Remove the only query param.
2287        let mut req =
2288            RequestHeader::build("GET", b"/path?key=val", None).unwrap();
2289        remove_query_from_header(&mut req, "key").unwrap();
2290        assert_eq!(req.uri.to_string(), "/path");
2291
2292        // Case 2: Remove the first of multiple params.
2293        let mut req =
2294            RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
2295                .unwrap();
2296        remove_query_from_header(&mut req, "key1").unwrap();
2297        assert_eq!(req.uri.to_string(), "/path?key2=val2");
2298
2299        // Case 3: Remove the last of multiple params.
2300        let mut req =
2301            RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
2302                .unwrap();
2303        remove_query_from_header(&mut req, "key2").unwrap();
2304        assert_eq!(req.uri.to_string(), "/path?key1=val1");
2305
2306        // Case 4: Remove a middle param.
2307        let mut req =
2308            RequestHeader::build("GET", b"/path?key1=v1&key2=v2&key3=v3", None)
2309                .unwrap();
2310        remove_query_from_header(&mut req, "key2").unwrap();
2311        assert_eq!(req.uri.to_string(), "/path?key1=v1&key3=v3");
2312
2313        // Case 5: Param to remove is not present.
2314        let mut req =
2315            RequestHeader::build("GET", b"/path?key=val", None).unwrap();
2316        remove_query_from_header(&mut req, "nonexistent").unwrap();
2317        assert_eq!(req.uri.to_string(), "/path?key=val");
2318
2319        // Case 6: No query string to begin with.
2320        let mut req = RequestHeader::build("GET", b"/path", None).unwrap();
2321        remove_query_from_header(&mut req, "key").unwrap();
2322        assert_eq!(req.uri.to_string(), "/path");
2323    }
2324}