#![cfg_attr(not(feature = "alloc"), allow(clippy::redundant_pub_crate))]
#[cfg(feature = "alloc")]
use crate::Error;
use crate::{Result, TriCow};
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::{string::String, vec::Vec};
#[derive(Copy, Clone)]
enum PctEncoded {
Nss,
RComponent,
QComponent,
FComponent,
}
fn parse(s: &mut TriCow, start: usize, kind: PctEncoded) -> Result<usize> {
let mut it = s.bytes().enumerate().skip(start).peekable();
while let Some((i, ch)) = it.next() {
#[allow(clippy::match_same_arms)]
match (kind, ch) {
(PctEncoded::FComponent, b'?') => {}
(PctEncoded::QComponent, b'?') if i != start => {}
(PctEncoded::RComponent, b'?') if i != start && it.peek().map(|x| x.1) != Some(b'=') => {}
(PctEncoded::FComponent, b'/') => {}
(_, b'/') if i != start => {}
(
_,
b'-' | b'.' | b'_' | b'~'
| b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
| b':' | b'@',
) => {}
(_, b'%') => {
let mut pct_chars = it.take(2);
if pct_chars.len() == 2 && pct_chars.all(|x| x.1.is_ascii_hexdigit()) {
s.make_uppercase(i + 1..i + 3)?;
it = s.bytes().enumerate().skip(i + 3).peekable();
} else {
return Ok(i);
}
}
(_, c) if c.is_ascii_alphanumeric() => {}
_ => return Ok(i),
}
}
Ok(s.len())
}
pub(crate) fn parse_nss(s: &mut TriCow, start: usize) -> Result<usize> {
parse(s, start, PctEncoded::Nss)
}
pub(crate) fn parse_r_component(s: &mut TriCow, start: usize) -> Result<usize> {
parse(s, start, PctEncoded::RComponent)
}
pub(crate) fn parse_q_component(s: &mut TriCow, start: usize) -> Result<usize> {
parse(s, start, PctEncoded::QComponent)
}
pub(crate) fn parse_f_component(s: &mut TriCow, start: usize) -> Result<usize> {
parse(s, start, PctEncoded::FComponent)
}
#[cfg(feature = "alloc")]
const fn parse_hex_char(ch: u8) -> u8 {
if ch.is_ascii_digit() {
ch - b'0'
} else if ch.is_ascii_lowercase() {
ch - b'a' + 0xA
} else {
ch - b'A' + 0xA
}
}
#[cfg(feature = "alloc")]
fn decode(s: &str, kind: PctEncoded) -> Option<String> {
let mut it = s.bytes().enumerate().skip(0).peekable();
let mut ret = Vec::new();
while let Some((i, ch)) = it.next() {
#[allow(clippy::match_same_arms)]
match (kind, ch) {
(PctEncoded::FComponent, b'?') => {}
(PctEncoded::QComponent, b'?') if i != 0 => {}
(PctEncoded::RComponent, b'?') if i != 0 && it.peek().map(|x| x.1) != Some(b'=') => {}
(PctEncoded::FComponent, b'/') => {}
(_, b'/') if i != 0 => {}
(
_,
b'-' | b'.' | b'_' | b'~' | b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+'
| b',' | b';' | b'=' | b':' | b'@',
) => {}
(_, b'%') => {
let mut pct_chars = it.take(2);
if pct_chars.len() == 2 && pct_chars.all(|x| x.1.is_ascii_hexdigit()) {
ret.push(
parse_hex_char(s.as_bytes()[i + 1]) * 0x10
+ parse_hex_char(s.as_bytes()[i + 2]),
);
it = s.bytes().enumerate().skip(i + 3).peekable();
continue;
}
return None;
}
(_, c) if c.is_ascii_alphanumeric() => {}
_ => return None,
}
ret.push(ch);
}
String::from_utf8(ret).ok()
}
#[cfg(feature = "alloc")]
pub fn decode_nss(s: &str) -> Result<String> {
decode(s, PctEncoded::Nss).ok_or(Error::InvalidNss)
}
#[cfg(feature = "alloc")]
pub fn decode_r_component(s: &str) -> Result<String> {
decode(s, PctEncoded::RComponent).ok_or(Error::InvalidRComponent)
}
#[cfg(feature = "alloc")]
pub fn decode_q_component(s: &str) -> Result<String> {
decode(s, PctEncoded::QComponent).ok_or(Error::InvalidQComponent)
}
#[cfg(feature = "alloc")]
pub fn decode_f_component(s: &str) -> Result<String> {
decode(s, PctEncoded::FComponent).ok_or(Error::InvalidFComponent)
}
#[cfg(feature = "alloc")]
const fn to_hex(n: u8) -> [u8; 2] {
let a = (n & 0xF0) >> 4;
let b = n & 0xF;
let a = if a < 10 { b'0' + a } else { b'A' + (a - 10) };
let b = if b < 10 { b'0' + b } else { b'A' + (b - 10) };
[a, b]
}
#[cfg(feature = "alloc")]
fn encode(s: &str, kind: PctEncoded) -> String {
let mut ret = String::with_capacity(s.len());
let mut buf = [0u8; 8];
for (i, ch) in s.chars().enumerate() {
#[allow(clippy::match_same_arms)]
match (kind, ch) {
(PctEncoded::FComponent, '?') => {}
(PctEncoded::QComponent, '?') if i != 0 => {}
(PctEncoded::RComponent, '?')
if i != 0 && !matches!(s.chars().nth(i + 1), Some('=')) => {}
(PctEncoded::FComponent, '/') => {}
(PctEncoded::RComponent | PctEncoded::QComponent, '/') if i != 0 => {}
(
PctEncoded::RComponent | PctEncoded::QComponent | PctEncoded::FComponent,
'-' | '.' | '_' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '='
| ':' | '@',
) => {}
(
PctEncoded::Nss,
'-' | '.' | '_' | '!' | '$' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | ':'
| '@',
) => {}
(_, ch) if ch.is_ascii_alphanumeric() => {}
(_, ch) => {
for byte in ch.encode_utf8(&mut buf).as_bytes() {
ret.push('%');
for digit in to_hex(*byte) {
ret.push(digit as char);
}
}
continue;
}
}
ret.push(ch);
}
ret
}
#[cfg(feature = "alloc")]
pub fn encode_nss(s: &str) -> Result<String> {
if s.is_empty() {
return Err(Error::InvalidNss)
}
Ok(encode(s, PctEncoded::Nss))
}
#[cfg(feature = "alloc")]
pub fn encode_r_component(s: &str) -> Result<String> {
if s.is_empty() {
return Err(Error::InvalidRComponent)
}
Ok(encode(s, PctEncoded::RComponent))
}
#[cfg(feature = "alloc")]
pub fn encode_q_component(s: &str) -> Result<String> {
if s.is_empty() {
return Err(Error::InvalidQComponent)
}
Ok(encode(s, PctEncoded::QComponent))
}
#[cfg(feature = "alloc")]
pub fn encode_f_component(s: &str) -> Result<String> {
Ok(encode(s, PctEncoded::FComponent))
}