Skip to content

Commit b3d2232

Browse files
committed
Support external redirects to non-web URLs
Fixed external redirects to URLs with non-http/s schemes so that they are properly treated as external URLs instead of servlet-relative paths. Also fixed a copy/paste bug in the test case for external http redirects. Signed-off-by: Jed Liu <liujed@users.noreply.github.com>
1 parent d29cc65 commit b3d2232

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.io.IOException;
1919
import java.util.Map;
20+
import java.util.function.Predicate;
21+
import java.util.regex.Pattern;
2022

2123
import jakarta.servlet.http.HttpServletRequest;
2224
import jakarta.servlet.http.HttpServletResponse;
@@ -70,6 +72,11 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd
7072

7173
private static final String SERVER_RELATIVE_LOCATION_PREFIX = "serverRelative:";
7274

75+
/**
76+
* Matches strings that start with an RFC 3986 URL scheme followed by a colon.
77+
*/
78+
private static final Predicate<String> URL_MATCHER = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+.-]*:.*").asMatchPredicate();
79+
7380
/**
7481
* The entry point into Spring Web Flow.
7582
*/
@@ -507,7 +514,7 @@ private void sendExternalRedirect(String location, HttpServletRequest request, H
507514
url = "/" + url;
508515
}
509516
sendRedirect(url, request, response);
510-
} else if (location.startsWith("http://") || location.startsWith("https://")) {
517+
} else if (URL_MATCHER.test(location)) {
511518
sendRedirect(location, request, response);
512519
} else {
513520
if (isRedirectServletRelative(request)) {

spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,14 @@ public void testLaunchFlowWithDefinitionRedirect() throws Exception {
231231
@Test
232232
public void testLaunchFlowWithExternalHttpRedirect() throws Exception {
233233
setupRequest("/springtravel", "/app", "/foo", "GET");
234-
context.requestExternalRedirect("https://www.paypal.com");
234+
context.requestExternalRedirect("http://www.paypal.com");
235235
flowExecutor.launchExecution("foo", flowInput, context);
236236
FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345");
237237
EasyMock.expectLastCall().andReturn(result);
238238
EasyMock.replay(flowExecutor);
239239
flowHandlerAdapter.handle(request, response, flowHandler);
240240
EasyMock.verify(flowExecutor);
241-
assertEquals("https://www.paypal.com", response.getRedirectedUrl());
241+
assertEquals("http://www.paypal.com", response.getRedirectedUrl());
242242
EasyMock.verify(flowExecutor);
243243
}
244244

@@ -256,6 +256,48 @@ public void testLaunchFlowWithExternalHttpsRedirect() throws Exception {
256256
EasyMock.verify(flowExecutor);
257257
}
258258

259+
@Test
260+
public void testLaunchFlowWithExternalMailtoRedirect() throws Exception {
261+
setupRequest("/springtravel", "/app", "/foo", "GET");
262+
context.requestExternalRedirect("mailto:help@mail.test");
263+
flowExecutor.launchExecution("foo", flowInput, context);
264+
FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345");
265+
EasyMock.expectLastCall().andReturn(result);
266+
EasyMock.replay(flowExecutor);
267+
flowHandlerAdapter.handle(request, response, flowHandler);
268+
EasyMock.verify(flowExecutor);
269+
assertEquals("mailto:help@mail.test", response.getRedirectedUrl());
270+
EasyMock.verify(flowExecutor);
271+
}
272+
273+
@Test
274+
public void testLaunchFlowWithExternalTelRedirect() throws Exception {
275+
setupRequest("/springtravel", "/app", "/foo", "GET");
276+
context.requestExternalRedirect("tel:+1.800.555.1212");
277+
flowExecutor.launchExecution("foo", flowInput, context);
278+
FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345");
279+
EasyMock.expectLastCall().andReturn(result);
280+
EasyMock.replay(flowExecutor);
281+
flowHandlerAdapter.handle(request, response, flowHandler);
282+
EasyMock.verify(flowExecutor);
283+
assertEquals("tel:+1.800.555.1212", response.getRedirectedUrl());
284+
EasyMock.verify(flowExecutor);
285+
}
286+
287+
@Test
288+
public void testLaunchFlowWithExternalCustomSchemeRedirect() throws Exception {
289+
setupRequest("/springtravel", "/app", "/foo", "GET");
290+
context.requestExternalRedirect("my-mobile-app://redirect/target/path");
291+
flowExecutor.launchExecution("foo", flowInput, context);
292+
FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345");
293+
EasyMock.expectLastCall().andReturn(result);
294+
EasyMock.replay(flowExecutor);
295+
flowHandlerAdapter.handle(request, response, flowHandler);
296+
EasyMock.verify(flowExecutor);
297+
assertEquals("my-mobile-app://redirect/target/path", response.getRedirectedUrl());
298+
EasyMock.verify(flowExecutor);
299+
}
300+
259301
@Test
260302
public void testLaunchFlowWithExternalRedirectServletRelative() throws Exception {
261303
setupRequest("/springtravel", "/app", "/foo", "GET");

0 commit comments

Comments
 (0)