xref: /haiku/src/kits/shared/JsonTextWriter.cpp (revision 20e420cdbe26b7702f1574be8ef8235bdf55660a)
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