Skip to content

Commit feeec34

Browse files
committed
ForwardedHeaderFilter works with Servlet FORWARD
Issue: SPR-16983
1 parent 51bb96d commit feeec34

File tree

2 files changed

+135
-29
lines changed

2 files changed

+135
-29
lines changed

spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Locale;
2424
import java.util.Map;
2525
import java.util.Set;
26+
import java.util.function.Supplier;
2627
import javax.servlet.FilterChain;
2728
import javax.servlet.ServletException;
2829
import javax.servlet.http.HttpServletRequest;
@@ -219,11 +220,7 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem
219220

220221
private final int port;
221222

222-
private final String contextPath;
223-
224-
private final String requestUri;
225-
226-
private final String requestUrl;
223+
private final ForwardedPrefixExtractor forwardedPrefixExtractor;
227224

228225

229226
ForwardedHeaderExtractingRequest(HttpServletRequest request, UrlPathHelper pathHelper) {
@@ -238,28 +235,9 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem
238235
this.host = uriComponents.getHost();
239236
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);
240237

241-
String prefix = getForwardedPrefix(request);
242-
this.contextPath = (prefix != null ? prefix : request.getContextPath());
243-
this.requestUri = this.contextPath + pathHelper.getPathWithinApplication(request);
244-
this.requestUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port) + this.requestUri;
245-
}
246-
247-
@Nullable
248-
private static String getForwardedPrefix(HttpServletRequest request) {
249-
String prefix = null;
250-
Enumeration<String> names = request.getHeaderNames();
251-
while (names.hasMoreElements()) {
252-
String name = names.nextElement();
253-
if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
254-
prefix = request.getHeader(name);
255-
}
256-
}
257-
if (prefix != null) {
258-
while (prefix.endsWith("/")) {
259-
prefix = prefix.substring(0, prefix.length() - 1);
260-
}
261-
}
262-
return prefix;
238+
String baseUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port);
239+
Supplier<HttpServletRequest> delegateRequest = () -> (HttpServletRequest) getRequest();
240+
this.forwardedPrefixExtractor = new ForwardedPrefixExtractor(delegateRequest, pathHelper, baseUrl);
263241
}
264242

265243

