OperatorMatches flags misguided evaluation attempts as FLAWED_PATTERN

Issue: SPR-16731
This commit is contained in:
Juergen Hoeller 2018-04-17 11:10:15 +02:00
parent c5b524db7c
commit d4a55a257b
4 changed files with 89 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,13 +30,15 @@ class VariableNotAvailableException extends EvaluationException {
private final String name; private final String name;
public VariableNotAvailableException(String name) { public VariableNotAvailableException(String name) {
super("Variable '" + name + "' is not available"); super("Variable not available");
this.name = name; this.name = name;
} }
public String getName() { public final String getName() {
return this.name; return this.name;
} }
} }

View File

@ -226,21 +226,22 @@ public enum SpelMessage {
"A required array dimension has not been specified"), "A required array dimension has not been specified"),
INITIALIZER_LENGTH_INCORRECT(Kind.ERROR, 1064, INITIALIZER_LENGTH_INCORRECT(Kind.ERROR, 1064,
"array initializer size does not match array dimensions"), "Array initializer size does not match array dimensions"),
UNEXPECTED_ESCAPE_CHAR(Kind.ERROR, 1065, "unexpected escape character."), UNEXPECTED_ESCAPE_CHAR(Kind.ERROR, 1065,
"Unexpected escape character"),
OPERAND_NOT_INCREMENTABLE(Kind.ERROR, 1066, OPERAND_NOT_INCREMENTABLE(Kind.ERROR, 1066,
"the expression component ''{0}'' does not support increment"), "The expression component ''{0}'' does not support increment"),
OPERAND_NOT_DECREMENTABLE(Kind.ERROR, 1067, OPERAND_NOT_DECREMENTABLE(Kind.ERROR, 1067,
"the expression component ''{0}'' does not support decrement"), "The expression component ''{0}'' does not support decrement"),
NOT_ASSIGNABLE(Kind.ERROR, 1068, NOT_ASSIGNABLE(Kind.ERROR, 1068,
"the expression component ''{0}'' is not assignable"), "The expression component ''{0}'' is not assignable"),
MISSING_CHARACTER(Kind.ERROR, 1069, MISSING_CHARACTER(Kind.ERROR, 1069,
"missing expected character ''{0}''"), "Missing expected character ''{0}''"),
LEFT_OPERAND_PROBLEM(Kind.ERROR, 1070, LEFT_OPERAND_PROBLEM(Kind.ERROR, 1070,
"Problem parsing left operand"), "Problem parsing left operand"),
@ -248,8 +249,13 @@ public enum SpelMessage {
MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071,
"A required selection expression has not been specified"), "A required selection expression has not been specified"),
/** @since 4.1 */
EXCEPTION_RUNNING_COMPILED_EXPRESSION(Kind.ERROR, 1072, EXCEPTION_RUNNING_COMPILED_EXPRESSION(Kind.ERROR, 1072,
"An exception occurred whilst evaluating a compiled expression"); "An exception occurred whilst evaluating a compiled expression"),
/** @since 4.3.17 */
FLAWED_PATTERN(Kind.ERROR, 1073,
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it");
private final Kind kind; private final Kind kind;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -40,6 +40,8 @@ import org.springframework.expression.spel.support.BooleanTypedValue;
*/ */
public class OperatorMatches extends Operator { public class OperatorMatches extends Operator {
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
@ -79,11 +81,59 @@ public class OperatorMatches extends Operator {
pattern = Pattern.compile(rightString); pattern = Pattern.compile(rightString);
this.patternCache.putIfAbsent(rightString, pattern); this.patternCache.putIfAbsent(rightString, pattern);
} }
Matcher matcher = pattern.matcher(left); Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
return BooleanTypedValue.forValue(matcher.matches()); return BooleanTypedValue.forValue(matcher.matches());
} }
catch (PatternSyntaxException ex) { catch (PatternSyntaxException ex) {
throw new SpelEvaluationException(rightOp.getStartPosition(), ex, SpelMessage.INVALID_PATTERN, right); throw new SpelEvaluationException(
rightOp.getStartPosition(), ex, SpelMessage.INVALID_PATTERN, right);
}
catch (IllegalStateException ex) {
throw new SpelEvaluationException(
rightOp.getStartPosition(), ex, SpelMessage.FLAWED_PATTERN, right);
}
}
private static class AccessCount {
private int count;
public void check() throws IllegalStateException {
if (this.count++ > PATTERN_ACCESS_THRESHOLD) {
throw new IllegalStateException("Pattern access threshold exceeded");
}
}
}
private static class MatcherInput implements CharSequence {
private final CharSequence value;
private AccessCount access;
public MatcherInput(CharSequence value, AccessCount access) {
this.value = value;
this.access = access;
}
public char charAt(int index) {
this.access.check();
return this.value.charAt(index);
}
public CharSequence subSequence(int start, int end) {
return new MatcherInput(this.value.subSequence(start, end), this.access);
}
public int length() {
return this.value.length();
}
@Override
public String toString() {
return this.value.toString();
} }
} }

View File

@ -194,6 +194,22 @@ public class EvaluationTests extends AbstractExpressionTests {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string
} }
@Test // SPR-16731
public void testMatchesWithPatternAccessThreshold() {
String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$";
String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'";
Expression expr = parser.parseExpression(expression);
try {
expr.getValue();
fail("Should have exceeded threshold");
}
catch (EvaluationException ee) {
SpelEvaluationException see = (SpelEvaluationException) ee;
assertEquals(SpelMessage.FLAWED_PATTERN, see.getMessageCode());
assertTrue(see.getCause() instanceof IllegalStateException);
}
}
// mixing operators // mixing operators
@Test @Test
public void testMixingOperators01() { public void testMixingOperators01() {