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