@ -17,6 +17,7 @@
@@ -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;
@@ -66,24 +67,23 @@ import org.springframework.web.util.UrlPathHelper;
* according to the guidelines of Page Speed , YSlow , etc .
*
* < p > The { @linkplain # setLocations "locations" } property takes a list of Spring
* { @link Resource } locations from which static resources are allowed to
* be served b y 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 .
*
* < p > 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 .
*
* < p > 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 .
* < p > 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
@@ -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
@@ -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 .
* < p > The default implementation replaces :
* < ul >
* < li > Backslash with forward slash .
* < li > Duplicate occurrences of slash with a single slash .
* < li > Any combination of leading slash and control characters ( 00 - 1F and 7F )
* with a single "/" or "" . For example { @code " / // foo/bar" }
* becomes { @code "/foo/bar" } .
* < / ul >
* @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
@@ -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
@@ -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 :
* < ul >
@ -627,36 +621,45 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
@@ -627,36 +621,45 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
* @return { @code true } if the path is invalid , { @code false } otherwise
* /
protected boolean isInvalidPath ( String path ) {
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\"." ) ;
}
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." ) ;
}
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." ) ;
}
logger . trace ( "Path contains \"../\" after call to StringUtils#cleanPath." ) ;
return true ;
}
}
return false ;
}
/ * *
* Process the given resource path .
* < p > The default implementation replaces :
* < ul >
* < li > Backslash with forward slash .
* < li > Duplicate occurrences of slash with a single slash .
* < li > Any combination of leading slash and control characters ( 00 - 1F and 7F )
* with a single "/" or "" . For example { @code " / // foo/bar" }
* becomes { @code "/foo/bar" } .
* < / ul >
* @since 3 . 2 . 12
* /
protected String processPath ( String path ) {
path = StringUtils . replace ( path , "\\" , "/" ) ;
path = cleanDuplicateSlashes ( path ) ;
return cleanLeadingSlash ( path ) ;
}
/ * *
* Determine the media type for the given request and the resource matched
* to it . This implementation tries to determine the MediaType based on the