Skip to content

Commit 8010de8

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-30144
1 parent 5529294 commit 8010de8

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,11 @@ public enum SpelMessage {
268268

269269
/** @since 5.3.26 */
270270
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
271-
"Repeated text results in too many characters, exceeding the threshold of ''{0}''");
271+
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
272+
273+
/** @since 5.3.26 */
274+
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
275+
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
272276

273277

274278
private final Kind kind;

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

Lines changed: 23 additions & 8 deletions
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.3.26
49+
*/
50+
private static final int MAX_REGEX_LENGTH = 256;
51+
4652
private final ConcurrentMap<String, Pattern> patternCache;
4753

4854

@@ -78,25 +84,27 @@ 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 = getRightOperand().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
}
88-
if (!(right instanceof String rightString)) {
93+
94+
Object right = rightOp.getValue(state);
95+
if (!(right instanceof String regex)) {
8996
throw new SpelEvaluationException(rightOp.getStartPosition(),
9097
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
9198
}
9299

93100
try {
94-
Pattern pattern = this.patternCache.get(rightString);
101+
Pattern pattern = this.patternCache.get(regex);
95102
if (pattern == null) {
96-
pattern = Pattern.compile(rightString);
97-
this.patternCache.putIfAbsent(rightString, pattern);
103+
checkRegexLength(regex);
104+
pattern = Pattern.compile(regex);
105+
this.patternCache.putIfAbsent(regex, pattern);
98106
}
99-
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
107+
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
100108
return BooleanTypedValue.forValue(matcher.matches());
101109
}
102110
catch (PatternSyntaxException ex) {
@@ -109,6 +117,13 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
109117
}
110118
}
111119

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

113128
private static class AccessCount {
114129

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,20 @@ void matchesWithPatternAccessThreshold() {
482482
evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN);
483483
}
484484

485+
@Test
486+
void matchesWithPatternLengthThreshold() {
487+
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
488+
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
489+
"01234567890123456789012345678901234567890123456789|abc)";
490+
assertThat(pattern).hasSize(256);
491+
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
492+
assertThat(expr.getValue(context, Boolean.class)).isTrue();
493+
494+
pattern += "?";
495+
assertThat(pattern).hasSize(257);
496+
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
497+
}
498+
485499
}
486500

487501
@Nested

0 commit comments

Comments
 (0)