You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Route resolution decodes the path it is given, but the path does not always
reach it encoded. PathUtil.getSegmentsListWithDecoding() — used by RouteSegment#getNavigationRouteTarget since #22791 — assumes a
percent-encoded path, while two of the three entry points hand it an already
decoded one:
BootstrapHandler builds the Location from HttpServletRequest#getPathInfo(), which the servlet container has already
decoded → decoded.
UI#navigate(String) and BeforeEvent#forwardTo(String) take whatever the
application passes, which in practice is human readable → decoded.
The two decoded sources are therefore decoded a second time. #25671 is one
consequence of this (a literal non-ASCII character was destroyed by the second
decode) and is fixed in #25672, but that fix only makes the second decode
harmless for characters that are not percent escapes. The contract problem
itself is still there, with two remaining symptoms.
1. A literal percent sign in the path is eaten.
A browser request for /wild/a%2541 means the literal text a%41. The
container decodes the path info to /wild/a%41, the router decodes it a second
time, and the view receives aA. The same happens for UI.navigate("wild/a%41").
2. Location#getPath() depends on how the user arrived.
For the same URL, an application observing BeforeEvent#getLocation() sees the
decoded path after an eager server-side page load and the percent-encoded path
after a client-side navigation.
Expected behavior
The path that reaches route resolution has one well-defined form, and a path
segment is decoded exactly once, so that a route or a parameter value keeps the
characters the user actually navigated to. Location#getPath() returns the same
value for the same URL regardless of the navigation trigger.
Open http://localhost:8080/wild/a%2541 directly (so that the server side
resolves it). Expected [a%41], actual [aA].
For the second symptom, add System.out.println(event.getLocation().getPath())
to a BeforeEnterObserver of a route with a space in it, and compare opening
the URL directly with navigating to it from another view through a RouterLink.
Proposed solution
A canonical form has to be picked for the path that reaches the router, and both
options have consequences:
Canonical encoded — build the bootstrap Location from the raw request
URI instead of getPathInfo(), and require pre-encoded input from UI#navigate and BeforeEvent#forwardTo, as RouteConfiguration#getUrl
already documents. The eager page load then agrees with client-side
navigation, which is encoded today. But UI.navigate("grüße") stays broken,
and Location#getPath() starts returning percent-encoded text on the initial
load, which applications can observe.
Canonical decoded — decode exactly once, at the boundary where the
encoded form is known, and never again in the router. That is the form
applications want downstream, but it needs Location to carry the path as
segments rather than as one string, because rejoining decoded segments loses
the distinction between a literal slash and %2F that fix: preserve URL-encoded characters in wildcard route parameters #22791 was about.
Either way Location semantics change, which is why it is not part of #25672.
Versions
Vaadin / Flow version: main, and any 24.9.12+ / 25.1+ where PathUtil#getSegmentsListWithDecoding exists
Description of the bug
Route resolution decodes the path it is given, but the path does not always
reach it encoded.
PathUtil.getSegmentsListWithDecoding()— used byRouteSegment#getNavigationRouteTargetsince #22791 — assumes apercent-encoded path, while two of the three entry points hand it an already
decoded one:
BootstrapHandlerbuilds theLocationfromHttpServletRequest#getPathInfo(), which the servlet container has alreadydecoded → decoded.
Flow.tssendswindow.location.pathnamewithout decoding it, both as thelocationparameter of the init request and as therouteofUI#browserNavigate→ encoded (deliberately, see fix: preserve URL-encoded characters in wildcard route parameters #22791).UI#navigate(String)andBeforeEvent#forwardTo(String)take whatever theapplication passes, which in practice is human readable → decoded.
The two decoded sources are therefore decoded a second time. #25671 is one
consequence of this (a literal non-ASCII character was destroyed by the second
decode) and is fixed in #25672, but that fix only makes the second decode
harmless for characters that are not percent escapes. The contract problem
itself is still there, with two remaining symptoms.
1. A literal percent sign in the path is eaten.
A browser request for
/wild/a%2541means the literal texta%41. Thecontainer decodes the path info to
/wild/a%41, the router decodes it a secondtime, and the view receives
aA. The same happens forUI.navigate("wild/a%41").2.
Location#getPath()depends on how the user arrived.For the same URL, an application observing
BeforeEvent#getLocation()sees thedecoded path after an eager server-side page load and the percent-encoded path
after a client-side navigation.
Expected behavior
The path that reaches route resolution has one well-defined form, and a path
segment is decoded exactly once, so that a route or a parameter value keeps the
characters the user actually navigated to.
Location#getPath()returns the samevalue for the same URL regardless of the navigation trigger.
Minimal reproducible example
With a wildcard route:
Open
http://localhost:8080/wild/a%2541directly (so that the server sideresolves it). Expected
[a%41], actual[aA].For the second symptom, add
System.out.println(event.getLocation().getPath())to a
BeforeEnterObserverof a route with a space in it, and compare openingthe URL directly with navigating to it from another view through a
RouterLink.Proposed solution
A canonical form has to be picked for the path that reaches the router, and both
options have consequences:
Locationfrom the raw requestURI instead of
getPathInfo(), and require pre-encoded input fromUI#navigateandBeforeEvent#forwardTo, asRouteConfiguration#getUrlalready documents. The eager page load then agrees with client-side
navigation, which is encoded today. But
UI.navigate("grüße")stays broken,and
Location#getPath()starts returning percent-encoded text on the initialload, which applications can observe.
encoded form is known, and never again in the router. That is the form
applications want downstream, but it needs
Locationto carry the path assegments rather than as one string, because rejoining decoded segments loses
the distinction between a literal slash and
%2Fthat fix: preserve URL-encoded characters in wildcard route parameters #22791 was about.Either way
Locationsemantics change, which is why it is not part of #25672.Versions
main, and any 24.9.12+ / 25.1+ wherePathUtil#getSegmentsListWithDecodingexists