001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.resource; 018 019import java.io.IOException; 020import javax.servlet.FilterChain; 021import javax.servlet.ServletException; 022import javax.servlet.ServletRequest; 023import javax.servlet.ServletResponse; 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletRequestWrapper; 026import javax.servlet.http.HttpServletResponse; 027import javax.servlet.http.HttpServletResponseWrapper; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.util.Assert; 033import org.springframework.web.filter.GenericFilterBean; 034import org.springframework.web.util.UrlPathHelper; 035 036/** 037 * A filter that wraps the {@link HttpServletResponse} and overrides its 038 * {@link HttpServletResponse#encodeURL(String) encodeURL} method in order to 039 * translate internal resource request URLs into public URL paths for external use. 040 * 041 * @author Jeremy Grelle 042 * @author Rossen Stoyanchev 043 * @author Sam Brannen 044 * @author Brian Clozel 045 * @since 4.1 046 */ 047public class ResourceUrlEncodingFilter extends GenericFilterBean { 048 049 private static final Log logger = LogFactory.getLog(ResourceUrlEncodingFilter.class); 050 051 052 @Override 053 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 054 throws ServletException, IOException { 055 056 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { 057 throw new ServletException("ResourceUrlEncodingFilter only supports HTTP requests"); 058 } 059 ResourceUrlEncodingRequestWrapper wrappedRequest = 060 new ResourceUrlEncodingRequestWrapper((HttpServletRequest) request); 061 ResourceUrlEncodingResponseWrapper wrappedResponse = 062 new ResourceUrlEncodingResponseWrapper(wrappedRequest, (HttpServletResponse) response); 063 filterChain.doFilter(wrappedRequest, wrappedResponse); 064 } 065 066 067 private static class ResourceUrlEncodingRequestWrapper extends HttpServletRequestWrapper { 068 069 private ResourceUrlProvider resourceUrlProvider; 070 071 private Integer indexLookupPath; 072 073 private String prefixLookupPath; 074 075 ResourceUrlEncodingRequestWrapper(HttpServletRequest request) { 076 super(request); 077 } 078 079 @Override 080 public void setAttribute(String name, Object value) { 081 super.setAttribute(name, value); 082 if (ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR.equals(name)) { 083 if (value instanceof ResourceUrlProvider) { 084 initLookupPath((ResourceUrlProvider) value); 085 } 086 } 087 } 088 089 private void initLookupPath(ResourceUrlProvider urlProvider) { 090 this.resourceUrlProvider = urlProvider; 091 if (this.indexLookupPath == null) { 092 UrlPathHelper pathHelper = this.resourceUrlProvider.getUrlPathHelper(); 093 String requestUri = pathHelper.getRequestUri(this); 094 String lookupPath = pathHelper.getLookupPathForRequest(this); 095 this.indexLookupPath = requestUri.lastIndexOf(lookupPath); 096 if (this.indexLookupPath == -1) { 097 throw new IllegalStateException( 098 "Failed to find lookupPath '" + lookupPath + "' within requestUri '" + requestUri + "'. " + 099 "Does the path have invalid encoded characters for characterEncoding '" + 100 getRequest().getCharacterEncoding() + "'?"); 101 } 102 this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath); 103 if ("/".equals(lookupPath) && !"/".equals(requestUri)) { 104 String contextPath = pathHelper.getContextPath(this); 105 if (requestUri.equals(contextPath)) { 106 this.indexLookupPath = requestUri.length(); 107 this.prefixLookupPath = requestUri; 108 } 109 } 110 } 111 } 112 113 public String resolveUrlPath(String url) { 114 if (this.resourceUrlProvider == null) { 115 logger.trace("ResourceUrlProvider not available via request attribute " + 116 ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR); 117 return null; 118 } 119 if (this.indexLookupPath != null && url.startsWith(this.prefixLookupPath)) { 120 int suffixIndex = getEndPathIndex(url); 121 String suffix = url.substring(suffixIndex); 122 String lookupPath = url.substring(this.indexLookupPath, suffixIndex); 123 lookupPath = this.resourceUrlProvider.getForLookupPath(lookupPath); 124 if (lookupPath != null) { 125 return this.prefixLookupPath + lookupPath + suffix; 126 } 127 } 128 return null; 129 } 130 131 private int getEndPathIndex(String path) { 132 int end = path.indexOf('?'); 133 int fragmentIndex = path.indexOf('#'); 134 if (fragmentIndex != -1 && (end == -1 || fragmentIndex < end)) { 135 end = fragmentIndex; 136 } 137 if (end == -1) { 138 end = path.length(); 139 } 140 return end; 141 } 142 } 143 144 145 private static class ResourceUrlEncodingResponseWrapper extends HttpServletResponseWrapper { 146 147 private final ResourceUrlEncodingRequestWrapper request; 148 149 ResourceUrlEncodingResponseWrapper(ResourceUrlEncodingRequestWrapper request, HttpServletResponse wrapped) { 150 super(wrapped); 151 this.request = request; 152 } 153 154 @Override 155 public String encodeURL(String url) { 156 String urlPath = this.request.resolveUrlPath(url); 157 if (urlPath != null) { 158 return super.encodeURL(urlPath); 159 } 160 return super.encodeURL(url); 161 } 162 } 163 164}