Browse Source

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
pull/30262/head
Sam Brannen 3 years ago
parent
commit
26e0343c16
  1. 6
      spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java
  2. 33
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java
  3. 14
      spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java

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

@ -268,7 +268,11 @@ public enum SpelMessage { @@ -268,7 +268,11 @@ public enum SpelMessage {
/** @since 5.3.26 */
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
"Repeated text results in too many characters, exceeding the threshold of ''{0}''");
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
/** @since 5.3.26 */
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
private final Kind kind;

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

@ -43,6 +43,12 @@ public class OperatorMatches extends Operator { @@ -43,6 +43,12 @@ public class OperatorMatches extends Operator {
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
/**
* Maximum number of characters permitted in a regular expression.
* @since 5.3.26
*/
private static final int MAX_REGEX_LENGTH = 256;
private final ConcurrentMap<String, Pattern> patternCache;
@ -78,26 +84,28 @@ public class OperatorMatches extends Operator { @@ -78,26 +84,28 @@ public class OperatorMatches extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
String left = leftOp.getValue(state, String.class);
Object right = rightOp.getValue(state);
if (left == null) {
String input = leftOp.getValue(state, String.class);
if (input == null) {
throw new SpelEvaluationException(leftOp.getStartPosition(),
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null);
}
Object right = rightOp.getValue(state);
if (!(right instanceof String)) {
throw new SpelEvaluationException(rightOp.getStartPosition(),
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
}
String regex = (String) right;
try {
String rightString = (String) right;
Pattern pattern = this.patternCache.get(rightString);
Pattern pattern = this.patternCache.get(regex);
if (pattern == null) {
pattern = Pattern.compile(rightString);
this.patternCache.putIfAbsent(rightString, pattern);
checkRegexLength(regex);
pattern = Pattern.compile(regex);
this.patternCache.putIfAbsent(regex, pattern);
}
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
return BooleanTypedValue.forValue(matcher.matches());
}
catch (PatternSyntaxException ex) {
@ -110,6 +118,13 @@ public class OperatorMatches extends Operator { @@ -110,6 +118,13 @@ public class OperatorMatches extends Operator {
}
}
private void checkRegexLength(String regex) {
if (regex.length() > MAX_REGEX_LENGTH) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
}
}
private static class AccessCount {
@ -127,7 +142,7 @@ public class OperatorMatches extends Operator { @@ -127,7 +142,7 @@ public class OperatorMatches extends Operator {
private final CharSequence value;
private AccessCount access;
private final AccessCount access;
public MatcherInput(CharSequence value, AccessCount access) {
this.value = value;

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

@ -480,6 +480,20 @@ class EvaluationTests extends AbstractExpressionTests { @@ -480,6 +480,20 @@ class EvaluationTests extends AbstractExpressionTests {
evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN);
}
@Test
void matchesWithPatternLengthThreshold() {
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
"01234567890123456789012345678901234567890123456789|abc)";
assertThat(pattern).hasSize(256);
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
assertThat(expr.getValue(context, Boolean.class)).isTrue();
pattern += "?";
assertThat(pattern).hasSize(257);
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
}
}
@Nested

Loading…
Cancel
Save