Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 46 additions & 27 deletions src/uri/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ impl Authority {
let mut colon_cnt = 0;
let mut start_bracket = false;
let mut end_bracket = false;
let mut has_percent = false;
let mut end = s.len();
let mut at_sign_pos = None;
let mut percent_encoded_left = 0_u8;

// Among other things, this loop checks that every byte in s up to the
// first '/', '?', or '#' is a valid URI character (or in some contexts,
Expand All @@ -90,7 +90,7 @@ impl Authority {
colon_cnt += 1;
}
b'[' => {
if has_percent || start_bracket {
if start_bracket {
// Something other than the userinfo has a `%`, so reject it.

@robjtede robjtede Apr 30, 2022

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment is not accurate any more

return Err(ErrorKind::InvalidAuthority.into());
}
Expand All @@ -104,33 +104,34 @@ impl Authority {

// Those were part of an IPv6 hostname, so forget them...
colon_cnt = 0;
has_percent = false;
}
b'@' => {
at_sign_pos = Some(i);

// Those weren't a port colon, but part of the
// userinfo, so it needs to be forgotten.
colon_cnt = 0;
has_percent = false;
}
0 if b == b'%' => {
// Per https://tools.ietf.org/html/rfc3986#section-3.2.1 and
// https://url.spec.whatwg.org/#authority-state
// the userinfo can have a percent-encoded username and password,
// so record that a `%` was found. If this turns out to be
// part of the userinfo, this flag will be cleared.
// Also per https://tools.ietf.org/html/rfc6874, percent-encoding can
// be used to indicate a zone identifier.
// If the flag hasn't been cleared at the end, that means this
// was part of the hostname (and not part of an IPv6 address), and
// will fail with an error.
has_percent = true;
b'%' => {
if percent_encoded_left > 0 {
return Err(ErrorKind::InvalidAuthority.into())
}
// the next two characters should be alphanumerics
percent_encoded_left = 2
}
b'a'..=b'f' | b'A'..=b'F' | b'0'..=b'9' => {
percent_encoded_left = percent_encoded_left.saturating_sub(1);
}
0 => {
return Err(ErrorKind::InvalidUriChar.into());
}
_ => {}
_ => {
// if we are in percent-encoding we shouldn't any other charcter than
// hexdigits
if percent_encoded_left > 0 {
return Err(ErrorKind::InvalidAuthority.into())
}
}
}
}

Expand All @@ -148,8 +149,8 @@ impl Authority {
return Err(ErrorKind::InvalidAuthority.into());
}

if has_percent {
// Something after the userinfo has a `%`, so reject it.
if percent_encoded_left > 0 {
// The percent-encoding is not finished
return Err(ErrorKind::InvalidAuthority.into());
}

Expand Down Expand Up @@ -629,12 +630,18 @@ mod tests {
}

#[test]
fn rejects_percent_in_hostname() {
let err = Authority::parse_non_empty(b"example%2f.com").unwrap_err();
assert_eq!(err.0, ErrorKind::InvalidAuthority);
fn allow_percent_encoding_in_hostname() {
let authority_str = "example%2f.com";
let authority: Authority = authority_str.parse().unwrap();
assert_eq!(authority, authority_str);

let err = Authority::parse_non_empty(b"a%2f:b%2f@example%2f.com").unwrap_err();
assert_eq!(err.0, ErrorKind::InvalidAuthority);
let authority_str = "a%2f:b%2f@example%2f.com";
let authority: Authority = authority_str.parse().unwrap();
assert_eq!(authority, authority_str);

let authority_str = "a%2f:b%2f@example%2F.com";
let authority: Authority = authority_str.parse().unwrap();
assert_eq!(authority, authority_str);
}

#[test]
Expand All @@ -645,11 +652,23 @@ mod tests {
}

#[test]
fn rejects_percent_outside_ipv6_address() {
let err = Authority::parse_non_empty(b"1234%20[fe80::1:2:3:4]").unwrap_err();
fn rejects_invalid_percent_in_ipv6_address() {
let authority_str = "[fe80::1:2:3:4%%25eth0]";
let err = authority_str.parse::<Authority>().unwrap_err();
assert_eq!(err.0, ErrorKind::InvalidAuthority);
}

let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err();
#[test]
fn reject_invalid_percent_encoding_too_short_in_hostname() {
let authority_str = "example%2.com";
let err = authority_str.parse::<Authority>().unwrap_err();
assert_eq!(err.0, ErrorKind::InvalidAuthority);
}

#[test]
fn reject_invalid_percent_encoding_in_hostname() {
let authority_str = "example%1z.com";
let err = authority_str.parse::<Authority>().unwrap_err();
assert_eq!(err.0, ErrorKind::InvalidAuthority);
}

Expand Down
2 changes: 1 addition & 1 deletion src/uri/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const URI_CHARS: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
0, 0, 0, b'!', 0, b'#', b'$', 0, b'&', b'\'', // 3x
0, 0, 0, b'!', 0, b'#', b'$', b'%', b'&', b'\'', // 3x
b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', // 4x
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', // 5x
0, b'=', 0, b'?', b'@', b'A', b'B', b'C', b'D', b'E', // 6x
Expand Down