mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-23 06:29:10 +00:00
Limit bracket depth in PropertyEditorRegistrySupport
Previously, PropertyEditorRegistrySupport.addStrippedPropertyPaths() recursively enumerated every combination of stripped/retained [key] segments in a property path, producing 2^n - 1 variants for a path with n bracket pairs. To address that, this commit limits the recursion at a depth of 8, preserving existing behavior for realistic property paths while bounding the work done for paths with an unusually large number of bracket segments. Closes gh-37020
This commit is contained in:
+28
-5
@@ -79,12 +79,15 @@ import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Base implementation of the {@link PropertyEditorRegistry} interface.
|
||||
* Provides management of default editors and custom editors.
|
||||
* Mainly serves as base class for {@link BeanWrapperImpl}.
|
||||
*
|
||||
* <p>Provides management of default editors and custom editors.
|
||||
*
|
||||
* <p>Mainly serves as base class for {@link BeanWrapperImpl}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Sebastien Deleuze
|
||||
* @author Sam Brannen
|
||||
* @since 1.2.6
|
||||
* @see java.beans.PropertyEditorManager
|
||||
* @see java.beans.PropertyEditorSupport#setAsText
|
||||
@@ -92,6 +95,15 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
|
||||
/**
|
||||
* The maximum number of {@code [key]} segments that {@link #addStrippedPropertyPaths}
|
||||
* will process in a single property path, to avoid excessive recursion and the
|
||||
* resulting exponential blowup in the number of generated stripped paths for
|
||||
* property paths with a large number of {@code [key]} segments.
|
||||
* @since 7.1
|
||||
*/
|
||||
private static final int MAX_STRIPPED_PROPERTY_PATH_DEPTH = 8;
|
||||
|
||||
private @Nullable ConversionService conversionService;
|
||||
|
||||
private boolean defaultEditorsActive = false;
|
||||
@@ -500,12 +512,23 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
|
||||
/**
|
||||
* Add property paths with all variations of stripped keys and/or indexes.
|
||||
* Invokes itself recursively with nested paths.
|
||||
* <p>Invokes itself recursively with nested paths, bounded to a nesting depth
|
||||
* of {@link #MAX_STRIPPED_PROPERTY_PATH_DEPTH}.
|
||||
* @param strippedPaths the result list to add to
|
||||
* @param nestedPath the current nested path
|
||||
* @param propertyPath the property path to check for keys/indexes to strip
|
||||
*/
|
||||
private void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) {
|
||||
addStrippedPropertyPaths(strippedPaths, nestedPath, propertyPath, 0);
|
||||
}
|
||||
|
||||
private void addStrippedPropertyPaths(
|
||||
List<String> strippedPaths, String nestedPath, String propertyPath, int depth) {
|
||||
|
||||
if (depth >= MAX_STRIPPED_PROPERTY_PATH_DEPTH) {
|
||||
// Avoid excessive recursion.
|
||||
return;
|
||||
}
|
||||
int startIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
|
||||
if (startIndex != -1) {
|
||||
int endIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR);
|
||||
@@ -516,9 +539,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
// Strip the first key.
|
||||
strippedPaths.add(nestedPath + prefix + suffix);
|
||||
// Search for further keys to strip, with the first key stripped.
|
||||
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
|
||||
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix, depth + 1);
|
||||
// Search for further keys to strip, with the first key not stripped.
|
||||
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
|
||||
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import org.springframework.beans.propertyeditors.CustomNumberEditor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertyEditorRegistrySupport}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
*/
|
||||
class PropertyEditorRegistrySupportTests {
|
||||
|
||||
/**
|
||||
* Matches the private {@code MAX_STRIPPED_PROPERTY_PATH_DEPTH} constant in
|
||||
* {@link PropertyEditorRegistrySupport}.
|
||||
*/
|
||||
private static final int MAX_DEPTH = 8;
|
||||
|
||||
private final PropertyEditorRegistrySupport registry = new PropertyEditorRegistrySupport();
|
||||
|
||||
private final PropertyEditor editor = new CustomNumberEditor(Integer.class, true);
|
||||
|
||||
|
||||
@Test
|
||||
void findCustomEditorMatchesStrippedPathAtMaxSupportedNestingDepth() {
|
||||
registry.registerCustomEditor(null, "list", this.editor);
|
||||
|
||||
String propertyPath = "list" + "[0]".repeat(MAX_DEPTH);
|
||||
assertThat(registry.findCustomEditor(null, propertyPath)).isSameAs(this.editor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findCustomEditorDoesNotMatchStrippedPathBeyondMaxSupportedNestingDepth() {
|
||||
registry.registerCustomEditor(null, "list", this.editor);
|
||||
|
||||
String propertyPath = "list" + "[0]".repeat(MAX_DEPTH + 1);
|
||||
assertThat(registry.findCustomEditor(null, propertyPath)).isNull();
|
||||
}
|
||||
|
||||
@Test // gh-37020
|
||||
@Timeout(5)
|
||||
void findCustomEditorWithExcessivelyNestedPropertyPathDoesNotHang() {
|
||||
registry.registerCustomEditor(null, "attrs", this.editor);
|
||||
|
||||
// A property path with a bracket-nesting depth (40) that would previously
|
||||
// have caused addStrippedPropertyPaths() to recursively enumerate 2^40 - 1
|
||||
// stripped path variants. With the depth limit in place, this should return
|
||||
// promptly.
|
||||
String propertyPath = "attrs" + "[k]".repeat(40);
|
||||
|
||||
assertThat(registry.findCustomEditor(null, propertyPath)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void guessPropertyTypeFromEditorsMatchesStrippedPathAtMaxSupportedNestingDepth() {
|
||||
registry.registerCustomEditor(Integer.class, "list", this.editor);
|
||||
|
||||
String propertyPath = "list" + "[0]".repeat(MAX_DEPTH);
|
||||
assertThat(registry.guessPropertyTypeFromEditors(propertyPath)).isEqualTo(Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void guessPropertyTypeFromEditorsDoesNotMatchStrippedPathBeyondMaxSupportedNestingDepth() {
|
||||
registry.registerCustomEditor(Integer.class, "list", this.editor);
|
||||
|
||||
String propertyPath = "list" + "[0]".repeat(MAX_DEPTH + 1);
|
||||
assertThat(registry.guessPropertyTypeFromEditors(propertyPath)).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user