Skip to main content

cratestack_axum/headers/
forwarded.rs

1use axum::http::HeaderMap;
2
3/// Extract the most-specific client IP available from the request headers,
4/// falling back to none. Prefers `Forwarded` (RFC 7239) over the legacy
5/// `X-Forwarded-For`. Banks running behind a single trusted L7 take the
6/// leftmost entry; deeper proxy chains must verify and rewrite at the edge.
7pub fn parse_client_ip(headers: &HeaderMap) -> Option<String> {
8    if let Some(forwarded) = headers.get("forwarded").and_then(|v| v.to_str().ok()) {
9        for segment in forwarded.split(',').map(str::trim) {
10            for kv in segment.split(';').map(str::trim) {
11                if let Some(rest) = kv.strip_prefix("for=") {
12                    let cleaned = rest.trim_matches('"');
13                    let cleaned = cleaned
14                        .strip_prefix('[')
15                        .and_then(|s| s.strip_suffix(']'))
16                        .unwrap_or(cleaned);
17                    if !cleaned.is_empty() {
18                        return Some(cleaned.to_owned());
19                    }
20                }
21            }
22        }
23    }
24    headers
25        .get("x-forwarded-for")
26        .and_then(|v| v.to_str().ok())
27        .and_then(|raw| raw.split(',').next())
28        .map(|s| s.trim().to_owned())
29        .filter(|s| !s.is_empty())
30}