Skip to content

Commit 4d65eaf

Browse files
committed
address three bugs in forwarded header parsing
1 parent affaf3f commit 4d65eaf

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
@@ -12,7 +12,7 @@ const X_FORWARDED_HOST: HeaderName = HeaderName::from_lowercase_str("x-forwarded
1212

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

211-
if starts_with_ignore_case("for=", input) {
212-
input = forwarded.parse_for(input)?;
213-
}
214-
215211
while !input.is_empty() {
216-
input = forwarded.parse_forwarded_pair(input)?;
212+
input = if starts_with_ignore_case("for=", input) {
213+
forwarded.parse_for(input)?
214+
} else {
215+
forwarded.parse_forwarded_pair(input)?
216+
}
217217
}
218218

219219
Ok(forwarded)
@@ -429,8 +429,12 @@ fn match_ignore_case<'a>(start: &'static str, input: &'a str) -> (bool, &'a str)
429429
}
430430

431431
fn starts_with_ignore_case(start: &'static str, input: &str) -> bool {
432-
let len = start.len();
433-
input[..len].eq_ignore_ascii_case(start)
432+
if start.len() <= input.len() {
433+
let len = start.len();
434+
input[..len].eq_ignore_ascii_case(start)
435+
} else {
436+
false
437+
}
434438
}
435439

436440
impl std::fmt::Display for Forwarded<'_> {
@@ -467,6 +471,11 @@ mod tests {
467471
use crate::{Method::Get, Request, Response, Result};
468472
use url::Url;
469473

474+
#[test]
475+
fn starts_with_ignore_case_can_handle_short_inputs() {
476+
assert!(!starts_with_ignore_case("helloooooo", "h"));
477+
}
478+
470479
#[test]
471480
fn parsing_for() -> Result<()> {
472481
assert_eq!(
@@ -641,4 +650,19 @@ mod tests {
641650
assert_eq!(forwarded.by(), Some("by"));
642651
Ok(())
643652
}
653+
654+
#[test]
655+
fn round_trip() -> Result<()> {
656+
let inputs = [
657+
"for=client,for=b,for=c;by=proxy.com;host=example.com;proto=https",
658+
"by=proxy.com;proto=https;host=example.com;for=a,for=b",
659+
];
660+
for input in inputs {
661+
let forwarded = Forwarded::parse(input).map_err(|_| crate::Error::new_adhoc(input))?;
662+
let header = forwarded.header_value();
663+
let parsed = Forwarded::parse(header.as_str())?;
664+
assert_eq!(forwarded, parsed);
665+
}
666+
Ok(())
667+
}
644668
}

0 commit comments

Comments
 (0)