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