Skip to main content

parse_net/
forwarded.rs

1//! `Forwarded` (RFC 7239) and the de facto `X-Forwarded-For`,
2//! `X-Forwarded-Proto`, and `X-Forwarded-Host`, with client-address
3//! resolution through trusted proxies.
4//!
5//! The leftmost `X-Forwarded-For` address is whatever the client typed; only
6//! addresses appended by proxies you trust mean anything. [`client_address`]
7//! ignores the header unless the connection's peer is trusted, walks the
8//! chain from the right, and stops at the first address it cannot trust. A
9//! malformed chain fails closed: use the peer address.
10//!
11//! # Examples
12//!
13//! ```
14//! use core::net::IpAddr;
15//! use parse_net::{self as pn, ClientAddress, ForwardedSource};
16//!
17//! let proxy: IpAddr = "10.0.0.2".parse()?;
18//! let inner: IpAddr = "10.0.0.1".parse()?;
19//! let trusted = |a: IpAddr| a == proxy || a == inner;
20//! let xff: [&[u8]; 1] = [b"203.0.113.9, 198.51.100.7, 10.0.0.1"];
21//! let ip = pn::client_address(proxy, ForwardedSource::XForwardedFor(&xff), &trusted, 16)?;
22//! assert_eq!(ip, ClientAddress::Forwarded { addr: "198.51.100.7".parse()?, hops: 2 });
23//!
24//! // The same header from an untrusted peer is ignored entirely.
25//! let stranger: IpAddr = "192.0.2.1".parse()?;
26//! assert_eq!(pn::client_address(stranger, ForwardedSource::XForwardedFor(&xff), &trusted, 16)?,
27//!            ClientAddress::Peer(stranger));
28//! # Ok::<(), Box<dyn std::error::Error>>(())
29//! ```
30
31use core::{
32    net::{IpAddr, Ipv4Addr, Ipv6Addr},
33    str::FromStr,
34};
35
36use crate::{
37    class,
38    error::{ErrorKind, ParseError, Result, WriteError, err},
39    host::{Host, HostPort, host_port},
40    limits::Limits,
41    list,
42    scan::control_kind,
43    text::{Word, parse_word, skip_ows, trim_ows},
44    write::{CountSink, Sink, reserve},
45};
46
47/// Decides whether an address belongs to a proxy you trust.
48///
49/// Implemented for every `Fn(IpAddr) -> bool`; a deployment's CIDR sets
50/// (net-io, bouncer-io configuration) implement it directly.
51///
52/// # Examples
53///
54/// ```
55/// use core::net::IpAddr;
56/// use parse_net::Trust;
57///
58/// struct Loopback;
59/// impl Trust for Loopback {
60///     fn is_trusted(&self, addr: IpAddr) -> bool { addr.is_loopback() }
61/// }
62/// assert!(Loopback.is_trusted("127.0.0.1".parse()?));
63/// let proxy: IpAddr = "10.0.0.2".parse()?;
64/// assert!(!(|a: IpAddr| a == proxy).is_trusted("192.0.2.1".parse()?));
65/// # Ok::<(), Box<dyn std::error::Error>>(())
66/// ```
67pub trait Trust {
68    /// Whether `addr` is a trusted proxy.
69    fn is_trusted(&self, addr: IpAddr) -> bool;
70}
71
72impl<F: Fn(IpAddr) -> bool> Trust for F {
73    fn is_trusted(&self, addr: IpAddr) -> bool {
74        self(addr)
75    }
76}
77
78/// Which header the deployment reads the chain from: every field line of
79/// it, in order. The two are never merged (merging is a parser
80/// differential: two proxies could disagree on the order).
81///
82/// # Examples
83///
84/// ```
85/// use core::net::IpAddr;
86/// use parse_net::{client_address, ClientAddress, ForwardedSource};
87///
88/// let peer: IpAddr = "10.0.0.1".parse()?;
89/// let lines: [&[u8]; 1] = [b"for=192.0.2.60"];
90/// let source = ForwardedSource::Forwarded(&lines);
91/// assert_eq!(client_address(peer, source, &|a: IpAddr| a == peer, 16)?,
92///            ClientAddress::Forwarded { addr: "192.0.2.60".parse()?, hops: 1 });
93/// # Ok::<(), Box<dyn std::error::Error>>(())
94/// ```
95#[derive(Clone, Copy, Debug)]
96pub enum ForwardedSource<'a> {
97    /// `Forwarded` field lines.
98    Forwarded(&'a [&'a [u8]]),
99    /// `X-Forwarded-For` field lines.
100    XForwardedFor(&'a [&'a [u8]]),
101}
102
103/// The client address a request is attributed to.
104///
105/// # Examples
106///
107/// ```
108/// use core::net::IpAddr;
109/// use parse_net::{client_address, ClientAddress, ForwardedSource};
110///
111/// let peer: IpAddr = "10.0.0.1".parse()?;
112/// let trusted = |a: IpAddr| a == peer;
113/// let hidden: [&[u8]; 1] = [b"for=unknown"];
114/// assert_eq!(client_address(peer, ForwardedSource::Forwarded(&hidden), &trusted, 16)?, ClientAddress::Unknown { hops: 1 });
115/// let none: [&[u8]; 0] = [];
116/// assert_eq!(client_address(peer, ForwardedSource::XForwardedFor(&none), &trusted, 16)?, ClientAddress::Peer(peer));
117/// # Ok::<(), Box<dyn std::error::Error>>(())
118/// ```
119#[non_exhaustive]
120#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
121pub enum ClientAddress {
122    /// The connection's peer: it is not a trusted proxy (its header was not
123    /// read), or it is a trusted proxy that forwarded no chain.
124    Peer(IpAddr),
125    /// An address from the chain, found after walking `hops` elements from
126    /// the right.
127    Forwarded {
128        /// The client address.
129        addr: IpAddr,
130        /// The elements walked, the returned one included.
131        hops: u8,
132    },
133    /// The walk reached `unknown`, an obfuscated identifier, or an element
134    /// without `for` after `hops` elements: nothing to its left can be
135    /// trusted.
136    Unknown {
137        /// The elements walked, the unknown one included.
138        hops: u8,
139    },
140}
141
142/// A node port: a number or an obfuscated identifier.
143///
144/// # Examples
145///
146/// ```
147/// use parse_net::{forwarded::{self, Node, NodePort}, Limits};
148///
149/// let lines: [&[u8]; 1] = [b"for=\"192.0.2.60:4711\";by=\"_proxy:_p1\""];
150/// let e = forwarded::parse(&lines, &Limits::DEFAULT)?.iter().next().expect("one element");
151/// assert!(matches!(e.for_, Some(Node::Addr(_, Some(NodePort::Number(4711))))));
152/// assert_eq!(e.by, Some(Node::Obfuscated(b"_proxy", Some(NodePort::Obfuscated(b"_p1")))));
153/// # Ok::<(), parse_net::ParseError>(())
154/// ```
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
156pub enum NodePort<'a> {
157    /// A port number.
158    Number(u16),
159    /// An obfuscated port (`_` and letters, digits, `.`, `_`, `-`).
160    Obfuscated(&'a [u8]),
161}
162
163/// A `for` or `by` node (RFC 7239 section 6).
164///
165/// # Examples
166///
167/// ```
168/// use parse_net::{forwarded::{self, Node}, Limits};
169///
170/// let lines: [&[u8]; 1] = [b"for=unknown, for=\"[2001:db8::1]\", for=_hidden"];
171/// let nodes: Vec<_> = forwarded::parse(&lines, &Limits::DEFAULT)?.iter().map(|e| e.for_).collect();
172/// assert_eq!(nodes[0], Some(Node::Unknown(None)));
173/// assert_eq!(nodes[1], Some(Node::Addr("2001:db8::1".parse()?, None)));
174/// assert_eq!(nodes[2], Some(Node::Obfuscated(b"_hidden", None)));
175/// # Ok::<(), Box<dyn std::error::Error>>(())
176/// ```
177#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
178pub enum Node<'a> {
179    /// An IP address.
180    Addr(IpAddr, Option<NodePort<'a>>),
181    /// `unknown`.
182    Unknown(Option<NodePort<'a>>),
183    /// An obfuscated identifier.
184    Obfuscated(&'a [u8], Option<NodePort<'a>>),
185}
186
187/// The protocol a request arrived over at the proxy.
188///
189/// # Examples
190///
191/// ```
192/// use parse_net::forwarded::{parse_x_forwarded_proto, Proto};
193///
194/// assert_eq!(parse_x_forwarded_proto(b"http")?, Proto::Http);
195/// assert_eq!(parse_x_forwarded_proto(b"https, HTTPS")?, Proto::Https);
196/// # Ok::<(), parse_net::ParseError>(())
197/// ```
198#[non_exhaustive]
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
200pub enum Proto {
201    /// `http`.
202    Http,
203    /// `https`.
204    Https,
205}
206
207/// One `Forwarded` element.
208///
209/// # Examples
210///
211/// ```
212/// use parse_net::{forwarded::{self, Proto}, Host, Limits};
213///
214/// let lines: [&[u8]; 1] = [b"for=192.0.2.60;proto=https;host=example.com"];
215/// let e = forwarded::parse(&lines, &Limits::DEFAULT)?.iter().next().expect("one element");
216/// assert_eq!(e.proto, Some(Proto::Https));
217/// assert_eq!(e.host.map(|h| h.host), Some(Host::Name(b"example.com")));
218/// assert!(e.by.is_none());
219/// # Ok::<(), parse_net::ParseError>(())
220/// ```
221#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
222pub struct Element<'a> {
223    /// `for=`: the client of this hop.
224    pub for_: Option<Node<'a>>,
225    /// `by=`: the proxy's receiving interface.
226    pub by: Option<Node<'a>>,
227    /// `host=`: the `Host` the proxy received.
228    pub host: Option<HostPort<'a>>,
229    /// `proto=`.
230    pub proto: Option<Proto>,
231}
232
233/// `"_" 1*( ALPHA / DIGIT / "." / "_" / "-" )`.
234fn is_obfuscated(s: &[u8]) -> bool {
235    matches!(s, [b'_', rest @ ..] if !rest.is_empty()
236        && rest.iter().all(|&b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-')))
237}
238
239fn node_port(s: &[u8]) -> Option<NodePort<'_>> {
240    if is_obfuscated(s) {
241        return Some(NodePort::Obfuscated(s));
242    }
243    if s.is_empty() || s.len() > 5 || !s.iter().all(u8::is_ascii_digit) {
244        return None;
245    }
246    let n = s.iter().fold(0u32, |n, &d| n * 10 + u32::from(d - b'0'));
247    u16::try_from(n).ok().map(NodePort::Number)
248}
249
250/// An IP literal: dotted-decimal IPv4 (no leading zeros) or IPv6.
251fn ip(text: &[u8], v6: bool) -> Option<IpAddr> {
252    if v6 {
253        let t = core::str::from_utf8(text).ok()?;
254        Ipv6Addr::from_str(t).ok().map(IpAddr::V6)
255    } else {
256        ipv4(text).map(IpAddr::V4)
257    }
258}
259
260/// Dotted-decimal IPv4 with `core`'s rules: four parts of one to three
261/// digits, each at most 255, no leading zeros (so `01.2.3.4` cannot pass).
262/// One pass over the bytes, with no UTF-8 check.
263fn ipv4(text: &[u8]) -> Option<Ipv4Addr> {
264    let mut octets = [0u8; 4];
265    let mut parts = text.split(|&b| b == b'.');
266    for o in &mut octets {
267        let part = parts.next()?;
268        if part.is_empty() || part.len() > 3 || (part.len() > 1 && part.first() == Some(&b'0')) {
269            return None;
270        }
271        let mut n: u16 = 0;
272        for &b in part {
273            if !b.is_ascii_digit() {
274                return None;
275            }
276            n = n * 10 + u16::from(b - b'0');
277        }
278        *o = u8::try_from(n).ok()?;
279    }
280    if parts.next().is_some() {
281        return None;
282    }
283    Some(Ipv4Addr::from(octets))
284}
285
286/// Parses a node value (already unquoted).
287fn node(v: &[u8]) -> Result<Node<'_>> {
288    let (name, rest) = if v.first() == Some(&b'[') {
289        match v.iter().position(|&b| b == b']') {
290            Some(close) => (
291                v.get(..=close).unwrap_or(&[]),
292                v.get(close + 1..).unwrap_or(&[]),
293            ),
294            None => return err(ErrorKind::InvalidAddress, 0),
295        }
296    } else {
297        match v.iter().position(|&b| b == b':') {
298            Some(c) => (v.get(..c).unwrap_or(&[]), v.get(c..).unwrap_or(&[])),
299            None => (v, &[][..]),
300        }
301    };
302    let port = match rest.split_first() {
303        None => None,
304        Some((b':', p)) => match node_port(p) {
305            Some(p) => Some(p),
306            None => return err(ErrorKind::InvalidPort, name.len() + 1),
307        },
308        Some(_) => return err(ErrorKind::InvalidAddress, name.len()),
309    };
310    if name == b"unknown" {
311        return Ok(Node::Unknown(port));
312    }
313    if is_obfuscated(name) {
314        return Ok(Node::Obfuscated(name, port));
315    }
316    let addr = match name {
317        [b'[', inner @ .., b']'] => ip(inner, true),
318        _ => ip(name, false),
319    };
320    match addr {
321        Some(a) => Ok(Node::Addr(a, port)),
322        None => err(ErrorKind::InvalidAddress, 0),
323    }
324}
325
326/// Parses one `forwarded-element`: `[ pair ] *( ";" [ pair ] )`, OWS around
327/// `;` accepted, names case-insensitive, a known name at most once.
328fn element(e: &[u8]) -> Result<Element<'_>> {
329    let mut out = Element {
330        for_: None,
331        by: None,
332        host: None,
333        proto: None,
334    };
335    let mut seen = [false; 4];
336    let mut i = 0usize;
337    loop {
338        i += skip_ows(e.get(i..).unwrap_or(&[]));
339        match e.get(i) {
340            None => return Ok(out),
341            Some(b';') => {
342                i += 1;
343                continue;
344            }
345            Some(_) => {}
346        }
347        let name_len = e
348            .get(i..)
349            .unwrap_or(&[])
350            .iter()
351            .take_while(|&&b| class::is_tchar(b))
352            .count();
353        if name_len == 0 {
354            return Err(refused(e, i, ErrorKind::NotToken));
355        }
356        let name_at = i;
357        let name = e.get(i..i + name_len).unwrap_or(&[]);
358        i += name_len;
359        if e.get(i) != Some(&b'=') {
360            return Err(refused(e, i, ErrorKind::MissingValue));
361        }
362        i += 1;
363        let (w, used) = parse_word(e.get(i..).unwrap_or(&[])).map_err(|x| match x.kind() {
364            ErrorKind::Empty => refused(e, i, ErrorKind::InvalidByte),
365            _ => x.shifted(i),
366        })?;
367        // Node, host, and proto values never need quoted pairs; refusing them
368        // keeps every view borrowed from the input.
369        let Some(v) = w.as_plain() else {
370            return err(ErrorKind::InvalidQuotedPair, i);
371        };
372        let value_at = i + usize::from(matches!(w, Word::Quoted(_)));
373        i += used;
374        let slot = [&b"for"[..], b"by", b"host", b"proto"]
375            .iter()
376            .position(|k| name.eq_ignore_ascii_case(k));
377        if let Some(s) = slot {
378            if seen.get(s).copied().unwrap_or(false) {
379                return err(ErrorKind::DuplicateParameter, name_at);
380            }
381            if let Some(flag) = seen.get_mut(s) {
382                *flag = true;
383            }
384        }
385        match slot {
386            Some(0) => out.for_ = Some(node(v).map_err(|x| x.shifted(value_at))?),
387            Some(1) => out.by = Some(node(v).map_err(|x| x.shifted(value_at))?),
388            Some(2) => out.host = Some(host_port(v).map_err(|x| x.shifted(value_at))?),
389            Some(3) => {
390                out.proto = Some(if v.eq_ignore_ascii_case(b"http") {
391                    Proto::Http
392                } else if v.eq_ignore_ascii_case(b"https") {
393                    Proto::Https
394                } else {
395                    return err(ErrorKind::Malformed, value_at);
396                });
397            }
398            _ => {}
399        }
400        i += skip_ows(e.get(i..).unwrap_or(&[]));
401        match e.get(i) {
402            None => return Ok(out),
403            Some(b';') => i += 1,
404            Some(_) => return Err(refused(e, i, ErrorKind::ExpectedSeparator)),
405        }
406    }
407}
408
409fn refused(e: &[u8], at: usize, kind: ErrorKind) -> ParseError {
410    match e.get(at) {
411        Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
412        _ => ParseError::new(kind, at),
413    }
414}
415
416/// Validated `Forwarded` field lines.
417///
418/// # Examples
419///
420/// ```
421/// use parse_net::{forwarded, Limits};
422///
423/// let lines: [&[u8]; 2] = [b"for=192.0.2.60", b"for=198.51.100.7;proto=https"];
424/// let elems = forwarded::parse(&lines, &Limits::DEFAULT)?;
425/// assert_eq!(elems.len(), 2);
426/// assert_eq!(elems.iter().filter(|e| e.proto.is_some()).count(), 1);
427/// # Ok::<(), parse_net::ParseError>(())
428/// ```
429#[derive(Clone, Copy, Debug)]
430pub struct ForwardedElements<'a> {
431    lines: &'a [&'a [u8]],
432    count: usize,
433}
434
435impl<'a> ForwardedElements<'a> {
436    /// The elements, left to right across every line.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use parse_net::{forwarded::{self, Proto}, Limits};
442    ///
443    /// let lines: [&[u8]; 2] = [b"for=192.0.2.60", b"for=198.51.100.7;proto=https"];
444    /// let protos: Vec<_> = forwarded::parse(&lines, &Limits::DEFAULT)?.iter().map(|e| e.proto).collect();
445    /// assert_eq!(protos, [None, Some(Proto::Https)]);
446    /// # Ok::<(), parse_net::ParseError>(())
447    /// ```
448    pub fn iter(&self) -> impl Iterator<Item = Element<'a>> + use<'a> {
449        let lines = self.lines;
450        lines
451            .iter()
452            .filter_map(|line| list::parse(line, &Limits::DEFAULT).ok())
453            .flat_map(|l| l.into_iter())
454            .filter_map(|e| element(e).ok())
455    }
456
457    /// The number of elements.
458    ///
459    /// # Examples
460    ///
461    /// ```
462    /// use parse_net::{forwarded, Limits};
463    ///
464    /// let lines: [&[u8]; 2] = [b"for=192.0.2.60, for=192.0.2.61", b"for=unknown"];
465    /// assert_eq!(forwarded::parse(&lines, &Limits::DEFAULT)?.len(), 3);
466    /// # Ok::<(), parse_net::ParseError>(())
467    /// ```
468    #[must_use]
469    pub const fn len(&self) -> usize {
470        self.count
471    }
472
473    /// Whether there are no elements.
474    ///
475    /// # Examples
476    ///
477    /// ```
478    /// use parse_net::{forwarded, Limits};
479    ///
480    /// let none: [&[u8]; 0] = [];
481    /// assert!(forwarded::parse(&none, &Limits::DEFAULT)?.is_empty());
482    /// assert!(!forwarded::parse(&[&b"for=unknown"[..]], &Limits::DEFAULT)?.is_empty());
483    /// # Ok::<(), parse_net::ParseError>(())
484    /// ```
485    #[must_use]
486    pub const fn is_empty(&self) -> bool {
487        self.count == 0
488    }
489}
490
491/// Parses every `Forwarded` field line (RFC 7239 section 4).
492///
493/// IPv6 must be quoted and bracketed (`for="[2001:db8::1]:4711"`); an
494/// unquoted IPv6 is refused because `:` and `[` are not token characters.
495/// Values containing quoted pairs are refused. `proto` must be `http` or
496/// `https`.
497///
498/// # Errors
499///
500/// [`ErrorKind::DuplicateParameter`] (a known name twice in one element);
501/// [`ErrorKind::InvalidAddress`], [`ErrorKind::InvalidPort`],
502/// [`ErrorKind::InvalidHost`]; [`ErrorKind::Malformed`] for another `proto`;
503/// [`ErrorKind::TooManyHops`] beyond [`Limits::max_hops`]; the list and
504/// token errors, with [`ParseError::line`] set.
505///
506/// # Examples
507///
508/// ```
509/// use parse_net::{forwarded, Limits};
510///
511/// let lines: [&[u8]; 1] = [b"for=192.0.2.60;proto=http;by=203.0.113.43, for=\"[2001:db8:cafe::17]:4711\""];
512/// let elems: Vec<_> = forwarded::parse(&lines, &Limits::DEFAULT)?.iter().collect();
513/// assert_eq!(elems.len(), 2);
514/// assert!(forwarded::parse(&[&b"for=2001:db8::1"[..]], &Limits::DEFAULT).is_err());
515/// # Ok::<(), parse_net::ParseError>(())
516/// ```
517pub fn parse<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<ForwardedElements<'a>> {
518    let mut count = 0usize;
519    for (n, line) in lines.iter().enumerate() {
520        let l = list::parse(line, limits).map_err(|e| e.on_line(n))?;
521        for e in l.iter() {
522            let at = (e.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
523            element(e)
524                .map(|_| ())
525                .map_err(|x| x.shifted(at).on_line(n))?;
526            count += 1;
527            if count > usize::from(limits.hops) {
528                return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
529            }
530        }
531    }
532    Ok(ForwardedElements { lines, count })
533}
534
535/// One `X-Forwarded-For` member: an IPv4 or IPv6 address, IPv6 optionally
536/// bracketed, with an optional port after IPv4 or bracketed IPv6.
537fn xff_member(m: &[u8]) -> Option<(IpAddr, Option<u16>)> {
538    let port = |p: &[u8]| -> Option<Option<u16>> {
539        match node_port(p)? {
540            NodePort::Number(n) => Some(Some(n)),
541            NodePort::Obfuscated(_) => None,
542        }
543    };
544    if let [b'[', ..] = m {
545        let close = m.iter().position(|&b| b == b']')?;
546        let a = ip(m.get(1..close)?, true)?;
547        return match m.get(close + 1..)? {
548            [] => Some((a, None)),
549            [b':', p @ ..] => Some((a, port(p)?)),
550            _ => None,
551        };
552    }
553    // By shape: no colon is IPv4; exactly one is IPv4 with a port; more is
554    // IPv6 (which always has at least two).
555    let mut colons = m.iter().enumerate().filter(|&(_, &b)| b == b':');
556    match (colons.next(), colons.next()) {
557        (None, _) => Some((ip(m, false)?, None)),
558        (Some((c, _)), None) => Some((ip(m.get(..c)?, false)?, port(m.get(c + 1..)?)?)),
559        (Some(_), Some(_)) => Some((ip(m, true)?, None)),
560    }
561}
562
563/// The members of one `X-Forwarded-For` line, refusing empty members.
564fn xff_line(line: &[u8]) -> impl Iterator<Item = &[u8]> {
565    line.split(|&b| b == b',').map(trim_ows)
566}
567
568/// A validated `X-Forwarded-For` chain.
569///
570/// # Examples
571///
572/// ```
573/// use parse_net::{forwarded::parse_x_forwarded_for, Limits};
574///
575/// let lines: [&[u8]; 1] = [b"203.0.113.9, 10.0.0.1:8080"];
576/// let chain = parse_x_forwarded_for(&lines, &Limits::DEFAULT)?;
577/// assert_eq!(chain.len(), 2);
578/// assert_eq!(chain.iter().last(), Some(("10.0.0.1".parse()?, Some(8080))));
579/// # Ok::<(), Box<dyn std::error::Error>>(())
580/// ```
581#[derive(Clone, Copy, Debug)]
582pub struct XffChain<'a> {
583    lines: &'a [&'a [u8]],
584    count: usize,
585}
586
587impl<'a> XffChain<'a> {
588    /// The addresses and ports, left to right across every line.
589    ///
590    /// # Examples
591    ///
592    /// ```
593    /// use parse_net::{forwarded::parse_x_forwarded_for, Limits};
594    ///
595    /// let lines: [&[u8]; 1] = [b"203.0.113.9, [2001:db8::1]:443"];
596    /// let mut it = parse_x_forwarded_for(&lines, &Limits::DEFAULT)?.iter();
597    /// assert_eq!(it.next(), Some(("203.0.113.9".parse()?, None)));
598    /// assert_eq!(it.next(), Some(("2001:db8::1".parse()?, Some(443))));
599    /// assert_eq!(it.next(), None);
600    /// # Ok::<(), Box<dyn std::error::Error>>(())
601    /// ```
602    pub fn iter(&self) -> impl Iterator<Item = (IpAddr, Option<u16>)> + use<'a> {
603        self.lines
604            .iter()
605            .flat_map(|line| xff_line(line))
606            .filter_map(xff_member)
607    }
608
609    /// The number of addresses.
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// use parse_net::{forwarded::parse_x_forwarded_for, Limits};
615    ///
616    /// let lines: [&[u8]; 2] = [b"203.0.113.9, 198.51.100.7", b"10.0.0.1"];
617    /// assert_eq!(parse_x_forwarded_for(&lines, &Limits::DEFAULT)?.len(), 3);
618    /// # Ok::<(), parse_net::ParseError>(())
619    /// ```
620    #[must_use]
621    pub const fn len(&self) -> usize {
622        self.count
623    }
624
625    /// Whether there are no addresses.
626    ///
627    /// # Examples
628    ///
629    /// ```
630    /// use parse_net::{forwarded::parse_x_forwarded_for, Limits};
631    ///
632    /// let none: [&[u8]; 0] = [];
633    /// assert!(parse_x_forwarded_for(&none, &Limits::DEFAULT)?.is_empty());
634    /// assert!(!parse_x_forwarded_for(&[&b"10.0.0.1"[..]], &Limits::DEFAULT)?.is_empty());
635    /// # Ok::<(), parse_net::ParseError>(())
636    /// ```
637    #[must_use]
638    pub const fn is_empty(&self) -> bool {
639        self.count == 0
640    }
641}
642
643/// Parses every `X-Forwarded-For` field line.
644///
645/// There is no RFC: the de facto grammar accepted is comma-separated IPv4
646/// and IPv6 literals with OWS, brackets optional around IPv6, and an
647/// optional `:port` after IPv4 or bracketed IPv6. Anything else, an empty
648/// member included, is refused (D-016).
649///
650/// # Errors
651///
652/// [`ErrorKind::Malformed`]; [`ErrorKind::TooManyHops`] beyond
653/// [`Limits::max_hops`]; each with [`ParseError::line`] set.
654///
655/// # Examples
656///
657/// ```
658/// use parse_net::{forwarded::parse_x_forwarded_for, Limits};
659///
660/// let lines: [&[u8]; 2] = [b"203.0.113.9:4711, [2001:db8::1]:443", b"2001:db8::2"];
661/// assert_eq!(parse_x_forwarded_for(&lines, &Limits::DEFAULT)?.len(), 3);
662/// assert!(parse_x_forwarded_for(&[&b"1.2.3.4, , 5.6.7.8"[..]], &Limits::DEFAULT).is_err());
663/// # Ok::<(), parse_net::ParseError>(())
664/// ```
665pub fn parse_x_forwarded_for<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<XffChain<'a>> {
666    let mut count = 0usize;
667    for (n, line) in lines.iter().enumerate() {
668        crate::scan::check_field_value(line).map_err(|e| e.on_line(n))?;
669        for m in xff_line(line) {
670            let at = (m.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
671            if xff_member(m).is_none() {
672                return Err(ParseError::new(ErrorKind::Malformed, at).on_line(n));
673            }
674            count += 1;
675            if count > usize::from(limits.hops) {
676                return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
677            }
678        }
679    }
680    Ok(XffChain { lines, count })
681}
682
683/// Parses `X-Forwarded-Proto`: `http` or `https`, case-insensitive; a
684/// comma list only when every member is the same.
685///
686/// # Errors
687///
688/// [`ErrorKind::Malformed`].
689///
690/// # Examples
691///
692/// ```
693/// use parse_net::forwarded::{parse_x_forwarded_proto, Proto};
694///
695/// assert_eq!(parse_x_forwarded_proto(b"HTTPS")?, Proto::Https);
696/// assert!(parse_x_forwarded_proto(b"https, http").is_err());
697/// # Ok::<(), parse_net::ParseError>(())
698/// ```
699pub fn parse_x_forwarded_proto(value: &[u8]) -> Result<Proto> {
700    let mut found: Option<Proto> = None;
701    for m in value.split(|&b| b == b',') {
702        let at = (m.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
703        let t = trim_ows(m);
704        let p = if t.eq_ignore_ascii_case(b"http") {
705            Proto::Http
706        } else if t.eq_ignore_ascii_case(b"https") {
707            Proto::Https
708        } else {
709            return err(ErrorKind::Malformed, at);
710        };
711        if found.is_some_and(|f| f != p) {
712            return err(ErrorKind::Malformed, at);
713        }
714        found = Some(p);
715    }
716    found.ok_or(ParseError::new(ErrorKind::Malformed, 0))
717}
718
719/// Parses `X-Forwarded-Host`: one `Host` value; a list is refused.
720///
721/// # Errors
722///
723/// [`ErrorKind::Malformed`] for a list; the `Host` errors.
724///
725/// # Examples
726///
727/// ```
728/// use parse_net::forwarded::parse_x_forwarded_host;
729///
730/// assert_eq!(parse_x_forwarded_host(b"example.com:8443")?.port, Some(8443));
731/// assert!(parse_x_forwarded_host(b"a.example, b.example").is_err());
732/// # Ok::<(), parse_net::ParseError>(())
733/// ```
734pub fn parse_x_forwarded_host(value: &[u8]) -> Result<HostPort<'_>> {
735    if let Some(at) = crate::scan::find_byte(b',', value) {
736        return err(ErrorKind::Malformed, at);
737    }
738    crate::host::parse_host_port(value)
739}
740
741/// What a walk step found.
742enum Step {
743    Addr(IpAddr),
744    Unknown,
745}
746
747/// Walks a chain of `count` steps from the right; `step(i)` gives the
748/// `i`-th step counted from the left.
749fn walk(
750    count: usize,
751    trust: &impl Trust,
752    mut step: impl FnMut(usize) -> Step,
753) -> Option<ClientAddress> {
754    let mut leftmost: Option<IpAddr> = None;
755    for (walked, i) in (0..count).rev().enumerate() {
756        let hops = u8::try_from(walked + 1).unwrap_or(u8::MAX);
757        match step(i) {
758            Step::Addr(a) if trust.is_trusted(a) => leftmost = Some(a),
759            Step::Addr(addr) => return Some(ClientAddress::Forwarded { addr, hops }),
760            Step::Unknown => return Some(ClientAddress::Unknown { hops }),
761        }
762    }
763    leftmost.map(|addr| ClientAddress::Forwarded {
764        addr,
765        hops: u8::try_from(count).unwrap_or(u8::MAX),
766    })
767}
768
769/// The `i`-th element (from the left) of validated lines, by index: the
770/// chain is short (at most `max_hops`), so a walk by index stays cheap and
771/// allocates nothing.
772fn nth_member<'a, I: Iterator<Item = &'a [u8]>>(
773    lines: &[&'a [u8]],
774    i: usize,
775    members: impl Fn(&'a [u8]) -> I,
776) -> Option<&'a [u8]> {
777    lines.iter().flat_map(|line| members(line)).nth(i)
778}
779
780/// Resolves the client address of a request that arrived from `peer`.
781///
782/// The algorithm: an untrusted `peer` gives [`ClientAddress::Peer`] and the
783/// header is never read. Otherwise the whole chain is parsed; a malformed
784/// chain anywhere is an error (fail closed: use the peer); more than
785/// `max_hops` elements is an error. The chain is walked from the right:
786/// trusted addresses are skipped; the first untrusted address is the client;
787/// `unknown`, an obfuscated identifier, or a `Forwarded` element without
788/// `for` stops the walk with [`ClientAddress::Unknown`]. When every element
789/// is trusted, the leftmost is the client. A trusted peer that sent no chain
790/// is itself the client.
791///
792/// # Errors
793///
794/// [`ErrorKind::Malformed`] (the error's offset and line point at the
795/// problem); [`ErrorKind::TooManyHops`].
796///
797/// # Examples
798///
799/// ```
800/// use core::net::IpAddr;
801/// use parse_net::{client_address, ClientAddress, ForwardedSource};
802///
803/// let proxy: IpAddr = "203.0.113.43".parse()?;
804/// let trusted = |a: IpAddr| a == proxy;
805/// let lines: [&[u8]; 1] = [b"for=192.0.2.60;proto=http;by=203.0.113.43"];
806/// assert_eq!(client_address(proxy, ForwardedSource::Forwarded(&lines), &trusted, 16)?,
807///            ClientAddress::Forwarded { addr: "192.0.2.60".parse()?, hops: 1 });
808/// # Ok::<(), Box<dyn std::error::Error>>(())
809/// ```
810pub fn client_address(
811    peer: IpAddr,
812    source: ForwardedSource<'_>,
813    trust: &impl Trust,
814    max_hops: u8,
815) -> Result<ClientAddress> {
816    if !trust.is_trusted(peer) {
817        return Ok(ClientAddress::Peer(peer));
818    }
819    let limits = Limits::DEFAULT.max_hops(max_hops);
820    let found = match source {
821        ForwardedSource::XForwardedFor(lines) => {
822            let chain = parse_x_forwarded_for(lines, &limits).map_err(as_malformed)?;
823            walk(chain.len(), trust, |i| {
824                match nth_member(lines, i, xff_line).and_then(xff_member) {
825                    Some((a, _)) => Step::Addr(a),
826                    None => Step::Unknown,
827                }
828            })
829        }
830        ForwardedSource::Forwarded(lines) => {
831            let elements = parse(lines, &limits).map_err(as_malformed)?;
832            walk(elements.len(), trust, |i| {
833                let e = nth_member(lines, i, |line| {
834                    list::parse(line, &Limits::DEFAULT)
835                        .ok()
836                        .into_iter()
837                        .flat_map(|l| l.into_iter())
838                })
839                .and_then(|e| element(e).ok());
840                match e.and_then(|e| e.for_) {
841                    Some(Node::Addr(a, _)) => Step::Addr(a),
842                    _ => Step::Unknown,
843                }
844            })
845        }
846    };
847    Ok(found.unwrap_or(ClientAddress::Peer(peer)))
848}
849
850/// Every parse error of a chain becomes `Malformed` (or stays
851/// `TooManyHops`), keeping its offset and line.
852fn as_malformed(e: ParseError) -> ParseError {
853    if e.kind() == ErrorKind::TooManyHops {
854        e
855    } else {
856        ParseError::new(ErrorKind::Malformed, e.offset()).on_line(e.line())
857    }
858}
859
860/// [`client_address`] and whether the other header kind was present too.
861///
862/// # Examples
863///
864/// ```
865/// use core::net::IpAddr;
866/// use parse_net::{forwarded::client_address_detailed, ClientAddress, ForwardedSource};
867///
868/// let peer: IpAddr = "10.0.0.1".parse()?;
869/// let xff: [&[u8]; 1] = [b"198.51.100.7"];
870/// let r = client_address_detailed(peer, ForwardedSource::XForwardedFor(&xff), &[], &|a: IpAddr| a == peer, 16)?;
871/// assert_eq!(r.address, ClientAddress::Forwarded { addr: "198.51.100.7".parse()?, hops: 1 });
872/// assert!(!r.other_present);
873/// # Ok::<(), Box<dyn std::error::Error>>(())
874/// ```
875#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
876pub struct Resolved {
877    /// The client address.
878    pub address: ClientAddress,
879    /// Whether the header kind not configured had a non-empty line: worth
880    /// logging as a likely spoofing attempt (the two are never merged).
881    pub other_present: bool,
882}
883
884/// As [`client_address`], and reports whether `other_lines` (the field
885/// lines of the header kind not configured) were present.
886///
887/// # Errors
888///
889/// As [`client_address`].
890///
891/// # Examples
892///
893/// ```
894/// use core::net::IpAddr;
895/// use parse_net::{forwarded::client_address_detailed, ForwardedSource};
896///
897/// let peer: IpAddr = "10.0.0.1".parse()?;
898/// let xff: [&[u8]; 1] = [b"198.51.100.7"];
899/// let fwd: [&[u8]; 1] = [b"for=192.0.2.1"];
900/// let r = client_address_detailed(peer, ForwardedSource::XForwardedFor(&xff), &fwd, &|a: IpAddr| a == peer, 16)?;
901/// assert!(r.other_present);
902/// # Ok::<(), Box<dyn std::error::Error>>(())
903/// ```
904pub fn client_address_detailed(
905    peer: IpAddr,
906    source: ForwardedSource<'_>,
907    other_lines: &[&[u8]],
908    trust: &impl Trust,
909    max_hops: u8,
910) -> Result<Resolved> {
911    Ok(Resolved {
912        address: client_address(peer, source, trust, max_hops)?,
913        other_present: other_lines.iter().any(|l| !trim_ows(l).is_empty()),
914    })
915}
916
917/// Writes a node's text: the address (IPv6 in brackets), `unknown`, or the
918/// obfuscated name, then `:` and the port.
919fn put_node(n: &Node<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
920    let port = match *n {
921        Node::Addr(ip, port) => {
922            let host = match ip {
923                IpAddr::V4(a) => Host::V4(a),
924                IpAddr::V6(a) => Host::V6(a),
925            };
926            HostPort {
927                host,
928                port: None,
929                empty_port: false,
930            }
931            .write_to(out)?;
932            port
933        }
934        Node::Unknown(port) => {
935            out.put(b"unknown")?;
936            port
937        }
938        Node::Obfuscated(name, port) => {
939            if !is_obfuscated(name) {
940                return Err(WriteError::NotToken);
941            }
942            out.put(name)?;
943            port
944        }
945    };
946    match port {
947        None => {}
948        Some(NodePort::Number(p)) => {
949            out.put(b":")?;
950            crate::write::write_decimal_u64(u64::from(p), out)?;
951        }
952        Some(NodePort::Obfuscated(p)) => {
953            if !is_obfuscated(p) {
954                return Err(WriteError::NotToken);
955            }
956            out.put(b":")?;
957            out.put(p)?;
958        }
959    }
960    Ok(())
961}
962
963/// Writes `name=` and the node, quoted when it holds an IPv6 address or a
964/// port (`:` is not a token character, RFC 7239 section 6).
965fn put_pair(
966    name: &[u8],
967    n: &Node<'_>,
968    first: &mut bool,
969    out: &mut impl Sink,
970) -> core::result::Result<(), WriteError> {
971    let quote = matches!(n, Node::Addr(IpAddr::V6(_), _))
972        || matches!(
973            n,
974            Node::Addr(_, Some(_)) | Node::Unknown(Some(_)) | Node::Obfuscated(_, Some(_))
975        );
976    if !*first {
977        out.put(b";")?;
978    }
979    *first = false;
980    out.put(name)?;
981    out.put(b"=")?;
982    if quote {
983        out.put(b"\"")?;
984    }
985    put_node(n, out)?;
986    if quote {
987        out.put(b"\"")?;
988    }
989    Ok(())
990}
991
992fn put_element(e: &Element<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
993    let mut first = true;
994    if let Some(n) = &e.for_ {
995        put_pair(b"for", n, &mut first, out)?;
996    }
997    if let Some(p) = e.proto {
998        if !first {
999            out.put(b";")?;
1000        }
1001        first = false;
1002        out.put(match p {
1003            Proto::Http => b"proto=http",
1004            Proto::Https => b"proto=https",
1005        })?;
1006    }
1007    if let Some(n) = &e.by {
1008        put_pair(b"by", n, &mut first, out)?;
1009    }
1010    if let Some(h) = &e.host {
1011        if !first {
1012            out.put(b";")?;
1013        }
1014        out.put(b"host=")?;
1015        // A port or an IPv6 literal needs quotes; host bytes never need
1016        // escapes.
1017        let quote = h.port.is_some() || matches!(h.host, Host::V6(_) | Host::Future(_));
1018        if quote {
1019            out.put(b"\"")?;
1020        }
1021        h.write_to(out)?;
1022        if quote {
1023            out.put(b"\"")?;
1024        }
1025    }
1026    Ok(())
1027}
1028
1029/// Writes one `Forwarded` element: `for`, `proto`, `by`, and `host`, in that
1030/// order, each when present, with IPv6 addresses and nodes with ports
1031/// quoted. For proxies that append their hop.
1032///
1033/// # Errors
1034///
1035/// [`WriteError::NotToken`] for an obfuscated name or port that is not `_`
1036/// followed by `ALPHA / DIGIT / "." / "_" / "-"`; [`WriteError::Full`]
1037/// (nothing written when the sink reports its room).
1038///
1039/// # Examples
1040///
1041/// ```
1042/// use core::net::IpAddr;
1043/// use parse_net::{forwarded::{self, Element, Node, NodePort, Proto}, SliceSink};
1044///
1045/// let e = Element {
1046///     for_: Some(Node::Addr("192.0.2.60".parse::<IpAddr>()?, None)),
1047///     by: Some(Node::Addr("2001:db8::17".parse::<IpAddr>()?, Some(NodePort::Number(4711)))),
1048///     host: None,
1049///     proto: Some(Proto::Https),
1050/// };
1051/// let mut buf = [0u8; 96];
1052/// let mut out = SliceSink::new(&mut buf);
1053/// forwarded::write_element(&e, &mut out)?;
1054/// assert_eq!(out.written(), b"for=192.0.2.60;proto=https;by=\"[2001:db8::17]:4711\"");
1055/// # Ok::<(), Box<dyn std::error::Error>>(())
1056/// ```
1057pub fn write_element(e: &Element<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
1058    let mut count = CountSink::new();
1059    put_element(e, &mut count)?;
1060    reserve(out, count.count())?;
1061    put_element(e, out)
1062}
1063
1064#[cfg(test)]
1065mod tests {
1066    use super::*;
1067
1068    #[test]
1069    fn test_write_element_round_trip() {
1070        let v6: IpAddr = "2001:db8:cafe::17".parse().unwrap();
1071        let cases = [
1072            Element {
1073                for_: Some(Node::Addr(ip4("192.0.2.43"), None)),
1074                by: None,
1075                host: None,
1076                proto: None,
1077            },
1078            Element {
1079                for_: Some(Node::Addr(v6, Some(NodePort::Number(4711)))),
1080                by: Some(Node::Obfuscated(
1081                    b"_hidden",
1082                    Some(NodePort::Obfuscated(b"_p1")),
1083                )),
1084                host: Some(crate::parse_host_port(b"example.com:8080").unwrap()),
1085                proto: Some(Proto::Http),
1086            },
1087            Element {
1088                for_: Some(Node::Unknown(Some(NodePort::Number(80)))),
1089                by: Some(Node::Addr(v6, None)),
1090                host: Some(crate::parse_host_port(b"[::1]").unwrap()),
1091                proto: Some(Proto::Https),
1092            },
1093        ];
1094        for e in cases {
1095            let mut buf = [0u8; 128];
1096            let mut out = crate::SliceSink::new(&mut buf);
1097            write_element(&e, &mut out).unwrap();
1098            let lines = [out.written()];
1099            let parsed = parse(&lines, &Limits::DEFAULT).unwrap();
1100            assert_eq!(
1101                parsed.iter().next(),
1102                Some(e),
1103                "{:?}",
1104                core::str::from_utf8(out.written())
1105            );
1106        }
1107        let bad = Element {
1108            for_: Some(Node::Obfuscated(b"hidden", None)),
1109            by: None,
1110            host: None,
1111            proto: None,
1112        };
1113        let mut buf = [0u8; 8];
1114        let mut out = crate::SliceSink::new(&mut buf);
1115        assert_eq!(write_element(&bad, &mut out), Err(WriteError::NotToken));
1116        let big = Element {
1117            for_: Some(Node::Addr(v6, None)),
1118            by: None,
1119            host: None,
1120            proto: None,
1121        };
1122        assert_eq!(write_element(&big, &mut out), Err(WriteError::Full));
1123        assert!(out.is_empty());
1124    }
1125
1126    fn ip4(s: &str) -> IpAddr {
1127        s.parse().unwrap()
1128    }
1129
1130    #[test]
1131    fn test_forwarded_rfc_examples() {
1132        let l = Limits::DEFAULT;
1133        let one = |v: &'static [u8]| -> Element<'static> {
1134            let lines: &'static [&'static [u8]] = std::boxed::Box::leak(std::boxed::Box::new([v]));
1135            parse(lines, &l).unwrap().iter().next().unwrap()
1136        };
1137        let e = one(b"for=\"_gazonk\"");
1138        assert_eq!(e.for_, Some(Node::Obfuscated(b"_gazonk", None)));
1139        let e = one(b"For=\"[2001:db8:cafe::17]:4711\"");
1140        assert!(matches!(
1141            e.for_,
1142            Some(Node::Addr(IpAddr::V6(_), Some(NodePort::Number(4711))))
1143        ));
1144        let e = one(b"for=192.0.2.60;proto=http;by=203.0.113.43");
1145        assert_eq!(
1146            (e.proto, e.by),
1147            (
1148                Some(Proto::Http),
1149                Some(Node::Addr(ip4("203.0.113.43"), None))
1150            )
1151        );
1152        assert_eq!(one(b"for=unknown").for_, Some(Node::Unknown(None)));
1153        assert_eq!(
1154            one(b"for=\"10.0.0.1:_p1\"").for_,
1155            Some(Node::Addr(
1156                ip4("10.0.0.1"),
1157                Some(NodePort::Obfuscated(b"_p1"))
1158            ))
1159        );
1160        assert_eq!(
1161            one(b"host=example.com; proto=https").proto,
1162            Some(Proto::Https)
1163        );
1164        assert_eq!(
1165            one(b";;for=1.2.3.4;").for_,
1166            Some(Node::Addr(ip4("1.2.3.4"), None))
1167        );
1168    }
1169
1170    #[test]
1171    fn test_forwarded_refusals() {
1172        let l = Limits::DEFAULT;
1173        let k = |v: &[u8]| parse(&[v], &l).unwrap_err().kind();
1174        assert_ne!(k(b"for=2001:db8::1"), ErrorKind::TooManyHops);
1175        assert_eq!(k(b"for=1.2.3.4;for=5.6.7.8"), ErrorKind::DuplicateParameter);
1176        assert_eq!(k(b"for=1.2.3"), ErrorKind::InvalidAddress);
1177        assert_eq!(k(b"for=01.2.3.4"), ErrorKind::InvalidAddress);
1178        assert_eq!(k(b"for=\"[::1]:99999\""), ErrorKind::InvalidPort);
1179        assert_eq!(k(b"proto=ftp"), ErrorKind::Malformed);
1180        assert_eq!(k(b"for"), ErrorKind::MissingValue);
1181        assert_eq!(k(b"for=\"a\\\"b\""), ErrorKind::InvalidQuotedPair);
1182        assert_eq!(k(b"for=1.2.3.4 x"), ErrorKind::ExpectedSeparator);
1183        let seventeen = [b"for=1.1.1.1,".repeat(17)].concat();
1184        assert_eq!(
1185            parse(&[&seventeen[..]], &l).unwrap_err().kind(),
1186            ErrorKind::TooManyHops
1187        );
1188        let sixteen = [b"for=1.1.1.1,".repeat(16)].concat();
1189        assert_eq!(parse(&[&sixteen[..]], &l).unwrap().len(), 16);
1190    }
1191
1192    #[test]
1193    fn test_xff_shapes() {
1194        let l = Limits::DEFAULT;
1195        let ok = |v: &[u8]| parse_x_forwarded_for(&[v], &l).map(|c| c.len());
1196        assert_eq!(ok(b"1.2.3.4"), Ok(1));
1197        assert_eq!(ok(b" 1.2.3.4 , 5.6.7.8:80 "), Ok(2));
1198        assert_eq!(ok(b"2001:db8::1, [2001:db8::2]:443, [::1]"), Ok(3));
1199        for bad in [
1200            &b"1.2.3.4, , 5.6.7.8"[..],
1201            b"",
1202            b"garbage",
1203            b"1.2.3.4:x",
1204            b"2001:db8::1:80:",
1205            b"[::1",
1206            b"1.2.3.4\r",
1207        ] {
1208            assert!(
1209                ok(bad).is_err(),
1210                "{}",
1211                std::string::String::from_utf8_lossy(bad)
1212            );
1213        }
1214    }
1215
1216    #[test]
1217    fn test_xff_untrusted_peer_ignored() {
1218        let t = |a: IpAddr| a == ip4("10.0.0.1");
1219        let lines: [&[u8]; 1] = [b"garbage that would be refused"];
1220        assert_eq!(
1221            client_address(
1222                ip4("192.0.2.9"),
1223                ForwardedSource::XForwardedFor(&lines),
1224                &t,
1225                16
1226            ),
1227            Ok(ClientAddress::Peer(ip4("192.0.2.9")))
1228        );
1229    }
1230
1231    #[test]
1232    fn test_xff_leftmost_spoof_ignored() {
1233        let t = |a: IpAddr| a == ip4("10.0.0.1") || a == ip4("10.0.0.2");
1234        let lines: [&[u8]; 1] = [b"1.1.1.1, 203.0.113.7, 10.0.0.2"];
1235        assert_eq!(
1236            client_address(
1237                ip4("10.0.0.1"),
1238                ForwardedSource::XForwardedFor(&lines),
1239                &t,
1240                16
1241            ),
1242            Ok(ClientAddress::Forwarded {
1243                addr: ip4("203.0.113.7"),
1244                hops: 2
1245            })
1246        );
1247    }
1248
1249    #[test]
1250    fn test_xff_malformed_fails_closed() {
1251        let t = |_: IpAddr| true;
1252        let lines: [&[u8]; 2] = [b"1.1.1.1", b"10.0.0.2, nope"];
1253        let e = client_address(
1254            ip4("10.0.0.1"),
1255            ForwardedSource::XForwardedFor(&lines),
1256            &t,
1257            16,
1258        )
1259        .unwrap_err();
1260        assert_eq!((e.kind(), e.line()), (ErrorKind::Malformed, 1));
1261        let too_many: [&[u8]; 1] = [b"1.1.1.1, 1.1.1.2, 1.1.1.3"];
1262        assert_eq!(
1263            client_address(
1264                ip4("10.0.0.1"),
1265                ForwardedSource::XForwardedFor(&too_many),
1266                &t,
1267                2
1268            )
1269            .unwrap_err()
1270            .kind(),
1271            ErrorKind::TooManyHops
1272        );
1273    }
1274
1275    #[test]
1276    fn test_client_address_walk_cases() {
1277        let t = |a: IpAddr| a.is_loopback() || a == ip4("10.0.0.1");
1278        let peer = ip4("10.0.0.1");
1279        let x = |v: &'static [u8]| {
1280            client_address(
1281                peer,
1282                ForwardedSource::XForwardedFor(std::boxed::Box::leak(std::boxed::Box::new([v]))),
1283                &t,
1284                16,
1285            )
1286        };
1287        assert_eq!(
1288            x(b"127.0.0.1, 10.0.0.1"),
1289            Ok(ClientAddress::Forwarded {
1290                addr: ip4("127.0.0.1"),
1291                hops: 2
1292            })
1293        );
1294        let none: [&[u8]; 0] = [];
1295        assert_eq!(
1296            client_address(peer, ForwardedSource::XForwardedFor(&none), &t, 16),
1297            Ok(ClientAddress::Peer(peer))
1298        );
1299        let f = |v: &'static [u8]| {
1300            client_address(
1301                peer,
1302                ForwardedSource::Forwarded(std::boxed::Box::leak(std::boxed::Box::new([v]))),
1303                &t,
1304                16,
1305            )
1306        };
1307        assert_eq!(
1308            f(b"for=192.0.2.1, for=unknown, for=127.0.0.1"),
1309            Ok(ClientAddress::Unknown { hops: 2 })
1310        );
1311        assert_eq!(
1312            f(b"for=192.0.2.1, for=_hidden"),
1313            Ok(ClientAddress::Unknown { hops: 1 })
1314        );
1315        assert_eq!(
1316            f(b"for=192.0.2.1, proto=https"),
1317            Ok(ClientAddress::Unknown { hops: 1 })
1318        );
1319        assert_eq!(
1320            f(b"for=\"[2001:db8::7]\""),
1321            Ok(ClientAddress::Forwarded {
1322                addr: "2001:db8::7".parse().unwrap(),
1323                hops: 1
1324            })
1325        );
1326        assert_eq!(
1327            f(b"for=2001:db8::7").unwrap_err().kind(),
1328            ErrorKind::Malformed
1329        );
1330    }
1331
1332    #[test]
1333    fn test_both_forwarded_headers_reported() {
1334        let t = |_: IpAddr| true;
1335        let xff: [&[u8]; 1] = [b"192.0.2.1"];
1336        let fwd: [&[u8]; 1] = [b"for=198.51.100.1"];
1337        let r = client_address_detailed(
1338            ip4("10.0.0.1"),
1339            ForwardedSource::XForwardedFor(&xff),
1340            &fwd,
1341            &t,
1342            16,
1343        )
1344        .unwrap();
1345        assert!(r.other_present);
1346        assert_eq!(
1347            r.address,
1348            ClientAddress::Forwarded {
1349                addr: ip4("192.0.2.1"),
1350                hops: 1
1351            }
1352        );
1353        let empty: [&[u8]; 1] = [b"  "];
1354        assert!(
1355            !client_address_detailed(
1356                ip4("10.0.0.1"),
1357                ForwardedSource::XForwardedFor(&xff),
1358                &empty,
1359                &t,
1360                16
1361            )
1362            .unwrap()
1363            .other_present
1364        );
1365    }
1366
1367    #[test]
1368    fn test_x_forwarded_proto_and_host() {
1369        assert_eq!(parse_x_forwarded_proto(b"http, HTTP"), Ok(Proto::Http));
1370        assert!(parse_x_forwarded_proto(b"").is_err());
1371        assert!(parse_x_forwarded_proto(b"wss").is_err());
1372        assert!(parse_x_forwarded_host(b"[::1]:8080").is_ok());
1373        assert_eq!(
1374            parse_x_forwarded_host(b"a,b").unwrap_err().kind(),
1375            ErrorKind::Malformed
1376        );
1377    }
1378}