BJson: Fixes, tweaks, and behavioral changes based on the JSON Minefield Tests.

As found on http://seriot.ch/parsing_json.php -- anything using the API
presently with valid JSON should have no troubles, but more valid JSON
that previously didn't work now does (e.g. JSON with root array nodes, not
root map nodes), and invalid JSON that silently succeeded before now fails.

Not all the bad cases from that testsuite now fail, and not all of the good
ones pass, but the few that remain are odd things that wouldn't map well to
the BMessage API (e.g. root string nodes, etc.) or are other behaviors that
make sense to leave as they are for compatibility reasons.
This commit is contained in:
Augustin Cavalier
2017-02-08 21:35:42 -05:00
parent 551b87cb9c
commit aae431375e
+79 -22
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2014, Augustin Cavalier (waddlesplash) * Copyright 2014-2017, Augustin Cavalier (waddlesplash)
* Copyright 2014, Stephan Aßmus <[email protected]> * Copyright 2014, Stephan Aßmus <[email protected]>
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -7,8 +7,9 @@
#include <Json.h> #include <Json.h>
#include <stdio.h> #include <cstdio>
#include <stdlib.h> #include <cstdlib>
#include <cerrno>
#include <MessageBuilder.h> #include <MessageBuilder.h>
#include <UnicodeChar.h> #include <UnicodeChar.h>
@@ -88,7 +89,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
{ {
BMessageBuilder builder(message); BMessageBuilder builder(message);
int32 pos = 0; int32 pos = 0;
int32 length = JSON.Length(); const int32 length = JSON.Length();
bool hadRootNode = false;
/* Locals used by the parser. */ /* Locals used by the parser. */
// Keeps track of the hierarchy (e.g. "{[{{") that has // Keeps track of the hierarchy (e.g. "{[{{") that has
@@ -117,18 +119,25 @@ BJson::_Parse(BMessage& message, BString& JSON)
builder.PushObject(key.String()); builder.PushObject(key.String());
key = ""; key = "";
} }
} } else if (hadRootNode == true) {
throw ParseException(pos,
"Got '{' with empty hierarchy but already had a root node");
} else
hadRootNode = true;
builder.SetWhat(JSON_TYPE_MAP); builder.SetWhat(JSON_TYPE_MAP);
break; break;
case '}': case '}':
if (key.Length() > 0)
throw ParseException(pos, "Got closebrace but still have a key");
if (hierarchy.EndsWith("{") && hierarchy.Length() != 1) { if (hierarchy.EndsWith("{") && hierarchy.Length() != 1) {
hierarchy.Truncate(hierarchy.Length() - 1); hierarchy.Truncate(hierarchy.Length() - 1);
builder.PopObject(); builder.PopObject();
} else if (hierarchy.Length() == 1) } else if (hierarchy.EndsWith("{") && hierarchy.Length() == 1) {
return; // End of the JSON data hierarchy.Truncate(hierarchy.Length() - 1);
else break; // Should be the end of the data.
} else
throw ParseException(pos, "Unmatched closebrace }"); throw ParseException(pos, "Unmatched closebrace }");
break; break;
@@ -136,30 +145,54 @@ BJson::_Parse(BMessage& message, BString& JSON)
case '[': case '[':
hierarchy += "["; hierarchy += "[";
if (hierarchy != "[") {
if (builder.What() == JSON_TYPE_ARRAY) if (builder.What() == JSON_TYPE_ARRAY)
builder.PushObject(builder.CountNames()); builder.PushObject(builder.CountNames());
else { else {
builder.PushObject(key.String()); builder.PushObject(key.String());
key = ""; key = "";
} }
} else if (hadRootNode == true) {
throw ParseException(pos,
"Got '[' with empty hierarchy but already had a root node");
} else
hadRootNode = true;
builder.SetWhat(JSON_TYPE_ARRAY); builder.SetWhat(JSON_TYPE_ARRAY);
break; break;
case ']': case ']':
if (hierarchy.EndsWith("[")) { if (hierarchy.EndsWith("[") && hierarchy.Length() != 1) {
hierarchy.Truncate(hierarchy.Length() - 1); hierarchy.Truncate(hierarchy.Length() - 1);
builder.PopObject(); builder.PopObject();
} else { } else if (hierarchy.EndsWith("[") && hierarchy.Length() == 1) {
BString error("Unmatched closebrace ] hierarchy: "); hierarchy.Truncate(hierarchy.Length() - 1);
error << hierarchy; break; // Should be the end of the data.
throw ParseException(pos, error); } else
} throw ParseException(pos, "Unmatched closebracket ]");
break; break;
case ':':
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got ':'");
if (builder.What() != JSON_TYPE_MAP || key.Length() == 0) {
throw ParseException(pos, "Unexpected ':'");
}
break;
case ',':
if (builder.What() == JSON_TYPE_MAP && key.Length() != 0) {
throw ParseException(pos, "Unexpected ',' expected ':'");
}
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got ','");
break;
case 't': case 't':
{ {
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got 't'");
if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) {
throw ParseException(pos, throw ParseException(pos,
"'true' cannot be a key, it can only be a value"); "'true' cannot be a key, it can only be a value");
@@ -178,6 +211,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
case 'f': case 'f':
{ {
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got 'f'");
if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) {
throw ParseException(pos, throw ParseException(pos,
"'false' cannot be a key, it can only be a value"); "'false' cannot be a key, it can only be a value");
@@ -196,6 +231,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
case 'n': case 'n':
{ {
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got 'n'");
if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) {
throw ParseException(pos, throw ParseException(pos,
"'null' cannot be a key, it can only be a value"); "'null' cannot be a key, it can only be a value");
@@ -213,6 +250,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
} }
case '"': case '"':
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got '\"'");
if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0)
key = _ParseString(JSON, pos); key = _ParseString(JSON, pos);
else if (builder.What() != JSON_TYPE_ARRAY && key.Length() > 0) { else if (builder.What() != JSON_TYPE_ARRAY && key.Length() > 0) {
@@ -240,6 +279,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
case '8': case '8':
case '9': case '9':
{ {
if (hierarchy.Length() == 0)
throw ParseException(pos, "Expected EOF, got number");
if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) {
throw ParseException(pos, throw ParseException(pos,
"Numbers cannot be keys, they can only be values"); "Numbers cannot be keys, they can only be values");
@@ -255,16 +296,21 @@ BJson::_Parse(BMessage& message, BString& JSON)
break; break;
} }
case ':': case ' ':
case ',': case '\t':
default: case '\n':
// No need to do anything here. case '\r':
// Whitespace; ignore.
break; break;
default:
throw ParseException(pos, "Unexpected character");
} }
pos++; pos++;
} }
throw ParseException(pos, "Unexpected end of document"); if (hierarchy.Length() != 0)
throw ParseException(pos, "Unexpected EOF");
} }
@@ -276,7 +322,7 @@ BJson::_ParseString(BString& JSON, int32& pos)
pos++; pos++;
BString str; BString str;
while (JSON[pos] != '"') { while (JSON[pos] != '"' && pos < JSON.Length()) {
if (JSON[pos] == '\\') { if (JSON[pos] == '\\') {
pos++; pos++;
switch (JSON[pos]) { switch (JSON[pos]) {
@@ -336,6 +382,7 @@ double
BJson::_ParseNumber(BString& JSON, int32& pos) BJson::_ParseNumber(BString& JSON, int32& pos)
{ {
BString value; BString value;
bool isDouble = false;
while (true) { while (true) {
switch (JSON[pos]) { switch (JSON[pos]) {
@@ -343,6 +390,9 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
case '-': case '-':
case 'e': case 'e':
case 'E': case 'E':
case '.':
isDouble = true;
// fall through
case '0': case '0':
case '1': case '1':
case '2': case '2':
@@ -353,7 +403,6 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
case '7': case '7':
case '8': case '8':
case '9': case '9':
case '.':
value += JSON[pos]; value += JSON[pos];
pos++; pos++;
continue; continue;
@@ -367,7 +416,15 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
break; break;
} }
return strtod(value.String(), NULL); errno = 0;
double ret = 0;
if (isDouble)
ret = strtod(value.String(), NULL);
else
ret = strtoll(value.String(), NULL, 10);
if (errno != 0)
throw ParseException(pos, "Invalid number!");
return ret;
} }