1 /* $NetBSD: json.c,v 1.2 2017/01/28 21:31:45 christos Exp $ */ 2 3 /* 4 * Copyright (c) 2010 Kungliga Tekniska Högskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Portions Copyright (c) 2010 Apple Inc. All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * 3. Neither the name of the Institute nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38 #include "baselocl.h" 39 #include <ctype.h> 40 #include <krb5/base64.h> 41 42 static heim_base_once_t heim_json_once = HEIM_BASE_ONCE_INIT; 43 static heim_string_t heim_tid_data_uuid_key = NULL; 44 static const char base64_chars[] = 45 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 46 47 static void 48 json_init_once(void *arg) 49 { 50 heim_tid_data_uuid_key = __heim_string_constant("heimdal-type-data-76d7fca2-d0da-4b20-a126-1a10f8a0eae6"); 51 } 52 53 struct twojson { 54 void *ctx; 55 void (*out)(void *, const char *); 56 size_t indent; 57 heim_json_flags_t flags; 58 int ret; 59 int first; 60 }; 61 62 struct heim_strbuf { 63 char *str; 64 size_t len; 65 size_t alloced; 66 int enomem; 67 heim_json_flags_t flags; 68 }; 69 70 static int 71 base2json(heim_object_t, struct twojson *); 72 73 static void 74 indent(struct twojson *j) 75 { 76 size_t i = j->indent; 77 if (j->flags & HEIM_JSON_F_ONE_LINE) 78 return; 79 while (i--) 80 j->out(j->ctx, "\t"); 81 } 82 83 static void 84 array2json(heim_object_t value, void *ctx, int *stop) 85 { 86 struct twojson *j = ctx; 87 if (j->ret) 88 return; 89 if (j->first) { 90 j->first = 0; 91 } else { 92 j->out(j->ctx, NULL); /* eat previous '\n' if possible */ 93 j->out(j->ctx, ",\n"); 94 } 95 j->ret = base2json(value, j); 96 } 97 98 static void 99 dict2json(heim_object_t key, heim_object_t value, void *ctx) 100 { 101 struct twojson *j = ctx; 102 if (j->ret) 103 return; 104 if (j->first) { 105 j->first = 0; 106 } else { 107 j->out(j->ctx, NULL); /* eat previous '\n' if possible */ 108 j->out(j->ctx, ",\n"); 109 } 110 j->ret = base2json(key, j); 111 if (j->ret) 112 return; 113 j->out(j->ctx, " : \n"); 114 j->indent++; 115 j->ret = base2json(value, j); 116 if (j->ret) 117 return; 118 j->indent--; 119 } 120 121 static int 122 base2json(heim_object_t obj, struct twojson *j) 123 { 124 heim_tid_t type; 125 int first = 0; 126 127 if (obj == NULL) { 128 if (j->flags & HEIM_JSON_F_CNULL2JSNULL) { 129 obj = heim_null_create(); 130 } else if (j->flags & HEIM_JSON_F_NO_C_NULL) { 131 return EINVAL; 132 } else { 133 indent(j); 134 j->out(j->ctx, "<NULL>\n"); /* This is NOT valid JSON! */ 135 return 0; 136 } 137 } 138 139 type = heim_get_tid(obj); 140 switch (type) { 141 case HEIM_TID_ARRAY: 142 indent(j); 143 j->out(j->ctx, "[\n"); 144 j->indent++; 145 first = j->first; 146 j->first = 1; 147 heim_array_iterate_f(obj, j, array2json); 148 j->indent--; 149 if (!j->first) 150 j->out(j->ctx, "\n"); 151 indent(j); 152 j->out(j->ctx, "]\n"); 153 j->first = first; 154 break; 155 156 case HEIM_TID_DICT: 157 indent(j); 158 j->out(j->ctx, "{\n"); 159 j->indent++; 160 first = j->first; 161 j->first = 1; 162 heim_dict_iterate_f(obj, j, dict2json); 163 j->indent--; 164 if (!j->first) 165 j->out(j->ctx, "\n"); 166 indent(j); 167 j->out(j->ctx, "}\n"); 168 j->first = first; 169 break; 170 171 case HEIM_TID_STRING: 172 indent(j); 173 j->out(j->ctx, "\""); 174 j->out(j->ctx, heim_string_get_utf8(obj)); 175 j->out(j->ctx, "\""); 176 break; 177 178 case HEIM_TID_DATA: { 179 heim_dict_t d; 180 heim_string_t v; 181 const heim_octet_string *data; 182 char *b64 = NULL; 183 int ret; 184 185 if (j->flags & HEIM_JSON_F_NO_DATA) 186 return EINVAL; /* JSON doesn't do binary */ 187 188 data = heim_data_get_data(obj); 189 ret = rk_base64_encode(data->data, data->length, &b64); 190 if (ret < 0 || b64 == NULL) 191 return ENOMEM; 192 193 if (j->flags & HEIM_JSON_F_NO_DATA_DICT) { 194 indent(j); 195 j->out(j->ctx, "\""); 196 j->out(j->ctx, b64); /* base64-encode; hope there's no aliasing */ 197 j->out(j->ctx, "\""); 198 free(b64); 199 } else { 200 /* 201 * JSON has no way to represent binary data, therefore the 202 * following is a Heimdal-specific convention. 203 * 204 * We encode binary data as a dict with a single very magic 205 * key with a base64-encoded value. The magic key includes 206 * a uuid, so we're not likely to alias accidentally. 207 */ 208 d = heim_dict_create(2); 209 if (d == NULL) { 210 free(b64); 211 return ENOMEM; 212 } 213 v = heim_string_ref_create(b64, free); 214 if (v == NULL) { 215 free(b64); 216 heim_release(d); 217 return ENOMEM; 218 } 219 ret = heim_dict_set_value(d, heim_tid_data_uuid_key, v); 220 heim_release(v); 221 if (ret) { 222 heim_release(d); 223 return ENOMEM; 224 } 225 ret = base2json(d, j); 226 heim_release(d); 227 if (ret) 228 return ret; 229 } 230 break; 231 } 232 233 case HEIM_TID_NUMBER: { 234 char num[32]; 235 indent(j); 236 snprintf(num, sizeof (num), "%d", heim_number_get_int(obj)); 237 j->out(j->ctx, num); 238 break; 239 } 240 case HEIM_TID_NULL: 241 indent(j); 242 j->out(j->ctx, "null"); 243 break; 244 case HEIM_TID_BOOL: 245 indent(j); 246 j->out(j->ctx, heim_bool_val(obj) ? "true" : "false"); 247 break; 248 default: 249 return 1; 250 } 251 return 0; 252 } 253 254 static int 255 heim_base2json(heim_object_t obj, void *ctx, heim_json_flags_t flags, 256 void (*out)(void *, const char *)) 257 { 258 struct twojson j; 259 260 if (flags & HEIM_JSON_F_STRICT_STRINGS) 261 return ENOTSUP; /* Sorry, not yet! */ 262 263 heim_base_once_f(&heim_json_once, NULL, json_init_once); 264 265 j.indent = 0; 266 j.ctx = ctx; 267 j.out = out; 268 j.flags = flags; 269 j.ret = 0; 270 j.first = 1; 271 272 return base2json(obj, &j); 273 } 274 275 276 /* 277 * 278 */ 279 280 struct parse_ctx { 281 unsigned long lineno; 282 const uint8_t *p; 283 const uint8_t *pstart; 284 const uint8_t *pend; 285 heim_error_t error; 286 size_t depth; 287 heim_json_flags_t flags; 288 }; 289 290 291 static heim_object_t 292 parse_value(struct parse_ctx *ctx); 293 294 /* 295 * This function eats whitespace, but, critically, it also succeeds 296 * only if there's anything left to parse. 297 */ 298 static int 299 white_spaces(struct parse_ctx *ctx) 300 { 301 while (ctx->p < ctx->pend) { 302 uint8_t c = *ctx->p; 303 if (c == ' ' || c == '\t' || c == '\r') { 304 305 } else if (c == '\n') { 306 ctx->lineno++; 307 } else 308 return 0; 309 (ctx->p)++; 310 } 311 return -1; 312 } 313 314 static int 315 is_number(uint8_t n) 316 { 317 return ('0' <= n && n <= '9'); 318 } 319 320 static heim_number_t 321 parse_number(struct parse_ctx *ctx) 322 { 323 int number = 0, neg = 1; 324 325 if (ctx->p >= ctx->pend) 326 return NULL; 327 328 if (*ctx->p == '-') { 329 if (ctx->p + 1 >= ctx->pend) 330 return NULL; 331 neg = -1; 332 ctx->p += 1; 333 } 334 335 while (ctx->p < ctx->pend) { 336 if (is_number(*ctx->p)) { 337 number = (number * 10) + (*ctx->p - '0'); 338 } else { 339 break; 340 } 341 ctx->p += 1; 342 } 343 344 return heim_number_create(number * neg); 345 } 346 347 static heim_string_t 348 parse_string(struct parse_ctx *ctx) 349 { 350 const uint8_t *start; 351 int quote = 0; 352 353 if (ctx->flags & HEIM_JSON_F_STRICT_STRINGS) { 354 ctx->error = heim_error_create(EINVAL, "Strict JSON string encoding " 355 "not yet supported"); 356 return NULL; 357 } 358 359 if (*ctx->p != '"') { 360 ctx->error = heim_error_create(EINVAL, "Expected a JSON string but " 361 "found something else at line %lu", 362 ctx->lineno); 363 return NULL; 364 } 365 start = ++ctx->p; 366 367 while (ctx->p < ctx->pend) { 368 if (*ctx->p == '\n') { 369 ctx->lineno++; 370 } else if (*ctx->p == '\\') { 371 if (ctx->p + 1 == ctx->pend) 372 goto out; 373 ctx->p++; 374 quote = 1; 375 } else if (*ctx->p == '"') { 376 heim_object_t o; 377 378 if (quote) { 379 char *p0, *p; 380 p = p0 = malloc(ctx->p - start); 381 if (p == NULL) 382 goto out; 383 while (start < ctx->p) { 384 if (*start == '\\') { 385 start++; 386 /* XXX validate quoted char */ 387 } 388 *p++ = *start++; 389 } 390 o = heim_string_create_with_bytes(p0, p - p0); 391 free(p0); 392 } else { 393 o = heim_string_create_with_bytes(start, ctx->p - start); 394 if (o == NULL) { 395 ctx->error = heim_error_create_enomem(); 396 return NULL; 397 } 398 399 /* If we can decode as base64, then let's */ 400 if (ctx->flags & HEIM_JSON_F_TRY_DECODE_DATA) { 401 void *buf; 402 size_t len; 403 const char *s; 404 405 s = heim_string_get_utf8(o); 406 len = strlen(s); 407 408 if (len >= 4 && strspn(s, base64_chars) >= len - 2) { 409 buf = malloc(len); 410 if (buf == NULL) { 411 heim_release(o); 412 ctx->error = heim_error_create_enomem(); 413 return NULL; 414 } 415 len = rk_base64_decode(s, buf); 416 if (len == -1) { 417 free(buf); 418 return o; 419 } 420 heim_release(o); 421 o = heim_data_ref_create(buf, len, free); 422 } 423 } 424 } 425 ctx->p += 1; 426 427 return o; 428 } 429 ctx->p += 1; 430 } 431 out: 432 ctx->error = heim_error_create(EINVAL, "ran out of string"); 433 return NULL; 434 } 435 436 static int 437 parse_pair(heim_dict_t dict, struct parse_ctx *ctx) 438 { 439 heim_string_t key; 440 heim_object_t value; 441 442 if (white_spaces(ctx)) 443 return -1; 444 445 if (*ctx->p == '}') { 446 ctx->p++; 447 return 0; 448 } 449 450 if (ctx->flags & HEIM_JSON_F_STRICT_DICT) 451 /* JSON allows only string keys */ 452 key = parse_string(ctx); 453 else 454 /* heim_dict_t allows any heim_object_t as key */ 455 key = parse_value(ctx); 456 if (key == NULL) 457 /* Even heim_dict_t does not allow C NULLs as keys though! */ 458 return -1; 459 460 if (white_spaces(ctx)) { 461 heim_release(key); 462 return -1; 463 } 464 465 if (*ctx->p != ':') { 466 heim_release(key); 467 return -1; 468 } 469 470 ctx->p += 1; /* safe because we call white_spaces() next */ 471 472 if (white_spaces(ctx)) { 473 heim_release(key); 474 return -1; 475 } 476 477 value = parse_value(ctx); 478 if (value == NULL && 479 (ctx->error != NULL || (ctx->flags & HEIM_JSON_F_NO_C_NULL))) { 480 if (ctx->error == NULL) 481 ctx->error = heim_error_create(EINVAL, "Invalid JSON encoding"); 482 heim_release(key); 483 return -1; 484 } 485 heim_dict_set_value(dict, key, value); 486 heim_release(key); 487 heim_release(value); 488 489 if (white_spaces(ctx)) 490 return -1; 491 492 if (*ctx->p == '}') { 493 /* 494 * Return 1 but don't consume the '}' so we can count the one 495 * pair in a one-pair dict 496 */ 497 return 1; 498 } else if (*ctx->p == ',') { 499 ctx->p++; 500 return 1; 501 } 502 return -1; 503 } 504 505 static heim_dict_t 506 parse_dict(struct parse_ctx *ctx) 507 { 508 heim_dict_t dict; 509 size_t count = 0; 510 int ret; 511 512 heim_assert(*ctx->p == '{', "string doesn't start with {"); 513 514 dict = heim_dict_create(11); 515 if (dict == NULL) { 516 ctx->error = heim_error_create_enomem(); 517 return NULL; 518 } 519 520 ctx->p += 1; /* safe because parse_pair() calls white_spaces() first */ 521 522 while ((ret = parse_pair(dict, ctx)) > 0) 523 count++; 524 if (ret < 0) { 525 heim_release(dict); 526 return NULL; 527 } 528 if (count == 1 && !(ctx->flags & HEIM_JSON_F_NO_DATA_DICT)) { 529 heim_object_t v = heim_dict_copy_value(dict, heim_tid_data_uuid_key); 530 531 /* 532 * Binary data encoded as a dict with a single magic key with 533 * base64-encoded value? Decode as heim_data_t. 534 */ 535 if (v != NULL && heim_get_tid(v) == HEIM_TID_STRING) { 536 void *buf; 537 size_t len; 538 539 buf = malloc(strlen(heim_string_get_utf8(v))); 540 if (buf == NULL) { 541 heim_release(dict); 542 heim_release(v); 543 ctx->error = heim_error_create_enomem(); 544 return NULL; 545 } 546 len = rk_base64_decode(heim_string_get_utf8(v), buf); 547 heim_release(v); 548 if (len == -1) { 549 free(buf); 550 return dict; /* assume aliasing accident */ 551 } 552 heim_release(dict); 553 return (heim_dict_t)heim_data_ref_create(buf, len, free); 554 } 555 } 556 return dict; 557 } 558 559 static int 560 parse_item(heim_array_t array, struct parse_ctx *ctx) 561 { 562 heim_object_t value; 563 564 if (white_spaces(ctx)) 565 return -1; 566 567 if (*ctx->p == ']') { 568 ctx->p++; /* safe because parse_value() calls white_spaces() first */ 569 return 0; 570 } 571 572 value = parse_value(ctx); 573 if (value == NULL && 574 (ctx->error || (ctx->flags & HEIM_JSON_F_NO_C_NULL))) 575 return -1; 576 577 heim_array_append_value(array, value); 578 heim_release(value); 579 580 if (white_spaces(ctx)) 581 return -1; 582 583 if (*ctx->p == ']') { 584 ctx->p++; 585 return 0; 586 } else if (*ctx->p == ',') { 587 ctx->p++; 588 return 1; 589 } 590 return -1; 591 } 592 593 static heim_array_t 594 parse_array(struct parse_ctx *ctx) 595 { 596 heim_array_t array = heim_array_create(); 597 int ret; 598 599 heim_assert(*ctx->p == '[', "array doesn't start with ["); 600 ctx->p += 1; 601 602 while ((ret = parse_item(array, ctx)) > 0) 603 ; 604 if (ret < 0) { 605 heim_release(array); 606 return NULL; 607 } 608 return array; 609 } 610 611 static heim_object_t 612 parse_value(struct parse_ctx *ctx) 613 { 614 size_t len; 615 heim_object_t o; 616 617 if (white_spaces(ctx)) 618 return NULL; 619 620 if (*ctx->p == '"') { 621 return parse_string(ctx); 622 } else if (*ctx->p == '{') { 623 if (ctx->depth-- == 1) { 624 ctx->error = heim_error_create(EINVAL, "JSON object too deep"); 625 return NULL; 626 } 627 o = parse_dict(ctx); 628 ctx->depth++; 629 return o; 630 } else if (*ctx->p == '[') { 631 if (ctx->depth-- == 1) { 632 ctx->error = heim_error_create(EINVAL, "JSON object too deep"); 633 return NULL; 634 } 635 o = parse_array(ctx); 636 ctx->depth++; 637 return o; 638 } else if (is_number(*ctx->p) || *ctx->p == '-') { 639 return parse_number(ctx); 640 } 641 642 len = ctx->pend - ctx->p; 643 644 if ((ctx->flags & HEIM_JSON_F_NO_C_NULL) == 0 && 645 len >= 6 && memcmp(ctx->p, "<NULL>", 6) == 0) { 646 ctx->p += 6; 647 return heim_null_create(); 648 } else if (len >= 4 && memcmp(ctx->p, "null", 4) == 0) { 649 ctx->p += 4; 650 return heim_null_create(); 651 } else if (len >= 4 && strncasecmp((char *)ctx->p, "true", 4) == 0) { 652 ctx->p += 4; 653 return heim_bool_create(1); 654 } else if (len >= 5 && strncasecmp((char *)ctx->p, "false", 5) == 0) { 655 ctx->p += 5; 656 return heim_bool_create(0); 657 } 658 659 ctx->error = heim_error_create(EINVAL, "unknown char %c at %lu line %lu", 660 (char)*ctx->p, 661 (unsigned long)(ctx->p - ctx->pstart), 662 ctx->lineno); 663 return NULL; 664 } 665 666 667 heim_object_t 668 heim_json_create(const char *string, size_t max_depth, heim_json_flags_t flags, 669 heim_error_t *error) 670 { 671 return heim_json_create_with_bytes(string, strlen(string), max_depth, flags, 672 error); 673 } 674 675 heim_object_t 676 heim_json_create_with_bytes(const void *data, size_t length, size_t max_depth, 677 heim_json_flags_t flags, heim_error_t *error) 678 { 679 struct parse_ctx ctx; 680 heim_object_t o; 681 682 heim_base_once_f(&heim_json_once, NULL, json_init_once); 683 684 ctx.lineno = 1; 685 ctx.p = data; 686 ctx.pstart = data; 687 ctx.pend = ((uint8_t *)data) + length; 688 ctx.error = NULL; 689 ctx.flags = flags; 690 ctx.depth = max_depth; 691 692 o = parse_value(&ctx); 693 694 if (o == NULL && error) { 695 *error = ctx.error; 696 } else if (ctx.error) { 697 heim_release(ctx.error); 698 } 699 700 return o; 701 } 702 703 704 static void 705 show_printf(void *ctx, const char *str) 706 { 707 if (str == NULL) 708 return; 709 fprintf(ctx, "%s", str); 710 } 711 712 /** 713 * Dump a heimbase object to stderr (useful from the debugger!) 714 * 715 * @param obj object to dump using JSON or JSON-like format 716 * 717 * @addtogroup heimbase 718 */ 719 void 720 heim_show(heim_object_t obj) 721 { 722 heim_base2json(obj, stderr, HEIM_JSON_F_NO_DATA_DICT, show_printf); 723 } 724 725 static void 726 strbuf_add(void *ctx, const char *str) 727 { 728 struct heim_strbuf *strbuf = ctx; 729 size_t len; 730 731 if (strbuf->enomem) 732 return; 733 734 if (str == NULL) { 735 /* 736 * Eat the last '\n'; this is used when formatting dict pairs 737 * and array items so that the ',' separating them is never 738 * preceded by a '\n'. 739 */ 740 if (strbuf->len > 0 && strbuf->str[strbuf->len - 1] == '\n') 741 strbuf->len--; 742 return; 743 } 744 745 len = strlen(str); 746 if ((len + 1) > (strbuf->alloced - strbuf->len)) { 747 size_t new_len = strbuf->alloced + (strbuf->alloced >> 2) + len + 1; 748 char *s; 749 750 s = realloc(strbuf->str, new_len); 751 if (s == NULL) { 752 strbuf->enomem = 1; 753 return; 754 } 755 strbuf->str = s; 756 strbuf->alloced = new_len; 757 } 758 /* +1 so we copy the NUL */ 759 (void) memcpy(strbuf->str + strbuf->len, str, len + 1); 760 strbuf->len += len; 761 if (strbuf->str[strbuf->len - 1] == '\n' && 762 strbuf->flags & HEIM_JSON_F_ONE_LINE) 763 strbuf->len--; 764 } 765 766 #define STRBUF_INIT_SZ 64 767 768 heim_string_t 769 heim_json_copy_serialize(heim_object_t obj, heim_json_flags_t flags, heim_error_t *error) 770 { 771 heim_string_t str; 772 struct heim_strbuf strbuf; 773 int ret; 774 775 if (error) 776 *error = NULL; 777 778 memset(&strbuf, 0, sizeof (strbuf)); 779 strbuf.str = malloc(STRBUF_INIT_SZ); 780 if (strbuf.str == NULL) { 781 if (error) 782 *error = heim_error_create_enomem(); 783 return NULL; 784 } 785 strbuf.len = 0; 786 strbuf.alloced = STRBUF_INIT_SZ; 787 strbuf.str[0] = '\0'; 788 strbuf.flags = flags; 789 790 ret = heim_base2json(obj, &strbuf, flags, strbuf_add); 791 if (ret || strbuf.enomem) { 792 if (error) { 793 if (strbuf.enomem || ret == ENOMEM) 794 *error = heim_error_create_enomem(); 795 else 796 *error = heim_error_create(1, "Impossible to JSON-encode " 797 "object"); 798 } 799 free(strbuf.str); 800 return NULL; 801 } 802 if (flags & HEIM_JSON_F_ONE_LINE) { 803 strbuf.flags &= ~HEIM_JSON_F_ONE_LINE; 804 strbuf_add(&strbuf, "\n"); 805 } 806 str = heim_string_ref_create(strbuf.str, free); 807 if (str == NULL) { 808 if (error) 809 *error = heim_error_create_enomem(); 810 free(strbuf.str); 811 } 812 return str; 813 } 814