Browse Source

Add missing URI template variables in RedirectViews

Prior to this commit, the URL handler mapping would expose the matching
pattern, the path within mapping and matching URI variables as request
attributes. This was the case when the mapping would use the
`AntPathMatcher` as matching infrastructure, but not when using the
`PathPattern` variant. In this case, the map of URI variables would be
`null`. This could throw `IllegalArgumentException` when `RedirectView`
instances were relying on the presence of specific variables.

This commit ensures that URI variables are also extracted when the
`PathPatternParser` is used.

Fixes gh-33422
pull/33720/head
Brian Clozel 1 year ago
parent
commit
6a20987933
  1. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
  2. 231
      spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java

4
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java

@ -219,7 +219,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
validateHandler(handler, request); validateHandler(handler, request);
String pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication()).value(); String pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication()).value();
pathWithinMapping = UrlPathHelper.defaultInstance.removeSemicolonContent(pathWithinMapping); pathWithinMapping = UrlPathHelper.defaultInstance.removeSemicolonContent(pathWithinMapping);
return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping, null); PathPattern.PathMatchInfo pathMatchInfo = pattern.matchAndExtract(path);
Map<String, String> uriVariables = (pathMatchInfo != null ? pathMatchInfo.getUriVariables(): null);
return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping, uriVariables);
} }
/** /**

231
spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java

@ -17,58 +17,48 @@
package org.springframework.web.servlet.handler; package org.springframework.web.servlet.handler;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockServletContext;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE; import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE; import static org.springframework.web.servlet.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
/** /**
* @author Rod Johnson * Tests for {@link SimpleUrlHandlerMapping}.
* @author Juergen Hoeller * @author Brian Clozel
*/ */
class SimpleUrlHandlerMappingTests { class SimpleUrlHandlerMappingTests {
@Test @Test
void handlerBeanNotFound() { void shouldFailWhenHandlerBeanNotFound() {
MockServletContext sc = new MockServletContext(""); SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(Map.of("/welcome.html", "mainController"));
XmlWebApplicationContext root = new XmlWebApplicationContext(); assertThatThrownBy(() -> handlerMapping.setApplicationContext(new StaticApplicationContext()))
root.setServletContext(sc); .isInstanceOf(NoSuchBeanDefinitionException.class);
root.setConfigLocations("/org/springframework/web/servlet/handler/map1.xml");
root.refresh();
XmlWebApplicationContext wac = new XmlWebApplicationContext();
wac.setParent(root);
wac.setServletContext(sc);
wac.setNamespace("map2err");
wac.setConfigLocations("/org/springframework/web/servlet/handler/map2err.xml");
assertThatExceptionOfType(FatalBeanException.class)
.isThrownBy(wac::refresh)
.withCauseInstanceOf(NoSuchBeanDefinitionException.class)
.satisfies(ex -> {
NoSuchBeanDefinitionException cause = (NoSuchBeanDefinitionException) ex.getCause();
assertThat(cause.getBeanName()).isEqualTo("mainControlle");
});
} }
@Test @Test
void testNewlineInRequest() throws Exception { void newlineInRequestShouldMatch() throws Exception {
Object controller = new Object(); Object controller = new Object();
UrlPathHelper urlPathHelper = new UrlPathHelper(); UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false); urlPathHelper.setUrlDecode(false);
@ -84,84 +74,143 @@ class SimpleUrlHandlerMappingTests {
} }
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {"urlMapping", "urlMappingWithProps", "urlMappingWithPathPatterns"}) @MethodSource("handlerMappings")
void checkMappings(String beanName) throws Exception { void resolveFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
MockServletContext sc = new MockServletContext(""); StaticApplicationContext applicationContext = new StaticApplicationContext();
XmlWebApplicationContext wac = new XmlWebApplicationContext(); applicationContext.registerSingleton("mainController", Object.class);
wac.setServletContext(sc); Object mainController = applicationContext.getBean("mainController");
wac.setConfigLocations("/org/springframework/web/servlet/handler/map2.xml"); handlerMapping.setUrlMap(Map.of("/welcome.html", "mainController"));
wac.refresh(); handlerMapping.setApplicationContext(applicationContext);
Object bean = wac.getBean("mainController");
Object otherBean = wac.getBean("otherController"); boolean usePathPatterns = handlerMapping.getPatternParser() != null;
Object defaultBean = wac.getBean("starController");
HandlerMapping hm = (HandlerMapping) wac.getBean(beanName);
wac.close();
boolean usePathPatterns = (((AbstractHandlerMapping) hm).getPatternParser() != null);
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.html", usePathPatterns); MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.html", usePathPatterns);
HandlerExecutionChain chain = getHandler(hm, request); HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(bean);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(bean);
request = PathPatternsTestUtils.initRequest("GET", "/welcome.x;jsessionid=123", usePathPatterns); assertThat(chain.getHandler()).isSameAs(mainController);
chain = getHandler(hm, request); assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html");
assertThat(chain.getHandler()).isSameAs(otherBean); assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x"); }
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(otherBean);
request = PathPatternsTestUtils.initRequest("GET", "/app", "/welcome.x", usePathPatterns); @ParameterizedTest
chain = getHandler(hm, request); @MethodSource("handlerMappings")
assertThat(chain.getHandler()).isSameAs(otherBean); void resolvePatternFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setUrlMap(Map.of("/welcome*", "mainController"));
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.x", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x"); assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(otherBean); assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
request = PathPatternsTestUtils.initRequest("GET", "/", usePathPatterns);
request.setServletPath("/welcome.html");
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
request = PathPatternsTestUtils.initRequest("GET", "/app", "/welcome.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
request = PathPatternsTestUtils.initRequest("GET", "/show.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
request = PathPatternsTestUtils.initRequest("GET", "/bookseats.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
request = PathPatternsTestUtils.initRequest("GET", null, "/original-welcome.html", usePathPatterns, @ParameterizedTest
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/welcome.html")); @MethodSource("handlerMappings")
chain = getHandler(hm, request); void resolvePathWithParamFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
assertThat(chain.getHandler()).isSameAs(bean); StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setUrlMap(Map.of("/welcome.x", "mainController"));
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.x;jsessionid=123", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.x");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
request = PathPatternsTestUtils.initRequest("GET", null, "/original-show.html", usePathPatterns, @ParameterizedTest
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/show.html")); @MethodSource("handlerMappings")
chain = getHandler(hm, request); void resolvePathWithContextFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
assertThat(chain.getHandler()).isSameAs(bean); StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setUrlMap(Map.of("/welcome.x", "mainController"));
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/app", "/welcome.x", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.x");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
request = PathPatternsTestUtils.initRequest("GET", null, "/original-bookseats.html", usePathPatterns, @ParameterizedTest
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/bookseats.html")); @MethodSource("handlerMappings")
chain = getHandler(hm, request); void resolvePathWithIncludeFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
assertThat(chain.getHandler()).isSameAs(bean); StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setUrlMap(Map.of("/welcome.html", "mainController"));
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/original.html", usePathPatterns);
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/welcome.html");
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
request = PathPatternsTestUtils.initRequest("GET", "/", usePathPatterns); @ParameterizedTest
chain = getHandler(hm, request); @MethodSource("handlerMappings")
assertThat(chain.getHandler()).isSameAs(bean); void resolveDefaultPathFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setDefaultHandler(mainController);
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/"); assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
@ParameterizedTest
@MethodSource("handlerMappings")
void resolveParameterizedControllerFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
ParameterizableViewController viewController = new ParameterizableViewController();
viewController.setView(new RedirectView("/after/{variable}"));
viewController.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
handlerMapping.setUrlMap(Map.of("/before/{variable}", viewController));
handlerMapping.setApplicationContext(new StaticApplicationContext());
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/before/test", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(viewController);
Map<String, String> variables = (Map<String, String>) request.getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE);
assertThat(variables).containsEntry("variable", "test");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(viewController);
}
request = PathPatternsTestUtils.initRequest("GET", "/somePath", usePathPatterns); static Stream<Arguments> handlerMappings() {
chain = getHandler(hm, request); SimpleUrlHandlerMapping defaultConfig = new SimpleUrlHandlerMapping();
assertThat(chain.getHandler()).as("Handler is correct bean").isSameAs(defaultBean); SimpleUrlHandlerMapping antPatternConfig = new SimpleUrlHandlerMapping();
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/somePath"); antPatternConfig.setPatternParser(null);
return Stream.of(Arguments.of(defaultConfig, "with PathPattern"), Arguments.of(antPatternConfig, "with AntPathMatcher"));
} }
private HandlerExecutionChain getHandler(HandlerMapping mapping, MockHttpServletRequest request) throws Exception { private HandlerExecutionChain getHandler(HandlerMapping mapping, MockHttpServletRequest request) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(request); HandlerExecutionChain chain = mapping.getHandler(request);
Assert.notNull(chain, "No handler found for request: " + request.getRequestURI());
for (HandlerInterceptor interceptor : chain.getInterceptorList()) { for (HandlerInterceptor interceptor : chain.getInterceptorList()) {
interceptor.preHandle(request, null, chain.getHandler()); interceptor.preHandle(request, null, chain.getHandler());
} }

Loading…
Cancel
Save