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