pub fn decode(url: &str) -> Option<String> {
let mut chars = url.chars();
let mut out = String::with_capacity(url.len());
while let Some(i) = chars.next() {
match i {
'+' => out.push(' '),
'%' => {
let mut hex = String::new();
hex.push(chars.next()?);
hex.push(chars.next()?);
out.push(u8::from_str_radix(&hex, 16).ok()? as char);
}
_ => out.push(i),
}
}
Some(out)
}
pub fn encode(url: &str) -> String {
const ALLOWED_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789-._~";
let mut out = String::with_capacity(url.len());
for i in url.chars() {
if i.is_ascii() && ALLOWED_CHARS.contains(&(i as u8)) {
out.push(i);
continue;
}
out.push_str(&format!("%{:02X}", i as u8));
}
out
}
#[cfg(test)]
mod test {
use super::{decode, encode};
#[test]
fn test_url_decode() {
assert_eq!(decode("hello+world").unwrap(), "hello world");
assert_eq!(decode("hello%20world").unwrap(), "hello world");
assert_eq!(
decode("%3C%3E%22%23%25%7B%7D%7C%5C%5E~%5B%5D%60").unwrap(),
"<>\"#%{}|\\^~[]`"
);
}
#[test]
fn test_url_decode_fail() {
assert_eq!(decode("hello%20world%"), None);
assert_eq!(decode("hello%20world%2"), None);
assert_eq!(decode("hello%20world%2G"), None);
}
#[test]
fn test_url_encode() {
assert_eq!(encode("hello world"), "hello%20world");
assert_eq!(encode("hello%20world"), "hello%2520world");
assert_eq!(
encode("<>\"#%{}|\\^~[]`"),
"%3C%3E%22%23%25%7B%7D%7C%5C%5E~%5B%5D%60"
);
}
}