use std::{fmt, net::IpAddr};
use rocket::{
http::{Status, uncased::Uncased},
outcome::Outcome,
request::{self, FromRequest, Request},
};
use crate::{
ClientIpConfig,
ClientIpRejection::{self, MissingConfig, MissingRemoteAddr},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ClientIp {
ip: IpAddr,
source: ClientIpSource,
}
impl ClientIp {
#[inline]
pub(crate) const fn new(ip: IpAddr, source: ClientIpSource) -> Self {
Self {
ip,
source,
}
}
#[inline]
pub const fn ip(&self) -> IpAddr {
self.ip
}
#[inline]
pub const fn source(&self) -> &ClientIpSource {
&self.source
}
#[inline]
pub fn into_parts(self) -> (IpAddr, ClientIpSource) {
(self.ip, self.source)
}
}
impl fmt::Display for ClientIp {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.ip, f)
}
}
impl From<ClientIp> for IpAddr {
#[inline]
fn from(client_ip: ClientIp) -> Self {
client_ip.ip
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum ClientIpSource {
ConfiguredHeader(Uncased<'static>),
ChainHeader(Uncased<'static>),
Socket,
}
impl ClientIpSource {
#[inline]
pub const fn header_name(&self) -> Option<&Uncased<'static>> {
match self {
Self::ConfiguredHeader(header) | Self::ChainHeader(header) => Some(header),
Self::Socket => None,
}
}
}
fn from_request(request: &Request<'_>) -> Result<ClientIp, ClientIpRejection> {
let config = request.rocket().state::<ClientIpConfig>().ok_or(MissingConfig)?;
let remote = request.remote().ok_or(MissingRemoteAddr)?;
Ok(config.resolve_client_ip(request.headers(), remote.ip()))
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ClientIp {
type Error = ClientIpRejection;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
match from_request(request) {
Ok(client_ip) => Outcome::Success(client_ip),
Err(rejection) => Outcome::Error((Status::InternalServerError, rejection)),
}
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for &'r ClientIp {
type Error = ClientIpRejection;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let cache: &Result<ClientIp, ClientIpRejection> =
request.local_cache(|| from_request(request));
match cache {
Ok(client_ip) => Outcome::Success(client_ip),
Err(rejection) => Outcome::Error((Status::InternalServerError, *rejection)),
}
}
}