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