Skip to content

Commit 50a4769

Browse files
committed
Fix ResourceUrlEncodingFilter lifecycle
Prior to this commit, the `ResourceUrlEncodingFilter` would wrap the response and keep a reference to the request. When `HttpServletResponse.encodeURL` is later called during view rendering, the filter looks at the request and extracts context mapping information in order to resolve resource paths in views. This approach is flawed, when the filter is used with JSPs - if the request is forwarded to the container by the `InternalResourceView`, the request information is overwritten by the container. When the view is being rendered, the information available in the request is outdated and does not allow to correctly compute that context mapping information. This commit ensures that that information is being extracted from the request as soon as the `ResourceUrlProvider` is set as a request attribute. Issue: SPR-17421
1 parent ffa032e commit 50a4769

File tree

3 files changed

+94
-53
lines changed

3 files changed

+94
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import javax.servlet.ServletRequest;
2323
import javax.servlet.ServletResponse;
2424
import javax.servlet.http.HttpServletRequest;
25+
import javax.servlet.http.HttpServletRequestWrapper;
2526
import javax.servlet.http.HttpServletResponse;
2627
import javax.servlet.http.HttpServletResponseWrapper;
2728

@@ -53,74 +54,75 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean {
5354
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
5455
throws IOException, ServletException {
5556
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
56-
throw new ServletException("ResourceUrlEncodingFilter just supports HTTP requests");
57+
throw new ServletException("ResourceUrlEncodingFilter only supports HTTP requests");
5758
}
58-
HttpServletRequest httpRequest = (HttpServletRequest) request;
59-
HttpServletResponse httpResponse = (HttpServletResponse) response;
60-
filterChain.doFilter(httpRequest, new ResourceUrlEncodingResponseWrapper(httpRequest, httpResponse));
59+
ResourceUrlEncodingRequestWrapper wrappedRequest =
60+
new ResourceUrlEncodingRequestWrapper((HttpServletRequest) request);
61+
ResourceUrlEncodingResponseWrapper wrappedResponse =
62+
new ResourceUrlEncodingResponseWrapper(wrappedRequest, (HttpServletResponse) response);
63+
filterChain.doFilter(wrappedRequest, wrappedResponse);
6164
}
6265

