blob: e5fdbae24e66a78d91a7f88a3db68551e93d1d6b [file] [log] [blame]
// Copyright 2018 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 "json.h"
#include <array>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "cbor.h"
#include "parser_handler.h"
#include "span.h"
#include "status.h"
#include "test_platform.h"
namespace crdtp {
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
void WriteUTF8AsUTF16(ParserHandler* writer, const std::string& utf8) {
writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
}
TEST(JsonEncoder, OverlongEncodings) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
// We encode 0x7f, which is the DEL ascii character, as a 4 byte UTF8
// sequence. This is called an overlong encoding, because only 1 byte
// is needed to represent 0x7f as UTF8.
std::vector<uint8_t> chars = {
0xf0, // Starts 4 byte utf8 sequence
0x80, // continuation byte
0x81, // continuation byte w/ payload bit 7 set to 1.
0xbf, // continuation byte w/ payload bits 0-6 set to 11111.
};
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("\"\"", out); // Empty string means that 0x7f was rejected (good).
}
TEST(JsonEncoder, IncompleteUtf8Sequence) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleArrayBegin(); // This emits [, which starts an array.
{ // 🌎 takes four bytes to encode in UTF-8. We test with the first three;
// This means we're trying to emit a string that consists solely of an
// incomplete UTF-8 sequence. So the string in the JSON output is empty.
std::string world_utf8 = "🌎";
ASSERT_EQ(4u, world_utf8.size());
std::vector<uint8_t> chars(world_utf8.begin(), world_utf8.begin() + 3);
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\"", out); // Incomplete sequence rejected: empty string.
}
{ // This time, the incomplete sequence is at the end of the string.
std::string msg = "Hello, \xF0\x9F\x8C";
std::vector<uint8_t> chars(msg.begin(), msg.end());
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\",\"Hello, \"", out); // Incomplete sequence dropped at end.
}
}
TEST(JsonStdStringWriterTest, HelloWorld) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
std::string key = "msg1-as-utf8";
std::string value = "Hello, 🌎.";
writer->HandleString8(SpanFrom(key));
writer->HandleString8(SpanFrom(value));
WriteUTF8AsUTF16(writer.get(), "msg2");
WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
WriteUTF8AsUTF16(writer.get(), "nested");
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "double");
writer->HandleDouble(3.1415);
WriteUTF8AsUTF16(writer.get(), "int");
writer->HandleInt32(-42);
WriteUTF8AsUTF16(writer.get(), "bool");
writer->HandleBool(false);
WriteUTF8AsUTF16(writer.get(), "null");
writer->HandleNull();
writer->HandleMapEnd();
WriteUTF8AsUTF16(writer.get(), "array");
writer->HandleArrayBegin();
writer->HandleInt32(1);
writer->HandleInt32(2);
writer->HandleInt32(3);
writer->HandleArrayEnd();
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ(
"{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
"\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
"\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
"\"nested\":{\"double\":3.1415,\"int\":-42,"
"\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
out);
}
TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
// JSON can't represent +Infinity, -Infinity, or NaN.
// So in practice it's mapped to null.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
writer->HandleString8(SpanFrom("Infinity"));
writer->HandleDouble(std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("-Infinity"));
writer->HandleDouble(-std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("NaN"));
writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
}
TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
// The encoder emits binary submitted to ParserHandler::HandleBinary
// as base64. The following three examples are taken from
// https://en.wikipedia.org/wiki/Base64.
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWFu\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWE=\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TQ==\"", out);
}
{ // "Hello, world.", verified with base64decode.org.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
}
}
TEST(JsonStdStringWriterTest, HandlesErrors) {
// When an error is sent via HandleError, it saves it in the provided
// status and clears the output.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error);
EXPECT_EQ(42u, status.pos);
EXPECT_EQ("", out);
}
TEST(JsonStdStringWriterTest, DoubleToString_LeadingZero) {
// In JSON, .1 must be rendered as 0.1, and -.7 must be rendered as -0.7.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleArrayBegin();
writer->HandleDouble(.1);
writer->HandleDouble(-.7);
writer->HandleArrayEnd();
EXPECT_EQ("[0.1,-0.7]", out);
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
class Log : public ParserHandler {
public:
void HandleMapBegin() override { log_ << "map begin\n"; }
void HandleMapEnd() override { log_ << "map end\n"; }
void HandleArrayBegin() override { log_ << "array begin\n"; }
void HandleArrayEnd() override { log_ << "array end\n"; }
void HandleString8(span<uint8_t> chars) override {
log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
}
void HandleString16(span<uint16_t> chars) override {
log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
}
void HandleBinary(span<uint8_t> bytes) override {
// JSON doesn't have native support for arbitrary bytes, so our parser will
// never call this.
CHECK(false);
}
void HandleDouble(double value) override {
log_ << "double: " << value << "\n";
}
void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
void HandleNull() override { log_ << "null\n"; }
void HandleError(Status status) override { status_ = status; }
std::string str() const { return status_.ok() ? log_.str() : ""; }
Status status() const { return status_; }
private:
std::ostringstream log_;
Status status_;
};
class JsonParserTest : public ::testing::Test {
protected:
Log log_;
};
TEST_F(JsonParserTest, SimpleDictionary) {
std::string json = "{\"foo\": 42}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UsAsciiDelCornerCase) {
// DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control
// character according to Unicode, it's not considered a control
// character in https://tools.ietf.org/html/rfc7159#section-7, so
// it can be placed directly into the JSON string, without JSON escaping.
std::string json = "{\"foo\": \"a\x7f\"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"string16: a\x7f\n"
"map end\n",
log_.str());
// We've seen an implementation of UTF16ToUTF8 which would replace the DEL
// character with ' ', so this simple roundtrip tests the routines in
// encoding_test_helper.h, to make test failures of the above easier to
// diagnose.
std::vector<uint16_t> utf16 = UTF8ToUTF16(SpanFrom(json));
EXPECT_EQ(json, UTF16ToUTF8(SpanFrom(utf16)));
}
TEST_F(JsonParserTest, Whitespace) {
std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, world.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, NestedDictionary) {
std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: bar\n"
"map begin\n"
"string16: baz\n"
"int: 1\n"
"map end\n"
"string16: bar2\n"
"int: 2\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Doubles) {
std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"double: 3.1415\n"
"string16: bar\n"
"double: 3.1415\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode) {
// Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, 🌎.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf16) {
// Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf16 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::vector<uint16_t> json =
UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: space\n"
"string16: 🌎 πŸŒ™.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf8) {
// Used below:
// Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ - example for 2 byte utf8, Russian word "glasnost"
// 屋 - example for 3 byte utf8, Chinese word for "house"
// 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// πŸŒ™ - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf8 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::string json =
"{"
"\"escapes\": \"\\uD83C\\uDF19\","
"\"2 byte\":\"Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ\","
"\"3 byte\":\"屋\","
"\"4 byte\":\"🌎\""
"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: escapes\n"
"string16: πŸŒ™\n"
"string16: 2 byte\n"
"string16: Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ\n"
"string16: 3 byte\n"
"string16: 屋\n"
"string16: 4 byte\n"
"string16: 🌎\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
// Trailing junk after the valid JSON.
std::string json = "{\"foo\": 3.1415} junk";
size_t junk_idx = json.find("junk");
EXPECT_NE(junk_idx, std::string::npos);
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error);
EXPECT_EQ(junk_idx, log_.status().pos);
EXPECT_EQ("", log_.str());
}
std::string MakeNestedJson(int depth) {
std::string json;
for (int ii = 0; ii < depth; ++ii)
json += "{\"foo\":";
json += "42";
for (int ii = 0; ii < depth; ++ii)
json += "}";
return json;
}
TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
// kStackLimit is 300 (see json_parser.cc). First let's
// try with a small nested example.
std::string json_3 = MakeNestedJson(3);
ParseJSON(SpanFrom(json_3), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
// Now with kStackLimit (300).
std::string json_limit = MakeNestedJson(300);
ParseJSON(span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
json_limit.size()),
&log_);
EXPECT_TRUE(log_.status().ok());
}
TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
// Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
std::string exceeded = MakeNestedJson(301);
ParseJSON(SpanFrom(exceeded), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
// Now way past the limit. Still, the point of exceeding is 301.
std::string far_out = MakeNestedJson(320);
ParseJSON(SpanFrom(far_out), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, NoInputError) {
std::string json = "";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidTokenError) {
std::string json = "|";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidNumberError) {
// Mantissa exceeds max (the constant used here is int64_t max).
std::string json = "1E9223372036854775807";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidStringError) {
// \x22 is an unsupported escape sequence
std::string json = "\"foo\\x22\"";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedArrayEndError) {
std::string json = "[1,2,]";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
std::string json = "[1,2 2";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, StringLiteralExpectedError) {
// There's an error because the key bar, a string, is not terminated.
std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error);
EXPECT_EQ(16u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ColonExpectedError) {
std::string json = "{\"foo\", 42}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error);
EXPECT_EQ(6u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedMapEndError) {
std::string json = "{\"foo\": 42, }";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error);
EXPECT_EQ(12u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
// The second separator should be a comma.
std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error);
EXPECT_EQ(14u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ValueExpectedError) {
std::string json = "}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
template <typename T>
class ConvertJSONToCBORTest : public ::testing::Test {};
using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}";
TypeParam json(json_in.begin(), json_in.end());
TypeParam cbor;
{
Status status = ConvertJSONToCBOR(SpanFrom(json), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
EXPECT_EQ(json, roundtrip_json);
}
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
std::vector<uint16_t> json16 = {
'{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
'"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
TypeParam cbor;
{
Status status =
ConvertJSONToCBOR(span<uint16_t>(json16.data(), json16.size()), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
TypeParam expected_json(json.begin(), json.end());
EXPECT_EQ(expected_json, roundtrip_json);
}
} // namespace json
} // namespace crdtp