@@ -287,18 +265,122 @@ public boolean isSecure() {
287265

288266
@Override
289267
public String getContextPath() {
290-
return this.contextPath;
268+
return this.forwardedPrefixExtractor.getContextPath();
291269
}
292270

293271
@Override
294272
public String getRequestURI() {
295-
return this.requestUri;
273+
return this.forwardedPrefixExtractor.getRequestUri();
296274
}
297275

298276
@Override
299277
public StringBuffer getRequestURL() {
278+
return this.forwardedPrefixExtractor.getRequestUrl();
279+
}
280+
}
281+
282+
283+
/**
284+
* Responsible for the contextPath, requestURI, and requestURL with forwarded
285+
* headers in mind, and also taking into account changes to the path of the
286+
* underlying delegate request (e.g. on a Servlet FORWARD).
287+
*/
288+
private static class ForwardedPrefixExtractor {
289+
290+
private final Supplier<HttpServletRequest> delegate;
291+
292+
private final UrlPathHelper pathHelper;
293+
294+
private final String baseUrl;
295+
296+
private String actualRequestUri;
297+
298+
@Nullable
299+
private final String forwardedPrefix;
300+
301+
@Nullable
302+
private String requestUri;
303+
304+
private String requestUrl;
305+
306+
307+
/**
308+
* Constructor with required information.
309+
* @param delegateRequest supplier for the current
310+
* {@link HttpServletRequestWrapper#getRequest() delegate request} which
311+
* may change during a forward (e.g. Tocat.
312+
* @param pathHelper the path helper instance
313+
* @param baseUrl the host, scheme, and port based on forwarded headers
314+
*/
315+
public ForwardedPrefixExtractor(
316+
Supplier<HttpServletRequest> delegateRequest, UrlPathHelper pathHelper, String baseUrl) {
317+
318+
this.delegate = delegateRequest;
319+
this.pathHelper = pathHelper;
320+
this.baseUrl = baseUrl;
321+
this.actualRequestUri = delegateRequest.get().getRequestURI();
322+
323+
this.forwardedPrefix = initForwardedPrefix(delegateRequest.get());
324+
this.requestUri = initRequestUri();
325+
this.requestUrl = initRequestUrl(); // Keep the order: depends on requestUri
326+
}
327+
328+
@Nullable
329+
private static String initForwardedPrefix(HttpServletRequest request) {
330+
String result = null;
331+
Enumeration<String> names = request.getHeaderNames();
332+
while (names.hasMoreElements()) {
333+
String name = names.nextElement();
334+
if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
335+
result = request.getHeader(name);
336+
}
337+
}
338+
if (result != null) {
339+
while (result.endsWith("/")) {
340+
result = result.substring(0, result.length() - 1);
341+
}
342+
}
343+
return result;
344+
}
345+
346+
@Nullable
347+
private String initRequestUri() {
348+
if (this.forwardedPrefix != null) {
349+
return this.forwardedPrefix + this.pathHelper.getPathWithinApplication(this.delegate.get());
350+
}
351+
return null;
352+
}
353+
354+
private String initRequestUrl() {
355+
return this.baseUrl + (this.requestUri != null ? this.requestUri : this.delegate.get().getRequestURI());
356+
}
357+
358+
359+
public String getContextPath() {
360+
return this.forwardedPrefix == null ? this.delegate.get().getContextPath() : this.forwardedPrefix;
361+
}
362+
363+
public String getRequestUri() {
364+
if (this.requestUri == null) {
365+
return this.delegate.get().getRequestURI();
366+
}
367+
recalculatePathsIfNecesary();
368+
return this.requestUri;
369+
}
370+
371+
public StringBuffer getRequestUrl() {
372+
recalculatePathsIfNecesary();
300373
return new StringBuffer(this.requestUrl);
301374
}
375+
376+
private void recalculatePathsIfNecesary() {
377+
if (!this.actualRequestUri.equals(this.delegate.get().getRequestURI())) {
378+
// Underlying path change (e.g. Servlet FORWARD).
379+
this.actualRequestUri = this.delegate.get().getRequestURI();
380+
this.requestUri = initRequestUri();
381+
this.requestUrl = initRequestUrl(); // Keep the order: depends on requestUri
382+
}
383+
}
302384
}
303385

304386

spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.springframework.web.filter;
1818

1919
import java.io.IOException;
20+
import java.net.URI;
2021
import java.util.Enumeration;
22+
import javax.servlet.DispatcherType;
2123
import javax.servlet.Filter;
2224
import javax.servlet.FilterChain;
2325
import javax.servlet.ServletException;
@@ -308,6 +310,28 @@ public void forwardedRequestWithSsl() throws Exception {
308310
assertEquals("bar", actual.getHeader("foo"));
309311
}
310312

313+
@Test // SPR-16983
314+
public void forwardedRequestWithServletForward() throws Exception {
315+
this.request.setRequestURI("/foo");
316+
this.request.addHeader(X_FORWARDED_PROTO, "https");
317+
this.request.addHeader(X_FORWARDED_HOST, "www.mycompany.com");
318+
this.request.addHeader(X_FORWARDED_PORT, "443");
319+
320+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
321+
HttpServletRequest wrappedRequest = (HttpServletRequest) this.filterChain.getRequest();
322+
323+
this.request.setDispatcherType(DispatcherType.FORWARD);
324+
this.request.setRequestURI("/bar");
325+
this.filterChain.reset();
326+
327+
this.filter.doFilter(wrappedRequest, new MockHttpServletResponse(), this.filterChain);
328+
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
329+
330+
assertNotNull(actual);
331+
assertEquals("/bar", actual.getRequestURI());
332+
assertEquals("https://www.mycompany.com/bar", actual.getRequestURL().toString());
333+
}
334+
311335
@Test
312336
public void requestUriWithForwardedPrefix() throws Exception {
313337
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");

0 commit comments

Comments
 (0)