66+
private static class ResourceUrlEncodingRequestWrapper extends HttpServletRequestWrapper {
6367

64-
private static class ResourceUrlEncodingResponseWrapper extends HttpServletResponseWrapper {
65-
66-
private final HttpServletRequest request;
68+
@Nullable
69+
private ResourceUrlProvider resourceUrlProvider;
6770

68-
/* Cache the index and prefix of the path within the DispatcherServlet mapping */
6971
@Nullable
7072
private Integer indexLookupPath;
7173

7274
private String prefixLookupPath = "";
7375

74-
public ResourceUrlEncodingResponseWrapper(HttpServletRequest request, HttpServletResponse wrapped) {
75-
super(wrapped);
76-
this.request = request;
76+
ResourceUrlEncodingRequestWrapper(HttpServletRequest request) {
77+
super(request);
7778
}
7879

7980
@Override
80-
public String encodeURL(String url) {
81-
ResourceUrlProvider resourceUrlProvider = getResourceUrlProvider();
82-
if (resourceUrlProvider == null) {
83-
logger.trace("ResourceUrlProvider not available via " +
84-
"request attribute ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR");
85-
return super.encodeURL(url);
86-
}
87-
88-
int index = initLookupPath(resourceUrlProvider);
89-
if (url.startsWith(this.prefixLookupPath)) {
90-
int suffixIndex = getQueryParamsIndex(url);
91-
String suffix = url.substring(suffixIndex);
92-
String lookupPath = url.substring(index, suffixIndex);
93-
lookupPath = resourceUrlProvider.getForLookupPath(lookupPath);
94-
if (lookupPath != null) {
95-
return super.encodeURL(this.prefixLookupPath + lookupPath + suffix);
81+
public void setAttribute(String name, Object o) {
82+
super.setAttribute(name, o);
83+
if (ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR.equals(name)) {
84+
if(o instanceof ResourceUrlProvider) {
85+
initLookupPath((ResourceUrlProvider) o);
9686
}
9787
}
9888

99-
return super.encodeURL(url);
100-
}
101-
102-
@Nullable
103-
private ResourceUrlProvider getResourceUrlProvider() {
104-
return (ResourceUrlProvider) this.request.getAttribute(
105-
ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR);
10689
}
10790

108-
private int initLookupPath(ResourceUrlProvider urlProvider) {
91+
private void initLookupPath(ResourceUrlProvider urlProvider) {
92+
this.resourceUrlProvider = urlProvider;
10993
if (this.indexLookupPath == null) {
110-
UrlPathHelper pathHelper = urlProvider.getUrlPathHelper();
111-
String requestUri = pathHelper.getRequestUri(this.request);
112-
String lookupPath = pathHelper.getLookupPathForRequest(this.request);
94+
UrlPathHelper pathHelper = this.resourceUrlProvider.getUrlPathHelper();
95+
String requestUri = pathHelper.getRequestUri(this);
96+
String lookupPath = pathHelper.getLookupPathForRequest(this);
11397
this.indexLookupPath = requestUri.lastIndexOf(lookupPath);
11498
this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath);
11599
if ("/".equals(lookupPath) && !"/".equals(requestUri)) {
116-
String contextPath = pathHelper.getContextPath(this.request);
100+
String contextPath = pathHelper.getContextPath(this);
117101
if (requestUri.equals(contextPath)) {
118102
this.indexLookupPath = requestUri.length();
119103
this.prefixLookupPath = requestUri;
120104
}
121105
}
122106
}
123-
return this.indexLookupPath;
107+
}
108+
109+
@Nullable
110+
public String resolveUrlPath(String url) {
111+
if (this.resourceUrlProvider == null) {
112+
logger.trace("ResourceUrlProvider not available via " +
113+
"request attribute ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR");
114+
return null;
115+
}
116+
if (url.startsWith(this.prefixLookupPath)) {
117+
int suffixIndex = getQueryParamsIndex(url);
118+
String suffix = url.substring(suffixIndex);
119+
String lookupPath = url.substring(this.indexLookupPath, suffixIndex);
120+
lookupPath = this.resourceUrlProvider.getForLookupPath(lookupPath);
121+
if (lookupPath != null) {
122+
return this.prefixLookupPath + lookupPath + suffix;
123+
}
124+
}
125+
return null;
124126
}
125127

126128
private int getQueryParamsIndex(String url) {
@@ -129,4 +131,24 @@ private int getQueryParamsIndex(String url) {
129131
}
130132
}
131133

134+
135+
private static class ResourceUrlEncodingResponseWrapper extends HttpServletResponseWrapper {
136+
137+
private final ResourceUrlEncodingRequestWrapper request;
138+
139+
ResourceUrlEncodingResponseWrapper(ResourceUrlEncodingRequestWrapper request, HttpServletResponse wrapped) {
140+
super(wrapped);
141+
this.request = request;
142+
}
143+
144+
@Override
145+
public String encodeURL(String url) {
146+
String urlPath = this.request.resolveUrlPath(url);
147+
if (urlPath != null) {
148+
return super.encodeURL(urlPath);
149+
}
150+
return super.encodeURL(url);
151+
}
152+
}
153+
132154
}

spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ private ResourceUrlProvider createResourceUrlProvider(List<ResourceResolver> res
6969
@Test
7070
public void encodeURL() throws Exception {
7171
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
72-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
7372
MockHttpServletResponse response = new MockHttpServletResponse();
7473

7574
this.filter.doFilter(request, response, (req, res) -> {
75+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
7676
String result = ((HttpServletResponse) res).encodeURL("/resources/bar.css");
7777
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result);
7878
});
@@ -82,10 +82,26 @@ public void encodeURL() throws Exception {
8282
public void encodeURLWithContext() throws Exception {
8383
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo");
8484
request.setContextPath("/context");
85-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
8685
MockHttpServletResponse response = new MockHttpServletResponse();
8786

8887
this.filter.doFilter(request, response, (req, res) -> {
88+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
89+
String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css");
90+
assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result);
91+
});
92+
}
93+
94+
95+
@Test
96+
public void encodeUrlWithContextAndForwardedRequest() throws Exception {
97+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo");
98+
request.setContextPath("/context");
99+
MockHttpServletResponse response = new MockHttpServletResponse();
100+
101+
this.filter.doFilter(request, response, (req, res) -> {
102+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
103+
request.setRequestURI("/forwarded");
104+
request.setContextPath("/");
89105
String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css");
90106
assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result);
91107
});
@@ -95,10 +111,10 @@ public void encodeURLWithContext() throws Exception {
95111
public void encodeContextPathUrlWithoutSuffix() throws Exception {
96112
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context");
97113
request.setContextPath("/context");
98-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
99114
MockHttpServletResponse response = new MockHttpServletResponse();
100115

101116
this.filter.doFilter(request, response, (req, res) -> {
117+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
102118
String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css");
103119
assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result);
104120
});
@@ -108,10 +124,10 @@ public void encodeContextPathUrlWithoutSuffix() throws Exception {
108124
public void encodeContextPathUrlWithSuffix() throws Exception {
109125
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/");
110126
request.setContextPath("/context");
111-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
112127
MockHttpServletResponse response = new MockHttpServletResponse();
113128

114129
this.filter.doFilter(request, response, (req, res) -> {
130+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
115131
String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css");
116132
assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result);
117133
});
@@ -121,10 +137,10 @@ public void encodeContextPathUrlWithSuffix() throws Exception {
121137
public void encodeEmptyURLWithContext() throws Exception {
122138
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo");
123139
request.setContextPath("/context");
124-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
125140
MockHttpServletResponse response = new MockHttpServletResponse();
126141

127142
this.filter.doFilter(request, response, (req, res) -> {
143+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
128144
String result = ((HttpServletResponse) res).encodeURL("?foo=1");
129145
assertEquals("?foo=1", result);
130146
});
@@ -134,10 +150,10 @@ public void encodeEmptyURLWithContext() throws Exception {
134150
public void encodeURLWithRequestParams() throws Exception {
135151
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
136152
request.setContextPath("/");
137-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
138153
MockHttpServletResponse response = new MockHttpServletResponse();
139154

140155
this.filter.doFilter(request, response, (req, res) -> {
156+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
141157
String result = ((HttpServletResponse) res).encodeURL("/resources/bar.css?foo=bar&url=http://example.org");
142158
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css?foo=bar&url=http://example.org", result);
143159
});
@@ -148,10 +164,10 @@ public void encodeUrlPreventStringOutOfBounds() throws Exception {
148164
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context-path/index");
149165
request.setContextPath("/context-path");
150166
request.setServletPath("");
151-
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
152167
MockHttpServletResponse response = new MockHttpServletResponse();
153168

154169
this.filter.doFilter(request, response, (req, res) -> {
170+
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
155171
String result = ((HttpServletResponse) res).encodeURL("index?key=value");
156172
assertEquals("index?key=value", result);
157173
});

spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,8 +55,6 @@ public class ResourceUrlProviderJavaConfigTests {
5555
@Before
5656
@SuppressWarnings("resource")
5757
public void setup() throws Exception {
58-
this.filterChain = new MockFilterChain(this.servlet, new ResourceUrlEncodingFilter());
59-
6058
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
6159
context.setServletContext(new MockServletContext());
6260
context.register(WebConfig.class);
@@ -66,8 +64,13 @@ public void setup() throws Exception {
6664
this.request.setContextPath("/myapp");
6765
this.response = new MockHttpServletResponse();
6866

69-
Object urlProvider = context.getBean(ResourceUrlProvider.class);
70-
this.request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, urlProvider);
67+
this.filterChain = new MockFilterChain(this.servlet,
68+
new ResourceUrlEncodingFilter(),
69+
(request, response, chain) -> {
70+
Object urlProvider = context.getBean(ResourceUrlProvider.class);
71+
request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, urlProvider);
72+
chain.doFilter(request, response);
73+
});
7174
}
7275

7376
@Test

0 commit comments

Comments
 (0)