diff --git a/src/main/java/org/apache/xmlbeans/XmlOptions.java b/src/main/java/org/apache/xmlbeans/XmlOptions.java index cd557439d..05e4a2bf9 100644 --- a/src/main/java/org/apache/xmlbeans/XmlOptions.java +++ b/src/main/java/org/apache/xmlbeans/XmlOptions.java @@ -159,11 +159,13 @@ public enum XmlOptionsKeys { XPATH_USE_XMLBEANS, ATTRIBUTE_VALIDATION_COMPAT_MODE, LOAD_STRICT_FLOATING_POINT, - LOAD_ALLOW_DECIMAL_EXPONENT + LOAD_ALLOW_DECIMAL_EXPONENT, + MAX_NUMBER_CHARS } public static final int DEFAULT_ENTITY_EXPANSION_LIMIT = 2048; + public static final int DEFAULT_MAX_NUMBER_CHARS = 1024; private static final XmlOptions EMPTY_OPTIONS; @@ -1601,6 +1603,39 @@ public void setCompilePartialMethod(Set list) { } } + /** + * @return the maximum number of characters allowed for a number + * @since 5.4.0 + */ + public int getMaxNumberOfCharsForNumbers() { + Object value = get(XmlOptionsKeys.MAX_NUMBER_CHARS); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return DEFAULT_MAX_NUMBER_CHARS; + } + + /** + * Sets the maximum number of characters allowed for a number + * @param max the maximum number of characters + * @since 5.4.0 + */ + public void setMaxNumberOfCharsForNumbers(int max) { + set(XmlOptionsKeys.MAX_NUMBER_CHARS, max); + } + + /** + * Sets the maximum number of characters allowed for a number + * @param max the maximum number of characters, null means apply the default of 1024 + * @since 5.4.0 + */ + public void setMaxNumberOfCharsForNumbers(Integer max) { + if (max == null) { + remove(XmlOptionsKeys.MAX_NUMBER_CHARS); + } else { + set(XmlOptionsKeys.MAX_NUMBER_CHARS, max); + } + } /** * If passed null, returns an empty options object. Otherwise, returns its argument. diff --git a/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java b/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java index 66caaa53f..f0ee0050e 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java @@ -19,6 +19,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.math.BigDecimal; import java.math.BigInteger; +import static org.apache.xmlbeans.XmlOptions.DEFAULT_MAX_NUMBER_CHARS; + /** * Internal Use Only. Utility methods for dealing with conversions */ @@ -51,9 +53,6 @@ public static int safeDoubleToInt(double d) { return (int) d; } - // TODO try to make this configurable - private static final int MAX_NUMBER_LENGTH = 1024; - /** * @param s string to parse * @return valid BigDecimal @@ -62,11 +61,23 @@ public static int safeDoubleToInt(double d) { * @throws NullPointerException if string is null */ public static BigDecimal parseAsBigDecimal(String s) { + return parseAsBigDecimal(s, DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param s string to parse + * @param maxNumberOfChars maximum number of characters allowed in the string + * @return valid BigDecimal + * @throws NumberFormatException if parse fails + * @throws IllegalArgumentException if string is too long + * @throws NullPointerException if string is null + */ + public static BigDecimal parseAsBigDecimal(String s, int maxNumberOfChars) { if (s == null) { throw new NullPointerException("Cannot parse null as BigDecimal"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > maxNumberOfChars) { + throw new IllegalArgumentException("Number has more than " + maxNumberOfChars + " characters"); } return new BigDecimal(s); } @@ -82,8 +93,8 @@ public static BigInteger parseAsBigInteger(String s) { if (s == null) { throw new NullPointerException("Cannot parse null as BigInteger"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { + throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); } return new BigInteger(s); } @@ -99,8 +110,8 @@ public static Float parseAsFloat(String s) { if (s == null) { throw new NullPointerException("Cannot parse null as Float"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { + throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); } return Float.parseFloat(s); } @@ -116,8 +127,8 @@ public static Double parseAsDouble(String s) { if (s == null) { throw new NullPointerException("Cannot parse null as Double"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { + throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); } return Double.parseDouble(s); } @@ -133,8 +144,8 @@ public static Long parseAsLong(String s) { if (s == null) { throw new NullPointerException("Cannot parse null as Long"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { + throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); } return Long.parseLong(s); } @@ -150,8 +161,8 @@ public static Integer parseAsInteger(String s) { if (s == null) { throw new NullPointerException("Cannot parse null as Integer"); } - if (s.length() > MAX_NUMBER_LENGTH) { - throw new IllegalArgumentException("Number has more than " + MAX_NUMBER_LENGTH + " characters"); + if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { + throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); } return Integer.parseInt(s); } @@ -165,10 +176,10 @@ public static Integer parseAsInteger(String s) { public static BigInteger toBigInteger(BigDecimal value) { BigDecimal normalized = value.stripTrailingZeros(); int integerDigits = normalized.precision() - normalized.scale(); - if (integerDigits > MAX_NUMBER_LENGTH || normalized.scale() < -MAX_NUMBER_LENGTH) { + if (integerDigits > DEFAULT_MAX_NUMBER_CHARS || normalized.scale() < -DEFAULT_MAX_NUMBER_CHARS) { throw new IllegalArgumentException( "BigDecimal magnitude too large to convert safely: approx " - + integerDigits + " integer digits (limit " + MAX_NUMBER_LENGTH + ")"); + + integerDigits + " integer digits (limit " + DEFAULT_MAX_NUMBER_CHARS + ")"); } return normalized.toBigInteger(); } diff --git a/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java b/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java index af956d3e8..9e11999ca 100644 --- a/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java +++ b/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java @@ -35,10 +35,10 @@ public final class Validator public Validator( SchemaType type, SchemaField field, SchemaTypeLoader globalLoader, XmlOptions options, Collection defaultErrorListener) { - options = XmlOptions.maskNull(options); - _errorListener = options.getErrorListener(); - _treatLaxAsSkip = options.isValidateTreatLaxAsSkip(); - _strict = options.isValidateStrict(); + _options = XmlOptions.maskNull(options); + _errorListener = _options.getErrorListener(); + _treatLaxAsSkip = _options.isValidateTreatLaxAsSkip(); + _strict = _options.isValidateStrict(); if (_errorListener == null) { _errorListener = defaultErrorListener; @@ -1099,7 +1099,8 @@ private void validateAtomicType( break; } case SchemaType.BTC_DECIMAL: { - JavaDecimalHolderEx.validateLexical(value, type, _vc); + JavaDecimalHolderEx.validateLexical(value, type, _vc, + false, _options.getMaxNumberOfCharsForNumbers()); // An additional rule states that if the type is xs:integer or derived from it, // then the decimal dot is not allowed. @@ -1109,7 +1110,7 @@ private void validateAtomicType( } if (errorState == _errorState) { - _decimalValue = MathUtil.parseAsBigDecimal(value); + _decimalValue = MathUtil.parseAsBigDecimal(value, _options.getMaxNumberOfCharsForNumbers()); JavaDecimalHolderEx.validateValue(_decimalValue, type, _vc); } @@ -1498,6 +1499,7 @@ private void addToList(SchemaType type) { // Members of the validator class // + private final XmlOptions _options; private boolean _invalid; private final SchemaType _rootType; private final SchemaField _rootField; diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java index 97acb3448..022dddb7f 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java @@ -18,6 +18,7 @@ import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.XmlErrorCodes; import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.impl.common.ValidationContext; import org.apache.xmlbeans.impl.schema.BuiltinSchemaTypeSystem; import org.apache.xmlbeans.impl.util.MathUtil; @@ -67,11 +68,16 @@ public static void validateLexical(String v, ValidationContext context) { } public static void validateLexical(String v, ValidationContext context, boolean allowExponent) { + validateLexical(v, context, allowExponent, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static void validateLexical(String v, ValidationContext context, boolean allowExponent, + int maxNumberOfChars) { if (allowExponent) { // long-standing lenient behaviour: accept whatever BigDecimal accepts, // which includes scientific/exponent notation such as "1E5". try { - MathUtil.parseAsBigDecimal(v); + MathUtil.parseAsBigDecimal(v, maxNumberOfChars); } catch (Exception e) { context.invalid(XmlErrorCodes.DECIMAL, new Object[]{v}); } diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java index 70c4b8d03..d04dffbf8 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java @@ -18,6 +18,7 @@ import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.XmlErrorCodes; import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.impl.common.QNameHelper; import org.apache.xmlbeans.impl.common.ValidationContext; import org.apache.xmlbeans.impl.util.MathUtil; @@ -69,7 +70,12 @@ public static void validateLexical(String v, SchemaType sType, ValidationContext } public static void validateLexical(String v, SchemaType sType, ValidationContext context, boolean allowExponent) { - JavaDecimalHolder.validateLexical(v, context, allowExponent); + validateLexical(v, sType, context, allowExponent, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static void validateLexical(String v, SchemaType sType, ValidationContext context, + boolean allowExponent, int maxNumberOfChars) { + JavaDecimalHolder.validateLexical(v, context, allowExponent, maxNumberOfChars); // check pattern if (sType.hasPatternFacet()) { diff --git a/src/test/java/misc/checkin/XmlOptionsTest.java b/src/test/java/misc/checkin/XmlOptionsTest.java index be7ec26a3..74995bba3 100644 --- a/src/test/java/misc/checkin/XmlOptionsTest.java +++ b/src/test/java/misc/checkin/XmlOptionsTest.java @@ -17,6 +17,7 @@ import org.apache.xmlbeans.XmlOptions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -60,4 +61,18 @@ void testSaveNoAttributeWhitespaceEscapeFlag() { xmlOptions.setSaveNoAttributeWhitespaceEscape(false); assertFalse(xmlOptions.isSaveNoAttributeWhitespaceEscape()); } + + @Test + void testMaxNumberOfCharsForNumbers() { + XmlOptions xmlOptions = new XmlOptions(); + assertEquals(XmlOptions.DEFAULT_MAX_NUMBER_CHARS, + xmlOptions.getMaxNumberOfCharsForNumbers()); + xmlOptions.setMaxNumberOfCharsForNumbers(2); + assertEquals(2, xmlOptions.getMaxNumberOfCharsForNumbers()); + xmlOptions.setMaxNumberOfCharsForNumbers(Integer.valueOf(3)); + assertEquals(3, xmlOptions.getMaxNumberOfCharsForNumbers()); + xmlOptions.setMaxNumberOfCharsForNumbers(null); + assertEquals(XmlOptions.DEFAULT_MAX_NUMBER_CHARS, xmlOptions.getMaxNumberOfCharsForNumbers()); + } + }