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