xref: /openbsd/usr.sbin/radiusctl/json.c (revision 842565f2)
1 /*	$OpenBSD: json.c,v 1.1 2024/07/09 17:26:14 yasuoka 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 	int		compact;
42 	enum json_type	type;
43 } stack[JSON_MAX_STACK];
44 
45 static char indent[JSON_MAX_STACK + 1];
46 static int level;
47 static int eb;
48 static FILE *jsonfh;
49 
50 static void
do_comma_indent(void)51 do_comma_indent(void)
52 {
53 	char sp = '\n';
54 
55 	if (stack[level].compact)
56 		sp = ' ';
57 
58 	if (stack[level].count++ > 0) {
59 		if (!eb)
60 			eb = fprintf(jsonfh, ",%c", sp) < 0;
61 	}
62 
63 	if (stack[level].compact)
64 		return;
65 	if (!eb)
66 		eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0;
67 }
68 
69 static void
do_name(const char * name)70 do_name(const char *name)
71 {
72 	if (stack[level].type == ARRAY)
73 		return;
74 	if (!eb)
75 		eb = fprintf(jsonfh, "\"%s\": ", name) < 0;
76 }
77 
78 static int
do_find(enum json_type type,const char * name)79 do_find(enum json_type type, const char *name)
80 {
81 	int i;
82 
83 	for (i = level; i > 0; i--)
84 		if (type == stack[i].type &&
85 		    strcmp(name, stack[i].name) == 0)
86 			return i;
87 
88 	/* not found */
89 	return -1;
90 }
91 
92 void
json_do_start(FILE * fh)93 json_do_start(FILE *fh)
94 {
95 	memset(indent, '\t', JSON_MAX_STACK);
96 	memset(stack, 0, sizeof(stack));
97 	level = 0;
98 	stack[level].type = START;
99 	jsonfh = fh;
100 	eb = 0;
101 
102 	eb = fprintf(jsonfh, "{\n") < 0;
103 }
104 
105 int
json_do_finish(void)106 json_do_finish(void)
107 {
108 	while (level > 0)
109 		json_do_end();
110 	if (!eb)
111 		eb = fprintf(jsonfh, "\n}\n") < 0;
112 
113 	return -eb;
114 }
115 
116 void
json_do_array(const char * name)117 json_do_array(const char *name)
118 {
119 	int i, l;
120 	char sp = '\n';
121 
122 	if ((l = do_find(ARRAY, name)) > 0) {
123 		/* array already in use, close element and move on */
124 		for (i = level - l; i > 0; i--)
125 			json_do_end();
126 		return;
127 	}
128 	/* Do not stack arrays, while allowed this is not needed */
129 	if (stack[level].type == ARRAY)
130 		json_do_end();
131 
132 	if (stack[level].compact)
133 		sp = ' ';
134 	do_comma_indent();
135 	do_name(name);
136 	if (!eb)
137 		eb = fprintf(jsonfh, "[%c", sp) < 0;
138 
139 	if (++level >= JSON_MAX_STACK)
140 		errx(1, "json stack too deep");
141 
142 	stack[level].name = name;
143 	stack[level].type = ARRAY;
144 	stack[level].count = 0;
145 	/* inherit compact setting from above level */
146 	stack[level].compact = stack[level - 1].compact;
147 }
148 
149 void
json_do_object(const char * name,int compact)150 json_do_object(const char *name, int compact)
151 {
152 	int i, l;
153 	char sp = '\n';
154 
155 	if ((l = do_find(OBJECT, name)) > 0) {
156 		/* roll back to that object and close it */
157 		for (i = level - l; i >= 0; i--)
158 			json_do_end();
159 	}
160 
161 	if (compact)
162 		sp = ' ';
163 	do_comma_indent();
164 	do_name(name);
165 	if (!eb)
166 		eb = fprintf(jsonfh, "{%c", sp) < 0;
167 
168 	if (++level >= JSON_MAX_STACK)
169 		errx(1, "json stack too deep");
170 
171 	stack[level].name = name;
172 	stack[level].type = OBJECT;
173 	stack[level].count = 0;
174 	stack[level].compact = compact;
175 }
176 
177 void
json_do_end(void)178 json_do_end(void)
179 {
180 	char c;
181 
182 	if (stack[level].type == ARRAY)
183 		c = ']';
184 	else if (stack[level].type == OBJECT)
185 		c = '}';
186 	else
187 		errx(1, "json bad stack state");
188 
189 	if (!stack[level].compact) {
190 		if (!eb)
191 			eb = fprintf(jsonfh, "\n%.*s%c", level, indent, c) < 0;
192 	} else {
193 		if (!eb)
194 			eb = fprintf(jsonfh, " %c", c) < 0;
195 	}
196 
197 	stack[level].name = NULL;
198 	stack[level].type = NONE;
199 	stack[level].count = 0;
200 	stack[level].compact = 0;
201 
202 	if (level-- <= 0)
203 		errx(1, "json stack underflow");
204 
205 	stack[level].count++;
206 }
207 
208 void
json_do_printf(const char * name,const char * fmt,...)209 json_do_printf(const char *name, const char *fmt, ...)
210 {
211 	va_list ap;
212 	char *str;
213 
214 	va_start(ap, fmt);
215 	if (!eb) {
216 		if (vasprintf(&str, fmt, ap) == -1)
217 			errx(1, "json printf failed");
218 		json_do_string(name, str);
219 		free(str);
220 	}
221 	va_end(ap);
222 }
223 
224 void
json_do_string(const char * name,const char * v)225 json_do_string(const char *name, const char *v)
226 {
227 	unsigned char c;
228 
229 	do_comma_indent();
230 	do_name(name);
231 	if (!eb)
232 		eb = fprintf(jsonfh, "\"") < 0;
233 	while ((c = *v++) != '\0' && !eb) {
234 		/* skip escaping '/' since our use case does not require it */
235 		switch (c) {
236 		case '"':
237 			eb = fprintf(jsonfh, "\\\"") < 0;
238 			break;
239 		case '\\':
240 			eb = fprintf(jsonfh, "\\\\") < 0;
241 			break;
242 		case '\b':
243 			eb = fprintf(jsonfh, "\\b") < 0;
244 			break;
245 		case '\f':
246 			eb = fprintf(jsonfh, "\\f") < 0;
247 			break;
248 		case '\n':
249 			eb = fprintf(jsonfh, "\\n") < 0;
250 			break;
251 		case '\r':
252 			eb = fprintf(jsonfh, "\\r") < 0;
253 			break;
254 		case '\t':
255 			eb = fprintf(jsonfh, "\\t") < 0;
256 			break;
257 		default:
258 			if (iscntrl(c))
259 				errx(1, "bad control character in string");
260 			eb = putc(c, jsonfh) == EOF;
261 			break;
262 		}
263 	}
264 	if (!eb)
265 		eb = fprintf(jsonfh, "\"") < 0;
266 }
267 
268 void
json_do_hexdump(const char * name,void * buf,size_t len)269 json_do_hexdump(const char *name, void *buf, size_t len)
270 {
271 	uint8_t *data = buf;
272 	size_t i;
273 
274 	do_comma_indent();
275 	do_name(name);
276 	if (!eb)
277 		eb = fprintf(jsonfh, "\"") < 0;
278 	for (i = 0; i < len; i++)
279 		if (!eb)
280 			eb = fprintf(jsonfh, "%02x", *(data + i)) < 0;
281 	if (!eb)
282 		eb = fprintf(jsonfh, "\"") < 0;
283 }
284 
285 void
json_do_bool(const char * name,int v)286 json_do_bool(const char *name, int v)
287 {
288 	do_comma_indent();
289 	do_name(name);
290 	if (v) {
291 		if (!eb)
292 			eb = fprintf(jsonfh, "true") < 0;
293 	} else {
294 		if (!eb)
295 			eb = fprintf(jsonfh, "false") < 0;
296 	}
297 }
298 
299 void
json_do_uint(const char * name,unsigned long long v)300 json_do_uint(const char *name, unsigned long long v)
301 {
302 	do_comma_indent();
303 	do_name(name);
304 	if (!eb)
305 		eb = fprintf(jsonfh, "%llu", v) < 0;
306 }
307 
308 void
json_do_int(const char * name,long long v)309 json_do_int(const char *name, long long v)
310 {
311 	do_comma_indent();
312 	do_name(name);
313 	if (!eb)
314 		eb = fprintf(jsonfh, "%lld", v) < 0;
315 }
316 
317 void
json_do_double(const char * name,double v)318 json_do_double(const char *name, double v)
319 {
320 	do_comma_indent();
321 	do_name(name);
322 	if (!eb)
323 		eb = fprintf(jsonfh, "%f", v) < 0;
324 }
325