use core::fmt;
use std::net::{IpAddr, SocketAddr};
use crate::net::normalize;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Scheme {
#[default]
Http,
Https,
}
impl Scheme {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Scheme::Http => "http",
Scheme::Https => "https",
}
}
}
impl fmt::Display for Scheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IngressKind {
Direct,
DirectTls,
ProxyProtocol,
Forwarded,
Unix,
FastCgi,
}
impl IngressKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
IngressKind::Direct => "direct",
IngressKind::DirectTls => "direct_tls",
IngressKind::ProxyProtocol => "proxy_protocol",
IngressKind::Forwarded => "forwarded",
IngressKind::Unix => "unix",
IngressKind::FastCgi => "fastcgi",
}
}
}
impl fmt::Display for IngressKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ClientInfo {
addr: Option<IpAddr>,
port: u16,
scheme: Scheme,
hops: u8,
ingress: IngressKind,
peer: Option<SocketAddr>,
proxy_local: bool,
}
impl ClientInfo {
#[must_use]
pub fn direct(peer: SocketAddr, tls: bool) -> ClientInfo {
let peer = SocketAddr::new(normalize(peer.ip()), peer.port());
ClientInfo {
addr: Some(peer.ip()),
port: peer.port(),
scheme: if tls { Scheme::Https } else { Scheme::Http },
hops: 0,
ingress: if tls {
IngressKind::DirectTls
} else {
IngressKind::Direct
},
peer: Some(peer),
proxy_local: false,
}
}
#[must_use]
pub fn addr(&self) -> Option<IpAddr> {
self.addr
}
#[must_use]
pub fn port(&self) -> u16 {
self.port
}
#[must_use]
pub fn socket_addr(&self) -> Option<SocketAddr> {
self.addr.map(|a| SocketAddr::new(a, self.port))
}
#[must_use]
pub fn scheme(&self) -> Scheme {
self.scheme
}
#[must_use]
pub fn hops(&self) -> u8 {
self.hops
}
#[must_use]
pub fn ingress(&self) -> IngressKind {
self.ingress
}
#[must_use]
pub fn peer(&self) -> Option<SocketAddr> {
self.peer
}
#[must_use]
pub fn is_proxy_local(&self) -> bool {
self.proxy_local
}
#[must_use]
pub fn is_unknown(&self) -> bool {
self.addr.is_none()
}
pub(crate) fn behind_proxy(
mut self,
addr: Option<IpAddr>,
port: u16,
hops: u8,
ingress: IngressKind,
) -> ClientInfo {
self.addr = addr.map(normalize);
self.port = port;
self.hops = hops;
self.ingress = ingress;
self
}
pub(crate) fn with_scheme(mut self, scheme: Scheme) -> ClientInfo {
self.scheme = scheme;
self
}
#[allow(dead_code)] pub(crate) fn with_proxy_local(mut self, on: bool) -> ClientInfo {
self.proxy_local = on;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_direct_mapped_v6_normalized() {
let c = ClientInfo::direct("[::ffff:192.0.2.1]:80".parse().unwrap(), true);
assert_eq!(c.addr(), Some("192.0.2.1".parse().unwrap()));
assert_eq!(c.peer(), Some("192.0.2.1:80".parse().unwrap()));
assert_eq!(c.scheme(), Scheme::Https);
assert_eq!(c.ingress(), IngressKind::DirectTls);
}
#[test]
fn test_client_behind_proxy_keeps_peer() {
let c = ClientInfo::direct("127.0.0.1:5000".parse().unwrap(), false).behind_proxy(
Some("203.0.113.9".parse().unwrap()),
0,
2,
IngressKind::Forwarded,
);
assert_eq!(c.addr(), Some("203.0.113.9".parse().unwrap()));
assert_eq!(c.peer(), Some("127.0.0.1:5000".parse().unwrap()));
assert_eq!(c.hops(), 2);
assert_eq!(c.socket_addr(), Some("203.0.113.9:0".parse().unwrap()));
let unknown = c.behind_proxy(None, 0, 1, IngressKind::Forwarded);
assert!(unknown.is_unknown());
assert_eq!(unknown.socket_addr(), None);
}
#[test]
fn test_client_info_is_small() {
assert!(core::mem::size_of::<ClientInfo>() <= 64);
}
#[test]
fn test_scheme_and_ingress_names() {
assert_eq!(Scheme::Https.to_string(), "https");
assert_eq!(IngressKind::FastCgi.as_str(), "fastcgi");
}
}