diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java index e51daa73f..4a4fdeff8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -70,6 +72,11 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd private static final String SERVER_RELATIVE_LOCATION_PREFIX = "serverRelative:"; + /** + * Matches strings that start with an RFC 3986 URL scheme followed by a colon. + */ + private static final Predicate URL_MATCHER = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+.-]*:.*").asMatchPredicate(); + /** * The entry point into Spring Web Flow. */ @@ -507,7 +514,7 @@ private void sendExternalRedirect(String location, HttpServletRequest request, H url = "/" + url; } sendRedirect(url, request, response); - } else if (location.startsWith("http://") || location.startsWith("https://")) { + } else if (URL_MATCHER.test(location)) { sendRedirect(location, request, response); } else { if (isRedirectServletRelative(request)) { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java index 880ab44ea..a098fa26e 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java @@ -231,14 +231,14 @@ public void testLaunchFlowWithDefinitionRedirect() throws Exception { @Test public void testLaunchFlowWithExternalHttpRedirect() throws Exception { setupRequest("/springtravel", "/app", "/foo", "GET"); - context.requestExternalRedirect("https://www.paypal.com"); + context.requestExternalRedirect("http://www.paypal.com"); flowExecutor.launchExecution("foo", flowInput, context); FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); EasyMock.expectLastCall().andReturn(result); EasyMock.replay(flowExecutor); flowHandlerAdapter.handle(request, response, flowHandler); EasyMock.verify(flowExecutor); - assertEquals("https://www.paypal.com", response.getRedirectedUrl()); + assertEquals("http://www.paypal.com", response.getRedirectedUrl()); EasyMock.verify(flowExecutor); } @@ -256,6 +256,48 @@ public void testLaunchFlowWithExternalHttpsRedirect() throws Exception { EasyMock.verify(flowExecutor); } + @Test + public void testLaunchFlowWithExternalMailtoRedirect() throws Exception { + setupRequest("/springtravel", "/app", "/foo", "GET"); + context.requestExternalRedirect("mailto:help@mail.test"); + flowExecutor.launchExecution("foo", flowInput, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(flowExecutor); + flowHandlerAdapter.handle(request, response, flowHandler); + EasyMock.verify(flowExecutor); + assertEquals("mailto:help@mail.test", response.getRedirectedUrl()); + EasyMock.verify(flowExecutor); + } + + @Test + public void testLaunchFlowWithExternalTelRedirect() throws Exception { + setupRequest("/springtravel", "/app", "/foo", "GET"); + context.requestExternalRedirect("tel:+1.800.555.1212"); + flowExecutor.launchExecution("foo", flowInput, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(flowExecutor); + flowHandlerAdapter.handle(request, response, flowHandler); + EasyMock.verify(flowExecutor); + assertEquals("tel:+1.800.555.1212", response.getRedirectedUrl()); + EasyMock.verify(flowExecutor); + } + + @Test + public void testLaunchFlowWithExternalCustomSchemeRedirect() throws Exception { + setupRequest("/springtravel", "/app", "/foo", "GET"); + context.requestExternalRedirect("my-mobile-app://redirect/target/path"); + flowExecutor.launchExecution("foo", flowInput, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(flowExecutor); + flowHandlerAdapter.handle(request, response, flowHandler); + EasyMock.verify(flowExecutor); + assertEquals("my-mobile-app://redirect/target/path", response.getRedirectedUrl()); + EasyMock.verify(flowExecutor); + } + @Test public void testLaunchFlowWithExternalRedirectServletRelative() throws Exception { setupRequest("/springtravel", "/app", "/foo", "GET");