Skip to main content

http_acl/
acl.rs

1//! Contains the [`HttpAcl`], [`HttpAclBuilder`],
2//! and related types.
3//!
4//! Each category an [`HttpAcl`] checks (scheme, method, host, port, IP, header, URL
5//! path) is evaluated the same way: the allow-list is checked first, then the
6//! deny-list, and if neither matches, the category's configured default (set via
7//! e.g. [`HttpAclBuilder::host_acl_default`]) decides the outcome. The allow-list
8//! always wins over the deny-list, so a broad allow entry can shadow a narrower deny
9//! entry in the same category. Every check returns an [`AclClassification`] rather
10//! than a plain `bool`, so callers can tell *why* something was allowed or denied;
11//! use [`AclClassification::is_allowed`]/[`AclClassification::is_denied`] where only
12//! the outcome matters.
13
14#[cfg(feature = "hashbrown")]
15use hashbrown::{HashMap, HashSet, hash_map::Entry};
16#[cfg(not(feature = "hashbrown"))]
17use std::collections::{HashMap, HashSet, hash_map::Entry};
18use std::hash::Hash;
19use std::net::{IpAddr, SocketAddr};
20use std::ops::RangeInclusive;
21use std::sync::Arc;
22
23use matchit::Router;
24#[cfg(feature = "serde")]
25use serde::{Deserialize, Serialize};
26
27use crate::{
28    error::AddError,
29    utils::{
30        self, IntoIpRange,
31        authority::Authority,
32        host_pattern::{HostPattern, is_wildcard_host},
33    },
34};
35
36/// A function that validates an HTTP request against an ACL.
37///
38/// Called by [`HttpAcl::is_valid`] with the request's scheme, authority (host and
39/// port), headers, and optional body, in that order. It is the only hook for checks
40/// that don't fit the built-in categories (e.g. inspecting the body, or applying
41/// custom cross-field logic). Return [`AclClassification::DeniedUserAcl`] or
42/// [`AclClassification::Denied`] to reject the request, or
43/// [`AclClassification::AllowedDefault`] to let it through.
44///
45/// A `ValidateFn` is attached via [`HttpAclBuilder::build_full`] or
46/// [`HttpAclBuilder::try_build_full`] rather than a dedicated builder setter, since
47/// it is typically a closure that captures state from outside the builder.
48pub type ValidateFn = Arc<
49    dyn for<'h> Fn(
50            &str,
51            &Authority,
52            Box<dyn Iterator<Item = (&'h str, &'h str)> + Send + Sync + 'h>,
53            Option<&[u8]>,
54        ) -> AclClassification
55        + Send
56        + Sync,
57>;
58
59#[derive(Clone)]
60/// Represents an HTTP ACL.
61///
62/// Built via [`HttpAcl::builder`] (an [`HttpAclBuilder`]) rather than constructed
63/// directly. Once built, an `HttpAcl` is immutable; the various `is_*_allowed`
64/// methods check a single aspect of a request (scheme, method, host, port, IP,
65/// header, or URL path) and return an [`AclClassification`]. See the module-level
66/// documentation for how allow-lists, deny-lists, and per-category defaults combine.
67pub struct HttpAcl {
68    allow_http: bool,
69    allow_https: bool,
70    allowed_methods: HashSet<HttpRequestMethod>,
71    denied_methods: HashSet<HttpRequestMethod>,
72    allowed_hosts: HashSet<Box<str>>,
73    denied_hosts: HashSet<Box<str>>,
74    allowed_host_patterns: Box<[HostPattern]>,
75    denied_host_patterns: Box<[HostPattern]>,
76    allowed_port_ranges: Box<[RangeInclusive<u16>]>,
77    denied_port_ranges: Box<[RangeInclusive<u16>]>,
78    allowed_ip_ranges: Box<[RangeInclusive<IpAddr>]>,
79    denied_ip_ranges: Box<[RangeInclusive<IpAddr>]>,
80    static_dns_mapping: HashMap<Box<str>, SocketAddr>,
81    trusted_static_dns_mapping: HashMap<Box<str>, SocketAddr>,
82    allowed_headers: HashMap<Box<str>, Option<Box<str>>>,
83    denied_headers: HashMap<Box<str>, Option<Box<str>>>,
84    allowed_url_paths_router: Router<()>,
85    denied_url_paths_router: Router<()>,
86    validate_fn: Option<ValidateFn>,
87    allow_non_global_ip_ranges: bool,
88    method_acl_default: bool,
89    host_acl_default: bool,
90    port_acl_default: bool,
91    ip_acl_default: bool,
92    header_acl_default: bool,
93    url_path_acl_default: bool,
94}
95
96impl std::fmt::Debug for HttpAcl {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.debug_struct("HttpAcl")
99            .field("allow_http", &self.allow_http)
100            .field("allow_https", &self.allow_https)
101            .field("allowed_methods", &self.allowed_methods)
102            .field("denied_methods", &self.denied_methods)
103            .field("allowed_hosts", &self.allowed_hosts)
104            .field("denied_hosts", &self.denied_hosts)
105            .field("allowed_port_ranges", &self.allowed_port_ranges)
106            .field("denied_port_ranges", &self.denied_port_ranges)
107            .field("allowed_ip_ranges", &self.allowed_ip_ranges)
108            .field("denied_ip_ranges", &self.denied_ip_ranges)
109            .field("static_dns_mapping", &self.static_dns_mapping)
110            .field(
111                "trusted_static_dns_mapping",
112                &self.trusted_static_dns_mapping,
113            )
114            .field("allowed_headers", &self.allowed_headers)
115            .field("denied_headers", &self.denied_headers)
116            .field(
117                "allow_non_global_ip_ranges",
118                &self.allow_non_global_ip_ranges,
119            )
120            .field("method_acl_default", &self.method_acl_default)
121            .field("host_acl_default", &self.host_acl_default)
122            .field("port_acl_default", &self.port_acl_default)
123            .field("ip_acl_default", &self.ip_acl_default)
124            .field("header_acl_default", &self.header_acl_default)
125            .field("url_path_acl_default", &self.url_path_acl_default)
126            .finish()
127    }
128}
129
130impl PartialEq for HttpAcl {
131    fn eq(&self, other: &Self) -> bool {
132        self.allow_http == other.allow_http
133            && self.allow_https == other.allow_https
134            && self.allowed_methods == other.allowed_methods
135            && self.denied_methods == other.denied_methods
136            && self.allowed_hosts == other.allowed_hosts
137            && self.denied_hosts == other.denied_hosts
138            && self.allowed_port_ranges == other.allowed_port_ranges
139            && self.denied_port_ranges == other.denied_port_ranges
140            && self.allowed_ip_ranges == other.allowed_ip_ranges
141            && self.denied_ip_ranges == other.denied_ip_ranges
142            && self.static_dns_mapping == other.static_dns_mapping
143            && self.trusted_static_dns_mapping == other.trusted_static_dns_mapping
144            && self.allowed_headers == other.allowed_headers
145            && self.denied_headers == other.denied_headers
146            && self.allow_non_global_ip_ranges == other.allow_non_global_ip_ranges
147            && self.method_acl_default == other.method_acl_default
148            && self.host_acl_default == other.host_acl_default
149            && self.port_acl_default == other.port_acl_default
150            && self.ip_acl_default == other.ip_acl_default
151            && self.header_acl_default == other.header_acl_default
152            && self.url_path_acl_default == other.url_path_acl_default
153    }
154}
155
156impl std::default::Default for HttpAcl {
157    fn default() -> Self {
158        Self {
159            allow_http: true,
160            allow_https: true,
161            allowed_methods: [
162                HttpRequestMethod::CONNECT,
163                HttpRequestMethod::DELETE,
164                HttpRequestMethod::GET,
165                HttpRequestMethod::HEAD,
166                HttpRequestMethod::OPTIONS,
167                HttpRequestMethod::PATCH,
168                HttpRequestMethod::POST,
169                HttpRequestMethod::PUT,
170                HttpRequestMethod::TRACE,
171            ]
172            .into_iter()
173            .collect(),
174            denied_methods: HashSet::new(),
175            allowed_hosts: HashSet::new(),
176            denied_hosts: HashSet::new(),
177            allowed_host_patterns: Box::new([]),
178            denied_host_patterns: Box::new([]),
179            allowed_port_ranges: vec![80..=80, 443..=443].into_boxed_slice(),
180            denied_port_ranges: Vec::new().into_boxed_slice(),
181            allowed_ip_ranges: Vec::new().into_boxed_slice(),
182            denied_ip_ranges: Vec::new().into_boxed_slice(),
183            static_dns_mapping: HashMap::new(),
184            trusted_static_dns_mapping: HashMap::new(),
185            allowed_headers: HashMap::new(),
186            denied_headers: HashMap::new(),
187            allowed_url_paths_router: Router::new(),
188            denied_url_paths_router: Router::new(),
189            validate_fn: None,
190            allow_non_global_ip_ranges: false,
191            method_acl_default: false,
192            host_acl_default: false,
193            port_acl_default: false,
194            ip_acl_default: false,
195            header_acl_default: true,
196            url_path_acl_default: true,
197        }
198    }
199}
200
201impl HttpAcl {
202    /// Returns a new [`HttpAclBuilder`].
203    pub fn builder() -> HttpAclBuilder {
204        HttpAclBuilder::new()
205    }
206
207    /// Returns whether the scheme is allowed.
208    ///
209    /// Unlike the other `is_*_allowed` methods, this is a plain per-scheme flag (set
210    /// via [`HttpAclBuilder::http`]/[`HttpAclBuilder::https`]) rather than an
211    /// allow/deny/default check, so it only ever returns
212    /// [`AclClassification::AllowedUserAcl`] or [`AclClassification::DeniedUserAcl`].
213    /// Any scheme other than `"http"`/`"https"` is denied.
214    pub fn is_scheme_allowed(&self, scheme: &str) -> AclClassification {
215        if scheme == "http" && self.allow_http || scheme == "https" && self.allow_https {
216            AclClassification::AllowedUserAcl
217        } else {
218            AclClassification::DeniedUserAcl
219        }
220    }
221
222    /// Returns whether the method is allowed.
223    ///
224    /// Note: If you pass a string ensure it is uppercased first.
225    pub fn is_method_allowed(&self, method: impl Into<HttpRequestMethod>) -> AclClassification {
226        let method = method.into();
227        if self.allowed_methods.contains(&method) {
228            AclClassification::AllowedUserAcl
229        } else if self.denied_methods.contains(&method) {
230            AclClassification::DeniedUserAcl
231        } else if self.method_acl_default {
232            AclClassification::AllowedDefault
233        } else {
234            AclClassification::DeniedDefault
235        }
236    }
237
238    /// Returns whether the host is allowed.
239    ///
240    /// Hosts may be exact hostnames or wildcard patterns (see
241    /// [`HttpAclBuilder::add_allowed_host`] for the wildcard syntax).
242    ///
243    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
244    pub fn is_host_allowed(&self, host: &str) -> AclClassification {
245        if self.allowed_hosts.contains(host)
246            || self.allowed_host_patterns.iter().any(|p| p.matches(host))
247        {
248            AclClassification::AllowedUserAcl
249        } else if self.denied_hosts.contains(host)
250            || self.denied_host_patterns.iter().any(|p| p.matches(host))
251        {
252            AclClassification::DeniedUserAcl
253        } else if self.host_acl_default {
254            AclClassification::AllowedDefault
255        } else {
256            AclClassification::DeniedDefault
257        }
258    }
259
260    /// Returns whether the port is allowed.
261    pub fn is_port_allowed(&self, port: u16) -> AclClassification {
262        if Self::is_port_in_ranges(port, &self.allowed_port_ranges) {
263            AclClassification::AllowedUserAcl
264        } else if Self::is_port_in_ranges(port, &self.denied_port_ranges) {
265            AclClassification::DeniedUserAcl
266        } else if self.port_acl_default {
267            AclClassification::AllowedDefault
268        } else {
269            AclClassification::DeniedDefault
270        }
271    }
272
273    /// Returns whether an IP is allowed.
274    ///
275    /// A non-global IP (private, loopback, link-local, and other special-use
276    /// addresses) is denied with [`AclClassification::DeniedNotGlobal`] before the
277    /// allow/deny lists are even checked, unless
278    /// [`HttpAclBuilder::non_global_ip_ranges`] was set to `true`.
279    pub fn is_ip_allowed(&self, ip: &IpAddr) -> AclClassification {
280        if !utils::ip::is_global_ip(ip) && !self.allow_non_global_ip_ranges {
281            AclClassification::DeniedNotGlobal
282        } else if Self::is_ip_in_ranges(ip, &self.allowed_ip_ranges) {
283            AclClassification::AllowedUserAcl
284        } else if Self::is_ip_in_ranges(ip, &self.denied_ip_ranges) {
285            AclClassification::DeniedUserAcl
286        } else if self.ip_acl_default {
287            AclClassification::AllowedDefault
288        } else {
289            AclClassification::DeniedDefault
290        }
291    }
292
293    /// Resolve a static DNS mapping.
294    ///
295    /// The returned address is still subject to the IP and port ACL - callers must
296    /// check it with [`Self::is_ip_allowed`] and [`Self::is_port_allowed`] themselves.
297    /// Use [`Self::resolve_trusted_static_dns_mapping`] for mappings that should
298    /// bypass those checks entirely.
299    ///
300    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
301    pub fn resolve_static_dns_mapping(&self, host: &str) -> Option<SocketAddr> {
302        self.static_dns_mapping.get(host).copied()
303    }
304
305    /// Resolve a trusted static DNS mapping.
306    ///
307    /// Unlike [`Self::resolve_static_dns_mapping`], the returned address is meant to
308    /// bypass the IP and port ACL entirely - only use this for mappings you trust
309    /// regardless of what the ACL would otherwise say (e.g. pinning a hostname to an
310    /// internal address on purpose).
311    ///
312    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
313    pub fn resolve_trusted_static_dns_mapping(&self, host: &str) -> Option<SocketAddr> {
314        self.trusted_static_dns_mapping.get(host).copied()
315    }
316
317    /// Returns whether a header is allowed.
318    ///
319    /// Note: Header names are case-insensitive, but this function assumes the caller provides them in a consistent case.
320    pub fn is_header_allowed(&self, header_name: &str, header_value: &str) -> AclClassification {
321        if let Some(allowed_value) = self.allowed_headers.get(header_name) {
322            if allowed_value.as_deref() == Some(header_value) || allowed_value.is_none() {
323                AclClassification::AllowedUserAcl
324            } else {
325                AclClassification::DeniedUserAcl
326            }
327        } else if let Some(denied_value) = self.denied_headers.get(header_name) {
328            if denied_value.as_deref() == Some(header_value) || denied_value.is_none() {
329                AclClassification::DeniedUserAcl
330            } else {
331                AclClassification::AllowedUserAcl
332            }
333        } else if self.header_acl_default {
334            AclClassification::AllowedDefault
335        } else {
336            AclClassification::DeniedDefault
337        }
338    }
339
340    /// Returns whether a URL path is allowed.
341    ///
342    /// Note: The URL path should be percent-decoded before passing it to this function.
343    pub fn is_url_path_allowed(&self, url_path: &str) -> AclClassification {
344        if self.allowed_url_paths_router.at(url_path).is_ok() {
345            AclClassification::AllowedUserAcl
346        } else if self.denied_url_paths_router.at(url_path).is_ok() {
347            AclClassification::DeniedUserAcl
348        } else if self.url_path_acl_default {
349            AclClassification::AllowedDefault
350        } else {
351            AclClassification::DeniedDefault
352        }
353    }
354
355    /// Runs the [`ValidateFn`] attached to this ACL, if any, against a request.
356    ///
357    /// Returns [`AclClassification::AllowedDefault`] when no `ValidateFn` was
358    /// attached (the default for an `HttpAcl` built without one), so calling this is
359    /// always safe even if you never configured custom validation.
360    pub fn is_valid<'h>(
361        &self,
362        scheme: &str,
363        authority: &Authority,
364        headers: impl Iterator<Item = (&'h str, &'h str)> + Send + Sync + 'h,
365        body: Option<&[u8]>,
366    ) -> AclClassification {
367        if let Some(validate_fn) = &self.validate_fn {
368            validate_fn(scheme, authority, Box::new(headers), body)
369        } else {
370            AclClassification::AllowedDefault
371        }
372    }
373
374    /// Checks if an ip is in a list of ip ranges.
375    fn is_ip_in_ranges(ip: &IpAddr, ranges: &[RangeInclusive<IpAddr>]) -> bool {
376        ranges.iter().any(|range| range.contains(ip))
377    }
378
379    /// Checks if a port is in a list of port ranges.
380    fn is_port_in_ranges(port: u16, ranges: &[RangeInclusive<u16>]) -> bool {
381        ranges.iter().any(|range| range.contains(&port))
382    }
383}
384
385/// Represents the outcome of an ACL check, and why it was reached.
386///
387/// Every `is_*_allowed` method on [`HttpAcl`] returns one of these instead of a plain
388/// `bool`, so the reason for an outcome is preserved for logging or error messages.
389/// Use [`Self::is_allowed`] or [`Self::is_denied`] to collapse it to a `bool` once you
390/// only care about the outcome.
391#[non_exhaustive]
392#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
393pub enum AclClassification {
394    /// The entity is allowed according to the allowed ACL.
395    AllowedUserAcl,
396    /// The entity is allowed because the default is to allow if no ACL match is found.
397    AllowedDefault,
398    /// The entity is denied according to the denied ACL.
399    DeniedUserAcl,
400    /// The entity is denied because the default is to deny if no ACL match is found.
401    DeniedDefault,
402    /// The entity is denied for a custom reason.
403    ///
404    /// Not produced by any built-in check; this exists for a [`ValidateFn`] to return
405    /// a denial with a human-readable explanation of its own.
406    Denied(String),
407    /// The IP is denied because it is not global.
408    DeniedNotGlobal,
409}
410
411impl std::fmt::Display for AclClassification {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        match self {
414            AclClassification::AllowedUserAcl => {
415                write!(f, "The entity is allowed according to the allowed ACL.")
416            }
417            AclClassification::AllowedDefault => write!(
418                f,
419                "The entity is allowed because the default is to allow if no ACL match is found."
420            ),
421            AclClassification::DeniedUserAcl => {
422                write!(f, "The entity is denied according to the denied ACL.")
423            }
424            AclClassification::DeniedNotGlobal => {
425                write!(f, "The ip is denied because it is not global.")
426            }
427            AclClassification::DeniedDefault => write!(
428                f,
429                "The entity is denied because the default is to deny if no ACL match is found."
430            ),
431            AclClassification::Denied(reason) => {
432                write!(f, "The entity is denied because {reason}.")
433            }
434        }
435    }
436}
437
438impl AclClassification {
439    /// Returns whether the classification is allowed.
440    pub fn is_allowed(&self) -> bool {
441        matches!(
442            self,
443            AclClassification::AllowedUserAcl | AclClassification::AllowedDefault
444        )
445    }
446
447    /// Returns whether the classification is denied.
448    pub fn is_denied(&self) -> bool {
449        matches!(
450            self,
451            AclClassification::DeniedUserAcl
452                | AclClassification::Denied(_)
453                | AclClassification::DeniedDefault
454                | AclClassification::DeniedNotGlobal
455        )
456    }
457}
458
459/// Represents an HTTP request method.
460#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
461#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
462pub enum HttpRequestMethod {
463    /// The CONNECT method.
464    CONNECT,
465    /// The DELETE method.
466    DELETE,
467    /// The GET method.
468    GET,
469    /// The HEAD method.
470    HEAD,
471    /// The OPTIONS method.
472    OPTIONS,
473    /// The PATCH method.
474    PATCH,
475    /// The POST method.
476    POST,
477    /// The PUT method.
478    PUT,
479    /// The TRACE method.
480    TRACE,
481    /// Any other method.
482    OTHER(Box<str>),
483}
484
485impl From<&str> for HttpRequestMethod {
486    fn from(method: &str) -> Self {
487        match method {
488            "CONNECT" => HttpRequestMethod::CONNECT,
489            "DELETE" => HttpRequestMethod::DELETE,
490            "GET" => HttpRequestMethod::GET,
491            "HEAD" => HttpRequestMethod::HEAD,
492            "OPTIONS" => HttpRequestMethod::OPTIONS,
493            "PATCH" => HttpRequestMethod::PATCH,
494            "POST" => HttpRequestMethod::POST,
495            "PUT" => HttpRequestMethod::PUT,
496            "TRACE" => HttpRequestMethod::TRACE,
497            _ => HttpRequestMethod::OTHER(method.into()),
498        }
499    }
500}
501
502impl HttpRequestMethod {
503    /// Return the method as a `&str`.
504    pub fn as_str(&self) -> &str {
505        match self {
506            HttpRequestMethod::CONNECT => "CONNECT",
507            HttpRequestMethod::DELETE => "DELETE",
508            HttpRequestMethod::GET => "GET",
509            HttpRequestMethod::HEAD => "HEAD",
510            HttpRequestMethod::OPTIONS => "OPTIONS",
511            HttpRequestMethod::PATCH => "PATCH",
512            HttpRequestMethod::POST => "POST",
513            HttpRequestMethod::PUT => "PUT",
514            HttpRequestMethod::TRACE => "TRACE",
515            HttpRequestMethod::OTHER(other) => other,
516        }
517    }
518}
519
520/// A builder for [`HttpAcl`].
521///
522/// Most categories (methods, hosts, port ranges, IP ranges, headers, URL paths,
523/// static DNS mappings) follow the same set of methods: `add_allowed_*`/
524/// `add_denied_*` to add a single entry, `remove_allowed_*`/`remove_denied_*` to
525/// remove one, `allowed_*`/`denied_*` to replace the whole list at once, and
526/// `clear_allowed_*`/`clear_denied_*` to empty it. The fallible variants return
527/// [`AddError`] rather than panicking, e.g. when an entry is already present on the
528/// opposite list, so a host (or header, port range, and so on) can never end up
529/// allowed and denied at the same time.
530///
531/// Call [`Self::build`] or [`Self::try_build`] to finish. Only the latter validates
532/// the finished configuration (uniqueness, overlaps, non-global IP ranges); see
533/// their docs for when each applies.
534#[derive(Default, Clone)]
535#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
536pub struct HttpAclBuilder {
537    allow_http: bool,
538    allow_https: bool,
539    allowed_methods: Vec<HttpRequestMethod>,
540    denied_methods: Vec<HttpRequestMethod>,
541    allowed_hosts: Vec<String>,
542    denied_hosts: Vec<String>,
543    #[cfg_attr(feature = "serde", serde(skip))]
544    allowed_host_patterns: Vec<HostPattern>,
545    #[cfg_attr(feature = "serde", serde(skip))]
546    denied_host_patterns: Vec<HostPattern>,
547    allowed_port_ranges: Vec<RangeInclusive<u16>>,
548    denied_port_ranges: Vec<RangeInclusive<u16>>,
549    allowed_ip_ranges: Vec<RangeInclusive<IpAddr>>,
550    denied_ip_ranges: Vec<RangeInclusive<IpAddr>>,
551    static_dns_mapping: HashMap<String, SocketAddr>,
552    trusted_static_dns_mapping: HashMap<String, SocketAddr>,
553    allowed_headers: HashMap<String, Option<String>>,
554    denied_headers: HashMap<String, Option<String>>,
555    allowed_url_paths: Vec<String>,
556    #[cfg_attr(feature = "serde", serde(skip))]
557    allowed_url_paths_router: Router<()>,
558    denied_url_paths: Vec<String>,
559    #[cfg_attr(feature = "serde", serde(skip))]
560    denied_url_paths_router: Router<()>,
561    allow_non_global_ip_ranges: bool,
562    method_acl_default: bool,
563    host_acl_default: bool,
564    port_acl_default: bool,
565    ip_acl_default: bool,
566    header_acl_default: bool,
567    url_path_acl_default: bool,
568}
569
570impl std::fmt::Debug for HttpAclBuilder {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        f.debug_struct("HttpAclBuilder")
573            .field("allow_http", &self.allow_http)
574            .field("allow_https", &self.allow_https)
575            .field("allowed_methods", &self.allowed_methods)
576            .field("denied_methods", &self.denied_methods)
577            .field("allowed_hosts", &self.allowed_hosts)
578            .field("denied_hosts", &self.denied_hosts)
579            .field("allowed_port_ranges", &self.allowed_port_ranges)
580            .field("denied_port_ranges", &self.denied_port_ranges)
581            .field("allowed_ip_ranges", &self.allowed_ip_ranges)
582            .field("denied_ip_ranges", &self.denied_ip_ranges)
583            .field("static_dns_mapping", &self.static_dns_mapping)
584            .field(
585                "trusted_static_dns_mapping",
586                &self.trusted_static_dns_mapping,
587            )
588            .field("allowed_headers", &self.allowed_headers)
589            .field("denied_headers", &self.denied_headers)
590            .field("allowed_url_paths", &self.allowed_url_paths)
591            .field("denied_url_paths", &self.denied_url_paths)
592            .field(
593                "allow_non_global_ip_ranges",
594                &self.allow_non_global_ip_ranges,
595            )
596            .field("method_acl_default", &self.method_acl_default)
597            .field("host_acl_default", &self.host_acl_default)
598            .field("port_acl_default", &self.port_acl_default)
599            .field("ip_acl_default", &self.ip_acl_default)
600            .field("header_acl_default", &self.header_acl_default)
601            .field("url_path_acl_default", &self.url_path_acl_default)
602            .finish()
603    }
604}
605
606impl PartialEq for HttpAclBuilder {
607    fn eq(&self, other: &Self) -> bool {
608        self.allow_http == other.allow_http
609            && self.allow_https == other.allow_https
610            && self.allowed_methods == other.allowed_methods
611            && self.denied_methods == other.denied_methods
612            && self.allowed_hosts == other.allowed_hosts
613            && self.denied_hosts == other.denied_hosts
614            && self.allowed_port_ranges == other.allowed_port_ranges
615            && self.denied_port_ranges == other.denied_port_ranges
616            && self.allowed_ip_ranges == other.allowed_ip_ranges
617            && self.denied_ip_ranges == other.denied_ip_ranges
618            && self.static_dns_mapping == other.static_dns_mapping
619            && self.trusted_static_dns_mapping == other.trusted_static_dns_mapping
620            && self.allowed_headers == other.allowed_headers
621            && self.denied_headers == other.denied_headers
622            && self.allowed_url_paths == other.allowed_url_paths
623            && self.denied_url_paths == other.denied_url_paths
624            && self.allow_non_global_ip_ranges == other.allow_non_global_ip_ranges
625            && self.method_acl_default == other.method_acl_default
626            && self.host_acl_default == other.host_acl_default
627            && self.port_acl_default == other.port_acl_default
628            && self.ip_acl_default == other.ip_acl_default
629            && self.header_acl_default == other.header_acl_default
630            && self.url_path_acl_default == other.url_path_acl_default
631    }
632}
633
634impl HttpAclBuilder {
635    /// Create a new [`HttpAclBuilder`].
636    pub fn new() -> Self {
637        Self {
638            allow_http: true,
639            allow_https: true,
640            allowed_methods: vec![
641                HttpRequestMethod::CONNECT,
642                HttpRequestMethod::DELETE,
643                HttpRequestMethod::GET,
644                HttpRequestMethod::HEAD,
645                HttpRequestMethod::OPTIONS,
646                HttpRequestMethod::PATCH,
647                HttpRequestMethod::POST,
648                HttpRequestMethod::PUT,
649                HttpRequestMethod::TRACE,
650            ],
651            denied_methods: Vec::new(),
652            allowed_hosts: Vec::new(),
653            denied_hosts: Vec::new(),
654            allowed_host_patterns: Vec::new(),
655            denied_host_patterns: Vec::new(),
656            allowed_port_ranges: vec![80..=80, 443..=443],
657            denied_port_ranges: Vec::new(),
658            allowed_ip_ranges: Vec::new(),
659            denied_ip_ranges: Vec::new(),
660            allowed_headers: HashMap::new(),
661            denied_headers: HashMap::new(),
662            allowed_url_paths: Vec::new(),
663            allowed_url_paths_router: Router::new(),
664            denied_url_paths: Vec::new(),
665            denied_url_paths_router: Router::new(),
666            allow_non_global_ip_ranges: false,
667            static_dns_mapping: HashMap::new(),
668            trusted_static_dns_mapping: HashMap::new(),
669            method_acl_default: false,
670            host_acl_default: false,
671            port_acl_default: false,
672            ip_acl_default: false,
673            header_acl_default: true,
674            url_path_acl_default: true,
675        }
676    }
677
678    /// Sets whether HTTP is allowed.
679    pub fn http(mut self, allow: bool) -> Self {
680        self.allow_http = allow;
681        self
682    }
683
684    /// Sets whether HTTPS is allowed.
685    pub fn https(mut self, allow: bool) -> Self {
686        self.allow_https = allow;
687        self
688    }
689
690    /// Sets whether non-global IP ranges are allowed.
691    ///
692    /// Non-global IP ranges include private, loopback, link-local, and other special-use addresses.
693    pub fn non_global_ip_ranges(mut self, allow: bool) -> Self {
694        self.allow_non_global_ip_ranges = allow;
695        self
696    }
697
698    /// Set default action for HTTP methods if no ACL match is found.
699    pub fn method_acl_default(mut self, allow: bool) -> Self {
700        self.method_acl_default = allow;
701        self
702    }
703
704    /// Set default action for hosts if no ACL match is found.
705    pub fn host_acl_default(mut self, allow: bool) -> Self {
706        self.host_acl_default = allow;
707        self
708    }
709
710    /// Set default action for ports if no ACL match is found.
711    pub fn port_acl_default(mut self, allow: bool) -> Self {
712        self.port_acl_default = allow;
713        self
714    }
715
716    /// Set default action for IPs if no ACL match is found.
717    pub fn ip_acl_default(mut self, allow: bool) -> Self {
718        self.ip_acl_default = allow;
719        self
720    }
721
722    /// Set default action for headers if no ACL match is found.
723    pub fn header_acl_default(mut self, allow: bool) -> Self {
724        self.header_acl_default = allow;
725        self
726    }
727
728    /// Set default action for URL paths if no ACL match is found.
729    pub fn url_path_acl_default(mut self, allow: bool) -> Self {
730        self.url_path_acl_default = allow;
731        self
732    }
733
734    /// Adds a method to the allowed methods.
735    ///
736    /// Note: If you pass a string ensure it is uppercased first.
737    pub fn add_allowed_method(
738        mut self,
739        method: impl Into<HttpRequestMethod>,
740    ) -> Result<Self, AddError> {
741        let method = method.into();
742        if self.denied_methods.contains(&method) {
743            Err(AddError::AlreadyDeniedMethod(method))
744        } else if self.allowed_methods.contains(&method) {
745            Err(AddError::AlreadyAllowedMethod(method))
746        } else {
747            self.allowed_methods.push(method);
748            Ok(self)
749        }
750    }
751
752    /// Removes a method from the allowed methods.
753    ///
754    /// Note: If you pass a string ensure it is uppercased first.
755    pub fn remove_allowed_method(mut self, method: impl Into<HttpRequestMethod>) -> Self {
756        let method = method.into();
757        self.allowed_methods.retain(|m| m != &method);
758        self
759    }
760
761    /// Sets the allowed methods.
762    ///
763    /// Note: If you pass strings ensure they are uppercased first.
764    pub fn allowed_methods(
765        mut self,
766        methods: Vec<impl Into<HttpRequestMethod>>,
767    ) -> Result<Self, AddError> {
768        let methods = methods.into_iter().map(|m| m.into()).collect::<Vec<_>>();
769
770        for method in &methods {
771            if self.denied_methods.contains(method) {
772                return Err(AddError::AlreadyDeniedMethod(method.clone()));
773            }
774        }
775        self.allowed_methods = methods;
776        Ok(self)
777    }
778
779    /// Clears the allowed methods.
780    pub fn clear_allowed_methods(mut self) -> Self {
781        self.allowed_methods.clear();
782        self
783    }
784
785    /// Adds a method to the denied methods.
786    ///
787    /// Note: If you pass a string ensure it is uppercased first.
788    pub fn add_denied_method(
789        mut self,
790        method: impl Into<HttpRequestMethod>,
791    ) -> Result<Self, AddError> {
792        let method = method.into();
793        if self.allowed_methods.contains(&method) {
794            Err(AddError::AlreadyAllowedMethod(method))
795        } else if self.denied_methods.contains(&method) {
796            Err(AddError::AlreadyDeniedMethod(method))
797        } else {
798            self.denied_methods.push(method);
799            Ok(self)
800        }
801    }
802
803    /// Removes a method from the denied methods.
804    ///
805    /// Note: If you pass a string ensure it is uppercased first.
806    pub fn remove_denied_method(mut self, method: impl Into<HttpRequestMethod>) -> Self {
807        let method = method.into();
808        self.denied_methods.retain(|m| m != &method);
809        self
810    }
811
812    /// Sets the denied methods.
813    ///
814    /// Note: If you pass strings ensure they are uppercased first.
815    pub fn denied_methods(
816        mut self,
817        methods: Vec<impl Into<HttpRequestMethod>>,
818    ) -> Result<Self, AddError> {
819        let methods = methods.into_iter().map(|m| m.into()).collect::<Vec<_>>();
820
821        for method in &methods {
822            if self.allowed_methods.contains(method) {
823                return Err(AddError::AlreadyAllowedMethod(method.clone()));
824            }
825        }
826        self.denied_methods = methods;
827        Ok(self)
828    }
829
830    /// Clears the denied methods.
831    pub fn clear_denied_methods(mut self) -> Self {
832        self.denied_methods.clear();
833        self
834    }
835
836    /// Adds a host to the allowed hosts.
837    ///
838    /// `host` may be an exact hostname, or a wildcard pattern where each label
839    /// (dot-separated segment) is either literal or one of:
840    ///
841    /// - `?` - matches exactly one label (e.g. `?.example.com` matches `foo.example.com`
842    ///   but not `foo.bar.example.com` or bare `example.com`).
843    /// - `*` - matches one or more labels (e.g. `*.example.com` matches `foo.example.com`
844    ///   and `foo.bar.example.com`, but not bare `example.com`).
845    ///
846    /// A wildcard must occupy an entire label; `foo*.example.com` is not a valid pattern.
847    ///
848    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
849    pub fn add_allowed_host(mut self, host: String) -> Result<Self, AddError> {
850        let pattern = Self::validate_host_or_pattern(&host)?;
851
852        if self.denied_hosts.contains(&host) {
853            return Err(AddError::AlreadyDeniedHost(host));
854        }
855        if self.allowed_hosts.contains(&host) {
856            return Err(AddError::AlreadyAllowedHost(host));
857        }
858
859        if let Some(pattern) = pattern {
860            self.allowed_host_patterns.push(pattern);
861        }
862        self.allowed_hosts.push(host);
863        Ok(self)
864    }
865
866    /// Removes a host from the allowed hosts.
867    ///
868    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
869    pub fn remove_allowed_host(mut self, host: String) -> Self {
870        self.allowed_hosts.retain(|h| h != &host);
871        self.allowed_host_patterns = Self::compile_host_patterns(&self.allowed_hosts);
872        self
873    }
874
875    /// Sets the allowed hosts.
876    ///
877    /// See [`Self::add_allowed_host`] for the wildcard pattern syntax.
878    ///
879    /// Note: The hosts should be in their canonical form (lowercase, punycode for IDN).
880    pub fn allowed_hosts(mut self, hosts: Vec<String>) -> Result<Self, AddError> {
881        let mut patterns = Vec::new();
882        for host in &hosts {
883            if let Some(pattern) = Self::validate_host_or_pattern(host)? {
884                patterns.push(pattern);
885            }
886            if self.denied_hosts.contains(host) {
887                return Err(AddError::AlreadyDeniedHost(host.clone()));
888            }
889        }
890        self.allowed_host_patterns = patterns;
891        self.allowed_hosts = hosts;
892        Ok(self)
893    }
894
895    /// Clears the allowed hosts.
896    pub fn clear_allowed_hosts(mut self) -> Self {
897        self.allowed_hosts.clear();
898        self.allowed_host_patterns.clear();
899        self
900    }
901
902    /// Adds a host to the denied hosts.
903    ///
904    /// See [`Self::add_allowed_host`] for the wildcard pattern syntax.
905    ///
906    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
907    pub fn add_denied_host(mut self, host: String) -> Result<Self, AddError> {
908        let pattern = Self::validate_host_or_pattern(&host)?;
909
910        if self.allowed_hosts.contains(&host) {
911            return Err(AddError::AlreadyAllowedHost(host));
912        }
913        if self.denied_hosts.contains(&host) {
914            return Err(AddError::AlreadyDeniedHost(host));
915        }
916
917        if let Some(pattern) = pattern {
918            self.denied_host_patterns.push(pattern);
919        }
920        self.denied_hosts.push(host);
921        Ok(self)
922    }
923
924    /// Removes a host from the denied hosts.
925    ///
926    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
927    pub fn remove_denied_host(mut self, host: String) -> Self {
928        self.denied_hosts.retain(|h| h != &host);
929        self.denied_host_patterns = Self::compile_host_patterns(&self.denied_hosts);
930        self
931    }
932
933    /// Sets the denied hosts.
934    ///
935    /// See [`Self::add_allowed_host`] for the wildcard pattern syntax.
936    ///
937    /// Note: The hosts should be in their canonical form (lowercase, punycode for IDN).
938    pub fn denied_hosts(mut self, hosts: Vec<String>) -> Result<Self, AddError> {
939        let mut patterns = Vec::new();
940        for host in &hosts {
941            if let Some(pattern) = Self::validate_host_or_pattern(host)? {
942                patterns.push(pattern);
943            }
944            if self.allowed_hosts.contains(host) {
945                return Err(AddError::AlreadyAllowedHost(host.clone()));
946            }
947        }
948        self.denied_host_patterns = patterns;
949        self.denied_hosts = hosts;
950        Ok(self)
951    }
952
953    /// Clears the denied hosts.
954    pub fn clear_denied_hosts(mut self) -> Self {
955        self.denied_hosts.clear();
956        self.denied_host_patterns.clear();
957        self
958    }
959
960    /// Validates a host string, returning its compiled [`HostPattern`] if it is a
961    /// wildcard pattern, or `None` if it's a literal host.
962    fn validate_host_or_pattern(host: &str) -> Result<Option<HostPattern>, AddError> {
963        if is_wildcard_host(host) {
964            match HostPattern::parse(host) {
965                Some(pattern) => Ok(Some(pattern)),
966                None => Err(AddError::InvalidEntity(host.to_string())),
967            }
968        } else if utils::authority::is_valid_host(host) {
969            Ok(None)
970        } else {
971            Err(AddError::InvalidEntity(host.to_string()))
972        }
973    }
974
975    /// Compiles the wildcard patterns out of a list of (already-validated) host strings.
976    fn compile_host_patterns(hosts: &[String]) -> Vec<HostPattern> {
977        hosts
978            .iter()
979            .filter(|h| is_wildcard_host(h))
980            .filter_map(|h| HostPattern::parse(h))
981            .collect()
982    }
983
984    /// Adds a port range to the allowed port ranges.
985    pub fn add_allowed_port_range(
986        mut self,
987        port_range: RangeInclusive<u16>,
988    ) -> Result<Self, AddError> {
989        if self.denied_port_ranges.contains(&port_range) {
990            Err(AddError::AlreadyDeniedPortRange(port_range))
991        } else if self.allowed_port_ranges.contains(&port_range) {
992            Err(AddError::AlreadyAllowedPortRange(port_range))
993        } else if utils::range_overlaps(&self.allowed_port_ranges, &port_range, None)
994            || utils::range_overlaps(&self.denied_port_ranges, &port_range, None)
995        {
996            Err(AddError::Overlaps(format!("{port_range:?}")))
997        } else {
998            self.allowed_port_ranges.push(port_range);
999            Ok(self)
1000        }
1001    }
1002
1003    /// Removes a port range from the allowed port ranges.
1004    pub fn remove_allowed_port_range(mut self, port_range: RangeInclusive<u16>) -> Self {
1005        self.allowed_port_ranges.retain(|p| p != &port_range);
1006        self
1007    }
1008
1009    /// Sets the allowed port ranges.
1010    pub fn allowed_port_ranges(
1011        mut self,
1012        port_ranges: Vec<RangeInclusive<u16>>,
1013    ) -> Result<Self, AddError> {
1014        for (i, port_range) in port_ranges.iter().enumerate() {
1015            if self.denied_port_ranges.contains(port_range) {
1016                return Err(AddError::AlreadyDeniedPortRange(port_range.clone()));
1017            } else if utils::range_overlaps(&port_ranges, port_range, Some(i))
1018                || utils::range_overlaps(&self.denied_port_ranges, port_range, None)
1019            {
1020                return Err(AddError::Overlaps(format!("{port_range:?}")));
1021            }
1022        }
1023        self.allowed_port_ranges = port_ranges;
1024        Ok(self)
1025    }
1026
1027    /// Clears the allowed port ranges.
1028    pub fn clear_allowed_port_ranges(mut self) -> Self {
1029        self.allowed_port_ranges.clear();
1030        self
1031    }
1032
1033    /// Adds a port range to the denied port ranges.
1034    pub fn add_denied_port_range(
1035        mut self,
1036        port_range: RangeInclusive<u16>,
1037    ) -> Result<Self, AddError> {
1038        if self.allowed_port_ranges.contains(&port_range) {
1039            Err(AddError::AlreadyAllowedPortRange(port_range))
1040        } else if self.denied_port_ranges.contains(&port_range) {
1041            Err(AddError::AlreadyDeniedPortRange(port_range))
1042        } else if utils::range_overlaps(&self.allowed_port_ranges, &port_range, None)
1043            || utils::range_overlaps(&self.denied_port_ranges, &port_range, None)
1044        {
1045            Err(AddError::Overlaps(format!("{port_range:?}")))
1046        } else {
1047            self.denied_port_ranges.push(port_range);
1048            Ok(self)
1049        }
1050    }
1051
1052    /// Removes a port range from the denied port ranges.
1053    pub fn remove_denied_port_range(mut self, port_range: RangeInclusive<u16>) -> Self {
1054        self.denied_port_ranges.retain(|p| p != &port_range);
1055        self
1056    }
1057
1058    /// Sets the denied port ranges.
1059    pub fn denied_port_ranges(
1060        mut self,
1061        port_ranges: Vec<RangeInclusive<u16>>,
1062    ) -> Result<Self, AddError> {
1063        for (i, port_range) in port_ranges.iter().enumerate() {
1064            if self.allowed_port_ranges.contains(port_range) {
1065                return Err(AddError::AlreadyAllowedPortRange(port_range.clone()));
1066            } else if utils::range_overlaps(&port_ranges, port_range, Some(i))
1067                || utils::range_overlaps(&self.allowed_port_ranges, port_range, None)
1068            {
1069                return Err(AddError::Overlaps(format!("{port_range:?}")));
1070            }
1071        }
1072        self.denied_port_ranges = port_ranges;
1073        Ok(self)
1074    }
1075
1076    /// Clears the denied port ranges.
1077    pub fn clear_denied_port_ranges(mut self) -> Self {
1078        self.denied_port_ranges.clear();
1079        self
1080    }
1081
1082    /// Adds an IP range to the allowed IP ranges.
1083    pub fn add_allowed_ip_range<Ip: IntoIpRange>(mut self, ip_range: Ip) -> Result<Self, AddError> {
1084        let ip_range = ip_range
1085            .into_range()
1086            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1087        if self.denied_ip_ranges.contains(&ip_range) {
1088            return Err(AddError::AlreadyDeniedIpRange(ip_range));
1089        } else if self.allowed_ip_ranges.contains(&ip_range) {
1090            return Err(AddError::AlreadyAllowedIpRange(ip_range));
1091        } else if utils::range_overlaps(&self.allowed_ip_ranges, &ip_range, None)
1092            || utils::range_overlaps(&self.denied_ip_ranges, &ip_range, None)
1093        {
1094            return Err(AddError::Overlaps(format!("{ip_range:?}")));
1095        }
1096        self.allowed_ip_ranges.push(ip_range);
1097        Ok(self)
1098    }
1099
1100    /// Removes an IP range from the allowed IP ranges.
1101    pub fn remove_allowed_ip_range<Ip: IntoIpRange>(
1102        mut self,
1103        ip_range: Ip,
1104    ) -> Result<Self, AddError> {
1105        let ip_range = ip_range
1106            .into_range()
1107            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1108        self.allowed_ip_ranges.retain(|ip| ip != &ip_range);
1109        Ok(self)
1110    }
1111
1112    /// Sets the allowed IP ranges.
1113    pub fn allowed_ip_ranges<Ip: IntoIpRange>(
1114        mut self,
1115        ip_ranges: Vec<Ip>,
1116    ) -> Result<Self, AddError> {
1117        let ip_ranges = ip_ranges
1118            .into_iter()
1119            .map(|ip| ip.into_range())
1120            .collect::<Option<Vec<_>>>()
1121            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1122        for (i, ip_range) in ip_ranges.iter().enumerate() {
1123            if self.denied_ip_ranges.contains(ip_range) {
1124                return Err(AddError::AlreadyDeniedIpRange(ip_range.clone()));
1125            } else if utils::range_overlaps(&ip_ranges, ip_range, Some(i))
1126                || utils::range_overlaps(&self.denied_ip_ranges, ip_range, None)
1127            {
1128                return Err(AddError::Overlaps(format!("{ip_range:?}")));
1129            }
1130        }
1131        self.allowed_ip_ranges = ip_ranges;
1132        Ok(self)
1133    }
1134
1135    /// Clears the allowed IP ranges.
1136    pub fn clear_allowed_ip_ranges(mut self) -> Self {
1137        self.allowed_ip_ranges.clear();
1138        self
1139    }
1140
1141    /// Adds an IP range to the denied IP ranges.
1142    pub fn add_denied_ip_range<Ip: IntoIpRange>(mut self, ip_range: Ip) -> Result<Self, AddError> {
1143        let ip_range = ip_range
1144            .into_range()
1145            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1146        if self.allowed_ip_ranges.contains(&ip_range) {
1147            return Err(AddError::AlreadyAllowedIpRange(ip_range));
1148        } else if self.denied_ip_ranges.contains(&ip_range) {
1149            return Err(AddError::AlreadyDeniedIpRange(ip_range));
1150        } else if utils::range_overlaps(&self.allowed_ip_ranges, &ip_range, None)
1151            || utils::range_overlaps(&self.denied_ip_ranges, &ip_range, None)
1152        {
1153            return Err(AddError::Overlaps(format!("{ip_range:?}")));
1154        }
1155        self.denied_ip_ranges.push(ip_range);
1156        Ok(self)
1157    }
1158
1159    /// Removes an IP range from the denied IP ranges.
1160    pub fn remove_denied_ip_range<Ip: IntoIpRange>(
1161        mut self,
1162        ip_range: Ip,
1163    ) -> Result<Self, AddError> {
1164        let ip_range = ip_range
1165            .into_range()
1166            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1167        self.denied_ip_ranges.retain(|ip| ip != &ip_range);
1168        Ok(self)
1169    }
1170
1171    /// Sets the denied IP ranges.
1172    pub fn denied_ip_ranges<Ip: IntoIpRange>(
1173        mut self,
1174        ip_ranges: Vec<Ip>,
1175    ) -> Result<Self, AddError> {
1176        let ip_ranges = ip_ranges
1177            .into_iter()
1178            .map(|ip| ip.into_range())
1179            .collect::<Option<Vec<_>>>()
1180            .ok_or_else(|| AddError::InvalidEntity("Invalid IP range".to_string()))?;
1181        for (i, ip_range) in ip_ranges.iter().enumerate() {
1182            if self.allowed_ip_ranges.contains(ip_range) {
1183                return Err(AddError::AlreadyAllowedIpRange(ip_range.clone()));
1184            } else if utils::range_overlaps(&ip_ranges, ip_range, Some(i))
1185                || utils::range_overlaps(&self.allowed_ip_ranges, ip_range, None)
1186            {
1187                return Err(AddError::Overlaps(format!("{ip_range:?}")));
1188            }
1189        }
1190        self.denied_ip_ranges = ip_ranges;
1191        Ok(self)
1192    }
1193
1194    /// Clears the denied IP ranges.
1195    pub fn clear_denied_ip_ranges(mut self) -> Self {
1196        self.denied_ip_ranges.clear();
1197        self
1198    }
1199
1200    /// Add a static DNS mapping.
1201    ///
1202    /// The resolved address is still subject to the IP and port ACL. Use
1203    /// [`Self::add_trusted_static_dns_mapping`] for a mapping that should bypass
1204    /// those checks entirely.
1205    ///
1206    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
1207    pub fn add_static_dns_mapping(
1208        mut self,
1209        host: String,
1210        sock_addr: SocketAddr,
1211    ) -> Result<Self, AddError> {
1212        if !utils::authority::is_valid_host(&host) {
1213            return Err(AddError::InvalidEntity(host));
1214        }
1215        if self.trusted_static_dns_mapping.contains_key(&host) {
1216            return Err(AddError::AlreadyPresentTrustedStaticDnsMapping(
1217                host, sock_addr,
1218            ));
1219        }
1220        if let Entry::Vacant(e) = self.static_dns_mapping.entry(host.clone()) {
1221            e.insert(sock_addr);
1222            Ok(self)
1223        } else {
1224            Err(AddError::AlreadyPresentStaticDnsMapping(host, sock_addr))
1225        }
1226    }
1227
1228    /// Removes a static DNS mapping.
1229    ///
1230    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
1231    pub fn remove_static_dns_mapping(mut self, host: &str) -> Self {
1232        self.static_dns_mapping.remove(host);
1233        self
1234    }
1235
1236    /// Sets the static DNS mappings.
1237    ///
1238    /// Note: The hosts should be in their canonical form (lowercase, punycode for IDN).
1239    pub fn static_dns_mappings(
1240        mut self,
1241        mappings: HashMap<String, SocketAddr>,
1242    ) -> Result<Self, AddError> {
1243        for (host, ip) in &mappings {
1244            if !utils::authority::is_valid_host(host) {
1245                return Err(AddError::InvalidEntity(host.clone()));
1246            }
1247            if self.trusted_static_dns_mapping.contains_key(host) {
1248                return Err(AddError::AlreadyPresentTrustedStaticDnsMapping(
1249                    host.clone(),
1250                    *ip,
1251                ));
1252            }
1253            if self.static_dns_mapping.contains_key(host) {
1254                return Err(AddError::AlreadyPresentStaticDnsMapping(host.clone(), *ip));
1255            }
1256            self.static_dns_mapping.insert(host.to_string(), *ip);
1257        }
1258        Ok(self)
1259    }
1260
1261    /// Clears the static DNS mappings.
1262    pub fn clear_static_dns_mappings(mut self) -> Self {
1263        self.static_dns_mapping.clear();
1264        self
1265    }
1266
1267    /// Add a trusted static DNS mapping.
1268    ///
1269    /// Unlike [`Self::add_static_dns_mapping`], the resolved address is meant to
1270    /// bypass the IP and port ACL entirely - only use this for mappings you trust
1271    /// regardless of what the ACL would otherwise say (e.g. pinning a hostname to an
1272    /// internal address on purpose).
1273    ///
1274    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
1275    pub fn add_trusted_static_dns_mapping(
1276        mut self,
1277        host: String,
1278        sock_addr: SocketAddr,
1279    ) -> Result<Self, AddError> {
1280        if !utils::authority::is_valid_host(&host) {
1281            return Err(AddError::InvalidEntity(host));
1282        }
1283        if self.static_dns_mapping.contains_key(&host) {
1284            return Err(AddError::AlreadyPresentStaticDnsMapping(host, sock_addr));
1285        }
1286        if let Entry::Vacant(e) = self.trusted_static_dns_mapping.entry(host.clone()) {
1287            e.insert(sock_addr);
1288            Ok(self)
1289        } else {
1290            Err(AddError::AlreadyPresentTrustedStaticDnsMapping(
1291                host, sock_addr,
1292            ))
1293        }
1294    }
1295
1296    /// Removes a trusted static DNS mapping.
1297    ///
1298    /// Note: The host should be in its canonical form (lowercase, punycode for IDN).
1299    pub fn remove_trusted_static_dns_mapping(mut self, host: &str) -> Self {
1300        self.trusted_static_dns_mapping.remove(host);
1301        self
1302    }
1303
1304    /// Sets the trusted static DNS mappings.
1305    ///
1306    /// Note: The hosts should be in their canonical form (lowercase, punycode for IDN).
1307    pub fn trusted_static_dns_mappings(
1308        mut self,
1309        mappings: HashMap<String, SocketAddr>,
1310    ) -> Result<Self, AddError> {
1311        for (host, ip) in &mappings {
1312            if !utils::authority::is_valid_host(host) {
1313                return Err(AddError::InvalidEntity(host.clone()));
1314            }
1315            if self.static_dns_mapping.contains_key(host) {
1316                return Err(AddError::AlreadyPresentStaticDnsMapping(host.clone(), *ip));
1317            }
1318            if self.trusted_static_dns_mapping.contains_key(host) {
1319                return Err(AddError::AlreadyPresentTrustedStaticDnsMapping(
1320                    host.clone(),
1321                    *ip,
1322                ));
1323            }
1324            self.trusted_static_dns_mapping
1325                .insert(host.to_string(), *ip);
1326        }
1327        Ok(self)
1328    }
1329
1330    /// Clears the trusted static DNS mappings.
1331    pub fn clear_trusted_static_dns_mappings(mut self) -> Self {
1332        self.trusted_static_dns_mapping.clear();
1333        self
1334    }
1335
1336    /// Adds a header to the allowed headers.
1337    ///
1338    /// If `value` is `None`, any value for the header is allowed.
1339    ///
1340    /// Note: Ensure header names are lowercased.
1341    pub fn add_allowed_header(
1342        mut self,
1343        header: String,
1344        value: Option<String>,
1345    ) -> Result<Self, AddError> {
1346        if self.denied_headers.contains_key(&header) {
1347            Err(AddError::AlreadyDeniedHeader(header, value.clone()))
1348        } else if let Entry::Vacant(e) = self.allowed_headers.entry(header.clone()) {
1349            e.insert(value);
1350            Ok(self)
1351        } else {
1352            Err(AddError::AlreadyAllowedHeader(header, value))
1353        }
1354    }
1355
1356    /// Removes a header from the allowed headers.
1357    ///
1358    /// Note: Ensure header names are lowercased.
1359    pub fn remove_allowed_header(mut self, header: &str) -> Self {
1360        self.allowed_headers.remove(header);
1361        self
1362    }
1363
1364    /// Sets the allowed headers.
1365    ///
1366    /// Note: Ensure header names are lowercased.
1367    pub fn allowed_headers(
1368        mut self,
1369        headers: HashMap<String, Option<String>>,
1370    ) -> Result<Self, AddError> {
1371        for (header, value) in &headers {
1372            if self.denied_headers.contains_key(header) {
1373                return Err(AddError::AlreadyDeniedHeader(header.clone(), value.clone()));
1374            }
1375        }
1376        self.allowed_headers = headers;
1377        Ok(self)
1378    }
1379
1380    /// Clears the allowed headers.
1381    pub fn clear_allowed_headers(mut self) -> Self {
1382        self.allowed_headers.clear();
1383        self
1384    }
1385
1386    /// Adds a header to the denied headers.
1387    ///
1388    /// If `value` is `None`, any value for the header is denied.
1389    ///
1390    /// Note: Ensure header names are lowercased.
1391    pub fn add_denied_header(
1392        mut self,
1393        header: String,
1394        value: Option<String>,
1395    ) -> Result<Self, AddError> {
1396        if self.allowed_headers.contains_key(&header) {
1397            Err(AddError::AlreadyAllowedHeader(header, value.clone()))
1398        } else if let Entry::Vacant(e) = self.denied_headers.entry(header.clone()) {
1399            e.insert(value);
1400            Ok(self)
1401        } else {
1402            Err(AddError::AlreadyDeniedHeader(header, value))
1403        }
1404    }
1405
1406    /// Removes a header from the denied headers.
1407    ///
1408    /// Note: Ensure header names are lowercased.
1409    pub fn remove_denied_header(mut self, header: &str) -> Self {
1410        self.denied_headers.remove(header);
1411        self
1412    }
1413
1414    /// Sets the denied headers.
1415    ///
1416    /// Note: Ensure header names are lowercased.
1417    pub fn denied_headers(
1418        mut self,
1419        headers: HashMap<String, Option<String>>,
1420    ) -> Result<Self, AddError> {
1421        for (header, value) in &headers {
1422            if self.allowed_headers.contains_key(header) {
1423                return Err(AddError::AlreadyAllowedHeader(
1424                    header.clone(),
1425                    value.clone(),
1426                ));
1427            }
1428        }
1429        self.denied_headers = headers;
1430        Ok(self)
1431    }
1432
1433    /// Clears the denied headers.
1434    pub fn clear_denied_headers(mut self) -> Self {
1435        self.denied_headers.clear();
1436        self
1437    }
1438
1439    /// Adds a URL path to the allowed URL paths.
1440    ///
1441    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1442    pub fn add_allowed_url_path(mut self, url_path: String) -> Result<Self, AddError> {
1443        if self.denied_url_paths.contains(&url_path)
1444            || self.denied_url_paths_router.at(&url_path).is_ok()
1445        {
1446            Err(AddError::AlreadyDeniedUrlPath(url_path))
1447        } else if self.allowed_url_paths.contains(&url_path)
1448            || self.allowed_url_paths_router.at(&url_path).is_ok()
1449        {
1450            Err(AddError::AlreadyAllowedUrlPath(url_path))
1451        } else {
1452            self.allowed_url_paths.push(url_path.clone());
1453            self.allowed_url_paths_router
1454                .insert(url_path, ())
1455                .map_err(|_| AddError::InvalidEntity("Invalid URL path".to_string()))?;
1456            Ok(self)
1457        }
1458    }
1459
1460    /// Removes a URL path from the allowed URL paths.
1461    ///
1462    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1463    pub fn remove_allowed_url_path(mut self, url_path: &str) -> Self {
1464        self.allowed_url_paths.retain(|p| p != url_path);
1465        self.allowed_url_paths_router = {
1466            let mut router = Router::new();
1467            for url_path in &self.allowed_url_paths {
1468                router
1469                    .insert(url_path.clone(), ())
1470                    .expect("failed to insert url path");
1471            }
1472            router
1473        };
1474        self
1475    }
1476
1477    /// Sets the allowed URL paths.
1478    ///
1479    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1480    pub fn allowed_url_paths(mut self, url_paths: Vec<String>) -> Result<Self, AddError> {
1481        for url_path in &url_paths {
1482            if self.denied_url_paths.contains(url_path)
1483                || self.denied_url_paths_router.at(url_path).is_ok()
1484            {
1485                return Err(AddError::AlreadyDeniedUrlPath(url_path.clone()));
1486            }
1487        }
1488        self.allowed_url_paths_router = Router::new();
1489        for url_path in &url_paths {
1490            self.allowed_url_paths_router
1491                .insert(url_path.clone(), ())
1492                .map_err(|_| AddError::InvalidEntity(format!("Invalid URL path: {url_path}")))?;
1493        }
1494        self.allowed_url_paths = url_paths;
1495        Ok(self)
1496    }
1497
1498    /// Clears the allowed URL paths.
1499    pub fn clear_allowed_url_paths(mut self) -> Self {
1500        self.allowed_url_paths.clear();
1501        self.allowed_url_paths_router = Router::new();
1502        self
1503    }
1504
1505    /// Adds a URL path to the denied URL paths.
1506    ///
1507    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1508    pub fn add_denied_url_path(mut self, url_path: String) -> Result<Self, AddError> {
1509        if self.allowed_url_paths.contains(&url_path)
1510            || self.allowed_url_paths_router.at(&url_path).is_ok()
1511        {
1512            Err(AddError::AlreadyAllowedUrlPath(url_path))
1513        } else if self.denied_url_paths.contains(&url_path)
1514            || self.denied_url_paths_router.at(&url_path).is_ok()
1515        {
1516            Err(AddError::AlreadyDeniedUrlPath(url_path))
1517        } else {
1518            self.denied_url_paths.push(url_path.clone());
1519            self.denied_url_paths_router
1520                .insert(url_path, ())
1521                .map_err(|_| AddError::InvalidEntity("Invalid URL path".to_string()))?;
1522            Ok(self)
1523        }
1524    }
1525
1526    /// Removes a URL path from the denied URL paths.
1527    ///
1528    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1529    pub fn remove_denied_url_path(mut self, url_path: &str) -> Self {
1530        self.denied_url_paths.retain(|p| p != url_path);
1531        self.denied_url_paths_router = {
1532            let mut router = Router::new();
1533            for url_path in &self.denied_url_paths {
1534                router
1535                    .insert(url_path.clone(), ())
1536                    .expect("failed to insert url path");
1537            }
1538            router
1539        };
1540        self
1541    }
1542
1543    /// Sets the denied URL paths.
1544    ///
1545    /// Note: URL paths should start with a '/' and be properly URL-encoded.
1546    pub fn denied_url_paths(mut self, url_paths: Vec<String>) -> Result<Self, AddError> {
1547        for url_path in &url_paths {
1548            if self.allowed_url_paths.contains(url_path)
1549                || self.allowed_url_paths_router.at(url_path).is_ok()
1550            {
1551                return Err(AddError::AlreadyAllowedUrlPath(url_path.clone()));
1552            }
1553        }
1554        self.denied_url_paths_router = Router::new();
1555        for url_path in &url_paths {
1556            self.denied_url_paths_router
1557                .insert(url_path.clone(), ())
1558                .map_err(|_| AddError::InvalidEntity(format!("Invalid URL path: {url_path}")))?;
1559        }
1560        self.denied_url_paths = url_paths;
1561        Ok(self)
1562    }
1563
1564    /// Clears the denied URL paths.
1565    pub fn clear_denied_url_paths(mut self) -> Self {
1566        self.denied_url_paths.clear();
1567        self.denied_url_paths_router = Router::new();
1568        self
1569    }
1570
1571    /// Builds the [`HttpAcl`], without a [`ValidateFn`].
1572    ///
1573    /// This does not validate the configuration (uniqueness, overlaps, non-global IP
1574    /// ranges); use [`Self::try_build`] instead if the builder wasn't assembled
1575    /// entirely through this type's own fallible `add_*`/`allowed_*`/`denied_*`
1576    /// methods, e.g. if it was deserialized. See [`Self::try_build`] for details.
1577    pub fn build(self) -> HttpAcl {
1578        self.build_full(None)
1579    }
1580
1581    /// Builds the [`HttpAcl`] with a [`ValidateFn`] attached.
1582    ///
1583    /// This is the only way to attach a `ValidateFn`; there is no dedicated builder
1584    /// setter for it. Like [`Self::build`], this does not validate the configuration;
1585    /// use [`Self::try_build_full`] for that.
1586    pub fn build_full(self, validate_fn: Option<ValidateFn>) -> HttpAcl {
1587        HttpAcl {
1588            allow_http: self.allow_http,
1589            allow_https: self.allow_https,
1590            allowed_methods: self.allowed_methods.into_iter().collect(),
1591            denied_methods: self.denied_methods.into_iter().collect(),
1592            allowed_hosts: self
1593                .allowed_hosts
1594                .into_iter()
1595                .filter(|h| !is_wildcard_host(h))
1596                .map(|x| x.into_boxed_str())
1597                .collect(),
1598            denied_hosts: self
1599                .denied_hosts
1600                .into_iter()
1601                .filter(|h| !is_wildcard_host(h))
1602                .map(|x| x.into_boxed_str())
1603                .collect(),
1604            allowed_host_patterns: self.allowed_host_patterns.into_boxed_slice(),
1605            denied_host_patterns: self.denied_host_patterns.into_boxed_slice(),
1606            allowed_port_ranges: self.allowed_port_ranges.into_boxed_slice(),
1607            denied_port_ranges: self.denied_port_ranges.into_boxed_slice(),
1608            allowed_ip_ranges: self.allowed_ip_ranges.into_boxed_slice(),
1609            denied_ip_ranges: self.denied_ip_ranges.into_boxed_slice(),
1610            allowed_headers: self
1611                .allowed_headers
1612                .into_iter()
1613                .map(|(k, v)| (k.into_boxed_str(), v.map(|s| s.into_boxed_str())))
1614                .collect(),
1615            denied_headers: self
1616                .denied_headers
1617                .into_iter()
1618                .map(|(k, v)| (k.into_boxed_str(), v.map(|s| s.into_boxed_str())))
1619                .collect(),
1620            allowed_url_paths_router: self.allowed_url_paths_router,
1621            denied_url_paths_router: self.denied_url_paths_router,
1622            static_dns_mapping: self
1623                .static_dns_mapping
1624                .into_iter()
1625                .map(|(k, v)| (k.into_boxed_str(), v))
1626                .collect(),
1627            trusted_static_dns_mapping: self
1628                .trusted_static_dns_mapping
1629                .into_iter()
1630                .map(|(k, v)| (k.into_boxed_str(), v))
1631                .collect(),
1632            validate_fn,
1633            allow_non_global_ip_ranges: self.allow_non_global_ip_ranges,
1634            method_acl_default: self.method_acl_default,
1635            host_acl_default: self.host_acl_default,
1636            port_acl_default: self.port_acl_default,
1637            ip_acl_default: self.ip_acl_default,
1638            header_acl_default: self.header_acl_default,
1639            url_path_acl_default: self.url_path_acl_default,
1640        }
1641    }
1642
1643    /// Builds the [`HttpAcl`] with a [`ValidateFn`] attached, validating the
1644    /// configuration first.
1645    ///
1646    /// Checks each category for unique entries, non-overlapping ranges, and no host
1647    /// (or port range, IP range, header, and so on) present on both the allowed and
1648    /// denied lists, returning [`AddError`] on the first problem found. It also
1649    /// enforces that IP ranges are global unless [`Self::non_global_ip_ranges`] was
1650    /// set to `true`, which [`Self::add_allowed_ip_range`]/
1651    /// [`Self::add_denied_ip_range`] do not check themselves.
1652    ///
1653    /// Prefer this over [`Self::build_full`] whenever the builder wasn't assembled
1654    /// entirely through this type's own fallible methods, most notably a builder
1655    /// deserialized from an untrusted source: deserialization writes fields directly
1656    /// and bypasses the checks each `add_*` method normally performs, so this is also
1657    /// what rebuilds the URL path routers and wildcard host patterns skipped for that
1658    /// reason.
1659    pub fn try_build_full(mut self, validate_fn: Option<ValidateFn>) -> Result<HttpAcl, AddError> {
1660        if !utils::has_unique_elements(&self.allowed_methods) {
1661            return Err(AddError::NotUnique(
1662                "Allowed methods must be unique.".to_string(),
1663            ));
1664        }
1665        for method in &self.allowed_methods {
1666            if self.denied_methods.contains(method) {
1667                return Err(AddError::BothAllowedAndDenied(format!(
1668                    "Method `{}`",
1669                    method.as_str()
1670                )));
1671            }
1672        }
1673        if !utils::has_unique_elements(&self.denied_methods) {
1674            return Err(AddError::NotUnique(
1675                "Denied methods must be unique.".to_string(),
1676            ));
1677        }
1678        for method in &self.denied_methods {
1679            if self.allowed_methods.contains(method) {
1680                return Err(AddError::BothAllowedAndDenied(format!(
1681                    "Method `{}`",
1682                    method.as_str()
1683                )));
1684            }
1685        }
1686        if !utils::has_unique_elements(&self.allowed_hosts) {
1687            return Err(AddError::NotUnique(
1688                "Allowed hosts must be unique.".to_string(),
1689            ));
1690        }
1691        for host in &self.allowed_hosts {
1692            if is_wildcard_host(host) {
1693                match HostPattern::parse(host) {
1694                    Some(pattern) => {
1695                        if !self.allowed_host_patterns.contains(&pattern) {
1696                            self.allowed_host_patterns.push(pattern);
1697                        }
1698                    }
1699                    None => return Err(AddError::InvalidEntity(host.to_string())),
1700                }
1701            } else if !utils::authority::is_valid_host(host) {
1702                return Err(AddError::InvalidEntity(host.to_string()));
1703            }
1704            if self.denied_hosts.contains(host) {
1705                return Err(AddError::BothAllowedAndDenied(format!("Host `{host}`")));
1706            }
1707        }
1708        if !utils::has_unique_elements(&self.denied_hosts) {
1709            return Err(AddError::NotUnique(
1710                "Denied hosts must be unique.".to_string(),
1711            ));
1712        }
1713        for host in &self.denied_hosts {
1714            if is_wildcard_host(host) {
1715                match HostPattern::parse(host) {
1716                    Some(pattern) => {
1717                        if !self.denied_host_patterns.contains(&pattern) {
1718                            self.denied_host_patterns.push(pattern);
1719                        }
1720                    }
1721                    None => return Err(AddError::InvalidEntity(host.to_string())),
1722                }
1723            } else if !utils::authority::is_valid_host(host) {
1724                return Err(AddError::InvalidEntity(host.to_string()));
1725            }
1726            if self.allowed_hosts.contains(host) {
1727                return Err(AddError::BothAllowedAndDenied(format!("Host `{host}`")));
1728            }
1729        }
1730        if !utils::has_unique_elements(&self.allowed_port_ranges) {
1731            return Err(AddError::NotUnique(
1732                "Allowed port ranges must be unique.".to_string(),
1733            ));
1734        }
1735        if utils::has_overlapping_ranges(&self.allowed_port_ranges) {
1736            return Err(AddError::Overlaps(
1737                "Allowed port ranges must not overlap.".to_string(),
1738            ));
1739        }
1740        for port_range in &self.allowed_port_ranges {
1741            if self.denied_port_ranges.contains(port_range) {
1742                return Err(AddError::BothAllowedAndDenied(format!(
1743                    "Port range `{port_range:?}`"
1744                )));
1745            }
1746        }
1747        if !utils::has_unique_elements(&self.denied_port_ranges) {
1748            return Err(AddError::NotUnique(
1749                "Denied port ranges must be unique.".to_string(),
1750            ));
1751        }
1752        if utils::has_overlapping_ranges(&self.denied_port_ranges) {
1753            return Err(AddError::Overlaps(
1754                "Denied port ranges must not overlap.".to_string(),
1755            ));
1756        }
1757        for port_range in &self.denied_port_ranges {
1758            if self.allowed_port_ranges.contains(port_range) {
1759                return Err(AddError::BothAllowedAndDenied(format!(
1760                    "Port range `{port_range:?}`"
1761                )));
1762            }
1763        }
1764        if !utils::has_unique_elements(&self.allowed_ip_ranges) {
1765            return Err(AddError::NotUnique(
1766                "Allowed IP ranges must be unique.".to_string(),
1767            ));
1768        }
1769        if utils::has_overlapping_ranges(&self.allowed_ip_ranges) {
1770            return Err(AddError::Overlaps(
1771                "Allowed IP ranges must not overlap.".to_string(),
1772            ));
1773        }
1774        for ip_range in &self.allowed_ip_ranges {
1775            if self.denied_ip_ranges.contains(ip_range) {
1776                return Err(AddError::BothAllowedAndDenied(format!(
1777                    "IP range `{ip_range:?}`"
1778                )));
1779            }
1780
1781            if (!utils::ip::is_global_ip(ip_range.start())
1782                || !utils::ip::is_global_ip(ip_range.end()))
1783                && !self.allow_non_global_ip_ranges
1784            {
1785                return Err(AddError::NonGlobalIpRange(ip_range.clone()));
1786            }
1787        }
1788        if !utils::has_unique_elements(&self.denied_ip_ranges) {
1789            return Err(AddError::NotUnique(
1790                "Denied IP ranges must be unique.".to_string(),
1791            ));
1792        }
1793        if utils::has_overlapping_ranges(&self.denied_ip_ranges) {
1794            return Err(AddError::Overlaps(
1795                "Denied IP ranges must not overlap.".to_string(),
1796            ));
1797        }
1798        for ip_range in &self.denied_ip_ranges {
1799            if self.allowed_ip_ranges.contains(ip_range) {
1800                return Err(AddError::BothAllowedAndDenied(format!(
1801                    "IP range `{ip_range:?}`"
1802                )));
1803            }
1804
1805            if (!utils::ip::is_global_ip(ip_range.start())
1806                || !utils::ip::is_global_ip(ip_range.end()))
1807                && !self.allow_non_global_ip_ranges
1808            {
1809                return Err(AddError::NonGlobalIpRange(ip_range.clone()));
1810            }
1811        }
1812        if !utils::has_unique_elements(&self.static_dns_mapping) {
1813            return Err(AddError::NotUnique(
1814                "Static DNS mapping must be unique.".to_string(),
1815            ));
1816        }
1817        for (host, addr) in &self.static_dns_mapping {
1818            if !utils::authority::is_valid_host(host) {
1819                return Err(AddError::InvalidEntity(host.to_string()));
1820            }
1821            if self.trusted_static_dns_mapping.contains_key(host) {
1822                return Err(AddError::AlreadyPresentTrustedStaticDnsMapping(
1823                    host.to_string(),
1824                    *addr,
1825                ));
1826            }
1827        }
1828        if !utils::has_unique_elements(&self.trusted_static_dns_mapping) {
1829            return Err(AddError::NotUnique(
1830                "Trusted static DNS mapping must be unique.".to_string(),
1831            ));
1832        }
1833        for host in self.trusted_static_dns_mapping.keys() {
1834            if !utils::authority::is_valid_host(host) {
1835                return Err(AddError::InvalidEntity(host.to_string()));
1836            }
1837        }
1838        if !utils::has_unique_elements(&self.allowed_url_paths) {
1839            return Err(AddError::NotUnique(
1840                "Allowed URL paths must be unique.".to_string(),
1841            ));
1842        }
1843        for url_path in &self.allowed_url_paths {
1844            if self.denied_url_paths.contains(url_path)
1845                || self.denied_url_paths_router.at(url_path).is_ok()
1846            {
1847                return Err(AddError::BothAllowedAndDenied(format!(
1848                    "URL path `{url_path}`"
1849                )));
1850            } else if self.allowed_url_paths_router.at(url_path).is_err() {
1851                self.allowed_url_paths_router
1852                    .insert(url_path.clone(), ())
1853                    .map_err(|_| {
1854                        AddError::InvalidEntity(format!(
1855                            "Failed to insert allowed URL path `{url_path}`."
1856                        ))
1857                    })?;
1858            }
1859        }
1860        if !utils::has_unique_elements(&self.denied_url_paths) {
1861            return Err(AddError::NotUnique(
1862                "Denied URL paths must be unique.".to_string(),
1863            ));
1864        }
1865        for url_path in &self.denied_url_paths {
1866            if self.allowed_url_paths.contains(url_path)
1867                || self.allowed_url_paths_router.at(url_path).is_ok()
1868            {
1869                return Err(AddError::BothAllowedAndDenied(format!(
1870                    "URL path `{url_path}`"
1871                )));
1872            } else if self.denied_url_paths_router.at(url_path).is_err() {
1873                self.denied_url_paths_router
1874                    .insert(url_path.clone(), ())
1875                    .map_err(|_| {
1876                        AddError::InvalidEntity(format!(
1877                            "Failed to insert denied URL path `{url_path}`."
1878                        ))
1879                    })?;
1880            }
1881        }
1882        Ok(self.build_full(validate_fn))
1883    }
1884
1885    /// Builds the [`HttpAcl`], without a [`ValidateFn`], validating the
1886    /// configuration first. See [`Self::try_build_full`] for what is validated and
1887    /// when to prefer this over [`Self::build`].
1888    pub fn try_build(self) -> Result<HttpAcl, AddError> {
1889        self.try_build_full(None)
1890    }
1891}