Skip to content

Commit 26e0343

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-30145
1 parent 4d5e720 commit 26e0343

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

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: 24 additions & 9 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,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

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

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

485499
@Nested

0 commit comments

Comments
 (0)