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