From ca5d2d5c23b783f449244332f3ebfcc3a4d5bfba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 7 Apr 2026 10:55:28 +0000 Subject: [PATCH] fix: strip base URL prefix as URL segment, not string (#499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Timothée Mazzucotelli --- .../src/middleware/path/base.rs | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/zensical-serve/src/middleware/path/base.rs b/crates/zensical-serve/src/middleware/path/base.rs index 9ec0744..e52db14 100644 --- a/crates/zensical-serve/src/middleware/path/base.rs +++ b/crates/zensical-serve/src/middleware/path/base.rs @@ -78,14 +78,60 @@ impl Middleware for BasePath { } // 2. Strip prefix, if it exists - if req.uri.path.starts_with(base) { - req.uri = Uri::from_parts( - Cow::Owned(req.uri.path.trim_start_matches(base).to_string()), - req.uri.query, - ); + if let Some(path) = strip_base_path(req.uri.path.as_ref(), base) { + req.uri = Uri::from_parts(Cow::Owned(path), req.uri.query); } // Forward with modified request next.handle(req) } } + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +fn strip_base_path(path: &str, base: &str) -> Option { + if path == base { + return Some("/".to_string()); + } + + path.strip_prefix(base) + .filter(|rest| rest.starts_with('/')) + .map(str::to_string) +} + +// ---------------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + use crate::http::{Request, Response}; + + #[test] + fn strips_base_path_once() { + let middleware = BasePath::new("/foo").expect("invariant"); + let req = Request::new().uri("/foo/food"); + + let res = middleware.process(req, &|req: Request| { + Response::new().body(req.uri.path.to_string()) + }); + + assert_eq!(res.body, b"/food"); + } + + #[test] + fn does_not_strip_non_segment_prefix() { + let middleware = BasePath::new("/foo").expect("invariant"); + let req = Request::new().uri("/foobar"); + + let res = middleware.process(req, &|req: Request| { + Response::new().body(req.uri.path.to_string()) + }); + + assert_eq!(res.body, b"/foobar"); + } +}