1use std::borrow::Cow;
2use std::string::FromUtf8Error;
3
4#[inline]
5pub(crate) fn from_hex_digit(digit: u8) -> Option<u8> {
6 match digit {
7 b'0'..=b'9' => Some(digit - b'0'),
8 b'A'..=b'F' => Some(digit - b'A' + 10),
9 b'a'..=b'f' => Some(digit - b'a' + 10),
10 _ => None,
11 }
12}
13
14pub fn decode(data: &str) -> Result<Cow<str>, FromUtf8Error> {
20 match decode_binary(data.as_bytes()) {
21 Cow::Borrowed(_) => Ok(Cow::Borrowed(data)),
22 Cow::Owned(s) => Ok(Cow::Owned(String::from_utf8(s)?)),
23 }
24}
25
26pub fn decode_binary(mut data: &[u8]) -> Cow<[u8]> {
30 let mut out: Vec<u8> = Vec::with_capacity(data.len());
31 loop {
32 let mut parts = data.splitn(2, |&c| c == b'%');
33 out.extend_from_slice(parts.next().unwrap());
35 match parts.next() {
37 None => {
38 if out.is_empty() {
39 return data.into();
41 }
42 break;
43 },
44 Some(rest) => match rest.get(0..2) {
45 Some(&[first, second]) => match from_hex_digit(first) {
46 Some(first_val) => match from_hex_digit(second) {
47 Some(second_val) => {
48 out.push((first_val << 4) | second_val);
49 data = &rest[2..];
50 },
51 None => {
52 out.extend_from_slice(&[b'%', first]);
53 data = &rest[1..];
54 },
55 },
56 None => {
57 out.push(b'%');
58 data = rest;
59 },
60 },
61 _ => {
62 out.push(b'%');
64 out.extend_from_slice(rest);
65 break;
66 },
67 },
68 };
69 }
70 Cow::Owned(out)
71}