Compare commits

...

26 Commits

Author SHA1 Message Date
Sean Leary
47fb49b6a8 Update for release 20230227 2023-02-27 07:21:11 -06:00
Sean Leary
0df034c9fd Update for release 20230227 2023-02-27 07:20:10 -06:00
Sean Leary
f0a05e6911 Update README.md 2023-02-27 07:17:51 -06:00
Sean Leary
1275f6809d Merge pull request #723 from TamasPergerDWP/master
JSONML should be protected from stack overflow exceptions caused by recursion, resolving #722
2023-02-17 13:47:36 -06:00
Tamas Perger
9234eab00a refactor: make JSONMLParserConfiguration all-args constructor private, enforcing the builder pattern. 2023-02-13 01:09:29 +00:00
Tamas Perger
72f4c3e646 refactor: rename XMLtoJSONMLParserConfiguration to JSONMLParserConfiguration 2023-02-12 01:32:34 +00:00
Tamas Perger
df2d6f8363 fix: introduce optional XMLtoJSONMLParserConfiguration parameter for JSONML.toJSONArray(...) functions, to facilitate max nesting depth override. 2023-02-11 01:52:13 +00:00
Tamas Perger
a6e412bded fix: limit the nesting depth in JSONML
Limit the XML nesting depth for CVE-2022-45688 when using the JsonML transform.
2023-02-10 01:46:44 +00:00
Tamas Perger
2391d248cc fix: amend XMLParserConfiguration.clone() to include the new maxNestingDepth param.
Amend Javadoc for XML and XMLParserConfiguration classes.
2023-02-10 01:45:34 +00:00
Sean Leary
401495ae86 Merge pull request #720 from cleydyr/issue-708
Limit the XML nesting depth for CVE-2022-45688
2023-02-05 19:30:04 -06:00
Cleydyr de Albuquerque
448e204186 docs: remove wrong description of parse method 2023-02-02 20:16:16 +01:00
Cleydyr de Albuquerque
eb56704e68 fix: set default maximum nesting depth as 512 2023-02-02 18:15:03 +01:00
Cleydyr de Albuquerque
651511f500 tests: add new test to verify that an XML having the permitted nesting depth can be converted 2023-02-01 20:22:47 +01:00
Cleydyr de Albuquerque
a14cb12c85 refactor: keep consistence with other tests and tidy up constant 2023-02-01 20:22:32 +01:00
Cleydyr de Albuquerque
f566a1d9ee fix: limit the nesting depth 2023-02-01 16:26:58 +01:00
Sean Leary
5920eca2d7 Merge pull request #711 from 6d64/revert-pull-707-interviewbit-spam
Revert pull 707 - interviewbit spam
2022-11-30 20:20:39 -06:00
6d64
3b097d051a Revert pull 707 - interviewbit spam
Reverted commit that was added by a bot adding interviewbit spam to
repos on github
2022-12-01 03:21:26 +11:00
Sean Leary
4e630e58a4 Merge pull request #707 from ASAlishaa/patch-1
Added new resource to the repos
2022-11-17 19:14:11 -06:00
ASAlisha
b732188e4e Added new resource to this repos.
Added resource in the correct format.
2022-11-15 16:31:05 +05:30
ASAlisha
5369442671 Added new resource to the repos
Added new useful JSON resource.
2022-11-14 03:26:18 +05:30
Sean Leary
bb1138762a Merge pull request #703 from TomerPacific/feature/update-release-for-JSONMap-Change
Update Releases.md for JSONObject(Map): Throws NPE if key is null
2022-11-05 17:39:13 -05:00
Sean Leary
6a732ec99d Merge pull request #704 from niranjanib/fix-javadoc-not-visible-in-website
move javadoc comments above the interface definition to make it visible
2022-11-05 17:38:15 -05:00
Niranjani
c798c76ddd move javadoc comments above the interface definition to make it visible
Fix #670
2022-10-30 22:10:38 +05:30
unknown
23d5e52a53 feature/update-release-for-JSONMap-Change adding breaking change for JSONMap to corresponding release 2022-10-28 08:45:54 +03:00
Sean Leary
98df35449a Merge pull request #696 from bmk15897/fix-flaky-test
Update JSONPointerTest for NonDex compatibility
2022-10-15 08:24:01 -05:00
Bharati Kulkarni
a2d3d3c9b5 Fix Flaky Test 2022-10-11 14:33:43 -05:00
14 changed files with 621 additions and 101 deletions

View File

@@ -8,7 +8,7 @@ JSON in Java [package org.json]
[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) [![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json)
**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20220924/json-20220924.jar)** **[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20230227/json-20230227.jar)**
# Overview # Overview

View File

