Merge branch '7.0.x'

This commit is contained in:
Sam Brannen
2026-06-08 18:29:20 +02:00
64 changed files with 1187 additions and 314 deletions
@@ -24,7 +24,6 @@ import jakarta.servlet.jsp.tagext.DynamicAttributes;
import org.jspecify.annotations.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -40,6 +39,7 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @author Jeremy Grelle
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 2.0
*/
@SuppressWarnings("serial")
@@ -429,8 +429,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen
*/
protected void writeOptionalAttributes(TagWriter tagWriter) throws JspException {
tagWriter.writeOptionalAttributeValue(CLASS_ATTRIBUTE, resolveCssClass());
tagWriter.writeOptionalAttributeValue(STYLE_ATTRIBUTE,
ObjectUtils.getDisplayString(evaluate("cssStyle", getCssStyle())));
writeOptionalAttribute(tagWriter, STYLE_ATTRIBUTE, getCssStyle());
writeOptionalAttribute(tagWriter, LANG_ATTRIBUTE, getLang());
writeOptionalAttribute(tagWriter, TITLE_ATTRIBUTE, getTitle());
writeOptionalAttribute(tagWriter, DIR_ATTRIBUTE, getDir());
@@ -459,10 +458,10 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen
*/
protected String resolveCssClass() throws JspException {
if (getBindStatus().isError() && StringUtils.hasText(getCssErrorClass())) {
return ObjectUtils.getDisplayString(evaluate("cssErrorClass", getCssErrorClass()));
return getDisplayString(evaluate("cssErrorClass", getCssErrorClass()));
}
else {
return ObjectUtils.getDisplayString(evaluate("cssClass", getCssClass()));
return getDisplayString(evaluate("cssClass", getCssClass()));
}
}
@@ -32,7 +32,6 @@ import org.springframework.core.Conventions;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.springframework.web.util.HtmlUtils;
@@ -239,6 +238,7 @@ import org.springframework.web.util.UriUtils;
* @author Juergen Hoeller
* @author Scott Andrews
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 2.0
*/
@SuppressWarnings("serial")
@@ -719,7 +719,7 @@ public class FormTag extends AbstractHtmlElementTag {
*/
@Override
protected String resolveCssClass() throws JspException {
return ObjectUtils.getDisplayString(evaluate("cssClass", getCssClass()));
return getDisplayString(evaluate("cssClass", getCssClass()));
}
/**
@@ -20,7 +20,9 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.util.ServletRequestPathUtils;
@@ -50,6 +52,7 @@ import org.springframework.web.util.ServletRequestPathUtils;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 2.0
* @see org.springframework.web.servlet.RequestToViewNameTranslator
* @see org.springframework.web.servlet.ViewResolver
@@ -127,13 +130,21 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
* into the view name based on the configured parameters.
* @throws IllegalArgumentException if neither a parsed RequestPath, nor a
* String lookupPath have been resolved and cached as a request attribute.
* @throws ResponseStatusException with a 400 error code if the path contains a "redirect:" or a "forward:" prefix
* @see ServletRequestPathUtils#getCachedPath(ServletRequest)
* @see #transformPath
*/
@Override
public String getViewName(HttpServletRequest request) {
String path = ServletRequestPathUtils.getCachedPathValue(request);
return (this.prefix + transformPath(path) + this.suffix);
String viewName = this.prefix + transformPath(path) + this.suffix;
if (viewName.startsWith(UrlBasedViewResolver.REDIRECT_URL_PREFIX)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Rejected path '" + path + "' with 'redirect:' prefix");
}
if (viewName.startsWith(UrlBasedViewResolver.FORWARD_URL_PREFIX)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Rejected path '" + path + "' with 'forward:' prefix");
}
return viewName;
}
/**
@@ -144,7 +155,7 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
* @return the transformed path, with slashes and extensions stripped
* if desired
*/
protected @Nullable String transformPath(String lookupPath) {
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(1);
@@ -129,6 +129,20 @@ class FormTagTests extends AbstractHtmlElementTagTests {
assertContainsAttribute(output, dynamicAttribute2, dynamicAttribute2);
}
@Test
void writeFormWithHtmlEscaping() throws Exception {
this.tag.setCssClass("\"class\"");
this.tag.setCssStyle("\"style\"");
this.tag.doStartTag();
this.tag.doEndTag();
this.tag.doFinally();
String output = getOutput();
assertContainsAttribute(output, "class", ""class"");
assertContainsAttribute(output, "style", ""style"");
}
@Test
void withActionFromRequest() throws Exception {
String commandName = "myCommand";
@@ -314,6 +314,28 @@ class InputTagTests extends AbstractFormTagTests {
assertContainsAttribute(output, "class", "bad");
}
@Test
void withErrorsAndHtmlEscaping() throws Exception {
this.tag.setPath("name");
this.tag.setCssClass("\"good\"");
this.tag.setCssErrorClass("\"bad\"");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(this.rob, COMMAND_NAME);
errors.rejectValue("name", "some.code", "Default Message");
errors.rejectValue("name", "too.short", "Too Short");
exposeBindingResult(errors);
assertThat(this.tag.doStartTag()).isEqualTo(Tag.SKIP_BODY);
String output = getOutput();
assertTagOpened(output);
assertTagClosed(output);
assertContainsAttribute(output, "type", getType());
assertValueAttribute(output, "Rob");
assertContainsAttribute(output, "class", ""bad"");
}
@Test
void disabledFalse() throws Exception {
this.tag.setPath("name");
@@ -21,15 +21,19 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Rick Evans
* @author Juergen Hoeller
* @author Sebastien Deleuze
*/
class DefaultRequestToViewNameTranslatorTests {
@@ -121,6 +125,22 @@ class DefaultRequestToViewNameTranslatorTests {
assertViewName(request, VIEW_NAME);
}
@PathPatternsParameterizedTest
void getViewNameWithRedirectPrefixFails(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(UrlBasedViewResolver.REDIRECT_URL_PREFIX + VIEW_NAME);
assertThatExceptionOfType(ResponseStatusException.class)
.isThrownBy(() -> this.translator.getViewName(request))
.satisfies(ex -> assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST));
}
@PathPatternsParameterizedTest
void getViewNameWithForwardPrefixFails(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(UrlBasedViewResolver.FORWARD_URL_PREFIX + VIEW_NAME);
assertThatExceptionOfType(ResponseStatusException.class)
.isThrownBy(() -> this.translator.getViewName(request))
.satisfies(ex -> assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST));
}
private void assertViewName(MockHttpServletRequest request, String expectedViewName) {
String actualViewName = this.translator.getViewName(request);