#[derive(Debug, PartialEq, Eq)]
pub enum RawTargetAuthority<'a> {
None,
Absolute {
scheme: &'a [u8],
authority: &'a [u8],
path_and_query: &'a [u8],
},
AmbiguousAuthority,
}
impl<'a> RawTargetAuthority<'a> {
pub fn authority(&self) -> Option<&'a [u8]> {
match self {
Self::None | Self::AmbiguousAuthority => None,
Self::Absolute { authority, .. } => Some(authority),
}
}
}
pub fn raw_target_authority(target: &[u8]) -> RawTargetAuthority<'_> {
if target.first() == Some(&b'/') {
return RawTargetAuthority::None;
}
let mut valid_scheme = true;
let mut scheme_end = None;
for (index, &byte) in target.iter().enumerate() {
if byte == b':' {
scheme_end = Some(index);
break;
}
valid_scheme &= if index == 0 {
byte.is_ascii_alphabetic()
} else {
byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')
};
}
let Some(scheme_end) = scheme_end else {
return RawTargetAuthority::None;
};
let scheme = &target[..scheme_end];
let remainder = &target[scheme_end + 1..];
let authority = remainder.strip_prefix(b"//");
if scheme.is_empty() || !valid_scheme {
return if authority.is_some() {
RawTargetAuthority::AmbiguousAuthority
} else {
RawTargetAuthority::None
};
}
let http_family_scheme = is_http_family_scheme(scheme);
let Some(authority) = authority else {
return if http_family_scheme {
RawTargetAuthority::AmbiguousAuthority
} else {
RawTargetAuthority::None
};
};
let mut end = authority.len();
for (index, &byte) in authority.iter().enumerate() {
if matches!(byte, b'/' | b'?' | b'#') {
end = index;
break;
}
if http_family_scheme && byte == b'\\' {
return RawTargetAuthority::AmbiguousAuthority;
}
}
if http_family_scheme && end == 0 {
return RawTargetAuthority::AmbiguousAuthority;
}
RawTargetAuthority::Absolute {
scheme,
authority: &authority[..end],
path_and_query: &authority[end..],
}
}
pub fn is_http_family_scheme(scheme: &[u8]) -> bool {
[b"http".as_slice(), b"https", b"ws", b"wss"]
.iter()
.any(|candidate| scheme.eq_ignore_ascii_case(candidate))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_raw_target_authority() {
assert_eq!(
raw_target_authority(b"http://authority.example/test"),
RawTargetAuthority::Absolute {
scheme: b"http",
authority: b"authority.example",
path_and_query: b"/test"
}
);
assert_eq!(
raw_target_authority(b"http://authority.example/test").authority(),
Some(b"authority.example".as_slice())
);
assert_eq!(
raw_target_authority(b"http://user@authority.example:8443/test?a=b"),
RawTargetAuthority::Absolute {
scheme: b"http",
authority: b"user@authority.example:8443",
path_and_query: b"/test?a=b"
}
);
assert_eq!(
raw_target_authority(b"http://authority.example"),
RawTargetAuthority::Absolute {
scheme: b"http",
authority: b"authority.example",
path_and_query: b""
}
);
assert_eq!(
raw_target_authority(b"http:/\\/\\authority.example/test"),
RawTargetAuthority::AmbiguousAuthority
);
assert_eq!(
raw_target_authority(b"https:authority.example/test"),
RawTargetAuthority::AmbiguousAuthority
);
assert_eq!(
raw_target_authority(b"http://good.example\\evil.example/"),
RawTargetAuthority::AmbiguousAuthority
);
for target in [
b"ht_tp://other.example/admin".as_slice(),
b"9x://other.example/admin",
b"http:///path",
b"https://?query",
b"ws://good.example\\evil.example/",
b"wss://good.example\\evil.example/",
] {
assert_eq!(
raw_target_authority(target),
RawTargetAuthority::AmbiguousAuthority,
"{}",
String::from_utf8_lossy(target)
);
}
assert_eq!(
raw_target_authority(b"file:///path"),
RawTargetAuthority::Absolute {
scheme: b"file",
authority: b"",
path_and_query: b"/path"
}
);
assert_eq!(
raw_target_authority(b"ftp://good.example\\evil.example/"),
RawTargetAuthority::Absolute {
scheme: b"ftp",
authority: b"good.example\\evil.example",
path_and_query: b"/"
}
);
assert_eq!(raw_target_authority(b"/test"), RawTargetAuthority::None);
assert_eq!(raw_target_authority(b":opaque"), RawTargetAuthority::None);
assert_eq!(
raw_target_authority(b"/redirect?next=http://user@evil.example/"),
RawTargetAuthority::None
);
assert_eq!(
raw_target_authority(b"/test#http://user@evil.example/"),
RawTargetAuthority::None
);
assert_eq!(
raw_target_authority(b"foo:bar://user@example/path"),
RawTargetAuthority::None
);
}
}