1use core::{
32 net::{IpAddr, Ipv4Addr, Ipv6Addr},
33 str::FromStr,
34};
35
36use crate::{
37 class,
38 error::{ErrorKind, ParseError, Result, WriteError, err},
39 host::{Host, HostPort, host_port},
40 limits::Limits,
41 list,
42 param::{MAX_CHECKED_PARAMS, Names},
43 scan::control_kind,
44 text::{Word, is_token, parse_word, skip_ows, trim_ows},
45 write::{CountSink, Sink, reserve},
46};
47
48pub trait Trust {
69 fn is_trusted(&self, addr: IpAddr) -> bool;
71}
72
73impl<F: Fn(IpAddr) -> bool> Trust for F {
74 fn is_trusted(&self, addr: IpAddr) -> bool {
75 self(addr)
76 }
77}
78
79#[derive(Clone, Copy, Debug)]
97pub enum ForwardedSource<'a> {
98 Forwarded(&'a [&'a [u8]]),
100 XForwardedFor(&'a [&'a [u8]]),
102}
103
104#[non_exhaustive]
121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
122pub enum ClientAddress {
123 Peer(IpAddr),
126 Forwarded {
129 addr: IpAddr,
131 hops: u8,
133 },
134 Unknown {
138 hops: u8,
140 },
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub enum NodePort<'a> {
158 Number(u16),
160 Obfuscated(&'a [u8]),
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
179pub enum Node<'a> {
180 Addr(IpAddr, Option<NodePort<'a>>),
182 Unknown(Option<NodePort<'a>>),
184 Obfuscated(&'a [u8], Option<NodePort<'a>>),
186}
187
188#[non_exhaustive]
200#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
201pub enum Proto {
202 Http,
204 Https,
206 Ws,
208 Wss,
210 Other,
213}
214
215fn proto(v: &[u8]) -> Option<Proto> {
217 const NAMES: [(&[u8], Proto); 4] = [
218 (b"http", Proto::Http),
219 (b"https", Proto::Https),
220 (b"ws", Proto::Ws),
221 (b"wss", Proto::Wss),
222 ];
223 if let Some(&(_, p)) = NAMES.iter().find(|(n, _)| v.eq_ignore_ascii_case(n)) {
224 return Some(p);
225 }
226 let (first, rest) = v.split_first()?;
227 let scheme = first.is_ascii_alphabetic()
228 && rest
229 .iter()
230 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.'));
231 scheme.then_some(Proto::Other)
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
249pub struct Element<'a> {
250 pub for_: Option<Node<'a>>,
252 pub by: Option<Node<'a>>,
254 pub host: Option<HostPort<'a>>,
256 pub proto: Option<Proto>,
258}
259
260const RELAXED: Limits = Limits::DEFAULT
262 .max_list_members(u16::MAX)
263 .max_empty_elements(u16::MAX)
264 .max_comment_depth(u8::MAX);
265
266fn is_obfuscated(s: &[u8]) -> bool {
268 matches!(s, [b'_', rest @ ..] if !rest.is_empty()
269 && rest.iter().all(|&b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-')))
270}
271
272fn node_port(s: &[u8]) -> Option<NodePort<'_>> {
273 if is_obfuscated(s) {
274 return Some(NodePort::Obfuscated(s));
275 }
276 if s.is_empty() || s.len() > 5 || !s.iter().all(u8::is_ascii_digit) {
277 return None;
278 }
279 let n = s.iter().fold(0u32, |n, &d| n * 10 + u32::from(d - b'0'));
280 u16::try_from(n).ok().map(NodePort::Number)
281}
282
283fn ip(text: &[u8], v6: bool) -> Option<IpAddr> {
285 if v6 {
286 let t = core::str::from_utf8(text).ok()?;
287 Ipv6Addr::from_str(t).ok().map(IpAddr::V6)
288 } else {
289 ipv4(text).map(IpAddr::V4)
290 }
291}
292
293fn ipv4_prefix(mut s: &[u8]) -> Option<(Ipv4Addr, &[u8])> {
298 let mut octets = [0u8; 4];
299 for (i, o) in octets.iter_mut().enumerate() {
300 if i > 0 {
301 let [b'.', rest @ ..] = s else {
302 return None;
303 };
304 s = rest;
305 }
306 let [first @ b'0'..=b'9', rest @ ..] = s else {
307 return None;
308 };
309 let (mut n, mut len) = (u16::from(*first - b'0'), 1usize);
310 s = rest;
311 while let [d @ b'0'..=b'9', rest @ ..] = s {
312 len += 1;
313 if len > 3 || *first == b'0' {
314 return None;
315 }
316 n = n * 10 + u16::from(*d - b'0');
317 s = rest;
318 }
319 *o = u8::try_from(n).ok()?;
320 }
321 Some((Ipv4Addr::from(octets), s))
322}
323
324fn ipv4(text: &[u8]) -> Option<Ipv4Addr> {
326 match ipv4_prefix(text)? {
327 (a, []) => Some(a),
328 _ => None,
329 }
330}
331
332fn node(v: &[u8]) -> Result<Node<'_>> {
334 let (name, rest) = if v.first() == Some(&b'[') {
335 match v.iter().position(|&b| b == b']') {
336 Some(close) => (
337 v.get(..=close).unwrap_or(&[]),
338 v.get(close + 1..).unwrap_or(&[]),
339 ),
340 None => return err(ErrorKind::InvalidAddress, 0),
341 }
342 } else {
343 match v.iter().position(|&b| b == b':') {
344 Some(c) => (v.get(..c).unwrap_or(&[]), v.get(c..).unwrap_or(&[])),
345 None => (v, &[][..]),
346 }
347 };
348 let port = match rest.split_first() {
349 None => None,
350 Some((b':', p)) => match node_port(p) {
351 Some(p) => Some(p),
352 None => return err(ErrorKind::InvalidPort, name.len() + 1),
353 },
354 Some(_) => return err(ErrorKind::InvalidAddress, name.len()),
355 };
356 if name.eq_ignore_ascii_case(b"unknown") {
357 return Ok(Node::Unknown(port));
358 }
359 if is_obfuscated(name) {
360 return Ok(Node::Obfuscated(name, port));
361 }
362 let addr = match name {
363 [b'[', inner @ .., b']'] => ip(inner, true),
364 _ => ip(name, false),
365 };
366 match addr {
367 Some(a) => Ok(Node::Addr(a, port)),
368 None => err(ErrorKind::InvalidAddress, 0),
369 }
370}
371
372fn element(e: &[u8]) -> Result<Element<'_>> {
375 let mut out = Element {
376 for_: None,
377 by: None,
378 host: None,
379 proto: None,
380 };
381 let (mut names, mut pairs) = (Names::new(), 0usize);
382 let mut i = 0usize;
383 loop {
384 i += skip_ows(e.get(i..).unwrap_or(&[]));
385 match e.get(i) {
386 None => return Ok(out),
387 Some(b';') => {
388 i += 1;
389 continue;
390 }
391 Some(_) => {}
392 }
393 let name_len = e
394 .get(i..)
395 .unwrap_or(&[])
396 .iter()
397 .take_while(|&&b| class::is_tchar(b))
398 .count();
399 if name_len == 0 {
400 return Err(refused(e, i, ErrorKind::NotToken));
401 }
402 let name_at = i;
403 let name = e.get(i..i + name_len).unwrap_or(&[]);
404 i += name_len;
405 if e.get(i) != Some(&b'=') {
406 return Err(refused(e, i, ErrorKind::MissingValue));
407 }
408 i += 1;
409 let (w, used) = parse_word(e.get(i..).unwrap_or(&[])).map_err(|x| match x.kind() {
410 ErrorKind::Empty => refused(e, i, ErrorKind::InvalidByte),
411 _ => x.shifted(i),
412 })?;
413 let Some(v) = w.as_plain() else {
416 return err(ErrorKind::InvalidQuotedPair, i);
417 };
418 let value_at = i + usize::from(matches!(w, Word::Quoted(_)));
419 i += used;
420 pairs += 1;
422 if pairs > MAX_CHECKED_PARAMS {
423 return err(ErrorKind::TooManyMembers, name_at);
424 }
425 if names.repeats(e, name_at, name, class::is_tchar) {
426 return err(ErrorKind::DuplicateParameter, name_at);
427 }
428 let slot = [&b"for"[..], b"by", b"host", b"proto"]
429 .iter()
430 .position(|k| name.eq_ignore_ascii_case(k));
431 match slot {
432 Some(0) => out.for_ = Some(node(v).map_err(|x| x.shifted(value_at))?),
433 Some(1) => out.by = Some(node(v).map_err(|x| x.shifted(value_at))?),
434 Some(2) => out.host = Some(host_port(v).map_err(|x| x.shifted(value_at))?),
435 Some(3) => match proto(v) {
436 Some(p) => out.proto = Some(p),
437 None => return err(ErrorKind::Malformed, value_at),
438 },
439 _ => {}
440 }
441 i += skip_ows(e.get(i..).unwrap_or(&[]));
442 match e.get(i) {
443 None => return Ok(out),
444 Some(b';') => i += 1,
445 Some(_) => return Err(refused(e, i, ErrorKind::ExpectedSeparator)),
446 }
447 }
448}
449
450fn refused(e: &[u8], at: usize, kind: ErrorKind) -> ParseError {
451 match e.get(at) {
452 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
453 _ => ParseError::new(kind, at),
454 }
455}
456
457#[derive(Clone, Copy, Debug)]
471pub struct ForwardedElements<'a> {
472 lines: &'a [&'a [u8]],
473 count: usize,
474}
475
476impl<'a> ForwardedElements<'a> {
477 pub fn iter(&self) -> impl Iterator<Item = Element<'a>> + use<'a> {
490 let lines = self.lines;
491 lines
492 .iter()
493 .filter_map(|line| list::parse(line, &RELAXED).ok())
494 .flat_map(|l| l.into_iter())
495 .filter_map(|e| element(e).ok())
496 }
497
498 #[must_use]
510 pub const fn len(&self) -> usize {
511 self.count
512 }
513
514 #[must_use]
527 pub const fn is_empty(&self) -> bool {
528 self.count == 0
529 }
530}
531
532pub fn parse<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<ForwardedElements<'a>> {
559 let mut count = 0usize;
560 for (n, line) in lines.iter().enumerate() {
561 let l = list::parse(line, limits).map_err(|e| e.on_line(n))?;
562 for e in l.iter() {
563 let at = (e.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
564 element(e)
565 .map(|_| ())
566 .map_err(|x| x.shifted(at).on_line(n))?;
567 count += 1;
568 if count > usize::from(limits.hops) {
569 return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
570 }
571 }
572 }
573 Ok(ForwardedElements { lines, count })
574}
575
576fn xff_member(m: &[u8]) -> Option<(IpAddr, Option<u16>)> {
579 let port = |p: &[u8]| -> Option<Option<u16>> {
580 match node_port(p)? {
581 NodePort::Number(n) => Some(Some(n)),
582 NodePort::Obfuscated(_) => None,
583 }
584 };
585 if let [b'[', ..] = m {
586 let close = m.iter().position(|&b| b == b']')?;
587 let a = ip(m.get(1..close)?, true)?;
588 return match m.get(close + 1..)? {
589 [] => Some((a, None)),
590 [b':', p @ ..] => Some((a, port(p)?)),
591 _ => None,
592 };
593 }
594 match ipv4_prefix(m) {
596 Some((a, [])) => Some((IpAddr::V4(a), None)),
597 Some((a, [b':', p @ ..])) => Some((IpAddr::V4(a), port(p)?)),
598 Some(_) => None,
599 None => ip(m, true).map(|a| (a, None)),
600 }
601}
602
603fn xff_line(line: &[u8]) -> impl Iterator<Item = &[u8]> {
605 line.split(|&b| b == b',').map(trim_ows)
606}
607
608#[derive(Clone, Copy, Debug)]
622pub struct XffChain<'a> {
623 lines: &'a [&'a [u8]],
624 count: usize,
625}
626
627impl<'a> XffChain<'a> {
628 pub fn iter(&self) -> impl Iterator<Item = (IpAddr, Option<u16>)> + use<'a> {
643 self.lines
644 .iter()
645 .flat_map(|line| xff_line(line))
646 .filter_map(xff_member)
647 }
648
649 #[must_use]
661 pub const fn len(&self) -> usize {
662 self.count
663 }
664
665 #[must_use]
678 pub const fn is_empty(&self) -> bool {
679 self.count == 0
680 }
681}
682
683pub fn parse_x_forwarded_for<'a>(lines: &'a [&'a [u8]], limits: &Limits) -> Result<XffChain<'a>> {
706 let mut count = 0usize;
707 for (n, line) in lines.iter().enumerate() {
708 crate::scan::check_field_value(line).map_err(|e| e.on_line(n))?;
709 for m in xff_line(line) {
710 let at = (m.as_ptr() as usize).saturating_sub(line.as_ptr() as usize);
711 if xff_member(m).is_none() {
712 return Err(ParseError::new(ErrorKind::Malformed, at).on_line(n));
713 }
714 count += 1;
715 if count > usize::from(limits.hops) {
716 return Err(ParseError::new(ErrorKind::TooManyHops, at).on_line(n));
717 }
718 }
719 }
720 Ok(XffChain { lines, count })
721}
722
723pub fn parse_x_forwarded_proto(value: &[u8]) -> Result<Proto> {
741 let mut found: Option<(Proto, &[u8])> = None;
742 for m in value.split(|&b| b == b',') {
743 let at = (m.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
744 let t = trim_ows(m);
745 let Some(p) = proto(t) else {
746 return err(ErrorKind::Malformed, at);
747 };
748 if found.is_some_and(|(_, f)| !f.eq_ignore_ascii_case(t)) {
749 return err(ErrorKind::Malformed, at);
750 }
751 found = Some((p, t));
752 }
753 found
754 .map(|(p, _)| p)
755 .ok_or(ParseError::new(ErrorKind::Malformed, 0))
756}
757
758pub fn parse_x_forwarded_host(value: &[u8]) -> Result<HostPort<'_>> {
774 if let Some(at) = crate::scan::find_byte(b',', value) {
775 return err(ErrorKind::Malformed, at);
776 }
777 crate::host::parse_host_port(value)
778}
779
780fn xff_client(
786 lines: &[&[u8]],
787 limits: &Limits,
788 trust: &impl Trust,
789) -> Option<Option<ClientAddress>> {
790 for line in lines {
791 crate::scan::check_field_value(line).ok()?;
792 }
793 let hops = |n: usize| u8::try_from(n).unwrap_or(u8::MAX);
794 let (mut count, mut found, mut leftmost) = (0usize, None, None);
795 for line in lines.iter().rev() {
796 for m in line.rsplit(|&b| b == b',') {
797 let (addr, _) = xff_member(trim_ows(m))?;
798 count += 1;
799 if count > usize::from(limits.hops) {
800 return None;
801 }
802 if found.is_none() {
803 if trust.is_trusted(addr) {
804 leftmost = Some(addr);
805 } else {
806 found = Some(ClientAddress::Forwarded {
807 addr,
808 hops: hops(count),
809 });
810 }
811 }
812 }
813 }
814 Some(found.or(leftmost.map(|addr| ClientAddress::Forwarded {
815 addr,
816 hops: hops(count),
817 })))
818}
819
820fn forwarded_client(elements: &ForwardedElements<'_>, trust: &impl Trust) -> Option<ClientAddress> {
824 let count = elements.len();
825 let hops = |n: usize| u8::try_from(n).unwrap_or(u8::MAX);
826 let (mut last, mut leftmost) = (None, None);
827 for (i, e) in elements.iter().enumerate() {
828 match e.for_ {
829 Some(Node::Addr(a, _)) if trust.is_trusted(a) => {
830 leftmost = leftmost.or(Some(a));
831 }
832 Some(Node::Addr(addr, _)) => {
833 last = Some(ClientAddress::Forwarded {
834 addr,
835 hops: hops(count - i),
836 });
837 }
838 _ => {
839 last = Some(ClientAddress::Unknown {
840 hops: hops(count - i),
841 });
842 }
843 }
844 }
845 last.or(leftmost.map(|addr| ClientAddress::Forwarded {
846 addr,
847 hops: hops(count),
848 }))
849}
850
851pub fn client_address(
882 peer: IpAddr,
883 source: ForwardedSource<'_>,
884 trust: &impl Trust,
885 max_hops: u8,
886) -> Result<ClientAddress> {
887 if !trust.is_trusted(peer) {
888 return Ok(ClientAddress::Peer(peer));
889 }
890 let limits = Limits::DEFAULT.max_hops(max_hops);
891 let found = match source {
892 ForwardedSource::XForwardedFor(lines) => match xff_client(lines, &limits, trust) {
893 Some(found) => found,
894 None => {
895 let _ = parse_x_forwarded_for(lines, &limits).map_err(as_malformed)?;
896 return err(ErrorKind::Malformed, 0);
897 }
898 },
899 ForwardedSource::Forwarded(lines) => {
900 let elements = parse(lines, &limits).map_err(as_malformed)?;
901 forwarded_client(&elements, trust)
902 }
903 };
904 Ok(found.unwrap_or(ClientAddress::Peer(peer)))
905}
906
907fn as_malformed(e: ParseError) -> ParseError {
910 if e.kind() == ErrorKind::TooManyHops {
911 e
912 } else {
913 ParseError::new(ErrorKind::Malformed, e.offset()).on_line(e.line())
914 }
915}
916
917#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
933pub struct Resolved {
934 pub address: ClientAddress,
936 pub other_present: bool,
939}
940
941pub fn client_address_detailed(
962 peer: IpAddr,
963 source: ForwardedSource<'_>,
964 other_lines: &[&[u8]],
965 trust: &impl Trust,
966 max_hops: u8,
967) -> Result<Resolved> {
968 Ok(Resolved {
969 address: client_address(peer, source, trust, max_hops)?,
970 other_present: other_lines.iter().any(|l| !trim_ows(l).is_empty()),
971 })
972}
973
974fn put_node(n: &Node<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
977 let port = match *n {
978 Node::Addr(ip, port) => {
979 let host = match ip {
980 IpAddr::V4(a) => Host::V4(a),
981 IpAddr::V6(a) => Host::V6(a),
982 };
983 HostPort {
984 host,
985 port: None,
986 empty_port: false,
987 }
988 .write_to(out)?;
989 port
990 }
991 Node::Unknown(port) => {
992 out.put(b"unknown")?;
993 port
994 }
995 Node::Obfuscated(name, port) => {
996 if !is_obfuscated(name) {
997 return Err(WriteError::NotToken);
998 }
999 out.put(name)?;
1000 port
1001 }
1002 };
1003 match port {
1004 None => {}
1005 Some(NodePort::Number(p)) => {
1006 out.put(b":")?;
1007 crate::write::write_decimal_u64(u64::from(p), out)?;
1008 }
1009 Some(NodePort::Obfuscated(p)) => {
1010 if !is_obfuscated(p) {
1011 return Err(WriteError::NotToken);
1012 }
1013 out.put(b":")?;
1014 out.put(p)?;
1015 }
1016 }
1017 Ok(())
1018}
1019
1020fn put_pair(
1023 name: &[u8],
1024 n: &Node<'_>,
1025 first: &mut bool,
1026 out: &mut impl Sink,
1027) -> core::result::Result<(), WriteError> {
1028 let quote = matches!(n, Node::Addr(IpAddr::V6(_), _))
1029 || matches!(
1030 n,
1031 Node::Addr(_, Some(_)) | Node::Unknown(Some(_)) | Node::Obfuscated(_, Some(_))
1032 );
1033 if !*first {
1034 out.put(b";")?;
1035 }
1036 *first = false;
1037 out.put(name)?;
1038 out.put(b"=")?;
1039 if quote {
1040 out.put(b"\"")?;
1041 }
1042 put_node(n, out)?;
1043 if quote {
1044 out.put(b"\"")?;
1045 }
1046 Ok(())
1047}
1048
1049fn put_element(e: &Element<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
1050 let mut first = true;
1051 if let Some(n) = &e.for_ {
1052 put_pair(b"for", n, &mut first, out)?;
1053 }
1054 if let Some(p) = e.proto {
1055 if !first {
1056 out.put(b";")?;
1057 }
1058 first = false;
1059 out.put(match p {
1060 Proto::Http => b"proto=http",
1061 Proto::Https => b"proto=https",
1062 Proto::Ws => b"proto=ws",
1063 Proto::Wss => b"proto=wss",
1064 Proto::Other => return Err(WriteError::NotToken),
1065 })?;
1066 }
1067 if let Some(n) = &e.by {
1068 put_pair(b"by", n, &mut first, out)?;
1069 }
1070 if let Some(h) = &e.host {
1071 if !first {
1072 out.put(b";")?;
1073 }
1074 out.put(b"host=")?;
1075 let quote = h.port.is_some()
1080 || match h.host {
1081 Host::V4(_) => false,
1082 Host::V6(_) | Host::Future(_) => true,
1083 Host::Name(n) => !is_token(n),
1084 };
1085 if quote {
1086 out.put(b"\"")?;
1087 }
1088 h.write_to(out)?;
1089 if quote {
1090 out.put(b"\"")?;
1091 }
1092 }
1093 Ok(())
1094}
1095
1096pub fn write_element(e: &Element<'_>, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
1127 let mut count = CountSink::new();
1128 put_element(e, &mut count)?;
1129 reserve(out, count.count())?;
1130 put_element(e, out)
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135 use std::vec::Vec;
1136
1137 use super::*;
1138
1139 #[test]
1140 fn test_write_element_round_trip() {
1141 let v6: IpAddr = "2001:db8:cafe::17".parse().unwrap();
1142 let cases = [
1143 Element {
1144 for_: Some(Node::Addr(ip4("192.0.2.43"), None)),
1145 by: None,
1146 host: None,
1147 proto: None,
1148 },
1149 Element {
1150 for_: Some(Node::Addr(v6, Some(NodePort::Number(4711)))),
1151 by: Some(Node::Obfuscated(
1152 b"_hidden",
1153 Some(NodePort::Obfuscated(b"_p1")),
1154 )),
1155 host: Some(crate::parse_host_port(b"example.com:8080").unwrap()),
1156 proto: Some(Proto::Http),
1157 },
1158 Element {
1159 for_: Some(Node::Unknown(Some(NodePort::Number(80)))),
1160 by: Some(Node::Addr(v6, None)),
1161 host: Some(crate::parse_host_port(b"[::1]").unwrap()),
1162 proto: Some(Proto::Https),
1163 },
1164 ];
1165 for e in cases {
1166 let mut buf = [0u8; 128];
1167 let mut out = crate::SliceSink::new(&mut buf);
1168 write_element(&e, &mut out).unwrap();
1169 let lines = [out.written()];
1170 let parsed = parse(&lines, &Limits::DEFAULT).unwrap();
1171 assert_eq!(
1172 parsed.iter().next(),
1173 Some(e),
1174 "{:?}",
1175 core::str::from_utf8(out.written())
1176 );
1177 }
1178 let bad = Element {
1179 for_: Some(Node::Obfuscated(b"hidden", None)),
1180 by: None,
1181 host: None,
1182 proto: None,
1183 };
1184 let mut buf = [0u8; 8];
1185 let mut out = crate::SliceSink::new(&mut buf);
1186 assert_eq!(write_element(&bad, &mut out), Err(WriteError::NotToken));
1187 let big = Element {
1188 for_: Some(Node::Addr(v6, None)),
1189 by: None,
1190 host: None,
1191 proto: None,
1192 };
1193 assert_eq!(write_element(&big, &mut out), Err(WriteError::Full));
1194 assert!(out.is_empty());
1195 }
1196
1197 fn ip4(s: &str) -> IpAddr {
1198 s.parse().unwrap()
1199 }
1200
1201 #[test]
1202 fn test_forwarded_rfc_examples() {
1203 let l = Limits::DEFAULT;
1204 let one = |v: &'static [u8]| -> Element<'static> {
1205 let lines: &'static [&'static [u8]] = std::boxed::Box::leak(std::boxed::Box::new([v]));
1206 parse(lines, &l).unwrap().iter().next().unwrap()
1207 };
1208 let e = one(b"for=\"_gazonk\"");
1209 assert_eq!(e.for_, Some(Node::Obfuscated(b"_gazonk", None)));
1210 let e = one(b"For=\"[2001:db8:cafe::17]:4711\"");
1211 assert!(matches!(
1212 e.for_,
1213 Some(Node::Addr(IpAddr::V6(_), Some(NodePort::Number(4711))))
1214 ));
1215 let e = one(b"for=192.0.2.60;proto=http;by=203.0.113.43");
1216 assert_eq!(
1217 (e.proto, e.by),
1218 (
1219 Some(Proto::Http),
1220 Some(Node::Addr(ip4("203.0.113.43"), None))
1221 )
1222 );
1223 assert_eq!(one(b"for=unknown").for_, Some(Node::Unknown(None)));
1224 assert_eq!(
1225 one(b"for=\"10.0.0.1:_p1\"").for_,
1226 Some(Node::Addr(
1227 ip4("10.0.0.1"),
1228 Some(NodePort::Obfuscated(b"_p1"))
1229 ))
1230 );
1231 assert_eq!(
1232 one(b"host=example.com; proto=https").proto,
1233 Some(Proto::Https)
1234 );
1235 assert_eq!(
1236 one(b";;for=1.2.3.4;").for_,
1237 Some(Node::Addr(ip4("1.2.3.4"), None))
1238 );
1239 }
1240
1241 #[test]
1242 fn test_forwarded_refusals() {
1243 let l = Limits::DEFAULT;
1244 let k = |v: &[u8]| parse(&[v], &l).unwrap_err().kind();
1245 assert_ne!(k(b"for=2001:db8::1"), ErrorKind::TooManyHops);
1246 assert_eq!(k(b"for=1.2.3.4;for=5.6.7.8"), ErrorKind::DuplicateParameter);
1247 assert_eq!(k(b"for=1.2.3"), ErrorKind::InvalidAddress);
1248 assert_eq!(k(b"for=01.2.3.4"), ErrorKind::InvalidAddress);
1249 assert_eq!(k(b"for=\"[::1]:99999\""), ErrorKind::InvalidPort);
1250 assert_eq!(k(b"proto=1x"), ErrorKind::Malformed);
1251 assert_eq!(k(b"for"), ErrorKind::MissingValue);
1252 assert_eq!(k(b"for=\"a\\\"b\""), ErrorKind::InvalidQuotedPair);
1253 assert_eq!(k(b"for=1.2.3.4 x"), ErrorKind::ExpectedSeparator);
1254 let seventeen = [b"for=1.1.1.1,".repeat(17)].concat();
1255 assert_eq!(
1256 parse(&[&seventeen[..]], &l).unwrap_err().kind(),
1257 ErrorKind::TooManyHops
1258 );
1259 let sixteen = [b"for=1.1.1.1,".repeat(16)].concat();
1260 assert_eq!(parse(&[&sixteen[..]], &l).unwrap().len(), 16);
1261 }
1262
1263 #[test]
1264 fn test_xff_shapes() {
1265 let l = Limits::DEFAULT;
1266 let ok = |v: &[u8]| parse_x_forwarded_for(&[v], &l).map(|c| c.len());
1267 assert_eq!(ok(b"1.2.3.4"), Ok(1));
1268 assert_eq!(ok(b" 1.2.3.4 , 5.6.7.8:80 "), Ok(2));
1269 assert_eq!(ok(b"2001:db8::1, [2001:db8::2]:443, [::1]"), Ok(3));
1270 for bad in [
1271 &b"1.2.3.4, , 5.6.7.8"[..],
1272 b"",
1273 b"garbage",
1274 b"1.2.3.4:x",
1275 b"2001:db8::1:80:",
1276 b"[::1",
1277 b"1.2.3.4\r",
1278 ] {
1279 assert!(
1280 ok(bad).is_err(),
1281 "{}",
1282 std::string::String::from_utf8_lossy(bad)
1283 );
1284 }
1285 }
1286
1287 #[test]
1288 fn test_xff_untrusted_peer_ignored() {
1289 let t = |a: IpAddr| a == ip4("10.0.0.1");
1290 let lines: [&[u8]; 1] = [b"garbage that would be refused"];
1291 assert_eq!(
1292 client_address(
1293 ip4("192.0.2.9"),
1294 ForwardedSource::XForwardedFor(&lines),
1295 &t,
1296 16
1297 ),
1298 Ok(ClientAddress::Peer(ip4("192.0.2.9")))
1299 );
1300 }
1301
1302 #[test]
1303 fn test_xff_leftmost_spoof_ignored() {
1304 let t = |a: IpAddr| a == ip4("10.0.0.1") || a == ip4("10.0.0.2");
1305 let lines: [&[u8]; 1] = [b"1.1.1.1, 203.0.113.7, 10.0.0.2"];
1306 assert_eq!(
1307 client_address(
1308 ip4("10.0.0.1"),
1309 ForwardedSource::XForwardedFor(&lines),
1310 &t,
1311 16
1312 ),
1313 Ok(ClientAddress::Forwarded {
1314 addr: ip4("203.0.113.7"),
1315 hops: 2
1316 })
1317 );
1318 }
1319
1320 #[test]
1321 fn test_xff_malformed_fails_closed() {
1322 let t = |_: IpAddr| true;
1323 let lines: [&[u8]; 2] = [b"1.1.1.1", b"10.0.0.2, nope"];
1324 let e = client_address(
1325 ip4("10.0.0.1"),
1326 ForwardedSource::XForwardedFor(&lines),
1327 &t,
1328 16,
1329 )
1330 .unwrap_err();
1331 assert_eq!((e.kind(), e.line()), (ErrorKind::Malformed, 1));
1332 let too_many: [&[u8]; 1] = [b"1.1.1.1, 1.1.1.2, 1.1.1.3"];
1333 assert_eq!(
1334 client_address(
1335 ip4("10.0.0.1"),
1336 ForwardedSource::XForwardedFor(&too_many),
1337 &t,
1338 2
1339 )
1340 .unwrap_err()
1341 .kind(),
1342 ErrorKind::TooManyHops
1343 );
1344 }
1345
1346 #[test]
1347 fn test_client_address_walk_cases() {
1348 let t = |a: IpAddr| a.is_loopback() || a == ip4("10.0.0.1");
1349 let peer = ip4("10.0.0.1");
1350 let x = |v: &'static [u8]| {
1351 client_address(
1352 peer,
1353 ForwardedSource::XForwardedFor(std::boxed::Box::leak(std::boxed::Box::new([v]))),
1354 &t,
1355 16,
1356 )
1357 };
1358 assert_eq!(
1359 x(b"127.0.0.1, 10.0.0.1"),
1360 Ok(ClientAddress::Forwarded {
1361 addr: ip4("127.0.0.1"),
1362 hops: 2
1363 })
1364 );
1365 let none: [&[u8]; 0] = [];
1366 assert_eq!(
1367 client_address(peer, ForwardedSource::XForwardedFor(&none), &t, 16),
1368 Ok(ClientAddress::Peer(peer))
1369 );
1370 let f = |v: &'static [u8]| {
1371 client_address(
1372 peer,
1373 ForwardedSource::Forwarded(std::boxed::Box::leak(std::boxed::Box::new([v]))),
1374 &t,
1375 16,
1376 )
1377 };
1378 assert_eq!(
1379 f(b"for=192.0.2.1, for=unknown, for=127.0.0.1"),
1380 Ok(ClientAddress::Unknown { hops: 2 })
1381 );
1382 assert_eq!(
1383 f(b"for=192.0.2.1, for=_hidden"),
1384 Ok(ClientAddress::Unknown { hops: 1 })
1385 );
1386 assert_eq!(
1387 f(b"for=192.0.2.1, proto=https"),
1388 Ok(ClientAddress::Unknown { hops: 1 })
1389 );
1390 assert_eq!(
1391 f(b"for=\"[2001:db8::7]\""),
1392 Ok(ClientAddress::Forwarded {
1393 addr: "2001:db8::7".parse().unwrap(),
1394 hops: 1
1395 })
1396 );
1397 assert_eq!(
1398 f(b"for=2001:db8::7").unwrap_err().kind(),
1399 ErrorKind::Malformed
1400 );
1401 }
1402
1403 #[test]
1404 fn test_both_forwarded_headers_reported() {
1405 let t = |_: IpAddr| true;
1406 let xff: [&[u8]; 1] = [b"192.0.2.1"];
1407 let fwd: [&[u8]; 1] = [b"for=198.51.100.1"];
1408 let r = client_address_detailed(
1409 ip4("10.0.0.1"),
1410 ForwardedSource::XForwardedFor(&xff),
1411 &fwd,
1412 &t,
1413 16,
1414 )
1415 .unwrap();
1416 assert!(r.other_present);
1417 assert_eq!(
1418 r.address,
1419 ClientAddress::Forwarded {
1420 addr: ip4("192.0.2.1"),
1421 hops: 1
1422 }
1423 );
1424 let empty: [&[u8]; 1] = [b" "];
1425 assert!(
1426 !client_address_detailed(
1427 ip4("10.0.0.1"),
1428 ForwardedSource::XForwardedFor(&xff),
1429 &empty,
1430 &t,
1431 16
1432 )
1433 .unwrap()
1434 .other_present
1435 );
1436 }
1437
1438 #[test]
1439 fn test_x_forwarded_proto_and_host() {
1440 assert_eq!(parse_x_forwarded_proto(b"http, HTTP"), Ok(Proto::Http));
1441 assert!(parse_x_forwarded_proto(b"").is_err());
1442 assert_eq!(parse_x_forwarded_proto(b"wss"), Ok(Proto::Wss));
1443 assert_eq!(parse_x_forwarded_proto(b"ftp"), Ok(Proto::Other));
1444 assert!(parse_x_forwarded_proto(b"ftp, sftp").is_err());
1445 assert!(parse_x_forwarded_proto(b"1x").is_err());
1446 assert!(parse_x_forwarded_host(b"[::1]:8080").is_ok());
1447 assert_eq!(
1448 parse_x_forwarded_host(b"a,b").unwrap_err().kind(),
1449 ErrorKind::Malformed
1450 );
1451 }
1452
1453 mod reference {
1456 use super::super::*;
1457
1458 pub(super) fn xff_member(m: &[u8]) -> Option<(IpAddr, Option<u16>)> {
1459 let port = |p: &[u8]| -> Option<Option<u16>> {
1460 match node_port(p)? {
1461 NodePort::Number(n) => Some(Some(n)),
1462 NodePort::Obfuscated(_) => None,
1463 }
1464 };
1465 if let [b'[', ..] = m {
1466 let close = m.iter().position(|&b| b == b']')?;
1467 let a = ip(m.get(1..close)?, true)?;
1468 return match m.get(close + 1..)? {
1469 [] => Some((a, None)),
1470 [b':', p @ ..] => Some((a, port(p)?)),
1471 _ => None,
1472 };
1473 }
1474 let v4 = |t: &[u8]| {
1475 core::str::from_utf8(t)
1476 .ok()?
1477 .parse::<Ipv4Addr>()
1478 .ok()
1479 .map(IpAddr::V4)
1480 };
1481 let mut colons = m.iter().enumerate().filter(|&(_, &b)| b == b':');
1482 match (colons.next(), colons.next()) {
1483 (None, _) => Some((v4(m)?, None)),
1484 (Some((c, _)), None) => Some((v4(m.get(..c)?)?, port(m.get(c + 1..)?)?)),
1485 (Some(_), Some(_)) => Some((ip(m, true)?, None)),
1486 }
1487 }
1488
1489 enum Step {
1490 Addr(IpAddr),
1491 Unknown,
1492 }
1493
1494 fn walk(
1495 count: usize,
1496 trust: &impl Trust,
1497 mut step: impl FnMut(usize) -> Step,
1498 ) -> Option<ClientAddress> {
1499 let mut leftmost: Option<IpAddr> = None;
1500 for (walked, i) in (0..count).rev().enumerate() {
1501 let hops = u8::try_from(walked + 1).unwrap_or(u8::MAX);
1502 match step(i) {
1503 Step::Addr(a) if trust.is_trusted(a) => leftmost = Some(a),
1504 Step::Addr(addr) => return Some(ClientAddress::Forwarded { addr, hops }),
1505 Step::Unknown => return Some(ClientAddress::Unknown { hops }),
1506 }
1507 }
1508 leftmost.map(|addr| ClientAddress::Forwarded {
1509 addr,
1510 hops: u8::try_from(count).unwrap_or(u8::MAX),
1511 })
1512 }
1513
1514 fn nth_member<'a, I: Iterator<Item = &'a [u8]>>(
1515 lines: &[&'a [u8]],
1516 i: usize,
1517 members: impl Fn(&'a [u8]) -> I,
1518 ) -> Option<&'a [u8]> {
1519 lines.iter().flat_map(|line| members(line)).nth(i)
1520 }
1521
1522 pub(super) fn client_address(
1523 peer: IpAddr,
1524 source: ForwardedSource<'_>,
1525 trust: &impl Trust,
1526 max_hops: u8,
1527 ) -> Result<ClientAddress> {
1528 if !trust.is_trusted(peer) {
1529 return Ok(ClientAddress::Peer(peer));
1530 }
1531 let limits = Limits::DEFAULT.max_hops(max_hops);
1532 let found = match source {
1533 ForwardedSource::XForwardedFor(lines) => {
1534 let chain = parse_x_forwarded_for(lines, &limits).map_err(as_malformed)?;
1535 walk(chain.len(), trust, |i| {
1536 match nth_member(lines, i, xff_line).and_then(xff_member) {
1537 Some((a, _)) => Step::Addr(a),
1538 None => Step::Unknown,
1539 }
1540 })
1541 }
1542 ForwardedSource::Forwarded(lines) => {
1543 let elements = parse(lines, &limits).map_err(as_malformed)?;
1544 walk(elements.len(), trust, |i| {
1545 let e = nth_member(lines, i, |line| {
1546 list::parse(line, &Limits::DEFAULT)
1547 .ok()
1548 .into_iter()
1549 .flat_map(|l| l.into_iter())
1550 })
1551 .and_then(|e| element(e).ok());
1552 match e.and_then(|e| e.for_) {
1553 Some(Node::Addr(a, _)) => Step::Addr(a),
1554 _ => Step::Unknown,
1555 }
1556 })
1557 }
1558 };
1559 Ok(found.unwrap_or(ClientAddress::Peer(peer)))
1560 }
1561 }
1562
1563 fn v4(s: &str) -> IpAddr {
1564 s.parse().unwrap()
1565 }
1566
1567 proptest::proptest! {
1568 #![proptest_config(proptest::prelude::ProptestConfig::with_cases(4096))]
1569
1570 #[test]
1571 fn test_xff_member_agrees_with_reference(
1572 text in "[0-9a-f.:\\[\\]]{0,28}",
1573 shaped in "(25[0-9]|1?[0-9]{1,2})\\.(0|[0-9]{1,3})\\.[0-9]{1,3}\\.[0-9]{1,3}(:[0-9_a]{0,6})?",
1574 ) {
1575 proptest::prop_assert_eq!(xff_member(text.as_bytes()), reference::xff_member(text.as_bytes()));
1576 proptest::prop_assert_eq!(xff_member(shaped.as_bytes()), reference::xff_member(shaped.as_bytes()));
1577 }
1578
1579 #[test]
1580 fn test_client_address_agrees_with_reference(
1581 xff in proptest::collection::vec(proptest::sample::select(&[
1582 "10.0.0.1", "10.0.0.2", "1.2.3.4", "198.51.100.7:80", "[2001:db8::1]:443",
1583 "2001:db8::2", "::1", "", "bad", "01.2.3.4", "1.2.3", "1.2.3.4:99999", "[::1",
1584 ][..]), 0..12),
1585 fwd in proptest::collection::vec(proptest::sample::select(&[
1586 "for=10.0.0.1", "for=10.0.0.2;proto=http", "for=1.2.3.4", "for=unknown",
1587 "for=_hidden", "proto=https", "for=\"[2001:db8::1]:80\"", "for=\"::1\"",
1588 "by=10.0.0.1;for=198.51.100.7", "bad", "for=", "",
1589 ][..]), 0..12),
1590 cuts in proptest::collection::vec(proptest::bool::ANY, 12),
1591 max_hops in 0u8..10,
1592 trusted_peer in proptest::bool::ANY,
1593 ) {
1594 fn lines<'a>(members: &[&str], cuts: &[bool], store: &'a mut Vec<std::string::String>) -> Vec<&'a [u8]> {
1597 let mut cur = std::string::String::new();
1598 for (i, m) in members.iter().enumerate() {
1599 if i > 0 && cuts[i] {
1600 store.push(core::mem::take(&mut cur));
1601 } else if i > 0 {
1602 cur.push_str(", ");
1603 }
1604 cur.push_str(m);
1605 }
1606 if !members.is_empty() {
1607 store.push(cur);
1608 }
1609 store.iter().map(|s| s.as_bytes()).collect()
1610 }
1611 let peer = if trusted_peer { v4("10.0.0.1") } else { v4("203.0.113.1") };
1612 let trust = |a: IpAddr| a == v4("10.0.0.1") || a == v4("10.0.0.2") || a == v4("::1");
1613 let shape = |r: Result<ClientAddress>| r.map_err(|e| (e.kind(), e.offset(), e.line()));
1614 let mut store = Vec::new();
1615 let ls = lines(&xff, &cuts, &mut store);
1616 proptest::prop_assert_eq!(
1617 shape(client_address(peer, ForwardedSource::XForwardedFor(&ls), &trust, max_hops)),
1618 shape(reference::client_address(peer, ForwardedSource::XForwardedFor(&ls), &trust, max_hops))
1619 );
1620 let mut store = Vec::new();
1621 let ls = lines(&fwd, &cuts, &mut store);
1622 proptest::prop_assert_eq!(
1623 shape(client_address(peer, ForwardedSource::Forwarded(&ls), &trust, max_hops)),
1624 shape(reference::client_address(peer, ForwardedSource::Forwarded(&ls), &trust, max_hops))
1625 );
1626 }
1627 }
1628}