/src/wireshark/wsutil/json_dumper.c
Line | Count | Source |
1 | | /* json_dumper.c |
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 "config.h" |
15 | 0 | #define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL |
16 | | |
17 | | #include <glib.h> |
18 | | #include <string.h> |
19 | | |
20 | | #include "json_dumper.h" |
21 | | #include <wsutil/to_str.h> |
22 | | #include <math.h> |
23 | | |
24 | | #include <wsutil/array.h> |
25 | | #include <wsutil/wslog.h> |
26 | | |
27 | | /* |
28 | | * json_dumper.state[current_depth] describes a nested element: |
29 | | * - type: none/object/array/non-base64 value/base64 value |
30 | | * - has_name: Whether the object member name was set. |
31 | | * |
32 | | * (A base64 value isn't really a nested element, but that's a |
33 | | * convenient way of handling them, with a begin call that opens |
34 | | * the string with a double-quote, one or more calls to convert |
35 | | * raw bytes to base64 and add them to the value, and an end call |
36 | | * that finishes the base64 encoding, adds any remaining raw bytes |
37 | | * in base64 encoding, and closes the string with a double-quote.) |
38 | | */ |
39 | | enum json_dumper_element_type { |
40 | | JSON_DUMPER_TYPE_NONE = 0, |
41 | | JSON_DUMPER_TYPE_VALUE = 1, |
42 | | JSON_DUMPER_TYPE_OBJECT = 2, |
43 | | JSON_DUMPER_TYPE_ARRAY = 3, |
44 | | JSON_DUMPER_TYPE_BASE64 = 4, |
45 | | }; |
46 | 0 | #define JSON_DUMPER_TYPE(state) ((enum json_dumper_element_type)((state) & 7)) |
47 | 0 | #define JSON_DUMPER_HAS_NAME (1 << 3) |
48 | | |
49 | | static const char * const json_dumper_element_type_names[] = { |
50 | | [JSON_DUMPER_TYPE_NONE] = "none", |
51 | | [JSON_DUMPER_TYPE_VALUE] = "value", |
52 | | [JSON_DUMPER_TYPE_OBJECT] = "object", |
53 | | [JSON_DUMPER_TYPE_ARRAY] = "array", |
54 | | [JSON_DUMPER_TYPE_BASE64] = "base64" |
55 | | }; |
56 | 0 | #define NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES array_length(json_dumper_element_type_names) |
57 | | |
58 | 0 | #define JSON_DUMPER_FLAGS_ERROR (1 << 16) /* Output flag: an error occurred. */ |
59 | | |
60 | | enum json_dumper_change { |
61 | | JSON_DUMPER_BEGIN, |
62 | | JSON_DUMPER_END, |
63 | | JSON_DUMPER_SET_NAME, |
64 | | JSON_DUMPER_SET_VALUE, |
65 | | JSON_DUMPER_WRITE_BASE64, |
66 | | JSON_DUMPER_FINISH, |
67 | | }; |
68 | | |
69 | | /* Internal write buffer to reduce per-character stdio call overhead. */ |
70 | | |
71 | | static inline void |
72 | | jd_flush(json_dumper *dumper) |
73 | 0 | { |
74 | 0 | if (dumper->buf_pos > 0) { |
75 | 0 | if (dumper->output_file) { |
76 | 0 | fwrite(dumper->buf, 1, dumper->buf_pos, dumper->output_file); |
77 | 0 | } |
78 | 0 | if (dumper->output_string) { |
79 | 0 | g_string_append_len(dumper->output_string, dumper->buf, dumper->buf_pos); |
80 | 0 | } |
81 | 0 | dumper->buf_pos = 0; |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | | static inline void |
86 | | jd_buf_append(json_dumper *dumper, const char *s, size_t len) |
87 | 0 | { |
88 | 0 | while (len > 0) { |
89 | 0 | size_t avail = JD_BUF_SIZE - dumper->buf_pos; |
90 | 0 | if (len <= avail) { |
91 | 0 | memcpy(dumper->buf + dumper->buf_pos, s, len); |
92 | 0 | dumper->buf_pos += len; |
93 | 0 | return; |
94 | 0 | } |
95 | 0 | memcpy(dumper->buf + dumper->buf_pos, s, avail); |
96 | 0 | dumper->buf_pos = JD_BUF_SIZE; |
97 | 0 | jd_flush(dumper); |
98 | 0 | s += avail; |
99 | 0 | len -= avail; |
100 | 0 | } |
101 | 0 | } |
102 | | |
103 | | /* JSON Dumper putc */ |
104 | | static inline void |
105 | | jd_putc(json_dumper *dumper, char c) |
106 | 0 | { |
107 | 0 | if (dumper->buf_pos >= JD_BUF_SIZE) { |
108 | 0 | jd_flush(dumper); |
109 | 0 | } |
110 | 0 | dumper->buf[dumper->buf_pos++] = c; |
111 | 0 | } |
112 | | |
113 | | /* JSON Dumper puts */ |
114 | | static inline void |
115 | | jd_puts(json_dumper *dumper, const char *s) |
116 | 0 | { |
117 | 0 | jd_buf_append(dumper, s, strlen(s)); |
118 | 0 | } |
119 | | |
120 | | static inline void |
121 | | jd_puts_len(json_dumper *dumper, const char *s, size_t len) |
122 | 0 | { |
123 | 0 | jd_buf_append(dumper, s, len); |
124 | 0 | } |
125 | | |
126 | | static void |
127 | | jd_vprintf(json_dumper *dumper, const char *format, va_list args) |
128 | 0 | { |
129 | | /* Try to format directly into the remaining buffer space */ |
130 | 0 | size_t saved_pos = dumper->buf_pos; |
131 | 0 | size_t avail = JD_BUF_SIZE - saved_pos; |
132 | 0 | va_list args_copy; |
133 | 0 | va_copy(args_copy, args); |
134 | 0 | int n = vsnprintf(dumper->buf + saved_pos, avail, format, args); |
135 | 0 | if (n >= 0 && (size_t)n < avail) { |
136 | 0 | dumper->buf_pos = saved_pos + n; |
137 | 0 | va_end(args_copy); |
138 | 0 | return; |
139 | 0 | } |
140 | | /* Didn't fit - restore position (discard truncated write), flush, retry */ |
141 | 0 | dumper->buf_pos = saved_pos; |
142 | 0 | jd_flush(dumper); |
143 | 0 | n = vsnprintf(dumper->buf, JD_BUF_SIZE, format, args_copy); |
144 | 0 | if (n >= 0 && (size_t)n < JD_BUF_SIZE) { |
145 | 0 | dumper->buf_pos = n; |
146 | 0 | } else { |
147 | | /* Very large format - write directly */ |
148 | 0 | if (dumper->output_file) { |
149 | 0 | vfprintf(dumper->output_file, format, args_copy); |
150 | 0 | } |
151 | 0 | if (dumper->output_string) { |
152 | 0 | g_string_append_vprintf(dumper->output_string, format, args_copy); |
153 | 0 | } |
154 | 0 | } |
155 | 0 | va_end(args_copy); |
156 | 0 | } |
157 | | |
158 | | static void |
159 | | json_puts_string(json_dumper *dumper, const char *str, bool dot_to_underscore) |
160 | 0 | { |
161 | 0 | if (!str) { |
162 | 0 | jd_puts(dumper, "null"); |
163 | 0 | return; |
164 | 0 | } |
165 | | |
166 | 0 | static const char json_cntrl[0x20][6] = { |
167 | 0 | "u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b", "t", "n", "u000b", "f", "r", "u000e", "u000f", |
168 | 0 | "u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f" |
169 | 0 | }; |
170 | |
|
171 | 0 | jd_putc(dumper, '"'); |
172 | 0 | if (!dot_to_underscore) { |
173 | | /* Fast path: scan for runs of characters that need no escaping */ |
174 | 0 | const char *p = str; |
175 | 0 | while (*p) { |
176 | 0 | const char *run_start = p; |
177 | 0 | while ((unsigned char)*p >= 0x20 && *p != '\\' && *p != '"' && |
178 | 0 | !(*p == '/' && p > str && *(p - 1) == '<')) { |
179 | 0 | p++; |
180 | 0 | } |
181 | 0 | if (p > run_start) { |
182 | 0 | jd_puts_len(dumper, run_start, p - run_start); |
183 | 0 | } |
184 | 0 | if (!*p) break; |
185 | 0 | if ((unsigned char)*p < 0x20) { |
186 | 0 | jd_putc(dumper, '\\'); |
187 | 0 | jd_puts(dumper, json_cntrl[(unsigned char)*p]); |
188 | 0 | } else if (*p == '/' && p > str && *(p - 1) == '<') { |
189 | 0 | jd_puts_len(dumper, "\\/", 2); |
190 | 0 | } else { |
191 | | /* '\\' or '"' */ |
192 | 0 | jd_putc(dumper, '\\'); |
193 | 0 | jd_putc(dumper, *p); |
194 | 0 | } |
195 | 0 | p++; |
196 | 0 | } |
197 | 0 | } else { |
198 | | /* Dot-to-underscore path: scan for runs of plain chars (not dot/backslash/quote/control/</) */ |
199 | 0 | const char *p = str; |
200 | 0 | while (*p) { |
201 | 0 | const char *run_start = p; |
202 | 0 | while ((unsigned char)*p >= 0x20 && *p != '\\' && *p != '"' && *p != '.' && |
203 | 0 | !(*p == '/' && p > str && *(p - 1) == '<')) { |
204 | 0 | p++; |
205 | 0 | } |
206 | 0 | if (p > run_start) { |
207 | 0 | jd_puts_len(dumper, run_start, p - run_start); |
208 | 0 | } |
209 | 0 | if (!*p) break; |
210 | 0 | if ((unsigned char)*p < 0x20) { |
211 | 0 | jd_putc(dumper, '\\'); |
212 | 0 | jd_puts(dumper, json_cntrl[(unsigned char)*p]); |
213 | 0 | } else if (*p == '/' && p > str && *(p - 1) == '<') { |
214 | 0 | jd_puts_len(dumper, "\\/", 2); |
215 | 0 | } else if (*p == '\\' || *p == '"') { |
216 | 0 | jd_putc(dumper, '\\'); |
217 | 0 | jd_putc(dumper, *p); |
218 | 0 | } else { |
219 | | /* dot -> underscore */ |
220 | 0 | jd_putc(dumper, '_'); |
221 | 0 | } |
222 | 0 | p++; |
223 | 0 | } |
224 | 0 | } |
225 | 0 | jd_putc(dumper, '"'); |
226 | 0 | } |
227 | | |
228 | | static inline uint8_t |
229 | | json_dumper_get_prev_state(json_dumper *dumper) |
230 | 0 | { |
231 | 0 | unsigned depth = dumper->current_depth; |
232 | 0 | return depth != 0 ? dumper->state[depth - 1] : 0; |
233 | 0 | } |
234 | | |
235 | | static inline uint8_t |
236 | | json_dumper_get_curr_state(json_dumper *dumper) |
237 | 0 | { |
238 | 0 | unsigned depth = dumper->current_depth; |
239 | 0 | return dumper->state[depth]; |
240 | 0 | } |
241 | | |
242 | | /** |
243 | | * Called when a programming error is encountered where the JSON manipulation |
244 | | * state got corrupted. This could happen when pairing the wrong begin/end |
245 | | * calls, when writing multiple values for the same object, etc. |
246 | | */ |
247 | | static void |
248 | | json_dumper_bad(json_dumper *dumper, const char *what) |
249 | 0 | { |
250 | 0 | dumper->flags |= JSON_DUMPER_FLAGS_ERROR; |
251 | 0 | if ((dumper->flags & JSON_DUMPER_FLAGS_NO_DEBUG)) { |
252 | | /* Console output can be slow, disable log calls to speed up fuzzing. */ |
253 | | /* |
254 | | * XXX - should this call abort()? If that flag isn't set, |
255 | | * ws_error() wou;d call it; is there any point in continuing |
256 | | * to do anything if we get here when fuzzing? |
257 | | */ |
258 | 0 | return; |
259 | 0 | } |
260 | | |
261 | 0 | if (dumper->output_file) { |
262 | 0 | jd_flush(dumper); |
263 | 0 | fflush(dumper->output_file); |
264 | 0 | } |
265 | 0 | char unknown_curr_type_name[10+1]; |
266 | 0 | char unknown_prev_type_name[10+1]; |
267 | 0 | const char *curr_type_name, *prev_type_name; |
268 | 0 | uint8_t curr_state = json_dumper_get_curr_state(dumper); |
269 | 0 | uint8_t curr_type = JSON_DUMPER_TYPE(curr_state); |
270 | 0 | if (curr_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) { |
271 | 0 | curr_type_name = json_dumper_element_type_names[curr_type]; |
272 | 0 | } else { |
273 | 0 | snprintf(unknown_curr_type_name, sizeof unknown_curr_type_name, "%u", curr_type); |
274 | 0 | curr_type_name = unknown_curr_type_name; |
275 | 0 | } |
276 | 0 | if (dumper->current_depth != 0) { |
277 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
278 | 0 | uint8_t prev_type = JSON_DUMPER_TYPE(prev_state); |
279 | 0 | if (prev_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) { |
280 | 0 | prev_type_name = json_dumper_element_type_names[prev_type]; |
281 | 0 | } else { |
282 | 0 | snprintf(unknown_prev_type_name, sizeof unknown_prev_type_name, "%u", prev_type); |
283 | 0 | prev_type_name = unknown_prev_type_name; |
284 | 0 | } |
285 | 0 | } else { |
286 | 0 | prev_type_name = "(none)"; |
287 | 0 | } |
288 | 0 | ws_error("json_dumper error: %s: current stack depth %u, current type %s, previous_type %s", |
289 | 0 | what, dumper->current_depth, curr_type_name, prev_type_name); |
290 | | /* NOTREACHED */ |
291 | 0 | } |
292 | | |
293 | | static inline bool |
294 | | json_dumper_stack_would_overflow(json_dumper *dumper) |
295 | 0 | { |
296 | 0 | if (dumper->current_depth + 1 >= JSON_DUMPER_MAX_DEPTH) { |
297 | 0 | json_dumper_bad(dumper, "JSON dumper stack overflow"); |
298 | 0 | return true; |
299 | 0 | } |
300 | 0 | return false; |
301 | 0 | } |
302 | | |
303 | | static inline bool |
304 | | json_dumper_stack_would_underflow(json_dumper *dumper) |
305 | 0 | { |
306 | 0 | if (dumper->current_depth == 0) { |
307 | 0 | json_dumper_bad(dumper, "JSON dumper stack underflow"); |
308 | 0 | return true; |
309 | 0 | } |
310 | 0 | return false; |
311 | 0 | } |
312 | | |
313 | | /** |
314 | | * Checks that the dumper has not already had an error. Fail, and |
315 | | * return false, to tell our caller not to do any more work, if it |
316 | | * has. |
317 | | */ |
318 | | static bool |
319 | | json_dumper_check_previous_error(json_dumper *dumper) |
320 | 0 | { |
321 | 0 | if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) { |
322 | 0 | json_dumper_bad(dumper, "previous corruption detected"); |
323 | 0 | return false; |
324 | 0 | } |
325 | 0 | return true; |
326 | 0 | } |
327 | | |
328 | | static void |
329 | | print_newline_indent(json_dumper *dumper, unsigned depth) |
330 | 0 | { |
331 | 0 | if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) { |
332 | | /* Pre-built indent: newline + up to 128 levels of 2-space indent */ |
333 | 0 | static const char indent_buf[1 + 256 + 1] = |
334 | 0 | "\n " |
335 | 0 | " " |
336 | 0 | " " |
337 | 0 | " "; |
338 | 0 | size_t indent_len = depth * 2; |
339 | 0 | if (indent_len <= 256) { |
340 | 0 | jd_puts_len(dumper, indent_buf, 1 + indent_len); |
341 | 0 | } else { |
342 | 0 | jd_putc(dumper, '\n'); |
343 | 0 | for (unsigned i = 0; i < depth; i++) { |
344 | 0 | jd_puts_len(dumper, " ", 2); |
345 | 0 | } |
346 | 0 | } |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | /** |
351 | | * Prints commas, newlines and indentation (if necessary). Used for array |
352 | | * values, object names and normal values (strings, etc.). |
353 | | */ |
354 | | static void |
355 | | prepare_token(json_dumper *dumper) |
356 | 0 | { |
357 | 0 | if (dumper->current_depth == 0) { |
358 | | // not part of an array or object. |
359 | 0 | return; |
360 | 0 | } |
361 | 0 | uint8_t prev_state = dumper->state[dumper->current_depth - 1]; |
362 | | |
363 | | // While processing the object value, reset the key state as it is consumed. |
364 | 0 | dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME; |
365 | |
|
366 | 0 | switch (JSON_DUMPER_TYPE(prev_state)) { |
367 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
368 | 0 | if ((prev_state & JSON_DUMPER_HAS_NAME)) { |
369 | | // Object key already set, value follows. No indentation needed. |
370 | 0 | return; |
371 | 0 | } |
372 | 0 | break; |
373 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
374 | 0 | break; |
375 | 0 | default: |
376 | | // Initial values do not need indentation. |
377 | 0 | return; |
378 | 0 | } |
379 | | |
380 | 0 | uint8_t curr_state = json_dumper_get_curr_state(dumper); |
381 | 0 | if (curr_state != JSON_DUMPER_TYPE_NONE) { |
382 | 0 | jd_putc(dumper, ','); |
383 | 0 | } |
384 | 0 | print_newline_indent(dumper, dumper->current_depth); |
385 | 0 | } |
386 | | |
387 | | /** |
388 | | * Common code to open an object/array/base64 value, printing |
389 | | * an opening character. |
390 | | * |
391 | | * It also makes various correctness checks. |
392 | | */ |
393 | | static bool |
394 | | json_dumper_begin_nested_element(json_dumper *dumper, enum json_dumper_element_type type) |
395 | 0 | { |
396 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
397 | 0 | return false; |
398 | 0 | } |
399 | | |
400 | | /* Make sure we won't overflow the dumper stack */ |
401 | 0 | if (json_dumper_stack_would_overflow(dumper)) { |
402 | 0 | return false; |
403 | 0 | } |
404 | | |
405 | 0 | prepare_token(dumper); |
406 | 0 | switch (type) { |
407 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
408 | 0 | jd_putc(dumper, '{'); |
409 | 0 | break; |
410 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
411 | 0 | jd_putc(dumper, '['); |
412 | 0 | break; |
413 | 0 | case JSON_DUMPER_TYPE_BASE64: |
414 | 0 | dumper->base64_state = 0; |
415 | 0 | dumper->base64_save = 0; |
416 | |
|
417 | 0 | jd_putc(dumper, '"'); |
418 | 0 | break; |
419 | 0 | default: |
420 | 0 | json_dumper_bad(dumper, "beginning unknown nested element type"); |
421 | 0 | return false; |
422 | 0 | } |
423 | | |
424 | 0 | dumper->state[dumper->current_depth] = type; |
425 | | /* |
426 | | * Guaranteed not to overflow, as json_dumper_stack_would_overflow() |
427 | | * returned false. |
428 | | */ |
429 | 0 | ++dumper->current_depth; |
430 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_NONE; |
431 | 0 | return true; |
432 | 0 | } |
433 | | |
434 | | /** |
435 | | * Common code to close an object/array/base64 value, printing a |
436 | | * closing character (and if necessary, it is preceded by newline |
437 | | * and indentation). |
438 | | * |
439 | | * It also makes various correctness checks. |
440 | | */ |
441 | | static bool |
442 | | json_dumper_end_nested_element(json_dumper *dumper, enum json_dumper_element_type type) |
443 | 0 | { |
444 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
445 | 0 | return false; |
446 | 0 | } |
447 | | |
448 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
449 | |
|
450 | 0 | switch (type) { |
451 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
452 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) { |
453 | 0 | json_dumper_bad(dumper, "ending non-object nested item type as object"); |
454 | 0 | return false; |
455 | 0 | } |
456 | 0 | break; |
457 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
458 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_ARRAY) { |
459 | 0 | json_dumper_bad(dumper, "ending non-array nested item type as array"); |
460 | 0 | return false; |
461 | 0 | } |
462 | 0 | break; |
463 | 0 | case JSON_DUMPER_TYPE_BASE64: |
464 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) { |
465 | 0 | json_dumper_bad(dumper, "ending non-base64 nested item type as base64"); |
466 | 0 | return false; |
467 | 0 | } |
468 | 0 | break; |
469 | 0 | default: |
470 | 0 | json_dumper_bad(dumper, "ending unknown nested element type"); |
471 | 0 | return false; |
472 | 0 | } |
473 | | |
474 | 0 | if (prev_state & JSON_DUMPER_HAS_NAME) { |
475 | 0 | json_dumper_bad(dumper, "finishing object with last item having name but no value"); |
476 | 0 | return false; |
477 | 0 | } |
478 | | |
479 | | /* Make sure we won't underflow the dumper stack */ |
480 | 0 | if (json_dumper_stack_would_underflow(dumper)) { |
481 | 0 | return false; |
482 | 0 | } |
483 | | |
484 | | // if the object/array was non-empty, add a newline and indentation. |
485 | 0 | if (dumper->state[dumper->current_depth]) { |
486 | 0 | print_newline_indent(dumper, dumper->current_depth - 1); |
487 | 0 | } |
488 | |
|
489 | 0 | switch (type) { |
490 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
491 | 0 | jd_putc(dumper, '}'); |
492 | 0 | break; |
493 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
494 | 0 | jd_putc(dumper, ']'); |
495 | 0 | break; |
496 | 0 | case JSON_DUMPER_TYPE_BASE64: |
497 | 0 | { |
498 | 0 | char buf[4]; |
499 | 0 | size_t wrote; |
500 | |
|
501 | 0 | wrote = g_base64_encode_close(false, buf, &dumper->base64_state, &dumper->base64_save); |
502 | 0 | jd_puts_len(dumper, buf, wrote); |
503 | |
|
504 | 0 | jd_putc(dumper, '"'); |
505 | 0 | break; |
506 | 0 | } |
507 | 0 | default: |
508 | 0 | json_dumper_bad(dumper, "ending unknown nested element type"); |
509 | 0 | return false; |
510 | 0 | } |
511 | | |
512 | | /* |
513 | | * Guaranteed not to underflow, as json_dumper_stack_would_underflow() |
514 | | * returned false. |
515 | | */ |
516 | 0 | --dumper->current_depth; |
517 | 0 | return true; |
518 | 0 | } |
519 | | |
520 | | void |
521 | | json_dumper_begin_object(json_dumper *dumper) |
522 | 0 | { |
523 | 0 | json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT); |
524 | 0 | } |
525 | | |
526 | | void |
527 | | json_dumper_set_member_name(json_dumper *dumper, const char *name) |
528 | 0 | { |
529 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
530 | 0 | return; |
531 | 0 | } |
532 | | |
533 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
534 | | |
535 | | /* Only object members, not array members, have names. */ |
536 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) { |
537 | 0 | json_dumper_bad(dumper, "setting name on non-object nested item type"); |
538 | 0 | return; |
539 | 0 | } |
540 | | /* An object member name can only be set once before its value is set. */ |
541 | 0 | if (prev_state & JSON_DUMPER_HAS_NAME) { |
542 | 0 | json_dumper_bad(dumper, "setting name twice on an object member"); |
543 | 0 | return; |
544 | 0 | } |
545 | | |
546 | 0 | prepare_token(dumper); |
547 | 0 | json_puts_string(dumper, name, dumper->flags & JSON_DUMPER_DOT_TO_UNDERSCORE); |
548 | 0 | jd_putc(dumper, ':'); |
549 | 0 | if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) { |
550 | 0 | jd_putc(dumper, ' '); |
551 | 0 | } |
552 | |
|
553 | 0 | dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME; |
554 | 0 | } |
555 | | |
556 | | void |
557 | | json_dumper_set_member_name_noesc(json_dumper *dumper, const char *name, size_t len) |
558 | 0 | { |
559 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
560 | 0 | return; |
561 | 0 | } |
562 | | |
563 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
564 | |
|
565 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) { |
566 | 0 | json_dumper_bad(dumper, "setting name on non-object nested item type"); |
567 | 0 | return; |
568 | 0 | } |
569 | 0 | if (prev_state & JSON_DUMPER_HAS_NAME) { |
570 | 0 | json_dumper_bad(dumper, "setting name twice on an object member"); |
571 | 0 | return; |
572 | 0 | } |
573 | | |
574 | 0 | prepare_token(dumper); |
575 | 0 | jd_putc(dumper, '"'); |
576 | 0 | jd_puts_len(dumper, name, len); |
577 | 0 | jd_putc(dumper, '"'); |
578 | 0 | jd_putc(dumper, ':'); |
579 | 0 | if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) { |
580 | 0 | jd_putc(dumper, ' '); |
581 | 0 | } |
582 | |
|
583 | 0 | dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME; |
584 | 0 | } |
585 | | |
586 | | void |
587 | | json_dumper_end_object(json_dumper *dumper) |
588 | 0 | { |
589 | 0 | json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT); |
590 | 0 | } |
591 | | |
592 | | void |
593 | | json_dumper_begin_array(json_dumper *dumper) |
594 | 0 | { |
595 | 0 | json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY); |
596 | 0 | } |
597 | | |
598 | | void |
599 | | json_dumper_end_array(json_dumper *dumper) |
600 | 0 | { |
601 | 0 | json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY); |
602 | 0 | } |
603 | | |
604 | | static bool |
605 | | json_dumper_setting_value_ok(json_dumper *dumper) |
606 | 0 | { |
607 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
608 | |
|
609 | 0 | switch (JSON_DUMPER_TYPE(prev_state)) { |
610 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
611 | | /* |
612 | | * This value is part of an object. As such, it must |
613 | | * have a name. |
614 | | */ |
615 | 0 | if (!(prev_state & JSON_DUMPER_HAS_NAME)) { |
616 | 0 | json_dumper_bad(dumper, "setting value of object member without a name"); |
617 | 0 | return false; |
618 | 0 | } |
619 | 0 | break; |
620 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
621 | | /* |
622 | | * This value is part of an array. As such, it's not |
623 | | * required to have a name (and shouldn't have a name; |
624 | | * that's already been checked in json_dumper_set_member_name()). |
625 | | */ |
626 | 0 | break; |
627 | 0 | case JSON_DUMPER_TYPE_BASE64: |
628 | | /* |
629 | | * We're in the middle of constructing a base64-encoded |
630 | | * value. Only json_dumper_write_base64() can be used |
631 | | * for that; we can't add individual values to it. |
632 | | */ |
633 | 0 | json_dumper_bad(dumper, "attempt to set value of base64 item to something not base64-encoded"); |
634 | 0 | return false; |
635 | 0 | case JSON_DUMPER_TYPE_NONE: |
636 | 0 | case JSON_DUMPER_TYPE_VALUE: |
637 | 0 | { |
638 | 0 | uint8_t curr_state = json_dumper_get_curr_state(dumper); |
639 | 0 | switch (JSON_DUMPER_TYPE(curr_state)) { |
640 | 0 | case JSON_DUMPER_TYPE_NONE: |
641 | | /* |
642 | | * We haven't put a value yet, so we can put one now. |
643 | | */ |
644 | 0 | break; |
645 | 0 | case JSON_DUMPER_TYPE_VALUE: |
646 | | /* |
647 | | * This value isn't part of an object or array, |
648 | | * and we've already put one value. |
649 | | */ |
650 | 0 | json_dumper_bad(dumper, "value not in object or array immediately follows another value"); |
651 | 0 | return false; |
652 | 0 | case JSON_DUMPER_TYPE_OBJECT: |
653 | 0 | case JSON_DUMPER_TYPE_ARRAY: |
654 | 0 | case JSON_DUMPER_TYPE_BASE64: |
655 | | /* |
656 | | * This should never be the case, no matter what |
657 | | * our callers do: |
658 | | * |
659 | | * JSON_DUMPER_TYPE_OBJECT can be the previous |
660 | | * type, meaning we're in the process of adding |
661 | | * elements to an object, but it should never be |
662 | | * the current type; |
663 | | * |
664 | | * JSON_DUMPER_TYPE_ARRAY can be the previous |
665 | | * type, meaning we're in the process of adding |
666 | | * elements to an array, but it should never be |
667 | | * the current type; |
668 | | * |
669 | | * JSON_DUMPER_TYPE_BASE64 should only be the |
670 | | * current type if we're in the middle of |
671 | | * building a base64 value, in which case the |
672 | | * previous type should also be JSON_DUMPER_TYPE_BASE64, |
673 | | * but that's not the previous type. |
674 | | */ |
675 | 0 | json_dumper_bad(dumper, "internal error setting value - should not happen"); |
676 | 0 | return false; |
677 | 0 | default: |
678 | 0 | json_dumper_bad(dumper, "internal error setting value, bad current state - should not happen"); |
679 | 0 | return false; |
680 | 0 | } |
681 | 0 | break; |
682 | 0 | } |
683 | 0 | default: |
684 | 0 | json_dumper_bad(dumper, "internal error setting value, bad previous state - should not happen"); |
685 | 0 | return false; |
686 | 0 | } |
687 | 0 | return true; |
688 | 0 | } |
689 | | |
690 | | void |
691 | | json_dumper_value_string(json_dumper *dumper, const char *value) |
692 | 0 | { |
693 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
694 | 0 | return; |
695 | 0 | } |
696 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
697 | 0 | return; |
698 | 0 | } |
699 | | |
700 | 0 | prepare_token(dumper); |
701 | 0 | json_puts_string(dumper, value, false); |
702 | |
|
703 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
704 | 0 | } |
705 | | |
706 | | void |
707 | | json_dumper_value_string_noesc(json_dumper *dumper, const char *value, size_t len) |
708 | 0 | { |
709 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
710 | 0 | return; |
711 | 0 | } |
712 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
713 | 0 | return; |
714 | 0 | } |
715 | | |
716 | 0 | prepare_token(dumper); |
717 | 0 | jd_putc(dumper, '"'); |
718 | 0 | jd_puts_len(dumper, value, len); |
719 | 0 | jd_putc(dumper, '"'); |
720 | |
|
721 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
722 | 0 | } |
723 | | |
724 | | void |
725 | | json_dumper_value_double(json_dumper *dumper, double value) |
726 | 0 | { |
727 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
728 | 0 | return; |
729 | 0 | } |
730 | | |
731 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
732 | 0 | return; |
733 | 0 | } |
734 | | |
735 | 0 | prepare_token(dumper); |
736 | 0 | char buffer[G_ASCII_DTOSTR_BUF_SIZE] = { 0 }; |
737 | 0 | if (isfinite(value) && g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, value) && buffer[0]) { |
738 | 0 | jd_puts(dumper, buffer); |
739 | 0 | } else { |
740 | 0 | jd_puts(dumper, "null"); |
741 | 0 | } |
742 | |
|
743 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
744 | 0 | } |
745 | | |
746 | | void |
747 | | json_dumper_value_va_list(json_dumper *dumper, const char *format, va_list ap) |
748 | 0 | { |
749 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
750 | 0 | return; |
751 | 0 | } |
752 | | |
753 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
754 | 0 | return; |
755 | 0 | } |
756 | | |
757 | 0 | prepare_token(dumper); |
758 | 0 | jd_vprintf(dumper, format, ap); |
759 | |
|
760 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
761 | 0 | } |
762 | | |
763 | | void |
764 | | json_dumper_value_anyf(json_dumper *dumper, const char *format, ...) |
765 | 0 | { |
766 | 0 | va_list ap; |
767 | |
|
768 | 0 | va_start(ap, format); |
769 | 0 | json_dumper_value_va_list(dumper, format, ap); |
770 | 0 | va_end(ap); |
771 | 0 | } |
772 | | |
773 | | void |
774 | | json_dumper_value_literal(json_dumper *dumper, const char *literal, size_t len) |
775 | 0 | { |
776 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
777 | 0 | return; |
778 | 0 | } |
779 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
780 | 0 | return; |
781 | 0 | } |
782 | 0 | prepare_token(dumper); |
783 | 0 | jd_puts_len(dumper, literal, len); |
784 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
785 | 0 | } |
786 | | |
787 | | void |
788 | | json_dumper_value_int(json_dumper *dumper, int64_t value) |
789 | 0 | { |
790 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
791 | 0 | return; |
792 | 0 | } |
793 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
794 | 0 | return; |
795 | 0 | } |
796 | 0 | prepare_token(dumper); |
797 | |
|
798 | 0 | char buf[22]; /* -9223372036854775808\0 = 21 chars + extra */ |
799 | 0 | char *end = buf + sizeof(buf); |
800 | 0 | char *p = int64_to_str_back(end, value); |
801 | 0 | jd_puts_len(dumper, p, end - p); |
802 | |
|
803 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
804 | 0 | } |
805 | | |
806 | | void |
807 | | json_dumper_value_uint(json_dumper *dumper, uint64_t value) |
808 | 0 | { |
809 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
810 | 0 | return; |
811 | 0 | } |
812 | 0 | if (!json_dumper_setting_value_ok(dumper)) { |
813 | 0 | return; |
814 | 0 | } |
815 | 0 | prepare_token(dumper); |
816 | |
|
817 | 0 | char buf[21]; |
818 | 0 | char *end = buf + sizeof(buf); |
819 | 0 | char *p = uint64_to_str_back(end, value); |
820 | 0 | jd_puts_len(dumper, p, end - p); |
821 | |
|
822 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; |
823 | 0 | } |
824 | | |
825 | | bool |
826 | | json_dumper_finish(json_dumper *dumper) |
827 | 0 | { |
828 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
829 | 0 | return false; |
830 | 0 | } |
831 | | |
832 | 0 | if (dumper->current_depth != 0) { |
833 | 0 | json_dumper_bad(dumper, "JSON dumper stack not empty at finish"); |
834 | 0 | return false; |
835 | 0 | } |
836 | | |
837 | 0 | jd_putc(dumper, '\n'); |
838 | 0 | jd_flush(dumper); |
839 | 0 | dumper->state[0] = JSON_DUMPER_TYPE_NONE; |
840 | 0 | return true; |
841 | 0 | } |
842 | | |
843 | | void |
844 | | json_dumper_begin_base64(json_dumper *dumper) |
845 | 0 | { |
846 | 0 | json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_BASE64); |
847 | 0 | } |
848 | | |
849 | | void |
850 | | json_dumper_write_base64(json_dumper* dumper, const unsigned char *data, size_t len) |
851 | 0 | { |
852 | 0 | if (!json_dumper_check_previous_error(dumper)) { |
853 | 0 | return; |
854 | 0 | } |
855 | | |
856 | 0 | uint8_t prev_state = json_dumper_get_prev_state(dumper); |
857 | |
|
858 | 0 | if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) { |
859 | 0 | json_dumper_bad(dumper, "writing base64 data to a non-base64 value"); |
860 | 0 | return; |
861 | 0 | } |
862 | | |
863 | 0 | #define CHUNK_SIZE 1024 |
864 | 0 | char buf[(CHUNK_SIZE / 3 + 1) * 4 + 4]; |
865 | |
|
866 | 0 | while (len > 0) { |
867 | 0 | size_t chunk_size = len < CHUNK_SIZE ? len : CHUNK_SIZE; |
868 | 0 | size_t output_size = g_base64_encode_step(data, chunk_size, false, buf, &dumper->base64_state, &dumper->base64_save); |
869 | 0 | jd_puts_len(dumper, buf, output_size); |
870 | 0 | data += chunk_size; |
871 | 0 | len -= chunk_size; |
872 | 0 | } |
873 | |
|
874 | 0 | dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_BASE64; |
875 | 0 | } |
876 | | |
877 | | void |
878 | | json_dumper_end_base64(json_dumper *dumper) |
879 | 0 | { |
880 | 0 | json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_BASE64); |
881 | 0 | } |