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 encoded) 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 + n + 1, "strnsave", return NULL); 50 if (encoded) { 51 newc = cp; 52 for (i = 0 ; i < n ; i++) { 53 if ((uint8_t)*s == 0xac) { 54 *newc++ = (char)0xac; 55 *newc++ = '1'; 56 s += 1; 57 } else if (*s == '"') { 58 *newc++ = (char)0xac; 59 *newc++ = '2'; 60 s += 1; 61 } else if (*s == 0x0) { 62 *newc++ = (char)0xac; 63 *newc++ = '0'; 64 s += 1; 65 } else { 66 *newc++ = *s++; 67 } 68 } 69 *newc = 0x0; 70 } else { 71 (void) memcpy(cp, s, (unsigned)n); 72 cp[n] = 0x0; 73 } 74 return cp; 75 } 76 77 /* look in an object for the item */ 78 static int 79 findentry(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 80 { 81 unsigned i; 82 83 for (i = from ; i < atom->c ; i += incr) { 84 if (strcmp(name, atom->value.v[i].value.s) == 0) { 85 return i; 86 } 87 } 88 return -1; 89 } 90 91 /* create a real number */ 92 static void 93 create_number(mj_t *atom, double d) 94 { 95 char number[128]; 96 97 atom->type = MJ_NUMBER; 98 atom->c = snprintf(number, sizeof(number), "%g", d); 99 atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN); 100 } 101 102 /* create an integer */ 103 static void 104 create_integer(mj_t *atom, int64_t i) 105 { 106 char number[128]; 107 108 atom->type = MJ_NUMBER; 109 atom->c = snprintf(number, sizeof(number), "%" PRIi64, i); 110 atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN); 111 } 112 113 /* create a string */ 114 static void 115 create_string(mj_t *atom, const char *s, ssize_t len) 116 { 117 atom->type = MJ_STRING; 118 atom->value.s = strnsave(s, (int)len, MJ_JSON_ENCODE); 119 atom->c = (unsigned)strlen(atom->value.s); 120 } 121 122 #define MJ_OPEN_BRACKET (MJ_OBJECT + 1) /* 8 */ 123 #define MJ_CLOSE_BRACKET (MJ_OPEN_BRACKET + 1) /* 9 */ 124 #define MJ_OPEN_BRACE (MJ_CLOSE_BRACKET + 1) /* 10 */ 125 #define MJ_CLOSE_BRACE (MJ_OPEN_BRACE + 1) /* 11 */ 126 #define MJ_COLON (MJ_CLOSE_BRACE + 1) /* 12 */ 127 #define MJ_COMMA (MJ_COLON + 1) /* 13 */ 128 129 /* return the token type, and start and finish locations in string */ 130 static int 131 gettok(const char *s, int *from, int *to, int *tok) 132 { 133 static regex_t tokregex; 134 regmatch_t matches[15]; 135 static int compiled; 136 137 if (!compiled) { 138 compiled = 1; 139 (void) regcomp(&tokregex, 140 "[ \t\r\n]*(([+-]?[0-9]{1,21}(\\.[0-9]*)?([eE][-+][0-9]+)?)|" 141 "(\"([^\"]|\\\\.)*\")|(null)|(false)|(true)|([][{}:,]))", 142 REG_EXTENDED); 143 } 144 if (regexec(&tokregex, &s[*from = *to], 15, matches, 0) != 0) { 145 return *tok = -1; 146 } 147 *to = *from + (int)(matches[1].rm_eo); 148 *tok = (matches[2].rm_so >= 0) ? MJ_NUMBER : 149 (matches[5].rm_so >= 0) ? MJ_STRING : 150 (matches[7].rm_so >= 0) ? MJ_NULL : 151 (matches[8].rm_so >= 0) ? MJ_FALSE : 152 (matches[9].rm_so >= 0) ? MJ_TRUE : 153 (matches[10].rm_so < 0) ? -1 : 154 (s[*from + (int)(matches[10].rm_so)] == '[') ? MJ_OPEN_BRACKET : 155 (s[*from + (int)(matches[10].rm_so)] == ']') ? MJ_CLOSE_BRACKET : 156 (s[*from + (int)(matches[10].rm_so)] == '{') ? MJ_OPEN_BRACE : 157 (s[*from + (int)(matches[10].rm_so)] == '}') ? MJ_CLOSE_BRACE : 158 (s[*from + (int)(matches[10].rm_so)] == ':') ? MJ_COLON : 159 MJ_COMMA; 160 *from += (int)(matches[1].rm_so); 161 return *tok; 162 } 163 164 /* minor function used to indent a JSON field */ 165 static void 166 indent(FILE *fp, unsigned depth, const char *trailer) 167 { 168 unsigned i; 169 170 for (i = 0 ; i < depth ; i++) { 171 (void) fprintf(fp, " "); 172 } 173 if (trailer) { 174 (void) fprintf(fp, "%s", trailer); 175 } 176 } 177 178 /***************************************************************************/ 179 180 /* return the number of entries in the array */ 181 int 182 mj_arraycount(mj_t *atom) 183 { 184 return atom->c; 185 } 186 187 /* create a new JSON node */ 188 int 189 mj_create(mj_t *atom, const char *type, ...) 190 { 191 va_list args; 192 ssize_t len; 193 char *s; 194 195 if (strcmp(type, "false") == 0) { 196 atom->type = MJ_FALSE; 197 atom->c = 0; 198 } else if (strcmp(type, "true") == 0) { 199 atom->type = MJ_TRUE; 200 atom->c = 1; 201 } else if (strcmp(type, "null") == 0) { 202 atom->type = MJ_NULL; 203 } else if (strcmp(type, "number") == 0) { 204 va_start(args, type); 205 create_number(atom, (double)va_arg(args, double)); 206 va_end(args); 207 } else if (strcmp(type, "integer") == 0) { 208 va_start(args, type); 209 create_integer(atom, (int64_t)va_arg(args, int64_t)); 210 va_end(args); 211 } else if (strcmp(type, "string") == 0) { 212 va_start(args, type); 213 s = (char *)va_arg(args, char *); 214 len = (size_t)va_arg(args, size_t); 215 va_end(args); 216 create_string(atom, s, len); 217 } else if (strcmp(type, "array") == 0) { 218 atom->type = MJ_ARRAY; 219 } else if (strcmp(type, "object") == 0) { 220 atom->type = MJ_OBJECT; 221 } else { 222 (void) fprintf(stderr, "weird type '%s'\n", type); 223 return 0; 224 } 225 return 1; 226 } 227 228 /* put a JSON tree into a text string */ 229 int 230 mj_snprint(char *buf, size_t size, mj_t *atom, int encoded) 231 { 232 unsigned i; 233 char *s; 234 char *bp; 235 int cc; 236 237 switch(atom->type) { 238 case MJ_NULL: 239 return snprintf(buf, size, "null"); 240 case MJ_FALSE: 241 return snprintf(buf, size, "false"); 242 case MJ_TRUE: 243 return snprintf(buf, size, "true"); 244 case MJ_NUMBER: 245 return snprintf(buf, size, "%s", atom->value.s); 246 case MJ_STRING: 247 if (encoded) { 248 return snprintf(buf, size, "\"%s\"", atom->value.s); 249 } 250 for (bp = buf, *bp++ = '"', s = atom->value.s ; 251 (size_t)(bp - buf) < size && (unsigned)(s - atom->value.s) < atom->c ; ) { 252 if ((uint8_t)*s == 0xac) { 253 switch(s[1]) { 254 case '0': 255 *bp++ = 0x0; 256 s += 2; 257 break; 258 case '1': 259 *bp++ = (char)0xac; 260 s += 2; 261 break; 262 case '2': 263 *bp++ = '"'; 264 s += 2; 265 break; 266 default: 267 (void) fprintf(stderr, "unrecognised character '%02x'\n", (uint8_t)s[1]); 268 s += 1; 269 break; 270 } 271 } else { 272 *bp++ = *s++; 273 } 274 } 275 *bp++ = '"'; 276 *bp = 0x0; 277 return (int)(bp - buf) - 1; 278 case MJ_ARRAY: 279 cc = snprintf(buf, size, "[ "); 280 for (i = 0 ; i < atom->c ; i++) { 281 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded); 282 if (i < atom->c - 1) { 283 cc += snprintf(&buf[cc], size - cc, ", "); 284 } 285 } 286 return cc + snprintf(&buf[cc], size - cc, "]\n"); 287 case MJ_OBJECT: 288 cc = snprintf(buf, size, "{ "); 289 for (i = 0 ; i < atom->c ; i += 2) { 290 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded); 291 cc += snprintf(&buf[cc], size - cc, ":"); 292 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i + 1], encoded); 293 if (i + 1 < atom->c - 1) { 294 cc += snprintf(&buf[cc], size - cc, ", "); 295 } 296 } 297 return cc + snprintf(&buf[cc], size - cc, "}\n"); 298 default: 299 (void) fprintf(stderr, "mj_snprint: weird type %d\n", atom->type); 300 return 0; 301 } 302 } 303 304 /* allocate and print the atom */ 305 int 306 mj_asprint(char **buf, mj_t *atom, int encoded) 307 { 308 int size; 309 310 size = mj_string_size(atom); 311 if ((*buf = calloc(1, (unsigned)(size + 1))) == NULL) { 312 return -1; 313 } 314 return mj_snprint(*buf, (unsigned)(size + 1), atom, encoded) + 1; 315 } 316 317 /* read into a JSON tree from a string */ 318 int 319 mj_parse(mj_t *atom, const char *s, int *from, int *to, int *tok) 320 { 321 int i; 322 323 switch(atom->type = *tok = gettok(s, from, to, tok)) { 324 case MJ_NUMBER: 325 atom->value.s = strnsave(&s[*from], *to - *from, MJ_JSON_ENCODE); 326 atom->c = atom->size = (unsigned)strlen(atom->value.s); 327 return gettok(s, from, to, tok); 328 case MJ_STRING: 329 atom->value.s = strnsave(&s[*from + 1], *to - *from - 2, MJ_HUMAN); 330 atom->c = atom->size = (unsigned)strlen(atom->value.s); 331 return gettok(s, from, to, tok); 332 case MJ_NULL: 333 case MJ_FALSE: 334 case MJ_TRUE: 335 atom->c = (unsigned)*to; 336 return gettok(s, from, to, tok); 337 case MJ_OPEN_BRACKET: 338 mj_create(atom, "array"); 339 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 340 while (mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACKET) { 341 if (*tok != MJ_COMMA) { 342 (void) fprintf(stderr, "1. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 343 break; 344 } 345 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 346 } 347 return gettok(s, from, to, tok); 348 case MJ_OPEN_BRACE: 349 mj_create(atom, "object"); 350 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 351 for (i = 0 ; mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACE ; i++) { 352 if (((i % 2) == 0 && *tok != MJ_COLON) || ((i % 2) == 1 && *tok != MJ_COMMA)) { 353 (void) fprintf(stderr, "2. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 354 break; 355 } 356 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 357 } 358 return gettok(s, from, to, tok); 359 default: 360 return *tok; 361 } 362 } 363 364 /* return the index of the item which corresponds to the name in the array */ 365 int 366 mj_object_find(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 367 { 368 return findentry(atom, name, from, incr); 369 } 370 371 /* find an atom in a composite mj JSON node */ 372 mj_t * 373 mj_get_atom(mj_t *atom, ...) 374 { 375 unsigned i; 376 va_list args; 377 char *name; 378 int n; 379 380 switch(atom->type) { 381 case MJ_ARRAY: 382 va_start(args, atom); 383 i = va_arg(args, int); 384 va_end(args); 385 return (i < atom->c) ? &atom->value.v[i] : NULL; 386 case MJ_OBJECT: 387 va_start(args, atom); 388 name = va_arg(args, char *); 389 va_end(args); 390 return ((n = findentry(atom, name, 0, 2)) >= 0) ? &atom->value.v[n + 1] : NULL; 391 default: 392 return NULL; 393 } 394 } 395 396 /* perform a deep copy on an mj JSON atom */ 397 int 398 mj_deepcopy(mj_t *dst, mj_t *src) 399 { 400 unsigned i; 401 402 switch(src->type) { 403 case MJ_FALSE: 404 case MJ_TRUE: 405 case MJ_NULL: 406 (void) memcpy(dst, src, sizeof(*dst)); 407 return 1; 408 case MJ_STRING: 409 case MJ_NUMBER: 410 (void) memcpy(dst, src, sizeof(*dst)); 411 dst->value.s = strnsave(src->value.s, -1, MJ_HUMAN); 412 dst->c = dst->size = (unsigned)strlen(dst->value.s); 413 return 1; 414 case MJ_ARRAY: 415 case MJ_OBJECT: 416 (void) memcpy(dst, src, sizeof(*dst)); 417 NEWARRAY(mj_t, dst->value.v, dst->size, "mj_deepcopy()", return 0); 418 for (i = 0 ; i < src->c ; i++) { 419 if (!mj_deepcopy(&dst->value.v[i], &src->value.v[i])) { 420 return 0; 421 } 422 } 423 return 1; 424 default: 425 (void) fprintf(stderr, "weird type '%d'\n", src->type); 426 return 0; 427 } 428 } 429 430 /* do a deep delete on the object */ 431 void 432 mj_delete(mj_t *atom) 433 { 434 unsigned i; 435 436 switch(atom->type) { 437 case MJ_STRING: 438 case MJ_NUMBER: 439 free(atom->value.s); 440 break; 441 case MJ_ARRAY: 442 case MJ_OBJECT: 443 for (i = 0 ; i < atom->c ; i++) { 444 mj_delete(&atom->value.v[i]); 445 } 446 /* XXX - agc - causing problems? free(atom->value.v); */ 447 break; 448 default: 449 break; 450 } 451 } 452 453 /* return the string size needed for the textual output of the JSON node */ 454 int 455 mj_string_size(mj_t *atom) 456 { 457 unsigned i; 458 int cc; 459 460 switch(atom->type) { 461 case MJ_NULL: 462 case MJ_TRUE: 463 return 4; 464 case MJ_FALSE: 465 return 5; 466 case MJ_NUMBER: 467 return atom->c; 468 case MJ_STRING: 469 return atom->c + 2; 470 case MJ_ARRAY: 471 for (cc = 2, i = 0 ; i < atom->c ; i++) { 472 cc += mj_string_size(&atom->value.v[i]); 473 if (i < atom->c - 1) { 474 cc += 2; 475 } 476 } 477 return cc + 1 + 1; 478 case MJ_OBJECT: 479 for (cc = 2, i = 0 ; i < atom->c ; i += 2) { 480 cc += mj_string_size(&atom->value.v[i]) + 1 + mj_string_size(&atom->value.v[i + 1]); 481 if (i + 1 < atom->c - 1) { 482 cc += 2; 483 } 484 } 485 return cc + 1 + 1; 486 default: 487 (void) fprintf(stderr, "mj_string_size: weird type %d\n", atom->type); 488 return 0; 489 } 490 } 491 492 /* create a new atom, and append it to the array or object */ 493 int 494 mj_append(mj_t *atom, const char *type, ...) 495 { 496 va_list args; 497 ssize_t len; 498 char *s; 499 500 if (atom->type != MJ_ARRAY && atom->type != MJ_OBJECT) { 501 return 0; 502 } 503 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append()", return 0); 504 va_start(args, type); 505 if (strcmp(type, "string") == 0) { 506 s = (char *)va_arg(args, char *); 507 len = (ssize_t)va_arg(args, ssize_t); 508 create_string(&atom->value.v[atom->c++], s, len); 509 } else if (strcmp(type, "integer") == 0) { 510 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 511 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 512 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 513 } else { 514 (void) fprintf(stderr, "mj_append: weird type '%s'\n", type); 515 } 516 va_end(args); 517 return 1; 518 } 519 520 /* append a field to an object */ 521 int 522 mj_append_field(mj_t *atom, const char *name, const char *type, ...) 523 { 524 va_list args; 525 ssize_t len; 526 char *s; 527 528 if (atom->type != MJ_OBJECT) { 529 return 0; 530 } 531 mj_append(atom, "string", name, -1); 532 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append_field()", return 0); 533 va_start(args, type); 534 if (strcmp(type, "string") == 0) { 535 s = (char *)va_arg(args, char *); 536 len = (ssize_t)va_arg(args, ssize_t); 537 create_string(&atom->value.v[atom->c++], s, len); 538 } else if (strcmp(type, "integer") == 0) { 539 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 540 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 541 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 542 } else { 543 (void) fprintf(stderr, "mj_append_field: weird type '%s'\n", type); 544 } 545 va_end(args); 546 return 1; 547 } 548 549 /* make sure a JSON object is politically correct */ 550 int 551 mj_lint(mj_t *obj) 552 { 553 unsigned i; 554 int ret; 555 556 switch(obj->type) { 557 case MJ_NULL: 558 case MJ_FALSE: 559 case MJ_TRUE: 560 if (obj->value.s != NULL) { 561 (void) fprintf(stderr, "null/false/true: non zero string\n"); 562 return 0; 563 } 564 return 1; 565 case MJ_NUMBER: 566 case MJ_STRING: 567 if (obj->c > obj->size) { 568 (void) fprintf(stderr, "string/number lint c (%u) > size (%u)\n", obj->c, obj->size); 569 return 0; 570 } 571 return 1; 572 case MJ_ARRAY: 573 case MJ_OBJECT: 574 if (obj->c > obj->size) { 575 (void) fprintf(stderr, "array/object lint c (%u) > size (%u)\n", obj->c, obj->size); 576 return 0; 577 } 578 for (ret = 1, i = 0 ; i < obj->c ; i++) { 579 if (!mj_lint(&obj->value.v[i])) { 580 (void) fprintf(stderr, "array/object lint found at %d of %p\n", i, obj); 581 ret = 0; 582 } 583 } 584 return ret; 585 default: 586 (void) fprintf(stderr, "problem type %d in %p\n", obj->type, obj); 587 return 0; 588 } 589 } 590 591 /* pretty-print a JSON struct - can be called recursively */ 592 int 593 mj_pretty(mj_t *mj, void *vp, unsigned depth, const char *trailer) 594 { 595 unsigned i; 596 FILE *fp; 597 char *s; 598 599 fp = (FILE *)vp; 600 switch(mj->type) { 601 case MJ_NUMBER: 602 case MJ_TRUE: 603 case MJ_FALSE: 604 case MJ_NULL: 605 indent(fp, depth, mj->value.s); 606 break; 607 case MJ_STRING: 608 indent(fp, depth, NULL); 609 mj_asprint(&s, mj, MJ_HUMAN); 610 (void) fprintf(fp, "\"%s\"", s); 611 free(s); 612 break; 613 case MJ_ARRAY: 614 indent(fp, depth, "[\n"); 615 for (i = 0 ; i < mj->c ; i++) { 616 mj_pretty(&mj->value.v[i], fp, depth + 1, (i < mj->c - 1) ? ",\n" : "\n"); 617 } 618 indent(fp, depth, "]"); 619 break; 620 case MJ_OBJECT: 621 indent(fp, depth, "{\n"); 622 for (i = 0 ; i < mj->c ; i += 2) { 623 mj_pretty(&mj->value.v[i], fp, depth + 1, " : "); 624 mj_pretty(&mj->value.v[i + 1], fp, 0, (i < mj->c - 2) ? ",\n" : "\n"); 625 } 626 indent(fp, depth, "}"); 627 break; 628 } 629 indent(fp, 0, trailer); 630 return 1; 631 } 632 633 /* show the contents of the simple atom as a string representation */ 634 const char * 635 mj_string_rep(mj_t *atom) 636 { 637 if (atom == NULL) { 638 return 0; 639 } 640 switch(atom->type) { 641 case MJ_STRING: 642 case MJ_NUMBER: 643 return atom->value.s; 644 case MJ_NULL: 645 return "null"; 646 case MJ_FALSE: 647 return "false"; 648 case MJ_TRUE: 649 return "true"; 650 default: 651 return NULL; 652 } 653 } 654