1use alloc::{
2 borrow::Cow,
3 string::{String, ToString},
4 vec::Vec,
5};
6
7use crate::Result;
8
9pub fn decode(url: &str) -> Result<Cow<str>> {
11 if !url.contains(['%', '+']) {
12 return Ok(url.into());
13 }
14 let mut result: Vec<u8> = Vec::new();
15 let mut it = url.as_bytes().iter();
16 while let Some(b) = it.next() {
17 if *b == b'+' {
18 result.push(b' ');
19 continue;
20 }
21 if *b != b'%' {
22 result.push(*b);
23 continue;
24 }
25 let first = it.next().ok_or("Missing byte after '%'")?;
26 let second = it.next().ok_or("Missing byte after '%'")?;
27 let first = from_hex_digit(*first)?;
28 let second = from_hex_digit(*second)?;
29 let c = (first << 4) | second;
30 result.push(c);
31 }
32 let result = String::from_utf8(result).map_err(|err| err.to_string())?;
33 Ok(result.into())
34}
35
36#[inline(always)]
37fn from_hex_digit(digit: u8) -> Result<u8> {
38 match digit {
39 b'0'..=b'9' => Ok(digit - b'0'),
40 b'A'..=b'F' => Ok(digit - b'A' + 10),
41 b'a'..=b'f' => Ok(digit - b'a' + 10),
42 _ => Err(format!("{digit} is not a valid hex digit").into()),
43 }
44}