Description
After upgrading from Spring Boot 2.5.6 to 2.6.1, I've detected a behaviour change affecting access to static files with "special chars" in file names(spaces, accents, etc.)
I believe this is related to the change from AntPathMatcher to PathPattern introduced in Spring Boot 2.6.0-M3.
When I try to serve static files from a custom location from my filesystem (outside the default static paths configured by Spring Boot), I can not access files with spaces (or accents) in file names.
I've prepared a sample project to demonstrate this behavior. My only customization is adding a new resource handler to be able to access static files under the /tmp/repo
directory:
@Configuration
public class MyCustomConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/repo/**").addResourceLocations("file:/tmp/repo/");
}
}
Steps to reproduce
git clone https://github.com/aaguilera/test-static-files.git
cd test-static-files
cp -r src/main/resources/static /tmp/repo # copy sample files to external directory
./mvnw spring-boot:run
Once the app is running, try to access the static file foo bar.txt
under resources/static and /tmp/repo:
http://localhost:8080/foo%20bar.txt (this works fine) http://localhost:8080/repo/foo%20bar.txt (this returns error 404)
The only difference is that the first URL is getting a file located at src/main/resources/static/
(this directory is preconfigured by default via Spring Boot to serve static files), and the second URL is trying to get a file located at file:/tmp/repo
, configured manually via my @Configuration class using addResourcehandler
/addResouceLocations
.
The interesting thing is that this second URL works perfectly (returns the file) when using Spring Boot 2.5.7 instead of 2.6.1 (you can try changing the version in pom.xml
and restarting the app).
Workaround
As a workaround, I can force the old behavior by setting the following property in my application.properties
:
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
With this configuration, the second URL works fine again, as it did with Spring Boot 2.5.x.
Comment From: wilkinsona
Thanks for the report and sample application, I've reproduced the problem. As far as I can tell, this appears to be a bug in Spring Framework. In the case where it works, foo%20bar.txt
becomes foo bar.txt
and the file is found. In the case were it does not work, foo%20bar.txt
comes foo%2520.txt
and the file is not found. This decoding or encoding is performed in PathResourceResolver
's encodeOrDecodeIfNecessary
method.
Could you please take a look, @bclozel?
Comment From: aaguilera
Also please note this bug affects not only files with spaces in the name, but files with non-ASCII characters such as foó.txt
, etc.
Comment From: bclozel
This almost look like a duplicate of #26775. While this case is in theory covered by ResourceHttpRequestHandlerIntegrationTests
in that issue, it looks like a typical Spring Boot setup is a bit different. Spring Boot configures a UrlPathHelper
with the urlDecode
option with its default value, true
. This has been done for optimization purposes in spring-projects/spring-boot#21499.
I think that this last bit is incorrect - in the case of a Spring MVC application using the PathPatternParser
matching strategy, Spring Boot should ensure that the configured UrlPathHelper
has the urlDecode
option set to false: the matching strategy does not decode the path for us beforehand and there is no need to re-encode it for resolving URL resources.
@rstoyanchev do you think this analysis is right? If so, I'm going to move this issue back to Spring Boot update the UrlPathHelper
.
Comment From: rstoyanchev
The settings of UrlPathHelper
shouldn't matter. It is mutually exclusive with PathPatternParser
.
I think I can see the issue in PathResourceResolver
where need to differentiate between UrlResource
and other resource types and check whether the URL path was decoded or not. For the latter we need to consider whether PathPatternParser
is in use in which case the path is encoded. #26775 as a fix along the same lines in shouldDecodeRelativePath
but we need the same in shouldEncodeRelativePath
as well.
I'll experiment with a test and a fix.