Skip to content
Open
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
52 changes: 48 additions & 4 deletions src/main/java/org/json/JSONArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,29 @@ public BigDecimal getBigDecimal (int index) throws JSONException {
* to a BigInteger.
*/
public BigInteger getBigInteger (int index) throws JSONException {
return this.getBigInteger(index, new JSONParserConfiguration());
}

/**
* Get the BigInteger value associated with an index.
*
* @param index
* The index must be between 0 and length() - 1.
* @param jsonParserConfiguration
* A configuration whose {@code maxNumberLength} bounds the number of
* decimal digits in the returned integer. Values exceeding this length
* are treated as unconvertible. Pass a configuration with
* {@link ParserConfiguration#UNDEFINED_MAXIMUM_NUMBER_LENGTH} to disable
* this check.
* @return The value.
* @throws JSONException
* If the key is not found or if the value cannot be converted
* to a BigInteger.
*/
public BigInteger getBigInteger (int index, JSONParserConfiguration jsonParserConfiguration)
throws JSONException {
Object object = this.get(index);
BigInteger val = JSONObject.objectToBigInteger(object, null);
BigInteger val = JSONObject.objectToBigInteger(object, null, jsonParserConfiguration);
if(val == null) {
throw wrongValueFormatException(index, "BigInteger", object, null);
}
Expand Down Expand Up @@ -960,8 +981,8 @@ public <E extends Enum<E>> E optEnum(Class<E> clazz, int index, E defaultValue)
}

/**
* Get the optional BigInteger value associated with an index. The
* defaultValue is returned if there is no value for the index, or if the
* Get the optional BigInteger value associated with an index. The
* defaultValue is returned if there is no value for the index, or if the
* value is not a number and cannot be converted to a number.
*
* @param index
Expand All @@ -971,8 +992,31 @@ public <E extends Enum<E>> E optEnum(Class<E> clazz, int index, E defaultValue)
* @return The value.
*/
public BigInteger optBigInteger(int index, BigInteger defaultValue) {
return this.optBigInteger(index, defaultValue, new JSONParserConfiguration());
}

/**
* Get the optional BigInteger value associated with an index. The
* defaultValue is returned if there is no value for the index, or if the
* value is not a number and cannot be converted to a number.
*
* @param index
* The index must be between 0 and length() - 1.
* @param defaultValue
* The default value.
* @param jsonParserConfiguration
* A configuration whose {@code maxNumberLength} bounds the number of
* decimal digits in the returned integer. Values exceeding this length
* are treated as unconvertible and {@code defaultValue} is returned.
* Pass a configuration with
* {@link ParserConfiguration#UNDEFINED_MAXIMUM_NUMBER_LENGTH} to disable
* this check.
* @return The value.
*/
public BigInteger optBigInteger(int index, BigInteger defaultValue,
JSONParserConfiguration jsonParserConfiguration) {
Object val = this.opt(index);
return JSONObject.objectToBigInteger(val, defaultValue);
return JSONObject.objectToBigInteger(val, defaultValue, jsonParserConfiguration);
}

/**
Expand Down
130 changes: 122 additions & 8 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,29 @@ public boolean getBoolean(String key) throws JSONException {
* be converted to BigInteger.
*/
public BigInteger getBigInteger(String key) throws JSONException {
return this.getBigInteger(key, new JSONParserConfiguration());
}

/**
* Get the BigInteger value associated with a key.
*
* @param key
* A key string.
* @param jsonParserConfiguration
* A configuration whose {@code maxNumberLength} bounds the number of
* decimal digits in the returned integer. Values exceeding this length
* are treated as unconvertible. Pass a configuration with
* {@link ParserConfiguration#UNDEFINED_MAXIMUM_NUMBER_LENGTH} to disable
* this check.
* @return The numeric value.
* @throws JSONException
* if the key is not found or if the value cannot
* be converted to BigInteger.
*/
public BigInteger getBigInteger(String key, JSONParserConfiguration jsonParserConfiguration)
throws JSONException {
Object object = this.get(key);
BigInteger ret = objectToBigInteger(object, null);
BigInteger ret = objectToBigInteger(object, null, jsonParserConfiguration);
if (ret != null) {
return ret;
}
Expand Down Expand Up @@ -1381,8 +1402,31 @@ static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue, boolea
* @return An object which is the value.
*/
public BigInteger optBigInteger(String key, BigInteger defaultValue) {
return this.optBigInteger(key, defaultValue, new JSONParserConfiguration());
}

/**
* Get an optional BigInteger associated with a key, or the defaultValue if
* there is no such key or if its value is not a number. If the value is a
* string, an attempt will be made to evaluate it as a number.
*
* @param key
* A key string.
* @param defaultValue
* The default.
* @param jsonParserConfiguration
* A configuration whose {@code maxNumberLength} bounds the number of
* decimal digits in the returned integer. Values exceeding this length
* are treated as unconvertible and {@code defaultValue} is returned.
* Pass a configuration with
* {@link ParserConfiguration#UNDEFINED_MAXIMUM_NUMBER_LENGTH} to disable
* this check.
* @return An object which is the value.
*/
public BigInteger optBigInteger(String key, BigInteger defaultValue,
JSONParserConfiguration jsonParserConfiguration) {
Object val = this.opt(key);
return objectToBigInteger(val, defaultValue);
return objectToBigInteger(val, defaultValue, jsonParserConfiguration);
}

/**
Expand All @@ -1392,14 +1436,43 @@ public BigInteger optBigInteger(String key, BigInteger defaultValue) {
* to convert.
*/
static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
return objectToBigInteger(val, defaultValue, new JSONParserConfiguration());
}

/**
* @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null.
* @param jsonParserConfiguration parser configuration whose {@code maxNumberLength}
* bounds the number of decimal digits in the resulting integer. Values whose
* integer part would exceed this length are treated as unconvertible and
* {@code defaultValue} is returned. Pass a configuration with
* {@link ParserConfiguration#UNDEFINED_MAXIMUM_NUMBER_LENGTH} to disable this check.
* @return BigInteger conversion of the original value, or the defaultValue if unable
* to convert.
*/
static BigInteger objectToBigInteger(Object val, BigInteger defaultValue,
JSONParserConfiguration jsonParserConfiguration) {
if (NULL.equals(val)) {
return defaultValue;
}
if (jsonParserConfiguration == null) {
jsonParserConfiguration = new JSONParserConfiguration();
}
final int maxNumberLength = jsonParserConfiguration.getMaxNumberLength();
if (val instanceof BigInteger){
return (BigInteger) val;
}
if (val instanceof BigDecimal){
return ((BigDecimal) val).toBigInteger();
BigDecimal bd = (BigDecimal) val;
// Same ceiling as the parse-time maxNumberLength guard: refuse to
// materialise an integer whose decimal representation would exceed
// maxNumberLength digits. Prevents DoS via short exponent literals
// like 1e100000000 (CVE-2026-59171, see issue #1063).
if (maxNumberLength != ParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH
&& (long) bd.precision() - bd.scale() > maxNumberLength) {
return defaultValue;
}
return bd.toBigInteger();
}
if (val instanceof Double || val instanceof Float){
if (!numberIsFinite((Number)val)) {
Expand All @@ -1411,6 +1484,18 @@ static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
|| val instanceof Short || val instanceof Byte){
return BigInteger.valueOf(((Number) val).longValue());
}
return attemptConversionToBigInteger(val, defaultValue, maxNumberLength);
}

/**
* Convenience method to attempt conversion of value to BigInteger.
* Added to reduce complexity of objectToBigInteger()
* @param val the value to be converted
* @param defaultValue the default value to use if conversion is not attempted or fails
* @param maxNumberLength the max length allowed for BigIntegers
* @return the converted value, or the defaultValue
*/
private static BigInteger attemptConversionToBigInteger(Object val, BigInteger defaultValue, int maxNumberLength) {
// don't check if it's a string in case of unchecked Number subclasses
try {
/**
Expand All @@ -1422,7 +1507,12 @@ static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
*/
final String valStr = val.toString();
if(isDecimalNotation(valStr)) {
return new BigDecimal(valStr).toBigInteger();
BigDecimal bd = new BigDecimal(valStr);
if (maxNumberLength != ParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH
&& (long) bd.precision() - bd.scale() > maxNumberLength) {
return defaultValue;
}
return bd.toBigInteger();
}
return new BigInteger(valStr);
} catch (Exception e) {
Expand Down Expand Up @@ -2667,16 +2757,33 @@ protected static boolean isDecimalNotation(final String val) {
/**
* Try to convert a string into a number, boolean, or null. If the string
* can't be converted, return the string.
* Warning! stringToValue(String) uses the default max number length. If you want to override it,
* use a suitable initialized JSONParserConfiguration and the method: stringToValue(String, JSONParserConfiguration).
*
* @param string
* A String. can not be null.
* @param str A String. can not be null.
* @return A simple JSON value.
* @throws NullPointerException
* Thrown if the string is null.
*/
// Changes to this method must be copied to the corresponding method in
// the XML class to keep full support for Android
public static Object stringToValue(String string) {
public static Object stringToValue(String str) {
return stringToValue(str, new JSONParserConfiguration());
}

/**
* Try to convert a string into a number, boolean, or null. If the string
* can't be converted, return the string.
*
* @param string A String. can not be null.
* @param jsonParserConfiguration the parser config
* @return A simple JSON value. If the string represents a number that is too large,
* a string will be returned.
* @throws NullPointerException Thrown if the string is null.
*/
// Changes to this method must be copied to the corresponding method in
// the XML class to keep full support for Android
public static Object stringToValue(String string, JSONParserConfiguration jsonParserConfiguration) {
if ("".equals(string)) {
return string;
}
Expand All @@ -2700,7 +2807,14 @@ public static Object stringToValue(String string) {
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
if (string.length() <= 1000) {
if (jsonParserConfiguration == null) {
jsonParserConfiguration = new JSONParserConfiguration();
}
// user declines max number checking
if (jsonParserConfiguration.getMaxNumberLength() == ParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH) {
return stringToNumber(string);
}
if (string.length() <= jsonParserConfiguration.getMaxNumberLength()) {
return stringToNumber(string);
}
} catch (Exception ignore) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/json/JSONTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ Object nextSimpleValue(char c) {
jsonParserConfiguration.isStrictMode() && string.endsWith(".")) {
throw this.syntaxError(String.format("Strict mode error: Value '%s' ends with dot", string));
}
Object obj = JSONObject.stringToValue(string);
Object obj = JSONObject.stringToValue(string, jsonParserConfiguration);
// if obj is a boolean, look at string
if (jsonParserConfiguration != null &&
jsonParserConfiguration.isStrictMode()) {
Expand Down
63 changes: 58 additions & 5 deletions src/main/java/org/json/ParserConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ public class ParserConfiguration {
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;

/**
* Used to indicate there's no defined limit to the maximum number length
*/
public static final int UNDEFINED_MAXIMUM_NUMBER_LENGTH = -1;

/**
* The default maximum nesting depth when parsing a document.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;

/**
* The default max number length
*/
public static final int DEFAULT_MAX_NUMBER_LENGTH = 1000;

/**
* Specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string).
Expand All @@ -29,23 +39,32 @@ public class ParserConfiguration {
*/
protected int maxNestingDepth;

/**
* The max number of chars for any number. Exceeding this limit will cause the value to be converted to a string
*/
protected int maxNumberLength;

/**
* Constructs a new ParserConfiguration with default settings.
*/
public ParserConfiguration() {
this.keepStrings = false;
this.maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
this.maxNumberLength = DEFAULT_MAX_NUMBER_LENGTH;
}

/**
* Constructs a new ParserConfiguration with the specified settings.
* Constructs a new ParserConfiguration with the specified settings. Use the with* methods instead of calling this ctor.
*
* @param keepStrings A boolean indicating whether to preserve strings during parsing.
* @param maxNestingDepth An integer representing the maximum allowed nesting depth.
* @deprecated Use the with*() methods instead
*/
@Deprecated
protected ParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
this.keepStrings = keepStrings;
this.maxNestingDepth = maxNestingDepth;
this.maxNumberLength = DEFAULT_MAX_NUMBER_LENGTH;
}

/**
Expand All @@ -58,10 +77,11 @@ protected ParserConfiguration clone() {
// item, a new map instance should be created and if possible each value in the
// map should be cloned as well. If the values of the map are known to also
// be immutable, then a shallow clone of the map is acceptable.
return new ParserConfiguration(
this.keepStrings,
this.maxNestingDepth
);
ParserConfiguration parserConfiguration = new ParserConfiguration();
parserConfiguration.keepStrings = this.keepStrings;
parserConfiguration.maxNestingDepth = this.maxNestingDepth;
parserConfiguration.maxNumberLength = this.maxNumberLength;
return parserConfiguration;
}

/**
Expand Down Expand Up @@ -123,4 +143,37 @@ public <T extends ParserConfiguration> T withMaxNestingDepth(int maxNestingDepth

return newConfig;
}


/**
* The maximum number length that the parser will allow
*
* @return the maximum number lengtj set for this configuration
*/
public int getMaxNumberLength() {
return maxNumberLength;
}

/**
* Defines the maximum number length that the parser will allow
* Using any negative value as a parameter is equivalent to setting no limit to the length
* which means any size number is allowed
*
* @param maxNumberLength the maximum number length allowed
* @param <T> the type of the configuration object
* @return The existing configuration will not be modified. A new configuration is returned.
*/
@SuppressWarnings("unchecked")
public <T extends ParserConfiguration> T withMaxNumberLength(int maxNumberLength) {
T newConfig = (T) this.clone();

if (maxNumberLength > UNDEFINED_MAXIMUM_NUMBER_LENGTH) {
newConfig.maxNumberLength = maxNumberLength;
} else {
newConfig.maxNumberLength = UNDEFINED_MAXIMUM_NUMBER_LENGTH;
}

return newConfig;
}

}
Loading
Loading