Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: spring-projects/spring-framework
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.2.20.RELEASE
Choose a base ref
...
head repository: spring-projects/spring-framework
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v5.2.21.RELEASE
Choose a head ref
  • 9 commits
  • 29 files changed
  • 3 contributors

Commits on Mar 31, 2022

  1. Copy the full SHA
    1b75f39 View commit details

Commits on Apr 8, 2022

  1. Consistent fallback in case of fast-class generation failure

    Closes gh-28138
    
    (cherry picked from commit 7aed627)
    jhoeller committed Apr 8, 2022
    Copy the full SHA
    fb763dd View commit details
  2. Restore ability to configure setClassLoader methods

    Closes gh-28269
    
    (cherry picked from commit 9f91168)
    jhoeller committed Apr 8, 2022
    Copy the full SHA
    69c7eb9 View commit details
  3. Avoid return value reference in potentially cached MethodParameter in…

    …stance
    
    Closes gh-28232
    
    (cherry picked from commit eefdd2c)
    jhoeller committed Apr 8, 2022
    Copy the full SHA
    5cbf85a View commit details
  4. Copy the full SHA
    87b5080 View commit details
  5. Polishing

    jhoeller committed Apr 8, 2022
    Copy the full SHA
    36e4951 View commit details
  6. Upgrade to Log4j2 2.17.2

    jhoeller committed Apr 8, 2022
    Copy the full SHA
    d70054d View commit details

Commits on Apr 13, 2022

  1. Copy the full SHA
    833e750 View commit details
  2. Release v5.2.21.RELEASE

    spring-builds committed Apr 13, 2022
    Copy the full SHA
    2bdab89 View commit details
Showing with 496 additions and 259 deletions.
  1. +1 −1 build.gradle
  2. +1 −1 gradle.properties
  3. +22 −12 spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
  4. +10 −7 spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  5. +4 −3 spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
  6. +26 −2 spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
  7. +77 −39 spring-context/src/main/java/org/springframework/validation/DataBinder.java
  8. +129 −137 spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
  9. +5 −5 spring-context/src/test/resources/org/springframework/jmx/export/notificationPublisherTests.xml
  10. +4 −4 spring-core/src/main/java/org/springframework/core/convert/Property.java
  11. +2 −2 spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
  12. +5 −5 spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
  13. +3 −3 spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
  14. +3 −4 spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java
  15. +10 −1 spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
  16. +10 −1 spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
  17. +1 −0 spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
  18. +16 −6 spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
  19. +18 −8 spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
  20. +10 −1 spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
  21. +10 −1 spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
  22. +5 −5 spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
  23. +3 −6 spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java
  24. +2 −2 ...web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java
  25. +2 −2 ...java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java
  26. +10 −1 .../java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java
  27. +95 −0 src/docs/asciidoc/web/web-data-binding-model-design.adoc
  28. +5 −0 src/docs/asciidoc/web/webflux.adoc
  29. +7 −0 src/docs/asciidoc/web/webmvc.adoc
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ configure(allprojects) { project ->
mavenBom "org.junit:junit-bom:5.6.3"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.17.1') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.17.2') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version=5.2.20.BUILD-SNAPSHOT
version=5.2.21.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -375,6 +375,22 @@ private static boolean implementsInterface(Method method, Set<Class<?>> ifcs) {
return false;
}

/**
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
* to a plain reflection invocation in case of a fast-class generation failure.
*/
@Nullable
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
return methodProxy.invoke(target, args);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
}

