Skip to content

feat(router): add preserve_encoded_slash to keep %2F in path parameters#13626

Draft
AlinsRan wants to merge 1 commit into
apache:masterfrom
AlinsRan:feat/encoded-slash-route-match
Draft

feat(router): add preserve_encoded_slash to keep %2F in path parameters#13626
AlinsRan wants to merge 1 commit into
apache:masterfrom
AlinsRan:feat/encoded-slash-route-match

Conversation

@AlinsRan

Copy link
Copy Markdown
Contributor

Description

Nginx decodes an URL-encoded slash (%2F) into a real / in $uri before
route matching happens. As a result a request like
/v1/te%2Fst/products/electronics/list is seen by the router as
/v1/te/st/products/electronics/list, which has a different number of path
segments and therefore fails to match a route defined with path parameters such
as /v1/:id/products/:type/list (returns 404 Route Not Found). This is the
problem reported in #11810, and it cannot be fixed in lua-resty-radixtree
(the parameter pattern [^/]+ must stop at a real slash to keep segmentation
correct) because the information is already lost by the time the router runs.

This PR adds an opt-in apisix.preserve_encoded_slash option (default false).
When enabled, the route matching uri is rebuilt from the raw request_uri with
every percent-encoding decoded except %2F/%2f, which is kept encoded so
it is treated as part of a path parameter instead of a separator. The encoded
slash is then forwarded to the upstream as is.

To stay safe while bypassing Nginx's $uri normalization, the rebuild:

  • resolves . and .. segments (so an encoded dot like %2e%2e cannot be used
    to bypass route rules via path traversal);
  • rejects a decoded null byte, matching Nginx's own behaviour;
  • only runs when an encoded slash is actually present, so requests without %2F
    keep using the already normalized $uri with no overhead.

This follows the same access-phase, opt-in pattern as the existing
normalize_uri_like_servlet option.

Which issue(s) this PR fixes:

Fixes #11810

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (the option defaults to false)

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jun 29, 2026
@AlinsRan AlinsRan force-pushed the feat/encoded-slash-route-match branch from 6ca9581 to fa757bc Compare June 29, 2026 10:12
@AlinsRan AlinsRan marked this pull request as draft June 29, 2026 23:36
@AlinsRan AlinsRan force-pushed the feat/encoded-slash-route-match branch 3 times, most recently from 3df89d7 to b9da49e Compare June 30, 2026 01:05
Nginx decodes an URL-encoded slash (%2F) into a real '/' in $uri before
route matching, so a request like /v1/te%2Fst/products/electronics/list is
seen as /v1/te/st/products/electronics/list and fails to match a route with
path parameters such as /v1/:id/products/:type/list.

Add an opt-in apisix.preserve_encoded_slash option. When enabled, the route
matching uri is rebuilt from the raw request_uri with every percent-encoding
decoded except %2F, which stays encoded so it is treated as part of a path
parameter instead of a separator. The encoded slash is then forwarded to the
upstream as is. '.' and '..' segments are still normalized to prevent path
traversal, and a decoded null byte is rejected. The rebuild only runs when an
encoded slash is present, so normal requests keep using the normalized $uri
with no overhead.

Closes apache#11810
@AlinsRan AlinsRan force-pushed the feat/encoded-slash-route-match branch from b9da49e to 6e47607 Compare June 30, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: As a user, I want to use '%2F' in a path parameter

1 participant