Route raw non-ASCII paths when an API declares a non-ASCII route - #2942
Merged
Merged
Conversation
ericproulx
force-pushed
the
fix/binary-non-ascii-path-info
branch
from
September 13, 2026 18:03
576c9a5 to
ac5b6eb
Compare
Danger ReportNo issues found. |
4 tasks
Rack hands PATH_INFO over binary, and matching a binary string that holds non-ASCII bytes against a UTF-8 regexp raises Encoding::CompatibilityError. A route whose path holds a non-ASCII literal (`/café/:id`) compiles to such a regexp, and every request is matched against a union that holds it. So once an API declared one, every request whose path carried raw non-ASCII bytes raised, whichever route it was meant for and whether or not the bytes were valid UTF-8. The percent-encoded spelling of the same path routed. Router#compile! now records whether any compiled union has a fixed encoding. Only then, and only for a path that is not ASCII, the router routes a copy of the path tagged UTF-8 and publishes that copy as the normalized path. A path whose bytes are not UTF-8 cannot be matched against those routes at all, so it is answered as a path no route matched. PATH_INFO itself stays binary, and an API without a non-ASCII route routes exactly as before. Router#recognize_path gets the same treatment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
fix/binary-non-ascii-path-info
branch
from
September 13, 2026 19:16
ac5b6eb to
d26f9c1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rack hands
PATH_INFOover binary, and matching a binary string that holds non-ASCII bytes against a UTF-8 regexp raisesEncoding::CompatibilityError. A route whose path holds a non-ASCII literal (get '/café/:id') compiles to such a regexp, and every request is matched against a union that holds it. So once an API declares one, every request whose path carries raw non-ASCII bytes raises — whichever route it was meant for, and whether or not the bytes are valid UTF-8. The percent-encoded spelling of the same path routes fine.Router#compile!records whether any compiled union has a fixed encoding.Router#callroutes a copy of the path tagged UTF-8 and publishes that copy as the normalized path, which the path versioner reads.PATH_INFOitself stays binary, as Rack requires.X-Cascade: pass) instead of raising.Router#recognize_pathgets the same treatment.Since #2940, a route that spells its path out in full is answered from a table keyed by that path, and a binary key holding non-ASCII bytes never equals the UTF-8 one. The tagged copy is what finds such a route there, so
get '/café'routes too.Behaviour
API declaring
get '/café',get '/café/:id'andget '/plain/:id',PATH_INFObinary:/café(UTF-8 bytes)Encoding::CompatibilityError/café/café/42(UTF-8 bytes)Encoding::CompatibilityError/café/:id/plain/é(UTF-8 bytes)Encoding::CompatibilityError/plain/:id,params[:id] == 'é'/plain/\xFF(not UTF-8)Encoding::CompatibilityError/caf%C3%A9/42,/plain/42An API whose routes are all ASCII routes every one of these paths as before,
/plain/\xFFincluded (200, with the param left as invalid UTF-8).Test plan
spec/grape/router_spec.rb, with a binaryPATH_INFO: the UTF-8 paths to a parameterized and a fully spelled-out non-ASCII route and to an ASCII route, the path that is not UTF-8,recognize_path, and an ASCII-only API still routing bytes that are not UTF-8.next unless inputguard, which is equivalent —Regexp#match(nil)returns nil, so an unguarded nil path reaches the same 404 — and is kept so a nil is never published as the normalized path.🤖 Generated with Claude Code