epan: use json_dumper for json outputs.
[metze/wireshark/wip.git] / wsutil / json_dumper.c
1 /* wsjson.h
2  * Routines for serializing data as JSON.
3  *
4  * Copyright 2018, Peter Wu <peter@lekensteyn.nl>
5  * Copyright (C) 2016 Jakub Zawadzki
6  *
7  * Wireshark - Network traffic analyzer
8  * By Gerald Combs <gerald@wireshark.org>
9  * Copyright 1998 Gerald Combs
10  *
11  * SPDX-License-Identifier: GPL-2.0-or-later
12  */
13
14 #include "json_dumper.h"
15
16 /*
17  * json_dumper.state[current_depth] describes a nested element:
18  * - type: none/object/array/value
19  * - has_name: Whether the object member name was set.
20  */
21 enum json_dumper_element_type {
22     JSON_DUMPER_TYPE_NONE = 0,
23     JSON_DUMPER_TYPE_VALUE = 1,
24     JSON_DUMPER_TYPE_OBJECT = 2,
25     JSON_DUMPER_TYPE_ARRAY = 3,
26     JSON_DUMPER_TYPE_BASE64 = 4,
27 };
28 #define JSON_DUMPER_TYPE(state)         ((enum json_dumper_element_type)((state) & 7))
29 #define JSON_DUMPER_HAS_NAME            (1 << 3)
30
31 #define JSON_DUMPER_FLAGS_ERROR     (1 << 16)   /* Output flag: an error occurred. */
32 #define JSON_DUMPER_FLAGS_NO_DEBUG  (1 << 17)   /* Input flag: disable debug prints (intended for speeding up fuzzing). */
33
34 enum json_dumper_change {
35     JSON_DUMPER_BEGIN,
36     JSON_DUMPER_END,
37     JSON_DUMPER_SET_NAME,
38     JSON_DUMPER_SET_VALUE,
39     JSON_DUMPER_WRITE_BASE64,
40     JSON_DUMPER_FINISH,
41 };
42
43 static void
44 json_puts_string(FILE *fp, const char *str, gboolean dot_to_underscore)
45 {
46     if (!str) {
47         fputs("null", fp);
48         return;
49     }
50
51     static const char json_cntrl[0x20][6] = {
52         "u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b",     "t",     "n",     "u000b", "f",     "r",     "u000e", "u000f",
53         "u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f"
54     };
55
56     fputc('"', fp);
57     for (int i = 0; str[i]; i++) {
58         if ((guint)str[i] < 0x20) {
59             fputc('\\', fp);
60             fputs(json_cntrl[(guint)str[i]], fp);
61         } else {
62             if (str[i] == '\\' || str[i] == '"') {
63                 fputc('\\', fp);
64             }
65             if (dot_to_underscore && str[i] == '.')
66                 fputc('_', fp);
67             else
68                 fputc(str[i], fp);
69         }
70     }
71     fputc('"', fp);
72 }
73
74 /**
75  * Called when a programming error is encountered where the JSON manipulation
76  * state got corrupted. This could happen when pairing the wrong begin/end
77  * calls, when writing multiple values for the same object, etc.
78  */
79 static void
80 json_dumper_bad(json_dumper *dumper, enum json_dumper_change change,
81         enum json_dumper_element_type type, const char *what)
82 {
83     unsigned states[3];
84     int depth = dumper->current_depth;
85     /* Do not use add/subtract from depth to avoid signed overflow. */
86     int adj = -1;
87     for (int i = 0; i < 3; i++, adj++) {
88         if (depth >= -adj && depth < JSON_DUMPER_MAX_DEPTH - adj) {
89             states[i] = dumper->state[depth + adj];
90         } else {
91             states[i] = 0xbad;
92         }
93     }
94     if ((dumper->flags & JSON_DUMPER_FLAGS_NO_DEBUG)) {
95         /* Console output can be slow, disable log calls to speed up fuzzing. */
96         return;
97     }
98     g_error("Bad json_dumper state: %s; change=%d type=%d depth=%d prev/curr/next state=%02x %02x %02x",
99             what, change, type, dumper->current_depth, states[0], states[1], states[2]);
100 }
101
102 /**
103  * Checks that the dumper state is valid for a new change. Any error will be
104  * sticky and prevent further dumps from succeeding.
105  */
106 static gboolean
107 json_dumper_check_state(json_dumper *dumper, enum json_dumper_change change, enum json_dumper_element_type type)
108 {
109     if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) {
110         json_dumper_bad(dumper, change, type, "previous corruption detected");
111         return FALSE;
112     }
113
114     int depth = dumper->current_depth;
115     if (depth < 0 || depth >= JSON_DUMPER_MAX_DEPTH) {
116         /* Corrupted state, no point in continuing. */
117         dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
118         json_dumper_bad(dumper, change, type, "depth corruption");
119         return FALSE;
120     }
121
122     guint8 prev_state = depth > 0 ? dumper->state[depth - 1] : 0;
123     enum json_dumper_element_type prev_type = JSON_DUMPER_TYPE(prev_state);
124
125     gboolean ok = FALSE;
126     switch (change) {
127         case JSON_DUMPER_BEGIN:
128             ok = depth + 1 < JSON_DUMPER_MAX_DEPTH;
129             break;
130         case JSON_DUMPER_END:
131             ok = prev_type == type && !(prev_state & JSON_DUMPER_HAS_NAME);
132             break;
133         case JSON_DUMPER_SET_NAME:
134             /* An object name can only be set once before a value is set. */
135             ok = prev_type == JSON_DUMPER_TYPE_OBJECT && !(prev_state & JSON_DUMPER_HAS_NAME);
136             break;
137         case JSON_DUMPER_SET_VALUE:
138             if (prev_type == JSON_DUMPER_TYPE_OBJECT) {
139                 ok = (prev_state & JSON_DUMPER_HAS_NAME);
140             } else if (prev_type == JSON_DUMPER_TYPE_ARRAY) {
141                 ok = TRUE;
142             } else if (prev_type == JSON_DUMPER_TYPE_BASE64) {
143                 ok = FALSE;
144             } else {
145                 ok = JSON_DUMPER_TYPE(dumper->state[depth]) == JSON_DUMPER_TYPE_NONE;
146             }
147             break;
148         case JSON_DUMPER_WRITE_BASE64:
149             ok = (prev_type == JSON_DUMPER_TYPE_BASE64) &&
150                 (type == JSON_DUMPER_TYPE_NONE || type == JSON_DUMPER_TYPE_BASE64);
151             break;
152         case JSON_DUMPER_FINISH:
153             ok = depth == 0;
154             break;
155     }
156     if (!ok) {
157         dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
158         json_dumper_bad(dumper, change, type, "illegal transition");
159     }
160     return ok;
161 }
162
163 static void
164 print_newline_indent(const json_dumper *dumper, int depth)
165 {
166     if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
167         fputc('\n', dumper->output_file);
168         for (int i = 0; i < depth; i++) {
169             fputs("  ", dumper->output_file);
170         }
171     }
172 }
173
174 /**
175  * Prints commas, newlines and indentation (if necessary). Used for array
176  * values, object names and normal values (strings, etc.).
177  */
178 static void
179 prepare_token(json_dumper *dumper)
180 {
181     if (dumper->current_depth == 0) {
182         // not part of an array or object.
183         return;
184     }
185     guint8 prev_state = dumper->state[dumper->current_depth - 1];
186
187     // While processing the object value, reset the key state as it is consumed.
188     dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME;
189
190     switch (JSON_DUMPER_TYPE(prev_state)) {
191         case JSON_DUMPER_TYPE_OBJECT:
192             if ((prev_state & JSON_DUMPER_HAS_NAME)) {
193                 // Object key already set, value follows. No indentation needed.
194                 return;
195             }
196             break;
197         case JSON_DUMPER_TYPE_ARRAY:
198             break;
199         default:
200             // Initial values do not need indentation.
201             return;
202     }
203
204     if (dumper->state[dumper->current_depth]) {
205         fputc(',', dumper->output_file);
206     }
207     print_newline_indent(dumper, dumper->current_depth);
208 }
209
210 /**
211  * Common code to close an object/array, printing a closing character (and if
212  * necessary, it is preceded by newline and indentation).
213  */
214 static void
215 finish_token(const json_dumper *dumper, char close_char)
216 {
217     // if the object/array was non-empty, add a newline and indentation.
218     if (dumper->state[dumper->current_depth]) {
219         print_newline_indent(dumper, dumper->current_depth - 1);
220     }
221     fputc(close_char, dumper->output_file);
222 }
223
224 void
225 json_dumper_begin_object(json_dumper *dumper)
226 {
227     if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_OBJECT)) {
228         return;
229     }
230
231     prepare_token(dumper);
232     fputc('{', dumper->output_file);
233
234     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_OBJECT;
235     ++dumper->current_depth;
236     dumper->state[dumper->current_depth] = 0;
237 }
238
239 void
240 json_dumper_set_member_name(json_dumper *dumper, const char *name)
241 {
242     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_NAME, JSON_DUMPER_TYPE_NONE)) {
243         return;
244     }
245
246     prepare_token(dumper);
247     json_puts_string(dumper->output_file, name, dumper->flags & JSON_DUMPER_DOT_TO_UNDERSCORE);
248     fputc(':', dumper->output_file);
249     if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
250         fputc(' ', dumper->output_file);
251     }
252
253     dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME;
254 }
255
256 void
257 json_dumper_end_object(json_dumper *dumper)
258 {
259     if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_OBJECT)) {
260         return;
261     }
262
263     finish_token(dumper, '}');
264
265     --dumper->current_depth;
266 }
267
268 void
269 json_dumper_begin_array(json_dumper *dumper)
270 {
271     if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_ARRAY)) {
272         return;
273     }
274
275     prepare_token(dumper);
276     fputc('[', dumper->output_file);
277
278     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_ARRAY;
279     ++dumper->current_depth;
280     dumper->state[dumper->current_depth] = 0;
281 }
282
283 void
284 json_dumper_end_array(json_dumper *dumper)
285 {
286     if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_ARRAY)) {
287         return;
288     }
289
290     finish_token(dumper, ']');
291
292     --dumper->current_depth;
293 }
294
295 void
296 json_dumper_value_string(json_dumper *dumper, const char *value)
297 {
298     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
299         return;
300     }
301
302     prepare_token(dumper);
303     json_puts_string(dumper->output_file, value, FALSE);
304
305     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
306 }
307
308 void
309 json_dumper_value_va_list(json_dumper *dumper, const char *format, va_list ap)
310 {
311     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
312         return;
313     }
314
315     prepare_token(dumper);
316     vfprintf(dumper->output_file, format, ap);
317
318     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
319 }
320
321 void
322 json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
323 {
324     va_list ap;
325
326     va_start(ap, format);
327     json_dumper_value_va_list(dumper, format, ap);
328     va_end(ap);
329 }
330
331 gboolean
332 json_dumper_finish(json_dumper *dumper)
333 {
334     if (!json_dumper_check_state(dumper, JSON_DUMPER_FINISH, JSON_DUMPER_TYPE_NONE)) {
335         return FALSE;
336     }
337
338     fputc('\n', dumper->output_file);
339     dumper->state[0] = 0;
340     return TRUE;
341 }
342
343 void
344 json_dumper_begin_base64(json_dumper *dumper)
345 {
346     if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_BASE64)) {
347         return;
348     }
349
350     dumper->base64_state = 0;
351     dumper->base64_save = 0;
352
353     prepare_token(dumper);
354
355     fputc('"', dumper->output_file);
356
357     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_BASE64;
358     ++dumper->current_depth;
359     dumper->state[dumper->current_depth] = 0;
360 }
361
362 void
363 json_dumper_write_base64(json_dumper* dumper, const guchar *data, size_t len)
364 {
365     if (!json_dumper_check_state(dumper, JSON_DUMPER_WRITE_BASE64, JSON_DUMPER_TYPE_BASE64)) {
366         return;
367     }
368
369     #define CHUNK_SIZE 1024
370     gchar buf[(CHUNK_SIZE / 3 + 1) * 4 + 4];
371
372     while (len > 0) {
373         gsize chunk_size = len < CHUNK_SIZE ? len : CHUNK_SIZE;
374         gsize output_size = g_base64_encode_step(data, chunk_size, FALSE, buf, &dumper->base64_state, &dumper->base64_save);
375         fwrite(buf, 1, output_size, dumper->output_file);
376         data += chunk_size;
377         len -= chunk_size;
378     }
379
380     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_BASE64;
381 }
382
383 void
384 json_dumper_end_base64(json_dumper *dumper)
385 {
386     if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_BASE64)) {
387         return;
388     }
389
390     gchar buf[4];
391     gsize wrote;
392
393     wrote = g_base64_encode_close(FALSE, buf, &dumper->base64_state, &dumper->base64_save);
394     fwrite(buf, 1, wrote, dumper->output_file);
395
396     fputc('"', dumper->output_file);
397
398     --dumper->current_depth;
399 }