1 /* $OpenBSD: json.c,v 1.10 2023/06/22 09:07:04 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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