Skip to main content

http_acl/
utils.rs

1//! Utility functions for the http-acl crate.
2
3use std::collections::HashSet;
4use std::hash::Hash;
5use std::net::IpAddr;
6use std::ops::RangeInclusive;
7
8use ipnet::IpNet;
9
10pub mod authority;
11pub(crate) mod host_pattern;
12pub(crate) mod ip;
13pub mod url;
14
15// Taken from https://stackoverflow.com/a/46767732
16pub(crate) fn has_unique_elements<T>(iter: T) -> bool
17where
18    T: IntoIterator,
19    T::Item: Eq + Hash,
20{
21    let mut uniq = HashSet::new();
22    iter.into_iter().all(move |x| uniq.insert(x))
23}
24
25/// Helper function to check if any ranges in a slice overlap.
26pub(crate) fn has_overlapping_ranges<T: Ord + Clone>(ranges: &[RangeInclusive<T>]) -> bool {
27    let mut sorted = ranges.to_vec();
28    sorted.sort_by(|a, b| a.start().cmp(b.start()));
29    for pair in sorted.windows(2) {
30        if let [a, b] = pair
31            && a.end() >= b.start()
32        {
33            return true;
34        }
35    }
36    false
37}
38
39/// Checks if a range overlaps with any existing ranges in a slice.
40#[inline]
41pub(crate) fn range_overlaps<T: Ord + Clone>(
42    ranges: &[RangeInclusive<T>],
43    range: &RangeInclusive<T>,
44    self_index: Option<usize>,
45) -> bool {
46    ranges
47        .iter()
48        .enumerate()
49        .filter_map(|(i, r)| {
50            self_index.map_or(Some(r), |index| if i != index { Some(r) } else { None })
51        })
52        .any(|r| r.start() <= range.end() && r.end() >= range.start())
53}
54
55/// Converts a type into an IP range accepted by
56/// [`HttpAclBuilder::add_allowed_ip_range`](crate::HttpAclBuilder::add_allowed_ip_range)
57/// and its denied/setter counterparts.
58///
59/// Implemented for [`IpNet`], `RangeInclusive<IpAddr>`, and `(IpAddr, IpAddr)`. Use
60/// whichever is most convenient: a CIDR block, an inclusive range, or a plain tuple.
61pub trait IntoIpRange {
62    /// Converts the type into an IP range, or `None` if it isn't a valid one (see
63    /// [`Self::validate`]).
64    fn into_range(self) -> Option<RangeInclusive<IpAddr>>;
65
66    /// Validates the IP range.
67    ///
68    /// Both ends must be the same IP address family. Without this check, a mixed-family
69    /// range like `1.0.0.0..=::` would be accepted (`IpAddr`'s `Ord` sorts all IPv4
70    /// addresses before all IPv6 addresses) and silently match every IPv4 address from
71    /// the start onward *and* every IPv6 address.
72    fn validate(ip_range: RangeInclusive<IpAddr>) -> Option<RangeInclusive<IpAddr>> {
73        let same_family = matches!(
74            (ip_range.start(), ip_range.end()),
75            (IpAddr::V4(_), IpAddr::V4(_)) | (IpAddr::V6(_), IpAddr::V6(_))
76        );
77        if same_family && ip_range.start() <= ip_range.end() {
78            Some(ip_range)
79        } else {
80            None
81        }
82    }
83}
84
85impl IntoIpRange for IpNet {
86    fn into_range(self) -> Option<RangeInclusive<IpAddr>> {
87        let start = self.network();
88        let end = self.broadcast();
89        Some(start..=end)
90    }
91}
92
93impl IntoIpRange for RangeInclusive<IpAddr> {
94    fn into_range(self) -> Option<RangeInclusive<IpAddr>> {
95        Self::validate(self)
96    }
97}
98
99impl IntoIpRange for (IpAddr, IpAddr) {
100    fn into_range(self) -> Option<RangeInclusive<IpAddr>> {
101        Self::validate(self.0..=self.1)
102    }
103}