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 7543d91d4f6..5e9753fc3be 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -17,6 +17,7 @@ package org.springframework.web.servlet.resource; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -251,21 +252,7 @@ public class PathResourceResolver extends AbstractResourceResolver { return true; } 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; + return (resourcePath.startsWith(locationPath) && !isInvalidEncodedPath(resourcePath)); } private String encodeIfNecessary(String path, @Nullable HttpServletRequest request, Resource location) { @@ -289,8 +276,30 @@ public class PathResourceResolver extends AbstractResourceResolver { } private boolean shouldEncodeRelativePath(Resource location) { - return location instanceof UrlResource && - this.urlPathHelper != null && this.urlPathHelper.isUrlDecode(); + return (location instanceof UrlResource && this.urlPathHelper != null && this.urlPathHelper.isUrlDecode()); + } + + private boolean isInvalidEncodedPath(String resourcePath) { + if (resourcePath.contains("%")) { + // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars... + try { + String decodedPath = URLDecoder.decode(resourcePath, "UTF-8"); + int separatorIndex = decodedPath.indexOf("..") + 2; + if (separatorIndex > 1 && separatorIndex < decodedPath.length()) { + char separator = decodedPath.charAt(separatorIndex); + if (separator == '/' || separator == '\\') { + if (logger.isTraceEnabled()) { + logger.trace("Resolved resource path contains \"../\" after decoding: " + resourcePath); + } + } + return true; + } + } + catch (UnsupportedEncodingException ex) { + // Should never happen... + } + } + return false; } } 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 80731c37b65..0691dd5ce2a 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 @@ -17,6 +17,7 @@ package org.springframework.web.servlet.resource; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.ArrayList; @@ -66,24 +67,23 @@ import org.springframework.web.util.UrlPathHelper; * according to the guidelines of Page Speed, YSlow, etc. * *

The {@linkplain #setLocations "locations"} property takes a list of Spring - * {@link Resource} locations from which static resources are allowed to - * be served by this handler. Resources could be served from a classpath location, - * e.g. "classpath:/META-INF/public-web-resources/", allowing convenient packaging + * {@link Resource} locations from which static resources are allowed to be served + * by this handler. Resources could be served from a classpath location, e.g. + * "classpath:/META-INF/public-web-resources/", allowing convenient packaging * and serving of resources such as .js, .css, and others in jar files. * *

This request handler may also be configured with a * {@link #setResourceResolvers(List) resourcesResolver} and * {@link #setResourceTransformers(List) resourceTransformer} chains to support - * arbitrary resolution and transformation of resources being served. By default a - * {@link PathResourceResolver} simply finds resources based on the configured - * "locations". An application can configure additional resolvers and - * transformers such as the {@link VersionResourceResolver} which can resolve - * and prepare URLs for resources with a version in the URL. + * arbitrary resolution and transformation of resources being served. By default + * a {@link PathResourceResolver} simply finds resources based on the configured + * "locations". An application can configure additional resolvers and transformers + * such as the {@link VersionResourceResolver} which can resolve and prepare URLs + * for resources with a version in the URL. * - *

This handler also properly evaluates the {@code Last-Modified} header (if - * present) so that a {@code 304} status code will be returned as appropriate, - * avoiding unnecessary overhead for resources that are already cached by the - * client. + *

This handler also properly evaluates the {@code Last-Modified} header + * (if present) so that a {@code 304} status code will be returned as appropriate, + * avoiding unnecessary overhead for resources that are already cached by the client. * * @author Keith Donald * @author Jeremy Grelle @@ -510,6 +510,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator 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()) { @@ -517,57 +518,25 @@ public class ResourceHttpRequestHandler extends WebContentGenerator } return null; } - if (path.contains("%")) { - try { - // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars - String decodedPath = URLDecoder.decode(path, "UTF-8"); - if (isInvalidPath(decodedPath)) { - if (logger.isTraceEnabled()) { - logger.trace("Ignoring invalid resource path with escape sequences [" + path + "]."); - } - return null; - } - decodedPath = processPath(decodedPath); - if (isInvalidPath(decodedPath)) { - if (logger.isTraceEnabled()) { - logger.trace("Ignoring invalid resource path with escape sequences [" + path + "]."); - } - return null; - } - } - catch (IllegalArgumentException ex) { - // ignore + if (isInvalidEncodedPath(path)) { + if (logger.isTraceEnabled()) { + logger.trace("Ignoring invalid resource path with escape sequences [" + path + "]"); } + return null; } + ResourceResolverChain resolveChain = new DefaultResourceResolverChain(getResourceResolvers()); Resource resource = resolveChain.resolveResource(request, path, getLocations()); if (resource == null || getResourceTransformers().isEmpty()) { return resource; } + ResourceTransformerChain transformChain = new DefaultResourceTransformerChain(resolveChain, getResourceTransformers()); resource = transformChain.transform(request, resource); return resource; } - /** - * Process the given resource path. - *

The default implementation replaces: - *

- * @since 3.2.12 - */ - protected String processPath(String path) { - path = StringUtils.replace(path, "\\", "/"); - path = cleanDuplicateSlashes(path); - return cleanLeadingSlash(path); - } - private String cleanDuplicateSlashes(String path) { StringBuilder sb = null; char prev = 0; @@ -601,9 +570,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator if (i == 0 || (i == 1 && slash)) { return path; } - path = slash ? "/" + path.substring(i) : path.substring(i); + path = (slash ? "/" + path.substring(i) : path.substring(i)); if (logger.isTraceEnabled()) { - logger.trace("Path after trimming leading '/' and control characters: " + path); + logger.trace("Path after trimming leading '/' and control characters: [" + path + "]"); } return path; } @@ -611,6 +580,31 @@ public class ResourceHttpRequestHandler extends WebContentGenerator return (slash ? "/" : ""); } + /** + * Check whether the given path contains invalid escape sequences. + * @param path the path to validate + * @return {@code true} if the path is invalid, {@code false} otherwise + */ + private boolean isInvalidEncodedPath(String path) { + if (path.contains("%")) { + try { + // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars + String decodedPath = URLDecoder.decode(path, "UTF-8"); + if (isInvalidPath(decodedPath)) { + return true; + } + decodedPath = processPath(decodedPath); + if (isInvalidPath(decodedPath)) { + return true; + } + } + catch (IllegalArgumentException | UnsupportedEncodingException ex) { + // Should never happen... + } + } + return false; + } + /** * Identifies invalid resource paths. By default rejects: *