Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit maximum number of characters of BigDecimal source being parsed. #80

Merged
merged 3 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions impl/src/main/java/org/eclipse/parsson/JsonContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;

Expand All @@ -62,7 +68,8 @@ final class JsonContext {
* @param defaultPool default char[] pool to use when no instance is configured
*/
JsonContext(Map<String, ?> 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);
Expand All @@ -77,7 +84,8 @@ final class JsonContext {
* @param properties properties to store in local copy of provider specific properties {@code Map}
*/
JsonContext(Map<String, ?> 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);
Expand All @@ -97,6 +105,10 @@ int bigIntegerScaleLimit() {
return bigIntegerScaleLimit;
}

int bigDecimalLengthLimit() {
return bigDecimalLengthLimit;
}

boolean prettyPrinting() {
return prettyPrinting;
}
Expand Down
21 changes: 13 additions & 8 deletions impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 8 additions & 1 deletion impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
79 changes: 78 additions & 1 deletion impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -272,7 +297,7 @@ public void testDefaultBigIntegerScaleAboveLimit() {
public void testConfigBigIntegerScaleAboveLimit() {
BigDecimal value = new BigDecimal("3.1415926535897932384626433")
.setScale(50001, RoundingMode.HALF_UP);
Map<String, ?> config = Map.of("org.eclipse.parsson.maxBigIntegerScale", "50000");
Map<String, ?> config = Map.of(JsonConfig.MAX_BIGINTEGER_SCALE, "50000");
try {
JsonObject jsonObject = Json.createBuilderFactory(config)
.createObjectBuilder()
Expand All @@ -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<String, ?> 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<String, ?> 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;
Expand Down