From 4d65eaf790a223ac6222b6e6e456465d4d470cc2 Mon Sep 17 00:00:00 2001 From: Jacob Rothstein Date: Fri, 30 Jul 2021 12:28:17 -0700 Subject: [PATCH] address three bugs in forwarded header parsing --- src/parse_utils.rs | 6 +++--- src/proxies/forwarded.rs | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/parse_utils.rs b/src/parse_utils.rs index 7e4674d..a2dc235 100644 --- a/src/parse_utils.rs +++ b/src/parse_utils.rs @@ -5,7 +5,7 @@ pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) { let mut end_of_token = 0; for (i, c) in input.char_indices() { if tchar(c) { - end_of_token = i; + end_of_token = i + 1; } else { break; } @@ -14,7 +14,7 @@ pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) { if end_of_token == 0 { (None, input) } else { - (Some(&input[..end_of_token + 1]), &input[end_of_token + 1..]) + (Some(&input[..end_of_token]), &input[end_of_token..]) } } @@ -125,7 +125,7 @@ mod test { assert_eq!(parse_token("key=value"), (Some("key"), "=value")); assert_eq!(parse_token("KEY=value"), (Some("KEY"), "=value")); assert_eq!(parse_token("0123)=value"), (Some("0123"), ")=value")); - + assert_eq!(parse_token("a=b"), (Some("a"), "=b")); assert_eq!( parse_token("!#$%&'*+-.^_`|~=value"), (Some("!#$%&'*+-.^_`|~"), "=value",) diff --git a/src/proxies/forwarded.rs b/src/proxies/forwarded.rs index 16a2e89..990bd0a 100644 --- a/src/proxies/forwarded.rs +++ b/src/proxies/forwarded.rs @@ -12,7 +12,7 @@ const X_FORWARDED_HOST: HeaderName = HeaderName::from_lowercase_str("x-forwarded /// A rust representation of the [forwarded /// header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded). -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Forwarded<'a> { by: Option>, forwarded_for: Vec>, @@ -208,12 +208,12 @@ impl<'a> Forwarded<'a> { let mut input = input; let mut forwarded = Forwarded::new(); - if starts_with_ignore_case("for=", input) { - input = forwarded.parse_for(input)?; - } - while !input.is_empty() { - input = forwarded.parse_forwarded_pair(input)?; + input = if starts_with_ignore_case("for=", input) { + forwarded.parse_for(input)? + } else { + forwarded.parse_forwarded_pair(input)? + } } Ok(forwarded) @@ -429,8 +429,12 @@ fn match_ignore_case<'a>(start: &'static str, input: &'a str) -> (bool, &'a str) } fn starts_with_ignore_case(start: &'static str, input: &str) -> bool { - let len = start.len(); - input[..len].eq_ignore_ascii_case(start) + if start.len() <= input.len() { + let len = start.len(); + input[..len].eq_ignore_ascii_case(start) + } else { + false + } } impl std::fmt::Display for Forwarded<'_> { @@ -467,6 +471,11 @@ mod tests { use crate::{Method::Get, Request, Response, Result}; use url::Url; + #[test] + fn starts_with_ignore_case_can_handle_short_inputs() { + assert!(!starts_with_ignore_case("helloooooo", "h")); + } + #[test] fn parsing_for() -> Result<()> { assert_eq!( @@ -641,4 +650,19 @@ mod tests { assert_eq!(forwarded.by(), Some("by")); Ok(()) } + + #[test] + fn round_trip() -> Result<()> { + let inputs = [ + "for=client,for=b,for=c;by=proxy.com;host=example.com;proto=https", + "by=proxy.com;proto=https;host=example.com;for=a,for=b", + ]; + for input in inputs { + let forwarded = Forwarded::parse(input).map_err(|_| crate::Error::new_adhoc(input))?; + let header = forwarded.header_value(); + let parsed = Forwarded::parse(header.as_str())?; + assert_eq!(forwarded, parsed); + } + Ok(()) + } }