Affects: 6.0.4
After adding a FilterRegistrationBean into my project, I swapped from MockMvcBuilder to Spring Boots @AutoConfigureMockMvc
to have that filter applied in tests. That project also uses Thymeleaf so a ResourceUrlEncodingFilter is added as well by Spring Boot.
A existing test started breaking. It tested a @RequestMapping("")
controller, which caused ResourceUrlEncodingFilter
to throw a LookupPathIndexException
[1] because the behaviour changed from requestUri returing ""
instead of "/"
.
When an ExceptionHandler
also calls HttpServletResponse#encodeURL
(rendering a HTML error page with @{...} from thymeleaf), indexLookupPath
will already have the value of -1 and instead will throw a StringIndexOutOfBoundsException
in resolveUrlPath [2].
Here's a project demonstrating the problem as well as the differences between MockMvcBuilder
without ResourceUrlEncodingFilter
, MockMvcBuilder
with ResourceUrlEncodingFilter
and @AutoConfigureMockMvc
:
https://github.com/schosin/AutoConfigureMockMvcErrorApplication
To see the StringIndexOutOfBoundsException
, enable the profile errorhandling in application.properties.
[1] LookupPathIndexException
Caused by: org.springframework.web.servlet.resource.ResourceUrlEncodingFilter$LookupPathIndexException: Failed to find lookupPath '/' within requestUri ''. This could be because the path has invalid encoded characters or isn't normalized.
at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter$ResourceUrlEncodingRequestWrapper.initLookupPath(ResourceUrlEncodingFilter.java:102)
at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter$ResourceUrlEncodingRequestWrapper.setAttribute(ResourceUrlEncodingFilter.java:89)
at org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor.preHandle(ResourceUrlProviderExposingInterceptor.java:53)
[2] IndexOutOfBoundsException
Caused by: java.lang.StringIndexOutOfBoundsException: Range [-1, 13) out of bounds for length 13
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:55) ~[na:na]
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:52) ~[na:na]
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:213) ~[na:na]
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:210) ~[na:na]
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98) ~[na:na]
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:112) ~[na:na]
at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:349) ~[na:na]
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:4602) ~[na:na]
at java.base/java.lang.String.substring(String.java:2715) ~[na:na]
at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter$ResourceUrlEncodingRequestWrapper.resolveUrlPath(ResourceUrlEncodingFilter.java:125) ~[spring-webmvc-6.0.4.jar:6.0.4]
at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter$ResourceUrlEncodingResponseWrapper.encodeURL(ResourceUrlEncodingFilter.java:159) ~[spring-webmvc-6.0.4.jar:6.0.4]
at org.thymeleaf.web.servlet.JakartaServletWebExchange.transformURL(JakartaServletWebExchange.java:124) ~[thymeleaf-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.thymeleaf.linkbuilder.StandardLinkBuilder.processLink(StandardLinkBuilder.java:547) ~[thymeleaf-3.1.1.RELEASE.jar:3.1.1.RELEASE]
Comment From: wilkinsona
Comment From: QuantumXiecao
This difference between @AutoConfigureMockMvc and MockMvcBuilder is that: spring boot's SpringBootMockMvcBuilderCustomizer would add some filters including ResourceUrlEncodingFilter to MockMvcBuilder which would directly leads to the LookupPathIndexException. You can set @AutoConfigureMockMvc addFilters=false to solve this. @schosin @wilkinsona
Comment From: schosin
As stated in the gitter discussion, the point of swapping to @AutoConfigureMockMvc was to add a custom filter. While the application works just fine when running it, ResourceUrlEncodingFilter seems to break tests that call the url "" when HttpServletResponse#encodeURL is invoked, with the SIOOBE being thrown when that same method is called again in an error handler.
If calls to "" aren't allowed and are rewritten to the requestURI "/" when running the actual application, shouldn't the same be happening when using mockMvc? That difference seems to be the root cause for this issue.
Comment From: rstoyanchev
Thanks for the sample.
In 5.2.4, paths without a leading slash were disallowed in MockMvc to avoid ambiguity (#24556). In 6.0.4, empty path was excluded (#28823), but it leads to issues in some places. The request path should have a leading "/", and rather than allowing or rejecting an empty path, we can use "/" instead.