This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new 9e1f4edb9 WW-5834 fix(conversion): reject over-long numeric strings
before parsing them
9e1f4edb9 is described below
commit 9e1f4edb9fecc49b18ca3a0e4657abeb04124d06
Author: ⳕⲛτⲉⲅⲥⲉⳏτⲟⲅ 🕵🏻 <[email protected]>
AuthorDate: Sat Oct 31 19:53:25 2026 +0530
WW-5834 fix(conversion): reject over-long numeric strings before parsing
them
NumberConverter parsed numeric strings of any length with BigInteger,
BigDecimal and DecimalFormat, whose string parsing is quadratic in the
length of the string. A request parameter value is not length-limited
(only the parameter name is), so a long digit string bound to a boxed
number, BigInteger or BigDecimal property cost seconds of CPU.
Strings longer than 1000 characters are now rejected with a
TypeConversionException, which is reported as the usual conversion error
for the field. 1000 is well above any number the framework itself
produces; surrounding whitespace is trimmed before the check.
---
.../struts2/conversion/impl/NumberConverter.java | 7 +++++
.../conversion/impl/NumberConverterTest.java | 34 ++++++++++++++++++++++
.../conversion/impl/XWorkConverterTest.java | 22 ++++++++++++++
3 files changed, 63 insertions(+)
diff --git
a/core/src/main/java/org/apache/struts2/conversion/impl/NumberConverter.java
b/core/src/main/java/org/apache/struts2/conversion/impl/NumberConverter.java
index 8a1da5123..78d368568 100644
--- a/core/src/main/java/org/apache/struts2/conversion/impl/NumberConverter.java
+++ b/core/src/main/java/org/apache/struts2/conversion/impl/NumberConverter.java
@@ -35,10 +35,17 @@ public class NumberConverter extends DefaultTypeConverter {
private static final Logger LOG =
LogManager.getLogger(NumberConverter.class);
+ // Longest accepted numeric string; parsing it as BigInteger/BigDecimal is
quadratic in its length
+ private static final int MAX_NUMBER_LENGTH = 1000;
+
public Object convertValue(Map<String, Object> context, Object target,
Member member, String propertyName, Object value, Class toType) {
if (value instanceof String) {
String stringValue = String.valueOf(value);
+ if (stringValue.length() > MAX_NUMBER_LENGTH) {
+ throw new TypeConversionException("Number too long: " +
stringValue.length() + " characters, the maximum is " + MAX_NUMBER_LENGTH);
+ }
+
if (toType == BigDecimal.class) {
return convertToBigDecimal(context, stringValue);
} else if (toType == BigInteger.class) {
diff --git
a/core/src/test/java/org/apache/struts2/conversion/impl/NumberConverterTest.java
b/core/src/test/java/org/apache/struts2/conversion/impl/NumberConverterTest.java
index b90b820be..657437b15 100644
---
a/core/src/test/java/org/apache/struts2/conversion/impl/NumberConverterTest.java
+++
b/core/src/test/java/org/apache/struts2/conversion/impl/NumberConverterTest.java
@@ -24,6 +24,7 @@ import org.apache.struts2.conversion.TypeConversionException;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Locale;
import java.util.Map;
@@ -35,6 +36,8 @@ public class NumberConverterTest extends XWorkTestCase {
private final static String MSG_OUT_OF_RANGE_CASTING = "Overflow or
underflow casting";
private final static String MSG_OUT_OF_RANGE_CONVERTING = "Overflow or
underflow converting";
private final static String MSG_UNPARSEABLE_NUMBER = "Unparseable number";
+ private final static String MSG_NUMBER_TOO_LONG = "Number too long";
+ private final static String MSG_TEST_FAILS_NUMBER_TOO_LONG =
"TypeConversionException expected when NUMBER TOO LONG";
private final static String MSG_TEST_FAILS_OUT_OF_RANGE =
"TypeConversionException expected when OUT OF RANGE";
private final static String MSG_TEST_FAILS_UNPARSEABLE_NUMBER =
"TypeConversionException expected when UNPARSEABLE NUMBER";
private final static Locale LOCALE_MEXICO = new Locale("es_MX", "MX");
@@ -105,6 +108,37 @@ public class NumberConverterTest extends XWorkTestCase {
assertEquals(BigDecimal.valueOf(100234.4), value);
}
+ public void testStringToBigDecimalConversionWithMaxLength() throws
Exception {
+ // given
+ NumberConverter converter = new NumberConverter();
+ Map<String, Object> context = createContextWithLocale(new Locale("en",
"US"));
+ String digits = StringUtils.repeat('1', 1000);
+
+ // when
+ Object value = converter.convertValue(context, null, null, null,
digits, BigDecimal.class);
+
+ // then
+ assertEquals(new BigDecimal(digits), value);
+ }
+
+ public void testExceptionWhenNumberIsTooLong() {
+ // given
+ NumberConverter converter = new NumberConverter();
+ Map<String, Object> context = createContextWithLocale(new Locale("en",
"US"));
+ String digits = StringUtils.repeat('1', 1001);
+
+ for (Class<?> type : new Class<?>[]{BigDecimal.class,
BigInteger.class, Double.class, Integer.class, int.class}) {
+ // when
+ try {
+ converter.convertValue(context, null, null, null, digits,
type);
+ fail(MSG_TEST_FAILS_NUMBER_TOO_LONG);
+ } catch (TypeConversionException ex) {
+ // then
+ assertTrue(ex.getMessage().startsWith(MSG_NUMBER_TOO_LONG));
+ }
+ }
+ }
+
public void testStringToDoubleConversionPL() throws Exception {
// given
NumberConverter converter = new NumberConverter();
diff --git
a/core/src/test/java/org/apache/struts2/conversion/impl/XWorkConverterTest.java
b/core/src/test/java/org/apache/struts2/conversion/impl/XWorkConverterTest.java
index a89a12adc..93aeebc93 100644
---
a/core/src/test/java/org/apache/struts2/conversion/impl/XWorkConverterTest.java
+++
b/core/src/test/java/org/apache/struts2/conversion/impl/XWorkConverterTest.java
@@ -225,6 +225,28 @@ public class XWorkConverterTest extends XWorkTestCase {
assertEquals(value, conversionErrors.get("date").getValue());
}
+ public void testFieldErrorMessageAddedWhenNumberIsTooLong() {
+ SimpleAction action = new SimpleAction();
+ stack.push(action);
+
+ Map<String, Object> ognlStackContext = stack.getContext();
+ ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS,
Boolean.TRUE);
+
+ String[] value = new String[]{"1".repeat(1001)};
+ assertEquals("Conversion should have failed.",
OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext,
action, null, "amount", value, BigDecimal.class));
+ stack.pop();
+
+ Map<String, ConversionData> conversionErrors =
ActionContext.of(ognlStackContext).getConversionErrors();
+ assertEquals(1, conversionErrors.size());
+ assertEquals(value, conversionErrors.get("amount").getValue());
+ }
+
+ public void testSurroundingWhitespaceIsNotCountedAgainstMaxNumberLength() {
+ String digits = "1".repeat(1000);
+
+ assertEquals(new BigDecimal(digits), converter.convertValue(context,
null, null, null, new String[]{" " + digits + " "}, BigDecimal.class));
+ }
+
public void testFieldErrorMessageAddedWhenConversionFailsOnModelDriven() {
ModelDrivenAction action = new ModelDrivenAction();
stack.push(action);