use std::borrow::Cow;
#[must_use]
pub unsafe fn encode(data_str: Cow<'_, str>, should_encode: impl Fn(u8) -> bool) -> Cow<'_, str> {
let data = data_str.as_bytes();
let mut escaped = String::new();
let _ = escaped.try_reserve(data.len() | 15);
let unmodified = encode_into(data, should_encode, |s| {
escaped.push_str(s);
});
if unmodified {
return data_str;
}
Cow::Owned(escaped)
}
fn encode_into(
mut data: &[u8],
should_encode: impl Fn(u8) -> bool,
mut push_str: impl FnMut(&str),
) -> bool {
let mut pushed = false;
loop {
let ascii_len = data.iter().take_while(|&&c| !should_encode(c)).count();
let (safe, rest) = if ascii_len >= data.len() {
if !pushed {
return true;
}
(data, &[][..]) } else {
data.split_at(ascii_len)
};
pushed = true;
if !safe.is_empty() {
push_str(unsafe { str::from_utf8_unchecked(safe) });
}
if rest.is_empty() {
break;
}
match rest.split_first() {
Some((byte, rest)) => {
let enc = &[b'%', to_hex_digit(byte >> 4), to_hex_digit(byte & 15)];
push_str(unsafe { str::from_utf8_unchecked(enc) });
data = rest;
}
None => break,
}
}
false
}
#[inline]
fn to_hex_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
10..=255 => b'A' - 10 + digit,
}
}
const URI_ALWAYS_UNESCAPED: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.!~*'()";
pub fn is_uri_always_unescaped(c: u8) -> bool {
URI_ALWAYS_UNESCAPED.contains(&c)
}