From f566a1d9ee1f8139357017dc6c7def1da19cd8d4 Mon Sep 17 00:00:00 2001 From: Cleydyr de Albuquerque Date: Tue, 31 Jan 2023 17:32:34 +0100 Subject: [PATCH 1/5] fix: limit the nesting depth --- src/main/java/org/json/XML.java | 14 +++++-- .../java/org/json/XMLParserConfiguration.java | 41 +++++++++++++++++++ .../org/json/junit/XMLConfigurationTest.java | 23 +++++++++++ src/test/java/org/json/junit/XMLTest.java | 35 ++++++++++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index bb614d4ac..b8fdefcf0 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) { @@ -644,6 +648,10 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws * All values are converted as strings, for 1, 01, 29.0 will not be coerced to * numbers but will instead be the exact value as seen in the XML document. * + * This method can parse documents with a maximum nesting depth of 256. If you + * need to parse documents with a nesting depth greater than 256, you should use + * + * * @param reader The XML source reader. * @param config Configuration options for the parser * @return A JSONObject containing the structured data from the XML string. @@ -655,7 +663,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..e3311fcd1 100644 --- a/src/main/java/org/json/XMLParserConfiguration.java +++ b/src/main/java/org/json/XMLParserConfiguration.java @@ -16,6 +16,12 @@ */ @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; + /** Original Configuration of the XML Parser. */ public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration(); @@ -54,6 +60,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 = UNDEFINED_MAXIMUM_NESTING_DEPTH; + /** * Default parser configuration. Does not keep strings (tries to implicitly convert * values), and the CDATA Tag Name is "content". @@ -297,4 +309,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 undefined, 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/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java index f9d24a6b0..25b17e242 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.UNDEFINED_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..9c9ada4d8 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -24,6 +25,7 @@ import org.json.*; import org.junit.Rule; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import org.junit.rules.TemporaryFolder; @@ -1247,6 +1249,39 @@ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ fail("file writer error: " +e.getMessage()); } } + + @Test + public void testMaxNestingDepthIsRespected() { + final String wayTooLongMalformedXML = ""; + + Throwable throwable = assertThrows(JSONException.class, new ThrowingRunnable() { + + @Override + public void run() throws Throwable { + XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(42)); + } + }); + + assertTrue("Wrong throwable thrown: not expecting message <" + throwable.getMessage() + ">", throwable.getMessage().startsWith("Maximum nesting depth of")); + + final String perfectlyFineXML = "\n" + + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\n"; + + throwable = assertThrows(JSONException.class, new ThrowingRunnable() { + + @Override + public void run() throws Throwable { + XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(1)); + } + }); + + assertTrue("Wrong throwable thrown: not expecting message <" + throwable.getMessage() + ">", throwable.getMessage().startsWith("Maximum nesting depth of")); + } } From a14cb12c85d50060b1f95840c01f0be5de24503d Mon Sep 17 00:00:00 2001 From: Cleydyr de Albuquerque Date: Wed, 1 Feb 2023 20:20:18 +0100 Subject: [PATCH 2/5] refactor: keep consistence with other tests and tidy up constant --- src/test/java/org/json/junit/XMLTest.java | 39 ++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 9c9ada4d8..6c0de9f6f 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -7,7 +7,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -25,7 +24,6 @@ import org.json.*; import org.junit.Rule; import org.junit.Test; -import org.junit.function.ThrowingRunnable; import org.junit.rules.TemporaryFolder; @@ -1251,19 +1249,23 @@ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ } @Test - public void testMaxNestingDepthIsRespected() { - final String wayTooLongMalformedXML = ""; + public void testMaxNestingDepthOf42IsRespected() { + final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", ""); - Throwable throwable = assertThrows(JSONException.class, new ThrowingRunnable() { + final int maxNestingDepth = 42; - @Override - public void run() throws Throwable { - XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(42)); - } - }); + try { + XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); - assertTrue("Wrong throwable thrown: not expecting message <" + throwable.getMessage() + ">", throwable.getMessage().startsWith("Maximum nesting depth of")); + 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" + @@ -1272,15 +1274,16 @@ public void run() throws Throwable { " \n" + "\n"; - throwable = assertThrows(JSONException.class, new ThrowingRunnable() { + final int maxNestingDepth = 1; - @Override - public void run() throws Throwable { - XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(1)); - } - }); + try { + XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); - assertTrue("Wrong throwable thrown: not expecting message <" + throwable.getMessage() + ">", throwable.getMessage().startsWith("Maximum nesting depth of")); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", + e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); + } } } From 651511f50099a3b8d59617838c82e3d72dd39178 Mon Sep 17 00:00:00 2001 From: Cleydyr de Albuquerque Date: Wed, 1 Feb 2023 20:21:14 +0100 Subject: [PATCH 3/5] tests: add new test to verify that an XML having the permitted nesting depth can be converted --- src/test/java/org/json/junit/XMLTest.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 6c0de9f6f..7d22610d7 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1285,6 +1285,27 @@ public void testMaxNestingDepthIsRespectedWithValidXML() { 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"); + } + } } From eb56704e68a186f975400e009d28d4e0b5d887ec Mon Sep 17 00:00:00 2001 From: Cleydyr de Albuquerque Date: Thu, 2 Feb 2023 18:15:03 +0100 Subject: [PATCH 4/5] fix: set default maximum nesting depth as 512 --- src/main/java/org/json/XMLParserConfiguration.java | 11 ++++++++--- src/test/java/org/json/junit/JSONArrayTest.java | 1 - .../java/org/json/junit/XMLConfigurationTest.java | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java index e3311fcd1..f118a812a 100644 --- a/src/main/java/org/json/XMLParserConfiguration.java +++ b/src/main/java/org/json/XMLParserConfiguration.java @@ -22,6 +22,11 @@ public class XMLParserConfiguration { */ 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(); @@ -64,7 +69,7 @@ public class XMLParserConfiguration { * When parsing the XML into JSON, specifies the tags whose values should be converted * to arrays */ - private int maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH; + private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH; /** * Default parser configuration. Does not keep strings (tries to implicitly convert @@ -321,8 +326,8 @@ public int getMaxNestingDepth() { /** * 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 undefined, which means the - * parser will go as deep as the maximum call stack size allows. Using any negative value as a + * 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. 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 25b17e242..21a2b595e 100755 --- a/src/test/java/org/json/junit/XMLConfigurationTest.java +++ b/src/test/java/org/json/junit/XMLConfigurationTest.java @@ -1056,7 +1056,7 @@ public void testEmptyTagForceList() { public void testMaxNestingDepthIsSet() { XMLParserConfiguration xmlParserConfiguration = XMLParserConfiguration.ORIGINAL; - assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH); + assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH); xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(42); From 448e204186784adc85c3498cf487eb7c8e83fa57 Mon Sep 17 00:00:00 2001 From: Cleydyr de Albuquerque Date: Thu, 2 Feb 2023 20:16:16 +0100 Subject: [PATCH 5/5] docs: remove wrong description of parse method --- src/main/java/org/json/XML.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index b8fdefcf0..db3c79fff 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -648,10 +648,6 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws * All values are converted as strings, for 1, 01, 29.0 will not be coerced to * numbers but will instead be the exact value as seen in the XML document. * - * This method can parse documents with a maximum nesting depth of 256. If you - * need to parse documents with a nesting depth greater than 256, you should use - * - * * @param reader The XML source reader. * @param config Configuration options for the parser * @return A JSONObject containing the structured data from the XML string.