From 53c126d678ec913f6b525dae5f7b19af6a46597b Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 10 Dec 2025 09:30:02 -0500 Subject: [PATCH] fix: decode percent-encoded paths in get_file_path() (#1072) Co-authored-by: houseme Co-authored-by: loverustfs --- crates/ecstore/src/disk/endpoint.rs | 56 ++++++++++++++++++++++++++--- crates/ecstore/src/endpoints.rs | 7 ++-- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/ecstore/src/disk/endpoint.rs b/crates/ecstore/src/disk/endpoint.rs index f1de59e1c..952cda946 100644 --- a/crates/ecstore/src/disk/endpoint.rs +++ b/crates/ecstore/src/disk/endpoint.rs @@ -198,15 +198,22 @@ impl Endpoint { } } - pub fn get_file_path(&self) -> &str { - let path = self.url.path(); + pub fn get_file_path(&self) -> String { + let path: &str = self.url.path(); + let decoded: std::borrow::Cow<'_, str> = match urlencoding::decode(path) { + Ok(decoded) => decoded, + Err(e) => { + debug!("Failed to decode path '{}': {}, using original path", path, e); + std::borrow::Cow::Borrowed(path) + } + }; #[cfg(windows)] if self.url.scheme() == "file" { - let stripped = path.strip_prefix('/').unwrap_or(path); + let stripped: &str = decoded.strip_prefix('/').unwrap_or(&decoded); debug!("get_file_path windows: path={}", stripped); - return stripped; + return stripped.to_string(); } - path + decoded.into_owned() } } @@ -501,6 +508,45 @@ mod test { assert_eq!(endpoint.get_type(), EndpointType::Path); } + #[test] + fn test_endpoint_with_spaces_in_path() { + let path_with_spaces = "/Users/test/Library/Application Support/rustfs/data"; + let endpoint = Endpoint::try_from(path_with_spaces).unwrap(); + assert_eq!(endpoint.get_file_path(), path_with_spaces); + assert!(endpoint.is_local); + assert_eq!(endpoint.get_type(), EndpointType::Path); + } + + #[test] + fn test_endpoint_percent_encoding_roundtrip() { + let path_with_spaces = "/Users/test/Library/Application Support/rustfs/data"; + let endpoint = Endpoint::try_from(path_with_spaces).unwrap(); + + // Verify that the URL internally stores percent-encoded path + assert!( + endpoint.url.path().contains("%20"), + "URL path should contain percent-encoded spaces: {}", + endpoint.url.path() + ); + + // Verify that get_file_path() decodes the percent-encoded path correctly + assert_eq!( + endpoint.get_file_path(), + "/Users/test/Library/Application Support/rustfs/data", + "get_file_path() should decode percent-encoded spaces" + ); + } + + #[test] + fn test_endpoint_with_various_special_characters() { + // Test path with multiple special characters that get percent-encoded + let path_with_special = "/tmp/test path/data[1]/file+name&more"; + let endpoint = Endpoint::try_from(path_with_special).unwrap(); + + // get_file_path() should return the original path with decoded characters + assert_eq!(endpoint.get_file_path(), path_with_special); + } + #[test] fn test_endpoint_update_is_local() { let mut endpoint = Endpoint::try_from("http://localhost:9000/path").unwrap(); diff --git a/crates/ecstore/src/endpoints.rs b/crates/ecstore/src/endpoints.rs index 5f3572e74..1a334c072 100644 --- a/crates/ecstore/src/endpoints.rs +++ b/crates/ecstore/src/endpoints.rs @@ -232,7 +232,7 @@ impl PoolEndpointList { for endpoints in pool_endpoint_list.inner.iter_mut() { // Check whether same path is not used in endpoints of a host on different port. - let mut path_ip_map: HashMap<&str, HashSet> = HashMap::new(); + let mut path_ip_map: HashMap> = HashMap::new(); let mut host_ip_cache = HashMap::new(); for ep in endpoints.as_ref() { if !ep.url.has_host() { @@ -275,8 +275,9 @@ impl PoolEndpointList { match path_ip_map.entry(path) { Entry::Occupied(mut e) => { if e.get().intersection(host_ip_set).count() > 0 { + let path_key = e.key().clone(); return Err(Error::other(format!( - "same path '{path}' can not be served by different port on same address" + "same path '{path_key}' can not be served by different port on same address" ))); } e.get_mut().extend(host_ip_set.iter()); @@ -295,7 +296,7 @@ impl PoolEndpointList { } let path = ep.get_file_path(); - if local_path_set.contains(path) { + if local_path_set.contains(&path) { return Err(Error::other(format!( "path '{path}' cannot be served by different address on same server" )));