use crate::HttpEntity;
use std::borrow::Cow;
use std::net::IpAddr;
#[derive(Debug, Clone)]
pub struct RemoteAddress<'r> {
request: &'r super::Request,
trusted_sources: Vec<RemoteAddressSource>,
}
impl<'r> RemoteAddress<'r> {
pub(crate) fn new(request: &'r super::Request) -> Self {
Self {
request,
trusted_sources: vec![],
}
}
}
impl RemoteAddress<'_> {
pub fn trust_forwarded_for(&mut self, index: isize) -> &mut Self {
self.trusted_sources
.push(RemoteAddressSource::XForwardedFor(index));
self
}
pub fn trust_forwarded(&mut self, index: isize) -> &mut Self {
self.trusted_sources
.push(RemoteAddressSource::Forwarded(index));
self
}
pub fn trust_header(&mut self, header: impl Into<Cow<'static, str>>) -> &mut Self {
self.trusted_sources
.push(RemoteAddressSource::Header(header.into()));
self
}
pub fn trust_cloudflare_header(&mut self) -> &mut Self {
self.trust_header("CF-Connecting-IP")
}
pub fn trust_real_ip_header(&mut self) -> &mut Self {
self.trust_header("X-Real-IP")
}
pub fn trust_client_ip_header(&mut self) -> &mut Self {
self.trust_header("True-Client-IP")
}
pub fn trust_peer_address(&mut self) -> &mut Self {
self.trusted_sources.push(RemoteAddressSource::PeerAddress);
self
}
#[must_use = "you probably don't intend to discard this value"]
pub fn apply(&self) -> Option<IpAddr> {
for source in &self.trusted_sources {
if let Some(ip) = source.apply(self.request) {
return Some(ip);
}
}
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum RemoteAddressSource {
XForwardedFor(isize),
Forwarded(isize),
Header(Cow<'static, str>),
PeerAddress,
}
impl RemoteAddressSource {
pub fn apply(&self, request: &super::Request) -> Option<IpAddr> {
match self {
RemoteAddressSource::XForwardedFor(index) => x_forwarded_for_header(request, *index),
RemoteAddressSource::Forwarded(index) => forwarded_header(request, *index),
RemoteAddressSource::Header(name) => request
.header_all(&**name)
.into_iter()
.filter_map(|v| v.to_str().ok())
.find_map(|v| v.parse().ok()),
RemoteAddressSource::PeerAddress => request.peer_addr().map(|v| v.ip()),
}
}
}
fn x_forwarded_for_header(request: &super::Request, index: isize) -> Option<IpAddr> {
let mut ip = request
.header_all("X-Forwarded-For")
.into_iter()
.filter_map(|s| s.to_str().ok())
.flat_map(|s| s.split(','))
.map(str::trim);
if index < 0 {
#[allow(clippy::cast_sign_loss)]
let index = (index.checked_abs()? as usize).checked_sub(1)?;
ip.nth_back(index).and_then(|s| s.parse().ok())
} else if index >= 0 {
#[allow(clippy::cast_sign_loss)]
ip.nth(index as usize).and_then(|s| s.parse().ok())
} else {
None
}
}
lazy_static::lazy_static! {
static ref FOR_WORD: regex::Regex = regex::Regex::new(r"(?i)^for$").unwrap();
static ref SPECIAL_TOKEN: regex::Regex = regex::Regex::new(r#"^"[(.+)]"$"#).unwrap();
}
fn forwarded_header(request: &super::Request, index: isize) -> Option<IpAddr> {
fn parse_key_value(s: &str) -> Option<(&str, &str)> {
let (key, value) = s.split_once('=')?;
Some((key, value))
}
fn parse_ip(s: &str) -> Option<IpAddr> {
let s = s.trim();
if let Some(cap) = SPECIAL_TOKEN.captures(s) {
cap[1].parse().ok()
} else {
s.parse().ok()
}
}
let ip = request
.header_all("Forwarded")
.into_iter()
.filter_map(|s| s.to_str().ok())
.flat_map(|s| s.split(','))
.map(str::trim)
.map(|s| {
s.split(';')
.filter_map(|s| parse_key_value(s.trim()))
.collect::<Vec<_>>()
});
let mut ffor = ip.filter_map(|v| {
v.iter()
.find(|(k, _)| FOR_WORD.is_match(k))
.map(|(_, v)| *v)
});
if index < 0 {
#[allow(clippy::cast_sign_loss)]
let index = (index.checked_abs()? as usize).checked_sub(1)?;
ffor.nth_back(index).and_then(parse_ip)
} else if index >= 0 {
#[allow(clippy::cast_sign_loss)]
ffor.nth(index as usize).and_then(parse_ip)
} else {
None
}
}