use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use super::scan::find_first_encode;
pub(crate) trait AppendBuf {
fn push(&mut self, c: char);
fn push_str(&mut self, s: &str);
}
impl AppendBuf for String {
#[inline]
fn push(&mut self, c: char) {
String::push(self, c);
}
#[inline]
fn push_str(&mut self, s: &str) {
String::push_str(self, s);
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn is_c0_control_or_space(c: u8) -> bool {
c <= 0x20
}
#[inline]
pub(crate) fn in_c0_encode_set(c: u8) -> bool {
c <= 0x1f || c > 0x7e
}
#[inline]
pub(crate) fn in_fragment_encode_set(c: u8) -> bool {
in_c0_encode_set(c) || matches!(c, b' ' | b'"' | b'<' | b'>' | b'`')
}
#[inline]
pub(crate) fn in_query_encode_set(c: u8) -> bool {
in_c0_encode_set(c) || matches!(c, b' ' | b'"' | b'#' | b'<' | b'>')
}
#[inline]
pub(crate) fn in_special_query_encode_set(c: u8) -> bool {
in_query_encode_set(c) || c == b'\''
}
#[inline]
pub(crate) fn in_path_encode_set(c: u8) -> bool {
in_query_encode_set(c) || matches!(c, b'?' | b'^' | b'`' | b'{' | b'}')
}
#[inline]
pub(crate) fn in_userinfo_encode_set(c: u8) -> bool {
in_path_encode_set(c)
|| matches!(
c,
b'/' | b':' | b';' | b'=' | b'@' | b'[' | b'\\' | b']' | b'|'
)
}
#[inline]
pub(crate) fn in_path_segment_encode_set(c: u8) -> bool {
in_path_encode_set(c) || c == b'/' || c == b'%'
}
#[inline]
pub(crate) fn in_special_path_segment_encode_set(c: u8) -> bool {
in_path_segment_encode_set(c) || c == b'\\'
}
#[inline]
fn append_percent(out: &mut impl AppendBuf, byte: u8) {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
out.push('%');
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0xf) as usize] as char);
}
pub(crate) fn utf8_percent_encode(
input: &str,
encode: impl Fn(u8) -> bool,
out: &mut impl AppendBuf,
) {
let bytes = input.as_bytes();
let Some(first) = find_first_encode(bytes, &encode) else {
out.push_str(input);
return;
};
debug_assert!(input.is_char_boundary(first));
if first > 0 {
out.push_str(&input[..first]);
}
let mut i = first;
while i < bytes.len() {
let start = i;
while i < bytes.len() && !encode(bytes[i]) {
i += 1;
}
if i > start {
debug_assert!(input.is_char_boundary(start) && input.is_char_boundary(i));
out.push_str(&input[start..i]);
}
while i < bytes.len() && encode(bytes[i]) {
append_percent(out, bytes[i]);
i += 1;
}
}
}
pub(crate) fn percent_encode_char(c: char, encode: impl Fn(u8) -> bool, out: &mut impl AppendBuf) {
let mut buf = [0u8; 4];
let encoded = c.encode_utf8(&mut buf);
if encoded.bytes().any(|b| encode(b)) {
for &b in encoded.as_bytes() {
append_percent(out, b);
}
} else {
out.push_str(encoded);
}
}
#[inline]
pub(crate) fn append_path_segment(segment: &str, out: &mut impl AppendBuf) {
utf8_percent_encode(segment, in_path_encode_set, out);
}
#[inline]
pub(crate) fn append_query(segment: &str, special: bool, out: &mut impl AppendBuf) {
if special {
utf8_percent_encode(segment, in_special_query_encode_set, out);
} else {
utf8_percent_encode(segment, in_query_encode_set, out);
}
}
#[inline]
pub(crate) fn append_fragment(segment: &str, out: &mut impl AppendBuf) {
utf8_percent_encode(segment, in_fragment_encode_set, out);
}
pub(crate) fn percent_decode(input: &[u8]) -> Cow<'_, [u8]> {
if memchr::memchr(b'%', input).is_none() {
return Cow::Borrowed(input);
}
let mut out = Vec::with_capacity(input.len());
let mut i = 0;
while i < input.len() {
if input[i] == b'%' && i + 2 < input.len() {
if let (Some(h), Some(l)) = (from_hex(input[i + 1]), from_hex(input[i + 2])) {
out.push((h << 4) | l);
i += 3;
continue;
}
}
out.push(input[i]);
i += 1;
}
Cow::Owned(out)
}
#[inline]
fn from_hex(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}