use std::{
borrow::Cow,
io::{self, Write},
str::from_utf8_unchecked,
};
pub use percent_encoding::CONTROLS;
pub use percent_encoding::NON_ALPHANUMERIC;
use crate::percent_encoding::{utf8_percent_encode, AsciiSet};
pub const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
pub const QUERY: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>');
pub const SPECIAL_QUERY: &AsciiSet = &QUERY.add(b'\'');
pub const PATH: &AsciiSet = &QUERY.add(b'?').add(b'^').add(b'`').add(b'{').add(b'}');
pub const USERINFO: &AsciiSet = &PATH
.add(b'/')
.add(b':')
.add(b';')
.add(b'=')
.add(b'@')
.add(b'[')
.add(b'\\')
.add(b']')
.add(b'|');
pub const COMPONENT: &AsciiSet = &USERINFO.add(b'$').add(b'%').add(b'&').add(b'+').add(b',');
pub const X_WWW_FORM_URLENCODED: &AsciiSet =
&COMPONENT.add(b'!').add(b'\'').add(b'(').add(b')').add(b'~');
#[inline]
pub fn encode<'a, S: ?Sized + AsRef<str>>(
text: &'a S,
ascii_set: &'static AsciiSet,
) -> Cow<'a, str> {
Cow::from(utf8_percent_encode(text.as_ref(), ascii_set))
}
#[inline]
pub fn encode_to_string<'a, S: AsRef<str>>(
text: S,
ascii_set: &'static AsciiSet,
output: &'a mut String,
) -> &'a str {
unsafe { from_utf8_unchecked(encode_to_vec(text, ascii_set, output.as_mut_vec())) }
}
pub fn encode_to_vec<'a, S: AsRef<str>>(
text: S,
ascii_set: &'static AsciiSet,
output: &'a mut Vec<u8>,
) -> &'a [u8] {
let text = text.as_ref();
let text_bytes = text.as_bytes();
let text_length = text_bytes.len();
output.reserve(text_length);
let current_length = output.len();
let pe = utf8_percent_encode(text, ascii_set);
for s in pe {
output.extend_from_slice(s.as_bytes());
}
&output[current_length..]
}
#[inline]
pub fn encode_to_writer<S: AsRef<str>, W: Write>(
text: S,
ascii_set: &'static AsciiSet,
output: &mut W,
) -> Result<(), io::Error> {
let pe = utf8_percent_encode(text.as_ref(), ascii_set);
for s in pe {
output.write_all(s.as_bytes())?;
}
Ok(())
}
macro_rules! encode_impl {
($(#[$attr: meta])* $escape_set:ident; $(#[$encode_attr: meta])* $encode_name: ident; $(#[$encode_to_string_attr: meta])* $encode_to_string_name: ident; $(#[$encode_to_vec_attr: meta])* $encode_to_vec_name: ident; $(#[$encode_to_writer_attr: meta])* $encode_to_writer_name: ident $(;)*) => {
$(#[$encode_attr])*
$(#[$attr])*
#[inline]
pub fn $encode_name<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
encode(text, $escape_set)
}
$(#[$encode_to_string_attr])*
$(#[$attr])*
#[inline]
pub fn $encode_to_string_name<S: AsRef<str>>(text: S, output: &mut String) -> &str {
encode_to_string(text, $escape_set, output)
}
$(#[$encode_to_vec_attr])*
$(#[$attr])*
#[inline]
pub fn $encode_to_vec_name<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
encode_to_vec(text, $escape_set, output)
}
$(#[$encode_to_writer_attr])*
$(#[$attr])*
#[inline]
pub fn $encode_to_writer_name<S: AsRef<str>, W: Write>(text: S, output: &mut W) -> Result<(), io::Error> {
encode_to_writer(text, $escape_set, output)
}
};
}
encode_impl! {
FRAGMENT;
encode_fragment;
encode_fragment_to_string;
encode_fragment_to_vec;
encode_fragment_to_writer;
}
encode_impl! {
QUERY;
encode_query;
encode_query_to_string;
encode_query_to_vec;
encode_query_to_writer;
}
encode_impl! {
SPECIAL_QUERY;
encode_special_query;
encode_special_query_to_string;
encode_special_query_to_vec;
encode_special_query_to_writer;
}
encode_impl! {
PATH;
encode_path;
encode_path_to_string;
encode_path_to_vec;
encode_path_to_writer;
}
encode_impl! {
USERINFO;
encode_userinfo;
encode_userinfo_to_string;
encode_userinfo_to_vec;
encode_userinfo_to_writer;
}
encode_impl! {
COMPONENT;
encode_component;
encode_component_to_string;
encode_component_to_vec;
encode_component_to_writer;
}
#[inline]
pub fn encode_www_form_urlencoded<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
let text = text.as_ref();
if text.as_bytes().contains(&b' ') {
let mut output = String::new();
encode_www_form_urlencoded_to_string(text, &mut output);
Cow::Owned(output)
} else {
encode(text, X_WWW_FORM_URLENCODED)
}
}
#[inline]
pub fn encode_www_form_urlencoded_to_string<S: AsRef<str>>(text: S, output: &mut String) -> &str {
unsafe { from_utf8_unchecked(encode_www_form_urlencoded_to_vec(text, output.as_mut_vec())) }
}
pub fn encode_www_form_urlencoded_to_vec<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
let text = text.as_ref();
output.reserve(text.len());
let current_length = output.len();
for (index, part) in text.split(' ').enumerate() {
if index > 0 {
output.push(b'+');
}
let pe = utf8_percent_encode(part, X_WWW_FORM_URLENCODED);
for s in pe {
output.extend_from_slice(s.as_bytes());
}
}
&output[current_length..]
}
#[inline]
pub fn encode_www_form_urlencoded_to_writer<S: AsRef<str>, W: Write>(
text: S,
output: &mut W,
) -> Result<(), io::Error> {
for (index, part) in text.as_ref().split(' ').enumerate() {
if index > 0 {
output.write_all(b"+")?;
}
let pe = utf8_percent_encode(part, X_WWW_FORM_URLENCODED);
for s in pe {
output.write_all(s.as_bytes())?;
}
}
Ok(())
}