diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index bb614d4ac..db3c79fff 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -232,7 +232,7 @@ public static void noSpace(String string) throws JSONException { * @return true if the close tag is processed. * @throws JSONException */ - 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 { char c; int i; @@ -402,7 +402,11 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } else if (token == LT) { // 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)) { // Force the value to be an array if (jsonObject.length() == 0) { @@ -655,7 +659,7 @@ public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration conf while (x.more()) { x.skipPast("<"); if(x.more()) { - parse(x, jo, null, config); + parse(x, jo, null, config, 0); } } return jo; diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java index 9f0071095..f118a812a 100644 --- a/src/main/java/org/json/XMLParserConfiguration.java +++ b/src/main/java/org/json/XMLParserConfiguration.java @@ -16,6 +16,17 @@ */ @SuppressWarnings({""}) 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. */ public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration(); @@ -54,6 +65,12 @@ public class XMLParserConfiguration { */ private Set forceList; + /** + * When parsing the XML into JSON, specifies the tags whose values should be converted + * to arrays + */ + private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH; + /** * Default parser configuration. Does not keep strings (tries to implicitly convert * values), and the CDATA Tag Name is "content". @@ -297,4 +314,33 @@ public XMLParserConfiguration withForceList(final Set forceList) { newConfig.forceList = Collections.unmodifiableSet(cloneForceList); 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 go as deep as the maximum call stack size allows. Using any negative value as a + * parameter is equivalent to setting no limit to the nesting depth. + * @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; + } } diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java index 1a2df7fe7..aa8657f06 100644 --- a/src/test/java/org/json/junit/JSONArrayTest.java +++ b/src/test/java/org/json/junit/JSONArrayTest.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java index f9d24a6b0..21a2b595e 100755 --- a/src/test/java/org/json/junit/XMLConfigurationTest.java +++ b/src/test/java/org/json/junit/XMLConfigurationTest.java @@ -1051,6 +1051,29 @@ public void testEmptyTagForceList() { 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, diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 937658e86..7d22610d7 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1247,6 +1247,65 @@ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ fail("file writer error: " +e.getMessage()); } } + + @Test + public void testMaxNestingDepthOf42IsRespected() { + final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", ""); + + 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 = "\n" + + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\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 = "\n" + + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\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"); + } + } }