diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index 6458ab254..bdad42753 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -67,12 +67,6 @@ public class JSONArray implements Iterable { */ private final ArrayList myArrayList; - // strict mode checks after constructor require access to this object - private JSONTokener jsonTokener; - - // strict mode checks after constructor require access to this object - private JSONParserConfiguration jsonParserConfiguration; - /** * Construct an empty JSONArray. */ @@ -102,14 +96,7 @@ public JSONArray(JSONTokener x) throws JSONException { public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { this(); - if (this.jsonParserConfiguration == null) { - this.jsonParserConfiguration = jsonParserConfiguration; - } - if (this.jsonTokener == null) { - this.jsonTokener = x; - this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration); - } - + boolean isInitial = x.getPrevious() == 0; if (x.nextClean() != '[') { throw x.syntaxError("A JSONArray text must start with '['"); } @@ -156,11 +143,19 @@ public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) x.back(); break; case ']': + if (isInitial && jsonParserConfiguration.isStrictMode() && + x.nextClean() != 0) { + throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text"); + } return; default: throw x.syntaxError("Expected a ',' or ']'"); } } + } else { + if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) { + throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text"); + } } } @@ -176,11 +171,6 @@ public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) */ public JSONArray(String source) throws JSONException { this(source, new JSONParserConfiguration()); - // Strict mode does not allow trailing chars - if (this.jsonParserConfiguration.isStrictMode() && - this.jsonTokener.nextClean() != 0) { - throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text"); - } } /** @@ -195,12 +185,7 @@ public JSONArray(String source) throws JSONException { * If there is a syntax error. */ public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException { - this(new JSONTokener(source), jsonParserConfiguration); - // Strict mode does not allow trailing chars - if (this.jsonParserConfiguration.isStrictMode() && - this.jsonTokener.nextClean() != 0) { - throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text"); - } + this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration); } /** diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 3bb6da7ed..7ec1b2cc6 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -152,12 +152,6 @@ public Class getMapType() { */ public static final Object NULL = new Null(); - // strict mode checks after constructor require access to this object - private JSONTokener jsonTokener; - - // strict mode checks after constructor require access to this object - private JSONParserConfiguration jsonParserConfiguration; - /** * Construct an empty JSONObject. */ @@ -217,18 +211,11 @@ public JSONObject(JSONTokener x) throws JSONException { */ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { this(); - - if (this.jsonParserConfiguration == null) { - this.jsonParserConfiguration = jsonParserConfiguration; - } - if (this.jsonTokener == null) { - this.jsonTokener = x; - this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration); - } - char c; String key; + boolean isInitial = x.getPrevious() == 0; + if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); } @@ -238,6 +225,9 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration case 0: throw x.syntaxError("A JSONObject text must end with '}'"); case '}': + if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) { + throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text"); + } return; default: key = x.nextSimpleValue(c).toString(); @@ -288,6 +278,9 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration x.back(); break; case '}': + if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) { + throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text"); + } return; default: throw x.syntaxError("Expected a ',' or '}'"); @@ -456,11 +449,6 @@ public JSONObject(Object object, String ... names) { */ public JSONObject(String source) throws JSONException { this(source, new JSONParserConfiguration()); - // Strict mode does not allow trailing chars - if (this.jsonParserConfiguration.isStrictMode() && - this.jsonTokener.nextClean() != 0) { - throw new JSONException("Strict mode error: Unparsed characters found at end of input text"); - } } /** @@ -478,12 +466,7 @@ public JSONObject(String source) throws JSONException { * duplicated key. */ public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException { - this(new JSONTokener(source), jsonParserConfiguration); - // Strict mode does not allow trailing chars - if (this.jsonParserConfiguration.isStrictMode() && - this.jsonTokener.nextClean() != 0) { - throw new JSONException("Strict mode error: Unparsed characters found at end of input text"); - } + this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration); } /** @@ -1280,7 +1263,7 @@ public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { return objectToBigDecimal(val, defaultValue, true); } - + /** * @param val value to convert * @param defaultValue default value to return is the conversion doesn't work or is null. diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java index d4c780e86..c17907cf9 100644 --- a/src/main/java/org/json/JSONTokener.java +++ b/src/main/java/org/json/JSONTokener.java @@ -38,9 +38,21 @@ public class JSONTokener { /** * Construct a JSONTokener from a Reader. The caller must close the Reader. * - * @param reader A reader. + * @param reader the source. */ public JSONTokener(Reader reader) { + this(reader, new JSONParserConfiguration()); + } + + /** + * Construct a JSONTokener from a Reader with a given JSONParserConfiguration. The caller must close the Reader. + * + * @param reader the source. + * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser. + * + */ + public JSONTokener(Reader reader, JSONParserConfiguration jsonParserConfiguration) { + this.jsonParserConfiguration = jsonParserConfiguration; this.reader = reader.markSupported() ? reader : new BufferedReader(reader); @@ -53,23 +65,40 @@ public JSONTokener(Reader reader) { this.line = 1; } - /** * Construct a JSONTokener from an InputStream. The caller must close the input stream. * @param inputStream The source. */ public JSONTokener(InputStream inputStream) { - this(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); + this(inputStream, new JSONParserConfiguration()); + } + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param inputStream The source. + * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser. + */ + public JSONTokener(InputStream inputStream, JSONParserConfiguration jsonParserConfiguration) { + this(new InputStreamReader(inputStream, Charset.forName("UTF-8")), jsonParserConfiguration); } /** * Construct a JSONTokener from a string. * - * @param s A source string. + * @param source A source string. + */ + public JSONTokener(String source) { + this(new StringReader(source)); + } + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param source The source. + * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser. */ - public JSONTokener(String s) { - this(new StringReader(s)); + public JSONTokener(String source, JSONParserConfiguration jsonParserConfiguration) { + this(new StringReader(source), jsonParserConfiguration); } /** diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java index 422c90c34..006f2a41d 100644 --- a/src/test/java/org/json/junit/JSONParserConfigurationTest.java +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -4,6 +4,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.json.JSONParserConfiguration; +import org.json.JSONTokener; import org.junit.Test; import java.io.IOException; @@ -490,6 +491,40 @@ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowKeyNotSurround je.getMessage()); } + @Test + public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingJSONTokener_shouldThrowJSONException() { + JSONException exception = assertThrows(JSONException.class, () -> { + new JSONObject(new JSONTokener("{\"key\":\"value\"} invalid trailing text"), new JSONParserConfiguration().withStrictMode(true)); + }); + + assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage()); + } + + @Test + public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingString_shouldThrowJSONException() { + JSONException exception = assertThrows(JSONException.class, () -> { + new JSONObject("{\"key\":\"value\"} invalid trailing text", new JSONParserConfiguration().withStrictMode(true)); + }); + assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage()); + } + + @Test + public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingJSONTokener_shouldThrowJSONException() { + JSONException exception = assertThrows(JSONException.class, () -> { + new JSONArray(new JSONTokener("[\"value\"] invalid trailing text"), new JSONParserConfiguration().withStrictMode(true)); + }); + + assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage()); + } + + @Test + public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingString_shouldThrowJSONException() { + JSONException exception = assertThrows(JSONException.class, () -> { + new JSONArray("[\"value\"] invalid trailing text", new JSONParserConfiguration().withStrictMode(true)); + }); + assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage()); + } + /** * This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in * this class. diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java index 59ca6d8f6..c436d27e2 100644 --- a/src/test/java/org/json/junit/JSONTokenerTest.java +++ b/src/test/java/org/json/junit/JSONTokenerTest.java @@ -325,4 +325,21 @@ public void testAutoClose(){ assertEquals("Stream closed", exception.getMessage()); } } + + @Test + public void testInvalidInput_JSONObject_withoutStrictModel_shouldParseInput() { + String input = "{\"invalidInput\": [],}"; + JSONTokener tokener = new JSONTokener(input); + Object value = tokener.nextValue(); + assertEquals(new JSONObject(input).toString(), value.toString()); + } + + @Test + public void testInvalidInput_JSONArray_withoutStrictModel_shouldParseInput() { + String input = "[\"invalidInput\",]"; + JSONTokener tokener = new JSONTokener(input); + Object value = tokener.nextValue(); + assertEquals(new JSONArray(input).toString(), value.toString()); + } + }