diff --git a/Cargo.lock b/Cargo.lock index 9d40191..d2b476e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,7 @@ dependencies = [ "indexmap", "itoa", "log", + "memchr", "mimalloc", "mio", "native-tls", diff --git a/aquatic_http/Cargo.toml b/aquatic_http/Cargo.toml index 9c1262a..611ca09 100644 --- a/aquatic_http/Cargo.toml +++ b/aquatic_http/Cargo.toml @@ -27,6 +27,7 @@ indexmap = "1" itoa = "0.4" log = "0.4" mimalloc = { version = "0.1", default-features = false } +memchr = "2" mio = { version = "0.7", features = ["tcp", "os-poll", "os-util"] } native-tls = "0.2" parking_lot = "0.10" diff --git a/aquatic_http/src/lib/protocol/request.rs b/aquatic_http/src/lib/protocol/request.rs index 6bb7877..b671921 100644 --- a/aquatic_http/src/lib/protocol/request.rs +++ b/aquatic_http/src/lib/protocol/request.rs @@ -60,8 +60,7 @@ impl Request { .with_context(|| format!("no key in {}", part))?; let value = key_and_value.next() .with_context(|| format!("no value in {}", part))?; - let value = Self::urldecode(value)? - .to_string(); + let value = Self::urldecode_memchr(value)?; if key == "info_hash" { info_hashes.push(value); @@ -170,4 +169,62 @@ impl Request { Ok(processed) } + + fn urldecode_memchr(value: &str) -> anyhow::Result { + let mut processed = String::with_capacity(value.len()); + + let bytes = value.as_bytes(); + let iter = ::memchr::memchr_iter(b'%', bytes); + + let mut str_index_after_hex = 0usize; + + for i in iter { + match (bytes.get(i), bytes.get(i + 1), bytes.get(i + 2)){ + (Some(0..=127), Some(0..=127), Some(0..=127)) => { + if i > 0 { + processed.push_str(&value[str_index_after_hex..i]); + } + + str_index_after_hex = i + 3; + + let hex = &value[i + 1..i + 3]; + let byte = u8::from_str_radix(&hex, 16)?; + + processed.push(byte as char); + }, + _ => { + return Err(anyhow::anyhow!( + "invalid urlencoded segment at byte {} in {}", i, value + )); + } + } + } + + if let Some(rest_of_str) = value.get(str_index_after_hex..){ + processed.push_str(rest_of_str); + } + + processed.shrink_to_fit(); + + Ok(processed) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_urldecode(){ + let f = Request::urldecode_memchr; + + assert_eq!(f("").unwrap(), "".to_string()); + assert_eq!(f("abc").unwrap(), "abc".to_string()); + assert_eq!(f("%21").unwrap(), "!".to_string()); + assert_eq!(f("%21%3D").unwrap(), "!=".to_string()); + assert_eq!(f("abc%21def%3Dghi").unwrap(), "abc!def=ghi".to_string()); + assert!(f("%").is_err()); + assert!(f("%å7").is_err()); + } } \ No newline at end of file