Skip to content

Commit b9b31af

Browse files
committed
Improve diagnostics in SpEL for matches operator
Supplying a large regular expression to the `matches` operator in a SpEL expression can result in errors that are not very helpful to the user. This commit improves the diagnostics in SpEL for the `matches` operator by throwing a SpelEvaluationException with a meaningful error message to better assist the user. Closes gh-30150
1 parent 4542b53 commit b9b31af

File tree

3 files changed

+43
-10
lines changed

3 files changed

+43
-10
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,11 @@ public enum SpelMessage {
264264

265265
/** @since 5.2.23 */
266266
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
267-
"Repeated text results in too many characters, exceeding the threshold of ''{0}''");
267+
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
268+
269+
/** @since 5.2.23 */
270+
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
271+
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
268272

269273

270274
private final Kind kind;

spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java

+24-9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public class OperatorMatches extends Operator {
4343

4444
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
4545

46+
/**
47+
* Maximum number of characters permitted in a regular expression.
48+
* @since 5.2.23
49+
*/
50+
private static final int MAX_REGEX_LENGTH = 256;
51+
4652
private final ConcurrentMap<String, Pattern> patternCache;
4753

4854

@@ -78,26 +84,28 @@ public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos
7884
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
7985
SpelNodeImpl leftOp = getLeftOperand();
8086
SpelNodeImpl rightOp = getRightOperand();
81-
String left = leftOp.getValue(state, String.class);
82-
Object right = rightOp.getValue(state);
8387

84-
if (left == null) {
88+
String input = leftOp.getValue(state, String.class);
89+
if (input == null) {
8590
throw new SpelEvaluationException(leftOp.getStartPosition(),
8691
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null);
8792
}
93+
94+
Object right = rightOp.getValue(state);
8895
if (!(right instanceof String)) {
8996
throw new SpelEvaluationException(rightOp.getStartPosition(),
9097
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
9198
}
99+
String regex = (String) right;
92100

93101
try {
94-
String rightString = (String) right;
95-
Pattern pattern = this.patternCache.get(rightString);
102+
Pattern pattern = this.patternCache.get(regex);
96103
if (pattern == null) {
97-
pattern = Pattern.compile(rightString);
98-
this.patternCache.putIfAbsent(rightString, pattern);
104+
checkRegexLength(regex);
105+
pattern = Pattern.compile(regex);
106+
this.patternCache.putIfAbsent(regex, pattern);
99107
}
100-
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
108+
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
101109
return BooleanTypedValue.forValue(matcher.matches());
102110
}
103111
catch (PatternSyntaxException ex) {
@@ -110,6 +118,13 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
110118
}
111119
}
112120

121+
private void checkRegexLength(String regex) {
122+
if (regex.length() > MAX_REGEX_LENGTH) {
123+
throw new SpelEvaluationException(getStartPosition(),
124+
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
125+
}
126+
}
127+
113128

114129
private static class AccessCount {
115130

@@ -127,7 +142,7 @@ private static class MatcherInput implements CharSequence {
127142

128143
private final CharSequence value;
129144

130-
private AccessCount access;
145+
private final AccessCount access;
131146

132147
public MatcherInput(CharSequence value, AccessCount access) {
133148
this.value = value;

spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,20 @@ void matchesWithPatternAccessThreshold() {
199199
evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN);
200200
}
201201

202+
@Test
203+
void matchesWithPatternLengthThreshold() {
204+
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
205+
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
206+
"01234567890123456789012345678901234567890123456789|abc)";
207+
assertThat(pattern).hasSize(256);
208+
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
209+
assertThat(expr.getValue(context, Boolean.class)).isTrue();
210+
211+
pattern += "?";
212+
assertThat(pattern).hasSize(257);
213+
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
214+
}
215+
202216
// mixing operators
203217
@Test
204218
public void testMixingOperators01() {

0 commit comments

Comments
 (0)