@@ -5,6 +5,8 @@ and artifactId "json". For example:
[https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav) [https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav)
~~~ ~~~
20230227 Fix for CVE-2022-45688 and recent commits
20220924 New License - public domain, and some minor updates 20220924 New License - public domain, and some minor updates
20220320 Wrap StackOverflow with JSONException 20220320 Wrap StackOverflow with JSONException
@@ -20,6 +22,7 @@ and artifactId "json". For example:
20190722 Recent commits 20190722 Recent commits
20180813 POM change to include Automatic-Module-Name (#431) 20180813 POM change to include Automatic-Module-Name (#431)
JSONObject(Map) now throws an exception if any of a map keys are null (#405)
20180130 Recent commits 20180130 Recent commits

View File

@@ -3,7 +3,7 @@
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20220924</version> <version>20230227</version>
<packaging>bundle</packaging> <packaging>bundle</packaging>
<name>JSON in Java</name> <name>JSON in Java</name>

View File

@@ -27,7 +27,32 @@ public class JSONML {
XMLTokener x, XMLTokener x,
boolean arrayForm, boolean arrayForm,
JSONArray ja, JSONArray ja,
boolean keepStrings boolean keepStrings,
int currentNestingDepth
) throws JSONException {
return parse(x,arrayForm, ja,
keepStrings ? JSONMLParserConfiguration.KEEP_STRINGS : JSONMLParserConfiguration.ORIGINAL,
currentNestingDepth);
}
/**
* Parse XML values and store them in a JSONArray.
* @param x The XMLTokener containing the source string.
* @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null
* if we are at the outermost level.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means Don't type-convert text nodes and attribute values.
* @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException if a parsing error occurs
*/
private static Object parse(
XMLTokener x,
boolean arrayForm,
JSONArray ja,
JSONMLParserConfiguration config,
int currentNestingDepth
) throws JSONException { ) throws JSONException {
String attribute; String attribute;
char c; char c;
@@ -152,7 +177,7 @@ public class JSONML {
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
} }
newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token)); newjo.accumulate(attribute, config.isKeepStrings() ? ((String)token) :XML.stringToValue((String)token));
token = null; token = null;
} else { } else {
newjo.accumulate(attribute, ""); newjo.accumulate(attribute, "");
@@ -181,7 +206,12 @@ public class JSONML {
if (token != XML.GT) { if (token != XML.GT) {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
} }
closeTag = (String)parse(x, arrayForm, newja, keepStrings);
if (currentNestingDepth == config.getMaxNestingDepth()) {
throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
}
closeTag = (String)parse(x, arrayForm, newja, config, currentNestingDepth + 1);
if (closeTag != null) { if (closeTag != null) {
if (!closeTag.equals(tagName)) { if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName + throw x.syntaxError("Mismatched '" + tagName +
@@ -203,7 +233,7 @@ public class JSONML {
} else { } else {
if (ja != null) { if (ja != null) {
ja.put(token instanceof String ja.put(token instanceof String
? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token) ? (config.isKeepStrings() ? XML.unescape((String)token) : XML.stringToValue((String)token))
: token); : token);
} }
} }
@@ -224,7 +254,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(String string) throws JSONException { public static JSONArray toJSONArray(String string) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, false); return (JSONArray)parse(new XMLTokener(string), true, null, JSONMLParserConfiguration.ORIGINAL, 0);
} }
@@ -246,7 +276,56 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings); return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The source string.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(String string, JSONMLParserConfiguration config) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, config, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(XMLTokener x, JSONMLParserConfiguration config) throws JSONException {
return (JSONArray)parse(x, true, null, config, 0);
} }
@@ -268,7 +347,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONArray)parse(x, true, null, keepStrings); return (JSONArray)parse(x, true, null, keepStrings, 0);
} }
@@ -285,7 +364,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(XMLTokener x) throws JSONException { public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray)parse(x, true, null, false); return (JSONArray)parse(x, true, null, false, 0);
} }
@@ -303,7 +382,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, false); return (JSONObject)parse(new XMLTokener(string), false, null, false, 0);
} }
@@ -323,7 +402,29 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings); return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The XML source text.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(String string, JSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, config, 0);
} }
@@ -341,7 +442,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(XMLTokener x) throws JSONException { public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject)parse(x, false, null, false); return (JSONObject)parse(x, false, null, false, 0);
} }
@@ -361,7 +462,29 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONObject)parse(x, false, null, keepStrings); return (JSONObject)parse(x, false, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener of the XML source text.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(XMLTokener x, JSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(x, false, null, config, 0);
} }
@@ -442,6 +565,7 @@ public class JSONML {
return sb.toString(); return sb.toString();
} }
/** /**
* Reverse the JSONML transformation, making an XML text from a JSONObject. * Reverse the JSONML transformation, making an XML text from a JSONObject.
* The JSONObject must contain a "tagName" property. If it has children, * The JSONObject must contain a "tagName" property. If it has children,

View File

@@ -0,0 +1,128 @@
package org.json;
/*
Public Domain.
*/
/**
* Configuration object for the XML to JSONML parser. The configuration is immutable.
*/
@SuppressWarnings({""})
public class JSONMLParserConfiguration {
/**
* Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
* document to JSONML.
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
/**
* The default maximum nesting depth when parsing a XML document to JSONML.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
/** Original Configuration of the XML to JSONML Parser. */
public static final JSONMLParserConfiguration ORIGINAL
= new JSONMLParserConfiguration();
/** Original configuration of the XML to JSONML Parser except that values are kept as strings. */
public static final JSONMLParserConfiguration KEEP_STRINGS
= new JSONMLParserConfiguration().withKeepStrings(true);
/**
* When parsing the XML into JSONML, 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)
*/
private boolean keepStrings;
/**
* The maximum nesting depth when parsing a XML document to JSONML.
*/
private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
/**
* Default parser configuration. Does not keep strings (tries to implicitly convert values).
*/
public JSONMLParserConfiguration() {
this.keepStrings = false;
}
/**
* Configure the parser string processing and use the default CDATA Tag Name as "content".
* @param keepStrings <code>true</code> to parse all values as string.
* <code>false</code> to try and convert XML string values into a JSON value.
* @param maxNestingDepth <code>int</code> to limit the nesting depth
*/
private JSONMLParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
this.keepStrings = keepStrings;
this.maxNestingDepth = maxNestingDepth;
}
/**
* Provides a new instance of the same configuration.
*/
@Override
protected JSONMLParserConfiguration clone() {
// future modifications to this method should always ensure a "deep"
// clone in the case of collections. i.e. if a Map is added as a configuration
// 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 JSONMLParserConfiguration(
this.keepStrings,
this.maxNestingDepth
);
}
/**
* When parsing the XML into JSONML, 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)
*
* @return The <code>keepStrings</code> configuration value.
*/
public boolean isKeepStrings() {
return this.keepStrings;
}
/**
* When parsing the XML into JSONML, 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)
*
* @param newVal
* new value to use for the <code>keepStrings</code> configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public JSONMLParserConfiguration withKeepStrings(final boolean newVal) {
JSONMLParserConfiguration newConfig = this.clone();
newConfig.keepStrings = newVal;
return newConfig;
}
/**
* The maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSONML.
* @return the maximum nesting depth set for this configuration
*/
public int getMaxNestingDepth() {
return maxNestingDepth;
}
/**
* Defines the maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
* will throw a JsonException if the maximum depth is reached.
* Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
* which means the parses will go as deep as the maximum call stack size allows.
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public JSONMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
JSONMLParserConfiguration newConfig = this.clone();
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
newConfig.maxNestingDepth = maxNestingDepth;
} else {
newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
}
return newConfig;
}
}

View File

@@ -11,13 +11,13 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/** /**
* Use this annotation on a getter method to override the Bean name * Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. If this annotation is * parser for Bean -&gt; JSONObject mapping. If this annotation is
* present at any level in the class hierarchy, then the method will * present at any level in the class hierarchy, then the method will
* not be serialized from the bean into the JSONObject. * not be serialized from the bean into the JSONObject.
*/ */
@Documented
@Retention(RUNTIME)
@Target({METHOD})
public @interface JSONPropertyIgnore { } public @interface JSONPropertyIgnore { }

