diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index a3f588a1de4d..4fc4eae7bec6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -260,7 +260,11 @@ public enum SpelMessage { /** @since 5.2.20 */ MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075, - "Array declares too many elements, exceeding the threshold of ''{0}''"); + "Array declares too many elements, exceeding the threshold of ''{0}''"), + + /** @since 5.2.23 */ + MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076, + "Repeated text results in too many characters, exceeding the threshold of ''{0}''"); private final Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java index fbf28317b32b..0cff5cb0c8c0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; @@ -52,6 +54,13 @@ */ public class OpMultiply extends Operator { + /** + * Maximum number of characters permitted in repeated text. + * @since 5.2.23 + */ + private static final int MAX_REPEATED_TEXT_SIZE = 256; + + public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) { super("*", startPos, endPos, operands); } @@ -109,10 +118,13 @@ else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNume } if (leftOperand instanceof String && rightOperand instanceof Integer) { - int repeats = (Integer) rightOperand; - StringBuilder result = new StringBuilder(); - for (int i = 0; i < repeats; i++) { - result.append(leftOperand); + String text = (String) leftOperand; + int count = (Integer) rightOperand; + int requestedSize = text.length() * count; + checkRepeatedTextSize(requestedSize); + StringBuilder result = new StringBuilder(requestedSize); + for (int i = 0; i < count; i++) { + result.append(text); } return new TypedValue(result.toString()); } @@ -120,6 +132,13 @@ else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNume return state.operate(Operation.MULTIPLY, leftOperand, rightOperand); } + private void checkRepeatedTextSize(int requestedSize) { + if (requestedSize > MAX_REPEATED_TEXT_SIZE) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE); + } + } + @Override public boolean isCompilable() { if (!getLeftOperand().isCompilable()) { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index 92fef3bec695..26a219ece332 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,12 @@ import org.junit.jupiter.api.Test; +import org.springframework.expression.Expression; import org.springframework.expression.spel.ast.Operator; import org.springframework.expression.spel.standard.SpelExpression; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED; /** * Tests the evaluation of expressions using relational operators. @@ -32,6 +34,7 @@ * @author Andy Clement * @author Juergen Hoeller * @author Giovanni Dall'Oglio Risso + * @author Sam Brannen */ public class OperatorTests extends AbstractExpressionTests { @@ -324,11 +327,6 @@ public void testRealLiteral() { evaluate("3.5", 3.5d, Double.class); } - @Test - public void testMultiplyStringInt() { - evaluate("'a' * 5", "aaaaa", String.class); - } - @Test public void testMultiplyDoubleDoubleGivesDouble() { evaluate("3.0d * 5.0d", 15.0d, Double.class); @@ -576,6 +574,19 @@ public void testStrings() { evaluate("'abc' != 'def'", true, Boolean.class); } + @Test + void stringRepeat() { + evaluate("'abc' * 0", "", String.class); + evaluate("'abc' * 1", "abc", String.class); + evaluate("'abc' * 2", "abcabc", String.class); + + Expression expr = parser.parseExpression("'a' * 256"); + assertThat(expr.getValue(context, String.class)).hasSize(256); + + // 4 is the position of the '*' (repeat operator) + evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4); + } + @Test public void testLongs() { evaluate("3L == 4L", false, Boolean.class);