Skip to content

fix(middleware): keep escaped path segments when redirecting on trailing slash - #3207

Open
methakon wants to merge 2 commits into
labstack:masterfrom
methakon:fix/3206-trailing-slash-escaped-path
Open

methakon wants to merge 2 commits into
labstack:masterfrom
methakon:fix/3206-trailing-slash-escaped-path

Conversation

@methakon

@methakon methakon commented Nov 1, 2026 •

Copy link
Copy Markdown

Fixes #3206.

AddTrailingSlashWithConfig and RemoveTrailingSlashWithConfig built the redirect Location from req.URL.Path, which is already percent-decoded. An escaped slash inside a path parameter was therefore promoted to a real path separator, so following the redirect matched a different route.

GET /files/a%2Fb    -> 301 /files/a/b/    -> 404
GET /files/a%3Fb    -> 301 /files/a?b/    (part of the segment becomes a query)
GET /files/a%20b    -> 301 /files/a b/    (raw space in Location)

Change

Build the redirect Location from URL.EscapedPath() so percent-encoded segments survive the redirect.

sanitizeURI only collapsed a leading / or \. Now that the location stays escaped it also treats a leading %2F / %5C as a slash, so the existing open-redirect normalisation still applies to /%5Cexample.com and friends.

Scope

Forward mode is deliberately untouched. Keeping RawPath in sync when forwarding is a separate concern and is already covered by #3134 (open), so this PR does not duplicate or conflict with it — it changes only the redirect branch.

One existing expectation changed

/%09%5Cevil.example/x now redirects to /%09%5Cevil.example/x/ rather than /%09\evil.example/x/. That is the same URL with the backslash left percent-encoded, which follows from keeping the location escaped. Every other open-redirect case in slash_test.go produces the same locations as before.

Tests

Added cases for %2F, %3F and %20 in both middlewares, plus two follow-the-redirect tests that assert the original route is reached. All four fail against master and pass with the change.

go vet ./middleware/     # clean
gofmt -l middleware/     # clean
go test ./...            # ok

…ing slash

AddTrailingSlash and RemoveTrailingSlash built the redirect Location from
req.URL.Path, which is already percent-decoded. An escaped slash inside a
path parameter therefore became a real path separator, so following the
redirect matched a different route (404) or delivered a different
parameter value.

Build the Location from URL.EscapedPath() instead, and keep RawPath in
step for the forward path. sanitizeURI now also treats a leading %2F/%5C
as a slash so the existing open-redirect normalisation still applies to
the escaped form.

The percent-encoded location means one existing expectation changes:
/%09%5Cevil.example/x keeps its backslash encoded. It is the same URL.
labstack#3134 already keeps RawPath in sync for forward mode, so this change must
not duplicate it. Forward mode is left exactly as it is on master; only
the redirect Location is now built from EscapedPath().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Trailing-slash redirects decode escaped path segments and change routing

1 participant