use std::collections::HashMap;
fn percent_decode(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
let hex: String = chars.by_ref().take(2).collect();
if hex.len() == 2
&& let Ok(byte) = u8::from_str_radix(&hex, 16)
{
result.push(byte as char);
continue;
}
result.push('%');
result.push_str(&hex);
} else if c == '+' {
result.push(' ');
} else {
result.push(c);
}
}
result
}
fn parse_query(query_str: &str) -> HashMap<String, String> {
query_str
.split('&')
.filter_map(|pair| {
if pair.is_empty() {
return None;
}
let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
Some((k.to_string(), percent_decode(v)))
})
.collect()
}
pub fn parse_url(url: &str) -> (String, HashMap<String, String>) {
let (path, query_str) = url.split_once('?').unwrap_or((url, ""));
let path = path.trim_start_matches('/');
(path.into(), parse_query(query_str))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_query() {
let mut params: HashMap<String, String> = HashMap::new();
params.insert("param1".into(), "hello".into());
params.insert("param2".into(), "world".into());
assert_eq!(parse_query("param1=hello¶m2=world"), params);
params.insert("param3".into(), "".into());
assert_eq!(parse_query("param1=hello¶m2=world¶m3"), params);
params.insert("param4".into(), "next param".into());
assert_eq!(
parse_query("param1=hello¶m2=world¶m3¶m4=next%20param"),
params
);
}
#[test]
fn test_parse_url() {
let mut params: HashMap<String, String> = HashMap::new();
assert_eq!(parse_url("/.pijul"), (".pijul".into(), params.clone()));
params.insert("a_param".into(), "a value".into());
assert_eq!(parse_url("/.pijul?a_param=a%20value"), (".pijul".into(), params.clone()));
}
#[test]
fn test_percent_decode() {
assert_eq!(percent_decode("Hello%20World%21"), "Hello World!");
assert_eq!(
percent_decode("~%20is%20where%20the%20%3C3%20is"),
"~ is where the <3 is"
);
}
}