From ad50467c4cfdf7dc1cfd17b6ed68d2f0d9999083 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 25 Aug 2026 14:12:59 +0100 Subject: [PATCH] apply the configured number-length limit on the parse path XmlOptions.setMaxNumberOfCharsForNumbers only reached MathUtil from Validator, ie only when XmlObject.validate(options) was called explicitly. The path that runs on every parse - lazy value materialization through XmlObjectBase.check_dated -> update_from_wscanon_text -> set_text - always used DEFAULT_MAX_NUMBER_CHARS, because no XmlOptions is reachable from a value holder. Raising or lowering the option had no effect there. Carry the limit on the Locale, the way _loadStrictFloatingPoint and _loadAllowDecimalExponent are already carried: a default method on XmlLocale, a field copied from the options in the Locale constructor, and a get_max_number_chars() helper on XmlObjectBase for the holders to use. Add the maxNumberOfChars overloads that were missing, so there is something to pass the value to: MathUtil.parseAsBigInteger, parseAsLong and toBigInteger, and XsTypeConverter.lexDecimal, lexInteger and lexLong. lexInt/lexShort/lexByte need none - they use the hand-rolled parseIntXsdNumber and are bounded by the target type. Wire it through the decimal, integer and long holders. Every new overload is additive and the existing signatures keep defaulting to DEFAULT_MAX_NUMBER_CHARS, so behaviour is unchanged unless the option is set. Validator needs no change: integer-derived types route through its BTC_DECIMAL case, which is already wired. Co-Authored-By: Claude Opus 5 (1M context) --- .../xmlbeans/impl/common/XmlLocale.java | 7 ++ .../apache/xmlbeans/impl/store/Locale.java | 8 ++ .../apache/xmlbeans/impl/util/MathUtil.java | 47 +++++++-- .../xmlbeans/impl/util/XsTypeConverter.java | 45 ++++++++- .../impl/values/JavaDecimalHolder.java | 7 +- .../impl/values/JavaDecimalHolderEx.java | 5 +- .../xmlbeans/impl/values/JavaIntHolder.java | 2 +- .../xmlbeans/impl/values/JavaIntHolderEx.java | 10 +- .../impl/values/JavaIntegerHolder.java | 11 ++- .../impl/values/JavaIntegerHolderEx.java | 13 ++- .../xmlbeans/impl/values/JavaLongHolder.java | 4 +- .../impl/values/JavaLongHolderEx.java | 10 +- .../xmlbeans/impl/values/XmlObjectBase.java | 12 ++- .../misc/checkin/MaxNumberOfCharsTest.java | 98 +++++++++++++++++++ .../xmlbeans/impl/util/TestMathUtil.java | 33 +++++++ 15 files changed, 284 insertions(+), 28 deletions(-) create mode 100644 src/test/java/misc/checkin/MaxNumberOfCharsTest.java diff --git a/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java b/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java index 93bd75cea..112c14ec1 100755 --- a/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java +++ b/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java @@ -17,6 +17,8 @@ package org.apache.xmlbeans.impl.common; +import org.apache.xmlbeans.XmlOptions; + public interface XmlLocale { boolean sync ( ); @@ -34,4 +36,9 @@ public interface XmlLocale // (e.g. "1E5"), which is outside the xsd:decimal lexical space. Defaults to // false (reject). Driven by XmlOptions.setLoadAllowDecimalExponent. default boolean isLoadAllowDecimalExponent ( ) { return false; } + + // the maximum number of characters a lexical number may have before it is + // rejected, applied when values are materialised from the store. Driven by + // XmlOptions.setMaxNumberOfCharsForNumbers. + default int getMaxNumberOfCharsForNumbers ( ) { return XmlOptions.DEFAULT_MAX_NUMBER_CHARS; } } diff --git a/src/main/java/org/apache/xmlbeans/impl/store/Locale.java b/src/main/java/org/apache/xmlbeans/impl/store/Locale.java index f27ea53ef..ddaef58e3 100755 --- a/src/main/java/org/apache/xmlbeans/impl/store/Locale.java +++ b/src/main/java/org/apache/xmlbeans/impl/store/Locale.java @@ -107,6 +107,8 @@ private Locale(SchemaTypeLoader stl, XmlOptions options) { _loadAllowDecimalExponent = options.isLoadAllowDecimalExponent(); + _maxNumberOfChars = options.getMaxNumberOfCharsForNumbers(); + // // Check for Saaj implementation request // @@ -2083,6 +2085,10 @@ public boolean isLoadAllowDecimalExponent() { return _loadAllowDecimalExponent; } + public int getMaxNumberOfCharsForNumbers() { + return _maxNumberOfChars; + } + static boolean isWhiteSpace(String s) { int l = s.length(); @@ -2805,6 +2811,8 @@ public QName getQName(char[] uriSrc, int uriPos, int uriCch, boolean _loadAllowDecimalExponent; + int _maxNumberOfChars; + int _posTemp; nthCache _nthCache_A = new nthCache(); 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 feb50d355..e9e51d91e 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java @@ -91,11 +91,23 @@ public static BigDecimal parseAsBigDecimal(String s, int maxNumberOfChars) { * @throws NullPointerException if string is null */ public static BigInteger parseAsBigInteger(String s) { + return parseAsBigInteger(s, DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param s string to parse + * @param maxNumberOfChars maximum number of characters allowed in the string + * @return valid BigInteger + * @throws NumberFormatException if parse fails + * @throws IllegalArgumentException if string is too long + * @throws NullPointerException if string is null + */ + public static BigInteger parseAsBigInteger(String s, int maxNumberOfChars) { if (s == null) { throw new NullPointerException("Cannot parse null as BigInteger"); } - if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { - throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); + if (s.length() > maxNumberOfChars) { + throw new IllegalArgumentException("Number has more than " + maxNumberOfChars + " characters"); } return new BigInteger(s); } @@ -166,11 +178,23 @@ public static double parseAsDouble(String s, int maxNumberOfChars) { * @throws NullPointerException if string is null */ public static long parseAsLong(String s) { + return parseAsLong(s, DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param s string to parse + * @param maxNumberOfChars maximum number of characters allowed in the string + * @return valid long + * @throws NumberFormatException if parse fails + * @throws IllegalArgumentException if string is too long + * @throws NullPointerException if string is null + */ + public static long parseAsLong(String s, int maxNumberOfChars) { if (s == null) { throw new NullPointerException("Cannot parse null as Long"); } - if (s.length() > DEFAULT_MAX_NUMBER_CHARS) { - throw new IllegalArgumentException("Number has more than " + DEFAULT_MAX_NUMBER_CHARS + " characters"); + if (s.length() > maxNumberOfChars) { + throw new IllegalArgumentException("Number has more than " + maxNumberOfChars + " characters"); } return Long.parseLong(s); } @@ -199,6 +223,17 @@ public static int parseAsInt(String s) { * @throws NullPointerException if value is null */ public static BigInteger toBigInteger(BigDecimal value) { + return toBigInteger(value, DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param value BigDecimal to convert + * @param maxNumberOfChars maximum number of integer digits allowed in the value + * @return valid BigInteger + * @throws IllegalArgumentException if the input has an absolute exponent that is too large to safely convert + * @throws NullPointerException if value is null + */ + public static BigInteger toBigInteger(BigDecimal value, int maxNumberOfChars) { if (value == null) { throw new NullPointerException("Cannot convert null to BigInteger"); } @@ -206,10 +241,10 @@ public static BigInteger toBigInteger(BigDecimal value) { int integerDigits = normalized.precision() - normalized.scale(); // the scale check is not redundant: for a very negative scale (eg 1E+2147483647) the // subtraction above overflows and integerDigits comes out negative - if (integerDigits > DEFAULT_MAX_NUMBER_CHARS || normalized.scale() < -DEFAULT_MAX_NUMBER_CHARS) { + if (integerDigits > maxNumberOfChars || normalized.scale() < -maxNumberOfChars) { throw new IllegalArgumentException( "BigDecimal magnitude too large to convert safely: approx " - + integerDigits + " integer digits (limit " + DEFAULT_MAX_NUMBER_CHARS + ")"); + + integerDigits + " integer digits (limit " + maxNumberOfChars + ")"); } if (integerDigits <= 0) { // abs(value) is less than 1, so it truncates to zero - avoid BigDecimal.toBigInteger() 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 f20978f8f..afcd131f3 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java @@ -239,6 +239,21 @@ public static BigDecimal lexDecimal(CharSequence cs) * @since 5.4.0 */ public static BigDecimal lexDecimal(CharSequence cs, boolean allowExponent) + throws NumberFormatException { + return lexDecimal(cs, allowExponent, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * Parses an xsd:decimal lexical value. + * + * @param cs the lexical value + * @param allowExponent see {@link #lexDecimal(CharSequence, boolean)} + * @param maxNumberOfChars maximum number of characters allowed in the lexical value + * @return the parsed decimal + * @throws NumberFormatException if the value is not a valid xsd:decimal + * @since 5.4.1 + */ + public static BigDecimal lexDecimal(CharSequence cs, boolean allowExponent, int maxNumberOfChars) throws NumberFormatException { rejectInvalidNumber(cs); if (!allowExponent) { @@ -252,7 +267,7 @@ public static BigDecimal lexDecimal(CharSequence cs, boolean allowExponent) //equals() method, but the xml value //space does not consider them significant. //See http://www.w3.org/2001/05/xmlschema-errata#e2-44 - return MathUtil.parseAsBigDecimal(trimTrailingZeros(v)); + return MathUtil.parseAsBigDecimal(trimTrailingZeros(v), maxNumberOfChars); } private static final char[] CH_ZEROS = new char[]{'0', '0', '0', '0', '0', '0', '0', '0', @@ -306,13 +321,25 @@ public static String printDecimal(BigDecimal value) { // ======================== integer ======================== public static BigInteger lexInteger(CharSequence cs) + throws NumberFormatException { + return lexInteger(cs, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param cs the lexical value + * @param maxNumberOfChars maximum number of characters allowed in the lexical value + * @return the parsed integer + * @throws NumberFormatException if the value is not a valid xsd:integer + * @since 5.4.1 + */ + public static BigInteger lexInteger(CharSequence cs, int maxNumberOfChars) throws NumberFormatException { rejectSignAfterPlus(cs); final String v = cs.toString(); //TODO: consider special casing zero and one to return static values //from BigInteger to avoid object creation. - return MathUtil.parseAsBigInteger(trimInitialPlus(v)); + return MathUtil.parseAsBigInteger(trimInitialPlus(v), maxNumberOfChars); } public static BigInteger lexInteger(CharSequence cs, Collection errors) { @@ -331,11 +358,23 @@ public static String printInteger(BigInteger value) { // ======================== long ======================== public static long lexLong(CharSequence cs) + throws NumberFormatException { + return lexLong(cs, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + /** + * @param cs the lexical value + * @param maxNumberOfChars maximum number of characters allowed in the lexical value + * @return the parsed long + * @throws NumberFormatException if the value is not a valid xsd:long + * @since 5.4.1 + */ + public static long lexLong(CharSequence cs, int maxNumberOfChars) throws NumberFormatException { rejectInvalidNumber(cs); rejectSignAfterPlus(cs); final String v = cs.toString(); - return MathUtil.parseAsLong(trimInitialPlus(v)); + return MathUtil.parseAsLong(trimInitialPlus(v), maxNumberOfChars); } // trimInitialPlus drops a single leading '+', then Long.parseLong / 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 022dddb7f..c2bdfbc25 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java @@ -44,12 +44,13 @@ protected String compute_text(NamespaceManager nsm) { protected void set_text(String s) { boolean allowExponent = has_store() && get_store().get_locale().isLoadAllowDecimalExponent(); + int maxNumberOfChars = get_max_number_chars(); if (_validateOnSet()) { - validateLexical(s, _voorVc, allowExponent); + validateLexical(s, _voorVc, allowExponent, maxNumberOfChars); } try { - set_BigDecimal(MathUtil.parseAsBigDecimal(s)); + set_BigDecimal(MathUtil.parseAsBigDecimal(s, maxNumberOfChars)); } catch (Exception e) { _voorVc.invalid(XmlErrorCodes.DECIMAL, new Object[]{s}); } @@ -162,7 +163,7 @@ protected int value_hash_code() { } } - BigInteger intval = MathUtil.toBigInteger(_value); + BigInteger intval = MathUtil.toBigInteger(_value, get_max_number_chars()); if (intval.compareTo(_maxlong) > 0 || intval.compareTo(_minlong) < 0) { 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 d04dffbf8..70c8d38ac 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java @@ -39,14 +39,15 @@ public JavaDecimalHolderEx(SchemaType type, boolean complex) { } protected void set_text(String s) { + int maxNumberOfChars = get_max_number_chars(); if (_validateOnSet()) { boolean allowExponent = has_store() && get_store().get_locale().isLoadAllowDecimalExponent(); - validateLexical(s, _schemaType, _voorVc, allowExponent); + validateLexical(s, _schemaType, _voorVc, allowExponent, maxNumberOfChars); } BigDecimal v = null; try { - v = MathUtil.parseAsBigDecimal(s); + v = MathUtil.parseAsBigDecimal(s, maxNumberOfChars); } catch (Exception e) { _voorVc.invalid(XmlErrorCodes.DECIMAL, new Object[]{s}); } diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolder.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolder.java index 48d533474..72d765ef2 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolder.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolder.java @@ -78,7 +78,7 @@ public int getIntValue() { // setters protected void set_BigDecimal(BigDecimal v) { - set_BigInteger(MathUtil.toBigInteger(v)); + set_BigInteger(MathUtil.toBigInteger(v, get_max_number_chars())); } protected void set_BigInteger(BigInteger v) { diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolderEx.java index dc0e8a4b8..acae15788 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntHolderEx.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; @@ -47,7 +48,7 @@ protected void set_text(String s) { if (_validateOnSet()) { validateValue(v, _schemaType, _voorVc); - validateLexical(s, _schemaType, _voorVc); + validateLexical(s, _schemaType, _voorVc, get_max_number_chars()); } super.set_int(v); @@ -62,7 +63,12 @@ protected void set_int(int v) { } public static void validateLexical(String v, SchemaType sType, ValidationContext context) { - JavaDecimalHolder.validateLexical(v, context); + validateLexical(v, sType, context, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static void validateLexical(String v, SchemaType sType, ValidationContext context, + int maxNumberOfChars) { + JavaDecimalHolder.validateLexical(v, context, false, maxNumberOfChars); // check pattern if (sType.hasPatternFacet()) { diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolder.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolder.java index ffc2bc0be..3c4c8f8ca 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolder.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolder.java @@ -19,6 +19,7 @@ import org.apache.xmlbeans.SimpleValue; 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; @@ -41,16 +42,20 @@ protected String compute_text(NamespaceManager nsm) { } protected void set_text(String s) { - set_BigInteger(lex(s, _voorVc)); + set_BigInteger(lex(s, _voorVc, get_max_number_chars())); } public static BigInteger lex(String s, ValidationContext vc) { + return lex(s, vc, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static BigInteger lex(String s, ValidationContext vc, int maxNumberOfChars) { if (!s.isEmpty() && s.charAt(0) == '+') { s = s.substring(1); } try { - return MathUtil.parseAsBigInteger(s); + return MathUtil.parseAsBigInteger(s, maxNumberOfChars); } catch (Exception e) { vc.invalid(XmlErrorCodes.INTEGER, new Object[]{s}); return null; @@ -74,7 +79,7 @@ public BigInteger getBigIntegerValue() { // setters protected void set_BigDecimal(BigDecimal v) { - _value = MathUtil.toBigInteger(v); + _value = MathUtil.toBigInteger(v, get_max_number_chars()); } protected void set_BigInteger(BigInteger v) { diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolderEx.java index 53ce72999..2f5477416 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaIntegerHolderEx.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.XmlPositiveInteger; import org.apache.xmlbeans.impl.common.QNameHelper; import org.apache.xmlbeans.impl.common.ValidationContext; @@ -39,14 +40,15 @@ public SchemaType schemaType() { } protected void set_text(String s) { - BigInteger v = lex(s, _voorVc); + int maxNumberOfChars = get_max_number_chars(); + BigInteger v = lex(s, _voorVc, maxNumberOfChars); if (_validateOnSet()) { validateValue(v, _schemaType, _voorVc); } if (_validateOnSet()) { - validateLexical(s, _schemaType, _voorVc); + validateLexical(s, _schemaType, _voorVc, maxNumberOfChars); } super.set_BigInteger(v); @@ -61,7 +63,12 @@ protected void set_BigInteger(BigInteger v) { } public static void validateLexical(String v, SchemaType sType, ValidationContext context) { - JavaDecimalHolder.validateLexical(v, context); + validateLexical(v, sType, context, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static void validateLexical(String v, SchemaType sType, ValidationContext context, + int maxNumberOfChars) { + JavaDecimalHolder.validateLexical(v, context, false, maxNumberOfChars); if (v.lastIndexOf('.') >= 0) { context.invalid(XmlErrorCodes.INTEGER, new Object[]{v}); diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolder.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolder.java index d432a0b20..bc21efe96 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolder.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolder.java @@ -42,7 +42,7 @@ protected String compute_text(NamespaceManager nsm) { protected void set_text(String s) { try { - set_long(XsTypeConverter.lexLong(s)); + set_long(XsTypeConverter.lexLong(s, get_max_number_chars())); } catch (Exception e) { throw new XmlValueOutOfRangeException(XmlErrorCodes.LONG, new Object[]{s}); } @@ -73,7 +73,7 @@ public long getLongValue() { // setters protected void set_BigDecimal(BigDecimal v) { - set_BigInteger(MathUtil.toBigInteger(v)); + set_BigInteger(MathUtil.toBigInteger(v, get_max_number_chars())); } protected void set_BigInteger(BigInteger v) { diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolderEx.java index d88f1c644..737989e77 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaLongHolderEx.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; @@ -47,7 +48,7 @@ protected void set_text(String s) { if (_validateOnSet()) { validateValue(v, _schemaType, _voorVc); - validateLexical(s, _schemaType, _voorVc); + validateLexical(s, _schemaType, _voorVc, get_max_number_chars()); } super.set_long(v); @@ -62,7 +63,12 @@ protected void set_long(long v) { } public static void validateLexical(String v, SchemaType sType, ValidationContext context) { - JavaDecimalHolder.validateLexical(v, context); + validateLexical(v, sType, context, XmlOptions.DEFAULT_MAX_NUMBER_CHARS); + } + + public static void validateLexical(String v, SchemaType sType, ValidationContext context, + int maxNumberOfChars) { + JavaDecimalHolder.validateLexical(v, context, false, maxNumberOfChars); // check pattern if (sType.hasPatternFacet()) { diff --git a/src/main/java/org/apache/xmlbeans/impl/values/XmlObjectBase.java b/src/main/java/org/apache/xmlbeans/impl/values/XmlObjectBase.java index 8623ff50f..daf812a49 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/XmlObjectBase.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/XmlObjectBase.java @@ -815,6 +815,16 @@ protected final boolean has_store() { return (_flags & FLAG_STORE) != 0; } + /** + * The maximum number of characters a lexical number may have, as configured by + * XmlOptions.setMaxNumberOfCharsForNumbers on the options the document was loaded with. + */ + protected final int get_max_number_chars() { + return has_store() + ? get_store().get_locale().getMaxNumberOfCharsForNumbers() + : XmlOptions.DEFAULT_MAX_NUMBER_CHARS; + } + /** * Called by a TypeStore to pull out the most reasonable * text value from us. This is done after we have invalidated @@ -1347,7 +1357,7 @@ public BigDecimal getBigDecimalValue() { // numerics: integral public BigInteger getBigIntegerValue() { BigDecimal bd = getBigDecimalValue(); - return bd == null ? null : MathUtil.toBigInteger(bd); + return bd == null ? null : MathUtil.toBigInteger(bd, get_max_number_chars()); } public byte getByteValue() { diff --git a/src/test/java/misc/checkin/MaxNumberOfCharsTest.java b/src/test/java/misc/checkin/MaxNumberOfCharsTest.java new file mode 100644 index 000000000..ad7f32311 --- /dev/null +++ b/src/test/java/misc/checkin/MaxNumberOfCharsTest.java @@ -0,0 +1,98 @@ +/* Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package misc.checkin; + +import org.apache.xmlbeans.SimpleValue; +import org.apache.xmlbeans.XmlDecimal; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlInteger; +import org.apache.xmlbeans.XmlOptions; +import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * XmlOptions.setMaxNumberOfCharsForNumbers has to apply to values materialised from the + * store, not just to the explicit XmlObject.validate(options) path. + */ +public class MaxNumberOfCharsTest { + /** a number of exactly {@code n} characters */ + private static String digits(int n) { + StringBuilder sb = new StringBuilder(n); + sb.append('1'); + for (int i = 1; i < n; i++) { + sb.append('0'); + } + return sb.toString(); + } + + private static String frag(String value) { + return "" + value + ""; + } + + private static XmlOptions maxChars(int max) { + XmlOptions options = new XmlOptions(); + options.setMaxNumberOfCharsForNumbers(max); + return options; + } + + @Test + public void testDecimalHonoursRaisedLimit() throws XmlException { + XmlDecimal value = XmlDecimal.Factory.parse(frag(digits(2000)), maxChars(4096)); + assertEquals(2000, value.getBigDecimalValue().precision()); + } + + @Test + public void testDecimalHonoursLoweredLimit() throws XmlException { + XmlDecimal value = XmlDecimal.Factory.parse(frag("123456789012"), maxChars(8)); + assertThrows(XmlValueOutOfRangeException.class, value::getBigDecimalValue); + } + + @Test + public void testDecimalUsesDefaultLimitWhenUnset() throws XmlException { + XmlDecimal withinDefault = XmlDecimal.Factory.parse(frag(digits(1000))); + assertEquals(1000, withinDefault.getBigDecimalValue().precision()); + + XmlDecimal overDefault = XmlDecimal.Factory.parse(frag(digits(2000))); + assertThrows(XmlValueOutOfRangeException.class, overDefault::getBigDecimalValue); + } + + @Test + public void testIntegerHonoursRaisedLimit() throws XmlException { + XmlInteger value = XmlInteger.Factory.parse(frag(digits(2000)), maxChars(4096)); + assertEquals(new java.math.BigInteger(digits(2000)), value.getBigIntegerValue()); + } + + @Test + public void testIntegerUsesDefaultLimitWhenUnset() throws XmlException { + XmlInteger overDefault = XmlInteger.Factory.parse(frag(digits(2000))); + assertThrows(XmlValueOutOfRangeException.class, overDefault::getBigIntegerValue); + } + + @Test + public void testDecimalToBigIntegerHonoursRaisedLimit() throws XmlException { + // getBigIntegerValue() over a decimal goes through MathUtil.toBigInteger + SimpleValue value = (SimpleValue) XmlDecimal.Factory.parse(frag(digits(2000)), maxChars(4096)); + assertEquals(new java.math.BigInteger(digits(2000)), value.getBigIntegerValue()); + } + + @Test + public void testDecimalToBigIntegerUsesDefaultLimitWhenUnset() throws XmlException { + SimpleValue value = (SimpleValue) XmlDecimal.Factory.parse(frag(digits(2000))); + assertThrows(XmlValueOutOfRangeException.class, value::getBigIntegerValue); + } +} diff --git a/src/test/java/org/apache/xmlbeans/impl/util/TestMathUtil.java b/src/test/java/org/apache/xmlbeans/impl/util/TestMathUtil.java index 5e304b115..d70666e3a 100644 --- a/src/test/java/org/apache/xmlbeans/impl/util/TestMathUtil.java +++ b/src/test/java/org/apache/xmlbeans/impl/util/TestMathUtil.java @@ -18,6 +18,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.junit.jupiter.api.Test; +import org.apache.xmlbeans.XmlOptions; + import java.math.BigDecimal; import java.math.BigInteger; import java.time.Duration; @@ -55,6 +57,37 @@ public void testToLongWithValueOutOfRange() { assertThrows(IllegalArgumentException.class, () -> MathUtil.toLong(expected2)); } + @Test + public void testParseAsBigIntegerWithMaxNumberOfChars() { + assertEquals(new BigInteger("12345"), MathUtil.parseAsBigInteger("12345", 5)); + assertThrows(IllegalArgumentException.class, () -> MathUtil.parseAsBigInteger("12345", 4)); + // the default overload still applies DEFAULT_MAX_NUMBER_CHARS + String tooLong = repeat('1', XmlOptions.DEFAULT_MAX_NUMBER_CHARS + 1); + assertThrows(IllegalArgumentException.class, () -> MathUtil.parseAsBigInteger(tooLong)); + assertEquals(new BigInteger(tooLong), MathUtil.parseAsBigInteger(tooLong, tooLong.length())); + } + + @Test + public void testParseAsLongWithMaxNumberOfChars() { + assertEquals(12345L, MathUtil.parseAsLong("12345", 5)); + assertThrows(IllegalArgumentException.class, () -> MathUtil.parseAsLong("12345", 4)); + } + + @Test + public void testToBigIntegerWithMaxNumberOfChars() { + BigDecimal value = new BigDecimal("1E+2000"); + assertThrows(IllegalArgumentException.class, () -> MathUtil.toBigInteger(value)); + assertEquals(new BigDecimal("1E+2000").toBigInteger(), MathUtil.toBigInteger(value, 4096)); + } + + private static String repeat(char c, int n) { + StringBuilder sb = new StringBuilder(n); + for (int i = 0; i < n; i++) { + sb.append(c); + } + return sb.toString(); + } + @Test public void testToBigIntegerNull() { assertThrows(NullPointerException.class, () -> MathUtil.toBigInteger(null));