diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java
index c6de3f62cb..9f77d3d2c3 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java
@@ -17,10 +17,14 @@
package org.springframework.web.servlet.resource;
import java.io.IOException;
+import java.net.URLDecoder;
import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
+import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
/**
* A simple {@code ResourceResolver} that tries to find a resource under the given
@@ -36,6 +40,35 @@ import org.springframework.core.io.Resource;
*/
public class PathResourceResolver extends AbstractResourceResolver {
+ private Resource[] allowedLocations;
+
+
+ /**
+ * By default when a Resource is found, the path of the resolved resource is
+ * compared to ensure it's under the input location where it was found.
+ * However sometimes that may not be the case, e.g. when
+ * {@link org.springframework.web.servlet.resource.CssLinkResourceTransformer}
+ * resolves public URLs of links it contains, the CSS file is the location
+ * and the resources being resolved are css files, images, fonts and others
+ * located in adjacent or parent directories.
+ *
This property allows configuring a complete list of locations under
+ * which resources must be so that if a resource is not under the location
+ * relative to which it was found, this list may be checked as well.
+ *
By default {@link ResourceHttpRequestHandler} initializes this property
+ * to match its list of locations.
+ * @param locations the list of allowed locations
+ * @since 4.1.2
+ * @see ResourceHttpRequestHandler#initAllowedLocations()
+ */
+ public void setAllowedLocations(Resource... locations) {
+ this.allowedLocations = locations;
+ }
+
+ public Resource[] getAllowedLocations() {
+ return this.allowedLocations;
+ }
+
+
@Override
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
List extends Resource> locations, ResourceResolverChain chain) {
@@ -84,7 +117,79 @@ public class PathResourceResolver extends AbstractResourceResolver {
*/
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource resource = location.createRelative(resourcePath);
- return (resource.exists() && resource.isReadable() ? resource : null);
+ if (resource.exists() && resource.isReadable()) {
+ if (checkResource(resource, location)) {
+ return resource;
+ }
+ else {
+ if (logger.isTraceEnabled()) {
+ logger.trace("resourcePath=\"" + resourcePath + "\" was successfully resolved " +
+ "but resource=\"" + resource.getURL() + "\" is neither under the " +
+ "current location=\"" + location.getURL() + "\" nor under any of the " +
+ "allowed locations=" + getAllowedLocations());
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Perform additional checks on a resolved resource beyond checking whether
+ * the resources exists and is readable. The default implementation also
+ * verifies the resource is either under the location relative to which it
+ * was found or is under one of the {@link #setAllowedLocations allowed
+ * locations}.
+ * @param resource the resource to check
+ * @param location the location relative to which the resource was found
+ * @return "true" if resource is in a valid location, "false" otherwise.
+ * @since 4.1.2
+ */
+ protected boolean checkResource(Resource resource, Resource location) throws IOException {
+ if (isResourceUnderLocation(resource, location)) {
+ return true;
+ }
+ if (getAllowedLocations() != null) {
+ for (Resource current : getAllowedLocations()) {
+ if (isResourceUnderLocation(resource, current)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isResourceUnderLocation(Resource resource, Resource location) throws IOException {
+ if (!resource.getClass().equals(location.getClass())) {
+ return false;
+ }
+ String resourcePath;
+ String locationPath;
+ if (resource instanceof ClassPathResource) {
+ resourcePath = ((ClassPathResource) resource).getPath();
+ locationPath = ((ClassPathResource) location).getPath();
+ }
+ else if (resource instanceof UrlResource) {
+ resourcePath = resource.getURL().toExternalForm();
+ locationPath = location.getURL().toExternalForm();
+ }
+ else {
+ resourcePath = resource.getURL().getPath();
+ locationPath = location.getURL().getPath();
+ }
+ locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/");
+ if (!resourcePath.startsWith(locationPath)) {
+ return false;
+ }
+ if (resourcePath.contains("%")) {
+ // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
+ if (URLDecoder.decode(resourcePath, "UTF-8").contains("../")) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Resolved resource path contains \"../\" after decoding: " + resourcePath);
+ }
+ return false;
+ }
+ }
+ return true;
}
}
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 e36c08c590..2713461e9a 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
@@ -18,8 +18,10 @@ package org.springframework.web.servlet.resource;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
+
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletException;
@@ -28,7 +30,6 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -36,6 +37,8 @@ import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler;
@@ -158,6 +161,29 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
+ initAllowedLocations();
+ }
+
+ /**
+ * Look for a {@link org.springframework.web.servlet.resource.PathResourceResolver}
+ * among the {@link #getResourceResolvers() resource resolvers} and configure
+ * its {@code "allowedLocations"} to match the value of the
+ * {@link #setLocations(java.util.List) locations} property unless the "allowed
+ * locations" of the {@code PathResourceResolver} is non-empty.
+ */
+ protected void initAllowedLocations() {
+ if (CollectionUtils.isEmpty(this.locations)) {
+ return;
+ }
+ for (int i = getResourceResolvers().size()-1; i >= 0; i--) {
+ if (getResourceResolvers().get(i) instanceof PathResourceResolver) {
+ PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
+ if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
+ pathResolver.setAllowedLocations(getLocations().toArray(new Resource[getLocations().size()]));
+ }
+ break;
+ }
+ }
}
/**
@@ -214,18 +240,33 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
writeContent(response, resource);
}
- protected Resource getResource(HttpServletRequest request) throws IOException{
+ protected Resource getResource(HttpServletRequest request) throws IOException {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) {
throw new IllegalStateException("Required request attribute '" +
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
}
+ path = processPath(path);
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring invalid resource path [" + path + "]");
}
return null;
}
+ if (path.contains("%")) {
+ try {
+ // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
+ if (isInvalidPath(URLDecoder.decode(path, "UTF-8"))) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
+ }
+ return null;
+ }
+ }
+ catch (IllegalArgumentException ex) {
+ // ignore
+ }
+ }
ResourceResolverChain resolveChain = new DefaultResourceResolverChain(getResourceResolvers());
Resource resource = resolveChain.resolveResource(request, path, getLocations());
if (resource == null || getResourceTransformers().isEmpty()) {
@@ -237,14 +278,76 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
}
/**
- * Validates the given path: returns {@code true} if the given path is not a valid resource path.
- *
The default implementation rejects paths containing "WEB-INF" or "META-INF" as well as paths
- * with relative paths ("../") that result in access of a parent directory.
+ * Process the given resource path to be used.
+ *
The default implementation replaces any combination of leading '/' and
+ * control characters (00-1F and 7F) with a single "/" or "". For example
+ * {@code " // /// //// foo/bar"} becomes {@code "/foo/bar"}.
+ * @since 3.2.12
+ */
+ protected String processPath(String path) {
+ boolean slash = false;
+ for (int i = 0; i < path.length(); i++) {
+ if (path.charAt(i) == '/') {
+ slash = true;
+ }
+ else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
+ if (i == 0 || (i == 1 && slash)) {
+ return path;
+ }
+ path = slash ? "/" + path.substring(i) : path.substring(i);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Path after trimming leading '/' and control characters: " + path);
+ }
+ return path;
+ }
+ }
+ return (slash ? "/" : "");
+ }
+
+ /**
+ * Identifies invalid resource paths. By default rejects:
+ *
+ * - Paths that contain "WEB-INF" or "META-INF"
+ *
- Paths that contain "../" after a call to
+ * {@link org.springframework.util.StringUtils#cleanPath}.
+ *
- Paths that represent a {@link org.springframework.util.ResourceUtils#isUrl
+ * valid URL} or would represent one after the leading slash is removed.
+ *
+ * Note: this method assumes that leading, duplicate '/'
+ * or control characters (e.g. white space) have been trimmed so that the
+ * path starts predictably with a single '/' or does not have one.
* @param path the path to validate
- * @return {@code true} if the path has been recognized as invalid, {@code false} otherwise
+ * @return {@code true} if the path is invalid, {@code false} otherwise
*/
protected boolean isInvalidPath(String path) {
- return (path.contains("WEB-INF") || path.contains("META-INF") || StringUtils.cleanPath(path).startsWith(".."));
+ if (logger.isTraceEnabled()) {
+ logger.trace("Applying \"invalid path\" checks to path: " + path);
+ }
+ if (path.contains("WEB-INF") || path.contains("META-INF")) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Path contains \"WEB-INF\" or \"META-INF\".");
+ }
+ return true;
+ }
+ if (path.contains(":/")) {
+ String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
+ if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Path represents URL or has \"url:\" prefix.");
+ }
+ return true;
+ }
+ }
+ if (path.contains("../")) {
+ path = StringUtils.cleanPath(path);
+ if (path.contains("../")) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Path contains \"../\" after call to StringUtils#cleanPath.");
+ }
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/AppCacheManifestTransformerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/AppCacheManifestTransformerTests.java
index f9590c22d4..5a197f4bcd 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/AppCacheManifestTransformerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/AppCacheManifestTransformerTests.java
@@ -17,6 +17,7 @@
package org.springframework.web.servlet.resource;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -75,13 +76,13 @@ public class AppCacheManifestTransformerTests {
@Test
public void transformManifest() throws Exception {
- VersionResourceResolver versionResourceResolver = new VersionResourceResolver();
- versionResourceResolver
- .setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
- List resolvers = new ArrayList();
- resolvers.add(versionResourceResolver);
- resolvers.add(new PathResourceResolver());
+ PathResourceResolver pathResolver = new PathResourceResolver();
+ pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
+
+ List resolvers = Arrays.asList(versionResolver, pathResolver);
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
List transformers = new ArrayList<>();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
index e59249e9c6..dd509d63b5 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
@@ -47,10 +47,13 @@ public class CssLinkResourceTransformerTests {
@Before
public void setUp() {
- VersionResourceResolver resolver = new VersionResourceResolver();
- resolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
- List resolvers = Arrays.asList(resolver, new PathResourceResolver());
+ PathResourceResolver pathResolver = new PathResourceResolver();
+ pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
+
+ List resolvers = Arrays.asList(versionResolver, pathResolver);
List transformers = Arrays.asList(new CssLinkResourceTransformer());
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java
index f0404e3005..26218e3377 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java
@@ -1,46 +1,102 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.springframework.web.servlet.resource;
-import java.util.ArrayList;
-import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
-
-import static org.junit.Assert.*;
+import org.springframework.core.io.UrlResource;
/**
* Unit tests for
* {@link org.springframework.web.servlet.resource.PathResourceResolver}.
*
* @author Brian Clozel
+ * @author Rossen Stoyanchev
*/
public class PathResourceResolverTests {
- private ResourceResolverChain chain;
+ private PathResourceResolver resolver;
- private List locations;
@Before
public void setup() {
-
- List resolvers = new ArrayList<>();
- resolvers.add(new PathResourceResolver());
- this.chain = new DefaultResourceResolverChain(resolvers);
-
- this.locations = new ArrayList<>();
- this.locations.add(new ClassPathResource("test/", getClass()));
+ this.resolver = new PathResourceResolver();
}
@Test
- public void resolveResourceInternal() {
- String file = "bar.css";
- Resource expected = new ClassPathResource("test/" + file, getClass());
- Resource actual = this.chain.resolveResource(null, file, this.locations);
+ public void resolveFromClasspath() throws IOException {
+ Resource location = new ClassPathResource("test/", PathResourceResolver.class);
+ String requestPath = "bar.css";
+ Resource actual = this.resolver.resolveResource(null, requestPath, Arrays.asList(location), null);
+ assertEquals(location.createRelative(requestPath), actual);
+ }
- assertEquals(expected, actual);
+ @Test
+ public void resolveFromClasspathRoot() throws IOException {
+ Resource location = new ClassPathResource("/");
+ String requestPath = "org/springframework/web/servlet/resource/test/bar.css";
+ Resource actual = this.resolver.resolveResource(null, requestPath, Arrays.asList(location), null);
+ assertNotNull(actual);
+ }
+
+ @Test
+ public void checkResource() throws IOException {
+ Resource location = new ClassPathResource("test/", PathResourceResolver.class);
+ testCheckResource(location, "../testsecret/secret.txt");
+ testCheckResource(location, "test/../../testsecret/secret.txt");
+
+ location = new UrlResource(getClass().getResource("./test/"));
+ String secretPath = new UrlResource(getClass().getResource("testsecret/secret.txt")).getURL().getPath();
+ testCheckResource(location, "file:" + secretPath);
+ testCheckResource(location, "/file:" + secretPath);
+ testCheckResource(location, "/" + secretPath);
+ testCheckResource(location, "////../.." + secretPath);
+ testCheckResource(location, "/%2E%2E/testsecret/secret.txt");
+ testCheckResource(location, "/%2e%2e/testsecret/secret.txt");
+ testCheckResource(location, " " + secretPath);
+ testCheckResource(location, "/ " + secretPath);
+ testCheckResource(location, "url:" + secretPath);
+ }
+
+ @Test
+ public void checkResourceWithAllowedLocations() {
+ this.resolver.setAllowedLocations(
+ new ClassPathResource("test/", PathResourceResolver.class),
+ new ClassPathResource("testalternatepath/", PathResourceResolver.class)
+ );
+
+ Resource location = new ClassPathResource("test/main.css", PathResourceResolver.class);
+ String actual = this.resolver.resolveUrlPath("../testalternatepath/bar.css", Arrays.asList(location), null);
+ assertEquals("../testalternatepath/bar.css", actual);
+ }
+
+ private void testCheckResource(Resource location, String requestPath) throws IOException {
+ Resource actual = this.resolver.resolveResource(null, requestPath, Arrays.asList(location), null);
+ assertTrue(location.createRelative(requestPath).exists());
+ assertNull(actual);
}
}
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 07268a3e6a..1fb896af87 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
@@ -16,25 +16,28 @@
package org.springframework.web.servlet.resource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.servlet.HandlerMapping;
-import static org.junit.Assert.*;
-
/**
* Unit tests for ResourceHttpRequestHandler.
*
@@ -126,23 +129,94 @@ public class ResourceHttpRequestHandlerTests {
}
@Test
- public void getResourceViaDirectoryTraversal() throws Exception {
- this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "../testsecret/secret.txt");
- this.handler.handleRequest(this.request, this.response);
- assertEquals(404, this.response.getStatus());
+ public void invalidPath() throws Exception {
- this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "test/../../testsecret/secret.txt");
+ Resource location = new ClassPathResource("test/", getClass());
+ this.handler.setLocations(Arrays.asList(location));
+
+ testInvalidPath(location, "../testsecret/secret.txt");
+ testInvalidPath(location, "test/../../testsecret/secret.txt");
+ testInvalidPath(location, ":/../../testsecret/secret.txt");
+
+ location = new UrlResource(getClass().getResource("./test/"));
+ this.handler.setLocations(Arrays.asList(location));
+ Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
+ String secretPath = secretResource.getURL().getPath();
+
+ testInvalidPath(location, "file:" + secretPath);
+ testInvalidPath(location, "/file:" + secretPath);
+ testInvalidPath(location, "url:" + secretPath);
+ testInvalidPath(location, "/url:" + secretPath);
+ testInvalidPath(location, "/" + secretPath);
+ testInvalidPath(location, "////../.." + secretPath);
+ testInvalidPath(location, "/%2E%2E/testsecret/secret.txt");
+ testInvalidPath(location, "/ " + secretPath);
+ testInvalidPath(location, "url:" + secretPath);
+ }
+
+ @Test
+ public void ignoreInvalidEscapeSequence() throws Exception {
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/%foo%/bar.txt");
this.response = new MockHttpServletResponse();
this.handler.handleRequest(this.request, this.response);
assertEquals(404, this.response.getStatus());
+ }
- this.handler.setLocations(Arrays.asList(new ClassPathResource("testsecret/", getClass())));
- this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "secret.txt");
- this.response = new MockHttpServletResponse();
- this.handler.handleRequest(this.request, this.response);
- assertEquals(200, this.response.getStatus());
- assertEquals("text/plain", this.response.getContentType());
- assertEquals("big secret", this.response.getContentAsString());
+ @Test
+ public void processPath() throws Exception {
+ assertSame("/foo/bar", this.handler.processPath("/foo/bar"));
+ assertSame("foo/bar", this.handler.processPath("foo/bar"));
+
+ // leading whitespace control characters (00-1F)
+ assertEquals("/foo/bar", this.handler.processPath(" /foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath((char) 1 + "/foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath((char) 31 + "/foo/bar"));
+ assertEquals("foo/bar", this.handler.processPath(" foo/bar"));
+ assertEquals("foo/bar", this.handler.processPath((char) 31 + "foo/bar"));
+
+ // leading control character 0x7F (DEL)
+ assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
+
+ // leading control and '/' characters
+ assertEquals("/foo/bar", this.handler.processPath(" / foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath(" / / foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath(" // /// //// foo/bar"));
+ assertEquals("/foo/bar", this.handler.processPath((char) 1 + " / " + (char) 127 + " // foo/bar"));
+
+ // root or empty path
+ assertEquals("", this.handler.processPath(" "));
+ assertEquals("/", this.handler.processPath("/"));
+ assertEquals("/", this.handler.processPath("///"));
+ assertEquals("/", this.handler.processPath("/ / / "));
+ }
+
+ @Test
+ public void initAllowedLocations() throws Exception {
+ PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0);
+ Resource[] locations = resolver.getAllowedLocations();
+
+ assertEquals(2, locations.length);
+ assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
+ assertEquals("testalternatepath/", ((ClassPathResource) locations[1]).getPath());
+ }
+
+ @Test
+ public void initAllowedLocationsWithExplicitConfiguration() throws Exception {
+ ClassPathResource location1 = new ClassPathResource("test/", getClass());
+ ClassPathResource location2 = new ClassPathResource("testalternatepath/", getClass());
+
+ PathResourceResolver pathResolver = new PathResourceResolver();
+ pathResolver.setAllowedLocations(location1);
+
+ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
+ handler.setResourceResolvers(Arrays.asList(pathResolver));
+ handler.setLocations(Arrays.asList(location1, location2));
+ handler.afterPropertiesSet();
+
+ Resource[] locations = pathResolver.getAllowedLocations();
+ assertEquals(1, locations.length);
+ assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
}
@Test
@@ -203,6 +277,14 @@ public class ResourceHttpRequestHandlerTests {
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
}
+ private void testInvalidPath(Resource location, String requestPath) throws Exception {
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath);
+ this.response = new MockHttpServletResponse();
+ this.handler.handleRequest(this.request, this.response);
+ assertTrue(location.createRelative(requestPath).exists());
+ assertEquals(404, this.response.getStatus());
+ }
+
private static class TestServletContext extends MockServletContext {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceTransformerSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceTransformerSupportTests.java
index 3ed6209cfe..977a70fade 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceTransformerSupportTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceTransformerSupportTests.java
@@ -46,9 +46,11 @@ public class ResourceTransformerSupportTests {
@Before
public void setUp() {
- VersionResourceResolver resolver = new VersionResourceResolver();
- resolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
- List resolvers = Arrays.asList(resolver, new PathResourceResolver());
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
+ PathResourceResolver pathResolver = new PathResourceResolver();
+ pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
+ List resolvers = Arrays.asList(versionResolver, pathResolver);
this.transformerChain = new DefaultResourceTransformerChain(new DefaultResourceResolverChain(resolvers), null);
this.transformer = new TestResourceTransformerSupport();