From 88d00471dc69d83723f3533657ef02d377b6f7a6 Mon Sep 17 00:00:00 2001 From: aizu-m Date: Sat, 13 Jun 2026 13:07:42 +0530 Subject: [PATCH] reject non-xsd lexical forms in lexFloat and lexDouble --- .../xmlbeans/impl/util/XsTypeConverter.java | 46 ++++++++++++++----- .../misc/checkin/XsTypeConverterTest.java | 31 +++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java index 8124f320b..f361693fd 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java @@ -40,6 +40,38 @@ public final class XsTypeConverter { private static final String[] URI_CHARS_TO_BE_REPLACED = {" ", "{", "}", "|", "\\", "^", "[", "]", "`"}; private static final String[] URI_CHARS_REPLACED_WITH = {"%20", "%7b", "%7d", "%7c", "%5c", "%5e", "%5b", "%5d", "%60"}; + // Float.parseFloat / Double.parseDouble accept lexical forms that are not + // in the XSD float/double value space: hexadecimal floats (0x1p4), the Java + // "Infinity" token, and a trailing type suffix (f/F/d/D). XSD only allows a + // decimal number with an optional exponent, or the special values INF, -INF + // and NaN. Reject the Java-only forms so they surface as invalid rather than + // being silently parsed. + private static void checkFloatingPointLexical(CharSequence cs) { + final int len = cs.length(); + for (int i = 0; i < len; i++) { + switch (cs.charAt(i)) { + case 'x': + case 'X': + case 'p': + case 'P': + case 'i': + case 't': + case 'y': + throw new NumberFormatException("invalid char '" + cs.charAt(i) + "' in floating point value"); + default: + break; + } + } + if (len > 0) { + final char last = cs.charAt(len - 1); + // a trailing 'F' is only valid as the last char of "INF" + if (last == 'd' || last == 'D' || + ((last == 'f' || last == 'F') && (len < 2 || cs.charAt(len - 2) != 'N'))) { + throw new NumberFormatException("invalid trailing char '" + last + "' in floating point value"); + } + } + } + // ======================== float ======================== public static float lexFloat(CharSequence cs) throws NumberFormatException { @@ -48,12 +80,7 @@ public static float lexFloat(CharSequence cs) //current jdk impl of parseFloat calls trim() on the string. //Any other space is illegal anyway, whether there are one or more spaces. //so no need to do a collapse pass through the string. - if (cs.length() > 1) { - char ch = cs.charAt(cs.length() - 1); - if ((ch == 'f' || ch == 'F') && cs.charAt(cs.length() - 2) != 'N') { - throw new NumberFormatException("Invalid char '" + ch + "' in float."); - } - } + checkFloatingPointLexical(cs); return Float.parseFloat(v); } catch (NumberFormatException e) { if (v.equals(POS_INF_LEX)) { @@ -103,12 +130,7 @@ public static double lexDouble(CharSequence cs) //current jdk impl of parseDouble calls trim() on the string. //Any other space is illegal anyway, whether there are one or more spaces. //so no need to do a collapse pass through the string. - if (cs.length() > 0) { - char ch = cs.charAt(cs.length() - 1); - if (ch == 'd' || ch == 'D') { - throw new NumberFormatException("Invalid char '" + ch + "' in double."); - } - } + checkFloatingPointLexical(cs); return Double.parseDouble(v); } catch (NumberFormatException e) { if (v.equals(POS_INF_LEX)) { diff --git a/src/test/java/misc/checkin/XsTypeConverterTest.java b/src/test/java/misc/checkin/XsTypeConverterTest.java index cf22bcfeb..d5567a531 100644 --- a/src/test/java/misc/checkin/XsTypeConverterTest.java +++ b/src/test/java/misc/checkin/XsTypeConverterTest.java @@ -75,6 +75,37 @@ void lexFloatAcceptsValidValues() { assertEquals(1.0f, XsTypeConverter.lexFloat("1.0")); assertEquals(Float.POSITIVE_INFINITY, XsTypeConverter.lexFloat("INF")); assertEquals(Float.NEGATIVE_INFINITY, XsTypeConverter.lexFloat("-INF")); + assertEquals(1500.0f, XsTypeConverter.lexFloat("1.5e3")); + } + + @Test + void lexFloatRejectsNonXsdLexicalForms() { + // hex floats, the java "Infinity" spelling and the double suffix are + // accepted by Float.parseFloat but are outside the xsd:float lexical space + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexFloat("0x1p4")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexFloat("Infinity")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexFloat("-Infinity")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexFloat("1.0d")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexFloat("1D")); + } + + @Test + void lexDoubleAcceptsValidValues() { + assertEquals(1.0, XsTypeConverter.lexDouble("1.0")); + assertEquals(Double.POSITIVE_INFINITY, XsTypeConverter.lexDouble("INF")); + assertEquals(Double.NEGATIVE_INFINITY, XsTypeConverter.lexDouble("-INF")); + assertEquals(1500.0, XsTypeConverter.lexDouble("1.5e3")); + } + + @Test + void lexDoubleRejectsNonXsdLexicalForms() { + // hex floats, the java "Infinity" spelling and the float suffix are + // accepted by Double.parseDouble but are outside the xsd:double lexical space + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDouble("0x1p4")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDouble("Infinity")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDouble("-Infinity")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDouble("1.0f")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDouble("1F")); } @Test