Skip to content

Commit c8e1d51

Browse files
committed
Rewrite Forwarded header module
The previous implementation of `Forwarded` header parsing didn't implement anything closely resembling the specification of the `Forwarded` header in RFC 7239 and would fail on almost any real-world `Forwarded` header with more than one proxy, or with the parameters in a different order from what it expected. This commit tries to implement the specification as accurately as possible. Signed-off-by: Johannes Löthberg <johannes@kyriasis.com>
1 parent 1fe07df commit c8e1d51

File tree

3 files changed

+900
-558
lines changed

3 files changed

+900
-558
lines changed

src/parse_utils.rs

+34-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::borrow::Cow;
22

3-
/// https://tools.ietf.org/html/rfc7230#section-3.2.6
4-
pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) {
3+
/// <https://tools.ietf.org/html/rfc7230#section-3.2.6>
4+
pub(crate) fn parse_token(input: &str) -> Option<(Cow<'_, str>, &str)> {
55
let mut end_of_token = 0;
66
for (i, c) in input.char_indices() {
77
if tchar(c) {
@@ -12,14 +12,15 @@ pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) {
1212
}
1313

1414
if end_of_token == 0 {
15-
(None, input)
15+
None
1616
} else {
17-
(Some(&input[..end_of_token]), &input[end_of_token..])
17+
let (token, rest) = input.split_at(end_of_token);
18+
Some((Cow::from(token), rest))
1819
}
1920
}
2021

21-
/// https://tools.ietf.org/html/rfc7230#section-3.2.6
22-
fn tchar(c: char) -> bool {
22+
/// <https://tools.ietf.org/html/rfc7230#section-3.2.6>
23+
pub(crate) fn tchar(c: char) -> bool {
2324
matches!(
2425
c, 'a'..='z'
2526
| 'A'..='Z'
@@ -42,16 +43,16 @@ fn tchar(c: char) -> bool {
4243
)
4344
}
4445

45-
/// https://tools.ietf.org/html/rfc7230#section-3.2.6
46+
/// <https://tools.ietf.org/html/rfc7230#section-3.2.6>
4647
fn vchar(c: char) -> bool {
4748
matches!(c as u8, b'\t' | 32..=126 | 128..=255)
4849
}
4950

50-
/// https://tools.ietf.org/html/rfc7230#section-3.2.6
51-
pub(crate) fn parse_quoted_string(input: &str) -> (Option<Cow<'_, str>>, &str) {
51+
/// <https://tools.ietf.org/html/rfc7230#section-3.2.6>
52+
pub(crate) fn parse_quoted_string(input: &str) -> Option<(Cow<'_, str>, &str)> {
5253
// quoted-string must start with a DQUOTE
5354
if !input.starts_with('"') {
54-
return (None, input);
55+
return None;
5556
}
5657

5758
let mut end_of_string = None;
@@ -61,7 +62,7 @@ pub(crate) fn parse_quoted_string(input: &str) -> (Option<Cow<'_, str>>, &str) {
6162
if i > 1 && backslashes.last() == Some(&(i - 2)) {
6263
if !vchar(c) {
6364
// only VCHARs can be escaped
64-
return (None, input);
65+
return None;
6566
}
6667
// otherwise, we skip over this character while parsing
6768
} else {
@@ -81,7 +82,7 @@ pub(crate) fn parse_quoted_string(input: &str) -> (Option<Cow<'_, str>>, &str) {
8182
b'\t' | b' ' | 15 | 35..=91 | 93..=126 | 128..=255 => {}
8283

8384
// unexpected character, bail
84-
_ => return (None, input),
85+
_ => return None,
8586
}
8687
}
8788
}
@@ -110,10 +111,10 @@ pub(crate) fn parse_quoted_string(input: &str) -> (Option<Cow<'_, str>>, &str) {
110111
.into()
111112
};
112113

113-
(Some(value), &input[end_of_string..])
114+
Some((value, &input[end_of_string..]))
114115
} else {
115116
// we never reached a closing DQUOTE, so we do not have a valid quoted-string
116-
(None, input)
117+
None
117118
}
118119
}
119120

@@ -122,51 +123,51 @@ mod test {
122123
use super::*;
123124
#[test]
124125
fn token_successful_parses() {
125-
assert_eq!(parse_token("key=value"), (Some("key"), "=value"));
126-
assert_eq!(parse_token("KEY=value"), (Some("KEY"), "=value"));
127-
assert_eq!(parse_token("0123)=value"), (Some("0123"), ")=value"));
128-
assert_eq!(parse_token("a=b"), (Some("a"), "=b"));
126+
assert_eq!(parse_token("key=value"), Some(("key".into(), "=value")));
127+
assert_eq!(parse_token("KEY=value"), Some(("KEY".into(), "=value")));
128+
assert_eq!(parse_token("0123)=value"), Some(("0123".into(), ")=value")));
129+
assert_eq!(parse_token("a=b"), Some(("a".into(), "=b")));
129130
assert_eq!(
130131
parse_token("!#$%&'*+-.^_`|~=value"),
131-
(Some("!#$%&'*+-.^_`|~"), "=value",)
132+
Some(("!#$%&'*+-.^_`|~".into(), "=value"))
132133
);
133134
}
134135

135136
#[test]
136137
fn token_unsuccessful_parses() {
137-
assert_eq!(parse_token(""), (None, ""));
138-
assert_eq!(parse_token("=value"), (None, "=value"));
138+
assert_eq!(parse_token(""), None);
139+
assert_eq!(parse_token("=value"), None);
139140
for c in r#"(),/:;<=>?@[\]{}"#.chars() {
140141
let s = c.to_string();
141-
assert_eq!(parse_token(&s), (None, &*s));
142+
assert_eq!(parse_token(&s), None);
142143

143144
let s = format!("match{}rest", s);
144-
assert_eq!(parse_token(&s), (Some("match"), &*format!("{}rest", c)));
145+
assert_eq!(
146+
parse_token(&s),
147+
Some(("match".into(), &*format!("{}rest", c)))
148+
);
145149
}
146150
}
147151

148152
#[test]
149153
fn qstring_successful_parses() {
150154
assert_eq!(
151155
parse_quoted_string(r#""key"=value"#),
152-
(Some(Cow::Borrowed("key")), "=value")
156+
Some((Cow::Borrowed("key"), "=value"))
153157
);
154158

155159
assert_eq!(
156160
parse_quoted_string(r#""escaped \" quote \""rest"#),
157-
(
158-
Some(Cow::Owned(String::from(r#"escaped " quote ""#))),
159-
r#"rest"#
160-
)
161+
Some((Cow::Owned(String::from(r#"escaped " quote ""#)), r#"rest"#))
161162
);
162163
}
163164

164165
#[test]
165166
fn qstring_unsuccessful_parses() {
166-
assert_eq!(parse_quoted_string(r#""abc"#), (None, "\"abc"));
167-
assert_eq!(parse_quoted_string(r#"hello""#), (None, "hello\"",));
168-
assert_eq!(parse_quoted_string(r#"=value\"#), (None, "=value\\"));
169-
assert_eq!(parse_quoted_string(r#"\""#), (None, r#"\""#));
170-
assert_eq!(parse_quoted_string(r#""\""#), (None, r#""\""#));
167+
assert_eq!(parse_quoted_string(r#""abc"#), None);
168+
assert_eq!(parse_quoted_string(r#"hello""#), None);
169+
assert_eq!(parse_quoted_string(r#"=value\"#), None);
170+
assert_eq!(parse_quoted_string(r#"\""#), None);
171+
assert_eq!(parse_quoted_string(r#""\""#), None);
171172
}
172173
}

0 commit comments

Comments
 (0)