1use core::{
32 net::{IpAddr, Ipv4Addr, Ipv6Addr},
33 str::FromStr,
34};
35
36use crate::{
37 class,
38 error::{ErrorKind, ParseError, Result, err},
39 host::{HostPort, host_port},
40 limits::Limits,
41 list,
42 scan::control_kind,
43 text::{Word, parse_word, skip_ows, trim_ows},
44};
45
46pub trait Trust {
51 fn is_trusted(&self, addr: IpAddr) -> bool;
53}
54
55impl<F: Fn(IpAddr) -> bool> Trust for F {
56 fn is_trusted(&self, addr: IpAddr) -> bool {
57 self(addr)
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
65pub enum ForwardedSource<'a> {
66 Forwarded(&'a [&'a [u8]]),
68 XForwardedFor(&'a [&'a [u8]]),
70}
71
72#[non_exhaustive]
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub enum ClientAddress {
76 Peer(IpAddr),
79 Forwarded {
82 addr: IpAddr,
84 hops: u8,
86 },
87 Unknown {
91 hops: u8,
93 },
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
98pub enum NodePort<'a> {
99 Number(u16),
101 Obfuscated(&'a [u8]),
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107pub enum Node<'a> {
108 Addr(IpAddr, Option<NodePort<'a>>),
110 Unknown(Option<NodePort<'a>>),
112 Obfuscated(&'a [u8], Option<NodePort<'a>>),
114}
115
116#[non_exhaustive]
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
119pub enum Proto {
120 Http,
122 Https,
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
128pub struct Element<'a> {
129 pub for_: Option<Node<'a>>,
131 pub by: Option<Node<'a>>,
133 pub host: Option<HostPort<'a>>,
135 pub proto: Option<Proto>,
137}
138
139fn is_obfuscated(s: &[u8]) -> bool {
141 matches!(s, [b'_', rest @ ..] if !rest.is_empty()
142 && rest.iter().all(|&b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-')))
143}
144
145fn node_port(s: &[u8]) -> Option<NodePort<'_>> {
146 if is_obfuscated(s) {
147 return Some(NodePort::Obfuscated(s));
148 }
149 if s.is_empty() || s.len() > 5 || !s.iter().all(u8::is_ascii_digit) {
150 return None;
151 }
152 let n = s.iter().fold(0u32, |n, &d| n * 10 + u32::from(d - b'0'));
153 u16::try_from(n).ok().map(NodePort::Number)
154}
155
156fn ip(text: &[u8], v6: bool) -> Option<IpAddr> {
158 let t = core::str::from_utf8(text).ok()?;
159 if v6 {
160 Ipv6Addr::from_str(t).ok().map(IpAddr::V6)
161 } else {
162 Ipv4Addr::from_str(t).ok().map(IpAddr::V4)
164 }
165}
166
167fn node(v: &[u8]) -> Result<Node<'_>> {
169 let (name, rest) = if v.first() == Some(&b'[') {
170 match v.iter().position(|&b| b == b']') {
171 Some(close) => (
172 v.get(..=close).unwrap_or(&[]),
173 v.get(close + 1..).unwrap_or(&[]),
174 ),
175 None => return err(ErrorKind::InvalidAddress, 0),
176 }
177 } else {
178 match v.iter().position(|&b| b == b':') {
179 Some(c) => (v.get(..c).unwrap_or(&[]), v.get(c..).unwrap_or(&[])),
180 None => (v, &[][..]),
181 }
182 };
183 let port = match rest.split_first() {
184 None => None,
185 Some((b':', p)) => match node_port(p) {
186 Some(p) => Some(p),
187 None => return err(ErrorKind::InvalidPort, name.len() + 1),
188 },
189 Some(_) => return err(ErrorKind::InvalidAddress, name.len()),
190 };
191 if name == b"unknown" {
192 return Ok(Node::Unknown(port));
193 }
194 if is_obfuscated(name) {
195 return Ok(Node::Obfuscated(name, port));
196 }
197 let addr = match name {
198 [b'[', inner @ .., b']'] => ip(inner, true),
199 _ => ip(name, false),
200 };
201 match addr {
202 Some(a) => Ok(Node::Addr(a, port)),
203 None => err(ErrorKind::InvalidAddress, 0),
204 }
205}
206
207fn element(e: &[u8]) -> Result<Element<'_>> {
210 let mut out = Element {
211 for_: None,
212 by: None,
213 host: None,
214 proto: None,
215 };
216 let mut seen = [false; 4];
217 let mut i = 0usize;
218 loop {
219 i += skip_ows(e.get(i..).unwrap_or(&[]));
220 match e.get(i) {
221 None => return Ok(out),
222 Some(b';') => {
223 i += 1;
224 continue;
225 }
226 Some(_) => {}
227 }
228 let name_len = e
229 .get(i..)
230 .unwrap_or(&[])
231 .iter()
232 .take_while(|&&b| class::is_tchar(b))
233 .count();
234 if name_len == 0 {
235 return Err(refused(e, i, ErrorKind::NotToken));
236 }
237 let name_at = i;
238 let name = e.get(i..i + name_len).unwrap_or(&[]);
239 i += name_len;
240 if e.get(i) != Some(&b'=') {
241 return Err(refused(e, i, ErrorKind::MissingValue));
242 }
243 i += 1;
244 let (w, used) = parse_word(e.get(i..).unwrap_or(&[])).map_err(|x| match x.kind() {
245 ErrorKind::Empty => refused(e, i, ErrorKind::InvalidByte),
246 _ => x.shifted(i),
247 })?;
248 let Some(v) = w.as_plain() else {
251 return err(ErrorKind::InvalidQuotedPair, i);
252 };
253 let value_at = i + usize::from(matches!(w, Word::Quoted(_)));
254 i += used;
255 let slot = [&b"for"[..], b"by", b"host", b"proto"]
256 .iter()
257 .position(|k| name.eq_ignore_ascii_case(k));
258 if let Some(s) = slot {
259 if seen.get(s).copied().unwrap_or(false) {
260 return err(ErrorKind::DuplicateParameter, name_at);
261 }
262 if let Some(flag) = seen.get_mut(s) {
263 *flag = true;
264 }
265 }
266 match slot {
267 Some(0) => out.for_ = Some(node(v).map_err(|x| x.shifted(value_at))?),
268 Some(1) => out.by = Some(node(v).map_err(|x| x.shifted(value_at))?),
269 Some(2) => out.host = Some(host_port(v).map_err(|x| x.shifted(value_at))?),
270 Some(3) => {
271 out.proto = Some(if v.eq_ignore_ascii_case(b"http") {
272 Proto::Http
273 } else if v.eq_ignore_ascii_case(b"https") {
274 Proto::Https
275 } else {
276 return err(ErrorKind::Malformed, value_at);
277 });
278 }
279 _ => {}
280 }
281 i += skip_ows(e.get(i..).unwrap_or(&[]));
282 match e.get(i) {
283 None => return Ok(out),
284 Some(b';') => i += 1,
285 Some(_) => return Err(refused(e, i, ErrorKind::ExpectedSeparator)),
286 }
287 }
288}
289
290fn refused(e: &[u8], at: usize, kind: ErrorKind) -> ParseError {
291 match e.get(at) {
292 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
293 _ => ParseError::new(kind, at),
294 }
295}
296
297#[derive(Clone, Copy, Debug)]
299pub struct ForwardedElements<'a> {
300 lines: &'a [&'a [u8]],
301 count: usize,
302}
303
304impl<'a> ForwardedElements<'a> {
305 pub fn iter(&self) -> impl Iterator<Item = Element<'a>> + use<'a> {
307 let lines = self.lines;
308 lines
309 .iter()
310 .filter_map(|line| list::parse(line, &Limits::DEFAULT).ok())
311 .flat_map(|l| l.into_iter())
312 .filter_map(|e| element(e).ok())
313 }
314
315 #[must_use]
317 pub const fn len(&self) -> usize {
318 self.count
319 }
320
321 #[must_use]
323 pub const fn is_empty(&self) -> bool {
324 self.count == 0
325 }
326}
327
328pub fn parse<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<ForwardedElements<'a>> {
355 let mut count = 0usize;
356 for (n, line) in lines.iter().enumerate() {
357 let l = list::parse(line, limits).map_err(|e| e.on_line(n))?;
358 for e in l.iter() {
359 let at = (e.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
360 element(e)
361 .map(|_| ())
362 .map_err(|x| x.shifted(at).on_line(n))?;
363 count += 1;
364 if count > usize::from(limits.hops) {
365 return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
366 }
367 }
368 }
369 Ok(ForwardedElements { lines, count })
370}
371
372fn xff_member(m: &[u8]) -> Option<(IpAddr, Option<u16>)> {
375 let port = |p: &[u8]| -> Option<Option<u16>> {
376 match node_port(p)? {
377 NodePort::Number(n) => Some(Some(n)),
378 NodePort::Obfuscated(_) => None,
379 }
380 };
381 if let [b'[', ..] = m {
382 let close = m.iter().position(|&b| b == b']')?;
383 let a = ip(m.get(1..close)?, true)?;
384 return match m.get(close + 1..)? {
385 [] => Some((a, None)),
386 [b':', p @ ..] => Some((a, port(p)?)),
387 _ => None,
388 };
389 }
390 if let Some(a) = ip(m, true) {
391 return Some((a, None));
392 }
393 match m.iter().position(|&b| b == b':') {
394 Some(c) => Some((ip(m.get(..c)?, false)?, port(m.get(c + 1..)?)?)),
395 None => Some((ip(m, false)?, None)),
396 }
397}
398
399fn xff_line(line: &[u8]) -> impl Iterator<Item = &[u8]> {
401 line.split(|&b| b == b',').map(trim_ows)
402}
403
404#[derive(Clone, Copy, Debug)]
406pub struct XffChain<'a> {
407 lines: &'a [&'a [u8]],
408 count: usize,
409}
410
411impl<'a> XffChain<'a> {
412 pub fn iter(&self) -> impl Iterator<Item = (IpAddr, Option<u16>)> + use<'a> {
414 self.lines
415 .iter()
416 .flat_map(|line| xff_line(line))
417 .filter_map(xff_member)
418 }
419
420 #[must_use]
422 pub const fn len(&self) -> usize {
423 self.count
424 }
425
426 #[must_use]
428 pub const fn is_empty(&self) -> bool {
429 self.count == 0
430 }
431}
432
433pub fn parse_x_forwarded_for<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<XffChain<'a>> {
456 let mut count = 0usize;
457 for (n, line) in lines.iter().enumerate() {
458 crate::scan::check_field_value(line).map_err(|e| e.on_line(n))?;
459 for m in xff_line(line) {
460 let at = (m.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
461 if xff_member(m).is_none() {
462 return Err(ParseError::new(ErrorKind::Malformed, at).on_line(n));
463 }
464 count += 1;
465 if count > usize::from(limits.hops) {
466 return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
467 }
468 }
469 }
470 Ok(XffChain { lines, count })
471}
472
473pub fn parse_x_forwarded_proto(value: &[u8]) -> Result<Proto> {
490 let mut found: Option<Proto> = None;
491 for m in value.split(|&b| b == b',') {
492 let at = (m.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
493 let t = trim_ows(m);
494 let p = if t.eq_ignore_ascii_case(b"http") {
495 Proto::Http
496 } else if t.eq_ignore_ascii_case(b"https") {
497 Proto::Https
498 } else {
499 return err(ErrorKind::Malformed, at);
500 };
501 if found.is_some_and(|f| f != p) {
502 return err(ErrorKind::Malformed, at);
503 }
504 found = Some(p);
505 }
506 found.ok_or(ParseError::new(ErrorKind::Malformed, 0))
507}
508
509pub fn parse_x_forwarded_host(value: &[u8]) -> Result<HostPort<'_>> {
525 if let Some(at) = crate::scan::find_byte(b',', value) {
526 return err(ErrorKind::Malformed, at);
527 }
528 crate::host::parse_host_port(value)
529}
530
531enum Step {
533 Addr(IpAddr),
534 Unknown,
535}
536
537fn walk(
540 count: usize,
541 trust: &impl Trust,
542 mut step: impl FnMut(usize) -> Step,
543) -> Option<ClientAddress> {
544 let mut leftmost: Option<IpAddr> = None;
545 for (walked, i) in (0..count).rev().enumerate() {
546 let hops = u8::try_from(walked + 1).unwrap_or(u8::MAX);
547 match step(i) {
548 Step::Addr(a) if trust.is_trusted(a) => leftmost = Some(a),
549 Step::Addr(addr) => return Some(ClientAddress::Forwarded { addr, hops }),
550 Step::Unknown => return Some(ClientAddress::Unknown { hops }),
551 }
552 }
553 leftmost.map(|addr| ClientAddress::Forwarded {
554 addr,
555 hops: u8::try_from(count).unwrap_or(u8::MAX),
556 })
557}
558
559fn nth_member<'a, I: Iterator<Item = &'a [u8]>>(
563 lines: &[&'a [u8]],
564 i: usize,
565 members: impl Fn(&'a [u8]) -> I,
566) -> Option<&'a [u8]> {
567 lines.iter().flat_map(|line| members(line)).nth(i)
568}
569
570pub fn client_address(
601 peer: IpAddr,
602 source: ForwardedSource<'_>,
603 trust: &impl Trust,
604 max_hops: u8,
605) -> Result<ClientAddress> {
606 if !trust.is_trusted(peer) {
607 return Ok(ClientAddress::Peer(peer));
608 }
609 let limits = Limits::DEFAULT.max_hops(max_hops);
610 let found = match source {
611 ForwardedSource::XForwardedFor(lines) => {
612 let chain = parse_x_forwarded_for(lines, &limits).map_err(as_malformed)?;
613 walk(chain.len(), trust, |i| {
614 match nth_member(lines, i, xff_line).and_then(xff_member) {
615 Some((a, _)) => Step::Addr(a),
616 None => Step::Unknown,
617 }
618 })
619 }
620 ForwardedSource::Forwarded(lines) => {
621 let elements = parse(lines, &limits).map_err(as_malformed)?;
622 walk(elements.len(), trust, |i| {
623 let e = nth_member(lines, i, |line| {
624 list::parse(line, &Limits::DEFAULT)
625 .ok()
626 .into_iter()
627 .flat_map(|l| l.into_iter())
628 })
629 .and_then(|e| element(e).ok());
630 match e.and_then(|e| e.for_) {
631 Some(Node::Addr(a, _)) => Step::Addr(a),
632 _ => Step::Unknown,
633 }
634 })
635 }
636 };
637 Ok(found.unwrap_or(ClientAddress::Peer(peer)))
638}
639
640fn as_malformed(e: ParseError) -> ParseError {
643 if e.kind() == ErrorKind::TooManyHops {
644 e
645 } else {
646 ParseError::new(ErrorKind::Malformed, e.offset()).on_line(e.line())
647 }
648}
649
650#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
652pub struct Resolved {
653 pub address: ClientAddress,
655 pub other_present: bool,
658}
659
660pub fn client_address_detailed(
681 peer: IpAddr,
682 source: ForwardedSource<'_>,
683 other_lines: &[&[u8]],
684 trust: &impl Trust,
685 max_hops: u8,
686) -> Result<Resolved> {
687 Ok(Resolved {
688 address: client_address(peer, source, trust, max_hops)?,
689 other_present: other_lines.iter().any(|l| !trim_ows(l).is_empty()),
690 })
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 fn ip4(s: &str) -> IpAddr {
698 s.parse().unwrap()
699 }
700
701 #[test]
702 fn test_forwarded_rfc_examples() {
703 let l = Limits::DEFAULT;
704 let one = |v: &'static [u8]| -> Element<'static> {
705 let lines: &'static [&'static [u8]] = std::boxed::Box::leak(std::boxed::Box::new([v]));
706 parse(lines, &l).unwrap().iter().next().unwrap()
707 };
708 let e = one(b"for=\"_gazonk\"");
709 assert_eq!(e.for_, Some(Node::Obfuscated(b"_gazonk", None)));
710 let e = one(b"For=\"[2001:db8:cafe::17]:4711\"");
711 assert!(matches!(
712 e.for_,
713 Some(Node::Addr(IpAddr::V6(_), Some(NodePort::Number(4711))))
714 ));
715 let e = one(b"for=192.0.2.60;proto=http;by=203.0.113.43");
716 assert_eq!(
717 (e.proto, e.by),
718 (
719 Some(Proto::Http),
720 Some(Node::Addr(ip4("203.0.113.43"), None))
721 )
722 );
723 assert_eq!(one(b"for=unknown").for_, Some(Node::Unknown(None)));
724 assert_eq!(
725 one(b"for=\"10.0.0.1:_p1\"").for_,
726 Some(Node::Addr(
727 ip4("10.0.0.1"),
728 Some(NodePort::Obfuscated(b"_p1"))
729 ))
730 );
731 assert_eq!(
732 one(b"host=example.com; proto=https").proto,
733 Some(Proto::Https)
734 );
735 assert_eq!(
736 one(b";;for=1.2.3.4;").for_,
737 Some(Node::Addr(ip4("1.2.3.4"), None))
738 );
739 }
740
741 #[test]
742 fn test_forwarded_refusals() {
743 let l = Limits::DEFAULT;
744 let k = |v: &[u8]| parse(&[v], &l).unwrap_err().kind();
745 assert_ne!(k(b"for=2001:db8::1"), ErrorKind::TooManyHops);
746 assert_eq!(k(b"for=1.2.3.4;for=5.6.7.8"), ErrorKind::DuplicateParameter);
747 assert_eq!(k(b"for=1.2.3"), ErrorKind::InvalidAddress);
748 assert_eq!(k(b"for=01.2.3.4"), ErrorKind::InvalidAddress);
749 assert_eq!(k(b"for=\"[::1]:99999\""), ErrorKind::InvalidPort);
750 assert_eq!(k(b"proto=ftp"), ErrorKind::Malformed);
751 assert_eq!(k(b"for"), ErrorKind::MissingValue);
752 assert_eq!(k(b"for=\"a\\\"b\""), ErrorKind::InvalidQuotedPair);
753 assert_eq!(k(b"for=1.2.3.4 x"), ErrorKind::ExpectedSeparator);
754 let seventeen = [b"for=1.1.1.1,".repeat(17)].concat();
755 assert_eq!(
756 parse(&[&seventeen[..]], &l).unwrap_err().kind(),
757 ErrorKind::TooManyHops
758 );
759 let sixteen = [b"for=1.1.1.1,".repeat(16)].concat();
760 assert_eq!(parse(&[&sixteen[..]], &l).unwrap().len(), 16);
761 }
762
763 #[test]
764 fn test_xff_shapes() {
765 let l = Limits::DEFAULT;
766 let ok = |v: &[u8]| parse_x_forwarded_for(&[v], &l).map(|c| c.len());
767 assert_eq!(ok(b"1.2.3.4"), Ok(1));
768 assert_eq!(ok(b" 1.2.3.4 , 5.6.7.8:80 "), Ok(2));
769 assert_eq!(ok(b"2001:db8::1, [2001:db8::2]:443, [::1]"), Ok(3));
770 for bad in [
771 &b"1.2.3.4, , 5.6.7.8"[..],
772 b"",
773 b"garbage",
774 b"1.2.3.4:x",
775 b"2001:db8::1:80:",
776 b"[::1",
777 b"1.2.3.4\r",
778 ] {
779 assert!(
780 ok(bad).is_err(),
781 "{}",
782 std::string::String::from_utf8_lossy(bad)
783 );
784 }
785 }
786
787 #[test]
788 fn test_xff_untrusted_peer_ignored() {
789 let t = |a: IpAddr| a == ip4("10.0.0.1");
790 let lines: [&[u8]; 1] = [b"garbage that would be refused"];
791 assert_eq!(
792 client_address(
793 ip4("192.0.2.9"),
794 ForwardedSource::XForwardedFor(&lines),
795 &t,
796 16
797 ),
798 Ok(ClientAddress::Peer(ip4("192.0.2.9")))
799 );
800 }
801
802 #[test]
803 fn test_xff_leftmost_spoof_ignored() {
804 let t = |a: IpAddr| a == ip4("10.0.0.1") || a == ip4("10.0.0.2");
805 let lines: [&[u8]; 1] = [b"1.1.1.1, 203.0.113.7, 10.0.0.2"];
806 assert_eq!(
807 client_address(
808 ip4("10.0.0.1"),
809 ForwardedSource::XForwardedFor(&lines),
810 &t,
811 16
812 ),
813 Ok(ClientAddress::Forwarded {
814 addr: ip4("203.0.113.7"),
815 hops: 2
816 })
817 );
818 }
819
820 #[test]
821 fn test_xff_malformed_fails_closed() {
822 let t = |_: IpAddr| true;
823 let lines: [&[u8]; 2] = [b"1.1.1.1", b"10.0.0.2, nope"];
824 let e = client_address(
825 ip4("10.0.0.1"),
826 ForwardedSource::XForwardedFor(&lines),
827 &t,
828 16,
829 )
830 .unwrap_err();
831 assert_eq!((e.kind(), e.line()), (ErrorKind::Malformed, 1));
832 let too_many: [&[u8]; 1] = [b"1.1.1.1, 1.1.1.2, 1.1.1.3"];
833 assert_eq!(
834 client_address(
835 ip4("10.0.0.1"),
836 ForwardedSource::XForwardedFor(&too_many),
837 &t,
838 2
839 )
840 .unwrap_err()
841 .kind(),
842 ErrorKind::TooManyHops
843 );
844 }
845
846 #[test]
847 fn test_client_address_walk_cases() {
848 let t = |a: IpAddr| a.is_loopback() || a == ip4("10.0.0.1");
849 let peer = ip4("10.0.0.1");
850 let x = |v: &'static [u8]| {
851 client_address(
852 peer,
853 ForwardedSource::XForwardedFor(std::boxed::Box::leak(std::boxed::Box::new([v]))),
854 &t,
855 16,
856 )
857 };
858 assert_eq!(
859 x(b"127.0.0.1, 10.0.0.1"),
860 Ok(ClientAddress::Forwarded {
861 addr: ip4("127.0.0.1"),
862 hops: 2
863 })
864 );
865 let none: [&[u8]; 0] = [];
866 assert_eq!(
867 client_address(peer, ForwardedSource::XForwardedFor(&none), &t, 16),
868 Ok(ClientAddress::Peer(peer))
869 );
870 let f = |v: &'static [u8]| {
871 client_address(
872 peer,
873 ForwardedSource::Forwarded(std::boxed::Box::leak(std::boxed::Box::new([v]))),
874 &t,
875 16,
876 )
877 };
878 assert_eq!(
879 f(b"for=192.0.2.1, for=unknown, for=127.0.0.1"),
880 Ok(ClientAddress::Unknown { hops: 2 })
881 );
882 assert_eq!(
883 f(b"for=192.0.2.1, for=_hidden"),
884 Ok(ClientAddress::Unknown { hops: 1 })
885 );
886 assert_eq!(
887 f(b"for=192.0.2.1, proto=https"),
888 Ok(ClientAddress::Unknown { hops: 1 })
889 );
890 assert_eq!(
891 f(b"for=\"[2001:db8::7]\""),
892 Ok(ClientAddress::Forwarded {
893 addr: "2001:db8::7".parse().unwrap(),
894 hops: 1
895 })
896 );
897 assert_eq!(
898 f(b"for=2001:db8::7").unwrap_err().kind(),
899 ErrorKind::Malformed
900 );
901 }
902
903 #[test]
904 fn test_both_forwarded_headers_reported() {
905 let t = |_: IpAddr| true;
906 let xff: [&[u8]; 1] = [b"192.0.2.1"];
907 let fwd: [&[u8]; 1] = [b"for=198.51.100.1"];
908 let r = client_address_detailed(
909 ip4("10.0.0.1"),
910 ForwardedSource::XForwardedFor(&xff),
911 &fwd,
912 &t,
913 16,
914 )
915 .unwrap();
916 assert!(r.other_present);
917 assert_eq!(
918 r.address,
919 ClientAddress::Forwarded {
920 addr: ip4("192.0.2.1"),
921 hops: 1
922 }
923 );
924 let empty: [&[u8]; 1] = [b" "];
925 assert!(
926 !client_address_detailed(
927 ip4("10.0.0.1"),
928 ForwardedSource::XForwardedFor(&xff),
929 &empty,
930 &t,
931 16
932 )
933 .unwrap()
934 .other_present
935 );
936 }
937
938 #[test]
939 fn test_x_forwarded_proto_and_host() {
940 assert_eq!(parse_x_forwarded_proto(b"http, HTTP"), Ok(Proto::Http));
941 assert!(parse_x_forwarded_proto(b"").is_err());
942 assert!(parse_x_forwarded_proto(b"wss").is_err());
943 assert!(parse_x_forwarded_host(b"[::1]:8080").is_ok());
944 assert_eq!(
945 parse_x_forwarded_host(b"a,b").unwrap_err().kind(),
946 ErrorKind::Malformed
947 );
948 }
949}