xref: /haiku/src/kits/shared/Json.cpp (revision 2897df967633aab846ff4917b53e2af7d1e54eeb)
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 
26 namespace BPrivate {
27 
28 
29 static bool
30 b_jsonparse_is_hex(char c)
31 {
32 	return isdigit(c)
33 		|| (c > 0x41 && c <= 0x46)
34 		|| (c > 0x61 && c <= 0x66);
35 }
36 
37 
38 static bool
39 b_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 
52 class JsonParseContext {
53 public:
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 
112 private:
113 	BJsonEventListener*		fListener;
114 	BDataIO*				fData;
115 	uint32					fLineNumber;
116 	char					fPushbackChar;
117 	bool					fHasPushbackChar;
118 };
119 
120 
121 status_t
122 BJson::Parse(const BString& JSON, BMessage& message)
123 {
124 	return Parse(JSON.String(), message);
125 }
126 
127 
128 status_t
129 BJson::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 
143 status_t
144 BJson::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 
160 void
161 BJson::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 
172 bool
173 BJson::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 
198 bool
199 BJson::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 
221 bool
222 BJson::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 
288 bool
289 BJson::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 
347 bool
348 BJson::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 
407 bool
408 BJson::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 
466 bool
467 BJson::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 
512 bool
513 BJson::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 
571 bool
572 BJson::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 
622 bool
623 BJson::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 
639 bool
640 BJson::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 
673 bool
674 BJson::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 
728 bool
729 BJson::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