aboutsummaryrefslogtreecommitdiffstats
path: root/wsutil/json_dumper.c
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2018-11-20 02:47:36 +0100
committerAnders Broman <a.broman58@gmail.com>2018-11-20 05:03:56 +0000
commit656cc19fc7de25c3ac7f9d847c37745ccc272247 (patch)
tree79de668a68c25fd5bfe8ac2ee36d5b18824fa653 /wsutil/json_dumper.c
parentfb9c6905eff10f6f49a433ead8bd78e232fa068d (diff)
Replace JSON-GLib by custom JSON dumper library
The (optional) JSON-GLib library adds dependencies on GObject, GIO. For statically linked oss-fuzz builds it also adds libffi and more. To avoid these dependencies, replace JSON-GLib by some custom code. This allows `tshark -G elastic-mapping` to be enabled by default without extra deps. API design goals of the new JSON dumper library: - Small interface without a lot of abstraction. - Avoid memory allocations if possible (currently none, but maybe json_puts_string will be replaced to improve UTF-8 support). - Do not implement parsing, this is currently handled by jsmn. Methods to open/close array/objects and to set members are inspired by the JsonGlib interface. The interfaces to write values is inspired by the sharkd code (json_puts_string is also borrowed from that). The only observed differences in the tshark output: - JSON-GLib ignores duplicates, json_dumper does not and may produce duplicates and currently print two "ip.opt.sec_prot_auth_unassigned". - JSON-GLib adds a space before a colon (unimportant formatting detail). - (Not observed, but UTF-8 strings will be wrong like bug 14948.) A test was added to catch changes in the tshark output. I also fuzzed json_dumper with libFuzzer + UBSAN/ASAN and fixed an off-by-one error. Change-Id: I0c85b18777b04d1e0f613a3d59935ec59be87ff4 Link: https://www.wireshark.org/lists/wireshark-dev/201811/msg00052.html Reviewed-on: https://code.wireshark.org/review/30732 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'wsutil/json_dumper.c')
-rw-r--r--wsutil/json_dumper.c285
1 files changed, 285 insertions, 0 deletions
diff --git a/wsutil/json_dumper.c b/wsutil/json_dumper.c
new file mode 100644
index 0000000000..ebac957617
--- /dev/null
+++ b/wsutil/json_dumper.c
@@ -0,0 +1,285 @@
+/* wsjson.h
+ * Routines for serializing data as JSON.
+ *
+ * Copyright 2018, Peter Wu <peter@lekensteyn.nl>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "json_dumper.h"
+
+/*
+ * json_dumper.state[current_depth] describes a nested element:
+ * - type: none/object/array/value
+ * - has_name: Whether the object member name was set.
+ */
+enum json_dumper_element_type {
+ JSON_DUMPER_TYPE_NONE = 0,
+ JSON_DUMPER_TYPE_VALUE = 1,
+ JSON_DUMPER_TYPE_OBJECT = 2,
+ JSON_DUMPER_TYPE_ARRAY = 3,
+};
+#define JSON_DUMPER_TYPE(state) ((enum json_dumper_element_type)((state) & 3))
+#define JSON_DUMPER_HAS_NAME (1 << 2)
+
+#define JSON_DUMPER_FLAGS_ERROR (1 << 16) /* Output flag: an error occurred. */
+
+enum json_dumper_change {
+ JSON_DUMPER_BEGIN,
+ JSON_DUMPER_END,
+ JSON_DUMPER_SET_NAME,
+ JSON_DUMPER_SET_VALUE,
+ JSON_DUMPER_FINISH,
+};
+
+static void
+json_puts_string(FILE *fp, const char *str)
+{
+ static const char json_cntrl[0x20][6] = {
+ "u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b", "t", "n", "u000b", "f", "r", "u000e", "u000f",
+ "u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f"
+ };
+
+ fputc('"', fp);
+ for (int i = 0; str[i]; i++) {
+ if ((guint)str[i] < 0x20) {
+ fputc('\\', fp);
+ fputs(json_cntrl[(guint)str[i]], fp);
+ } else {
+ if (str[i] == '\\' || str[i] == '"') {
+ fputc('\\', fp);
+ }
+ fputc(str[i], fp);
+ }
+ }
+ fputc('"', fp);
+}
+
+/**
+ * Checks that the dumper state is valid for a new change. Any error will be
+ * sticky and prevent further dumps from succeeding.
+ */
+static gboolean
+json_dumper_check_state(json_dumper *dumper, enum json_dumper_change change, enum json_dumper_element_type type)
+{
+ if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) {
+ return FALSE;
+ }
+
+ int depth = dumper->current_depth;
+ if (depth < 0 || depth >= JSON_DUMPER_MAX_DEPTH) {
+ /* Corrupted state, no point in continuing. */
+ dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
+ return FALSE;
+ }
+
+ guint8 prev_state = depth > 0 ? dumper->state[depth - 1] : 0;
+ enum json_dumper_element_type prev_type = JSON_DUMPER_TYPE(prev_state);
+
+ gboolean ok = FALSE;
+ switch (change) {
+ case JSON_DUMPER_BEGIN:
+ ok = depth + 1 < JSON_DUMPER_MAX_DEPTH;
+ break;
+ case JSON_DUMPER_END:
+ ok = prev_type == type && !(prev_state & JSON_DUMPER_HAS_NAME);
+ break;
+ case JSON_DUMPER_SET_NAME:
+ /* An object name can only be set once before a value is set. */
+ ok = prev_type == JSON_DUMPER_TYPE_OBJECT && !(prev_state & JSON_DUMPER_HAS_NAME);
+ break;
+ case JSON_DUMPER_SET_VALUE:
+ if (prev_type == JSON_DUMPER_TYPE_OBJECT) {
+ ok = (prev_state & JSON_DUMPER_HAS_NAME);
+ } else if (prev_type == JSON_DUMPER_TYPE_ARRAY) {
+ ok = TRUE;
+ } else {
+ ok = JSON_DUMPER_TYPE(dumper->state[depth]) == JSON_DUMPER_TYPE_NONE;
+ }
+ break;
+ case JSON_DUMPER_FINISH:
+ ok = depth == 0;
+ break;
+ }
+ if (!ok) {
+ dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
+ }
+ return ok;
+}
+
+static void
+print_newline_indent(const json_dumper *dumper, int depth)
+{
+ if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
+ fputc('\n', dumper->output_file);
+ for (int i = 0; i < depth; i++) {
+ fputs(" ", dumper->output_file);
+ }
+ }
+}
+
+/**
+ * Prints commas, newlines and indentation (if necessary). Used for array
+ * values, object names and normal values (strings, etc.).
+ */
+static void
+prepare_token(json_dumper *dumper)
+{
+ if (dumper->current_depth == 0) {
+ // not part of an array or object.
+ return;
+ }
+ guint8 prev_state = dumper->state[dumper->current_depth - 1];
+
+ // While processing the object value, reset the key state as it is consumed.
+ dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME;
+
+ switch (JSON_DUMPER_TYPE(prev_state)) {
+ case JSON_DUMPER_TYPE_OBJECT:
+ if ((prev_state & JSON_DUMPER_HAS_NAME)) {
+ // Object key already set, value follows. No indentation needed.
+ return;
+ }
+ break;
+ case JSON_DUMPER_TYPE_ARRAY:
+ break;
+ default:
+ // Initial values do not need indentation.
+ return;
+ }
+
+ if (dumper->state[dumper->current_depth]) {
+ fputc(',', dumper->output_file);
+ }
+ print_newline_indent(dumper, dumper->current_depth);
+}
+
+/**
+ * Common code to close an object/array, printing a closing character (and if
+ * necessary, it is preceded by newline and indentation).
+ */
+static void
+finish_token(const json_dumper *dumper, char close_char)
+{
+ // if the object/array was non-empty, add a newline and indentation.
+ if (dumper->state[dumper->current_depth]) {
+ print_newline_indent(dumper, dumper->current_depth - 1);
+ }
+ fputc(close_char, dumper->output_file);
+}
+
+void
+json_dumper_begin_object(json_dumper *dumper)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_OBJECT)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ fputc('{', dumper->output_file);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_OBJECT;
+ ++dumper->current_depth;
+ dumper->state[dumper->current_depth] = 0;
+}
+
+void
+json_dumper_set_member_name(json_dumper *dumper, const char *name)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_NAME, JSON_DUMPER_TYPE_NONE)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ json_puts_string(dumper->output_file, name);
+ fputc(':', dumper->output_file);
+ if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
+ fputc(' ', dumper->output_file);
+ }
+
+ dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME;
+}
+
+void
+json_dumper_end_object(json_dumper *dumper)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_OBJECT)) {
+ return;
+ }
+
+ finish_token(dumper, '}');
+
+ --dumper->current_depth;
+}
+
+void
+json_dumper_begin_array(json_dumper *dumper)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_ARRAY)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ fputc('[', dumper->output_file);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_ARRAY;
+ ++dumper->current_depth;
+ dumper->state[dumper->current_depth] = 0;
+}
+
+void
+json_dumper_end_array(json_dumper *dumper)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_ARRAY)) {
+ return;
+ }
+
+ finish_token(dumper, ']');
+
+ --dumper->current_depth;
+}
+
+void
+json_dumper_value_string(json_dumper *dumper, const char *value)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ json_puts_string(dumper->output_file, value);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
+}
+
+void
+json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
+{
+ va_list ap;
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ va_start(ap, format);
+ vfprintf(dumper->output_file, format, ap);
+ va_end(ap);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
+}
+
+gboolean
+json_dumper_finish(json_dumper *dumper)
+{
+ if (!json_dumper_check_state(dumper, JSON_DUMPER_FINISH, JSON_DUMPER_TYPE_NONE)) {
+ return FALSE;
+ }
+
+ fputc('\n', dumper->output_file);
+ dumper->state[0] = 0;
+ return TRUE;
+}