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
15namespace BPrivate {
16
17
18static bool
19b_json_is_7bit_clean(uint8 c)
20{
21	return c >= 0x20 && c < 0x7f;
22}
23
24
25static bool
26b_json_is_illegal(uint8 c)
27{
28	return c < 0x20 || c == 0x7f;
29}
30
31
32static const char*
33b_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
58static size_t
59b_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
78class BJsonTextWriterStackedEventListener : public BJsonEventListener {
79public:
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
95protected:
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
129class BJsonTextWriterArrayStackedEventListener
130	: public BJsonTextWriterStackedEventListener {
131public:
132								BJsonTextWriterArrayStackedEventListener(
133									BJsonTextWriter* writer,
134									BJsonTextWriterStackedEventListener* parent);
135								~BJsonTextWriterArrayStackedEventListener();
136
137				bool			Handle(const BJsonEvent& event);
138
139protected:
140				bool			WillAdd();
141};
142
143
144class BJsonTextWriterObjectStackedEventListener
145	: public BJsonTextWriterStackedEventListener {
146public:
147								BJsonTextWriterObjectStackedEventListener(
148									BJsonTextWriter* writer,
149									BJsonTextWriterStackedEventListener* parent);
150								~BJsonTextWriterObjectStackedEventListener();
151
152				bool			Handle(const BJsonEvent& event);
153};
154
155} // namespace BPrivate
156
157
158using BPrivate::BJsonTextWriterStackedEventListener;
159using BPrivate::BJsonTextWriterArrayStackedEventListener;
160using BPrivate::BJsonTextWriterObjectStackedEventListener;
161
162
163// #pragma mark - BJsonTextWriterStackedEventListener
164
165
166BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
167	BJsonTextWriter* writer,
168	BJsonTextWriterStackedEventListener* parent)
169{
170	fWriter = writer;
171	fParent = parent;
172	fCount = 0;
173}
174
175
176BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
177{
178}
179
180
181bool
182BJsonTextWriterStackedEventListener::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
271void
272BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line,
273	const char* message)
274{
275	fWriter->HandleError(status, line, message);
276}
277
278
279void
280BJsonTextWriterStackedEventListener::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
288status_t
289BJsonTextWriterStackedEventListener::ErrorStatus()
290{
291	return fWriter->ErrorStatus();
292}
293
294
295BJsonTextWriterStackedEventListener*
296BJsonTextWriterStackedEventListener::Parent()
297{
298	return fParent;
299}
300
301
302status_t
303BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event)
304{
305	return fWriter->StreamNumberNode(event);
306}
307
308
309status_t
310BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string)
311{
312	return fWriter->StreamStringVerbatim(string);
313}
314
315
316status_t
317BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string,
318	off_t offset, size_t length)
319{
320	return fWriter->StreamStringVerbatim(string, offset, length);
321}
322
323
324status_t
325BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string)
326{
327	return fWriter->StreamStringEncoded(string);
328}
329
330
331status_t
332BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string,
333	off_t offset, size_t length)
334{
335	return fWriter->StreamStringEncoded(string, offset, length);
336}
337
338
339status_t
340BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
341	const char* string)
342{
343	return fWriter->StreamQuotedEncodedString(string);
344}
345
346
347status_t
348BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
349	const char* string, off_t offset, size_t length)
350{
351	return fWriter->StreamQuotedEncodedString(string, offset, length);
352}
353
354
355status_t
356BJsonTextWriterStackedEventListener::StreamChar(char c)
357{
358	return fWriter->StreamChar(c);
359}
360
361
362bool
363BJsonTextWriterStackedEventListener::WillAdd()
364{
365	return true; // carry on
366}
367
368
369void
370BJsonTextWriterStackedEventListener::DidAdd()
371{
372	fCount++;
373}
374
375
376void
377BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
378	BJsonTextWriterStackedEventListener* stackedListener)
379{
380	fWriter->SetStackedListener(stackedListener);
381}
382
383
384// #pragma mark - BJsonTextWriterArrayStackedEventListener
385
386
387BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
388	BJsonTextWriter* writer,
389	BJsonTextWriterStackedEventListener* parent)
390	:
391	BJsonTextWriterStackedEventListener(writer, parent)
392{
393}
394
395
396BJsonTextWriterArrayStackedEventListener
397	::~BJsonTextWriterArrayStackedEventListener()
398{
399}
400
401
402bool
403BJsonTextWriterArrayStackedEventListener::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
436bool
437BJsonTextWriterArrayStackedEventListener::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
457BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
458	BJsonTextWriter* writer,
459	BJsonTextWriterStackedEventListener* parent)
460	:
461	BJsonTextWriterStackedEventListener(writer, parent)
462{
463}
464
465
466BJsonTextWriterObjectStackedEventListener
467	::~BJsonTextWriterObjectStackedEventListener()
468{
469}
470
471
472bool
473BJsonTextWriterObjectStackedEventListener::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
523BJsonTextWriter::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
539BJsonTextWriter::~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
553bool
554BJsonTextWriter::Handle(const BJsonEvent& event)
555{
556	return fStackedListener->Handle(event);
557}
558
559
560void
561BJsonTextWriter::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
575void
576BJsonTextWriter::SetStackedListener(
577	BJsonTextWriterStackedEventListener* stackedListener)
578{
579	fStackedListener = stackedListener;
580}
581
582
583status_t
584BJsonTextWriter::StreamNumberNode(const BJsonEvent& event)
585{
586	return StreamStringVerbatim(event.Content());
587}
588
589
590status_t
591BJsonTextWriter::StreamStringVerbatim(const char* string)
592{
593	return StreamStringVerbatim(string, 0, strlen(string));
594}
595
596
597status_t
598BJsonTextWriter::StreamStringVerbatim(const char* string,
599	off_t offset, size_t length)
600{
601	return fDataIO->WriteExactly(&string[offset], length);
602}
603
604
605status_t
606BJsonTextWriter::StreamStringEncoded(const char* string)
607{
608	return StreamStringEncoded(string, 0, strlen(string));
609}
610
611
612status_t
613BJsonTextWriter::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
624status_t
625BJsonTextWriter::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
676status_t
677BJsonTextWriter::StreamQuotedEncodedString(const char* string)
678{
679	return StreamQuotedEncodedString(string, 0, strlen(string));
680}
681
682
683status_t
684BJsonTextWriter::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
702status_t
703BJsonTextWriter::StreamChar(char c)
704{
705	return fDataIO->WriteExactly(&c, 1);
706}
707