const HEX_CHARS: &[u8; 16] = b"0123456789ABCDEF";
#[inline(always)]
fn is_ucschar(c: &char) -> bool {
('\u{d0}'..='\u{d7ff}').contains(c)
|| ('\u{f900}'..='\u{fdcf}').contains(c)
|| ('\u{fdf0}'..='\u{ffef}').contains(c)
|| ('\u{10000}'..='\u{1fffd}').contains(c)
|| ('\u{20000}'..='\u{2fffd}').contains(c)
|| ('\u{30000}'..='\u{3fffd}').contains(c)
|| ('\u{40000}'..='\u{4fffd}').contains(c)
|| ('\u{50000}'..='\u{5fffd}').contains(c)
|| ('\u{60000}'..='\u{6fffd}').contains(c)
|| ('\u{70000}'..='\u{7fffd}').contains(c)
|| ('\u{80000}'..='\u{8fffd}').contains(c)
|| ('\u{90000}'..='\u{9fffd}').contains(c)
|| ('\u{a0000}'..='\u{afffd}').contains(c)
|| ('\u{b0000}'..='\u{bfffd}').contains(c)
|| ('\u{c0000}'..='\u{cfffd}').contains(c)
|| ('\u{d0000}'..='\u{dfffd}').contains(c)
|| ('\u{e0000}'..='\u{efffd}').contains(c)
}
fn pct_encode(c: &char, buf: &mut [u8; 12]) -> usize {
let mut temp_buf: [u8; 4] = [0; 4];
c.encode_utf8(&mut temp_buf);
let len = c.len_utf8();
let mut bnum = 0usize;
let mut offset = 0usize;
while bnum < c.len_utf8() {
buf[offset] = b'%';
buf[offset + 1] = HEX_CHARS[(temp_buf[bnum] >> 4) as usize];
buf[offset + 2] = HEX_CHARS[(temp_buf[bnum] & 15) as usize];
offset += 3;
bnum += 1;
}
len * 3
}
fn hex_decode(c: char) -> Option<u8> {
if !('0'..='f').contains(&c) {
return None;
}
let mut buf = [0u8; 1];
c.encode_utf8(&mut buf);
let c = buf[0];
Some(if c.is_ascii_digit() {
c - b'0'
} else if (b'a'..=b'f').contains(&c) {
c - b'a' + 10
} else if (b'A'..=b'F').contains(&c) {
c - b'A' + 10
} else {
return None;
})
}
fn pct_decode(a: char, b: char) -> Option<u8> {
Some((hex_decode(a)? << 4) + hex_decode(b)?)
}
fn encode_ipchar(c: &char, buf: &mut [u8; 12]) -> usize {
if c.is_alphanumeric()
|| *c == '.'
|| *c == '-'
|| *c == '_'
|| *c == '~'
|| *c == ':'
|| *c == '@'
|| *c == '!'
|| *c == '$'
|| *c == '&'
|| *c == '\''
|| *c == '('
|| *c == ')'
|| *c == '*'
|| *c == '+'
|| *c == ','
|| *c == ';'
|| *c == '='
|| is_ucschar(c)
{
c.encode_utf8(buf);
c.len_utf8()
} else {
pct_encode(c, buf)
}
}
pub fn iriencode(source: &str) -> String {
let mut buf = [0u8; 12];
let mut target = String::with_capacity(source.len() * 3);
let mut len;
for c in source.chars() {
len = encode_ipchar(&c, &mut buf);
target.push_str(str::from_utf8(&buf[0..len]).expect("input was valid UTF-8"));
}
target.shrink_to_fit();
target
}
pub fn iridecode(source: &str) -> Option<String> {
let mut target = String::with_capacity(source.len());
let mut buf = [0u8; 4];
let mut chars = source.chars();
while let Some(c) = chars.next() {
if c == '%' {
if let (Some(a), Some(b)) = (chars.next(), chars.next()) {
let byte = pct_decode(a, b)?;
buf[0] = byte;
let len = if (b'\xc2'..=b'\xdf').contains(&byte) {
2usize
} else if (b'\xe0'..=b'\xef').contains(&byte) {
3usize
} else if (b'\xf0'..=b'\xf4').contains(&byte) {
4usize
} else {
1usize
};
for byte in buf.iter_mut().take(len).skip(1) {
if let (Some('%'), Some(a), Some(b)) =
(chars.next(), chars.next(), chars.next())
{
*byte = pct_decode(a, b)?;
} else {
return None;
}
}
target.push_str(str::from_utf8(&buf[0..len]).ok().unwrap());
} else {
return None;
}
} else {
target.push(c);
}
}
Some(target)
}