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