Skip to content

Fix routing of percent-encodable fixed path segments - #13459

Draft
silentiris wants to merge 2 commits into
aio-libs:masterfrom
silentiris:fix/percent-encoded-route-segments
Draft

Fix routing of percent-encodable fixed path segments#13459
silentiris wants to merge 2 commits into
aio-libs:masterfrom
silentiris:fix/percent-encoded-route-segments

Conversation

@silentiris

Copy link
Copy Markdown

What do these changes do?

Fixes #13433

Dynamic routes (and static/prefix resources) whose fixed path segment contains characters that need percent-encoding — a space, non-ASCII, or some reserved characters — were unreachable: every request to them returned 404, and the URL produced by url_for() could not be routed back either.

Root cause: DynamicResource.__init__ percent-encoded fixed parts with _requote_path() and used the encoded form for both the regex pattern and the resource index key. PrefixResource.__init__ did the same with the prefix. But UrlDispatcher.resolve walks the decoded request.rel_url.path_safe backwards to find candidates. The encoded index key was never probed, so the resource was never a candidate. Even if it had been, the encoded regex pattern would not have matched the decoded path.

Fix:

  • DynamicResource: use decoded fixed parts for the regex pattern and the canonical property (which feeds the resource index key); keep the encoded form in the formatter so url_for() still produces valid encoded URLs.
  • PrefixResource: store the decoded prefix (used by StaticResource.resolve for prefix matching and by canonical for the index key); StaticResource.url_for now encodes the prefix on the fly.

Are there changes in behavior for the user?

Routes with spaces or non-ASCII characters in fixed path segments (e.g. /hello world/{name} or /static files/) are now correctly resolvable. Previously they returned 404. url_for() output is unchanged — it still produces properly percent-encoded URLs.

Is it a substantial burden for the maintainers to support?

No. The fix is a small, well-contained change in web_urldispatcher.py with three focused regression tests. No new dependencies, no configuration changes, no API changes.

Related issue number

Fixes #13433

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes (N/A — internal routing fix, no user-facing API change)
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
  • Add a new news fragment into the CHANGES/ folder
Test results
tests/test_urldispatch.py: 155 passed in 0.13s
tests/test_web_functional.py + test_web_response.py + test_urldispatch.py: 463 passed, 43 skipped, 2 deselected in 1.70s

Drafted with Claude Code; reviewed by silentiris.

DynamicResource and PrefixResource percent-encoded fixed path segments
at registration time and used the encoded form for both the regex
pattern and the resource index key.  The resolver walks the decoded
path (path_safe), so the encoded index key was never probed and the
resource was never a candidate — every request to such a route
returned 404.

Use the decoded form for the regex pattern, the canonical property,
and the stored prefix so they match the decoded path_safe.  Keep the
encoded form for the formatter and url_for() so generated URLs
remain valid.

Fixes aio-libs#13433
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Aug 17, 2026
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.00%. Comparing base (7dc43ae) to head (b8e6390).
⚠️ Report is 15 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #13459      +/-   ##
==========================================
+ Coverage   98.42%   99.00%   +0.58%     
==========================================
  Files         133      132       -1     
  Lines       49747    49651      -96     
  Branches     2628     2575      -53     
==========================================
+ Hits        48961    49157     +196     
+ Misses        662      370     -292     
  Partials      124      124              
Flag Coverage Δ
Autobahn 22.03% <9.37%> (+0.08%) ⬆️
CI-GHA 98.91% <100.00%> (+0.58%) ⬆️
OS-Linux 98.68% <100.00%> (+0.58%) ⬆️
OS-Windows 97.01% <100.00%> (+0.55%) ⬆️
OS-macOS 97.93% <100.00%> (+0.58%) ⬆️
Py-3.10 98.13% <100.00%> (+0.58%) ⬆️
Py-3.11 98.37% <100.00%> (+0.57%) ⬆️
Py-3.12 98.45% <100.00%> (+0.57%) ⬆️
Py-3.13 98.45% <100.00%> (+0.57%) ⬆️
Py-3.14 98.46% <100.00%> (+0.56%) ⬆️
Py-3.14t 97.55% <100.00%> (+0.56%) ⬆️
Py-pypy-3.11 97.40% <100.00%> (+0.54%) ⬆️
VM-macos 97.93% <100.00%> (+0.58%) ⬆️
VM-ubuntu 98.68% <100.00%> (+0.58%) ⬆️
VM-windows 97.01% <100.00%> (+0.55%) ⬆️
cython-coverage 82.26% <100.00%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed-hq

codspeed-hq Bot commented Aug 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing silentiris:fix/percent-encoded-route-segments (b8e6390) with master (79b5f5f)

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamic routes with percent-encodable fixed segments (space, non-ASCII) are unreachable and url_for() URLs 404

1 participant