1 /* $OpenBSD: json.c,v 1.2 2023/05/03 07:56:05 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