use core::fmt::Display;
use core::fmt::Write;
use core::iter::FusedIterator;
#[cfg(feature = "alloc")]
use alloc::borrow::Cow;
#[cfg(feature = "alloc")]
use core::str::from_utf8_unchecked;
#[cfg(feature = "alloc")]
use alloc::string::ToString;
fn is_char_uri_unreserved(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '_' || c == '~'
}
fn is_char_uri_sub_delim(c: char) -> bool {
c == '!'
|| c == '$'
|| c == '&'
|| c == '\''
|| c == '('
|| c == ')'
|| c == '*'
|| c == '+'
|| c == ','
|| c == ';'
|| c == '='
}
fn is_char_uri_pchar(c: char) -> bool {
is_char_uri_unreserved(c) || is_char_uri_sub_delim(c) || c == ':' || c == '@'
}
fn is_char_uri_quote(c: char) -> bool {
c != '+' && (is_char_uri_pchar(c) || c == '/' || c == '?')
}
fn is_char_uri_fragment(c: char) -> bool {
is_char_uri_pchar(c) || c == '/' || c == '?' || c == '#'
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub(super) enum EscapeUriState {
Normal,
OutputHighNibble(u8),
OutputLowNibble(u8),
}
#[doc(hidden)]
pub trait NeedsEscape: Clone {
fn byte_needs_escape(b: u8) -> bool {
Self::char_needs_escape(b as char) || (b & 0x80) != 0
}
fn char_needs_escape(c: char) -> bool;
fn escape_space_as_plus() -> bool {
false
}
}
#[doc(hidden)]
#[derive(Default, Copy, Clone, Debug)]
pub struct EscapeUriFull;
impl NeedsEscape for EscapeUriFull {
fn char_needs_escape(c: char) -> bool {
!is_char_uri_unreserved(c)
}
}
#[doc(hidden)]
#[derive(Default, Copy, Clone, Debug)]
pub struct EscapeUriSegment;
impl NeedsEscape for EscapeUriSegment {
fn char_needs_escape(c: char) -> bool {
!is_char_uri_pchar(c)
}
}
#[doc(hidden)]
#[derive(Default, Copy, Clone, Debug)]
pub struct EscapeUriAuthority;
impl NeedsEscape for EscapeUriAuthority {
fn char_needs_escape(c: char) -> bool {
!is_char_uri_pchar(c) && c != '[' && c != ']'
}
}
#[doc(hidden)]
#[derive(Default, Copy, Clone, Debug)]
pub struct EscapeUriQuery;
impl NeedsEscape for EscapeUriQuery {
fn char_needs_escape(c: char) -> bool {
!is_char_uri_quote(c)
}
fn escape_space_as_plus() -> bool {
true
}
}
#[doc(hidden)]
#[derive(Default, Copy, Clone, Debug)]
pub struct EscapeUriFragment;
impl NeedsEscape for EscapeUriFragment {
fn char_needs_escape(c: char) -> bool {
!is_char_uri_fragment(c)
}
}
#[derive(Debug, Clone)]
pub struct EscapeUri<'a, X: NeedsEscape = EscapeUriSegment> {
pub(super) iter: core::slice::Iter<'a, u8>,
pub(super) state: EscapeUriState,
pub(super) _needs_escape: X,
}
#[cfg(feature = "alloc")]
impl<'a, X: NeedsEscape> From<EscapeUri<'a, X>> for Cow<'a, str> {
fn from(iter: EscapeUri<'a, X>) -> Self {
iter.to_cow()
}
}
impl<'a, X: NeedsEscape> EscapeUri<'a, X> {
pub fn is_needed(&self) -> bool {
for &b in self.iter.clone() {
if X::byte_needs_escape(b) {
return true;
}
}
false
}
#[cfg(feature = "alloc")]
pub fn to_cow(&self) -> Cow<'a, str> {
if self.is_needed() {
Cow::from(self.to_string())
} else {
Cow::from(unsafe { from_utf8_unchecked(self.iter.as_slice()) })
}
}
pub fn full(self) -> EscapeUri<'a, EscapeUriFull> {
EscapeUri {
iter: self.iter,
state: self.state,
_needs_escape: EscapeUriFull,
}
}
pub fn for_query(self) -> EscapeUri<'a, EscapeUriQuery> {
EscapeUri {
iter: self.iter,
state: self.state,
_needs_escape: EscapeUriQuery,
}
}
pub fn for_fragment(self) -> EscapeUri<'a, EscapeUriFragment> {
EscapeUri {
iter: self.iter,
state: self.state,
_needs_escape: EscapeUriFragment,
}
}
pub fn for_authority(self) -> EscapeUri<'a, EscapeUriAuthority> {
EscapeUri {
iter: self.iter,
state: self.state,
_needs_escape: EscapeUriAuthority,
}
}
}
impl<'a, X: NeedsEscape> Display for EscapeUri<'a, X> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.clone().try_for_each(|c| f.write_char(c))
}
}
impl<'a, X: NeedsEscape> FusedIterator for EscapeUri<'a, X> {}
impl<'a, X: NeedsEscape> Iterator for EscapeUri<'a, X> {
type Item = char;
#[inline]
fn next(&mut self) -> Option<char> {
match self.state {
EscapeUriState::Normal => match self.iter.next().copied() {
Some(b) if X::escape_space_as_plus() && b == b' ' => Some('+'),
Some(b) if X::byte_needs_escape(b) => {
self.state = EscapeUriState::OutputHighNibble(b);
Some('%')
}
Some(b) => Some(b as char),
None => None,
},
EscapeUriState::OutputHighNibble(b) => {
self.state = EscapeUriState::OutputLowNibble(b);
let nibble = b >> 4;
if nibble < 9 {
Some((b'0' + nibble) as char)
} else {
Some((b'A' + nibble - 10) as char)
}
}
EscapeUriState::OutputLowNibble(b) => {
self.state = EscapeUriState::Normal;
let nibble = b & 0b1111;
if nibble < 9 {
Some((b'0' + nibble) as char)
} else {
Some((b'A' + nibble - 10) as char)
}
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.iter.size_hint().0;
(n, Some(n * 3))
}
}