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);

Reply via email to