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
b_json_is_7bit_clean(uint8 c)19 b_json_is_7bit_clean(uint8 c)
20 {
21 return c >= 0x20 && c < 0x7f;
22 }
23
24
25 static bool
b_json_is_illegal(uint8 c)26 b_json_is_illegal(uint8 c)
27 {
28 return c < 0x20 || c == 0x7f;
29 }
30
31
32 static const char*
b_json_simple_esc_sequence(char c)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
b_json_len_7bit_clean_non_esc(uint8 * c,size_t length)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
BJsonTextWriterStackedEventListener(BJsonTextWriter * writer,BJsonTextWriterStackedEventListener * parent)166 BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
167 BJsonTextWriter* writer,
168 BJsonTextWriterStackedEventListener* parent)
169 {
170 fWriter = writer;
171 fParent = parent;
172 fCount = 0;
173 }
174
175
~BJsonTextWriterStackedEventListener()176 BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
177 {
178 }
179
180
181 bool
Handle(const BJsonEvent & event)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
HandleError(status_t status,int32 line,const char * message)272 BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line,
273 const char* message)
274 {
275 fWriter->HandleError(status, line, message);
276 }
277
278
279 void
Complete()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
ErrorStatus()289 BJsonTextWriterStackedEventListener::ErrorStatus()
290 {
291 return fWriter->ErrorStatus();
292 }
293
294
295 BJsonTextWriterStackedEventListener*
Parent()296 BJsonTextWriterStackedEventListener::Parent()
297 {
298 return fParent;
299 }
300
301
302 status_t
StreamNumberNode(const BJsonEvent & event)303 BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event)
304 {
305 return fWriter->StreamNumberNode(event);
306 }
307
308
309 status_t
StreamStringVerbatim(const char * string)310 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string)
311 {
312 return fWriter->StreamStringVerbatim(string);
313 }
314
315
316 status_t
StreamStringVerbatim(const char * string,off_t offset,size_t length)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
StreamStringEncoded(const char * string)325 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string)
326 {
327 return fWriter->StreamStringEncoded(string);
328 }
329
330
331 status_t
StreamStringEncoded(const char * string,off_t offset,size_t length)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
StreamQuotedEncodedString(const char * string)340 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
341 const char* string)
342 {
343 return fWriter->StreamQuotedEncodedString(string);
344 }
345
346
347 status_t
StreamQuotedEncodedString(const char * string,off_t offset,size_t length)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
StreamChar(char c)356 BJsonTextWriterStackedEventListener::StreamChar(char c)
357 {
358 return fWriter->StreamChar(c);
359 }
360
361
362 bool
WillAdd()363 BJsonTextWriterStackedEventListener::WillAdd()
364 {
365 return true; // carry on
366 }
367
368
369 void
DidAdd()370 BJsonTextWriterStackedEventListener::DidAdd()
371 {
372 fCount++;
373 }
374
375
376 void
SetStackedListenerOnWriter(BJsonTextWriterStackedEventListener * stackedListener)377 BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
378 BJsonTextWriterStackedEventListener* stackedListener)
379 {
380 fWriter->SetStackedListener(stackedListener);
381 }
382
383
384 // #pragma mark - BJsonTextWriterArrayStackedEventListener
385
386
BJsonTextWriterArrayStackedEventListener(BJsonTextWriter * writer,BJsonTextWriterStackedEventListener * parent)387 BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
388 BJsonTextWriter* writer,
389 BJsonTextWriterStackedEventListener* parent)
390 :
391 BJsonTextWriterStackedEventListener(writer, parent)
392 {
393 }
394
395
396 BJsonTextWriterArrayStackedEventListener
~BJsonTextWriterArrayStackedEventListener()397 ::~BJsonTextWriterArrayStackedEventListener()
398 {
399 }
400
401
402 bool
Handle(const BJsonEvent & event)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
WillAdd()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
BJsonTextWriterObjectStackedEventListener(BJsonTextWriter * writer,BJsonTextWriterStackedEventListener * parent)457 BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
458 BJsonTextWriter* writer,
459 BJsonTextWriterStackedEventListener* parent)
460 :
461 BJsonTextWriterStackedEventListener(writer, parent)
462 {
463 }
464
465
466 BJsonTextWriterObjectStackedEventListener
~BJsonTextWriterObjectStackedEventListener()467 ::~BJsonTextWriterObjectStackedEventListener()
468 {
469 }
470
471
472 bool
Handle(const BJsonEvent & event)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
BJsonTextWriter(BDataIO * dataIO)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
~BJsonTextWriter()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
Handle(const BJsonEvent & event)554 BJsonTextWriter::Handle(const BJsonEvent& event)
555 {
556 return fStackedListener->Handle(event);
557 }
558
559
560 void
Complete()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
SetStackedListener(BJsonTextWriterStackedEventListener * stackedListener)576 BJsonTextWriter::SetStackedListener(
577 BJsonTextWriterStackedEventListener* stackedListener)
578 {
579 fStackedListener = stackedListener;
580 }
581
582
583 status_t
StreamNumberNode(const BJsonEvent & event)584 BJsonTextWriter::StreamNumberNode(const BJsonEvent& event)
585 {
586 return StreamStringVerbatim(event.Content());
587 }
588
589
590 status_t
StreamStringVerbatim(const char * string)591 BJsonTextWriter::StreamStringVerbatim(const char* string)
592 {
593 return StreamStringVerbatim(string, 0, strlen(string));
594 }
595
596
597 status_t
StreamStringVerbatim(const char * string,off_t offset,size_t length)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
StreamStringEncoded(const char * string)606 BJsonTextWriter::StreamStringEncoded(const char* string)
607 {
608 return StreamStringEncoded(string, 0, strlen(string));
609 }
610
611
612 status_t
StreamStringUnicodeCharacter(uint32 c)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
StreamStringEncoded(const char * string,off_t offset,size_t length)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
StreamQuotedEncodedString(const char * string)677 BJsonTextWriter::StreamQuotedEncodedString(const char* string)
678 {
679 return StreamQuotedEncodedString(string, 0, strlen(string));
680 }
681
682
683 status_t
StreamQuotedEncodedString(const char * string,off_t offset,size_t length)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
StreamChar(char c)703 BJsonTextWriter::StreamChar(char c)
704 {
705 return fDataIO->WriteExactly(&c, 1);
706 }
707