Merge branch 'stleary:master' into feature-disable-whitespace-trim

This commit is contained in:
Keaton 2024-01-18 09:17:26 +02:00 committed by GitHub
commit 7915d8518f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 393 additions and 30 deletions

View File

@ -66,7 +66,7 @@ import org.json.JSONObject;
public class Test {
public static void main(String args[]){
JSONObject jo = new JSONObject("{ \"abc\" : \"def\" }");
System.out.println(jo.toString());
System.out.println(jo);
}
}
```

0
gradlew vendored Normal file → Executable file
View File

View File

@ -104,6 +104,9 @@
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-Xlint:unchecked</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>

View File

@ -46,19 +46,19 @@ public class CookieList {
* @throws JSONException if a called function fails
*/
public static String toString(JSONObject jo) throws JSONException {
boolean b = false;
boolean isEndOfPair = false;
final StringBuilder sb = new StringBuilder();
// Don't use the new entrySet API to maintain Android support
for (final String key : jo.keySet()) {
final Object value = jo.opt(key);
if (!JSONObject.NULL.equals(value)) {
if (b) {
if (isEndOfPair) {
sb.append(';');
}
sb.append(Cookie.escape(key));
sb.append("=");
sb.append(Cookie.escape(value.toString()));
b = true;
isEndOfPair = true;
}
}
return sb.toString();

View File

@ -149,11 +149,40 @@ public class JSONArray implements Iterable<Object> {
* A Collection.
*/
public JSONArray(Collection<?> collection) {
this(collection, 0, new JSONParserConfiguration());
}
/**
* Construct a JSONArray from a Collection.
*
* @param collection
* A Collection.
* @param jsonParserConfiguration
* Configuration object for the JSON parser
*/
public JSONArray(Collection<?> collection, JSONParserConfiguration jsonParserConfiguration) {
this(collection, 0, jsonParserConfiguration);
}
/**
* Construct a JSONArray from a collection with recursion depth.
*
* @param collection
* A Collection.
* @param recursionDepth
* Variable for tracking the count of nested object creations.
* @param jsonParserConfiguration
* Configuration object for the JSON parser
*/
JSONArray(Collection<?> collection, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
throw new JSONException("JSONArray has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
}
if (collection == null) {
this.myArrayList = new ArrayList<Object>();
} else {
this.myArrayList = new ArrayList<Object>(collection.size());
this.addAll(collection, true);
this.addAll(collection, true, recursionDepth, jsonParserConfiguration);
}
}
@ -205,7 +234,7 @@ public class JSONArray implements Iterable<Object> {
throw new JSONException(
"JSONArray initial value should be a string or collection or array.");
}
this.addAll(array, true);
this.addAll(array, true, 0);
}
/**
@ -1330,7 +1359,8 @@ public class JSONArray implements Iterable<Object> {
* The subscript.
* @param value
* The Map value.
* @return this.
* @return
* reference to self
* @throws JSONException
* If the index is negative or if the value is an invalid
* number.
@ -1338,7 +1368,27 @@ public class JSONArray implements Iterable<Object> {
* If a key in the map is <code>null</code>
*/
public JSONArray put(int index, Map<?, ?> value) throws JSONException {
this.put(index, new JSONObject(value));
this.put(index, new JSONObject(value, new JSONParserConfiguration()));
return this;
}
/**
* Put a value in the JSONArray, where the value will be a JSONObject that
* is produced from a Map.
*
* @param index
* The subscript
* @param value
* The Map value.
* @param jsonParserConfiguration
* Configuration object for the JSON parser
* @return reference to self
* @throws JSONException
* If the index is negative or if the value is an invalid
* number.
*/
public JSONArray put(int index, Map<?, ?> value, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this.put(index, new JSONObject(value, jsonParserConfiguration));
return this;
}
@ -1779,13 +1829,14 @@ public class JSONArray implements Iterable<Object> {
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
*
* @param recursionDepth
* Variable for tracking the count of nested object creations.
*/
private void addAll(Collection<?> collection, boolean wrap) {
private void addAll(Collection<?> collection, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
this.myArrayList.ensureCapacity(this.myArrayList.size() + collection.size());
if (wrap) {
for (Object o: collection){
this.put(JSONObject.wrap(o));
this.put(JSONObject.wrap(o, recursionDepth + 1, jsonParserConfiguration));
}
} else {
for (Object o: collection){
@ -1814,7 +1865,24 @@ public class JSONArray implements Iterable<Object> {
}
}
}
/**
* Add an array's elements to the JSONArray.
*
* @param array
* Array. If the parameter passed is null, or not an array,
* JSONArray, Collection, or Iterable, an exception will be
* thrown.
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
* @throws JSONException
* If not an array or if an array value is non-finite number.
*/
private void addAll(Object array, boolean wrap) throws JSONException {
this.addAll(array, wrap, 0);
}
/**
* Add an array's elements to the JSONArray.
*
@ -1823,21 +1891,40 @@ public class JSONArray implements Iterable<Object> {
* JSONArray, Collection, or Iterable, an exception will be
* thrown.
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
* @param recursionDepth
* Variable for tracking the count of nested object creations.
*/
private void addAll(Object array, boolean wrap, int recursionDepth) {
addAll(array, wrap, recursionDepth, new JSONParserConfiguration());
}
/**
* Add an array's elements to the JSONArray.
*`
* @param array
* Array. If the parameter passed is null, or not an array,
* JSONArray, Collection, or Iterable, an exception will be
* thrown.
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
*
* @param recursionDepth
* Variable for tracking the count of nested object creations.
* @param jsonParserConfiguration
* Variable to pass parser custom configuration for json parsing.
* @throws JSONException
* If not an array or if an array value is non-finite number.
* @throws NullPointerException
* Thrown if the array parameter is null.
*/
private void addAll(Object array, boolean wrap) throws JSONException {
private void addAll(Object array, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
if (array.getClass().isArray()) {
int length = Array.getLength(array);
this.myArrayList.ensureCapacity(this.myArrayList.size() + length);
if (wrap) {
for (int i = 0; i < length; i += 1) {
this.put(JSONObject.wrap(Array.get(array, i)));
this.put(JSONObject.wrap(Array.get(array, i), recursionDepth + 1, jsonParserConfiguration));
}
} else {
for (int i = 0; i < length; i += 1) {
@ -1850,7 +1937,7 @@ public class JSONArray implements Iterable<Object> {
// JSONArray
this.myArrayList.addAll(((JSONArray)array).myArrayList);
} else if (array instanceof Collection) {
this.addAll((Collection<?>)array, wrap);
this.addAll((Collection<?>)array, wrap, recursionDepth);
} else if (array instanceof Iterable) {
this.addAll((Iterable<?>)array, wrap);
} else {

View File

@ -55,11 +55,13 @@ public class JSONMLParserConfiguration extends ParserConfiguration {
);
}
@SuppressWarnings("unchecked")
@Override
public JSONMLParserConfiguration withKeepStrings(final boolean newVal) {
return super.withKeepStrings(newVal);
}
@SuppressWarnings("unchecked")
@Override
public JSONMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
return super.withMaxNestingDepth(maxNestingDepth);

View File

@ -276,6 +276,30 @@ public class JSONObject {
* If a key in the map is <code>null</code>
*/
public JSONObject(Map<?, ?> m) {
this(m, 0, new JSONParserConfiguration());
}
/**
* Construct a JSONObject from a Map with custom json parse configurations.
*
* @param m
* A map object that can be used to initialize the contents of
* the JSONObject.
* @param jsonParserConfiguration
* Variable to pass parser custom configuration for json parsing.
*/
public JSONObject(Map<?, ?> m, JSONParserConfiguration jsonParserConfiguration) {
this(m, 0, jsonParserConfiguration);
}
/**
* Construct a JSONObject from a map with recursion depth.
*
*/
private JSONObject(Map<?, ?> m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
throw new JSONException("JSONObject has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
}
if (m == null) {
this.map = new HashMap<String, Object>();
} else {
@ -287,7 +311,7 @@ public class JSONObject {
final Object value = e.getValue();
if (value != null) {
testValidity(value);
this.map.put(String.valueOf(e.getKey()), wrap(value));
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
}
}
}
@ -1841,6 +1865,10 @@ public class JSONObject {
}
}
//If the superclass is Object, no annotations will be found any more
if (c.getSuperclass().equals(Object.class))
return null;
try {
return getAnnotation(
c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
@ -1895,6 +1923,10 @@ public class JSONObject {
}
}
//If the superclass is Object, no annotations will be found any more
if (c.getSuperclass().equals(Object.class))
return -1;
try {
int d = getAnnotationDepth(
c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
@ -2566,7 +2598,31 @@ public class JSONObject {
return wrap(object, null);
}
/**
* Wrap an object, if necessary. If the object is <code>null</code>, return the NULL
* object. If it is an array or collection, wrap it in a JSONArray. If it is
* a map, wrap it in a JSONObject. If it is a standard property (Double,
* String, et al) then it is already wrapped. Otherwise, if it comes from
* one of the java packages, turn it into a string. And if it doesn't, try
* to wrap it in a JSONObject. If the wrapping fails, then null is returned.
*
* @param object
* The object to wrap
* @param recursionDepth
* Variable for tracking the count of nested object creations.
* @param jsonParserConfiguration
* Variable to pass parser custom configuration for json parsing.
* @return The wrapped value
*/
static Object wrap(Object object, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
return wrap(object, null, recursionDepth, jsonParserConfiguration);
}
private static Object wrap(Object object, Set<Object> objectsRecord) {
return wrap(object, objectsRecord, 0, new JSONParserConfiguration());
}
private static Object wrap(Object object, Set<Object> objectsRecord, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
try {
if (NULL.equals(object)) {
return NULL;
@ -2584,14 +2640,14 @@ public class JSONObject {
if (object instanceof Collection) {
Collection<?> coll = (Collection<?>) object;
return new JSONArray(coll);
return new JSONArray(coll, recursionDepth, jsonParserConfiguration);
}
if (object.getClass().isArray()) {
return new JSONArray(object);
}
if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
return new JSONObject(map);
return new JSONObject(map, recursionDepth, jsonParserConfiguration);
}
Package objectPackage = object.getClass().getPackage();
String objectPackageName = objectPackage != null ? objectPackage

View File

@ -0,0 +1,26 @@
package org.json;
/**
* Configuration object for the JSON parser. The configuration is immutable.
*/
public class JSONParserConfiguration extends ParserConfiguration {
/**
* Configuration with the default values.
*/
public JSONParserConfiguration() {
super();
}
@Override
protected JSONParserConfiguration clone() {
return new JSONParserConfiguration();
}
@SuppressWarnings("unchecked")
@Override
public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
return super.withMaxNestingDepth(maxNestingDepth);
}
}

View File

@ -24,7 +24,7 @@ class NumberConversionUtil {
val = "-0."+val.substring(2);
}
char initial = val.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-' ) {
if ( isNumericChar(initial) || initial == '-' ) {
// decimal representation
if (isDecimalNotation(val)) {
// Use a BigDecimal all the time so we keep the original
@ -53,13 +53,13 @@ class NumberConversionUtil {
initial = val.charAt(0);
if(initial == '0' && val.length() > 1) {
char at1 = val.charAt(1);
if(at1 >= '0' && at1 <= '9') {
if(isNumericChar(at1)) {
throw new NumberFormatException("val ["+input+"] is not a valid number.");
}
} else if (initial == '-' && val.length() > 2) {
char at1 = val.charAt(1);
char at2 = val.charAt(2);
if(at1 == '0' && at2 >= '0' && at2 <= '9') {
if(at1 == '0' && isNumericChar(at2)) {
throw new NumberFormatException("val ["+input+"] is not a valid number.");
}
}
@ -83,6 +83,16 @@ class NumberConversionUtil {
throw new NumberFormatException("val ["+input+"] is not a valid number.");
}
/**
* Checks if the character is a numeric digit ('0' to '9').
*
* @param c The character to be checked.
* @return true if the character is a numeric digit, false otherwise.
*/
private static boolean isNumericChar(char c) {
return (c <= '9' && c >= '0');
}
/**
* Checks if the value could be considered a number in decimal number system.
* @param value

View File

@ -75,6 +75,7 @@ public class ParserConfiguration {
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
@SuppressWarnings("unchecked")
public <T extends ParserConfiguration> T withKeepStrings(final boolean newVal) {
T newConfig = (T)this.clone();
newConfig.keepStrings = newVal;
@ -101,6 +102,7 @@ public class ParserConfiguration {
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
@SuppressWarnings("unchecked")
public <T extends ParserConfiguration> T withMaxNestingDepth(int maxNestingDepth) {
T newConfig = (T)this.clone();

View File

@ -892,14 +892,14 @@ public class XML {
string = (object == null) ? "null" : escape(object.toString());
String indentationSuffix = (indentFactor > 0) ? "\n" : "";
if(tagName == null){
return indent(indent) + "\"" + string + "\"" + ((indentFactor > 0) ? "\n" : "");
return indent(indent) + "\"" + string + "\"" + indentationSuffix;
} else if(string.length() == 0){
return indent(indent) + "<" + tagName + "/>" + ((indentFactor > 0) ? "\n" : "");
return indent(indent) + "<" + tagName + "/>" + indentationSuffix;
} else {
return indent(indent) + "<" + tagName
+ ">" + string + "</" + tagName + ">" + ((indentFactor > 0) ? "\n" : "");
+ ">" + string + "</" + tagName + ">" + indentationSuffix;
}
}

View File

@ -204,6 +204,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
@SuppressWarnings("unchecked")
@Override
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
return super.withKeepStrings(newVal);
@ -321,6 +322,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
* @return The existing configuration will not be modified. A new configuration is returned.
*/
@SuppressWarnings("unchecked")
@Override
public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
return super.withMaxNestingDepth(maxNestingDepth);
@ -328,7 +330,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
/**
* To enable explicit end tag with empty value.
* @param closeEmptyTag
* @param closeEmptyTag new value for the closeEmptyTag property
* @return same instance of configuration with empty tag config updated
*/
public XMLParserConfiguration withCloseEmptyTag(boolean closeEmptyTag){

View File

@ -28,9 +28,11 @@ import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONParserConfiguration;
import org.json.JSONPointerException;
import org.json.JSONString;
import org.json.JSONTokener;
import org.json.ParserConfiguration;
import org.json.junit.data.MyJsonString;
import org.junit.Ignore;
import org.junit.Test;
@ -1417,4 +1419,78 @@ public class JSONArrayTest {
.put(2);
assertFalse(ja1.similar(ja3));
}
@Test(expected = JSONException.class)
public void testRecursiveDepth() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
new JSONArray().put(map);
}
@Test(expected = JSONException.class)
public void testRecursiveDepthAtPosition() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
new JSONArray().put(0, map);
}
@Test(expected = JSONException.class)
public void testRecursiveDepthArray() {
ArrayList<Object> array = new ArrayList<>();
array.add(array);
new JSONArray(array);
}
@Test
public void testRecursiveDepthAtPositionDefaultObject() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
new JSONArray().put(0, map);
}
@Test
public void testRecursiveDepthAtPosition1000Object() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1000);
new JSONArray().put(0, map, new JSONParserConfiguration().withMaxNestingDepth(1000));
}
@Test(expected = JSONException.class)
public void testRecursiveDepthAtPosition1001Object() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1001);
new JSONArray().put(0, map);
}
@Test(expected = JSONException.class)
public void testRecursiveDepthArrayLimitedMaps() {
ArrayList<Object> array = new ArrayList<>();
array.add(array);
new JSONArray(array);
}
@Test
public void testRecursiveDepthArrayForDefaultLevels() {
ArrayList<Object> array = buildNestedArray(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
new JSONArray(array, new JSONParserConfiguration());
}
@Test
public void testRecursiveDepthArrayFor1000Levels() {
ArrayList<Object> array = buildNestedArray(1000);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
new JSONArray(array, parserConfiguration);
}
@Test(expected = JSONException.class)
public void testRecursiveDepthArrayFor1001Levels() {
ArrayList<Object> array = buildNestedArray(1001);
new JSONArray(array);
}
public static ArrayList<Object> buildNestedArray(int maxDepth) {
if (maxDepth <= 0) {
return new ArrayList<>();
}
ArrayList<Object> nestedArray = new ArrayList<>();
nestedArray.add(buildNestedArray(maxDepth - 1));
return nestedArray;
}
}

View File

@ -32,8 +32,10 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONPointerException;
import org.json.JSONParserConfiguration;
import org.json.JSONString;
import org.json.JSONTokener;
import org.json.ParserConfiguration;
import org.json.XML;
import org.json.junit.data.BrokenToString;
import org.json.junit.data.ExceptionalBean;
@ -3718,4 +3720,101 @@ public class JSONObjectTest {
assertThrows(JSONException.class, () -> new JSONObject(bean));
}
}
@Test(expected = JSONException.class)
public void issue743SerializationMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}
@Test(expected = JSONException.class)
public void testCircularReferenceMultipleLevel() {
HashMap<String, Object> inside = new HashMap<>();
HashMap<String, Object> jsonObject = new HashMap<>();
inside.put("inside", jsonObject);
jsonObject.put("test", inside);
new JSONObject(jsonObject);
}
@Test
public void issue743SerializationMapWith512Objects() {
HashMap<String, Object> map = buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}
@Test
public void issue743SerializationMapWith1000Objects() {
HashMap<String, Object> map = buildNestedMap(1000);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
JSONObject object = new JSONObject(map, parserConfiguration);
String jsonString = object.toString();
}
@Test(expected = JSONException.class)
public void issue743SerializationMapWith1001Objects() {
HashMap<String, Object> map = buildNestedMap(1001);
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}
@Test(expected = JSONException.class)
public void testCircleReferenceFirstLevel() {
Map<Object, Object> jsonObject = new HashMap<>();
jsonObject.put("test", jsonObject);
new JSONObject(jsonObject, new JSONParserConfiguration());
}
@Test(expected = StackOverflowError.class)
public void testCircleReferenceMultiplyLevel_notConfigured_expectedStackOverflow() {
Map<Object, Object> inside = new HashMap<>();
Map<Object, Object> jsonObject = new HashMap<>();
inside.put("test", jsonObject);
jsonObject.put("test", inside);
new JSONObject(jsonObject, new JSONParserConfiguration().withMaxNestingDepth(99999));
}
@Test(expected = JSONException.class)
public void testCircleReferenceMultiplyLevel_configured_expectedJSONException() {
Map<Object, Object> inside = new HashMap<>();
Map<Object, Object> jsonObject = new HashMap<>();
inside.put("test", jsonObject);
jsonObject.put("test", inside);
new JSONObject(jsonObject, new JSONParserConfiguration());
}
@Test
public void testDifferentKeySameInstanceNotACircleReference() {
Map<Object, Object> map1 = new HashMap<>();
Map<Object, Object> map2 = new HashMap<>();
map1.put("test1", map2);
map1.put("test2", map2);
new JSONObject(map1);
}
/**
* Method to build nested map of max maxDepth
*
* @param maxDepth
* @return
*/
public static HashMap<String, Object> buildNestedMap(int maxDepth) {
if (maxDepth <= 0) {
return new HashMap<>();
}
HashMap<String, Object> nestedMap = new HashMap<>();
nestedMap.put("t", buildNestedMap(maxDepth - 1));
return nestedMap;
}
}

View File

@ -12,7 +12,7 @@ import java.util.List;
*/
public class WeirdList {
/** */
private final List<Integer> list = new ArrayList();
private final List<Integer> list = new ArrayList<>();
/**
* @param vals
@ -25,14 +25,14 @@ public class WeirdList {
* @return a copy of the list
*/
public List<Integer> get() {
return new ArrayList(this.list);
return new ArrayList<>(this.list);
}
/**
* @return a copy of the list
*/
public List<Integer> getALL() {
return new ArrayList(this.list);
return new ArrayList<>(this.list);
}
/**