A pit full of rusty nails
1use axum::http::HeaderValue;
2use hyper::{HeaderMap, header::FORWARDED};
3use nailfv::{Parser, extract_for};
4
5const X_REAL_IP: &str = "x-real-ip";
6const X_FORWARDED_FOR: &str = "x-forwarded-for";
7
8/// Tries to parse the `x-forwarded-for` header
9#[inline]
10pub fn maybe_x_forwarded_for(headers: &HeaderMap) -> Option<Box<str>> {
11 headers
12 .get(X_FORWARDED_FOR)
13 .and_then(header_value_to_str)
14 .and_then(|header| {
15 header
16 .split(',')
17 .map(str::trim)
18 .find_map(|header_parts| (!header_parts.is_empty()).then(|| header_parts.into()))
19 })
20}
21
22/// Tries to parse the `x-real-ip` header
23#[inline]
24pub fn maybe_x_real_ip(headers: &HeaderMap) -> Option<Box<str>> {
25 headers
26 .get(X_REAL_IP)
27 .and_then(header_value_to_str)
28 .map(|header| header.trim().into())
29}
30
31/// Tries to parse `forwarded` headers
32#[inline]
33pub fn maybe_forwarded_for(headers: &HeaderMap) -> Option<Box<str>> {
34 headers.get_all(FORWARDED).iter().find_map(|header| {
35 header_value_to_str(header).and_then(|header| {
36 header
37 .split(&[',', ';'])
38 .map(str::trim)
39 .filter(|&header_parts| !header_parts.is_empty())
40 .find_map(|header_parts| extract_for.parse(header_parts).ok())
41 })
42 })
43}
44
45#[inline]
46pub fn header_value_to_str(header_value: &HeaderValue) -> Option<&str> {
47 header_value.to_str().ok()
48}