View File

@@ -11,14 +11,14 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/** /**
* Use this annotation on a getter method to override the Bean name * Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code> * parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code>
* will have the Bean parser fall back to the default field name processing. * will have the Bean parser fall back to the default field name processing.
*/ */
@Documented
@Retention(RUNTIME)
@Target({METHOD})
public @interface JSONPropertyName { public @interface JSONPropertyName {
/** /**
* @return The name of the property as to be used in the JSON Object. * @return The name of the property as to be used in the JSON Object.

View File

@@ -229,10 +229,14 @@ public class XML {
* The JSONObject that will include the new material. * The JSONObject that will include the new material.
* @param name * @param name
* The tag name. * The tag name.
* @param config
* The XML parser configuration.
* @param currentNestingDepth
* The current nesting depth.
* @return true if the close tag is processed. * @return true if the close tag is processed.
* @throws JSONException * @throws JSONException Thrown if any parsing error occurs.
*/ */
private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config) private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth)
throws JSONException { throws JSONException {
char c; char c;
int i; int i;
@@ -402,7 +406,11 @@ public class XML {
} else if (token == LT) { } else if (token == LT) {
// Nested element // Nested element
if (parse(x, jsonObject, tagName, config)) { if (currentNestingDepth == config.getMaxNestingDepth()) {
throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
}
if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) {
if (config.getForceList().contains(tagName)) { if (config.getForceList().contains(tagName)) {
// Force the value to be an array // Force the value to be an array
if (jsonObject.length() == 0) { if (jsonObject.length() == 0) {
@@ -655,7 +663,7 @@ public class XML {
while (x.more()) { while (x.more()) {
x.skipPast("<"); x.skipPast("<");
if(x.more()) { if(x.more()) {
parse(x, jo, null, config); parse(x, jo, null, config, 0);
} }
} }
return jo; return jo;

View File

@@ -16,6 +16,17 @@ import java.util.Set;
*/ */
@SuppressWarnings({""}) @SuppressWarnings({""})
public class XMLParserConfiguration { public class XMLParserConfiguration {
/**
* Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
* document to JSON.
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
/**
* The default maximum nesting depth when parsing a XML document to JSON.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
/** Original Configuration of the XML Parser. */ /** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration(); = new XMLParserConfiguration();
@@ -54,6 +65,11 @@ public class XMLParserConfiguration {
*/ */
private Set<String> forceList; private Set<String> forceList;
/**
* The maximum nesting depth when parsing a XML document to JSON.
*/
private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
/** /**
* Default parser configuration. Does not keep strings (tries to implicitly convert * Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content". * values), and the CDATA Tag Name is "content".
@@ -141,14 +157,17 @@ public class XMLParserConfiguration {
* @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute * @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string * xsi:type="integer" as integer, xsi:type="string" as string
* @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays * @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays
* @param maxNestingDepth <code>int</code> to limit the nesting depth
*/ */
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList ) { final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
final int maxNestingDepth) {
this.keepStrings = keepStrings; this.keepStrings = keepStrings;
this.cDataTagName = cDataTagName; this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull; this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap); this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
this.forceList = Collections.unmodifiableSet(forceList); this.forceList = Collections.unmodifiableSet(forceList);
this.maxNestingDepth = maxNestingDepth;
} }
/** /**
@@ -166,7 +185,8 @@ public class XMLParserConfiguration {
this.cDataTagName, this.cDataTagName,
this.convertNilAttributeToNull, this.convertNilAttributeToNull,
this.xsiTypeMap, this.xsiTypeMap,
this.forceList this.forceList,
this.maxNestingDepth
); );
} }
@@ -297,4 +317,34 @@ public class XMLParserConfiguration {
newConfig.forceList = Collections.unmodifiableSet(cloneForceList); newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
return newConfig; return newConfig;
} }
/**
* The maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSON.
* @return the maximum nesting depth set for this configuration
*/
public int getMaxNestingDepth() {
return maxNestingDepth;
}
/**
* Defines the maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSON. The default max nesting depth is 512, which means the parser
* will throw a JsonException if the maximum depth is reached.
* Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
* which means the parses will go as deep as the maximum call stack size allows.
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
XMLParserConfiguration newConfig = this.clone();
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
newConfig.maxNestingDepth = maxNestingDepth;
} else {
newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
}
return newConfig;
}
} }

View File

@@ -6,7 +6,6 @@ Public Domain.
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -833,4 +833,128 @@ public class JSONMLTest {
ex.getMessage()); ex.getMessage());
} }
} }
@Test
public void testToJSONArrayMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
JSONML.toJSONArray(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONArrayMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONArrayMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the JSONMLParserConfiguration used");
}
}
@Test
public void testToJSONObjectMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONObjectMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONObjectMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the JSONMLParserConfiguration used");
}
}
} }

View File

@@ -72,8 +72,10 @@ public class JSONPointerTest {
@Test @Test
public void queryByEmptyKeySubObject() { public void queryByEmptyKeySubObject() {
assertEquals( "{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" + JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
" other value\"}", query("/obj/").toString()); " other value\"}");
JSONObject obj = (JSONObject) query("/obj/");
assertTrue(json.similar(obj));
} }
@Test @Test

View File

@@ -1052,6 +1052,29 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
} }
@Test
public void testMaxNestingDepthIsSet() {
XMLParserConfiguration xmlParserConfiguration = XMLParserConfiguration.ORIGINAL;
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(42);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 42);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(0);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 0);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(-31415926);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(Integer.MIN_VALUE);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
}
/** /**
* Convenience method, given an input string and expected result, * Convenience method, given an input string and expected result,
* convert to JSONObject and compare actual to expected result. * convert to JSONObject and compare actual to expected result.

View File

@@ -1247,6 +1247,65 @@ public class XMLTest {
fail("file writer error: " +e.getMessage()); fail("file writer error: " +e.getMessage());
} }
} }
@Test
public void testMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the XMLParserConfiguration used");
}
}
} }