From 6809fd302b6e1229910b7b7c623e74c018dfa0d4 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Mon, 6 Jul 2026 18:31:43 +0530 Subject: [PATCH] reject non-integer strings in integer locale converters The integer locale converters parsed a value like 5.5 with a DecimalFormat that allows decimals, then narrowed it, silently truncating to 5. Reject a non-integer parse result before narrowing; BigIntegerLocaleConverter uses toBigIntegerExact. --- .../converters/BigIntegerLocaleConverter.java | 6 +++++- .../converters/ByteLocaleConverter.java | 4 ++++ .../converters/IntegerLocaleConverter.java | 4 ++++ .../converters/LongLocaleConverter.java | 3 +++ .../converters/ShortLocaleConverter.java | 4 ++++ .../BigIntegerLocaleConverterTest.java | 19 ++++++++++++++----- .../converters/ByteLocaleConverterTest.java | 19 ++++++++++++++----- .../IntegerLocaleConverterTest.java | 19 ++++++++++++++----- .../converters/LongLocaleConverterTest.java | 19 ++++++++++++++----- .../converters/ShortLocaleConverterTest.java | 19 ++++++++++++++----- 10 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java b/src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java index 41dc4a471..73433fffc 100644 --- a/src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java +++ b/src/main/java/org/apache/commons/beanutils2/locale/converters/BigIntegerLocaleConverter.java @@ -80,7 +80,11 @@ protected BigInteger parse(final Object value, final String pattern) throws Pars return (BigInteger) result; } if (result instanceof BigDecimal) { - return ((BigDecimal) result).toBigInteger(); + try { + return ((BigDecimal) result).toBigIntegerExact(); + } catch (final ArithmeticException e) { + throw new ConversionException("Supplied number is not an integer: " + result, e); + } } return BigInteger.valueOf(result.longValue()); } diff --git a/src/main/java/org/apache/commons/beanutils2/locale/converters/ByteLocaleConverter.java b/src/main/java/org/apache/commons/beanutils2/locale/converters/ByteLocaleConverter.java index 9119629f0..a3286dda6 100644 --- a/src/main/java/org/apache/commons/beanutils2/locale/converters/ByteLocaleConverter.java +++ b/src/main/java/org/apache/commons/beanutils2/locale/converters/ByteLocaleConverter.java @@ -74,6 +74,10 @@ protected Byte parse(final Object value, final String pattern) throws ParseExcep if (parsed.longValue() != parsed.byteValue()) { throw new ConversionException("Supplied number is not of type Byte: " + parsed.longValue()); } + final double doubleValue = parsed.doubleValue(); + if (doubleValue != Math.rint(doubleValue)) { + throw new ConversionException("Supplied number is not an integer: " + parsed); + } // now returns property Byte return Byte.valueOf(parsed.byteValue()); } diff --git a/src/main/java/org/apache/commons/beanutils2/locale/converters/IntegerLocaleConverter.java b/src/main/java/org/apache/commons/beanutils2/locale/converters/IntegerLocaleConverter.java index e805e4929..0cfe6957c 100644 --- a/src/main/java/org/apache/commons/beanutils2/locale/converters/IntegerLocaleConverter.java +++ b/src/main/java/org/apache/commons/beanutils2/locale/converters/IntegerLocaleConverter.java @@ -74,6 +74,10 @@ protected Integer parse(final Object value, final String pattern) throws ParseEx if (parsed.longValue() != parsed.intValue()) { throw new ConversionException("Supplied number is not of type Integer: " + parsed.longValue()); } + final double doubleValue = parsed.doubleValue(); + if (doubleValue != Math.rint(doubleValue)) { + throw new ConversionException("Supplied number is not an integer: " + parsed); + } return Integer.valueOf(parsed.intValue()); // unlike superclass it will return proper Integer } } diff --git a/src/main/java/org/apache/commons/beanutils2/locale/converters/LongLocaleConverter.java b/src/main/java/org/apache/commons/beanutils2/locale/converters/LongLocaleConverter.java index 0fe51987d..7f9ae3779 100644 --- a/src/main/java/org/apache/commons/beanutils2/locale/converters/LongLocaleConverter.java +++ b/src/main/java/org/apache/commons/beanutils2/locale/converters/LongLocaleConverter.java @@ -79,6 +79,9 @@ protected Long parse(final Object value, final String pattern) throws ParseExcep if (doubleValue < Long.MIN_VALUE || doubleValue > Long.MAX_VALUE) { throw new ConversionException("Supplied number is not of type Long: " + result); } + if (doubleValue != Math.rint(doubleValue)) { + throw new ConversionException("Supplied number is not an integer: " + result); + } return Long.valueOf(result.longValue()); } } diff --git a/src/main/java/org/apache/commons/beanutils2/locale/converters/ShortLocaleConverter.java b/src/main/java/org/apache/commons/beanutils2/locale/converters/ShortLocaleConverter.java index 570190cc1..e2d2fad4c 100644 --- a/src/main/java/org/apache/commons/beanutils2/locale/converters/ShortLocaleConverter.java +++ b/src/main/java/org/apache/commons/beanutils2/locale/converters/ShortLocaleConverter.java @@ -84,6 +84,10 @@ protected Short parse(final Object value, final String pattern) throws ParseExce if (parsed.longValue() != parsed.shortValue()) { throw new ConversionException("Supplied number is not of type Short: " + parsed.longValue()); } + final double doubleValue = parsed.doubleValue(); + if (doubleValue != Math.rint(doubleValue)) { + throw new ConversionException("Supplied number is not an integer: " + parsed); + } // now returns property Short return Short.valueOf(parsed.shortValue()); } diff --git a/src/test/java/org/apache/commons/beanutils2/converters/BigIntegerLocaleConverterTest.java b/src/test/java/org/apache/commons/beanutils2/converters/BigIntegerLocaleConverterTest.java index 11d7f09e7..476a6f8ed 100644 --- a/src/test/java/org/apache/commons/beanutils2/converters/BigIntegerLocaleConverterTest.java +++ b/src/test/java/org/apache/commons/beanutils2/converters/BigIntegerLocaleConverterTest.java @@ -176,12 +176,12 @@ void testConstructorMain() { convertInvalid(converter, "(A)", defaultValue); convertNull(converter, "(A)", defaultValue); // ************************************************************************** - // Convert value in the wrong format - maybe you would expect it to throw an - // exception and return the default - it doesn't, DecimalFormat parses it - // quite happily turning "1,234" into "1" - // I guess this is one of the limitations of DecimalFormat + // Convert value in the wrong format - the localized (German) converter reads + // ',' as the decimal separator, so "1,234" parses to the fractional value + // 1.234; the integer converter now rejects a non-integer result and returns + // the default. // ************************************************************************** - convertValueNoPattern(converter, "(B)", defaultIntegerValue, new BigInteger("1")); + convertValueNoPattern(converter, "(B)", defaultIntegerValue, defaultValue); // ************************************************************************** // Convert with non-localized pattern - the trailing characters left after the // partial parse are now rejected, so the converter returns the default. @@ -203,4 +203,13 @@ void testConstructorMain() { convertInvalid(converter, "(C)", defaultValue); convertNull(converter, "(C)", defaultValue); } + + /** + * Tests that a non-integer value is rejected rather than silently truncated to an integer. + */ + @Test + void testNonIntegerRejected() { + converter = BigIntegerLocaleConverter.builder().setDefault(defaultValue).setLocale(defaultLocale).get(); + convertValueNoPattern(converter, "non-integer", "5.5", defaultValue); + } } diff --git a/src/test/java/org/apache/commons/beanutils2/converters/ByteLocaleConverterTest.java b/src/test/java/org/apache/commons/beanutils2/converters/ByteLocaleConverterTest.java index 609805c86..383d24fe0 100644 --- a/src/test/java/org/apache/commons/beanutils2/converters/ByteLocaleConverterTest.java +++ b/src/test/java/org/apache/commons/beanutils2/converters/ByteLocaleConverterTest.java @@ -169,12 +169,12 @@ void testConstructorMain() { convertInvalid(converter, "(A)", defaultValue); convertNull(converter, "(A)", defaultValue); // ************************************************************************** - // Convert value in the wrong format - maybe you would expect it to throw an - // exception and return the default - it doesn't, DecimalFormat parses it - // quite happily turning ",123" into "0" - // I guess this is one of the limitations of DecimalFormat + // Convert value in the wrong format - the localized (German) converter reads + // ',' as the decimal separator, so ",123" parses to the fractional value + // 0.123; the integer converter now rejects a non-integer result and returns + // the default. // ************************************************************************** - convertValueNoPattern(converter, "(B)", defaultIntegerValue, Byte.valueOf("0")); + convertValueNoPattern(converter, "(B)", defaultIntegerValue, defaultValue); // ************************************************************************** // Convert with non-localized pattern // ************************************************************************** @@ -196,4 +196,13 @@ void testConstructorMain() { convertInvalid(converter, "(C)", defaultValue); convertNull(converter, "(C)", defaultValue); } + + /** + * Tests that a non-integer value is rejected rather than silently truncated to an integer. + */ + @Test + void testNonIntegerRejected() { + converter = ByteLocaleConverter.builder().setDefault(defaultValue).setLocale(defaultLocale).get(); + convertValueNoPattern(converter, "non-integer", "5.5", defaultValue); + } } diff --git a/src/test/java/org/apache/commons/beanutils2/converters/IntegerLocaleConverterTest.java b/src/test/java/org/apache/commons/beanutils2/converters/IntegerLocaleConverterTest.java index 213986ef6..9fcefe7cb 100644 --- a/src/test/java/org/apache/commons/beanutils2/converters/IntegerLocaleConverterTest.java +++ b/src/test/java/org/apache/commons/beanutils2/converters/IntegerLocaleConverterTest.java @@ -164,12 +164,12 @@ void testConstructorMain() { convertInvalid(converter, "(A)", defaultValue); convertNull(converter, "(A)", defaultValue); // ************************************************************************** - // Convert value in the wrong format - maybe you would expect it to throw an - // exception and return the default - it doesn't, DecimalFormat parses it - // quite happily turning "1,234" into "1" - // I guess this is one of the limitations of DecimalFormat + // Convert value in the wrong format - the localized (German) converter reads + // ',' as the decimal separator, so "1,234" parses to the fractional value + // 1.234; the integer converter now rejects a non-integer result and returns + // the default. // ************************************************************************** - convertValueNoPattern(converter, "(B)", defaultIntegerValue, Integer.valueOf("1")); + convertValueNoPattern(converter, "(B)", defaultIntegerValue, defaultValue); // ************************************************************************** // Convert with non-localized pattern - the trailing characters left after the // partial parse are now rejected, so the converter returns the default. @@ -215,4 +215,13 @@ void testToPrimitiveType() { final int result = converter.convert(target, (Object) value.toString()); assertEquals(value.intValue(), result, "Wrong result"); } + + /** + * Tests that a non-integer value is rejected rather than silently truncated to an integer. + */ + @Test + void testNonIntegerRejected() { + converter = IntegerLocaleConverter.builder().setDefault(defaultValue).setLocale(defaultLocale).get(); + convertValueNoPattern(converter, "non-integer", "5.5", defaultValue); + } } diff --git a/src/test/java/org/apache/commons/beanutils2/converters/LongLocaleConverterTest.java b/src/test/java/org/apache/commons/beanutils2/converters/LongLocaleConverterTest.java index dac385f3e..ae15809bf 100644 --- a/src/test/java/org/apache/commons/beanutils2/converters/LongLocaleConverterTest.java +++ b/src/test/java/org/apache/commons/beanutils2/converters/LongLocaleConverterTest.java @@ -168,12 +168,12 @@ void testConstructorMain() { convertInvalid(converter, "(A)", defaultValue); convertNull(converter, "(A)", defaultValue); // ************************************************************************** - // Convert value in the wrong format - maybe you would expect it to throw an - // exception and return the default - it doesn't, DecimalFormat parses it - // quite happily turning "1,234" into "1" - // I guess this is one of the limitations of DecimalFormat + // Convert value in the wrong format - the localized (German) converter reads + // ',' as the decimal separator, so "1,234" parses to the fractional value + // 1.234; the integer converter now rejects a non-integer result and returns + // the default. // ************************************************************************** - convertValueNoPattern(converter, "(B)", defaultIntegerValue, Long.valueOf("1")); + convertValueNoPattern(converter, "(B)", defaultIntegerValue, defaultValue); // ************************************************************************** // Convert with non-localized pattern - the trailing characters left after the // partial parse are now rejected, so the converter returns the default. @@ -208,4 +208,13 @@ void testLongLimits() { assertThrows(ConversionException.class, () -> converter.convert("99999999999999999999")); assertThrows(ConversionException.class, () -> converter.convert("-99999999999999999999")); } + + /** + * Tests that a non-integer value is rejected rather than silently truncated to an integer. + */ + @Test + void testNonIntegerRejected() { + converter = LongLocaleConverter.builder().setDefault(defaultValue).setLocale(defaultLocale).get(); + convertValueNoPattern(converter, "non-integer", "5.5", defaultValue); + } } diff --git a/src/test/java/org/apache/commons/beanutils2/converters/ShortLocaleConverterTest.java b/src/test/java/org/apache/commons/beanutils2/converters/ShortLocaleConverterTest.java index 7c752bf99..76b6834cb 100644 --- a/src/test/java/org/apache/commons/beanutils2/converters/ShortLocaleConverterTest.java +++ b/src/test/java/org/apache/commons/beanutils2/converters/ShortLocaleConverterTest.java @@ -162,12 +162,12 @@ void testConstructorMain() { convertInvalid(converter, "(A)", defaultValue); convertNull(converter, "(A)", defaultValue); // ************************************************************************** - // Convert value in the wrong format - maybe you would expect it to throw an - // exception and return the default - it doesn't, DecimalFormat parses it - // quite happily turning "1,234" into "1" - // I guess this is one of the limitations of DecimalFormat + // Convert value in the wrong format - the localized (German) converter reads + // ',' as the decimal separator, so "1,234" parses to the fractional value + // 1.234; the integer converter now rejects a non-integer result and returns + // the default. // ************************************************************************** - convertValueNoPattern(converter, "(B)", defaultIntegerValue, Short.valueOf("1")); + convertValueNoPattern(converter, "(B)", defaultIntegerValue, defaultValue); // ************************************************************************** // Convert with non-localized pattern - the trailing characters left after the // partial parse are now rejected, so the converter returns the default. @@ -189,4 +189,13 @@ void testConstructorMain() { convertInvalid(converter, "(C)", defaultValue); convertNull(converter, "(C)", defaultValue); } + + /** + * Tests that a non-integer value is rejected rather than silently truncated to an integer. + */ + @Test + void testNonIntegerRejected() { + converter = ShortLocaleConverter.builder().setDefault(defaultValue).setLocale(defaultLocale).get(); + convertValueNoPattern(converter, "non-integer", "5.5", defaultValue); + } }