use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::ParserMode;
use super::check_pct_encoded;
use crate::address::parse_utils;
use crate::address::{Domain, Host, OptPort, UninterpretedHost};
use crate::byte_sets::{
is_control_byte, is_ipvfuture_tail_byte, is_reg_name_byte, is_userinfo_byte,
};
use crate::uri::lazy::LazyAuthority;
use crate::uri::{Component, ParseError};
use rama_core::bytes::Bytes;
pub(super) struct AuthorityScan {
pub(super) authority: Option<LazyAuthority>,
pub(super) path_start: usize,
}
pub(super) fn parse_optional_authority(
bytes: &Bytes,
start: usize,
mode: ParserMode,
) -> Result<AuthorityScan, ParseError> {
if let Some((auth_start, auth_end)) = find_optional_authority(bytes, start) {
let auth = parse_authority(bytes, auth_start, auth_end, mode)?;
Ok(AuthorityScan {
authority: Some(auth),
path_start: auth_end,
})
} else {
Ok(AuthorityScan {
authority: None,
path_start: start,
})
}
}
pub(super) fn find_optional_authority(bytes: &[u8], start: usize) -> Option<(usize, usize)> {
if !bytes.get(start..)?.starts_with(b"//") {
return None;
}
let authority_start = start + 2;
let authority_end = bytes[authority_start..]
.iter()
.position(|&b| matches!(b, b'/' | b'?' | b'#'))
.map_or(bytes.len(), |offset| authority_start + offset);
Some((authority_start, authority_end))
}
pub(super) fn parse_authority(
bytes: &Bytes,
start: usize,
end: usize,
mode: ParserMode,
) -> Result<LazyAuthority, ParseError> {
let scanned = scan_authority(bytes, start, end, mode)?;
let host = match scanned.host {
ScannedHost::Empty { at } => Host::Uninterpreted(UninterpretedHost::from_validated_bytes(
bytes.slice(at..at),
false,
)),
ScannedHost::Ipv6(address) => Host::Address(IpAddr::V6(address)),
ScannedHost::IpvFuture { start, end } => Host::Uninterpreted(
UninterpretedHost::from_validated_bytes(bytes.slice(start..end), true),
),
ScannedHost::RegName { start, end } => {
let host_bytes = &bytes[start..end];
let host_str = unsafe { core::str::from_utf8_unchecked(host_bytes) };
if let Ok(address) = host_str.parse::<Ipv4Addr>() {
Host::Address(IpAddr::V4(address))
} else if host_bytes.is_ascii() && Domain::try_from(host_str).is_ok() {
let domain_bytes = bytes.slice(start..end);
let domain = unsafe { Domain::from_maybe_borrowed_unchecked(domain_bytes) };
Host::Name(domain)
} else {
Host::Uninterpreted(UninterpretedHost::from_validated_bytes(
bytes.slice(start..end),
false,
))
}
}
};
Ok(LazyAuthority {
userinfo_range: scanned.userinfo_range,
host,
port: scanned.port,
})
}
struct ScannedAuthority {
userinfo_range: Option<(u16, u16)>,
host: ScannedHost,
port: OptPort,
}
enum ScannedHost {
Empty { at: usize },
Ipv6(Ipv6Addr),
IpvFuture { start: usize, end: usize },
RegName { start: usize, end: usize },
}
fn scan_authority(
bytes: &[u8],
start: usize,
end: usize,
mode: ParserMode,
) -> Result<ScannedAuthority, ParseError> {
let mut k = start;
while k < end {
let b = bytes[k];
if is_control_byte(b) {
return Err(ParseError::ControlCharInUri { at: k, byte: b });
}
if mode == ParserMode::Graceful && b >= 0x80 {
let seq_len = super::check_utf8_sequence(bytes, k)?;
k += seq_len;
continue;
}
k += 1;
}
let userinfo_range = parse_utils::find_userinfo_split(&bytes[start..end])
.map(|rel| (start as u16, (start + rel) as u16));
let host_start = userinfo_range.map_or(start, |(_, e)| (e as usize) + 1);
if let (ParserMode::Strict, Some((s, e))) = (mode, userinfo_range) {
validate_userinfo_strict(&bytes[s as usize..e as usize])?;
}
let (host, port) = scan_host_and_port(bytes, host_start, end, mode)?;
Ok(ScannedAuthority {
userinfo_range,
host,
port,
})
}
fn validate_userinfo_strict(bytes: &[u8]) -> Result<(), ParseError> {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'%' {
check_pct_encoded(bytes, i)?;
i += 3;
continue;
}
if !is_userinfo_byte(b) {
return Err(ParseError::StrictViolation);
}
i += 1;
}
Ok(())
}
fn scan_host_and_port(
bytes: &[u8],
host_start: usize,
end: usize,
mode: ParserMode,
) -> Result<(ScannedHost, OptPort), ParseError> {
let view = &bytes[host_start..end];
if view.is_empty() {
return Ok((ScannedHost::Empty { at: host_start }, OptPort::Unset));
}
if view[0] == b'[' {
let close_rel = view
.iter()
.position(|&b| b == b']')
.ok_or(ParseError::InvalidComponent(Component::Host))?;
let inside = &view[1..close_rel];
let inside_start = host_start + 1;
let host = if matches!(inside.first(), Some(b'v' | b'V')) {
validate_ipvfuture(inside)?;
ScannedHost::IpvFuture {
start: inside_start,
end: inside_start + inside.len(),
}
} else {
if parse_utils::ipv6_bracket_has_zone(inside) {
return Err(ParseError::IPv6ZoneNotSupported);
}
let Ok(s) = core::str::from_utf8(inside) else {
return Err(ParseError::InvalidComponent(Component::Host));
};
let Ok(addr) = s.parse::<Ipv6Addr>() else {
return Err(ParseError::InvalidComponent(Component::Host));
};
ScannedHost::Ipv6(addr)
};
let after = &view[close_rel + 1..];
let port = match after {
[] => OptPort::Unset,
[b':', rest @ ..] => parse_port(rest)?,
_ => return Err(ParseError::InvalidComponent(Component::Authority)),
};
return Ok((host, port));
}
let (host_bytes_rel, port) = match view.iter().rposition(|&b| b == b':') {
Some(colon) => {
let port = parse_port(&view[colon + 1..])?;
(&view[..colon], port)
}
None => (view, OptPort::Unset),
};
if host_bytes_rel.is_empty() {
return Err(ParseError::InvalidComponent(Component::Host));
}
validate_reg_name(host_bytes_rel, mode)?;
if core::str::from_utf8(host_bytes_rel).is_err() {
return Err(ParseError::InvalidComponent(Component::Host));
}
Ok((
ScannedHost::RegName {
start: host_start,
end: host_start + host_bytes_rel.len(),
},
port,
))
}
pub(crate) fn validate_reg_name_graceful(bytes: &[u8]) -> Result<(), ParseError> {
validate_reg_name(bytes, ParserMode::Graceful)
}
pub(crate) fn validate_reg_name_strict(bytes: &[u8]) -> Result<(), ParseError> {
validate_reg_name(bytes, ParserMode::Strict)
}
#[cfg(feature = "http")]
pub(super) fn validate_authority(
bytes: &[u8],
start: usize,
end: usize,
mode: ParserMode,
) -> Result<(), ParseError> {
scan_authority(bytes, start, end, mode).map(drop)
}
fn validate_reg_name(bytes: &[u8], mode: ParserMode) -> Result<(), ParseError> {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'%' {
check_pct_encoded(bytes, i)?;
if let Some(decoded) =
crate::byte_sets::pct_decoded_control_byte(bytes[i + 1], bytes[i + 2])
{
return Err(ParseError::ControlCharInUri {
at: i,
byte: decoded,
});
}
i += 3;
continue;
}
if is_reg_name_byte(b) {
i += 1;
continue;
}
if b >= 0x80 {
if mode == ParserMode::Strict {
return Err(ParseError::StrictViolation);
}
i += super::check_utf8_sequence(bytes, i)?;
continue;
}
return Err(if mode == ParserMode::Strict {
ParseError::StrictViolation
} else {
ParseError::InvalidComponent(Component::Host)
});
}
Ok(())
}
pub(crate) fn validate_ipvfuture(inside: &[u8]) -> Result<(), ParseError> {
let Some((&v, rest)) = inside.split_first() else {
return Err(ParseError::InvalidComponent(Component::Host));
};
if v != b'v' && v != b'V' {
return Err(ParseError::InvalidComponent(Component::Host));
}
let dot_at = rest
.iter()
.position(|&b| b == b'.')
.ok_or(ParseError::InvalidComponent(Component::Host))?;
if dot_at == 0 {
return Err(ParseError::InvalidComponent(Component::Host));
}
let hex = &rest[..dot_at];
let tail = &rest[dot_at + 1..];
if !hex.iter().all(|&b| b.is_ascii_hexdigit()) {
return Err(ParseError::InvalidComponent(Component::Host));
}
if tail.is_empty() || !tail.iter().all(|&b| is_ipvfuture_tail_byte(b)) {
return Err(ParseError::InvalidComponent(Component::Host));
}
Ok(())
}
fn parse_port(bytes: &[u8]) -> Result<crate::address::OptPort, ParseError> {
if bytes.is_empty() {
return Ok(crate::address::OptPort::Empty);
}
parse_utils::parse_port_bytes(bytes)
.map(crate::address::OptPort::Set)
.ok_or(ParseError::InvalidComponent(Component::Port))
}