Skip to content

http: percent-decode path segments for literal route matching - #37615

Open
Nic-Polumeyv wants to merge 1 commit into
oven-sh:mainfrom
Nic-Polumeyv:claude/serve-route-percent-decoding
Open

Nic-Polumeyv wants to merge 1 commit into
oven-sh:mainfrom
Nic-Polumeyv:claude/serve-route-percent-decoding

Conversation

@Nic-Polumeyv

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #37603. uWS's HttpRouter compares literal segments against raw request bytes, while :param values are percent-decoded after matching in ServerRouteList, so /robots.tx%74 misses a literal /robots.txt route. This decodes per segment before literal comparison, in HttpRouter itself, so static, function, HTML, negative and HTTP/3 routes become consistent together. The path splits on raw / first, so %2F never creates a separator; route keys normalize the same way at registration; malformed escapes fall back to raw comparison; segments without % cost one memchr. Param capture still pushes raw bytes, decoded once as before.

Observable change: requests whose encoded spelling decodes to a literal route now hit that route instead of falling through to a :param, wildcard, fetch, or 404.

How did you verify your code works?

10 new tests in bun-serve-routes: encoded spellings on static and function routes, encoded route keys against both wire spellings, hex case, malformed-escape raw fallback, %2F non-separation, literal-over-param precedence, HEAD on an encoded literal. bun-serve-routes 65/0; serve-directory-routes, bun-serve-static, bun-serve-file, bun-serve-html, filesystem_router, serve-http3 and bun-server suites pass (the one bun-server failure reproduces on unpatched main). No existing test needed changes.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: b2cd88f9-00c3-4c81-8404-96337413ac7d

📥 Commits

Reviewing files that changed from the base of the PR and between fc849e0 and 2eeba97.

📒 Files selected for processing (1)
  • test/js/bun/http/bun-serve-routes.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

Changes

Encoded route matching

Layer / File(s) Summary
Literal pattern normalization
packages/bun-uws/src/HttpRouter.h
Literal route patterns are normalized during registration and handler lookup. Parameter and wildcard patterns retain their existing handling.
Decoded URL segment matching
packages/bun-uws/src/HttpRouter.h, test/js/bun/http/bun-serve-routes.test.ts
URL segments keep raw and decoded views. Static routes use decoded segments, parameters keep raw captures, and tests cover encoded, malformed, UTF-8, slash, precedence, and HEAD cases.

Suggested reviewers: robobun

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 2eeba

This PR fixes a real bug where percent-encoded literal route segments (e.g. '/robots.tx%74') failed to match their decoded route ('/robots.txt'), bringing literal route matching in line with existing parameter decoding. The change is narrowly scoped to route matching, includes a fast path to avoid decoding overhead on typical requests, and is covered by a comprehensive new test suite exercising the relevant edge cases (malformed escapes, encoded slashes, UTF-8, precedence, HEAD). No material risk was identified in this review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: percent-decoding path segments for literal HTTP route matching.
Description check ✅ Passed The description includes both required sections and provides clear implementation details, verification steps, test coverage, and known test results.
Linked Issues check ✅ Passed The implementation satisfies issue #37603 by decoding literal route segments before matching, preserving raw slash separation and parameter behavior, and adding tests for the reported and related case…
Out of Scope Changes check ✅ Passed The code and test changes remain within the linked issue scope. They address literal route normalization and verify related matching, fallback, precedence, and request-method behavior.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-uws/src/HttpRouter.h`:
- Around line 99-110: Update normalizePatternSegment and the route-node
representation in packages/bun-uws/src/HttpRouter.h (lines 99-110) to store node
kind separately from segment text, then decode every valid literal segment,
including values beginning with ':' or '*'; preserve parameter and wildcard
classification based on the original route syntax. In
test/js/bun/http/bun-serve-routes.test.ts (lines 343-350), add literal routes
for /api/%3Aname and /api/%2Aname and verify encoded and raw request spellings
reach the same handlers, covering both reserved-marker representations at the
route boundary.

In `@test/js/bun/http/bun-serve-routes.test.ts`:
- Around line 330-334: In the regression test near the issue reference, remove
the explanatory comment lines and retain only the single issue URL comment.
Leave the test name and assertions unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5a2ed9c5-2521-401f-a61f-da920a669581

📥 Commits

Reviewing files that changed from the base of the PR and between 97e21e5 and 9cd17c9.

📒 Files selected for processing (2)
  • packages/bun-uws/src/HttpRouter.h
  • test/js/bun/http/bun-serve-routes.test.ts

Comment thread packages/bun-uws/src/HttpRouter.h
Comment on lines +330 to +334
// https://github.com/oven-sh/bun/issues/37603
// Literal route segments must match percent-encoded request spellings the same
// way :param segments already do: the path is split on raw "/" first, then
// each segment is percent-decoded, so %2F never creates a new separator and
// "/robots.tx%74" hits "/robots.txt".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the regression comment to the issue URL.

Remove the explanatory lines. The test name and assertions describe the covered behavior.

As per coding guidelines, “regression tests use one issue-URL comment.”

Proposed fix
 // https://github.com/oven-sh/bun/issues/37603
-//
-// Literal route segments must match percent-encoded request spellings the same
-// way :param segments already do: the path is split on raw "/" first, then
-// each segment is percent-decoded, so %2F never creates a new separator and
-// "/robots.tx%74" hits "/robots.txt".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// https://github.com/oven-sh/bun/issues/37603
// Literal route segments must match percent-encoded request spellings the same
// way :param segments already do: the path is split on raw "/" first, then
// each segment is percent-decoded, so %2F never creates a new separator and
// "/robots.tx%74" hits "/robots.txt".
// https://github.com/oven-sh/bun/issues/37603
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/bun/http/bun-serve-routes.test.ts` around lines 330 - 334, In the
regression test near the issue reference, remove the explanatory comment lines
and retain only the single issue URL comment. Leave the test name and assertions
unchanged.

Source: Coding guidelines

Bun.serve routes matched literal segments against the raw request bytes
while :param segments captured raw and percent-decoded afterwards, so
'/robots.tx%74' missed the literal '/robots.txt' route (and fell through
to fetch) while '/user/a%20b' matched '/user/:id' with id 'a b'.

Decode per segment, matching what params already do: the path is still
split on raw '/' first, then each segment is percent-decoded for literal
comparison, so %2F never creates a new separator. Route pattern segments
are normalized the same way at registration, so a route key spelled
'/caf%C3%A9' matches both request spellings. A segment with no '%' costs
one memchr; a segment with any malformed escape falls back to raw-byte
comparison instead of erroring. Param capture still pushes the raw bytes
so the params consumer's decode is not applied twice, and pattern
segments whose decoded form would look like a param or wildcard keep
their raw spelling (the raw request spelling still matches them).

This lives in HttpRouter so every consumer - static Response routes,
function routes, HTML bundle routes, negative routes, and the HTTP/3
router - matches identically. Requests that previously fell through to a
:param, wildcard, or the fetch handler now hit the literal route when an
encoded spelling decodes to it; that precedence shift is the fix.

Fixes oven-sh#37603

This branch has not been deployed

No deployments
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.

Bun.serve routes: literal segments match raw bytes while :param segments are percent-decoded

1 participant