Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow nested objects and arrays as keys in objects #772

Merged
merged 4 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 4 additions & 12 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,22 +208,14 @@ public JSONObject(JSONTokener x) throws JSONException {
throw x.syntaxError("A JSONObject text must begin with '{'");
}
for (;;) {
char prev = x.getPrevious();
c = x.nextClean();
switch (c) {
case 0:
throw x.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
case '{':
case '[':
if(prev=='{') {
throw x.syntaxError("A JSON Object can not directly nest another JSON Object or JSON Array.");
}
// fall through
default:
x.back();
key = x.nextValue().toString();
key = x.nextSimpleValue(c).toString();
}

// The key is followed by ':'.
Expand Down Expand Up @@ -1712,12 +1704,12 @@ && isValidMethodName(method.getName())) {
final Object result = method.invoke(bean);
if (result != null) {
// check cyclic dependency and throw error if needed
// the wrap and populateMap combination method is
// the wrap and populateMap combination method is
// itself DFS recursive
if (objectsRecord.contains(result)) {
throw recursivelyDefinedObjectException(key);
}

objectsRecord.add(result);

this.map.put(key, wrap(result, objectsRecord));
Expand All @@ -1726,7 +1718,7 @@ && isValidMethodName(method.getName())) {

// we don't use the result anywhere outside of wrap
// if it's a resource we should be sure to close it
// after calling toString
// after calling toString
if (result instanceof Closeable) {
try {
((Closeable) result).close();
Expand Down
16 changes: 11 additions & 5 deletions src/main/java/org/json/JSONTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -402,12 +402,7 @@ public String nextTo(String delimiters) throws JSONException {
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
String string;

switch (c) {
case '"':
case '\'':
return this.nextString(c);
case '{':
this.back();
try {
Expand All @@ -423,6 +418,17 @@ public Object nextValue() throws JSONException {
throw new JSONException("JSON Array or Object depth too large to process.", e);
}
}
return nextSimpleValue(c);
}

Object nextSimpleValue(char c) {
String string;

switch (c) {
case '"':
case '\'':
return this.nextString(c);
}

/*
* Handle unquoted text. This could be the values true, false, or
Expand Down
16 changes: 15 additions & 1 deletion src/test/java/org/json/junit/JSONArrayTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void nullException() {
* Expects a JSONException.
*/
@Test
public void emptStr() {
public void emptyStr() {
String str = "";
try {
assertNull("Should throw an exception", new JSONArray(str));
Expand Down Expand Up @@ -460,6 +460,20 @@ public void failedGetArrayValues() {
Util.checkJSONArrayMaps(jsonArray);
}

/**
* The JSON parser is permissive of unambiguous unquoted keys and values.
* Such JSON text should be allowed, even if it does not strictly conform
* to the spec. However, after being parsed, toString() should emit strictly
* conforming JSON text.
*/
@Test
public void unquotedText() {
String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
JSONArray jsonArray = new JSONArray(str);
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
assertEquals(expected, jsonArray.toList());
}

/**
* Exercise JSONArray.join() by converting a JSONArray into a
* comma-separated string. Since this is very nearly a JSON document,
Expand Down
42 changes: 41 additions & 1 deletion src/test/java/org/json/junit/JSONObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,17 @@ public void jsonObjectByNullBean() {
*/
@Test
public void unquotedText() {
String str = "{key1:value1, key2:42}";
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
JSONObject jsonObject = new JSONObject(str);
String textStr = jsonObject.toString();
assertTrue("expected key1", textStr.contains("\"key1\""));
assertTrue("expected value1", textStr.contains("\"value1\""));
assertTrue("expected key2", textStr.contains("\"key2\""));
assertTrue("expected 42", textStr.contains("42"));
assertTrue("expected 1.2", textStr.contains("\"1.2\""));
assertTrue("expected 3.4", textStr.contains("3.4"));
assertTrue("expected -7E+5", textStr.contains("\"-7E+5\""));
assertTrue("expected something!", textStr.contains("\"something!\""));
Util.checkJSONObjectMaps(jsonObject);
}

Expand Down Expand Up @@ -2224,6 +2228,42 @@ public void jsonObjectParsingErrors() {
"Expected a ',' or '}' at 15 [character 16 line 1]",
e.getMessage());
}
try {
// key is a nested map
String str = "{{\"foo\": \"bar\"}: \"baz\"}";
assertNull("Expected an exception",new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 1 [character 2 line 1]",
e.getMessage());
}
try {
// key is a nested array containing a map
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
assertNull("Expected an exception",new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 9 [character 10 line 1]",
e.getMessage());
}
try {
// key contains }
String str = "{foo}: 2}";
assertNull("Expected an exception",new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Expected a ':' after a key at 5 [character 6 line 1]",
e.getMessage());
}
try {
// key contains ]
String str = "{foo]: 2}";
assertNull("Expected an exception",new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Expected a ':' after a key at 5 [character 6 line 1]",
e.getMessage());
}
try {
// \0 after ,
String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
Expand Down