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 3a3c96eaa5f..ae94b4c3b4f 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-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -30,6 +30,12 @@ import org.springframework.lang.Nullable; */ 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; + /** 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"; @@ -54,6 +60,8 @@ public class SpelParserConfiguration { private final int maximumAutoGrowSize; + private final int maximumExpressionLength; + /** * Create a new {@code SpelParserConfiguration} instance with default settings. @@ -102,11 +110,30 @@ public class SpelParserConfiguration { public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { + this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections, + maximumAutoGrowSize, DEFAULT_MAX_EXPRESSION_LENGTH); + } + + /** + * Create a new {@code SpelParserConfiguration} instance. + * @param compilerMode the compiler mode that parsers using this configuration object should use + * @param compilerClassLoader the ClassLoader to use as the basis for expression compilation + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @param maximumAutoGrowSize the maximum size that a collection can auto grow + * @param maximumExpressionLength the maximum length of a SpEL expression; + * must be a positive number + * @since 5.2.25 + */ + public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, + boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) { + this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode); this.compilerClassLoader = compilerClassLoader; this.autoGrowNullReferences = autoGrowNullReferences; this.autoGrowCollections = autoGrowCollections; this.maximumAutoGrowSize = maximumAutoGrowSize; + this.maximumExpressionLength = maximumExpressionLength; } @@ -146,4 +173,12 @@ public class SpelParserConfiguration { return this.maximumAutoGrowSize; } + /** + * Return the maximum number of characters that a SpEL expression can contain. + * @since 5.2.25 + */ + public int getMaximumExpressionLength() { + return this.maximumExpressionLength; + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index b8b4b68c38f..54275a974e4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -93,13 +93,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+"); - /** - * Maximum length permitted for a SpEL expression. - * @since 5.2.24 - */ - private static final int MAX_EXPRESSION_LENGTH = 10_000; - - private final SpelParserConfiguration configuration; // For rules that build nodes, they are stacked here for return @@ -158,8 +151,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } private void checkExpressionLength(String string) { - if (string.length() > MAX_EXPRESSION_LENGTH) { - throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, MAX_EXPRESSION_LENGTH); + int maxLength = this.configuration.getMaximumExpressionLength(); + if (string.length() > maxLength) { + throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, maxLength); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java index b60d6942faa..e808aa661c5 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java @@ -164,6 +164,24 @@ public abstract class AbstractExpressionTests { */ protected void evaluateAndCheckError(String expression, Class expectedReturnType, SpelMessage expectedMessage, Object... otherProperties) { + + evaluateAndCheckError(this.parser, expression, expectedReturnType, expectedMessage, otherProperties); + } + + /** + * Evaluate the specified expression and ensure the expected message comes out. + * The message may have inserts and they will be checked if otherProperties is specified. + * The first entry in otherProperties should always be the position. + * @param parser the expression parser to use + * @param expression the expression to evaluate + * @param expectedReturnType ask the expression return value to be of this type if possible + * ({@code null} indicates don't ask for conversion) + * @param expectedMessage the expected message + * @param otherProperties the expected inserts within the message + */ + protected void evaluateAndCheckError(ExpressionParser parser, String expression, Class expectedReturnType, SpelMessage expectedMessage, + Object... otherProperties) { + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> { Expression expr = parser.parseExpression(expression); assertThat(expr).as("expression").isNotNull(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index fb3dda85507..a1862222e1a 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -77,6 +77,26 @@ class EvaluationTests extends AbstractExpressionTests { evaluateAndCheckError(expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED); } + @Test + void maxExpressionLengthIsConfigurable() { + int maximumExpressionLength = 20_000; + + String expression = "'%s'".formatted("Y".repeat(19_998)); + assertThat(expression).hasSize(maximumExpressionLength); + + SpelParserConfiguration configuration = + new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength); + ExpressionParser parser = new SpelExpressionParser(configuration); + + Expression expr = parser.parseExpression(expression); + String result = expr.getValue(String.class); + assertThat(result).hasSize(19_998); + + expression = "'%s'".formatted("Y".repeat(25_000)); + assertThat(expression).hasSize(25_002); + evaluateAndCheckError(parser, expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED); + } + @Test void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));