diff --git a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemCompiler.java b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemCompiler.java index 1566e1049..5d193c3d5 100644 --- a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemCompiler.java +++ b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemCompiler.java @@ -226,8 +226,11 @@ static SchemaTypeSystemImpl compileImpl(SchemaTypeSystem system, String name, // load all the xsd files into it if (validate) { XmlOptions validateOptions = new XmlOptions().setErrorListener(errorWatcher); - if (options != null && options.isValidateTreatLaxAsSkip()) { - validateOptions.setValidateTreatLaxAsSkip(); + if (options != null) { + if (options.isValidateTreatLaxAsSkip()) { + validateOptions.setValidateTreatLaxAsSkip(); + } + validateOptions.setMaxNumberOfCharsForNumbers(options.getMaxNumberOfCharsForNumbers()); } for (Schema schema : schemas) { if (schema.validate(validateOptions)) { diff --git a/src/main/java/org/apache/xmlbeans/impl/schema/StscState.java b/src/main/java/org/apache/xmlbeans/impl/schema/StscState.java index 0651646e1..0690ec8a4 100644 --- a/src/main/java/org/apache/xmlbeans/impl/schema/StscState.java +++ b/src/main/java/org/apache/xmlbeans/impl/schema/StscState.java @@ -106,6 +106,7 @@ public class StscState { private boolean _noAnn; private boolean _mdefAll; private String _sourceCodeEncoding ; + private int _maxNumberOfCharsForNumbers = XmlOptions.DEFAULT_MAX_NUMBER_CHARS; private final Set _mdefNamespaces = buildDefaultMdefNamespaces(); private EntityResolver _entityResolver; private File _schemasDir; @@ -462,6 +463,7 @@ public void setOptions(XmlOptions options) { !"true".equals(SystemProperties.getProperty("xmlbean.schemaannotations", "true")); _doingDownloads = options.isCompileDownloadUrls() || "true".equals(SystemProperties.getProperty("xmlbean.downloadurls", "false")); + _maxNumberOfCharsForNumbers = options.getMaxNumberOfCharsForNumbers(); _sourceCodeEncoding = options.getCharacterEncoding(); if (_sourceCodeEncoding == null || _sourceCodeEncoding.isEmpty()) { _sourceCodeEncoding = SystemProperties.getProperty("xmlbean.sourcecodeencoding"); @@ -538,6 +540,14 @@ public String sourceCodeEncoding() { return _sourceCodeEncoding ; } + /** + * The maximum number of characters a number in the schema being compiled may have, + * as configured by XmlOptions.setMaxNumberOfCharsForNumbers. + */ + public int maxNumberOfCharsForNumbers() { + return _maxNumberOfCharsForNumbers; + } + /** * Get count of recovered errors. Not for public. */ diff --git a/src/main/java/org/apache/xmlbeans/impl/schema/StscTranslator.java b/src/main/java/org/apache/xmlbeans/impl/schema/StscTranslator.java index a23795ecd..28764de29 100644 --- a/src/main/java/org/apache/xmlbeans/impl/schema/StscTranslator.java +++ b/src/main/java/org/apache/xmlbeans/impl/schema/StscTranslator.java @@ -1509,10 +1509,13 @@ static BigInteger buildBigInt(XmlAnySimpleType value) { if (value == null) { return null; } - String text = value.getStringValue(); + // materialising the facet's text can fail in its own right, if the schema was + // loaded with a lower limit than the number it declares, so it is inside the try + String text = null; BigInteger bigInt; try { - bigInt = MathUtil.parseAsBigInteger(text); + text = value.getStringValue(); + bigInt = MathUtil.parseAsBigInteger(text, StscState.get().maxNumberOfCharsForNumbers()); } catch (Exception e) { StscState.get().error(XmlErrorCodes.INVALID_VALUE_DETAIL, new Object[]{text, "nonNegativeInteger", e.getMessage()}, value); return null; 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 2a87e18f7..e69a4aab4 100644 --- a/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java +++ b/src/main/java/org/apache/xmlbeans/impl/validator/Validator.java @@ -1110,8 +1110,20 @@ private void validateAtomicType( } if (errorState == _errorState) { - _decimalValue = MathUtil.parseAsBigDecimal(value, _options.getMaxNumberOfCharsForNumbers()); - JavaDecimalHolderEx.validateValue(_decimalValue, type, _vc); + BigDecimal parsed = null; + try { + parsed = MathUtil.parseAsBigDecimal(value, _options.getMaxNumberOfCharsForNumbers()); + } catch (IllegalArgumentException e) { + // a number longer than the configured maximum is invalid input, + // not a programming error - report it like any other bad value + // rather than throwing out of validate() + _vc.invalid(derivedFromInteger(type) ? XmlErrorCodes.INTEGER : XmlErrorCodes.DECIMAL, + new Object[]{value}); + } + if (parsed != null) { + _decimalValue = parsed; + JavaDecimalHolderEx.validateValue(_decimalValue, type, _vc); + } } break; diff --git a/src/test/java/misc/checkin/MaxNumberOfCharsTest.java b/src/test/java/misc/checkin/MaxNumberOfCharsTest.java index c3c9c87de..f11aeffd3 100644 --- a/src/test/java/misc/checkin/MaxNumberOfCharsTest.java +++ b/src/test/java/misc/checkin/MaxNumberOfCharsTest.java @@ -14,22 +14,30 @@ */ package misc.checkin; +import org.apache.xmlbeans.SchemaTypeSystem; import org.apache.xmlbeans.SimpleValue; +import org.apache.xmlbeans.XmlBeans; import org.apache.xmlbeans.XmlDecimal; +import org.apache.xmlbeans.XmlError; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlInt; import org.apache.xmlbeans.XmlInteger; import org.apache.xmlbeans.XmlLong; +import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * XmlOptions.setMaxNumberOfCharsForNumbers has to apply to values materialised from the @@ -214,6 +222,46 @@ public void testDecimalStillAcceptsExponentWithinLimit() throws XmlException { assertEquals(2001, wide.getBigDecimalValue().precision() - wide.getBigDecimalValue().scale()); } + @Test + public void testValidateReportsAnOverLongNumber() throws XmlException { + // validate() reports what is wrong with a document; a number past the limit is + // bad input like any other and must not come back as an exception + List errors = new ArrayList<>(); + XmlInteger integer = XmlInteger.Factory.parse(frag(digits(2000))); + assertFalse(integer.validate(new XmlOptions().setErrorListener(errors))); + assertFalse(errors.isEmpty()); + + errors.clear(); + XmlDecimal decimal = XmlDecimal.Factory.parse(frag(digits(2000) + ".5")); + assertFalse(decimal.validate(new XmlOptions().setErrorListener(errors))); + assertFalse(errors.isEmpty()); + } + + @Test + public void testSchemaCompileHonoursLimit() throws XmlException { + // the facet value is a number in the schema document, so the limit has to reach + // the validation and the facet handling that compilation does + String xsd = "" + + "" + + "" + + "" + + ""; + + XmlOptions raised = maxChars(4096); + SchemaTypeSystem sts = XmlBeans.compileXsd( + new XmlObject[]{XmlObject.Factory.parse(xsd, raised)}, XmlBeans.getBuiltinTypeSystem(), raised); + assertEquals(1, sts.globalElements().length); + + // at the default limit the same schema is reported as invalid, not thrown out of + List errors = new ArrayList<>(); + XmlOptions dflt = new XmlOptions().setErrorListener(errors); + XmlObject parsed = XmlObject.Factory.parse(xsd); + assertThrows(XmlException.class, + () -> XmlBeans.compileXsd(new XmlObject[]{parsed}, XmlBeans.getBuiltinTypeSystem(), dflt)); + assertTrue(errors.stream().anyMatch(e -> e.getMessage().contains("Invalid integer value")), + errors.toString()); + } + @Test public void testDecimalHashCodeIgnoresLimit() { // hashCode() must not throw, whatever the limit is, and must stay aligned with