use super::lazy::{LazyAuthority, LazyUriRef};
use super::{Component, ParseError, Uri};
use rama_core::bytes::Bytes;
pub(crate) mod authority;
mod path;
mod scheme;
#[cfg(test)]
mod tests;
pub(in crate::uri) const MAX_URI_LEN: usize = u16::MAX as usize - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ParserMode {
Graceful,
Strict,
}
pub(super) fn parse(bytes: Bytes, mode: ParserMode) -> Result<Uri, ParseError> {
if bytes.is_empty() {
return Err(ParseError::Empty);
}
if bytes.len() > MAX_URI_LEN {
return Err(ParseError::TooLong { len: bytes.len() });
}
if bytes.as_ref() == b"*" {
return Ok(Uri::from_asterisk());
}
if bytes[0] == b'/' {
let scan = path::scan_path_query_fragment(&bytes, 0, mode)?;
return Ok(Uri::from_lazy(LazyUriRef {
scheme: None,
authority: None,
path: (0, scan.path_end),
query: scan.query,
fragment: scan.fragment,
bytes,
}));
}
if let Some(uri) = try_parse_absolute(&bytes, mode)? {
return Ok(uri);
}
Err(ParseError::InvalidComponent(Component::Scheme))
}
fn try_parse_absolute(bytes: &Bytes, mode: ParserMode) -> Result<Option<Uri>, ParseError> {
let Some(colon) = scheme::find_scheme_end(bytes) else {
return Ok(None);
};
let scheme_str = bytes_to_str(&bytes[..colon]);
let Ok(scheme) = crate::Protocol::try_from(scheme_str) else {
return Err(ParseError::InvalidComponent(Component::Scheme));
};
let after_colon = colon + 1;
let auth_scan = authority::parse_optional_authority(bytes, after_colon, mode)?;
let path_start = auth_scan.path_start;
let path_scan = path::scan_path_query_fragment(bytes, path_start, mode)?;
Ok(Some(build_uri(
Some(scheme),
auth_scan.authority,
(path_start as u16, path_scan.path_end),
path_scan.query,
path_scan.fragment,
bytes.clone(),
)))
}
pub(super) fn parse_authority_form(bytes: Bytes, mode: ParserMode) -> Result<Uri, ParseError> {
if bytes.is_empty() {
return Err(ParseError::Empty);
}
if bytes.len() > MAX_URI_LEN {
return Err(ParseError::TooLong { len: bytes.len() });
}
if let Some(at) = bytes.iter().position(|&b| matches!(b, b'/' | b'?' | b'#')) {
let _ = at;
return Err(ParseError::InvalidComponent(Component::Authority));
}
let len = bytes.len();
let auth = authority::parse_authority(&bytes, 0, len, mode)?;
if matches!(mode, ParserMode::Strict) {
if auth.userinfo_range.is_some() {
return Err(ParseError::StrictViolation);
}
if !matches!(auth.port, crate::address::OptPort::Set(_)) {
return Err(ParseError::StrictViolation);
}
}
Ok(build_uri(
None,
Some(auth),
(len as u16, len as u16),
None,
None,
bytes,
))
}
pub(super) fn parse_uri_reference(bytes: Bytes, mode: ParserMode) -> Result<Uri, ParseError> {
if bytes.len() > MAX_URI_LEN {
return Err(ParseError::TooLong { len: bytes.len() });
}
if bytes.is_empty() {
return Ok(Uri::from_lazy(LazyUriRef {
scheme: None,
authority: None,
path: (0, 0),
query: None,
fragment: None,
bytes,
}));
}
if bytes.as_ref() == b"*" {
return Ok(Uri::from_asterisk());
}
if let Some(uri) = try_parse_absolute(&bytes, mode)? {
return Ok(uri);
}
let auth_scan = authority::parse_optional_authority(&bytes, 0, mode)?;
let path_start = auth_scan.path_start;
let path_scan = path::scan_path_query_fragment(&bytes, path_start, mode)?;
if matches!(mode, ParserMode::Strict)
&& auth_scan.authority.is_none()
&& path_scan.path_end as usize > path_start
&& !bytes[path_start..].starts_with(b"/")
{
let first_seg_end = bytes[path_start..path_scan.path_end as usize]
.iter()
.position(|&b| matches!(b, b'/' | b'?' | b'#'))
.map(|i| path_start + i)
.unwrap_or(path_scan.path_end as usize);
if bytes[path_start..first_seg_end].contains(&b':') {
return Err(ParseError::StrictViolation);
}
}
Ok(build_uri(
None,
auth_scan.authority,
(path_start as u16, path_scan.path_end),
path_scan.query,
path_scan.fragment,
bytes,
))
}
fn build_uri(
scheme: Option<crate::Protocol>,
authority: Option<LazyAuthority>,
path: (u16, u16),
query: Option<(u16, u16)>,
fragment: Option<(u16, u16)>,
bytes: Bytes,
) -> Uri {
Uri::from_lazy(LazyUriRef {
scheme,
authority,
path,
query,
fragment,
bytes,
})
}
pub(super) fn bytes_to_str(bytes: &[u8]) -> &str {
unsafe { core::str::from_utf8_unchecked(bytes) }
}
#[inline]
pub(super) fn check_utf8_sequence(bytes: &[u8], i: usize) -> Result<usize, ParseError> {
let b1 = bytes[i];
let len: usize = match b1 {
0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => return Err(ParseError::NonUtf8 { at: i }),
};
let Some(seq) = bytes.get(i..i + len) else {
return Err(ParseError::NonUtf8 { at: i });
};
let b2 = seq[1];
let b2_ok = match b1 {
0xE0 => (0xA0..=0xBF).contains(&b2),
0xED => (0x80..=0x9F).contains(&b2),
0xF0 => (0x90..=0xBF).contains(&b2),
0xF4 => (0x80..=0x8F).contains(&b2),
_ => (b2 & 0xC0) == 0x80,
};
if !b2_ok {
return Err(ParseError::NonUtf8 { at: i + 1 });
}
for (k, &b) in seq[2..].iter().enumerate() {
if (b & 0xC0) != 0x80 {
return Err(ParseError::NonUtf8 { at: i + 2 + k });
}
}
Ok(len)
}
pub(super) fn check_pct_encoded(bytes: &[u8], i: usize) -> Result<(), ParseError> {
let h1 = bytes.get(i + 1).copied();
let h2 = bytes.get(i + 2).copied();
match (h1, h2) {
(Some(a), Some(b)) if a.is_ascii_hexdigit() && b.is_ascii_hexdigit() => Ok(()),
_ => Err(ParseError::InvalidPercentEncoding { at: i }),
}
}