blob: ded2c20d7f56d69a5ff1198f4c080542c8fb274b [file] [log] [blame]
// Copyright 2013 The Flutter 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 "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
#include "rapidjson/reader.h"
#include "rapidjson/writer.h"
#include <gmodule.h>
G_DEFINE_QUARK(fl_json_message_codec_error_quark, fl_json_message_codec_error)
struct _FlJsonMessageCodec {
FlMessageCodec parent_instance;
};
G_DEFINE_TYPE(FlJsonMessageCodec,
fl_json_message_codec,
fl_message_codec_get_type())
// Recursively writes #FlValue objects using rapidjson
static gboolean write_value(rapidjson::Writer<rapidjson::StringBuffer>& writer,
FlValue* value,
GError** error) {
if (value == nullptr) {
writer.Null();
return TRUE;
}
switch (fl_value_get_type(value)) {
case FL_VALUE_TYPE_NULL:
writer.Null();
break;
case FL_VALUE_TYPE_BOOL:
writer.Bool(fl_value_get_bool(value));
break;
case FL_VALUE_TYPE_INT:
writer.Int64(fl_value_get_int(value));
break;
case FL_VALUE_TYPE_FLOAT:
writer.Double(fl_value_get_float(value));
break;
case FL_VALUE_TYPE_STRING:
writer.String(fl_value_get_string(value));
break;
case FL_VALUE_TYPE_UINT8_LIST: {
writer.StartArray();
const uint8_t* data = fl_value_get_uint8_list(value);
for (size_t i = 0; i < fl_value_get_length(value); i++)
writer.Int(data[i]);
writer.EndArray();
break;
}
case FL_VALUE_TYPE_INT32_LIST: {
writer.StartArray();
const int32_t* data = fl_value_get_int32_list(value);
for (size_t i = 0; i < fl_value_get_length(value); i++)
writer.Int(data[i]);
writer.EndArray();
break;
}
case FL_VALUE_TYPE_INT64_LIST: {
writer.StartArray();
const int64_t* data = fl_value_get_int64_list(value);
for (size_t i = 0; i < fl_value_get_length(value); i++)
writer.Int64(data[i]);
writer.EndArray();
break;
}
case FL_VALUE_TYPE_FLOAT_LIST: {
writer.StartArray();
const double* data = fl_value_get_float_list(value);
for (size_t i = 0; i < fl_value_get_length(value); i++)
writer.Double(data[i]);
writer.EndArray();
break;
}
case FL_VALUE_TYPE_LIST: {
writer.StartArray();
for (size_t i = 0; i < fl_value_get_length(value); i++)
if (!write_value(writer, fl_value_get_list_value(value, i), error))
return FALSE;
writer.EndArray();
break;
}
case FL_VALUE_TYPE_MAP: {
writer.StartObject();
for (size_t i = 0; i < fl_value_get_length(value); i++) {
FlValue* key = fl_value_get_map_key(value, i);
if (fl_value_get_type(key) != FL_VALUE_TYPE_STRING) {
g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE,
"Invalid object key type");
return FALSE;
}
writer.Key(fl_value_get_string(key));
if (!write_value(writer, fl_value_get_map_value(value, i), error))
return FALSE;
}
writer.EndObject();
break;
}
default:
g_set_error(error, FL_MESSAGE_CODEC_ERROR,
FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE,
"Unexpected FlValue type %d", fl_value_get_type(value));
return FALSE;
}
return TRUE;
}
// Handler to parse JSON using rapidjson in SAX mode
struct FlValueHandler {
GPtrArray* stack;
FlValue* key;
GError* error;
FlValueHandler() {
stack = g_ptr_array_new_with_free_func(
reinterpret_cast<GDestroyNotify>(fl_value_unref));
key = nullptr;
error = nullptr;
}
~FlValueHandler() {
g_ptr_array_unref(stack);
if (key != nullptr)
fl_value_unref(key);
if (error != nullptr)
g_error_free(error);
}
// Gets the current head of the stack
FlValue* get_head() {
if (stack->len == 0)
return nullptr;
return static_cast<FlValue*>(g_ptr_array_index(stack, stack->len - 1));
}
// Pushes a value onto the stack
void push(FlValue* value) { g_ptr_array_add(stack, fl_value_ref(value)); }
// Pops the stack
void pop() { g_ptr_array_remove_index(stack, stack->len - 1); }
// Adds a new value to the stack
bool add(FlValue* value) {
g_autoptr(FlValue) owned_value = value;
FlValue* head = get_head();
if (head == nullptr)
push(owned_value);
else if (fl_value_get_type(head) == FL_VALUE_TYPE_LIST)
fl_value_append(head, owned_value);
else if (fl_value_get_type(head) == FL_VALUE_TYPE_MAP) {
fl_value_set_take(head, key, fl_value_ref(owned_value));
key = nullptr;
} else {
g_set_error(&error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
"Can't add value to non container");
return false;
}
if (fl_value_get_type(owned_value) == FL_VALUE_TYPE_LIST ||
fl_value_get_type(owned_value) == FL_VALUE_TYPE_MAP)
push(value);
return true;
}
// The following implements the rapidjson SAX API
bool Null() { return add(fl_value_new_null()); }
bool Bool(bool b) { return add(fl_value_new_bool(b)); }
bool Int(int i) { return add(fl_value_new_int(i)); }
bool Uint(unsigned i) { return add(fl_value_new_int(i)); }
bool Int64(int64_t i) { return add(fl_value_new_int(i)); }
bool Uint64(uint64_t i) {
// For some reason (bug in rapidjson?) this is not returned in Int64
if (i == G_MAXINT64)
return add(fl_value_new_int(i));
else
return add(fl_value_new_float(i));
}
bool Double(double d) { return add(fl_value_new_float(d)); }
bool RawNumber(const char* str, rapidjson::SizeType length, bool copy) {
g_set_error(&error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
"RawNumber not supported");
return false;
}
bool String(const char* str, rapidjson::SizeType length, bool copy) {
FlValue* v = fl_value_new_string_sized(str, length);
return add(v);
}
bool StartObject() { return add(fl_value_new_map()); }
bool Key(const char* str, rapidjson::SizeType length, bool copy) {
if (key != nullptr)
fl_value_unref(key);
key = fl_value_new_string_sized(str, length);
return true;
}
bool EndObject(rapidjson::SizeType memberCount) {
pop();
return true;
}
bool StartArray() { return add(fl_value_new_list()); }
bool EndArray(rapidjson::SizeType elementCount) {
pop();
return true;
}
};
// Implements FlMessageCodec:encode_message
static GBytes* fl_json_message_codec_encode_message(FlMessageCodec* codec,
FlValue* message,
GError** error) {
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
if (!write_value(writer, message, error))
return nullptr;
const gchar* text = buffer.GetString();
return g_bytes_new(text, strlen(text));
}
// Implements FlMessageCodec:decode_message
static FlValue* fl_json_message_codec_decode_message(FlMessageCodec* codec,
GBytes* message,
GError** error) {
gsize data_length;
const gchar* data =
static_cast<const char*>(g_bytes_get_data(message, &data_length));
if (!g_utf8_validate(data, data_length, nullptr)) {
g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8,
"Message is not valid UTF8");
return nullptr;
}
FlValueHandler handler;
rapidjson::Reader reader;
rapidjson::MemoryStream ss(data, data_length);
if (!reader.Parse(ss, handler)) {
if (handler.error != nullptr) {
g_propagate_error(error, handler.error);
handler.error = nullptr;
} else
g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON,
"Message is not valid JSON");
return nullptr;
}
FlValue* value = handler.get_head();
if (value == nullptr) {
g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON,
"Message is not valid JSON");
return nullptr;
}
return fl_value_ref(value);
}
static void fl_json_message_codec_class_init(FlJsonMessageCodecClass* klass) {
FL_MESSAGE_CODEC_CLASS(klass)->encode_message =
fl_json_message_codec_encode_message;
FL_MESSAGE_CODEC_CLASS(klass)->decode_message =
fl_json_message_codec_decode_message;
}
static void fl_json_message_codec_init(FlJsonMessageCodec* self) {}
G_MODULE_EXPORT FlJsonMessageCodec* fl_json_message_codec_new() {
return static_cast<FlJsonMessageCodec*>(
g_object_new(fl_json_message_codec_get_type(), nullptr));
}
G_MODULE_EXPORT gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec,
FlValue* value,
GError** error) {
g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
if (!write_value(writer, value, error))
return nullptr;
return g_strdup(buffer.GetString());
}
G_MODULE_EXPORT FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec,
const gchar* text,
GError** error) {
g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr);
g_autoptr(GBytes) data = g_bytes_new_static(text, strlen(text));
g_autoptr(FlValue) value = fl_json_message_codec_decode_message(
FL_MESSAGE_CODEC(codec), data, error);
if (value == nullptr)
return nullptr;
return fl_value_ref(value);
}