// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/json/json_reader.h"
#include "base/float_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
namespace base {
static const JSONReader::Token kInvalidToken(JSONReader::Token::INVALID_TOKEN,
0, 0);
static const int kStackLimit = 100;
namespace {
// A helper method for ParseNumberToken. It reads an int from the end of
// token. The method returns false if there is no valid integer at the end of
// the token.
bool ReadInt(JSONReader::Token& token, bool can_have_leading_zeros) {
wchar_t first = token.NextChar();
int len = 0;
// Read in more digits
wchar_t c = first;
while ('\0' != c && '0' <= c && c <= '9') {
++token.length;
++len;
c = token.NextChar();
}
// We need at least 1 digit.
if (len == 0)
return false;
if (!can_have_leading_zeros && len > 1 && '0' == first)
return false;
return true;
}
// A helper method for ParseStringToken. It reads |digits| hex digits from the
// token. If the sequence if digits is not valid (contains other characters),
// the method returns false.
bool ReadHexDigits(JSONReader::Token& token, int digits) {
for (int i = 1; i <= digits; ++i) {
wchar_t c = *(token.begin + token.length + i);
if ('\0' == c)
return false;
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F'))) {
return false;
}
}
token.length += digits;
return true;
}
} // anonymous namespace
const char* JSONReader::kBadRootElementType =
"Root value must be an array or object.";
const char* JSONReader::kInvalidEscape =
"Invalid escape sequence.";
const char* JSONReader::kSyntaxError =
"Syntax error.";
const char* JSONReader::kTrailingComma =
"Trailing comma not allowed.";
const char* JSONReader::kTooMuchNesting =
"Too much nesting.";
const char* JSONReader::kUnexpectedDataAfterRoot =
"Unexpected data after root element.";
const char* JSONReader::kUnsupportedEncoding =
"Unsupported encoding. JSON must be UTF-8.";
const char* JSONReader::kUnquotedDictionaryKey =
"Dictionary keys must be quoted.";
JSONReader::JSONReader()
: start_pos_(NULL), json_pos_(NULL), stack_depth_(0),
allow_trailing_comma_(false),
error_code_(JSON_NO_ERROR), error_line_(0), error_col_(0) {}
/* static */
Value* JSONReader::Read(const std::string& json,
bool allow_trailing_comma) {
return ReadAndReturnError(json, allow_trailing_comma, NULL, NULL);
}
/* static */
Value* JSONReader::ReadAndReturnError(const std::string& json,
bool allow_trailing_comma,
int* error_code_out,
std::string* error_msg_out) {
JSONReader reader = JSONReader();
Value* root = reader.JsonToValue(json, true, allow_trailing_comma);
if (root)
return root;
if (error_code_out)
*error_code_out = reader.error_code();
if (error_msg_out)
*error_msg_out = reader.GetErrorMessage();
return NULL;
}
/* static */
std::string JSONReader::ErrorCodeToString(JsonParseError error_code) {
switch (error_code) {
case JSON_NO_ERROR:
return std::string();
case JSON_BAD_ROOT_ELEMENT_TYPE:
return kBadRootElementType;
case JSON_INVALID_ESCAPE:
return kInvalidEscape;
case JSON_SYNTAX_ERROR:
return kSyntaxError;
case JSON_TRAILING_COMMA:
return kTrailingComma;
case JSON_TOO_MUCH_NESTING:
return kTooMuchNesting;
case JSON_UNEXPECTED_DATA_AFTER_ROOT:
return kUnexpectedDataAfterRoot;
case JSON_UNSUPPORTED_ENCODING:
return kUnsupportedEncoding;
case JSON_UNQUOTED_DICTIONARY_KEY:
return kUnquotedDictionaryKey;
default:
NOTREACHED();
return std::string();
}
}
std::string JSONReader::GetErrorMessage() const {
return FormatErrorMessage(error_line_, error_col_,
ErrorCodeToString(error_code_));
}
Value* JSONReader::JsonToValue(const std::string& json, bool check_root,
bool allow_trailing_comma) {
// The input must be in UTF-8.
if (!IsStringUTF8(json.c_str())) {
error_code_ = JSON_UNSUPPORTED_ENCODING;
return NULL;
}
// The conversion from UTF8 to wstring removes null bytes for us
// (a good thing).
std::wstring json_wide(UTF8ToWide(json));
start_pos_ = json_wide.c_str();
// When the input JSON string starts with a UTF-8 Byte-Order-Mark
// (0xEF, 0xBB, 0xBF), the UTF8ToWide() function converts it to a Unicode
// BOM (U+FEFF). To avoid the JSONReader::BuildValue() function from
// mis-treating a Unicode BOM as an invalid character and returning NULL,
// skip a converted Unicode BOM if it exists.
if (!json_wide.empty() && start_pos_[0] == 0xFEFF) {
++start_pos_;
}
json_pos_ = start_pos_;
allow_trailing_comma_ = allow_trailing_comma;
stack_depth_ = 0;
error_code_ = JSON_NO_ERROR;
scoped_ptr<Value> root(BuildValue(check_root));
if (root.get()) {
if (ParseToken().type == Token::END_OF_INPUT) {
return root.release();
} else {
SetErrorCode(JSON_UNEXPECTED_DATA_AFTER_ROOT, json_pos_);
}
}
// Default to calling errors "syntax errors".
if (error_code_ == 0)
SetErrorCode(JSON_SYNTAX_ERROR, json_pos_);
return NULL;
}
/* static */
std::string JSONReader::FormatErrorMessage(int line, int column,
const std::string& description) {
if (line || column) {
return StringPrintf("Line: %i, column: %i, %s",
line, column, description.c_str());
}
return description;
}
Value* JSONReader::BuildValue(bool is_root) {
++stack_depth_;
if (stack_depth_ > kStackLimit) {
SetErrorCode(JSON_TOO_MUCH_NESTING, json_pos_);
return NULL;
}
Token token = ParseToken();
// The root token must be an array or an object.
if (is_root && token.type != Token::OBJECT_BEGIN &&
token.type != Token::ARRAY_BEGIN) {
SetErrorCode(JSON_BAD_ROOT_ELEMENT_TYPE, json_pos_);
return NULL;
}
scoped_ptr<Value> node;
switch (token.type) {
case Token::END_OF_INPUT:
case Token::INVALID_TOKEN:
return NULL;
case Token::NULL_TOKEN:
node.reset(Value::CreateNullValue());
break;
case Token::BOOL_TRUE:
node.reset(Value::CreateBooleanValue(true));
break;
case Token::BOOL_FALSE:
node.reset(Value::CreateBooleanValue(false));
break;
case Token::NUMBER:
node.reset(DecodeNumber(token));
if (!node.get())
return NULL;
break;
case Token::STRING:
node.reset(DecodeString(token));
if (!node.get())
return NULL;
break;
case Token::ARRAY_BEGIN:
{
json_pos_ += token.length;
token = ParseToken();
node.reset(new ListValue());
while (token.type != Token::ARRAY_END) {
Value* array_node = BuildValue(false);
if (!array_node)
return NULL;
static_cast<ListValue*>(node.get())->Append(array_node);
// After a list value, we expect a comma or the end of the list.
token = ParseToken();
if (token.type == Token::LIST_SEPARATOR) {
json_pos_ += token.length;
token = ParseToken();
// Trailing commas are invalid according to the JSON RFC, but some
// consumers need the parsing leniency, so handle accordingly.
if (token.type == Token::ARRAY_END) {
if (!allow_trailing_comma_) {
SetErrorCode(JSON_TRAILING_COMMA, json_pos_);
return NULL;
}
// Trailing comma OK, stop parsing the Array.
break;
}
} else if (token.type != Token::ARRAY_END) {
// Unexpected value after list value. Bail out.
return NULL;
}
}
if (token.type != Token::ARRAY_END) {
return NULL;
}
break;
}
case Token::OBJECT_BEGIN:
{
json_pos_ += token.length;
token = ParseToken();
node.reset(new DictionaryValue);
while (token.type != Token::OBJECT_END) {
if (token.type != Token::STRING) {
SetErrorCode(JSON_UNQUOTED_DICTIONARY_KEY, json_pos_);
return NULL;
}
scoped_ptr<Value> dict_key_value(DecodeString(token));
if (!dict_key_value.get())
return NULL;
// Convert the key into a wstring.
std::string dict_key;
bool success = dict_key_value->GetAsString(&dict_key);
DCHECK(success);
json_pos_ += token.length;
token = ParseToken();
if (token.type != Token::OBJECT_PAIR_SEPARATOR)
return NULL;
json_pos_ += token.length;
token = ParseToken();
Value* dict_value = BuildValue(false);
if (!dict_value)
return NULL;
static_cast<DictionaryValue*>(node.get())->SetWithoutPathExpansion(
dict_key, dict_value);
// After a key/value pair, we expect a comma or the end of the
// object.
token = ParseToken();
if (token.type == Token::LIST_SEPARATOR) {
json_pos_ += token.length;
token = ParseToken();
// Trailing commas are invalid according to the JSON RFC, but some
// consumers need the parsing leniency, so handle accordingly.
if (token.type == Token::OBJECT_END) {
if (!allow_trailing_comma_) {
SetErrorCode(JSON_TRAILING_COMMA, json_pos_);
return NULL;
}
// Trailing comma OK, stop parsing the Object.
break;
}
} else if (token.type != Token::OBJECT_END) {
// Unexpected value after last object value. Bail out.
return NULL;
}
}
if (token.type != Token::OBJECT_END)
return NULL;
break;
}
default:
// We got a token that's not a value.
return NULL;
}
json_pos_ += token.length;
--stack_depth_;
return node.release();
}
JSONReader::Token JSONReader::ParseNumberToken() {
// We just grab the number here. We validate the size in DecodeNumber.
// According to RFC4627, a valid number is: [minus] int [frac] [exp]
Token token(Token::NUMBER, json_pos_, 0);
wchar_t c = *json_pos_;
if ('-' == c) {
++token.length;
c = token.NextChar();
}
if (!ReadInt(token, false))
return kInvalidToken;
// Optional fraction part
c = token.NextChar();
if ('.' == c) {
++token.length;
if (!ReadInt(token, true))
return kInvalidToken;
c = token.NextChar();
}
// Optional exponent part
if ('e' == c || 'E' == c) {
++token.length;
c = token.NextChar();
if ('-' == c || '+' == c) {
++token.length;
c = token.NextChar();
}
if (!ReadInt(token, true))
return kInvalidToken;
}
return token;
}
Value* JSONReader::DecodeNumber(const Token& token) {
const std::wstring num_string(token.begin, token.length);
int num_int;
if (StringToInt(WideToUTF8(num_string), &num_int))
return Value::CreateIntegerValue(num_int);
double num_double;
if (StringToDouble(WideToUTF8(num_string), &num_double) &&
base::IsFinite(num_double))
return Value::CreateDoubleValue(num_double);
return NULL;
}
JSONReader::Token JSONReader::ParseStringToken() {
Token token(Token::STRING, json_pos_, 1);
wchar_t c = token.NextChar();
while ('\0' != c) {
if ('\\' == c) {
++token.length;
c = token.NextChar();
// Make sure the escaped char is valid.
switch (c) {
case 'x':
if (!ReadHexDigits(token, 2)) {
SetErrorCode(JSON_INVALID_ESCAPE, json_pos_ + token.length);
return kInvalidToken;
}
break;
case 'u':
if (!ReadHexDigits(token, 4)) {
SetErrorCode(JSON_INVALID_ESCAPE, json_pos_ + token.length);
return kInvalidToken;
}
break;
case '\\':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '"':
break;
default:
SetErrorCode(JSON_INVALID_ESCAPE, json_pos_ + token.length);
return kInvalidToken;
}
} else if ('"' == c) {
++token.length;
return token;
}
++token.length;
c = token.NextChar();
}
return kInvalidToken;
}
Value* JSONReader::DecodeString(const Token& token) {
std::wstring decoded_str;
decoded_str.reserve(token.length - 2);
for (int i = 1; i < token.length - 1; ++i) {
wchar_t c = *(token.begin + i);
if ('\\' == c) {
++i;
c = *(token.begin + i);
switch (c) {
case '"':
case '/':
case '\\':
decoded_str.push_back(c);
break;
case 'b':
decoded_str.push_back('\b');
break;
case 'f':
decoded_str.push_back('\f');
break;
case 'n':
decoded_str.push_back('\n');
break;
case 'r':
decoded_str.push_back('\r');
break;
case 't':
decoded_str.push_back('\t');
break;
case 'v':
decoded_str.push_back('\v');
break;
case 'x':
decoded_str.push_back((HexDigitToInt(*(token.begin + i + 1)) << 4) +
HexDigitToInt(*(token.begin + i + 2)));
i += 2;
break;
case 'u':
decoded_str.push_back((HexDigitToInt(*(token.begin + i + 1)) << 12 ) +
(HexDigitToInt(*(token.begin + i + 2)) << 8) +
(HexDigitToInt(*(token.begin + i + 3)) << 4) +
HexDigitToInt(*(token.begin + i + 4)));
i += 4;
break;
default:
// We should only have valid strings at this point. If not,
// ParseStringToken didn't do it's job.
NOTREACHED();
return NULL;
}
} else {
// Not escaped
decoded_str.push_back(c);
}
}
return Value::CreateStringValue(WideToUTF16Hack(decoded_str));
}
JSONReader::Token JSONReader::ParseToken() {
static const std::wstring kNullString(L"null");
static const std::wstring kTrueString(L"true");
static const std::wstring kFalseString(L"false");
EatWhitespaceAndComments();
Token token(Token::INVALID_TOKEN, 0, 0);
switch (*json_pos_) {
case '\0':
token.type = Token::END_OF_INPUT;
break;
case 'n':
if (NextStringMatch(kNullString))
token = Token(Token::NULL_TOKEN, json_pos_, 4);
break;
case 't':
if (NextStringMatch(kTrueString))
token = Token(Token::BOOL_TRUE, json_pos_, 4);
break;
case 'f':
if (NextStringMatch(kFalseString))
token = Token(Token::BOOL_FALSE, json_pos_, 5);
break;
case '[':
token = Token(Token::ARRAY_BEGIN, json_pos_, 1);
break;
case ']':
token = Token(Token::ARRAY_END, json_pos_, 1);
break;
case ',':
token = Token(Token::LIST_SEPARATOR, json_pos_, 1);
break;
case '{':
token = Token(Token::OBJECT_BEGIN, json_pos_, 1);
break;
case '}':
token = Token(Token::OBJECT_END, json_pos_, 1);
break;
case ':':
token = Token(Token::OBJECT_PAIR_SEPARATOR, json_pos_, 1);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
token = ParseNumberToken();
break;
case '"':
token = ParseStringToken();
break;
}
return token;
}
void JSONReader::EatWhitespaceAndComments() {
while ('\0' != *json_pos_) {
switch (*json_pos_) {
case ' ':
case '\n':
case '\r':
case '\t':
++json_pos_;
break;
case '/':
// TODO(tc): This isn't in the RFC so it should be a parser flag.
if (!EatComment())
return;
break;
default:
// Not a whitespace char, just exit.
return;
}
}
}
bool JSONReader::EatComment() {
if ('/' != *json_pos_)
return false;
wchar_t next_char = *(json_pos_ + 1);
if ('/' == next_char) {
// Line comment, read until \n or \r
json_pos_ += 2;
while ('\0' != *json_pos_) {
switch (*json_pos_) {
case '\n':
case '\r':
++json_pos_;
return true;
default:
++json_pos_;
}
}
} else if ('*' == next_char) {
// Block comment, read until */
json_pos_ += 2;
while ('\0' != *json_pos_) {
if ('*' == *json_pos_ && '/' == *(json_pos_ + 1)) {
json_pos_ += 2;
return true;
}
++json_pos_;
}
} else {
return false;
}
return true;
}
bool JSONReader::NextStringMatch(const std::wstring& str) {
for (size_t i = 0; i < str.length(); ++i) {
if ('\0' == *json_pos_)
return false;
if (*(json_pos_ + i) != str[i])
return false;
}
return true;
}
void JSONReader::SetErrorCode(JsonParseError error,
const wchar_t* error_pos) {
int line_number = 1;
int column_number = 1;
// Figure out the line and column the error occured at.
for (const wchar_t* pos = start_pos_; pos != error_pos; ++pos) {
if (*pos == '\0') {
NOTREACHED();
return;
}
if (*pos == '\n') {
++line_number;
column_number = 1;
} else {
++column_number;
}
}
error_line_ = line_number;
error_col_ = column_number;
error_code_ = error;
}
} // namespace base