use std::net::IpAddr;
use cidr::IpCidr;
use rocket::http::uncased::Uncased;
use crate::{
ClientIpConfigBuildError,
canonical::{canonical_cidr, canonical_ip},
cidr_merge::{ensure_no_cross_metadata_overlap, merge_rules_by_metadata},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ClientIpConfig {
pub(crate) trust: TrustModel,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum TrustModel {
NoProxy,
TrustedProxies {
rules: Vec<TrustedProxyRule>,
chain_header_order: Vec<ChainHeader>,
},
TrustAllProxies(TrustAllProxyMode),
}
impl ClientIpConfig {
#[inline]
pub const fn builder() -> ClientIpConfigBuilder {
ClientIpConfigBuilder
}
#[inline]
pub fn trusted_proxy_rules(&self) -> &[TrustedProxyRule] {
match &self.trust {
TrustModel::TrustedProxies {
rules, ..
} => rules,
TrustModel::NoProxy | TrustModel::TrustAllProxies(_) => &[],
}
}
#[inline]
pub fn chain_header_order(&self) -> &[ChainHeader] {
match &self.trust {
TrustModel::NoProxy => &[],
TrustModel::TrustedProxies {
chain_header_order, ..
} => chain_header_order,
TrustModel::TrustAllProxies(mode) => &mode.chain_header_order,
}
}
#[inline]
pub const fn trust_all_proxy_mode(&self) -> Option<&TrustAllProxyMode> {
match &self.trust {
TrustModel::TrustAllProxies(mode) => Some(mode),
_ => None,
}
}
#[inline]
pub const fn trusts_all_proxies(&self) -> bool {
matches!(self.trust, TrustModel::TrustAllProxies(_))
}
#[inline]
pub const fn trusts_no_proxy(&self) -> bool {
matches!(self.trust, TrustModel::NoProxy)
}
#[inline]
pub fn is_trusted_proxy(&self, ip: IpAddr) -> bool {
match &self.trust {
TrustModel::NoProxy => false,
TrustModel::TrustedProxies {
..
} => self.rule_for(ip).is_some(),
TrustModel::TrustAllProxies(_) => true,
}
}
#[inline]
pub(crate) fn rule_for(&self, ip: IpAddr) -> Option<&TrustedProxyRule> {
match &self.trust {
TrustModel::TrustedProxies {
rules, ..
} => {
let ip = canonical_ip(ip);
let index = rules.partition_point(|rule| rule.cidr.first_address() <= ip);
let rule = &rules[index.checked_sub(1)?];
rule.cidr.contains(&ip).then_some(rule)
},
TrustModel::NoProxy | TrustModel::TrustAllProxies(_) => None,
}
}
}
impl Default for ClientIpConfig {
#[inline]
fn default() -> Self {
Self {
trust: TrustModel::NoProxy
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct ClientIpConfigBuilder;
impl ClientIpConfigBuilder {
#[inline]
pub const fn new() -> Self {
Self
}
#[inline]
pub const fn trust_no_proxy(self) -> ClientIpConfig {
ClientIpConfig {
trust: TrustModel::NoProxy
}
}
#[inline]
pub fn trusted_proxies(self) -> TrustedProxiesBuilder {
TrustedProxiesBuilder::new()
}
#[inline]
pub fn trust_all_proxies(self) -> TrustAllProxiesBuilder {
TrustAllProxiesBuilder::new()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TrustedProxiesBuilder {
rules: Vec<TrustedProxyRule>,
chain_header_order: Vec<ChainHeader>,
}
impl TrustedProxiesBuilder {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn proxy(self, cidr: IpCidr) -> Self {
self.proxy_rule(TrustedProxyRule::new(cidr))
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn proxy_with_client_ip_header(self, cidr: IpCidr, header: Uncased<'static>) -> Self {
self.proxy_rule(TrustedProxyRule::with_client_ip_header(cidr, header))
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn proxy_with_x_real_ip(self, cidr: IpCidr) -> Self {
self.proxy_rule(TrustedProxyRule::with_x_real_ip(cidr))
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn proxy_rule(mut self, rule: TrustedProxyRule) -> Self {
self.rules.push(rule);
self
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn proxies(mut self, rules: impl IntoIterator<Item = TrustedProxyRule>) -> Self {
self.rules.extend(rules);
self
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn chain_header_order(mut self, order: impl IntoIterator<Item = ChainHeader>) -> Self {
self.chain_header_order = order.into_iter().collect();
self
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn disable_chain_headers(self) -> Self {
self.chain_header_order([])
}
pub fn build(self) -> Result<ClientIpConfig, ClientIpConfigBuildError> {
let mut rules = self.rules;
for rule in &mut rules {
rule.cidr = canonical_cidr(rule.cidr);
}
ensure_no_cross_metadata_overlap(&rules)?;
let mut rules = merge_rules_by_metadata(rules);
debug_assert!(ensure_no_cross_metadata_overlap(&rules).is_ok());
rules.sort_by_key(|rule| rule.cidr.first_address());
Ok(ClientIpConfig {
trust: TrustModel::TrustedProxies {
rules,
chain_header_order: self.chain_header_order,
},
})
}
}
impl Default for TrustedProxiesBuilder {
#[inline]
fn default() -> Self {
Self {
rules: Vec::new(), chain_header_order: default_chain_header_order()
}
}
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct TrustAllProxiesBuilder {
mode: TrustAllProxyMode,
}
impl TrustAllProxiesBuilder {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn client_ip_header(mut self, header: Uncased<'static>) -> Self {
self.mode.client_ip_header = Some(header);
self
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn chain_header_order(mut self, order: impl IntoIterator<Item = ChainHeader>) -> Self {
self.mode.chain_header_order = order.into_iter().collect();
self
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub fn disable_chain_headers(self) -> Self {
self.chain_header_order([])
}
#[must_use = "builder methods return an updated builder and do not mutate in place"]
#[inline]
pub const fn chain_ip_selection(mut self, selection: TrustAllChainIpSelection) -> Self {
self.mode.chain_ip_selection = selection;
self
}
#[inline]
pub fn build(self) -> ClientIpConfig {
ClientIpConfig {
trust: TrustModel::TrustAllProxies(self.mode)
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TrustAllProxyMode {
pub(crate) client_ip_header: Option<Uncased<'static>>,
pub(crate) chain_header_order: Vec<ChainHeader>,
pub(crate) chain_ip_selection: TrustAllChainIpSelection,
}
impl TrustAllProxyMode {
#[inline]
pub const fn client_ip_header(&self) -> Option<&Uncased<'static>> {
self.client_ip_header.as_ref()
}
#[inline]
pub fn chain_header_order(&self) -> &[ChainHeader] {
&self.chain_header_order
}
#[inline]
pub const fn chain_ip_selection(&self) -> TrustAllChainIpSelection {
self.chain_ip_selection
}
}
impl Default for TrustAllProxyMode {
#[inline]
fn default() -> Self {
Self {
client_ip_header: None,
chain_header_order: default_chain_header_order(),
chain_ip_selection: TrustAllChainIpSelection::Rightmost,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum TrustAllChainIpSelection {
Leftmost,
Rightmost,
SkipRightmostHops(usize),
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct TrustedProxyMetadata {
pub(crate) client_ip_header: Option<Uncased<'static>>,
}
impl TrustedProxyMetadata {
#[inline]
pub(crate) const fn none() -> Self {
Self {
client_ip_header: None
}
}
#[inline]
pub(crate) const fn with_client_ip_header(header: Uncased<'static>) -> Self {
Self {
client_ip_header: Some(header)
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TrustedProxyRule {
pub(crate) cidr: IpCidr,
pub(crate) metadata: TrustedProxyMetadata,
}
impl TrustedProxyRule {
#[inline]
pub const fn new(cidr: IpCidr) -> Self {
Self {
cidr,
metadata: TrustedProxyMetadata::none(),
}
}
#[inline]
pub const fn with_client_ip_header(cidr: IpCidr, header: Uncased<'static>) -> Self {
Self {
cidr,
metadata: TrustedProxyMetadata::with_client_ip_header(header),
}
}
#[inline]
pub const fn with_x_real_ip(cidr: IpCidr) -> Self {
Self::with_client_ip_header(cidr, Uncased::from_borrowed("x-real-ip"))
}
#[inline]
pub const fn cidr(&self) -> &IpCidr {
&self.cidr
}
#[inline]
pub const fn client_ip_header(&self) -> Option<&Uncased<'static>> {
self.metadata.client_ip_header.as_ref()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ChainHeader {
name: Uncased<'static>,
kind: ChainHeaderKind,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum ChainHeaderKind {
XForwardedFor,
Forwarded,
}
impl ChainHeader {
#[inline]
pub fn new(header: Uncased<'static>) -> Self {
let kind = if header == "forwarded" {
ChainHeaderKind::Forwarded
} else {
ChainHeaderKind::XForwardedFor
};
Self {
name: header,
kind,
}
}
#[inline]
pub const fn x_forwarded_for() -> Self {
Self {
name: Uncased::from_borrowed("x-forwarded-for"),
kind: ChainHeaderKind::XForwardedFor,
}
}
#[inline]
pub const fn forwarded() -> Self {
Self {
name: Uncased::from_borrowed("forwarded"), kind: ChainHeaderKind::Forwarded
}
}
#[inline]
pub const fn forwarded_style(header: Uncased<'static>) -> Self {
Self {
name: header, kind: ChainHeaderKind::Forwarded
}
}
#[inline]
pub const fn as_header_name(&self) -> &Uncased<'static> {
&self.name
}
#[inline]
pub fn into_header_name(self) -> Uncased<'static> {
self.name
}
#[inline]
pub(crate) const fn kind(&self) -> ChainHeaderKind {
self.kind
}
}
impl From<Uncased<'static>> for ChainHeader {
#[inline]
fn from(header: Uncased<'static>) -> Self {
Self::new(header)
}
}
#[inline]
fn default_chain_header_order() -> Vec<ChainHeader> {
vec![ChainHeader::x_forwarded_for(), ChainHeader::forwarded()]
}