1 /* 2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz> 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "JsonTextWriter.h" 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 12 #include <UnicodeChar.h> 13 14 15 namespace BPrivate { 16 17 18 static bool 19 b_json_is_7bit_clean(uint8 c) 20 { 21 return c >= 0x20 && c < 0x7f; 22 } 23 24 25 static bool 26 b_json_is_illegal(uint8 c) 27 { 28 return c < 0x20 || c == 0x7f; 29 } 30 31 32 static const char* 33 b_json_simple_esc_sequence(char c) 34 { 35 switch (c) { 36 case '"': 37 return "\\\""; 38 case '\\': 39 return "\\\\"; 40 case '/': 41 return "\\/"; 42 case '\b': 43 return "\\b"; 44 case '\f': 45 return "\\f"; 46 case '\n': 47 return "\\n"; 48 case '\r': 49 return "\\r"; 50 case '\t': 51 return "\\t"; 52 default: 53 return NULL; 54 } 55 } 56 57 58 static size_t 59 b_json_len_7bit_clean_non_esc(uint8* c, size_t length) { 60 size_t result = 0; 61 62 while (result < length 63 && b_json_is_7bit_clean(c[result]) 64 && b_json_simple_esc_sequence(c[result]) == NULL) { 65 result++; 66 } 67 68 return result; 69 } 70 71 72 /*! The class and sub-classes of it are used as a stack internal to the 73 BJsonTextWriter class. As the JSON is parsed, the stack of these 74 internal listeners follows the stack of the JSON parsing in terms of 75 containers; arrays and objects. 76 */ 77 78 class BJsonTextWriterStackedEventListener : public BJsonEventListener { 79 public: 80 BJsonTextWriterStackedEventListener( 81 BJsonTextWriter* writer, 82 BJsonTextWriterStackedEventListener* parent); 83 ~BJsonTextWriterStackedEventListener(); 84 85 bool Handle(const BJsonEvent& event); 86 void HandleError(status_t status, int32 line, 87 const char* message); 88 void Complete(); 89 90 status_t ErrorStatus(); 91 92 BJsonTextWriterStackedEventListener* 93 Parent(); 94 95 protected: 96 97 status_t StreamNumberNode(const BJsonEvent& event); 98 99 status_t StreamStringVerbatim(const char* string); 100 status_t StreamStringVerbatim(const char* string, 101 off_t offset, size_t length); 102 103 status_t StreamStringEncoded(const char* string); 104 status_t StreamStringEncoded(const char* string, 105 off_t offset, size_t length); 106 107 status_t StreamQuotedEncodedString(const char* string); 108 status_t StreamQuotedEncodedString(const char* string, 109 off_t offset, size_t length); 110 111 status_t StreamChar(char c); 112 113 virtual bool WillAdd(); 114 virtual void DidAdd(); 115 116 void SetStackedListenerOnWriter( 117 BJsonTextWriterStackedEventListener* 118 stackedListener); 119 120 BJsonTextWriter* 121 fWriter; 122 BJsonTextWriterStackedEventListener* 123 fParent; 124 uint32 fCount; 125 126 }; 127 128 129 class BJsonTextWriterArrayStackedEventListener 130 : public BJsonTextWriterStackedEventListener { 131 public: 132 BJsonTextWriterArrayStackedEventListener( 133 BJsonTextWriter* writer, 134 BJsonTextWriterStackedEventListener* parent); 135 ~BJsonTextWriterArrayStackedEventListener(); 136 137 bool Handle(const BJsonEvent& event); 138 139 protected: 140 bool WillAdd(); 141 }; 142 143 144 class BJsonTextWriterObjectStackedEventListener 145 : public BJsonTextWriterStackedEventListener { 146 public: 147 BJsonTextWriterObjectStackedEventListener( 148 BJsonTextWriter* writer, 149 BJsonTextWriterStackedEventListener* parent); 150 ~BJsonTextWriterObjectStackedEventListener(); 151 152 bool Handle(const BJsonEvent& event); 153 }; 154 155 } // namespace BPrivate 156 157 158 using BPrivate::BJsonTextWriterStackedEventListener; 159 using BPrivate::BJsonTextWriterArrayStackedEventListener; 160 using BPrivate::BJsonTextWriterObjectStackedEventListener; 161 162 163 // #pragma mark - BJsonTextWriterStackedEventListener 164 165 166 BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener( 167 BJsonTextWriter* writer, 168 BJsonTextWriterStackedEventListener* parent) 169 { 170 fWriter = writer; 171 fParent = parent; 172 fCount = 0; 173 } 174 175 176 BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener() 177 { 178 } 179 180 181 bool 182 BJsonTextWriterStackedEventListener::Handle(const BJsonEvent& event) 183 { 184 status_t writeResult = B_OK; 185 186 if (fWriter->ErrorStatus() != B_OK) 187 return false; 188 189 switch (event.EventType()) { 190 191 case B_JSON_NUMBER: 192 case B_JSON_STRING: 193 case B_JSON_TRUE: 194 case B_JSON_FALSE: 195 case B_JSON_NULL: 196 case B_JSON_OBJECT_START: 197 case B_JSON_ARRAY_START: 198 if (!WillAdd()) 199 return false; 200 break; 201 202 default: 203 break; 204 } 205 206 switch (event.EventType()) { 207 208 case B_JSON_NUMBER: 209 writeResult = StreamNumberNode(event); 210 break; 211 212 case B_JSON_STRING: 213 writeResult = StreamQuotedEncodedString(event.Content()); 214 break; 215 216 case B_JSON_TRUE: 217 writeResult = StreamStringVerbatim("true", 0, 4); 218 break; 219 220 case B_JSON_FALSE: 221 writeResult = StreamStringVerbatim("false", 0, 5); 222 break; 223 224 case B_JSON_NULL: 225 writeResult = StreamStringVerbatim("null", 0, 4); 226 break; 227 228 case B_JSON_OBJECT_START: 229 { 230 writeResult = StreamChar('{'); 231 232 if (writeResult == B_OK) { 233 SetStackedListenerOnWriter( 234 new BJsonTextWriterObjectStackedEventListener( 235 fWriter, this)); 236 } 237 break; 238 } 239 240 case B_JSON_ARRAY_START: 241 { 242 writeResult = StreamChar('['); 243 244 if (writeResult == B_OK) { 245 SetStackedListenerOnWriter( 246 new BJsonTextWriterArrayStackedEventListener( 247 fWriter, this)); 248 } 249 break; 250 } 251 252 default: 253 { 254 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, 255 "unexpected type of json item to add to container"); 256 return false; 257 } 258 } 259 260 if (writeResult == B_OK) 261 DidAdd(); 262 else { 263 HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, 264 "error writing output"); 265 } 266 267 return ErrorStatus() == B_OK; 268 } 269 270 271 void 272 BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line, 273 const char* message) 274 { 275 fWriter->HandleError(status, line, message); 276 } 277 278 279 void 280 BJsonTextWriterStackedEventListener::Complete() 281 { 282 // illegal state. 283 HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED, 284 "Complete() called on stacked message listener"); 285 } 286 287 288 status_t 289 BJsonTextWriterStackedEventListener::ErrorStatus() 290 { 291 return fWriter->ErrorStatus(); 292 } 293 294 295 BJsonTextWriterStackedEventListener* 296 BJsonTextWriterStackedEventListener::Parent() 297 { 298 return fParent; 299 } 300 301 302 status_t 303 BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event) 304 { 305 return fWriter->StreamNumberNode(event); 306 } 307 308 309 status_t 310 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string) 311 { 312 return fWriter->StreamStringVerbatim(string); 313 } 314 315 316 status_t 317 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string, 318 off_t offset, size_t length) 319 { 320 return fWriter->StreamStringVerbatim(string, offset, length); 321 } 322 323 324 status_t 325 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string) 326 { 327 return fWriter->StreamStringEncoded(string); 328 } 329 330 331 status_t 332 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string, 333 off_t offset, size_t length) 334 { 335 return fWriter->StreamStringEncoded(string, offset, length); 336 } 337 338 339 status_t 340 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString( 341 const char* string) 342 { 343 return fWriter->StreamQuotedEncodedString(string); 344 } 345 346 347 status_t 348 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString( 349 const char* string, off_t offset, size_t length) 350 { 351 return fWriter->StreamQuotedEncodedString(string, offset, length); 352 } 353 354 355 status_t 356 BJsonTextWriterStackedEventListener::StreamChar(char c) 357 { 358 return fWriter->StreamChar(c); 359 } 360 361 362 bool 363 BJsonTextWriterStackedEventListener::WillAdd() 364 { 365 return true; // carry on 366 } 367 368 369 void 370 BJsonTextWriterStackedEventListener::DidAdd() 371 { 372 fCount++; 373 } 374 375 376 void 377 BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter( 378 BJsonTextWriterStackedEventListener* stackedListener) 379 { 380 fWriter->SetStackedListener(stackedListener); 381 } 382 383 384 // #pragma mark - BJsonTextWriterArrayStackedEventListener 385 386 387 BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener( 388 BJsonTextWriter* writer, 389 BJsonTextWriterStackedEventListener* parent) 390 : 391 BJsonTextWriterStackedEventListener(writer, parent) 392 { 393 } 394 395 396 BJsonTextWriterArrayStackedEventListener 397 ::~BJsonTextWriterArrayStackedEventListener() 398 { 399 } 400 401 402 bool 403 BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent& event) 404 { 405 status_t writeResult = B_OK; 406 407 if (fWriter->ErrorStatus() != B_OK) 408 return false; 409 410 switch (event.EventType()) { 411 case B_JSON_ARRAY_END: 412 { 413 writeResult = StreamChar(']'); 414 415 if (writeResult == B_OK) { 416 SetStackedListenerOnWriter(fParent); 417 delete this; 418 return true; // must exit immediately after delete this. 419 } 420 break; 421 } 422 423 default: 424 return BJsonTextWriterStackedEventListener::Handle(event); 425 } 426 427 if(writeResult != B_OK) { 428 HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, 429 "error writing output"); 430 } 431 432 return ErrorStatus() == B_OK; 433 } 434 435 436 bool 437 BJsonTextWriterArrayStackedEventListener::WillAdd() 438 { 439 status_t writeResult = B_OK; 440 441 if (writeResult == B_OK && fCount > 0) 442 writeResult = StreamChar(','); 443 444 if (writeResult != B_OK) { 445 HandleError(B_IO_ERROR, JSON_EVENT_LISTENER_ANY_LINE, 446 "error writing data"); 447 return false; 448 } 449 450 return BJsonTextWriterStackedEventListener::WillAdd(); 451 } 452 453 454 // #pragma mark - BJsonTextWriterObjectStackedEventListener 455 456 457 BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener( 458 BJsonTextWriter* writer, 459 BJsonTextWriterStackedEventListener* parent) 460 : 461 BJsonTextWriterStackedEventListener(writer, parent) 462 { 463 } 464 465 466 BJsonTextWriterObjectStackedEventListener 467 ::~BJsonTextWriterObjectStackedEventListener() 468 { 469 } 470 471 472 bool 473 BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent& event) 474 { 475 status_t writeResult = B_OK; 476 477 if (fWriter->ErrorStatus() != B_OK) 478 return false; 479 480 switch (event.EventType()) { 481 case B_JSON_OBJECT_END: 482 { 483 writeResult = StreamChar('}'); 484 485 if (writeResult == B_OK) { 486 SetStackedListenerOnWriter(fParent); 487 delete this; 488 return true; // just exit after delete this. 489 } 490 break; 491 } 492 493 case B_JSON_OBJECT_NAME: 494 { 495 if (writeResult == B_OK && fCount > 0) 496 writeResult = StreamChar(','); 497 498 if (writeResult == B_OK) 499 writeResult = StreamQuotedEncodedString(event.Content()); 500 501 if (writeResult == B_OK) 502 writeResult = StreamChar(':'); 503 504 break; 505 } 506 507 default: 508 return BJsonTextWriterStackedEventListener::Handle(event); 509 } 510 511 if (writeResult != B_OK) { 512 HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, 513 "error writing data"); 514 } 515 516 return ErrorStatus() == B_OK; 517 } 518 519 520 // #pragma mark - BJsonTextWriter 521 522 523 BJsonTextWriter::BJsonTextWriter( 524 BDataIO* dataIO) 525 : 526 fDataIO(dataIO) 527 { 528 529 // this is a preparation for this buffer to easily be used later 530 // to efficiently output encoded unicode characters. 531 532 fUnicodeAssemblyBuffer[0] = '\\'; 533 fUnicodeAssemblyBuffer[1] = 'u'; 534 535 fStackedListener = new BJsonTextWriterStackedEventListener(this, NULL); 536 } 537 538 539 BJsonTextWriter::~BJsonTextWriter() 540 { 541 BJsonTextWriterStackedEventListener* listener = fStackedListener; 542 543 while (listener != NULL) { 544 BJsonTextWriterStackedEventListener* nextListener = listener->Parent(); 545 delete listener; 546 listener = nextListener; 547 } 548 549 fStackedListener = NULL; 550 } 551 552 553 bool 554 BJsonTextWriter::Handle(const BJsonEvent& event) 555 { 556 return fStackedListener->Handle(event); 557 } 558 559 560 void 561 BJsonTextWriter::Complete() 562 { 563 // upon construction, this object will add one listener to the 564 // stack. On complete, this listener should still be there; 565 // otherwise this implies an unterminated structure such as array 566 // / object. 567 568 if (fStackedListener->Parent() != NULL) { 569 HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE, 570 "unexpected end of input data"); 571 } 572 } 573 574 575 void 576 BJsonTextWriter::SetStackedListener( 577 BJsonTextWriterStackedEventListener* stackedListener) 578 { 579 fStackedListener = stackedListener; 580 } 581 582 583 status_t 584 BJsonTextWriter::StreamNumberNode(const BJsonEvent& event) 585 { 586 return StreamStringVerbatim(event.Content()); 587 } 588 589 590 status_t 591 BJsonTextWriter::StreamStringVerbatim(const char* string) 592 { 593 return StreamStringVerbatim(string, 0, strlen(string)); 594 } 595 596 597 status_t 598 BJsonTextWriter::StreamStringVerbatim(const char* string, 599 off_t offset, size_t length) 600 { 601 return fDataIO->WriteExactly(&string[offset], length); 602 } 603 604 605 status_t 606 BJsonTextWriter::StreamStringEncoded(const char* string) 607 { 608 return StreamStringEncoded(string, 0, strlen(string)); 609 } 610 611 612 status_t 613 BJsonTextWriter::StreamStringUnicodeCharacter(uint32 c) 614 { 615 sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, c); 616 // note that the buffer's first two bytes are populated with the JSON 617 // prefix for a unicode char. 618 return StreamStringVerbatim(fUnicodeAssemblyBuffer, 0, 6); 619 } 620 621 622 /*! Note that this method will expect a UTF-8 encoded string. */ 623 624 status_t 625 BJsonTextWriter::StreamStringEncoded(const char* string, 626 off_t offset, size_t length) 627 { 628 status_t writeResult = B_OK; 629 uint8* string8bit = (uint8*)string; 630 size_t i = 0; 631 632 while (i < length && writeResult == B_OK) { 633 uint8 c = string8bit[offset + i]; 634 const char* simpleEsc = b_json_simple_esc_sequence(c); 635 636 if (simpleEsc != NULL) { 637 // here the character to emit is something like a tab or a quote 638 // in this case the output JSON should escape it so that it looks 639 // like \t or \n in the output. 640 writeResult = StreamStringVerbatim(simpleEsc, 0, 2); 641 i++; 642 } else { 643 if (b_json_is_7bit_clean(c)) { 644 // in this case the first character is a simple one that can be 645 // output without any special handling. Find the sequence of 646 // such characters and output them as a sequence so that it's 647 // included as one write operation. 648 size_t l = 1 + b_json_len_7bit_clean_non_esc( 649 &string8bit[offset + i + 1], length - (offset + i + 1)); 650 writeResult = StreamStringVerbatim(&string[offset + i], 0, l); 651 i += static_cast<size_t>(l); 652 } else { 653 if (b_json_is_illegal(c)) { 654 fprintf(stderr, "! string encoding error - illegal " 655 "character [%" B_PRIu32 "]\n", static_cast<uint32>(c)); 656 i++; 657 } else { 658 // now we have a UTF-8 sequence. Read the UTF-8 sequence 659 // to get the unicode character and then encode that as 660 // JSON. 661 const char* unicodeStr = &string[offset + i]; 662 uint32 unicodeCharacter = BUnicodeChar::FromUTF8( 663 &unicodeStr); 664 writeResult = StreamStringUnicodeCharacter( 665 unicodeCharacter); 666 i += static_cast<size_t>(unicodeStr - &string[offset + i]); 667 } 668 } 669 } 670 } 671 672 return writeResult; 673 } 674 675 676 status_t 677 BJsonTextWriter::StreamQuotedEncodedString(const char* string) 678 { 679 return StreamQuotedEncodedString(string, 0, strlen(string)); 680 } 681 682 683 status_t 684 BJsonTextWriter::StreamQuotedEncodedString(const char* string, 685 off_t offset, size_t length) 686 { 687 status_t write_result = B_OK; 688 689 if (write_result == B_OK) 690 write_result = StreamChar('\"'); 691 692 if (write_result == B_OK) 693 write_result = StreamStringEncoded(string, offset, length); 694 695 if (write_result == B_OK) 696 write_result = StreamChar('\"'); 697 698 return write_result; 699 } 700 701 702 status_t 703 BJsonTextWriter::StreamChar(char c) 704 { 705 return fDataIO->WriteExactly(&c, 1); 706 } 707