Skip to content

Commit 608ef46

Browse files
committed
ForwardedHeaderFilter handles query+fragment correctly
Issue: SPR-16506
1 parent d60446a commit 608ef46

File tree

4 files changed

+44
-35
lines changed

4 files changed

+44
-35
lines changed

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

Lines changed: 17 additions & 10 deletions
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.
@@ -100,13 +100,13 @@ public void setRemoveOnly(boolean removeOnly) {
100100
}
101101

102102
/**
103-
* Use this property to enable relative redirects as explained in and also
104-
* using the same response wrapper as {@link RelativeRedirectFilter} does.
105-
* Or if both filters are used, only one will wrap the response.
103+
* Use this property to enable relative redirects as explained in
104+
* {@link RelativeRedirectFilter}, and also using the same response wrapper
105+
* as that filter does, or if both are configured, only one will wrap.
106106
* <p>By default, if this property is set to false, in which case calls to
107107
* {@link HttpServletResponse#sendRedirect(String)} are overridden in order
108-
* to turn relative into absolute URLs since (which Servlet containers are
109-
* also required to do) also taking forwarded headers into consideration.
108+
* to turn relative into absolute URLs, also taking into account forwarded
109+
* headers.
110110
* @param relativeRedirects whether to use relative redirects
111111
* @since 4.3.10
112112
*/
@@ -304,10 +304,12 @@ public ForwardedHeaderExtractingResponse(HttpServletResponse response, HttpServl
304304

305305
@Override
306306
public void sendRedirect(String location) throws IOException {
307+
307308
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(location);
309+
UriComponents uriComponents = builder.build();
308310

309311
// Absolute location
310-
if (builder.build().getScheme() != null) {
312+
if (uriComponents.getScheme() != null) {
311313
super.sendRedirect(location);
312314
return;
313315
}
@@ -319,13 +321,18 @@ public void sendRedirect(String location) throws IOException {
319321
return;
320322
}
321323

322-
// Relative to Servlet container root or to current request
323-
String path = (location.startsWith(FOLDER_SEPARATOR) ? location :
324-
StringUtils.applyRelativePath(this.request.getRequestURI(), location));
324+
String path = uriComponents.getPath();
325+
if (path != null) {
326+
// Relative to Servlet container root or to current request
327+
path = (path.startsWith(FOLDER_SEPARATOR) ? path :
328+
StringUtils.applyRelativePath(this.request.getRequestURI(), path));
329+
}
325330

326331
String result = UriComponentsBuilder
327332
.fromHttpRequest(new ServletServerHttpRequest(this.request))
328333
.replacePath(path)
334+
.replaceQuery(uriComponents.getQuery())
335+
.fragment(uriComponents.getFragment())
329336
.build().normalize().toUriString();
330337

331338
super.sendRedirect(result);

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

Lines changed: 8 additions & 7 deletions
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.
@@ -27,13 +27,14 @@
2727

2828
/**
2929
* Overrides {@link HttpServletResponse#sendRedirect(String)} and handles it by
30-
* setting the HTTP status and "Location" headers. This keeps the Servlet
31-
* container from re-writing relative redirect URLs and instead follows the
32-
* recommendation in <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">
33-
* RFC 7231 Section 7.1.2</a>.
30+
* setting the HTTP status and "Location" headers, which keeps the Servlet
31+
* container from re-writing relative redirect URLs into absolute ones.
32+
* Servlet containers are required to do that but against the recommendation of
33+
* <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2"> RFC 7231 Section 7.1.2</a>,
34+
* and furthermore not necessarily taking into account "X-Forwarded" headers.
3435
*
35-
* <p><strong>Note:</strong> While relative redirects are more efficient they
36-
* may not work with reverse proxies under some configurations.
36+
* <p><strong>Note:</strong> While relative redirects are recommended in the
37+
* RFC, under some configurations with reverse proxies they may not work.
3738
*
3839
* @author Rob Winch
3940
* @author Rossen Stoyanchev

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

Lines changed: 7 additions & 17 deletions
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.
@@ -15,14 +15,13 @@
1515
*/
1616
package org.springframework.web.filter;
1717

18-
import java.io.IOException;
19-
import javax.servlet.ServletResponse;
2018
import javax.servlet.http.HttpServletResponse;
2119
import javax.servlet.http.HttpServletResponseWrapper;
2220

2321
import org.springframework.http.HttpHeaders;
2422
import org.springframework.http.HttpStatus;
2523
import org.springframework.util.Assert;
24+
import org.springframework.web.util.WebUtils;
2625

2726
/**
2827
* A response wrapper used for the implementation of
@@ -44,7 +43,7 @@ private RelativeRedirectResponseWrapper(HttpServletResponse response, HttpStatus
4443

4544

4645
@Override
47-
public void sendRedirect(String location) throws IOException {
46+
public void sendRedirect(String location) {
4847
setStatus(this.redirectStatus.value());
4948
setHeader(HttpHeaders.LOCATION, location);
5049
}
@@ -53,20 +52,11 @@ public void sendRedirect(String location) throws IOException {
5352
public static HttpServletResponse wrapIfNecessary(HttpServletResponse response,
5453
HttpStatus redirectStatus) {
5554

56-
return (hasWrapper(response) ? response : new RelativeRedirectResponseWrapper(response, redirectStatus));
57-
}
55+
RelativeRedirectResponseWrapper wrapper =
56+
WebUtils.getNativeResponse(response, RelativeRedirectResponseWrapper.class);
5857

59-
private static boolean hasWrapper(ServletResponse response) {
60-
if (response instanceof RelativeRedirectResponseWrapper) {
61-
return true;
62-
}
63-
while (response instanceof HttpServletResponseWrapper) {
64-
response = ((HttpServletResponseWrapper) response).getResponse();
65-
if (response instanceof RelativeRedirectResponseWrapper) {
66-
return true;
67-
}
68-
}
69-
return false;
58+
return (wrapper != null ? response :
59+
new RelativeRedirectResponseWrapper(response, redirectStatus));
7060
}
7161

7262
}

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

Lines changed: 12 additions & 1 deletion
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.
@@ -301,6 +301,17 @@ public void sendRedirectWithAbsolutePath() throws Exception {
301301
assertEquals("https://example.com/foo/bar", redirectedUrl);
302302
}
303303

304+
@Test // SPR-16506
305+
public void sendRedirectWithAbsolutePathQueryParamAndFragment() throws Exception {
306+
this.request.addHeader(X_FORWARDED_PROTO, "https");
307+
this.request.addHeader(X_FORWARDED_HOST, "example.com");
308+
this.request.addHeader(X_FORWARDED_PORT, "443");
309+
this.request.setQueryString("oldqp=1");
310+
311+
String redirectedUrl = sendRedirect("/foo/bar?newqp=2#fragment");
312+
assertEquals("https://example.com/foo/bar?newqp=2#fragment", redirectedUrl);
313+
}
314+
304315
@Test
305316
public void sendRedirectWithContextPath() throws Exception {
306317
this.request.addHeader(X_FORWARDED_PROTO, "https");

0 commit comments

Comments
 (0)