real_ip/
headers.rs

1use comma_separated::CommaSeparatedIterator;
2use either::Either;
3use rfc7239::{parse, Forwarded, NodeIdentifier, NodeName};
4use std::borrow::Cow;
5use std::iter::{empty, IntoIterator};
6use std::net::{AddrParseError, IpAddr};
7use std::str::FromStr;
8
9/// Get the list of ip addresses from an `forwarded` header
10///
11/// # Example
12///
13/// ```rust
14/// # use std::net::IpAddr;
15/// # use real_ip::headers::*;
16/// assert_eq!(
17///    vec![IpAddr::from([10, 10, 10, 10]), IpAddr::from([10, 10, 10, 20])],
18///    extract_forwarded_header("for=10.10.10.10, for=10.10.10.20;proto=https").collect::<Vec<_>>()
19/// );
20/// ```
21///
22/// Note: if you need the other data provided by the `forwarded` header, have a look at the [`rfc7239`](https://docs.rs/rfc7239) crate.
23pub fn extract_forwarded_header(
24    header_value: &str,
25) -> impl DoubleEndedIterator<Item = IpAddr> + '_ {
26    if parse(header_value).all(|result| result.is_ok()) {
27        Either::Left(parse(header_value).filter_map(|forward| match forward {
28            Ok(Forwarded {
29                forwarded_for:
30                    Some(NodeIdentifier {
31                        name: NodeName::Ip(ip),
32                        ..
33                    }),
34                ..
35            }) => Some(ip),
36            _ => None,
37        }))
38    } else {
39        Either::Right(empty())
40    }
41}
42
43fn extract_x_forwarded_for_header_raw(
44    header_value: &str,
45) -> impl DoubleEndedIterator<Item = Result<IpAddr, AddrParseError>> + '_ {
46    CommaSeparatedIterator::new(header_value)
47        .map(str::trim)
48        .map(|x| IpAddr::from_str(maybe_bracketed(&maybe_quoted(x))))
49}
50
51/// Get the list of ip addresses from an `x-forwarded-for` header
52///
53/// # Example
54///
55/// ```rust
56/// # use std::net::IpAddr;
57/// # use real_ip::headers::*;
58/// assert_eq!(
59///    vec![IpAddr::from([10, 10, 10, 10]), IpAddr::from([10, 10, 10, 20])],
60///    extract_x_forwarded_for_header("10.10.10.10,10.10.10.20").collect::<Vec<_>>()
61/// );
62/// ```
63pub fn extract_x_forwarded_for_header(
64    header_value: &str,
65) -> impl DoubleEndedIterator<Item = IpAddr> + '_ {
66    if extract_x_forwarded_for_header_raw(header_value).all(|result| result.is_ok()) {
67        Either::Left(extract_x_forwarded_for_header_raw(header_value).flatten())
68    } else {
69        Either::Right(empty())
70    }
71}
72
73/// Get the list of ip addresses from an `x-real-ip` header
74///
75/// # Example
76///
77/// ```rust
78/// # use std::net::IpAddr;
79/// # use real_ip::headers::*;
80/// assert_eq!(
81///    vec![IpAddr::from([10, 10, 10, 10])],
82///    extract_x_forwarded_for_header("10.10.10.10").collect::<Vec<_>>()
83/// );
84/// ```
85pub fn extract_real_ip_header(header_value: &str) -> impl DoubleEndedIterator<Item = IpAddr> + '_ {
86    IpAddr::from_str(maybe_bracketed(&maybe_quoted(header_value))).into_iter()
87}
88
89enum EscapeState {
90    Normal,
91    Escaped,
92}
93
94fn maybe_quoted<'a>(x: &'a str) -> Cow<'a, str> {
95    let mut i = x.chars();
96    if i.next() == Some('"') {
97        let mut s = String::with_capacity(x.len());
98        let mut state = EscapeState::Normal;
99        for c in i {
100            state = match state {
101                EscapeState::Normal => match c {
102                    '"' => break,
103                    '\\' => EscapeState::Escaped,
104                    _ => {
105                        s.push(c);
106                        EscapeState::Normal
107                    }
108                },
109                EscapeState::Escaped => {
110                    s.push(c);
111                    EscapeState::Normal
112                }
113            };
114        }
115        s.into()
116    } else {
117        x.into()
118    }
119}
120
121fn maybe_bracketed(x: &str) -> &str {
122    if x.as_bytes().first() == Some(&b'[') && x.as_bytes().last() == Some(&b']') {
123        &x[1..x.len() - 1]
124    } else {
125        x
126    }
127}