diff --git a/router/src/components/routes.rs b/router/src/components/routes.rs index dab47cb67c..832313f1e5 100644 --- a/router/src/components/routes.rs +++ b/router/src/components/routes.rs @@ -751,9 +751,7 @@ fn redirect_route_for(route: &RouteDefinition) -> Option { let new_pattern = if add_slash { // If we need to add a slash, we need to match on the path w/o it: - let mut path = route.path.clone(); - path.pop(); - path + route.path.trim_end_matches('/').to_string() } else { format!("{}/", route.path) }; @@ -779,9 +777,7 @@ fn FixTrailingSlash(add_slash: bool) -> impl IntoView { let path = if add_slash { format!("{}/", route.path()) } else { - let mut path = route.path().to_string(); - path.pop(); - path + route.path().trim_end_matches('/').to_string() }; let options = NavigateOptions { replace: true, diff --git a/router/src/matching/matcher.rs b/router/src/matching/matcher.rs index b3bec5ed23..b5278de55d 100644 --- a/router/src/matching/matcher.rs +++ b/router/src/matching/matcher.rs @@ -31,10 +31,7 @@ impl Matcher { Some((p, s)) => (p, Some(s.to_string())), None => (path, None), }; - let segments = get_segments(pattern) - .iter() - .map(|s| s.to_string()) - .collect::>(); + let segments: Vec = get_segments(pattern); let len = segments.len(); Self { splat, @@ -46,7 +43,7 @@ impl Matcher { #[doc(hidden)] pub fn test(&self, location: &str) -> Option { - let loc_segments = get_segments(location); + let loc_segments: Vec<&str> = get_segments(location); let loc_len = loc_segments.len(); let len_diff: i32 = loc_len as i32 - self.len as i32; @@ -104,18 +101,17 @@ impl Matcher { } } -fn get_segments(pattern: &str) -> Vec<&str> { - // URL root paths "/" and "" are equivalent. - // Web servers (at least, Axum and Actix-Web) will send us a path of "/" - // even if we've routed "". Always treat these as equivalent: - if pattern == "/" { - return vec![]; - } - pattern +fn get_segments<'a, S: From<&'a str>>(pattern: &'a str) -> Vec { + // URL root paths ("/" and "") are equivalent and treated as 0-segment paths. + // non-root paths with trailing slashes get extra empty segment at the end. + // This makes sure that segment matching is trailing-slash sensitive. + let mut segments: Vec = pattern .split('/') - .enumerate() - // Only remove a leading slash, not trailing slashes: - .skip_while(|(i, part)| *i == 0 && part.is_empty()) - .map(|(_, part)| part) - .collect() + .filter(|p| !p.is_empty()) + .map(Into::into) + .collect(); + if segments.len() > 0 && pattern.ends_with('/') { + segments.push("".into()); + } + segments } diff --git a/router/tests/matcher.rs b/router/tests/matcher.rs index 8de3514dd5..90f9c5d2c0 100644 --- a/router/tests/matcher.rs +++ b/router/tests/matcher.rs @@ -133,7 +133,7 @@ cfg_if! { Some(PathMatch { path: "".into(), params: params_map!( - "any" => "///" + "any" => "" ) }) ); @@ -148,7 +148,7 @@ cfg_if! { Some(PathMatch { path: "/foo/bar".into(), params: params_map!( - "any" => "///" + "any" => "" ) }) );