From c45ad3022b1e7d6574b79a734d19355be0d1b596 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 10 Feb 2016 14:20:21 -0500 Subject: [PATCH] Add "hosts" property to RedirectView Issue: SPR-13693 --- .../web/servlet/view/RedirectView.java | 59 +++++++++++++++++-- .../servlet/view/UrlBasedViewResolver.java | 27 ++++++++- .../web/servlet/view/RedirectViewTests.java | 19 ++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java index 0a188696a1..a22b044c03 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java @@ -104,6 +104,8 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { private boolean propagateQueryParams = false; + private String[] hosts; + /** * Constructor for use as a bean. @@ -252,6 +254,27 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { return this.propagateQueryParams; } + /** + * Configure one or more hosts associated with the application. All other + * hosts will be considered external hosts. In effect this property + * provides a way turn off encoding via + * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a host + * and that host is not listed as a known host. + *

If not set (the default) all URLs are encoded through the response. + * @param hosts one or more application hosts + * @since 4.3 + */ + public void setHosts(String[] hosts) { + this.hosts = hosts; + } + + /** + * Return the configured application hosts. + */ + public String[] getHosts() { + return this.hosts; + } + /** * Returns "true" indicating this view performs a redirect. */ @@ -583,29 +606,55 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { - String encodedRedirectURL = response.encodeRedirectURL(targetUrl); + String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl)); if (http10Compatible) { HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE); if (this.statusCode != null) { response.setStatus(this.statusCode.value()); - response.setHeader("Location", encodedRedirectURL); + response.setHeader("Location", encodedURL); } else if (attributeStatusCode != null) { response.setStatus(attributeStatusCode.value()); - response.setHeader("Location", encodedRedirectURL); + response.setHeader("Location", encodedURL); } else { // Send status code 302 by default. - response.sendRedirect(encodedRedirectURL); + response.sendRedirect(encodedURL); } } else { HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl); response.setStatus(statusCode.value()); - response.setHeader("Location", encodedRedirectURL); + response.setHeader("Location", encodedURL); } } + /** + * Whether the given targetUrl has a host that is a "foreign" system in which + * case {@link HttpServletResponse#encodeRedirectURL} will not be applied. + * This method returns {@code true} if the {@link #setHosts(String[])} + * property is configured and the target URL has a host that does not match. + * @param targetUrl the target redirect URL + * @return {@code true} the target URL has a remote host, {@code false} if it + * the URL does not have a host or the "host" property is not configured. + * @since 4.3 + */ + protected boolean isRemoteHost(String targetUrl) { + if (ObjectUtils.isEmpty(getHosts())) { + return false; + } + String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost(); + if (StringUtils.isEmpty(targetHost)) { + return false; + } + for (String host : getHosts()) { + if (targetHost.equals(host)) { + return false; + } + } + return true; + } + /** * Determines the status code to use for HTTP 1.1 compatible requests. *

The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java index 8bd441eaed..2a859c2266 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.BeanUtils; import org.springframework.core.Ordered; @@ -112,6 +113,8 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements private boolean redirectHttp10Compatible = true; + private String[] redirectHosts; + private String requestContextAttribute; /** Map of static attributes, keyed by attribute name (String) */ @@ -253,6 +256,27 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements return this.redirectHttp10Compatible; } + /** + * Configure one or more hosts associated with the application. All other + * hosts will be considered external hosts. In effect this property + * provides a way turn off encoding on redirect via + * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a host + * and that host is not listed as a known host. + *

If not set (the default) all URLs are encoded through the response. + * @param redirectHosts one or more application hosts + * @since 4.3 + */ + public void setRedirectHosts(String[] redirectHosts) { + this.redirectHosts = redirectHosts; + } + + /** + * Return the configured application hosts for redirect purposes. + */ + public String[] getRedirectHosts() { + return this.redirectHosts; + } + /** * Set the name of the RequestContext attribute for all views. * @param requestContextAttribute name of the RequestContext attribute @@ -435,6 +459,7 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); + view.setHosts(getRedirectHosts()); return applyLifecycleMethods(viewName, view); } // Check for special "forward:" prefix. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java index 8607a769da..fb66f41f89 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java @@ -43,6 +43,7 @@ import org.springframework.web.servlet.support.SessionFlashMapManager; import org.springframework.web.util.WebUtils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; @@ -205,6 +206,24 @@ public class RedirectViewTests { } } + // SPR-13693 + + @Test + public void remoteHost() throws Exception { + RedirectView rv = new RedirectView(); + + assertFalse(rv.isRemoteHost("http://url.somewhere.com")); + assertFalse(rv.isRemoteHost("/path")); + assertFalse(rv.isRemoteHost("http://url.somewhereelse.com")); + + rv.setHosts(new String[] {"url.somewhere.com"}); + + assertFalse(rv.isRemoteHost("http://url.somewhere.com")); + assertFalse(rv.isRemoteHost("/path")); + assertTrue(rv.isRemoteHost("http://url.somewhereelse.com")); + + } + @Test public void emptyMap() throws Exception { String url = "/myUrl";