1 /*- 2 * Copyright (c) 2010 Alistair Crooks <agc@NetBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 #include <sys/types.h> 26 27 #include <inttypes.h> 28 #include <regex.h> 29 #include <stdarg.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include "mj.h" 36 #include "defs.h" 37 38 /* save 'n' chars of 's' in malloc'd memory */ 39 static char * 40 strnsave(const char *s, int n, unsigned esc) 41 { 42 char *newc; 43 char *cp; 44 int i; 45 46 if (n < 0) { 47 n = (int)strlen(s); 48 } 49 NEWARRAY(char, cp, (n * 2) + 1, "strnsave", return NULL); 50 if (esc) { 51 newc = cp; 52 for (i = 0 ; i < n ; i++) { 53 if (*s == '\\') { 54 *newc++ = *s++; 55 } else if (*s == '"') { 56 *newc++ = '\\'; 57 } 58 *newc++ = *s++; 59 } 60 *newc = 0x0; 61 } else { 62 (void) memcpy(cp, s, (unsigned)n); 63 cp[n] = 0x0; 64 } 65 return cp; 66 } 67 68 /* look in an object for the item */ 69 static int 70 findentry(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 71 { 72 unsigned i; 73 74 for (i = from ; i < atom->c ; i += incr) { 75 if (strcmp(name, atom->value.v[i].value.s) == 0) { 76 return i; 77 } 78 } 79 return -1; 80 } 81 82 /* create a real number */ 83 static void 84 create_number(mj_t *atom, double d) 85 { 86 char number[128]; 87 88 atom->type = MJ_NUMBER; 89 atom->c = snprintf(number, sizeof(number), "%g", d); 90 atom->value.s = strnsave(number, (int)atom->c, 0); 91 } 92 93 /* create an integer */ 94 static void 95 create_integer(mj_t *atom, int64_t i) 96 { 97 char number[128]; 98 99 atom->type = MJ_NUMBER; 100 atom->c = snprintf(number, sizeof(number), "%" PRIi64, i); 101 atom->value.s = strnsave(number, (int)atom->c, 0); 102 } 103 104 /* create a string */ 105 static void 106 create_string(mj_t *atom, const char *s) 107 { 108 atom->type = MJ_STRING; 109 atom->value.s = strnsave(s, -1, 1); 110 atom->c = (unsigned)strlen(atom->value.s); 111 } 112 113 #define MJ_OPEN_BRACKET (MJ_OBJECT + 1) /* 8 */ 114 #define MJ_CLOSE_BRACKET (MJ_OPEN_BRACKET + 1) /* 9 */ 115 #define MJ_OPEN_BRACE (MJ_CLOSE_BRACKET + 1) /* 10 */ 116 #define MJ_CLOSE_BRACE (MJ_OPEN_BRACE + 1) /* 11 */ 117 #define MJ_COLON (MJ_CLOSE_BRACE + 1) /* 12 */ 118 #define MJ_COMMA (MJ_COLON + 1) /* 13 */ 119 120 /* return the token type, and start and finish locations in string */ 121 static int 122 gettok(const char *s, int *from, int *to, int *tok) 123 { 124 static regex_t tokregex; 125 regmatch_t matches[15]; 126 static int compiled; 127 128 if (!compiled) { 129 compiled = 1; 130 (void) regcomp(&tokregex, 131 "[ \t\r\n]*(([+-]?[0-9]{1,21}(\\.[0-9]*)?([eE][-+][0-9]+)?)|" 132 "(\"([^\"]|\\\\.)*\")|(null)|(false)|(true)|([][{}:,]))", 133 REG_EXTENDED); 134 } 135 if (regexec(&tokregex, &s[*from = *to], 15, matches, 0) != 0) { 136 return *tok = -1; 137 } 138 *to = *from + (int)(matches[1].rm_eo); 139 *tok = (matches[2].rm_so >= 0) ? MJ_NUMBER : 140 (matches[5].rm_so >= 0) ? MJ_STRING : 141 (matches[7].rm_so >= 0) ? MJ_NULL : 142 (matches[8].rm_so >= 0) ? MJ_FALSE : 143 (matches[9].rm_so >= 0) ? MJ_TRUE : 144 (matches[10].rm_so < 0) ? -1 : 145 (s[*from + (int)(matches[10].rm_so)] == '[') ? MJ_OPEN_BRACKET : 146 (s[*from + (int)(matches[10].rm_so)] == ']') ? MJ_CLOSE_BRACKET : 147 (s[*from + (int)(matches[10].rm_so)] == '{') ? MJ_OPEN_BRACE : 148 (s[*from + (int)(matches[10].rm_so)] == '}') ? MJ_CLOSE_BRACE : 149 (s[*from + (int)(matches[10].rm_so)] == ':') ? MJ_COLON : 150 MJ_COMMA; 151 *from += (int)(matches[1].rm_so); 152 return *tok; 153 } 154 155 /* minor function used to indent a JSON field */ 156 static void 157 indent(FILE *fp, unsigned depth, const char *trailer) 158 { 159 unsigned i; 160 161 for (i = 0 ; i < depth ; i++) { 162 (void) fprintf(fp, " "); 163 } 164 if (trailer) { 165 (void) fprintf(fp, "%s", trailer); 166 } 167 } 168 169 /***************************************************************************/ 170 171 /* return the number of entries in the array */ 172 int 173 mj_arraycount(mj_t *atom) 174 { 175 return atom->c; 176 } 177 178 /* create a new JSON node */ 179 int 180 mj_create(mj_t *atom, const char *type, ...) 181 { 182 va_list args; 183 184 if (strcmp(type, "false") == 0) { 185 atom->type = MJ_FALSE; 186 atom->c = 0; 187 } else if (strcmp(type, "true") == 0) { 188 atom->type = MJ_TRUE; 189 atom->c = 1; 190 } else if (strcmp(type, "null") == 0) { 191 atom->type = MJ_NULL; 192 } else if (strcmp(type, "number") == 0) { 193 va_start(args, type); 194 create_number(atom, (double)va_arg(args, double)); 195 va_end(args); 196 } else if (strcmp(type, "integer") == 0) { 197 va_start(args, type); 198 create_integer(atom, (int64_t)va_arg(args, int64_t)); 199 va_end(args); 200 } else if (strcmp(type, "string") == 0) { 201 va_start(args, type); 202 create_string(atom, (char *)va_arg(args, char *)); 203 va_end(args); 204 } else if (strcmp(type, "array") == 0) { 205 atom->type = MJ_ARRAY; 206 } else if (strcmp(type, "object") == 0) { 207 atom->type = MJ_OBJECT; 208 } else { 209 (void) fprintf(stderr, "weird type '%s'\n", type); 210 return 0; 211 } 212 return 1; 213 } 214 215 /* put a JSON tree into a text string */ 216 int 217 mj_snprint(char *buf, size_t size, mj_t *atom) 218 { 219 unsigned i; 220 int cc; 221 222 switch(atom->type) { 223 case MJ_NULL: 224 return snprintf(buf, size, "null"); 225 case MJ_FALSE: 226 return snprintf(buf, size, "false"); 227 case MJ_TRUE: 228 return snprintf(buf, size, "true"); 229 case MJ_NUMBER: 230 return snprintf(buf, size, "%s", atom->value.s); 231 case MJ_STRING: 232 return snprintf(buf, size, "\"%s\"", atom->value.s); 233 case MJ_ARRAY: 234 cc = snprintf(buf, size, "[ "); 235 for (i = 0 ; i < atom->c ; i++) { 236 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i]); 237 if (i < atom->c - 1) { 238 cc += snprintf(&buf[cc], size - cc, ", "); 239 } 240 } 241 return cc + snprintf(&buf[cc], size - cc, "]\n"); 242 case MJ_OBJECT: 243 cc = snprintf(buf, size, "{ "); 244 for (i = 0 ; i < atom->c ; i += 2) { 245 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i]); 246 cc += snprintf(&buf[cc], size - cc, ":"); 247 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i + 1]); 248 if (i + 1 < atom->c - 1) { 249 cc += snprintf(&buf[cc], size - cc, ", "); 250 } 251 } 252 return cc + snprintf(&buf[cc], size - cc, "}\n"); 253 default: 254 (void) fprintf(stderr, "mj_snprint: weird type %d\n", atom->type); 255 return 0; 256 } 257 } 258 259 /* allocate and print the atom */ 260 int 261 mj_asprint(char **buf, mj_t *atom) 262 { 263 int size; 264 265 size = mj_string_size(atom); 266 if ((*buf = calloc(1, (unsigned)(size + 1))) == NULL) { 267 return -1; 268 } 269 (void) mj_snprint(*buf, (unsigned)(size + 1), atom); 270 return size + 1; 271 } 272 273 /* read into a JSON tree from a string */ 274 int 275 mj_parse(mj_t *atom, const char *s, int *from, int *to, int *tok) 276 { 277 int i; 278 279 switch(atom->type = *tok = gettok(s, from, to, tok)) { 280 case MJ_NUMBER: 281 atom->value.s = strnsave(&s[*from], *to - *from, 1); 282 atom->c = atom->size = (unsigned)strlen(atom->value.s); 283 return gettok(s, from, to, tok); 284 case MJ_STRING: 285 atom->value.s = strnsave(&s[*from + 1], *to - *from - 2, 1); 286 atom->c = atom->size = (unsigned)strlen(atom->value.s); 287 return gettok(s, from, to, tok); 288 case MJ_NULL: 289 case MJ_FALSE: 290 case MJ_TRUE: 291 atom->c = (unsigned)*to; 292 return gettok(s, from, to, tok); 293 case MJ_OPEN_BRACKET: 294 mj_create(atom, "array"); 295 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 296 while (mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACKET) { 297 if (*tok != MJ_COMMA) { 298 (void) fprintf(stderr, "1. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 299 break; 300 } 301 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 302 } 303 return gettok(s, from, to, tok); 304 case MJ_OPEN_BRACE: 305 mj_create(atom, "object"); 306 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 307 for (i = 0 ; mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACE ; i++) { 308 if (((i % 2) == 0 && *tok != MJ_COLON) || ((i % 2) == 1 && *tok != MJ_COMMA)) { 309 (void) fprintf(stderr, "2. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 310 break; 311 } 312 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 313 } 314 return gettok(s, from, to, tok); 315 default: 316 return *tok; 317 } 318 } 319 320 /* return the index of the item which corresponds to the name in the array */ 321 int 322 mj_object_find(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 323 { 324 return findentry(atom, name, from, incr); 325 } 326 327 /* find an atom in a composite mj JSON node */ 328 mj_t * 329 mj_get_atom(mj_t *atom, ...) 330 { 331 unsigned i; 332 va_list args; 333 char *name; 334 int n; 335 336 switch(atom->type) { 337 case MJ_ARRAY: 338 va_start(args, atom); 339 i = va_arg(args, int); 340 va_end(args); 341 return (i < atom->c) ? &atom->value.v[i] : NULL; 342 case MJ_OBJECT: 343 va_start(args, atom); 344 name = va_arg(args, char *); 345 va_end(args); 346 return ((n = findentry(atom, name, 0, 2)) >= 0) ? &atom->value.v[n + 1] : NULL; 347 default: 348 return NULL; 349 } 350 } 351 352 /* perform a deep copy on an mj JSON atom */ 353 int 354 mj_deepcopy(mj_t *dst, mj_t *src) 355 { 356 unsigned i; 357 358 switch(src->type) { 359 case MJ_FALSE: 360 case MJ_TRUE: 361 case MJ_NULL: 362 (void) memcpy(dst, src, sizeof(*dst)); 363 return 1; 364 case MJ_STRING: 365 case MJ_NUMBER: 366 (void) memcpy(dst, src, sizeof(*dst)); 367 dst->value.s = strnsave(src->value.s, -1, 0); 368 dst->c = dst->size = (unsigned)strlen(dst->value.s); 369 return 1; 370 case MJ_ARRAY: 371 case MJ_OBJECT: 372 (void) memcpy(dst, src, sizeof(*dst)); 373 NEWARRAY(mj_t, dst->value.v, dst->size, "mj_deepcopy()", return 0); 374 for (i = 0 ; i < src->c ; i++) { 375 if (!mj_deepcopy(&dst->value.v[i], &src->value.v[i])) { 376 return 0; 377 } 378 } 379 return 1; 380 default: 381 (void) fprintf(stderr, "weird type '%d'\n", src->type); 382 return 0; 383 } 384 } 385 386 /* do a deep delete on the object */ 387 void 388 mj_delete(mj_t *atom) 389 { 390 unsigned i; 391 392 switch(atom->type) { 393 case MJ_STRING: 394 case MJ_NUMBER: 395 free(atom->value.s); 396 break; 397 case MJ_ARRAY: 398 case MJ_OBJECT: 399 for (i = 0 ; i < atom->c ; i++) { 400 mj_delete(&atom->value.v[i]); 401 } 402 break; 403 default: 404 break; 405 } 406 } 407 408 /* return the string size needed for the textual output of the JSON node */ 409 int 410 mj_string_size(mj_t *atom) 411 { 412 unsigned i; 413 int cc; 414 415 switch(atom->type) { 416 case MJ_NULL: 417 case MJ_TRUE: 418 return 4; 419 case MJ_FALSE: 420 return 5; 421 case MJ_NUMBER: 422 return atom->c; 423 case MJ_STRING: 424 return atom->c + 2; 425 case MJ_ARRAY: 426 for (cc = 2, i = 0 ; i < atom->c ; i++) { 427 cc += mj_string_size(&atom->value.v[i]); 428 if (i < atom->c - 1) { 429 cc += 2; 430 } 431 } 432 return cc + 1 + 1; 433 case MJ_OBJECT: 434 for (cc = 2, i = 0 ; i < atom->c ; i += 2) { 435 cc += mj_string_size(&atom->value.v[i]) + 1 + mj_string_size(&atom->value.v[i + 1]); 436 if (i + 1 < atom->c - 1) { 437 cc += 2; 438 } 439 } 440 return cc + 1 + 1; 441 default: 442 (void) fprintf(stderr, "mj_string_size: weird type %d\n", atom->type); 443 return 0; 444 } 445 } 446 447 /* create a new atom, and append it to the array or object */ 448 int 449 mj_append(mj_t *atom, const char *type, ...) 450 { 451 va_list args; 452 453 if (atom->type != MJ_ARRAY && atom->type != MJ_OBJECT) { 454 return 0; 455 } 456 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append()", return 0); 457 va_start(args, type); 458 if (strcmp(type, "string") == 0) { 459 create_string(&atom->value.v[atom->c++], (char *)va_arg(args, char *)); 460 } else if (strcmp(type, "integer") == 0) { 461 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 462 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 463 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 464 } else { 465 (void) fprintf(stderr, "mj_append: weird type '%s'\n", type); 466 } 467 va_end(args); 468 return 1; 469 } 470 471 /* append a field to an object */ 472 int 473 mj_append_field(mj_t *atom, const char *name, const char *type, ...) 474 { 475 va_list args; 476 477 if (atom->type != MJ_OBJECT) { 478 return 0; 479 } 480 mj_append(atom, "string", name); 481 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append_field()", return 0); 482 va_start(args, type); 483 if (strcmp(type, "string") == 0) { 484 create_string(&atom->value.v[atom->c++], (char *)va_arg(args, char *)); 485 } else if (strcmp(type, "integer") == 0) { 486 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 487 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 488 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 489 } else { 490 (void) fprintf(stderr, "mj_append_field: weird type '%s'\n", type); 491 } 492 va_end(args); 493 return 1; 494 } 495 496 /* make sure a JSON object is politically correct */ 497 int 498 mj_lint(mj_t *obj) 499 { 500 unsigned i; 501 int ret; 502 503 switch(obj->type) { 504 case MJ_NULL: 505 case MJ_FALSE: 506 case MJ_TRUE: 507 if (obj->value.s != NULL) { 508 (void) fprintf(stderr, "null/false/true: non zero string\n"); 509 return 0; 510 } 511 return 1; 512 case MJ_NUMBER: 513 case MJ_STRING: 514 if (obj->c > obj->size) { 515 (void) fprintf(stderr, "string/number lint c (%u) > size (%u)\n", obj->c, obj->size); 516 return 0; 517 } 518 return 1; 519 case MJ_ARRAY: 520 case MJ_OBJECT: 521 if (obj->c > obj->size) { 522 (void) fprintf(stderr, "array/object lint c (%u) > size (%u)\n", obj->c, obj->size); 523 return 0; 524 } 525 for (ret = 1, i = 0 ; i < obj->c ; i++) { 526 if (!mj_lint(&obj->value.v[i])) { 527 (void) fprintf(stderr, "array/object lint found at %d of %p\n", i, obj); 528 ret = 0; 529 } 530 } 531 return ret; 532 default: 533 (void) fprintf(stderr, "problem type %d in %p\n", obj->type, obj); 534 return 0; 535 } 536 } 537 538 /* pretty-print a JSON struct - can be called recursively */ 539 int 540 mj_pretty(mj_t *mj, void *vp, unsigned depth, const char *trailer) 541 { 542 unsigned i; 543 FILE *fp; 544 545 fp = (FILE *)vp; 546 switch(mj->type) { 547 case MJ_NUMBER: 548 case MJ_TRUE: 549 case MJ_FALSE: 550 case MJ_NULL: 551 indent(fp, depth, mj->value.s); 552 break; 553 case MJ_STRING: 554 indent(fp, depth, NULL); 555 (void) fprintf(fp, "\"%s\"", mj->value.s); 556 break; 557 case MJ_ARRAY: 558 indent(fp, depth, "[\n"); 559 for (i = 0 ; i < mj->c ; i++) { 560 mj_pretty(&mj->value.v[i], fp, depth + 1, (i < mj->c - 1) ? ",\n" : "\n"); 561 } 562 indent(fp, depth, "]"); 563 break; 564 case MJ_OBJECT: 565 indent(fp, depth, "{\n"); 566 for (i = 0 ; i < mj->c ; i += 2) { 567 mj_pretty(&mj->value.v[i], fp, depth + 1, " : "); 568 mj_pretty(&mj->value.v[i + 1], fp, 0, (i < mj->c - 2) ? ",\n" : "\n"); 569 } 570 indent(fp, depth, "}"); 571 break; 572 } 573 indent(fp, 0, trailer); 574 return 1; 575 } 576