diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 538913b16af5..260b443863e1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -57,6 +57,7 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.support.ServletContextResource; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.servlet.HandlerMapping; @@ -445,6 +446,12 @@ private void resolveResourceLocations() { location = location.substring(endIndex + 1); } Resource resource = applicationContext.getResource(location); + if (location.equals("/") && !(resource instanceof ServletContextResource)) { + throw new IllegalStateException( + "The String-based location \"/\" should be relative to the web application root " + + "but resolved to a Resource of type: " + resource.getClass() + ". " + + "If this is intentional, please pass it as a pre-configured Resource via setLocations."); + } this.locationsToUse.add(resource); if (charset != null) { if (!(resource instanceof UrlResource)) { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index 38b39505f359..6ced3a30d33e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpMethod; @@ -38,6 +39,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManagerFactoryBean; +import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; @@ -723,6 +725,25 @@ public void ignoreLastModified() throws Exception { assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }"); } + @Test + public void servletContextRootValidation() { + StaticWebApplicationContext context = new StaticWebApplicationContext() { + @Override + public Resource getResource(String location) { + return new FileSystemResource("/"); + } + }; + + ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); + handler.setLocationValues(Collections.singletonList("/")); + handler.setApplicationContext(context); + + assertThatIllegalStateException().isThrownBy(handler::afterPropertiesSet) + .withMessage("The String-based location \"/\" should be relative to the web application root but " + + "resolved to a Resource of type: class org.springframework.core.io.FileSystemResource. " + + "If this is intentional, please pass it as a pre-configured Resource via setLocations."); + } + private long resourceLastModified(String resourceName) throws IOException { return new ClassPathResource(resourceName, getClass()).getFile().lastModified();