xref: /openbsd-src/usr.sbin/bgpctl/json.c (revision fc405d53b73a2d73393cb97f684863d17b583e38)
1 /*	$OpenBSD: json.c,v 1.8 2023/05/05 07:42:40 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <ctype.h>
20 #include <err.h>
21 #include <stdarg.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "json.h"
28 
29 #define JSON_MAX_STACK	16
30 
31 enum json_type {
32 	NONE,
33 	START,
34 	ARRAY,
35 	OBJECT
36 };
37 
38 static struct json_stack {
39 	const char	*name;
40 	unsigned int	count;
41 	enum json_type	type;
42 } stack[JSON_MAX_STACK];
43 
44 static char indent[JSON_MAX_STACK + 1];
45 static int level;
46 static int eb;
47 static FILE *jsonfh;
48 
49 static void
50 do_comma_indent(void)
51 {
52 	if (stack[level].count++ > 0)
53 		if (!eb)
54 			eb = fprintf(jsonfh, ",\n") < 0;
55 	if (!eb)
56 		eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0;
57 }
58 
59 static void
60 do_name(const char *name)
61 {
62 	if (stack[level].type == ARRAY)
63 		return;
64 	if (!eb)
65 		eb = fprintf(jsonfh, "\"%s\": ", name) < 0;
66 }
67 
68 static int
69 do_find(enum json_type type, const char *name)
70 {
71 	int i;
72 
73 	for (i = level; i > 0; i--)
74 		if (type == stack[i].type &&
75 		    strcmp(name, stack[i].name) == 0)
76 			return i;
77 
78 	/* not found */
79 	return -1;
80 }
81 
82 void
83 json_do_start(FILE *fh)
84 {
85 	memset(indent, '\t', JSON_MAX_STACK);
86 	memset(stack, 0, sizeof(stack));
87 	level = 0;
88 	stack[level].type = START;
89 	jsonfh = fh;
90 	eb = 0;
91 
92 	eb = fprintf(jsonfh, "{\n") < 0;
93 }
94 
95 int
96 json_do_finish(void)
97 {
98 	while (level > 0)
99 		json_do_end();
100 	if (!eb)
101 		eb = fprintf(jsonfh, "\n}\n") < 0;
102 
103 	return -eb;
104 }
105 
106 void
107 json_do_array(const char *name)
108 {
109 	int i, l;
110 
111 	if ((l = do_find(ARRAY, name)) > 0) {
112 		/* array already in use, close element and move on */
113 		for (i = level - l; i > 0; i--)
114 			json_do_end();
115 		return;
116 	}
117 	/* Do not stack arrays, while allowed this is not needed */
118 	if (stack[level].type == ARRAY)
119 		json_do_end();
120 
121 	do_comma_indent();
122 	do_name(name);
123 	if (!eb)
124 		eb = fprintf(jsonfh, "[\n") < 0;
125 
126 	if (++level >= JSON_MAX_STACK)
127 		errx(1, "json stack too deep");
128 
129 	stack[level].name = name;
130 	stack[level].type = ARRAY;
131 	stack[level].count = 0;
132 }
133 
134 void
135 json_do_object(const char *name)
136 {
137 	int i, l;
138 
139 	if ((l = do_find(OBJECT, name)) > 0) {
140 		/* roll back to that object and close it */
141 		for (i = level - l; i >= 0; i--)
142 			json_do_end();
143 	}
144 
145 	do_comma_indent();
146 	do_name(name);
147 	if (!eb)
148 		eb = fprintf(jsonfh, "{\n") < 0;
149 
150 	if (++level >= JSON_MAX_STACK)
151 		errx(1, "json stack too deep");
152 
153 	stack[level].name = name;
154 	stack[level].type = OBJECT;
155 	stack[level].count = 0;
156 }
157 
158 void
159 json_do_end(void)
160 {
161 	if (stack[level].type == ARRAY) {
162 		if (!eb)
163 			eb = fprintf(jsonfh, "\n%.*s]", level, indent) < 0;
164 	} else if (stack[level].type == OBJECT) {
165 		if (!eb)
166 			eb = fprintf(jsonfh, "\n%.*s}", level, indent) < 0;
167 	} else {
168 		errx(1, "json bad stack state");
169 	}
170 	stack[level].name = NULL;
171 	stack[level].type = NONE;
172 	stack[level].count = 0;
173 
174 	if (level-- <= 0)
175 		errx(1, "json stack underflow");
176 
177 	stack[level].count++;
178 }
179 
180 void
181 json_do_printf(const char *name, const char *fmt, ...)
182 {
183 	va_list ap;
184 	char *str;
185 
186 	va_start(ap, fmt);
187 	if (!eb) {
188 		if (vasprintf(&str, fmt, ap) == -1)
189 			errx(1, "json printf failed");
190 		json_do_string(name, str);
191 		free(str);
192 	}
193 	va_end(ap);
194 }
195 
196 void
197 json_do_string(const char *name, const char *v)
198 {
199 	unsigned char c;
200 
201 	do_comma_indent();
202 	do_name(name);
203 	if (!eb)
204 		eb = fprintf(jsonfh, "\"") < 0;
205 	while ((c = *v++) != '\0' && !eb) {
206 		/* skip escaping '/' since our use case does not require it */
207 		switch(c) {
208 		case '"':
209 			eb = fprintf(jsonfh, "\\\"") < 0;
210 			break;
211 		case '\\':
212 			eb = fprintf(jsonfh, "\\\\") < 0;
213 			break;
214 		case '\b':
215 			eb = fprintf(jsonfh, "\\b") < 0;
216 			break;
217 		case '\f':
218 			eb = fprintf(jsonfh, "\\f") < 0;
219 			break;
220 		case '\n':
221 			eb = fprintf(jsonfh, "\\n") < 0;
222 			break;
223 		case '\r':
224 			eb = fprintf(jsonfh, "\\r") < 0;
225 			break;
226 		case '\t':
227 			eb = fprintf(jsonfh, "\\t") < 0;
228 			break;
229 		default:
230 			if (iscntrl(c))
231 				errx(1, "bad control character in string");
232 			eb = putc(c, jsonfh) == EOF;
233 			break;
234 		}
235 	}
236 	if (!eb)
237 		eb = fprintf(jsonfh, "\"") < 0;
238 }
239 
240 void
241 json_do_hexdump(const char *name, void *buf, size_t len)
242 {
243 	uint8_t *data = buf;
244 	size_t i;
245 
246 	do_comma_indent();
247 	do_name(name);
248 	if (!eb)
249 		eb = fprintf(jsonfh, "\"") < 0;
250 	for (i = 0; i < len; i++)
251 		if (!eb)
252 			eb = fprintf(jsonfh, "%02x", *(data + i)) < 0;
253 	if (!eb)
254 		eb = fprintf(jsonfh, "\"") < 0;
255 }
256 
257 void
258 json_do_bool(const char *name, int v)
259 {
260 	do_comma_indent();
261 	do_name(name);
262 	if (v) {
263 		if (!eb)
264 			eb = fprintf(jsonfh, "true") < 0;
265 	} else {
266 		if (!eb)
267 			eb = fprintf(jsonfh, "false") < 0;
268 	}
269 }
270 
271 void
272 json_do_uint(const char *name, unsigned long long v)
273 {
274 	do_comma_indent();
275 	do_name(name);
276 	if (!eb)
277 		eb = fprintf(jsonfh, "%llu", v) < 0;
278 }
279 
280 void
281 json_do_int(const char *name, long long v)
282 {
283 	do_comma_indent();
284 	do_name(name);
285 	if (!eb)
286 		eb = fprintf(jsonfh, "%lld", v) < 0;
287 }
288 
289 void
290 json_do_double(const char *name, double v)
291 {
292 	do_comma_indent();
293 	do_name(name);
294 	if (!eb)
295 		eb = fprintf(jsonfh, "%f", v) < 0;
296 }
297