#[cfg(test)]
mod tests;
use crate::application::Application;
use crate::middleware::Middleware;
use crate::request::Request;
use crate::response::Response;
use crate::server::ConnectionInfo;
enum FilterMode {
Allow,
Deny,
}
#[derive(Debug, Clone, PartialEq)]
struct IpRange {
network: u128,
mask: u128,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TrustedProxies {
All,
Ranges(Vec<IpRangeEntry>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct IpRangeEntry(IpRange);
pub const DEFAULT_TRUSTED_PROXIES: &str = "127.0.0.0/8, ::1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7";
impl TrustedProxies {
pub fn parse(list: &str) -> Result<TrustedProxies, String> {
let list = if list.trim().is_empty() { DEFAULT_TRUSTED_PROXIES } else { list.trim() };
if list == "*" {
return Ok(TrustedProxies::All);
}
if list.eq_ignore_ascii_case("none") {
return Ok(TrustedProxies::Ranges(Vec::new()));
}
list.split(',')
.map(str::trim)
.filter(|e| !e.is_empty())
.map(|e| IpRange::parse(e).map(IpRangeEntry).ok_or_else(|| format!("trusted_proxies: {e:?} is not an address or CIDR range")))
.collect::<Result<Vec<_>, _>>()
.map(TrustedProxies::Ranges)
}
pub fn trusts(&self, ip: &str) -> bool {
match self {
TrustedProxies::All => true,
TrustedProxies::Ranges(ranges) => parse_ip(ip).is_some_and(|a| ranges.iter().any(|r| (a & r.0.mask) == r.0.network)),
}
}
}
pub struct IpFilter {
mode: FilterMode,
ranges: Vec<IpRange>,
}
impl IpFilter {
pub fn allow(entries: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
Self::from_entries(FilterMode::Allow, entries)
}
pub fn deny(entries: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
Self::from_entries(FilterMode::Deny, entries)
}
fn from_entries(mode: FilterMode, entries: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
let ranges = entries.into_iter().filter_map(|e| IpRange::parse(e.as_ref())).collect();
IpFilter { mode, ranges }
}
fn matches(&self, ip: &str) -> bool {
match parse_ip(ip) {
Some(addr) => self.ranges.iter().any(|r| (addr & r.mask) == r.network),
None => false,
}
}
}
pub(crate) fn matches(entry: &str, ip: &str) -> bool {
IpFilter::deny([entry]).matches(ip)
}
pub fn is_valid_entry(entry: &str) -> bool {
IpRange::parse(entry).is_some()
}
impl Middleware for IpFilter {
fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
fn handle(
&self,
request: &Request,
connection: &ConnectionInfo,
next: &dyn Application,
) -> Result<Response, String> {
use crate::error::{AppError, IntoResponse};
let matched = self.matches(&connection.client.ip);
match self.mode {
FilterMode::Allow if !matched => Ok(AppError::Forbidden.into_response()),
FilterMode::Deny if matched => Ok(AppError::Forbidden.into_response()),
_ => next.execute(request, connection),
}
}
fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
}
impl IpRange {
fn parse(entry: &str) -> Option<Self> {
let entry = entry.trim();
let (addr, prefix_len) = match entry.split_once('/') {
Some((addr, len)) => {
let len: u32 = len.parse().ok()?;
let is_v4 = addr.parse::<std::net::Ipv4Addr>().is_ok();
let max = if is_v4 { 32 } else { 128 };
if len > max {
return None;
}
(addr, if is_v4 { len + 96 } else { len })
}
None => (entry, 128),
};
let network = parse_ip(addr)?;
let mask = if prefix_len == 0 { 0 } else { !0u128 << (128 - prefix_len) };
Some(IpRange { network: network & mask, mask })
}
}
fn parse_ip(s: &str) -> Option<u128> {
let s = s.trim().trim_start_matches('[').trim_end_matches(']');
let s = s.split('%').next()?;
match s.parse::<std::net::IpAddr>().ok()? {
std::net::IpAddr::V4(v4) => Some(u128::from(v4.to_ipv6_mapped())),
std::net::IpAddr::V6(v6) => Some(u128::from(v6)),
}
}