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