diff --git a/impl/src/main/java/org/eclipse/parsson/JsonContext.java b/impl/src/main/java/org/eclipse/parsson/JsonContext.java index fd78ac6a..5ec77fbe 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonContext.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonContext.java @@ -35,7 +35,10 @@ final class JsonContext { /** Default maximum value of BigInteger scale value limit. */ - private static final int DEFAULT_MAX_BIGINT_SCALE = 100000; + private static final int DEFAULT_MAX_BIGINTEGER_SCALE = 100000; + + /** Default maximum number of characters of BigDecimal source being parsed. */ + private static final int DEFAULT_MAX_BIGDECIMAL_LEN = 1100; /** * Custom char[] pool instance property. Can be set in properties {@code Map} only. @@ -47,6 +50,9 @@ final class JsonContext { // Maximum value of BigInteger scale value private final int bigIntegerScaleLimit; + // Maximum number of characters of BigDecimal source + private final int bigDecimalLengthLimit; + // Whether JSON pretty printing is enabled private final boolean prettyPrinting; @@ -62,7 +68,8 @@ final class JsonContext { * @param defaultPool default char[] pool to use when no instance is configured */ JsonContext(Map config, BufferPool defaultPool) { - this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINT_SCALE, config, DEFAULT_MAX_BIGINT_SCALE); + this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINTEGER_SCALE, config, DEFAULT_MAX_BIGINTEGER_SCALE); + this.bigDecimalLengthLimit = getIntConfig(JsonConfig.MAX_BIGDECIMAL_LEN, config, DEFAULT_MAX_BIGDECIMAL_LEN); this.prettyPrinting = getBooleanConfig(JsonGenerator.PRETTY_PRINTING, config); this.rejectDuplicateKeys = getBooleanConfig(JsonConfig.REJECT_DUPLICATE_KEYS, config); this.bufferPool = getBufferPool(config, defaultPool); @@ -77,7 +84,8 @@ final class JsonContext { * @param properties properties to store in local copy of provider specific properties {@code Map} */ JsonContext(Map config, BufferPool defaultPool, String... properties) { - this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINT_SCALE, config, DEFAULT_MAX_BIGINT_SCALE); + this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINTEGER_SCALE, config, DEFAULT_MAX_BIGINTEGER_SCALE); + this.bigDecimalLengthLimit = getIntConfig(JsonConfig.MAX_BIGDECIMAL_LEN, config, DEFAULT_MAX_BIGDECIMAL_LEN); this.prettyPrinting = getBooleanConfig(JsonGenerator.PRETTY_PRINTING, config); this.rejectDuplicateKeys = getBooleanConfig(JsonConfig.REJECT_DUPLICATE_KEYS, config); this.bufferPool = getBufferPool(config, defaultPool); @@ -97,6 +105,10 @@ int bigIntegerScaleLimit() { return bigIntegerScaleLimit; } + int bigDecimalLengthLimit() { + return bigDecimalLengthLimit; + } + boolean prettyPrinting() { return prettyPrinting; } diff --git a/impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java b/impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java index 3113e846..9ffac77c 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java @@ -16,8 +16,6 @@ package org.eclipse.parsson; -import org.eclipse.parsson.api.BufferPool; - import jakarta.json.JsonException; import jakarta.json.stream.JsonLocation; import jakarta.json.stream.JsonParser; @@ -52,7 +50,7 @@ final class JsonTokenizer implements Closeable { } private final static int HEX_LENGTH = HEX.length; - private final BufferPool bufferPool; + private final JsonContext jsonContext; private final Reader reader; @@ -123,8 +121,8 @@ boolean isValue() { JsonTokenizer(Reader reader, JsonContext jsonContext) { this.reader = reader; - this.bufferPool = jsonContext.bufferPool(); - buf = bufferPool.take(); + this.jsonContext = jsonContext; + buf = jsonContext.bufferPool().take(); } private void readString() { @@ -477,7 +475,7 @@ private int fillBuf() throws IOException { if (storeLen == buf.length) { // buffer is full, double the capacity char[] doubleBuf = Arrays.copyOf(buf, 2 * buf.length); - bufferPool.recycle(buf); + jsonContext.bufferPool().recycle(buf); buf = doubleBuf; } else { // Left shift all the stored data to make space @@ -519,7 +517,14 @@ CharSequence getCharSequence() { BigDecimal getBigDecimal() { if (bd == null) { - bd = new BigDecimal(buf, storeBegin, storeEnd-storeBegin); + int sourceLen = storeEnd - storeBegin; + if (sourceLen > jsonContext.bigDecimalLengthLimit()) { + throw new UnsupportedOperationException( + String.format( + "Number of BigDecimal source characters %d exceeded maximal allowed value of %d", + sourceLen, jsonContext.bigDecimalLengthLimit())); + } + bd = new BigDecimal(buf, storeBegin, sourceLen); } return bd; } @@ -575,7 +580,7 @@ boolean isIntegral() { @Override public void close() throws IOException { reader.close(); - bufferPool.recycle(buf); + jsonContext.bufferPool().recycle(buf); } private JsonParsingException unexpectedChar(int ch) { diff --git a/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java b/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java index 6f6756c6..9b71959a 100644 --- a/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java +++ b/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java @@ -25,7 +25,14 @@ public interface JsonConfig { * and {@link jakarta.json.JsonNumber#bigIntegerValueExact()} implemented methods. * Default value is set to {@code 100000}. */ - String MAX_BIGINT_SCALE = "org.eclipse.parsson.maxBigIntegerScale"; + String MAX_BIGINTEGER_SCALE = "org.eclipse.parsson.maxBigIntegerScale"; + + /** + * Configuration property to limit maximum value of BigDecimal length when being parsed. + * This property limits maximum number of characters of BigDecimal source being parsed. + * Default value is set to {@code 1100}. + */ + String MAX_BIGDECIMAL_LEN = "org.eclipse.parsson.maxBigDecimalLength"; /** * Configuration property to reject duplicate keys. diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalLengthLimitTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalLengthLimitTest.java new file mode 100644 index 00000000..a4d78d65 --- /dev/null +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalLengthLimitTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.eclipse.parsson.tests; + +import java.io.StringReader; +import java.math.BigDecimal; + +import jakarta.json.Json; +import jakarta.json.JsonNumber; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; +import junit.framework.TestCase; +import org.eclipse.parsson.api.JsonConfig; + +/** + * Test maxBigDecimalLength limit set from System property. + */ +public class JsonBigDecimalLengthLimitTest extends TestCase { + + public JsonBigDecimalLengthLimitTest(String testName) { + super(testName); + } + + @Override + protected void setUp() { + System.setProperty(JsonConfig.MAX_BIGDECIMAL_LEN, "500"); + } + + @Override + protected void tearDown() { + System.clearProperty(JsonConfig.MAX_BIGDECIMAL_LEN); + } + + // Test BigDecimal max source characters array length using length equal to system property limit of 500. + // Parsing shall pass and return value equal to source String. + public void testLargeBigDecimalBellowLimit() { + JsonReader reader = Json.createReader(new StringReader(JsonNumberTest.Π_500)); + JsonNumber check = Json.createValue(new BigDecimal(JsonNumberTest.Π_500)); + JsonValue value = reader.readValue(); + assertEquals(value.getValueType(), JsonValue.ValueType.NUMBER); + assertEquals(value, check); + } + + // Test BigDecimal max source characters array length using length above system property limit of 500. + // Parsing shall pass and return value equal to source String. + public void testLargeBigDecimalAboveLimit() { + JsonReader reader = Json.createReader(new StringReader(JsonNumberTest.Π_501)); + try { + reader.readValue(); + fail("No exception was thrown from BigDecimal parsing with source characters array length over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + assertEquals( + "Number of BigDecimal source characters 501 exceeded maximal allowed value of 500", + e.getMessage()); + } + } + +} diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java index 8e7be594..ed59a5c4 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java @@ -21,6 +21,7 @@ import jakarta.json.Json; import junit.framework.TestCase; +import org.eclipse.parsson.api.JsonConfig; /** * Test maxBigIntegerScale limit set from System property. @@ -33,12 +34,12 @@ public JsonBigDecimalScaleLimitTest(String testName) { @Override protected void setUp() { - System.setProperty("org.eclipse.parsson.maxBigIntegerScale", "50000"); + System.setProperty(JsonConfig.MAX_BIGINTEGER_SCALE, "50000"); } @Override protected void tearDown() { - System.clearProperty("org.eclipse.parsson.maxBigIntegerScale"); + System.clearProperty(JsonConfig.MAX_BIGINTEGER_SCALE); } // Test BigInteger scale value limit set from system property using value bellow limit. diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java index c760487a..c47a0b61 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java @@ -31,11 +31,36 @@ import jakarta.json.JsonValue; import jakarta.json.JsonWriter; import junit.framework.TestCase; +import org.eclipse.parsson.api.JsonConfig; /** * @author Jitendra Kotamraju */ public class JsonNumberTest extends TestCase { + + // π as JsonNumber with 500 source characters + static final String Π_500 + = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706" + + "7982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381" + + "9644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412" + + "7372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160" + + "9433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949"; + + // π as JsonNumber with 501 source characters + static final String Π_501 = Π_500 + "1"; + + // π as JsonNumber with 1100 source characters + private static final String Π_1100 = Π_500 + + "1298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051" + + "3200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892" + + "3542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318" + + "5950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473" + + "0359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019" + + "8938095257201065485863278865936153381827968230301952035301852968995773622599413891249721775283479131"; + + // π as JsonNumber with 1101 source characters + private static final String Π_1101 = Π_1100 + "5"; + public JsonNumberTest(String testName) { super(testName); } @@ -272,7 +297,7 @@ public void testDefaultBigIntegerScaleAboveLimit() { public void testConfigBigIntegerScaleAboveLimit() { BigDecimal value = new BigDecimal("3.1415926535897932384626433") .setScale(50001, RoundingMode.HALF_UP); - Map config = Map.of("org.eclipse.parsson.maxBigIntegerScale", "50000"); + Map config = Map.of(JsonConfig.MAX_BIGINTEGER_SCALE, "50000"); try { JsonObject jsonObject = Json.createBuilderFactory(config) .createObjectBuilder() @@ -288,6 +313,58 @@ public void testConfigBigIntegerScaleAboveLimit() { } } + // Test BigDecimal max source characters array length using length equal to default limit of 1100. + // Parsing shall pass and return value equal to source String. + public void testLargeBigDecimalBellowLimit() { + JsonReader reader = Json.createReader(new StringReader(Π_1100)); + JsonNumber check = Json.createValue(new BigDecimal(Π_1100)); + JsonValue value = reader.readValue(); + assertEquals(value.getValueType(), JsonValue.ValueType.NUMBER); + assertEquals(value, check); + } + + // Test BigDecimal max source characters array length using length above default limit of 1100. + // Parsing shall throw specific UnsupportedOperationException exception. + public void testLargeBigDecimalAboveLimit() { + JsonReader reader = Json.createReader(new StringReader(Π_1101)); + try { + reader.readValue(); + fail("No exception was thrown from BigDecimal parsing with source characters array length over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + assertEquals( + "Number of BigDecimal source characters 1101 exceeded maximal allowed value of 1100", + e.getMessage()); + } + } + + // Test BigDecimal max source characters array length using length equal to custom limit of 500. + // Parsing shall pass and return value equal to source String. + public void testLargeBigDecimalBellowCustomLimit() { + Map config = Map.of(JsonConfig.MAX_BIGDECIMAL_LEN, "500"); + JsonReader reader = Json.createReaderFactory(config).createReader(new StringReader(Π_500)); + JsonNumber check = Json.createValue(new BigDecimal(Π_500)); + JsonValue value = reader.readValue(); + assertEquals(value.getValueType(), JsonValue.ValueType.NUMBER); + assertEquals(value, check); + } + + // Test BigDecimal max source characters array length using length equal to custom limit of 200. + // Parsing shall pass and return value equal to source String. + public void testLargeBigDecimalAboveCustomLimit() { + Map config = Map.of(JsonConfig.MAX_BIGDECIMAL_LEN, "500"); + JsonReader reader = Json.createReaderFactory(config).createReader(new StringReader(Π_501)); + try { + reader.readValue(); + fail("No exception was thrown from BigDecimal parsing with source characters array length over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + assertEquals( + "Number of BigDecimal source characters 501 exceeded maximal allowed value of 500", + e.getMessage()); + } + } + private static class CustomNumber extends Number { private static final long serialVersionUID = 1L;