From 785598629abda944343a02307ad82a79bb31b589 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:09:44 +0100 Subject: [PATCH] Make max length of SpEL expressions in an ApplicationContext configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces support for a Spring property named `spring.context.expression.maxLength`. When set, the value of that property is used internally in StandardBeanExpressionResolver to configure the SpelParserConfiguration used when evaluating String values in bean definitions, @⁠Value, etc. Closes gh-31952 --- .../modules/ROOT/pages/appendix.adoc | 5 ++ .../pages/core/expressions/evaluation.adoc | 9 +++ .../StandardBeanExpressionResolver.java | 40 +++++++++- .../ApplicationContextExpressionTests.java | 74 ++++++++++++++++++- .../spel/SpelParserConfiguration.java | 4 +- 5 files changed, 126 insertions(+), 6 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 4f3229f94ef8..e746b53c703c 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -28,6 +28,11 @@ JavaBeans `Introspector`. See {spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`] for details. +| `spring.context.expression.maxLength` +| The maximum length for +xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language] +expressions used in XML bean definitions, `@Value`, etc. + | `spring.expression.compiler.mode` | The mode to use when compiling expressions for the xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language]. diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index 2ed0079d67be..50e67391e5c3 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -380,6 +380,15 @@ Kotlin:: ---- ====== +By default, a SpEL expression cannot contain more than 10,000 characters; however, the +`maxExpressionLength` is configurable. If you create a `SpelExpressionParser` +programmatically, you can specify a custom `maxExpressionLength` when creating the +`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to +set the `maxExpressionLength` used for parsing SpEL expressions within an +`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can +set a JVM system property or Spring property named `spring.context.expression.maxLength` +to the maximum expression length needed by your application (see +xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). [[expressions-spel-compilation]] diff --git a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java index 29bb92402fdf..88077591ef47 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java +++ b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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,7 @@ import org.springframework.beans.factory.BeanExpressionException; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.expression.Expression; @@ -47,6 +48,7 @@ * beans such as "environment", "systemProperties" and "systemEnvironment". * * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 * @see BeanExpressionContext#getBeanFactory() * @see org.springframework.expression.ExpressionParser @@ -55,6 +57,14 @@ */ public class StandardBeanExpressionResolver implements BeanExpressionResolver { + /** + * System property to configure the maximum length for SpEL expressions: {@value}. + *

Can also be configured via the {@link SpringProperties} mechanism. + * @since 6.1.3 + * @see SpelParserConfiguration#getMaximumExpressionLength() + */ + public static final String MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME = "spring.context.expression.maxLength"; + /** Default expression prefix: "#{". */ public static final String DEFAULT_EXPRESSION_PREFIX = "#{"; @@ -90,18 +100,24 @@ public String getExpressionSuffix() { /** * Create a new {@code StandardBeanExpressionResolver} with default settings. + *

As of Spring Framework 6.1.3, the maximum SpEL expression length can be + * configured via the {@link #MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME} property. */ public StandardBeanExpressionResolver() { - this.expressionParser = new SpelExpressionParser(); + this(null); } /** * Create a new {@code StandardBeanExpressionResolver} with the given bean class loader, * using it as the basis for expression compilation. + *

As of Spring Framework 6.1.3, the maximum SpEL expression length can be + * configured via the {@link #MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME} property. * @param beanClassLoader the factory's bean class loader */ public StandardBeanExpressionResolver(@Nullable ClassLoader beanClassLoader) { - this.expressionParser = new SpelExpressionParser(new SpelParserConfiguration(null, beanClassLoader)); + SpelParserConfiguration parserConfig = new SpelParserConfiguration( + null, beanClassLoader, false, false, Integer.MAX_VALUE, retrieveMaxExpressionLength()); + this.expressionParser = new SpelExpressionParser(parserConfig); } @@ -178,4 +194,22 @@ public Object evaluate(@Nullable String value, BeanExpressionContext beanExpress protected void customizeEvaluationContext(StandardEvaluationContext evalContext) { } + private static int retrieveMaxExpressionLength() { + String value = SpringProperties.getProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME); + if (!StringUtils.hasText(value)) { + return SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH; + } + + try { + int maxLength = Integer.parseInt(value.trim()); + Assert.isTrue(maxLength > 0, () -> "Value [" + maxLength + "] for system property [" + + MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME + "] must be positive"); + return maxLength; + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("Failed to parse value for system property [" + + MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME + "]: " + ex.getMessage(), ex); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 30e7e972ec74..4e18f567b6c1 100644 --- a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -42,17 +43,24 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.testfixture.io.SerializationTestUtils; +import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.springframework.context.expression.StandardBeanExpressionResolver.MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME; /** + * Integration tests for SpEL expression support in an {@code ApplicationContext}. + * * @author Juergen Hoeller * @author Sam Brannen * @since 3.0 @@ -268,6 +276,70 @@ void resourceInjection() throws IOException { } } + @Test + void maxSpelExpressionLengthMustBeAnInteger() { + doWithMaxSpelExpressionLength("boom", () -> { + try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) { + assertThatIllegalArgumentException() + .isThrownBy(ac::refresh) + .withMessageStartingWith("Failed to parse value for system property [%s]", + MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME) + .withMessageContaining("boom"); + } + }); + } + + @Test + void maxSpelExpressionLengthMustBePositive() { + doWithMaxSpelExpressionLength("-99", () -> { + try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) { + assertThatIllegalArgumentException() + .isThrownBy(ac::refresh) + .withMessage("Value [%d] for system property [%s] must be positive", -99, + MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME); + } + }); + } + + @Test + void maxSpelExpressionLength() { + final String expression = "#{ 'xyz' + 'xyz' + 'xyz' }"; + + // With the default max length of 10_000, the expression should succeed. + evaluateExpressionInBean(expression); + + // With a max length of 20, the expression should fail. + doWithMaxSpelExpressionLength("20", () -> + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> evaluateExpressionInBean(expression)) + .havingRootCause() + .isInstanceOf(SpelEvaluationException.class) + .withMessageEndingWith("exceeding the threshold of '20' characters")); + } + + private static void doWithMaxSpelExpressionLength(String maxLength, Runnable action) { + try { + SpringProperties.setProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME, maxLength); + action.run(); + } + finally { + SpringProperties.setProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME, null); + } + } + + private static void evaluateExpressionInBean(String expression) { + try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) { + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setBeanClass(String.class); + bd.getConstructorArgumentValues().addGenericArgumentValue(expression); + ac.registerBeanDefinition("str", bd); + ac.refresh(); + + String str = ac.getBean("str", String.class); + assertThat(str).isEqualTo("xyz".repeat(3)); // "#{ 'xyz' + 'xyz' + 'xyz' }" + } + } + @SuppressWarnings("serial") public static class ValueTestBean implements Serializable { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index ff17068fbbc4..f0430ac2d676 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -35,7 +35,7 @@ public class SpelParserConfiguration { * Default maximum length permitted for a SpEL expression. * @since 5.2.24 */ - private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000; + public static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000; /** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */ public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";