1 /* 2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz> 3 * Copyright 2014-2017, Augustin Cavalier (waddlesplash) 4 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de> 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "Json.h" 10 11 #include <cstdio> 12 #include <cstdlib> 13 #include <ctype.h> 14 #include <cerrno> 15 16 #include <AutoDeleter.h> 17 #include <DataIO.h> 18 #include <UnicodeChar.h> 19 20 #include "JsonEventListener.h" 21 #include "JsonMessageWriter.h" 22 23 24 // #pragma mark - Public methods 25 26 namespace BPrivate { 27 28 29 static bool 30 b_jsonparse_is_hex(char c) 31 { 32 return isdigit(c) 33 || (c > 0x41 && c <= 0x46) 34 || (c > 0x61 && c <= 0x66); 35 } 36 37 38 static bool 39 b_jsonparse_all_hex(const char* c) 40 { 41 for (int i = 0; i < 4; i++) { 42 if (!b_jsonparse_is_hex(c[i])) 43 return false; 44 } 45 46 return true; 47 } 48 49 50 /*! This class carries state around the parsing process. */ 51 52 class JsonParseContext { 53 public: 54 JsonParseContext(BDataIO* data, BJsonEventListener* listener) 55 : 56 fListener(listener), 57 fData(data), 58 fLineNumber(1), // 1 is the first line 59 fPushbackChar(0), 60 fHasPushbackChar(false) 61 { 62 } 63 64 65 BJsonEventListener* Listener() const 66 { 67 return fListener; 68 } 69 70 71 BDataIO* Data() const 72 { 73 return fData; 74 } 75 76 77 int LineNumber() const 78 { 79 return fLineNumber; 80 } 81 82 83 void IncrementLineNumber() 84 { 85 fLineNumber++; 86 } 87 88 89 // TODO; there is considerable opportunity for performance improvements 90 // here by buffering the input and then feeding it into the parse 91 // algorithm character by character. 92 93 status_t NextChar(char* buffer) 94 { 95 96 if (fHasPushbackChar) { 97 buffer[0] = fPushbackChar; 98 fHasPushbackChar = false; 99 return B_OK; 100 } 101 102 return Data()->ReadExactly(buffer, 1); 103 } 104 105 106 void PushbackChar(char c) 107 { 108 fPushbackChar = c; 109 fHasPushbackChar = true; 110 } 111 112 private: 113 BJsonEventListener* fListener; 114 BDataIO* fData; 115 uint32 fLineNumber; 116 char fPushbackChar; 117 bool fHasPushbackChar; 118 }; 119 120 121 status_t 122 BJson::Parse(const BString& JSON, BMessage& message) 123 { 124 return Parse(JSON.String(), message); 125 } 126 127 128 status_t 129 BJson::Parse(const char* JSON, size_t length, BMessage& message) 130 { 131 BMemoryIO* input = new BMemoryIO(JSON, length); 132 ObjectDeleter<BMemoryIO> inputDeleter(input); 133 BJsonMessageWriter* writer = new BJsonMessageWriter(message); 134 ObjectDeleter<BJsonMessageWriter> writerDeleter(writer); 135 136 Parse(input, writer); 137 status_t result = writer->ErrorStatus(); 138 139 return result; 140 } 141 142 143 status_t 144 BJson::Parse(const char* JSON, BMessage& message) 145 { 146 return Parse(JSON, strlen(JSON), message); 147 } 148 149 150 /*! The data is read as a stream of JSON data. As the JSON is read, events are 151 raised such as; 152 - string 153 - number 154 - true 155 - array start 156 - object end 157 Each event is sent to the listener to process as required. 158 */ 159 160 void 161 BJson::Parse(BDataIO* data, BJsonEventListener* listener) 162 { 163 JsonParseContext context(data, listener); 164 ParseAny(context); 165 listener->Complete(); 166 } 167 168 169 // #pragma mark - Specific parse logic. 170 171 172 bool 173 BJson::NextChar(JsonParseContext& jsonParseContext, char* c) 174 { 175 status_t result = jsonParseContext.NextChar(c); 176 177 switch (result) { 178 case B_OK: 179 return true; 180 181 case B_PARTIAL_READ: 182 { 183 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 184 jsonParseContext.LineNumber(), "unexpected end of input"); 185 return false; 186 } 187 188 default: 189 { 190 jsonParseContext.Listener()->HandleError(result, -1, 191 "io related read error"); 192 return false; 193 } 194 } 195 } 196 197 198 bool 199 BJson::NextNonWhitespaceChar(JsonParseContext& jsonParseContext, char* c) 200 { 201 while (true) { 202 if (!NextChar(jsonParseContext, c)) 203 return false; 204 205 switch (*c) { 206 case 0x0a: // newline 207 case 0x0d: // cr 208 jsonParseContext.IncrementLineNumber(); 209 case ' ': // space 210 // swallow whitespace as it is not syntactically 211 // significant. 212 break; 213 214 default: 215 return true; 216 } 217 } 218 } 219 220 221 bool 222 BJson::ParseAny(JsonParseContext& jsonParseContext) 223 { 224 char c; 225 226 if (!NextNonWhitespaceChar(jsonParseContext, &c)) 227 return false; 228 229 switch (c) { 230 case 'f': // [f]alse 231 return ParseExpectedVerbatimStringAndRaiseEvent( 232 jsonParseContext, "alse", 4, 'f', B_JSON_FALSE); 233 234 case 't': // [t]rue 235 return ParseExpectedVerbatimStringAndRaiseEvent( 236 jsonParseContext, "rue", 3, 't', B_JSON_TRUE); 237 238 case 'n': // [n]ull 239 return ParseExpectedVerbatimStringAndRaiseEvent( 240 jsonParseContext, "ull", 3, 'n', B_JSON_NULL); 241 242 case '"': 243 return ParseString(jsonParseContext, B_JSON_STRING); 244 245 case '{': 246 return ParseObject(jsonParseContext); 247 248 case '[': 249 return ParseArray(jsonParseContext); 250 251 case '+': 252 case '-': 253 case '0': 254 case '1': 255 case '2': 256 case '3': 257 case '4': 258 case '5': 259 case '6': 260 case '7': 261 case '8': 262 case '9': 263 jsonParseContext.PushbackChar(c); // keeps the parse simple 264 return ParseNumber(jsonParseContext); 265 266 default: 267 { 268 BString errorMessage; 269 if (c >= 0x20 && c < 0x7f) { 270 errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]" 271 " (%c) when parsing element", static_cast<uint8>(c), c); 272 } else { 273 errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]" 274 " when parsing element", (uint8) c); 275 } 276 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 277 jsonParseContext.LineNumber(), errorMessage.String()); 278 return false; 279 } 280 } 281 282 return true; 283 } 284 285 286 /*! This method captures an object name, a separator ':' and then any value. */ 287 288 bool 289 BJson::ParseObjectNameValuePair(JsonParseContext& jsonParseContext) 290 { 291 bool didParseName = false; 292 char c; 293 294 while (true) { 295 if (!NextNonWhitespaceChar(jsonParseContext, &c)) 296 return false; 297 298 switch (c) { 299 case '\"': // name of the object 300 { 301 if (!didParseName) { 302 if (!ParseString(jsonParseContext, B_JSON_OBJECT_NAME)) 303 return false; 304 305 didParseName = true; 306 } else { 307 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 308 jsonParseContext.LineNumber(), "unexpected" 309 " [\"] character when parsing object name-" 310 " value separator"); 311 return false; 312 } 313 break; 314 } 315 316 case ':': // separator 317 { 318 if (didParseName) { 319 if (!ParseAny(jsonParseContext)) 320 return false; 321 return true; 322 } else { 323 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 324 jsonParseContext.LineNumber(), "unexpected" 325 " [:] character when parsing object name-" 326 " value pair"); 327 return false; 328 } 329 } 330 331 default: 332 { 333 BString errorMessage; 334 errorMessage.SetToFormat( 335 "unexpected character [%c] when parsing object" 336 " name-value pair", 337 c); 338 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 339 jsonParseContext.LineNumber(), errorMessage.String()); 340 return false; 341 } 342 } 343 } 344 } 345 346 347 bool 348 BJson::ParseObject(JsonParseContext& jsonParseContext) 349 { 350 if (!jsonParseContext.Listener()->Handle( 351 BJsonEvent(B_JSON_OBJECT_START))) { 352 return false; 353 } 354 355 char c; 356 bool firstItem = true; 357 358 while (true) { 359 if (!NextNonWhitespaceChar(jsonParseContext, &c)) 360 return false; 361 362 switch (c) { 363 case '}': // terminate the object 364 { 365 if (!jsonParseContext.Listener()->Handle( 366 BJsonEvent(B_JSON_OBJECT_END))) { 367 return false; 368 } 369 return true; 370 } 371 372 case ',': // next value. 373 { 374 if (firstItem) { 375 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 376 jsonParseContext.LineNumber(), "unexpected" 377 " item separator when parsing start of" 378 " object"); 379 return false; 380 } 381 382 if (!ParseObjectNameValuePair(jsonParseContext)) 383 return false; 384 break; 385 } 386 387 default: 388 { 389 if (firstItem) { 390 jsonParseContext.PushbackChar(c); 391 if (!ParseObjectNameValuePair(jsonParseContext)) 392 return false; 393 firstItem = false; 394 } else { 395 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 396 jsonParseContext.LineNumber(), "expected" 397 " separator when parsing an object"); 398 } 399 } 400 } 401 } 402 403 return true; 404 } 405 406 407 bool 408 BJson::ParseArray(JsonParseContext& jsonParseContext) 409 { 410 if (!jsonParseContext.Listener()->Handle( 411 BJsonEvent(B_JSON_ARRAY_START))) { 412 return false; 413 } 414 415 char c; 416 bool firstItem = true; 417 418 while (true) { 419 if (!NextNonWhitespaceChar(jsonParseContext, &c)) 420 return false; 421 422 switch (c) { 423 case ']': // terminate the array 424 { 425 if (!jsonParseContext.Listener()->Handle( 426 BJsonEvent(B_JSON_ARRAY_END))) { 427 return false; 428 } 429 return true; 430 } 431 432 case ',': // next value. 433 { 434 if (firstItem) { 435 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 436 jsonParseContext.LineNumber(), "unexpected" 437 " item separator when parsing start of" 438 " array"); 439 } 440 441 if (!ParseAny(jsonParseContext)) 442 return false; 443 break; 444 } 445 446 default: 447 { 448 if (firstItem) { 449 jsonParseContext.PushbackChar(c); 450 if (!ParseAny(jsonParseContext)) 451 return false; 452 firstItem = false; 453 } else { 454 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 455 jsonParseContext.LineNumber(), "expected" 456 " separator when parsing an array"); 457 } 458 } 459 } 460 } 461 462 return true; 463 } 464 465 466 bool 467 BJson::ParseEscapeUnicodeSequence(JsonParseContext& jsonParseContext, 468 BString& stringResult) 469 { 470 char buffer[5]; 471 buffer[4] = 0; 472 473 if (!NextChar(jsonParseContext, &buffer[0]) 474 || !NextChar(jsonParseContext, &buffer[1]) 475 || !NextChar(jsonParseContext, &buffer[2]) 476 || !NextChar(jsonParseContext, &buffer[3])) { 477 return false; 478 } 479 480 if (!b_jsonparse_all_hex(buffer)) { 481 BString errorMessage; 482 errorMessage.SetToFormat( 483 "malformed unicode sequence [%s] in string parsing", 484 buffer); 485 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 486 jsonParseContext.LineNumber(), errorMessage.String()); 487 return false; 488 } 489 490 uint intValue; 491 492 if (sscanf(buffer, "%4x", &intValue) != 1) { 493 BString errorMessage; 494 errorMessage.SetToFormat( 495 "unable to process unicode sequence [%s] in string " 496 " parsing", buffer); 497 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 498 jsonParseContext.LineNumber(), errorMessage.String()); 499 return false; 500 } 501 502 char character[7]; 503 char* ptr = character; 504 BUnicodeChar::ToUTF8(intValue, &ptr); 505 int32 sequenceLength = ptr - character; 506 stringResult.Append(character, sequenceLength); 507 508 return true; 509 } 510 511 512 bool 513 BJson::ParseStringEscapeSequence(JsonParseContext& jsonParseContext, 514 BString& stringResult) 515 { 516 char c; 517 518 if (!NextChar(jsonParseContext, &c)) 519 return false; 520 521 switch (c) { 522 case 'n': 523 stringResult += "\n"; 524 break; 525 case 'r': 526 stringResult += "\r"; 527 break; 528 case 'b': 529 stringResult += "\b"; 530 break; 531 case 'f': 532 stringResult += "\f"; 533 break; 534 case '\\': 535 stringResult += "\\"; 536 break; 537 case '/': 538 stringResult += "/"; 539 break; 540 case 't': 541 stringResult += "\t"; 542 break; 543 case '"': 544 stringResult += "\""; 545 break; 546 case 'u': 547 { 548 // unicode escape sequence. 549 if (!ParseEscapeUnicodeSequence(jsonParseContext, 550 stringResult)) { 551 return false; 552 } 553 break; 554 } 555 default: 556 { 557 BString errorMessage; 558 errorMessage.SetToFormat( 559 "unexpected escaped character [%c] in string parsing", 560 c); 561 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 562 jsonParseContext.LineNumber(), errorMessage.String()); 563 return false; 564 } 565 } 566 567 return true; 568 } 569 570 571 bool 572 BJson::ParseString(JsonParseContext& jsonParseContext, 573 json_event_type eventType) 574 { 575 char c; 576 BString stringResult; 577 578 while(true) { 579 if (!NextChar(jsonParseContext, &c)) 580 return false; 581 582 switch (c) { 583 case '"': 584 { 585 // terminates the string assembled so far. 586 jsonParseContext.Listener()->Handle( 587 BJsonEvent(eventType, stringResult.String())); 588 return true; 589 } 590 591 case '\\': 592 { 593 if (!ParseStringEscapeSequence(jsonParseContext, 594 stringResult)) { 595 return false; 596 } 597 break; 598 } 599 600 default: 601 { 602 uint8 uc = static_cast<uint8>(c); 603 604 if(uc < 0x20) { // control characters are not allowed 605 BString errorMessage; 606 errorMessage.SetToFormat("illegal control character" 607 " [%" B_PRIu8 "] when parsing a string", uc); 608 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 609 jsonParseContext.LineNumber(), 610 errorMessage.String()); 611 return false; 612 } 613 614 stringResult.Append(&c, 1); 615 break; 616 } 617 } 618 } 619 } 620 621 622 bool 623 BJson::ParseExpectedVerbatimStringAndRaiseEvent( 624 JsonParseContext& jsonParseContext, const char* expectedString, 625 size_t expectedStringLength, char leadingChar, 626 json_event_type jsonEventType) 627 { 628 if (ParseExpectedVerbatimString(jsonParseContext, expectedString, 629 expectedStringLength, leadingChar)) { 630 if (!jsonParseContext.Listener()->Handle(BJsonEvent(jsonEventType))) 631 return false; 632 } 633 634 return true; 635 } 636 637 /*! This will make sure that the constant string is available at the input. */ 638 639 bool 640 BJson::ParseExpectedVerbatimString(JsonParseContext& jsonParseContext, 641 const char* expectedString, size_t expectedStringLength, char leadingChar) 642 { 643 char c; 644 size_t offset = 0; 645 646 while (offset < expectedStringLength) { 647 if (!NextChar(jsonParseContext, &c)) 648 return false; 649 650 if (c != expectedString[offset]) { 651 BString errorMessage; 652 errorMessage.SetToFormat("malformed json primative literal; " 653 "expected [%c%s], but got [%c] at position %" B_PRIdSSIZE, 654 leadingChar, expectedString, c, offset); 655 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 656 jsonParseContext.LineNumber(), errorMessage.String()); 657 return false; 658 } 659 660 offset++; 661 } 662 663 return true; 664 } 665 666 667 /*! This function checks to see that the supplied string is a well formed 668 JSON number. It does this from a string rather than a stream for 669 convenience. This is not anticipated to impact performance because 670 the string values are short. 671 */ 672 673 bool 674 BJson::IsValidNumber(BString& number) 675 { 676 int32 offset = 0; 677 int32 len = number.Length(); 678 679 if (offset < len && number[offset] == '-') 680 offset++; 681 682 if (offset >= len) 683 return false; 684 685 if (isdigit(number[offset]) && number[offset] != '0') { 686 while (offset < len && isdigit(number[offset])) 687 offset++; 688 } else { 689 if (number[offset] == '0') 690 offset++; 691 else 692 return false; 693 } 694 695 if (offset < len && number[offset] == '.') { 696 offset++; 697 698 if (offset >= len) 699 return false; 700 701 while (offset < len && isdigit(number[offset])) 702 offset++; 703 } 704 705 if (offset < len && (number[offset] == 'E' || number[offset] == 'e')) { 706 offset++; 707 708 if(offset < len && (number[offset] == '+' || number[offset] == '-')) 709 offset++; 710 711 if (offset >= len) 712 return false; 713 714 while (offset < len && isdigit(number[offset])) 715 offset++; 716 } 717 718 return offset == len; 719 } 720 721 722 /*! Note that this method hits the 'NextChar' method on the context directly 723 and handles any end-of-file state itself because it is feasible that the 724 entire JSON payload is a number and because (unlike other structures, the 725 number can take the end-of-file to signify the end of the number. 726 */ 727 728 bool 729 BJson::ParseNumber(JsonParseContext& jsonParseContext) 730 { 731 BString value; 732 733 while (true) { 734 char c; 735 status_t result = jsonParseContext.NextChar(&c); 736 737 switch (result) { 738 case B_OK: 739 { 740 if (isdigit(c)) { 741 value += c; 742 break; 743 } 744 745 if (NULL != strchr("+-eE.", c)) { 746 value += c; 747 break; 748 } 749 750 jsonParseContext.PushbackChar(c); 751 // intentional fall through 752 } 753 case B_PARTIAL_READ: 754 { 755 errno = 0; 756 757 if (!IsValidNumber(value)) { 758 jsonParseContext.Listener()->HandleError(B_BAD_DATA, 759 jsonParseContext.LineNumber(), "malformed number"); 760 return false; 761 } 762 763 jsonParseContext.Listener()->Handle(BJsonEvent(B_JSON_NUMBER, 764 value.String())); 765 766 return true; 767 } 768 default: 769 { 770 jsonParseContext.Listener()->HandleError(result, -1, 771 "io related read error"); 772 return false; 773 } 774 } 775 } 776 } 777 778 } // namespace BPrivate 779