don't return any forwarded hops if the header is invalid

This commit is contained in:
Robin Appelman 2025-10-10 23:23:33 +02:00
commit fcd6646f1b
2 changed files with 54 additions and 16 deletions

View file

@ -1,8 +1,9 @@
use comma_separated::CommaSeparatedIterator; use comma_separated::CommaSeparatedIterator;
use either::Either;
use rfc7239::{parse, Forwarded, NodeIdentifier, NodeName}; use rfc7239::{parse, Forwarded, NodeIdentifier, NodeName};
use std::borrow::Cow; use std::borrow::Cow;
use std::iter::IntoIterator; use std::iter::{empty, IntoIterator};
use std::net::IpAddr; use std::net::{AddrParseError, IpAddr};
use std::str::FromStr; use std::str::FromStr;
/// Get the list of ip addresses from an `forwarded` header /// Get the list of ip addresses from an `forwarded` header
@ -22,17 +23,29 @@ use std::str::FromStr;
pub fn extract_forwarded_header( pub fn extract_forwarded_header(
header_value: &str, header_value: &str,
) -> impl DoubleEndedIterator<Item = IpAddr> + '_ { ) -> impl DoubleEndedIterator<Item = IpAddr> + '_ {
parse(header_value).filter_map(|forward| match forward { if parse(header_value).all(|result| result.is_ok()) {
Ok(Forwarded { Either::Left(parse(header_value).filter_map(|forward| match forward {
forwarded_for: Ok(Forwarded {
Some(NodeIdentifier { forwarded_for:
name: NodeName::Ip(ip), Some(NodeIdentifier {
.. name: NodeName::Ip(ip),
}), ..
.. }),
}) => Some(ip), ..
_ => None, }) => Some(ip),
}) _ => None,
}))
} else {
Either::Right(empty())
}
}
fn extract_x_forwarded_for_header_raw(
header_value: &str,
) -> impl DoubleEndedIterator<Item = Result<IpAddr, AddrParseError>> + '_ {
CommaSeparatedIterator::new(header_value)
.map(str::trim)
.map(|x| IpAddr::from_str(maybe_bracketed(&maybe_quoted(x))))
} }
/// Get the list of ip addresses from an `x-forwarded-for` header /// Get the list of ip addresses from an `x-forwarded-for` header
@ -50,9 +63,11 @@ pub fn extract_forwarded_header(
pub fn extract_x_forwarded_for_header( pub fn extract_x_forwarded_for_header(
header_value: &str, header_value: &str,
) -> impl DoubleEndedIterator<Item = IpAddr> + '_ { ) -> impl DoubleEndedIterator<Item = IpAddr> + '_ {
CommaSeparatedIterator::new(header_value) if extract_x_forwarded_for_header_raw(header_value).all(|result| result.is_ok()) {
.map(str::trim) Either::Left(extract_x_forwarded_for_header_raw(header_value).flatten())
.flat_map(|x| IpAddr::from_str(maybe_bracketed(&maybe_quoted(x)))) } else {
Either::Right(empty())
}
} }
/// Get the list of ip addresses from an `x-real-ip` header /// Get the list of ip addresses from an `x-real-ip` header

View file

@ -74,6 +74,7 @@ pub fn real_ip(headers: &HeaderMap, remote: IpAddr, trusted_proxies: &[IpNet]) -
'outer: for hop in hops.rev() { 'outer: for hop in hops.rev() {
for proxy in trusted_proxies { for proxy in trusted_proxies {
dbg!(proxy);
if proxy.contains(&hop) { if proxy.contains(&hop) {
continue 'outer; continue 'outer;
} }
@ -110,3 +111,25 @@ pub fn get_forwarded_for(headers: &HeaderMap) -> impl DoubleEndedIterator<Item =
#[allow(dead_code)] #[allow(dead_code)]
#[doc = include_str!("../README.md")] #[doc = include_str!("../README.md")]
fn test_readme_examples() {} fn test_readme_examples() {}
#[test]
fn test_malformed() {
use http::header::HeaderValue;
let mut headers = HeaderMap::new();
headers.insert(
"forwarded",
HeaderValue::from_static(
"by=203.0.111.42;for=1.2.3.4:8888,for=5.6.7.8;proto=https;nonsense",
),
);
let trusted_proxies = [
IpAddr::from([2, 3, 4, 5]).into(),
IpAddr::from([5, 6, 7, 8]).into(),
];
let remote = IpAddr::from([2, 3, 4, 5]);
assert!(get_forwarded_for(&headers).next().is_none());
assert_eq!(
Some(IpAddr::from([2, 3, 4, 5])),
real_ip(&headers, remote, &trusted_proxies)
);
}