1*26fc0cbcSclaudio /* $OpenBSD: json.c,v 1.4 2023/06/22 09:08:02 claudio Exp $ */
215797777Sclaudio
315797777Sclaudio /*
415797777Sclaudio * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
515797777Sclaudio *
615797777Sclaudio * Permission to use, copy, modify, and distribute this software for any
715797777Sclaudio * purpose with or without fee is hereby granted, provided that the above
815797777Sclaudio * copyright notice and this permission notice appear in all copies.
915797777Sclaudio *
1015797777Sclaudio * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1115797777Sclaudio * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1215797777Sclaudio * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1315797777Sclaudio * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1415797777Sclaudio * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1515797777Sclaudio * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1615797777Sclaudio * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1715797777Sclaudio */
1815797777Sclaudio
19b42288f9Sclaudio #include <ctype.h>
2015797777Sclaudio #include <err.h>
2115797777Sclaudio #include <stdarg.h>
2215797777Sclaudio #include <stdint.h>
2315797777Sclaudio #include <stdio.h>
24b42288f9Sclaudio #include <stdlib.h>
2515797777Sclaudio #include <string.h>
2615797777Sclaudio
2715797777Sclaudio #include "json.h"
2815797777Sclaudio
2915797777Sclaudio #define JSON_MAX_STACK 16
3015797777Sclaudio
3115797777Sclaudio enum json_type {
3215797777Sclaudio NONE,
3315797777Sclaudio START,
3415797777Sclaudio ARRAY,
3515797777Sclaudio OBJECT
3615797777Sclaudio };
3715797777Sclaudio
3815797777Sclaudio static struct json_stack {
3915797777Sclaudio const char *name;
4015797777Sclaudio unsigned int count;
41a09a3191Sclaudio int compact;
4215797777Sclaudio enum json_type type;
4315797777Sclaudio } stack[JSON_MAX_STACK];
4415797777Sclaudio
4515797777Sclaudio static char indent[JSON_MAX_STACK + 1];
4615797777Sclaudio static int level;
4715797777Sclaudio static int eb;
4815797777Sclaudio static FILE *jsonfh;
4915797777Sclaudio
5015797777Sclaudio static void
do_comma_indent(void)5115797777Sclaudio do_comma_indent(void)
5215797777Sclaudio {
53a09a3191Sclaudio char sp = '\n';
54a09a3191Sclaudio
55a09a3191Sclaudio if (stack[level].compact)
56a09a3191Sclaudio sp = ' ';
57a09a3191Sclaudio
58a09a3191Sclaudio if (stack[level].count++ > 0) {
5915797777Sclaudio if (!eb)
60a09a3191Sclaudio eb = fprintf(jsonfh, ",%c", sp) < 0;
61a09a3191Sclaudio }
62a09a3191Sclaudio
63a09a3191Sclaudio if (stack[level].compact)
64a09a3191Sclaudio return;
6515797777Sclaudio if (!eb)
6615797777Sclaudio eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0;
6715797777Sclaudio }
6815797777Sclaudio
6915797777Sclaudio static void
do_name(const char * name)7015797777Sclaudio do_name(const char *name)
7115797777Sclaudio {
7215797777Sclaudio if (stack[level].type == ARRAY)
7315797777Sclaudio return;
7415797777Sclaudio if (!eb)
7515797777Sclaudio eb = fprintf(jsonfh, "\"%s\": ", name) < 0;
7615797777Sclaudio }
7715797777Sclaudio
7815797777Sclaudio static int
do_find(enum json_type type,const char * name)7915797777Sclaudio do_find(enum json_type type, const char *name)
8015797777Sclaudio {
8115797777Sclaudio int i;
8215797777Sclaudio
8315797777Sclaudio for (i = level; i > 0; i--)
8415797777Sclaudio if (type == stack[i].type &&
8515797777Sclaudio strcmp(name, stack[i].name) == 0)
8615797777Sclaudio return i;
8715797777Sclaudio
8815797777Sclaudio /* not found */
8915797777Sclaudio return -1;
9015797777Sclaudio }
9115797777Sclaudio
9215797777Sclaudio void
json_do_start(FILE * fh)9315797777Sclaudio json_do_start(FILE *fh)
9415797777Sclaudio {
9515797777Sclaudio memset(indent, '\t', JSON_MAX_STACK);
9615797777Sclaudio memset(stack, 0, sizeof(stack));
9715797777Sclaudio level = 0;
9815797777Sclaudio stack[level].type = START;
9915797777Sclaudio jsonfh = fh;
10015797777Sclaudio eb = 0;
10115797777Sclaudio
10215797777Sclaudio eb = fprintf(jsonfh, "{\n") < 0;
10315797777Sclaudio }
10415797777Sclaudio
10515797777Sclaudio int
json_do_finish(void)10615797777Sclaudio json_do_finish(void)
10715797777Sclaudio {
10815797777Sclaudio while (level > 0)
10915797777Sclaudio json_do_end();
11015797777Sclaudio if (!eb)
11115797777Sclaudio eb = fprintf(jsonfh, "\n}\n") < 0;
11215797777Sclaudio
11315797777Sclaudio return -eb;
11415797777Sclaudio }
11515797777Sclaudio
11615797777Sclaudio void
json_do_array(const char * name)11715797777Sclaudio json_do_array(const char *name)
11815797777Sclaudio {
11915797777Sclaudio int i, l;
120a09a3191Sclaudio char sp = '\n';
12115797777Sclaudio
12215797777Sclaudio if ((l = do_find(ARRAY, name)) > 0) {
12315797777Sclaudio /* array already in use, close element and move on */
12415797777Sclaudio for (i = level - l; i > 0; i--)
12515797777Sclaudio json_do_end();
12615797777Sclaudio return;
12715797777Sclaudio }
12815797777Sclaudio /* Do not stack arrays, while allowed this is not needed */
12915797777Sclaudio if (stack[level].type == ARRAY)
13015797777Sclaudio json_do_end();
13115797777Sclaudio
132a09a3191Sclaudio if (stack[level].compact)
133a09a3191Sclaudio sp = ' ';
13415797777Sclaudio do_comma_indent();
13515797777Sclaudio do_name(name);
13615797777Sclaudio if (!eb)
137a09a3191Sclaudio eb = fprintf(jsonfh, "[%c", sp) < 0;
13815797777Sclaudio
13915797777Sclaudio if (++level >= JSON_MAX_STACK)
14015797777Sclaudio errx(1, "json stack too deep");
14115797777Sclaudio
14215797777Sclaudio stack[level].name = name;
14315797777Sclaudio stack[level].type = ARRAY;
14415797777Sclaudio stack[level].count = 0;
145a09a3191Sclaudio /* inherit compact setting from above level */
146a09a3191Sclaudio stack[level].compact = stack[level - 1].compact;
14715797777Sclaudio }
14815797777Sclaudio
14915797777Sclaudio void
json_do_object(const char * name,int compact)150a09a3191Sclaudio json_do_object(const char *name, int compact)
15115797777Sclaudio {
15215797777Sclaudio int i, l;
153a09a3191Sclaudio char sp = '\n';
15415797777Sclaudio
15515797777Sclaudio if ((l = do_find(OBJECT, name)) > 0) {
15615797777Sclaudio /* roll back to that object and close it */
15715797777Sclaudio for (i = level - l; i >= 0; i--)
15815797777Sclaudio json_do_end();
15915797777Sclaudio }
16015797777Sclaudio
161a09a3191Sclaudio if (compact)
162a09a3191Sclaudio sp = ' ';
16315797777Sclaudio do_comma_indent();
16415797777Sclaudio do_name(name);
16515797777Sclaudio if (!eb)
166a09a3191Sclaudio eb = fprintf(jsonfh, "{%c", sp) < 0;
16715797777Sclaudio
16815797777Sclaudio if (++level >= JSON_MAX_STACK)
16915797777Sclaudio errx(1, "json stack too deep");
17015797777Sclaudio
17115797777Sclaudio stack[level].name = name;
17215797777Sclaudio stack[level].type = OBJECT;
17315797777Sclaudio stack[level].count = 0;
174a09a3191Sclaudio stack[level].compact = compact;
17515797777Sclaudio }
17615797777Sclaudio
17715797777Sclaudio void
json_do_end(void)17815797777Sclaudio json_do_end(void)
17915797777Sclaudio {
180a09a3191Sclaudio char c;
181a09a3191Sclaudio
182a09a3191Sclaudio if (stack[level].type == ARRAY)
183a09a3191Sclaudio c = ']';
184a09a3191Sclaudio else if (stack[level].type == OBJECT)
185a09a3191Sclaudio c = '}';
186a09a3191Sclaudio else
18715797777Sclaudio errx(1, "json bad stack state");
188a09a3191Sclaudio
189a09a3191Sclaudio if (!stack[level].compact) {
190a09a3191Sclaudio if (!eb)
191a09a3191Sclaudio eb = fprintf(jsonfh, "\n%.*s%c", level, indent, c) < 0;
192a09a3191Sclaudio } else {
193a09a3191Sclaudio if (!eb)
194a09a3191Sclaudio eb = fprintf(jsonfh, " %c", c) < 0;
19515797777Sclaudio }
196a09a3191Sclaudio
19715797777Sclaudio stack[level].name = NULL;
19815797777Sclaudio stack[level].type = NONE;
19915797777Sclaudio stack[level].count = 0;
200a09a3191Sclaudio stack[level].compact = 0;
20115797777Sclaudio
20215797777Sclaudio if (level-- <= 0)
20315797777Sclaudio errx(1, "json stack underflow");
20415797777Sclaudio
20515797777Sclaudio stack[level].count++;
20615797777Sclaudio }
20715797777Sclaudio
20815797777Sclaudio void
json_do_printf(const char * name,const char * fmt,...)20915797777Sclaudio json_do_printf(const char *name, const char *fmt, ...)
21015797777Sclaudio {
21115797777Sclaudio va_list ap;
212b42288f9Sclaudio char *str;
213b42288f9Sclaudio
214b42288f9Sclaudio va_start(ap, fmt);
215b42288f9Sclaudio if (!eb) {
216b42288f9Sclaudio if (vasprintf(&str, fmt, ap) == -1)
217b42288f9Sclaudio errx(1, "json printf failed");
218b42288f9Sclaudio json_do_string(name, str);
219b42288f9Sclaudio free(str);
220b42288f9Sclaudio }
221b42288f9Sclaudio va_end(ap);
222b42288f9Sclaudio }
223b42288f9Sclaudio
224b42288f9Sclaudio void
json_do_string(const char * name,const char * v)225b42288f9Sclaudio json_do_string(const char *name, const char *v)
226b42288f9Sclaudio {
227b42288f9Sclaudio unsigned char c;
22815797777Sclaudio
22915797777Sclaudio do_comma_indent();
23015797777Sclaudio do_name(name);
23115797777Sclaudio if (!eb)
23215797777Sclaudio eb = fprintf(jsonfh, "\"") < 0;
233b42288f9Sclaudio while ((c = *v++) != '\0' && !eb) {
234b42288f9Sclaudio /* skip escaping '/' since our use case does not require it */
235b42288f9Sclaudio switch (c) {
236b42288f9Sclaudio case '"':
237b42288f9Sclaudio eb = fprintf(jsonfh, "\\\"") < 0;
238b42288f9Sclaudio break;
239b42288f9Sclaudio case '\\':
240b42288f9Sclaudio eb = fprintf(jsonfh, "\\\\") < 0;
241b42288f9Sclaudio break;
242b42288f9Sclaudio case '\b':
243b42288f9Sclaudio eb = fprintf(jsonfh, "\\b") < 0;
244b42288f9Sclaudio break;
245b42288f9Sclaudio case '\f':
246b42288f9Sclaudio eb = fprintf(jsonfh, "\\f") < 0;
247b42288f9Sclaudio break;
248b42288f9Sclaudio case '\n':
249b42288f9Sclaudio eb = fprintf(jsonfh, "\\n") < 0;
250b42288f9Sclaudio break;
251b42288f9Sclaudio case '\r':
252b42288f9Sclaudio eb = fprintf(jsonfh, "\\r") < 0;
253b42288f9Sclaudio break;
254b42288f9Sclaudio case '\t':
255b42288f9Sclaudio eb = fprintf(jsonfh, "\\t") < 0;
256b42288f9Sclaudio break;
257b42288f9Sclaudio default:
258b42288f9Sclaudio if (iscntrl(c))
259b42288f9Sclaudio errx(1, "bad control character in string");
260b42288f9Sclaudio eb = putc(c, jsonfh) == EOF;
261b42288f9Sclaudio break;
262b42288f9Sclaudio }
263b42288f9Sclaudio }
26415797777Sclaudio if (!eb)
26515797777Sclaudio eb = fprintf(jsonfh, "\"") < 0;
26615797777Sclaudio }
26715797777Sclaudio
26815797777Sclaudio void
json_do_hexdump(const char * name,void * buf,size_t len)26915797777Sclaudio json_do_hexdump(const char *name, void *buf, size_t len)
27015797777Sclaudio {
27115797777Sclaudio uint8_t *data = buf;
27215797777Sclaudio size_t i;
27315797777Sclaudio
27415797777Sclaudio do_comma_indent();
27515797777Sclaudio do_name(name);
27615797777Sclaudio if (!eb)
27715797777Sclaudio eb = fprintf(jsonfh, "\"") < 0;
27815797777Sclaudio for (i = 0; i < len; i++)
27915797777Sclaudio if (!eb)
28015797777Sclaudio eb = fprintf(jsonfh, "%02x", *(data + i)) < 0;
28115797777Sclaudio if (!eb)
28215797777Sclaudio eb = fprintf(jsonfh, "\"") < 0;
28315797777Sclaudio }
28415797777Sclaudio
28515797777Sclaudio void
json_do_bool(const char * name,int v)28615797777Sclaudio json_do_bool(const char *name, int v)
28715797777Sclaudio {
28815797777Sclaudio do_comma_indent();
28915797777Sclaudio do_name(name);
29015797777Sclaudio if (v) {
29115797777Sclaudio if (!eb)
29215797777Sclaudio eb = fprintf(jsonfh, "true") < 0;
29315797777Sclaudio } else {
29415797777Sclaudio if (!eb)
29515797777Sclaudio eb = fprintf(jsonfh, "false") < 0;
29615797777Sclaudio }
29715797777Sclaudio }
29815797777Sclaudio
29915797777Sclaudio void
json_do_uint(const char * name,unsigned long long v)30015797777Sclaudio json_do_uint(const char *name, unsigned long long v)
30115797777Sclaudio {
30215797777Sclaudio do_comma_indent();
30315797777Sclaudio do_name(name);
30415797777Sclaudio if (!eb)
30515797777Sclaudio eb = fprintf(jsonfh, "%llu", v) < 0;
30615797777Sclaudio }
30715797777Sclaudio
30815797777Sclaudio void
json_do_int(const char * name,long long v)30915797777Sclaudio json_do_int(const char *name, long long v)
31015797777Sclaudio {
31115797777Sclaudio do_comma_indent();
31215797777Sclaudio do_name(name);
31315797777Sclaudio if (!eb)
31415797777Sclaudio eb = fprintf(jsonfh, "%lld", v) < 0;
31515797777Sclaudio }
31615797777Sclaudio
31715797777Sclaudio void
json_do_double(const char * name,double v)31815797777Sclaudio json_do_double(const char *name, double v)
31915797777Sclaudio {
32015797777Sclaudio do_comma_indent();
32115797777Sclaudio do_name(name);
32215797777Sclaudio if (!eb)
32315797777Sclaudio eb = fprintf(jsonfh, "%f", v) < 0;
32415797777Sclaudio }
325