use std::net::{IpAddr, SocketAddr};
use axum::{
extract::{ConnectInfo, FromRequestParts},
http::{request::Parts, HeaderMap, HeaderName, StatusCode},
response::{IntoResponse, Response},
};
use forwarded_header_value::{ForwardedHeaderValue, Identifier};
pub enum Rejection {
NoClientIpFound,
}
impl IntoResponse for Rejection {
fn into_response(self) -> Response { (StatusCode::BAD_REQUEST, "Could not determine the client ip from the request").into_response() }
}
pub struct SmartClientIp(pub IpAddr);
impl<S> FromRequestParts<S> for SmartClientIp
where S: Sync
{
type Rejection = Rejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
ip_from_rightmost_forwarded_header(&parts.headers)
.or(ip_from_cf_connecting_ip_header(&parts.headers))
.or(ip_from_x_real_ip_header(&parts.headers))
.or(ip_from_x_forwarded_for_header(&parts.headers))
.or(ip_from_connect_info(parts))
.ok_or(Rejection::NoClientIpFound)
.map(Self)
}
}
fn ip_from_connect_info(parts: &Parts) -> Option<IpAddr> {
parts.extensions.get::<ConnectInfo<SocketAddr>>().map(|ConnectInfo(addr)| addr.ip())
}
fn last_header_value(headers: &HeaderMap, name: &HeaderName) -> Option<String> {
headers.get_all(name).into_iter().next_back().and_then(|s| s.to_str().ok().map(|f| f.to_owned()))
}
const FORWARDED_HEADER_NAME: HeaderName = HeaderName::from_static("forwarded");
fn ip_from_rightmost_forwarded_header(headers: &HeaderMap) -> Option<IpAddr> {
last_header_value(headers, &FORWARDED_HEADER_NAME).and_then(|header_value| {
ForwardedHeaderValue::from_forwarded(header_value.as_str())
.map(|v| v.iter().next_back().and_then(|s| s.forwarded_for.clone()))
.ok()
.flatten()
.and_then(|forwarded_for| match forwarded_for {
Identifier::SocketAddr(a) => Some(a.ip()),
Identifier::IpAddr(ip) => Some(ip),
_ => None,
})
})
}
const X_FORWARDED_FOR_HEADER_NAME: HeaderName = HeaderName::from_static("x-forwarded-for");
fn ip_from_x_forwarded_for_header(headers: &HeaderMap) -> Option<IpAddr> {
last_header_value(headers, &X_FORWARDED_FOR_HEADER_NAME)
.and_then(|header_value| header_value.split(',').next_back().and_then(|v| v.trim().parse::<IpAddr>().ok()))
}
fn ip_from_header_value(headers: &HeaderMap, header_name: &HeaderName) -> Option<IpAddr> {
last_header_value(headers, header_name).and_then(|v| v.clone().trim().parse::<IpAddr>().ok())
}
const X_REAL_IP_HEADER_NAME: HeaderName = HeaderName::from_static("x-real-ip");
fn ip_from_x_real_ip_header(headers: &HeaderMap) -> Option<IpAddr> { ip_from_header_value(headers, &X_REAL_IP_HEADER_NAME) }
const CF_CONNECTING_IP_HEADER_NAME: HeaderName = HeaderName::from_static("cf-connecting-ip");
fn ip_from_cf_connecting_ip_header(headers: &HeaderMap) -> Option<IpAddr> { ip_from_header_value(headers, &CF_CONNECTING_IP_HEADER_NAME) }