use alloc::string::String;
fn is_unescaped_component(b: u8) -> bool {
b.is_ascii_alphanumeric()
|| matches!(
b,
b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')'
)
}
fn is_uri_reserved(b: u8) -> bool {
matches!(
b,
b';' | b'/' | b'?' | b':' | b'@' | b'&' | b'=' | b'+' | b'$' | b',' | b'#'
)
}
fn push_percent(out: &mut String, b: u8) {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
out.push('%');
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0F) as usize] as char);
}
pub fn encode_uri(s: &str) -> String {
encode_with(s, |b| is_unescaped_component(b) || is_uri_reserved(b))
}
pub fn encode_uri_component(s: &str) -> String {
encode_with(s, is_unescaped_component)
}
fn encode_with(s: &str, keep: impl Fn(u8) -> bool) -> String {
let mut out = String::with_capacity(s.len());
for &b in s.as_bytes() {
if keep(b) {
out.push(b as char);
} else {
push_percent(&mut out, b);
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct UriMalformed;
impl core::fmt::Display for UriMalformed {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("URI malformed")
}
}
#[cfg(feature = "std")]
impl std::error::Error for UriMalformed {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UriEncoding {
Full,
Component,
}
pub fn encode_uri_units(units: &[u16], encoding: UriEncoding) -> Result<String, UriMalformed> {
let s = utf16_to_string(units)?;
Ok(match encoding {
UriEncoding::Component => encode_uri_component(&s),
UriEncoding::Full => encode_uri(&s),
})
}
fn utf16_to_string(units: &[u16]) -> Result<String, UriMalformed> {
let mut out = String::with_capacity(units.len());
for decoded in core::char::decode_utf16(units.iter().copied()) {
out.push(decoded.map_err(|_| UriMalformed)?);
}
Ok(out)
}