New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MockMvc needs to accept prepared URI with encoded URI path variables [SPR-11441] #16067
Comments
daniel carter commented "My guess is that the MockMvc Framework is not using the normal request mapping handler mapping, and so calling setUrlDecode on the normal one has no effect?" My guess is wrong. Having spent a couple hours debugging it. UrlPathHelper.getRequestUri(...)
Under Catalina request.getRequestURI returns While MockHttpServletRequest returns There seems to be a consistent issue in spring-mvc whereby it mistakenly treating %2F as / So by my reading, a "/" should be interpreted as a delimiter. "%2F" should be interpreted differently, that is, as data encoded into the path, and not as a path delimiter. The whole purpose of encoding something as %2F is to avoid it being treated as a path delimiter. Yet spring is doing that. Nowhere in RFC3986 does it distinguish %2F from the other reserved characters, and say it should be decoded. Why does spring treat it differently to %20 ? |
daniel carter commented The problem seems to be in all the UriComponents stuff, first you do the expansions, and then you encode the whole path. For path variables, this is fundamentally flawed. Once you've done the substitutions, you don't know which / are path delimiter and which are part of path variables. The decision to have special URL Encoding mechanism where you encode all the reserved characters except for / is a symptom of this flaw. URL encoding must encode all reserved characters. given the UrlTemplate ```
instead of ```
UriComponentsBuilder.fromUriString(urlTemplate).build(encoded).expand(urlVariables).encode();
this.uriComponents = UriComponentsBuilder.fromUriString(urlTemplate).buildAndExpand(urlVariables).encode()
|
daniel carter commented From the RFC The opposite of this of course is application must encode the components and subcomponents prior to composing the full URI, as otherwise the unencoded octets might be mistaken for delimiters. Which is what's happening here. |
Rossen Stoyanchev commented UriComponentsBuilder is smart about expanding. If you look inside you'll see UriComponents is composed of fields, not a String, so we can encode each URI component accordingly down to the path segment and query variable. Whatever the problem is, this is not the cause of it. Can you provide information on how you build your MockMvc? In other words do you load your spring configuration or do you use the StandaloneMockMvcBuilder? My guess is it's the latter in which case the urlDecode and alwaysUseFullPath properties of the RequestMappingHandlerMapping are not getting set. We can add support for those properties on StandaloneMockMvcBuilder but you can also try building MockMvc from actual Spring configuration. |
daniel carter commented Hi, Finally got the release out the door so can return to this issue. I'm using MockMvc to load an actual spring configuration
I've tested in the debugger that urlDecode and alwaysUseFullPath properties are being set. The problem is the MockHttpServletRequest.getRequestURI() returns a URL with %2F decoded to / In a real container, the HandlerMapping works In MockMvc the mapping doesn't work UriComponents is composed of fields, but that's not the issue. The issue is with variable substitution. You substitute variables into each field, then you encode the each field. This is incorrect. You need to encode each variable, then substitute it into each field. They way it works at the moment, with substitution then encoding, when you encounter a reserved character, how can you know if it was a literal character taking it's reserved meaning (and thus doesn't need encoding) or part of a variable (and thus does need encoding). An example: but you do the substitution first You assume that variable might have a # or a space, or a " etc, and encode those, but for some reason you assume a / will never be in a variable. variables need to be encoded to escape reserved characters. In a path, |
daniel carter commented A full working test case is worth a 1000 of my rambling words. Extract the attached maven project and build. Neither of the two tests pass
MockMvc, encoded id
Deploy the war to tomcat started with
Hit the URL /uri-encode-test/test.jsp and it works fine.
|
daniel carter commented For another perspective, consider if this was implemented with old-style query parameters, and the id value contained a reserved delimiter /circuits?id={id} where id="abc&def" the following URL is generated /circuits?id=abc%26def Nobody says, oh & is a valid character in a query string, maybe they are expecting the value of one parameter to expand out into a whole query string, we better leave it alone and generate |
daniel carter commented #16028 spring:url tag has the same issue |
Rossen Stoyanchev commented
I'm sorry but this ticket has too much already. Please submit a repro project and I'll be happy to take a look and respond. |
daniel carter commented Ok i have copied the demonstration project from this JIRA into a git repro project. It's my first time using git, do you see the pull request? |
daniel carter commented I've also verified that Jetty displays the expected behaviour
|
Rossen Stoyanchev commented daniel carter, thanks for the sample project. It shows exactly the problem. MockMvcRequestBuilders process the provided URI template and also encode it. This is documented in the Javadoc and it works exactly like the RestTemplate methods. However unlike the RestTemplate, MockMvcRequestBuilders does not provide alternative methods that accept a URI, which is already encoded (or not) and is used as is. So we will add additional methods to MockMvcRequestBuilders (one for each HTTP method) that accepts a prepared URI. That will match to the way the RestTemplate works. |
daniel carter opened SPR-11441 and commented
Good Afternoon,
I have a controller with the following mapping
The id value can contain reserved characters so I URL encode when rendering the view. For an id of "ja-ran-17 gigabitethernet 10/0.5790740:579-747" the browser request thus looks like
This is working ok.
When i try to write a test for it however, the URL is getting double encoded
log from real request in tomcat
log from mockmvc request
With a real request my controller gets passed id="ja-ran-17 gigabitethernet 10/0.5790740:579-747"
With a mock request my controller is passed id="ja-ran-47%20gigabitethernet%2010%2F0.5790740:579-747"
Perhaps it is a feature of MockMvc that it automatically does URI encoding of path variables? If so it is inconsistent with other parts of the Web-Mvc framework see #16028
So i remove the encoding of my path variable and let MockMvc encode it. Now it doesn't find the handler
I had a similar problem where the framework was treating %2F as / in contravention of RFC3986[1]. That is solved by a BeanPostProcessor calling
My guess is that the MockMvc Framework is not using the normal request mapping handler mapping, and so calling setUrlDecode on the normal one has no effect?
[1]"When a URI is dereferenced, the components and subcomponents
significant to the scheme-specific dereferencing process (if any)
must be parsed and separated before the percent-encoded octets within
those components can be safely decoded, as otherwise the data may be
mistaken for component delimiters."
and again in 7.3
"Percent-encoded octets must be decoded at some point during the
dereference process. Applications must split the URI into its
components and subcomponents prior to decoding the octets, as
otherwise the decoded octets might be mistaken for delimiters."
That is clearly what is happening here, %2F is being mistaken for a path delimiter. Path delimitation must take place before decoding.
Affects: 3.2.5
Attachments:
Referenced from: commits 0b69a0b
0 votes, 5 watchers
The text was updated successfully, but these errors were encountered: