diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 642e96703..8be904b5d 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -223,6 +223,13 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration case 0: throw x.syntaxError("A JSONObject text must end with '}'"); case '}': + if (jsonParserConfiguration.isStrictMode()) { + c = x.nextClean(); + if (c != 0) { + throw x.syntaxError(String.format("Invalid char '%s' after '}'", c)); + } + x.back(); + } return; default: key = x.nextSimpleValue(c, jsonParserConfiguration).toString(); @@ -254,20 +261,27 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration // Pairs are separated by ','. switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + if (x.end()) { + throw x.syntaxError("A JSONObject text must end with '}'"); + } + x.back(); + break; + case '}': + if (jsonParserConfiguration.isStrictMode()) { + c = x.nextClean(); + if (c != 0 && c != '}' && c != ',' && c != ']') { + throw x.syntaxError(String.format("Invalid char '%s' after '}'", c)); + } + x.back(); + } return; - } - if (x.end()) { - throw x.syntaxError("A JSONObject text must end with '}'"); - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); + default: + throw x.syntaxError("Expected a ',' or '}'"); } } } diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java index 63effc5f7..e4d4e166b 100644 --- a/src/main/java/org/json/JSONTokener.java +++ b/src/main/java/org/json/JSONTokener.java @@ -334,63 +334,71 @@ public char nextClean() throws JSONException { * @return A String. * @throws JSONException Unterminated string or unbalanced quotes if strictMode == true. */ - public String nextString(char quote) throws JSONException { - char c; - StringBuilder sb = new StringBuilder(); - for (;;) { - c = this.next(); - switch (c) { - case 0: - case '\n': - case '\r': - throw this.syntaxError("Unterminated string. " + - "Character with int code " + (int) c + " is not allowed within a quoted string."); - case '\\': - c = this.next(); - switch (c) { - case 'b': - sb.append('\b'); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'f': - sb.append('\f'); - break; - case 'r': - sb.append('\r'); - break; - case 'u': - String next = this.next(4); - try { - sb.append((char) Integer.parseInt(next, 16)); - } catch (NumberFormatException e) { - throw this.syntaxError("Illegal escape. " + - "\\u must be followed by a 4 digit hexadecimal number. \\" + next - + " is not valid.", - e); - } - break; - case '"': - case '\'': - case '\\': - case '/': - sb.append(c); - break; - default: - throw this.syntaxError("Illegal escape. Escape sequence \\" + c + " is not valid."); - } - break; - default: - if (c == quote) { - return sb.toString(); - } - sb.append(c); + public Object nextString(char quote, boolean strictMode) throws JSONException { + if (strictMode && quote == '\'') { + throw this.syntaxError("Single quote wrap not allowed in strict mode"); + } + + if (quote == '"' || quote == '\'') { + char c; + StringBuilder sb = new StringBuilder(); + for (; ; ) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string. " + + "Character with int code " + (int) c + " is not allowed within a quoted string."); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + String next = this.next(4); + try { + sb.append((char) Integer.parseInt(next, 16)); + } catch (NumberFormatException e) { + throw this.syntaxError("Illegal escape. " + + "\\u must be followed by a 4 digit hexadecimal number. \\" + next + + " is not valid.", + e); + } + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape. Escape sequence \\" + c + " is not valid."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } } } + + return parsedUnquotedText(quote, strictMode); } /** @@ -528,15 +536,7 @@ private JSONArray getJsonArray() { Object nextSimpleValue(char c, JSONParserConfiguration jsonParserConfiguration) { boolean strictMode = jsonParserConfiguration.isStrictMode(); - if (strictMode && c == '\'') { - throw this.syntaxError("Single quote wrap not allowed in strict mode"); - } - - if (c == '"' || c == '\'') { - return this.nextString(c); - } - - return parsedUnquotedText(c, strictMode); + return this.nextString(c, strictMode); } /** diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java index 427aad4df..e56df0f57 100644 --- a/src/test/java/org/json/junit/JSONParserConfigurationTest.java +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -1,12 +1,8 @@ package org.json.junit; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -46,6 +42,48 @@ public void givenInvalidInputArrays_testStrictModeTrue_shouldThrowJsonException( () -> new JSONArray(testCase, jsonParserConfiguration))); } + @Test + public void givenInvalidJsonObjects_testStrictModeTrue_shouldThrowJsonException() { + final List jsonObjects = Arrays.asList( + "{}}", + "{}abc", + "{{}" + ); + + jsonObjects.forEach(jsonObject -> + assertThrows(JSONException.class, + () -> new JSONObject(jsonObject, new JSONParserConfiguration().withStrictMode(true)))); + } + + @Test + public void givenValidArraysWithInvalidJsonObjects_testStrictMode_shouldThrowJsonException() { + final List jsonArrays = Arrays.asList( + "[1, {}}]", + "[1, 2, 3, \"value\", true]abc", + "[{}, {\"key\":\"value\"}abc]" + ); + + jsonArrays.forEach(jsonArray -> + assertThrows(JSONException.class, + () -> new JSONArray(jsonArray, new JSONParserConfiguration().withStrictMode(true)))); + } + + @Test + public void givenValidJsonObjects_testStrictModeTrue() { + final List jsonPaths = Arrays.asList( + "src/test/resources/Issue884-validJsonObj.json" + ); + + jsonPaths.forEach(path -> { + final String resultJsonAsString = Util.getJsonStringFromFilePath(Paths.get(path)); + + final JSONObject resultJsonObject = new JSONObject(resultJsonAsString, + new JSONParserConfiguration().withStrictMode(true)); + + assertEquals(resultJsonAsString.replaceAll("\\s", "").length(), resultJsonObject.toString().length()); + }); + } + @Test public void givenEmptyArray_testStrictModeTrue_shouldNotThrowJsonException() { JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() @@ -126,15 +164,13 @@ public void shouldHandleNumericArray() { } @Test - public void givenCompliantJSONArrayFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException { - try (Stream lines = Files.lines(Paths.get("src/test/resources/compliantJsonArray.json"))) { - String compliantJsonArrayAsString = lines.collect(Collectors.joining()); - JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() - .withStrictMode(true); - - new JSONArray(compliantJsonArrayAsString, jsonParserConfiguration); - } + public void givenCompliantJSONArrayFile_testStrictModeTrue_shouldNotThrowAnyException() { + final String compliantJsonArrayAsString = Util.getJsonStringFromFilePath( + Paths.get("src/test/resources/compliantJsonArray.json")); + final JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() + .withStrictMode(true); + new JSONArray(compliantJsonArrayAsString, jsonParserConfiguration); } @Test diff --git a/src/test/java/org/json/junit/Util.java b/src/test/java/org/json/junit/Util.java index b676045b8..ff81867a1 100644 --- a/src/test/java/org/json/junit/Util.java +++ b/src/test/java/org/json/junit/Util.java @@ -6,8 +6,13 @@ import static org.junit.Assert.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.json.*; /** @@ -17,6 +22,14 @@ */ public class Util { + public static String getJsonStringFromFilePath(final Path path) { + try (final Stream lines = Files.lines(path)) { + return lines.collect(Collectors.joining()); + } catch (final IOException ioe) { + throw new IllegalStateException(String.format("Unable to retrieve path: %s", path)); + } + } + /** * Compares two JSONArrays for equality. * The arrays need not be in the same order. @@ -36,7 +49,7 @@ public static void compareActualVsExpectedJsonArrays(JSONArray jsonArray, /** * Compares two JSONObjects for equality. The objects need not be - * in the same order + * in the same order * @param jsonObject created by the code to be tested * @param expectedJsonObject created specifically for comparing */ @@ -141,7 +154,7 @@ public static void checkJSONObjectMaps(JSONObject jsonObject, Class jsonArrays) { diff --git a/src/test/resources/Issue884-validJsonObj.json b/src/test/resources/Issue884-validJsonObj.json new file mode 100644 index 000000000..3c08c3e5b --- /dev/null +++ b/src/test/resources/Issue884-validJsonObj.json @@ -0,0 +1,14 @@ +{ + "test": { + "key": "value", + "key2": "value", + "key3": { + "anotherNested": "obj", + "anArray": [ + "val1", + 1, + 3 + ] + } + } +} \ No newline at end of file