Skip to content

Commit c45ad30

Browse files
committed
Add "hosts" property to RedirectView
Issue: SPR-13693
1 parent aba04d5 commit c45ad30

File tree

3 files changed

+99
-6
lines changed

3 files changed

+99
-6
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
104104

105105
private boolean propagateQueryParams = false;
106106

107+
private String[] hosts;
108+
107109

108110
/**
109111
* Constructor for use as a bean.
@@ -252,6 +254,27 @@ public boolean isPropagateQueryProperties() {
252254
return this.propagateQueryParams;
253255
}
254256

257+
/**
258+
* Configure one or more hosts associated with the application. All other
259+
* hosts will be considered external hosts. In effect this property
260+
* provides a way turn off encoding via
261+
* {@link HttpServletResponse#encodeRedirectURL} for URLs that have a host
262+
* and that host is not listed as a known host.
263+
* <p>If not set (the default) all URLs are encoded through the response.
264+
* @param hosts one or more application hosts
265+
* @since 4.3
266+
*/
267+
public void setHosts(String[] hosts) {
268+
this.hosts = hosts;
269+
}
270+
271+
/**
272+
* Return the configured application hosts.
273+
*/
274+
public String[] getHosts() {
275+
return this.hosts;
276+
}
277+
255278
/**
256279
* Returns "true" indicating this view performs a redirect.
257280
*/
@@ -583,29 +606,55 @@ protected String updateTargetUrl(String targetUrl, Map<String, Object> model,
583606
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
584607
String targetUrl, boolean http10Compatible) throws IOException {
585608

586-
String encodedRedirectURL = response.encodeRedirectURL(targetUrl);
609+
String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
587610
if (http10Compatible) {
588611
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
589612
if (this.statusCode != null) {
590613
response.setStatus(this.statusCode.value());
591-
response.setHeader("Location", encodedRedirectURL);
614+
response.setHeader("Location", encodedURL);
592615
}
593616
else if (attributeStatusCode != null) {
594617
response.setStatus(attributeStatusCode.value());
595-
response.setHeader("Location", encodedRedirectURL);
618+
response.setHeader("Location", encodedURL);
596619
}
597620
else {
598621
// Send status code 302 by default.
599-
response.sendRedirect(encodedRedirectURL);
622+
response.sendRedirect(encodedURL);
600623
}
601624
}
602625
else {
603626
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
604627
response.setStatus(statusCode.value());
605-
response.setHeader("Location", encodedRedirectURL);
628+
response.setHeader("Location", encodedURL);
606629
}
607630
}
608631

632+
/**
633+
* Whether the given targetUrl has a host that is a "foreign" system in which
634+
* case {@link HttpServletResponse#encodeRedirectURL} will not be applied.
635+
* This method returns {@code true} if the {@link #setHosts(String[])}
636+
* property is configured and the target URL has a host that does not match.
637+
* @param targetUrl the target redirect URL
638+
* @return {@code true} the target URL has a remote host, {@code false} if it
639+
* the URL does not have a host or the "host" property is not configured.
640+
* @since 4.3
641+
*/
642+
protected boolean isRemoteHost(String targetUrl) {
643+
if (ObjectUtils.isEmpty(getHosts())) {
644+
return false;
645+
}
646+
String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
647+
if (StringUtils.isEmpty(targetHost)) {
648+
return false;
649+
}
650+
for (String host : getHosts()) {
651+
if (targetHost.equals(host)) {
652+
return false;
653+
}
654+
}
655+
return true;
656+
}
657+
609658
/**
610659
* Determines the status code to use for HTTP 1.1 compatible requests.
611660
* <p>The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java

Lines changed: 26 additions & 1 deletion
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-2016 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.
@@ -20,6 +20,7 @@
2020
import java.util.Locale;
2121
import java.util.Map;
2222
import java.util.Properties;
23+
import javax.servlet.http.HttpServletResponse;
2324

2425
import org.springframework.beans.BeanUtils;
2526
import org.springframework.core.Ordered;
@@ -112,6 +113,8 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
112113

113114
private boolean redirectHttp10Compatible = true;
114115

116+
private String[] redirectHosts;
117+
115118
private String requestContextAttribute;
116119

117120
/** Map of static attributes, keyed by attribute name (String) */
@@ -253,6 +256,27 @@ protected boolean isRedirectHttp10Compatible() {
253256
return this.redirectHttp10Compatible;
254257
}
255258

259+
/**
260+
* Configure one or more hosts associated with the application. All other
261+
* hosts will be considered external hosts. In effect this property
262+
* provides a way turn off encoding on redirect via
263+
* {@link HttpServletResponse#encodeRedirectURL} for URLs that have a host
264+
* and that host is not listed as a known host.
265+
* <p>If not set (the default) all URLs are encoded through the response.
266+
* @param redirectHosts one or more application hosts
267+
* @since 4.3
268+
*/
269+
public void setRedirectHosts(String[] redirectHosts) {
270+
this.redirectHosts = redirectHosts;
271+
}
272+
273+
/**
274+
* Return the configured application hosts for redirect purposes.
275+
*/
276+
public String[] getRedirectHosts() {
277+
return this.redirectHosts;
278+
}
279+
256280
/**
257281
* Set the name of the RequestContext attribute for all views.
258282
* @param requestContextAttribute name of the RequestContext attribute
@@ -435,6 +459,7 @@ protected View createView(String viewName, Locale locale) throws Exception {
435459
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
436460
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
437461
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
462+
view.setHosts(getRedirectHosts());
438463
return applyLifecycleMethods(viewName, view);
439464
}
440465
// Check for special "forward:" prefix.

spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.web.util.WebUtils;
4444

4545
import static org.junit.Assert.assertEquals;
46+
import static org.junit.Assert.assertFalse;
4647
import static org.junit.Assert.assertTrue;
4748
import static org.mockito.BDDMockito.given;
4849
import static org.mockito.BDDMockito.mock;
@@ -205,6 +206,24 @@ public void updateTargetUrlWithContextLoader() throws Exception {
205206
}
206207
}
207208

209+
// SPR-13693
210+
211+
@Test
212+
public void remoteHost() throws Exception {
213+
RedirectView rv = new RedirectView();
214+
215+
assertFalse(rv.isRemoteHost("http://url.somewhere.com"));
216+
assertFalse(rv.isRemoteHost("/path"));
217+
assertFalse(rv.isRemoteHost("http://url.somewhereelse.com"));
218+
219+
rv.setHosts(new String[] {"url.somewhere.com"});
220+
221+
assertFalse(rv.isRemoteHost("http://url.somewhere.com"));
222+
assertFalse(rv.isRemoteHost("/path"));
223+
assertTrue(rv.isRemoteHost("http://url.somewhereelse.com"));
224+
225+
}
226+
208227
@Test
209228
public void emptyMap() throws Exception {
210229
String url = "/myUrl";

0 commit comments

Comments
 (0)