From 73398c3fc595697a86b311f8e405e366fe74994a Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Sun, 18 Aug 2024 00:48:41 +0100 Subject: [PATCH] Manually implement decoding --- Cargo.lock | 1 - Cargo.toml | 3 --- src/decode.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/errors.rs | 1 + src/errors/decode.rs | 24 ++++++++++++++++++++++++ src/errors/search.rs | 10 ++++++++++ src/lib.rs | 1 + src/router.rs | 26 +++++++++++++------------- 8 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 src/decode.rs create mode 100644 src/errors/decode.rs diff --git a/Cargo.lock b/Cargo.lock index e58e5551..4e84e56d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2211,7 +2211,6 @@ dependencies = [ "ntex-router", "path-table", "path-tree", - "percent-encoding", "regex", "route-recognizer", "routefinder", diff --git a/Cargo.toml b/Cargo.toml index 88b6ed12..7c397330 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,6 @@ categories.workspace = true [lints] workspace = true -[dependencies] -percent-encoding = "2.3" - [dev-dependencies] # Snapshots # NOTE: Keep in sync with `cargo-insta` Nix package. diff --git a/src/decode.rs b/src/decode.rs new file mode 100644 index 00000000..71f4df2f --- /dev/null +++ b/src/decode.rs @@ -0,0 +1,39 @@ +use crate::errors::decode::DecodeError; + +pub(crate) fn percent_decode(input: &[u8]) -> Result, DecodeError> { + if !input.contains(&b'%') { + return Ok(input.to_vec()); + } + + let mut output = Vec::with_capacity(input.len()); + + let mut i = 0; + let len = input.len(); + + while i < len { + if input[i] == b'%' && i + 2 < len { + let a = input[i + 1]; + let b = input[i + 2]; + output.push(decode_hex(a, b)?); + i += 3; + } else { + output.push(input[i]); + i += 1; + } + } + + Ok(output) +} + +#[allow(clippy::cast_possible_truncation)] +fn decode_hex(a: u8, b: u8) -> Result { + let a = (a as char) + .to_digit(16) + .ok_or(DecodeError::InvalidEncoding)?; + + let b = (b as char) + .to_digit(16) + .ok_or(DecodeError::InvalidEncoding)?; + + Ok((a as u8) << 4 | (b as u8)) +} diff --git a/src/errors.rs b/src/errors.rs index 496adce9..48a8c58a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,4 +1,5 @@ pub mod constraint; +pub mod decode; pub mod delete; pub mod insert; pub mod route; diff --git a/src/errors/decode.rs b/src/errors/decode.rs new file mode 100644 index 00000000..9097882e --- /dev/null +++ b/src/errors/decode.rs @@ -0,0 +1,24 @@ +use std::{error::Error, fmt::Display, str::Utf8Error}; + +#[derive(Debug, PartialEq, Eq)] +pub enum DecodeError { + Utf8Error(Utf8Error), + InvalidEncoding, +} + +impl Error for DecodeError {} + +impl Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Utf8Error(error) => error.fmt(f), + Self::InvalidEncoding => write!(f, "Invalid Encoding"), + } + } +} + +impl From for DecodeError { + fn from(error: Utf8Error) -> Self { + Self::Utf8Error(error) + } +} diff --git a/src/errors/search.rs b/src/errors/search.rs index 849cb66d..c66452c9 100644 --- a/src/errors/search.rs +++ b/src/errors/search.rs @@ -1,8 +1,11 @@ use std::{error::Error, fmt::Display, str::Utf8Error}; +use super::decode::DecodeError; + #[derive(Debug, PartialEq, Eq)] pub enum SearchError { Utf8Error(Utf8Error), + DecodeError(DecodeError), } impl Error for SearchError {} @@ -11,6 +14,7 @@ impl Display for SearchError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Utf8Error(error) => error.fmt(f), + Self::DecodeError(error) => error.fmt(f), } } } @@ -20,3 +24,9 @@ impl From for SearchError { Self::Utf8Error(error) } } + +impl From for SearchError { + fn from(error: DecodeError) -> Self { + Self::DecodeError(error) + } +} diff --git a/src/lib.rs b/src/lib.rs index b4325bbe..1fe67223 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod constraints; +pub mod decode; pub mod errors; pub mod node; pub mod parts; diff --git a/src/router.rs b/src/router.rs index 152d0e57..82f4bc4c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,12 +1,12 @@ use crate::{ constraints::Constraint, + decode::percent_decode, errors::{ constraint::ConstraintError, delete::DeleteError, insert::InsertError, search::SearchError, }, node::{Node, NodeData, NodeKind}, parts::{Part, Parts}, }; -use percent_encoding::percent_decode_str; use std::{ collections::{hash_map::Entry, HashMap}, fmt::Display, @@ -143,16 +143,16 @@ impl Router { } pub fn search<'a>(&'a self, path: &str) -> Result>, SearchError> { - let decoded_path = if self.percent_encoding { - percent_decode_str(path).decode_utf8()?.into_owned() + let path_bytes = if self.percent_encoding { + &percent_decode(path.as_bytes())? } else { - path.to_string() + path.as_bytes() }; let mut parameters = vec![]; - let Some(node) = - self.root - .search(decoded_path.as_bytes(), &mut parameters, &self.constraints) + let Some(node) = self + .root + .search(path_bytes, &mut parameters, &self.constraints) else { return Ok(None); }; @@ -164,16 +164,16 @@ impl Router { let parameters = parameters .into_iter() .map(|raw| { - let key = std::str::from_utf8(raw.key)?.to_string(); let value = if self.percent_encoding { - percent_decode_str(std::str::from_utf8(raw.value)?) - .decode_utf8()? - .into_owned() + &percent_decode(raw.value)? } else { - std::str::from_utf8(raw.value)?.to_string() + raw.value }; - Ok(Parameter { key, value }) + Ok(Parameter { + key: std::str::from_utf8(raw.key)?.to_string(), + value: std::str::from_utf8(value)?.to_string(), + }) }) .collect::, SearchError>>()?;