/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -425,7 +441,7 @@ public StaticUnadvisedInterceptor(@Nullable Object target) {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
}
@@ -450,7 +466,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@@ -478,7 +494,7 @@ public DynamicUnadvisedInterceptor(TargetSource targetSource) {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -508,7 +524,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -685,13 +701,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
retVal = invokeMethod(target, method, argsToUse, methodProxy);
}
else {
// We need to create a method invocation...
Original file line number Diff line number Diff line change
@@ -282,13 +282,15 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
// This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) {
if (Class.class == beanClass && !("name".equals(pd.getName()) ||
(pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) {
// Only allow all name variants of Class properties
continue;
}
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
if (pd.getWriteMethod() == null && pd.getPropertyType() != null &&
(ClassLoader.class.isAssignableFrom(pd.getPropertyType()) ||
ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those
continue;
}
if (logger.isTraceEnabled()) {
@@ -326,9 +328,10 @@ private void introspectInterfaces(Class<?> beanClass, Class<?> currClass) throws
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
// against a declared read method, so we prefer read method descriptors here.
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
if (pd.getWriteMethod() == null && pd.getPropertyType() != null &&
(ClassLoader.class.isAssignableFrom(pd.getPropertyType()) ||
ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those
continue;
}
this.propertyDescriptors.put(pd.getName(), pd);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -23,8 +23,9 @@

/**
* Common interface for classes that can access named properties
* (such as bean properties of an object or fields in an object)
* Serves as base interface for {@link BeanWrapper}.
* (such as bean properties of an object or fields in an object).
*
* <p>Serves as base interface for {@link BeanWrapper}.
*
* @author Juergen Hoeller
* @since 1.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -23,6 +23,8 @@
import org.junit.jupiter.api.Test;

import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.io.DefaultResourceLoader;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -104,7 +106,7 @@ public void checkNotWritablePropertyHoldPossibleMatches() {
.satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age"));
}

@Test // Can't be shared; there is no such thing as a read-only field
@Test // Can't be shared; there is no such thing as a read-only field
public void setReadOnlyMapProperty() {
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
@@ -156,12 +158,34 @@ public void propertyDescriptors() {
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "a");
accessor.setPropertyValue("spouse.name", "b");

assertThat(target.getName()).isEqualTo("a");
assertThat(target.getSpouse().getName()).isEqualTo("b");
assertThat(accessor.getPropertyValue("name")).isEqualTo("a");
assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b");
assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class);

assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName());
assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName());
assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class);

accessor = createAccessor(new DefaultResourceLoader());

assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("classLoader")).isTrue();
assertThat(accessor.isWritableProperty("classLoader")).isTrue();
OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader());
accessor.setPropertyValue("classLoader", ocl);
assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl);
}

@Test
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -51,18 +51,20 @@
import org.springframework.util.StringUtils;

/**
* Binder that allows for setting property values onto a target object,
* including support for validation and binding result analysis.
* The binding process can be customized through specifying allowed fields,
* Binder that allows for setting property values on a target object, including
* support for validation and binding result analysis.
*
* <p>The binding process can be customized by specifying allowed field patterns,
* required fields, custom editors, etc.
*
* <p>Note that there are potential security implications in failing to set an array
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
* can attempt to subvert an application by supplying values for fields or properties
* that do not exist on the form. In some cases this could lead to illegal data being
* set on command objects <i>or their nested objects</i>. For this reason, it is
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
* on the DataBinder.
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
@@ -96,6 +98,7 @@
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Sam Brannen
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
@@ -415,13 +418,21 @@ public boolean isIgnoreInvalidFields() {
}

/**
* Register fields that should be allowed for binding. Default is all
* fields. Restrict this for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
* @param allowedFields array of field names
* Register field patterns that should be allowed for binding.
* <p>Default is all fields.
* <p>Restrict this for example to avoid unwanted modifications by malicious
* users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores allowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. Subclasses which override this method must therefore take this into
* account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
* @param allowedFields array of allowed field patterns
* @see #setDisallowedFields
* @see #isAllowed(String)
*/
@@ -430,32 +441,54 @@ public void setAllowedFields(@Nullable String... allowedFields) {
}

/**
* Return the fields that should be allowed for binding.
* @return array of field names
* Return the field patterns that should be allowed for binding.
* @return array of allowed field patterns
* @see #setAllowedFields(String...)
*/
@Nullable
public String[] getAllowedFields() {
return this.allowedFields;
}

/**
* Register fields that should <i>not</i> be allowed for binding. Default is none.
* Mark fields as disallowed for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> fields.
* @param disallowedFields array of field names
* Register field patterns that should <i>not</i> be allowed for binding.
* <p>Default is none.
* <p>Mark fields as disallowed, for example to avoid unwanted
* modifications by malicious users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores disallowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. As of Spring Framework 5.2.21, the default implementation also transforms
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
* which override this method must therefore take both of these transformations
* into account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
* @param disallowedFields array of disallowed field patterns
* @see #setAllowedFields
* @see #isAllowed(String)
*/
public void setDisallowedFields(@Nullable String... disallowedFields) {
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
if (disallowedFields == null) {
this.disallowedFields = null;
}
else {
String[] fieldPatterns = new String[disallowedFields.length];
for (int i = 0; i < fieldPatterns.length; i++) {
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
}
this.disallowedFields = fieldPatterns;
}
}

/**
* Return the fields that should <i>not</i> be allowed for binding.
* @return array of field names
* Return the field patterns that should <i>not</i> be allowed for binding.
* @return array of disallowed field patterns
* @see #setDisallowedFields(String...)
*/
@Nullable
public String[] getDisallowedFields() {
@@ -767,15 +800,20 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) {
}

/**
* Return if the given field is allowed for binding.
* Invoked for each passed-in property value.
* <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
* as well as direct equality, in the specified lists of allowed fields and
* disallowed fields. A field matching a disallowed pattern will not be accepted
* even if it also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses.
* Determine if the given field is allowed for binding.
* <p>Invoked for each passed-in property value.
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality, in the configured lists of allowed field patterns
* and disallowed field patterns.
* <p>Matching against allowed field patterns is case-sensitive; whereas,
* matching against disallowed field patterns is case-insensitive.
* <p>A field matching a disallowed pattern will not be accepted even if it
* also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses, but care must be taken to honor the
* aforementioned contract.
* @param field the field to check
* @return if the field is allowed
* @return {@code true} if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
@@ -784,7 +822,7 @@ protected boolean isAllowed(String field) {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
}

/**
Loading