xref: /openbsd-src/usr.sbin/rpki-client/json.c (revision 26fc0cbcc3a2e62df0cb0641b813df7ac92d6147)
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