Skip to content

Commit 7e711f5

Browse files
committed
address three bugs in forwarded header parsing (backported to 2.x)
1 parent 816725a commit 7e711f5

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

src/parse_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) {
55
let mut end_of_token = 0;
66
for (i, c) in input.char_indices() {
77
if tchar(c) {
8-
end_of_token = i;
8+
end_of_token = i + 1;
99
} else {
1010
break;
1111
}
@@ -14,7 +14,7 @@ pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) {
1414
if end_of_token == 0 {
1515
(None, input)
1616
} else {
17-
(Some(&input[..end_of_token + 1]), &input[end_of_token + 1..])
17+
(Some(&input[..end_of_token]), &input[end_of_token..])
1818
}
1919
}
2020

@@ -125,7 +125,7 @@ mod test {
125125
assert_eq!(parse_token("key=value"), (Some("key"), "=value"));
126126
assert_eq!(parse_token("KEY=value"), (Some("KEY"), "=value"));
127127
assert_eq!(parse_token("0123)=value"), (Some("0123"), ")=value"));
128-
128+
assert_eq!(parse_token("a=b"), (Some("a"), "=b"));
129129
assert_eq!(
130130
parse_token("!#$%&'*+-.^_`|~=value"),
131131
(Some("!#$%&'*+-.^_`|~"), "=value",)

src/proxies/forwarded.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const X_FORWARDED_BY: HeaderName = HeaderName::from_lowercase_str("x-forwarded-b
1111

1212
/// A rust representation of the [forwarded
1313
/// header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded).
14-
#[derive(Debug, Clone, Default)]
14+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
1515
pub struct Forwarded<'a> {
1616
by: Option<Cow<'a, str>>,
1717
forwarded_for: Vec<Cow<'a, str>>,
@@ -197,12 +197,12 @@ impl<'a> Forwarded<'a> {
197197
let mut input = input;
198198
let mut forwarded = Forwarded::new();
199199

200-
if starts_with_ignore_case("for=", input) {
201-
input = forwarded.parse_for(input)?;
202-
}
203-
204200
while !input.is_empty() {
205-
input = forwarded.parse_forwarded_pair(input)?;
201+
input = if starts_with_ignore_case("for=", input) {
202+
forwarded.parse_for(input)?
203+
} else {
204+
forwarded.parse_forwarded_pair(input)?
205+
}
206206
}
207207

208208
Ok(forwarded)
@@ -442,8 +442,12 @@ fn match_ignore_case<'a>(start: &'static str, input: &'a str) -> (bool, &'a str)
442442
}
443443

444444
fn starts_with_ignore_case(start: &'static str, input: &str) -> bool {
445-
let len = start.len();
446-
input[..len].eq_ignore_ascii_case(start)
445+
if start.len() <= input.len() {
446+
let len = start.len();
447+
input[..len].eq_ignore_ascii_case(start)
448+
} else {
449+
false
450+
}
447451
}
448452

449453
impl std::fmt::Display for Forwarded<'_> {
@@ -494,6 +498,11 @@ mod tests {
494498
use crate::{Method::Get, Request, Response, Result};
495499
use url::Url;
496500

501+
#[test]
502+
fn starts_with_ignore_case_can_handle_short_inputs() {
503+
assert!(!starts_with_ignore_case("helloooooo", "h"));
504+
}
505+
497506
#[test]
498507
fn parsing_for() -> Result<()> {
499508
assert_eq!(
@@ -667,4 +676,19 @@ mod tests {
667676
assert_eq!(forwarded.by(), Some("by"));
668677
Ok(())
669678
}
679+
680+
#[test]
681+
fn round_trip() -> Result<()> {
682+
let inputs = [
683+
"for=client,for=b,for=c;by=proxy.com;host=example.com;proto=https",
684+
"by=proxy.com;proto=https;host=example.com;for=a,for=b",
685+
];
686+
for input in inputs {
687+
let forwarded = Forwarded::parse(input).map_err(|_| crate::Error::new_adhoc(input))?;
688+
let header = forwarded.to_header_values()?.next().unwrap();
689+
let parsed = Forwarded::parse(header.as_str())?;
690+
assert_eq!(forwarded, parsed);
691+
}
692+
Ok(())
693+
}
670694
}

0 commit comments

Comments
 (0)