diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java index ecec3709..10902152 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java @@ -47,6 +47,25 @@ public class FilterCompiler { private static final char PATTERN = '/'; private static final char IGNORE_CASE = 'i'; + /** + * Upper bound for the length of a numeric literal inside a filter. The literal is turned into a + * {@link java.math.BigDecimal} during compilation, so an arbitrarily long token would let a caller + * spend unbounded CPU and memory before any document is evaluated. + */ + private static final int MAX_NUMBER_LITERAL_LENGTH = 1000; + + /** + * Signals that a numeric literal exceeds {@link #MAX_NUMBER_LITERAL_LENGTH}. It is an + * {@link InvalidPathException} so callers keep seeing the documented exception type, but it is + * rethrown by {@link #readExpression()} instead of being handled as "this is not a relational + * expression", which would hide the real problem and fail with a confusing message later on. + */ + private static final class NumberLiteralTooLongException extends InvalidPathException { + NumberLiteralTooLongException(String message) { + super(message); + } + } + private CharacterIndex filter; public static Filter compile(String filterString) { @@ -194,6 +213,9 @@ private RelationalExpressionNode readExpression() { ValueNode right = readValueNode(); return new RelationalExpressionNode(left, operator, right); } + catch (NumberLiteralTooLongException exc) { + throw exc; + } catch (InvalidPathException exc) { filter.setPosition(savepoint); } @@ -327,6 +349,10 @@ private NumberNode readNumberLiteral() { while (filter.inBounds() && filter.isNumberCharacter(filter.position())) { filter.incrementPosition(1); + if (filter.position() - begin > MAX_NUMBER_LITERAL_LENGTH) { + throw new NumberLiteralTooLongException( + "Number literal exceeds the maximum allowed length of " + MAX_NUMBER_LITERAL_LENGTH); + } } CharSequence numberLiteral = filter.subSequence(begin, filter.position()); logger.trace("NumberLiteral from {} to {} -> [{}]", begin, filter.position(), numberLiteral); diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java index 30628015..701fd462 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java @@ -102,4 +102,22 @@ private void assertInvalidPathException(String filter) { //e.printStackTrace(); } } + + @Test + public void number_literal_longer_than_the_limit_is_rejected() { + String number = "9".repeat(1001); + + InvalidPathException exception = assertThrows(InvalidPathException.class, + () -> compile("[?(@.x == " + number + ")]")); + + assertThat(exception.getMessage()).contains("maximum allowed length"); + } + + @Test + public void number_literal_at_the_limit_is_accepted() { + String number = "9".repeat(1000); + + assertThat(compile("[?(@.x == " + number + ")]").toString()) + .isEqualTo("[?(@['x'] == " + number + ")]"); + } }