1/*
2 * Copyright (c) 2010 Serge A. Zaitsev
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 *
22 * Slightly modified by AK to not assume 0 terminated input.
23 *
24 * $FreeBSD$
25 *
26 */
27
28#include <stdlib.h>
29#include "jsmn.h"
30
31/*
32 * Allocates a fresh unused token from the token pool.
33 */
34static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
35				   jsmntok_t *tokens, size_t num_tokens)
36{
37	jsmntok_t *tok;
38
39	if ((unsigned)parser->toknext >= num_tokens)
40		return NULL;
41	tok = &tokens[parser->toknext++];
42	tok->start = tok->end = -1;
43	tok->size = 0;
44	return tok;
45}
46
47/*
48 * Fills token type and boundaries.
49 */
50static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
51			    int start, int end)
52{
53	token->type = type;
54	token->start = start;
55	token->end = end;
56	token->size = 0;
57}
58
59/*
60 * Fills next available token with JSON primitive.
61 */
62static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
63				      size_t len,
64				      jsmntok_t *tokens, size_t num_tokens)
65{
66	jsmntok_t *token;
67	int start;
68
69	start = parser->pos;
70
71	for (; parser->pos < len; parser->pos++) {
72		switch (js[parser->pos]) {
73#ifndef JSMN_STRICT
74		/*
75		 * In strict mode primitive must be followed by ","
76		 * or "}" or "]"
77		 */
78		case ':':
79#endif
80		case '\t':
81		case '\r':
82		case '\n':
83		case ' ':
84		case ',':
85		case ']':
86		case '}':
87			goto found;
88		default:
89			break;
90		}
91		if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
92			parser->pos = start;
93			return JSMN_ERROR_INVAL;
94		}
95	}
96#ifdef JSMN_STRICT
97	/*
98	 * In strict mode primitive must be followed by a
99	 * comma/object/array.
100	 */
101	parser->pos = start;
102	return JSMN_ERROR_PART;
103#endif
104
105found:
106	token = jsmn_alloc_token(parser, tokens, num_tokens);
107	if (token == NULL) {
108		parser->pos = start;
109		return JSMN_ERROR_NOMEM;
110	}
111	jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
112	parser->pos--; /* parent sees closing brackets */
113	return JSMN_SUCCESS;
114}
115
116/*
117 * Fills next token with JSON string.
118 */
119static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
120				   size_t len,
121				   jsmntok_t *tokens, size_t num_tokens)
122{
123	jsmntok_t *token;
124	int start = parser->pos;
125
126	/* Skip starting quote */
127	parser->pos++;
128
129	for (; parser->pos < len; parser->pos++) {
130		char c = js[parser->pos];
131
132		/* Quote: end of string */
133		if (c == '\"') {
134			token = jsmn_alloc_token(parser, tokens, num_tokens);
135			if (token == NULL) {
136				parser->pos = start;
137				return JSMN_ERROR_NOMEM;
138			}
139			jsmn_fill_token(token, JSMN_STRING, start+1,
140					parser->pos);
141			return JSMN_SUCCESS;
142		}
143
144		/* Backslash: Quoted symbol expected */
145		if (c == '\\') {
146			parser->pos++;
147			switch (js[parser->pos]) {
148				/* Allowed escaped symbols */
149			case '\"':
150			case '/':
151			case '\\':
152			case 'b':
153			case 'f':
154			case 'r':
155			case 'n':
156			case 't':
157				break;
158				/* Allows escaped symbol \uXXXX */
159			case 'u':
160				/* TODO */
161				break;
162				/* Unexpected symbol */
163			default:
164				parser->pos = start;
165				return JSMN_ERROR_INVAL;
166			}
167		}
168	}
169	parser->pos = start;
170	return JSMN_ERROR_PART;
171}
172
173/*
174 * Parse JSON string and fill tokens.
175 */
176jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
177		     jsmntok_t *tokens, unsigned int num_tokens)
178{
179	jsmnerr_t r;
180	int i;
181	jsmntok_t *token;
182
183	for (; parser->pos < len; parser->pos++) {
184		char c;
185		jsmntype_t type;
186
187		c = js[parser->pos];
188		switch (c) {
189		case '{':
190		case '[':
191			token = jsmn_alloc_token(parser, tokens, num_tokens);
192			if (token == NULL)
193				return JSMN_ERROR_NOMEM;
194			if (parser->toksuper != -1)
195				tokens[parser->toksuper].size++;
196			token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
197			token->start = parser->pos;
198			parser->toksuper = parser->toknext - 1;
199			break;
200		case '}':
201		case ']':
202			type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
203			for (i = parser->toknext - 1; i >= 0; i--) {
204				token = &tokens[i];
205				if (token->start != -1 && token->end == -1) {
206					if (token->type != type)
207						return JSMN_ERROR_INVAL;
208					parser->toksuper = -1;
209					token->end = parser->pos + 1;
210					break;
211				}
212			}
213			/* Error if unmatched closing bracket */
214			if (i == -1)
215				return JSMN_ERROR_INVAL;
216			for (; i >= 0; i--) {
217				token = &tokens[i];
218				if (token->start != -1 && token->end == -1) {
219					parser->toksuper = i;
220					break;
221				}
222			}
223			break;
224		case '\"':
225			r = jsmn_parse_string(parser, js, len, tokens,
226					      num_tokens);
227			if (r < 0)
228				return r;
229			if (parser->toksuper != -1)
230				tokens[parser->toksuper].size++;
231			break;
232		case '\t':
233		case '\r':
234		case '\n':
235		case ':':
236		case ',':
237		case ' ':
238			break;
239#ifdef JSMN_STRICT
240			/*
241			 * In strict mode primitives are:
242			 * numbers and booleans.
243			 */
244		case '-':
245		case '0':
246		case '1':
247		case '2':
248		case '3':
249		case '4':
250		case '5':
251		case '6':
252		case '7':
253		case '8':
254		case '9':
255		case 't':
256		case 'f':
257		case 'n':
258#else
259			/*
260			 * In non-strict mode every unquoted value
261			 * is a primitive.
262			 */
263			/*FALL THROUGH */
264		default:
265#endif
266			r = jsmn_parse_primitive(parser, js, len, tokens,
267						 num_tokens);
268			if (r < 0)
269				return r;
270			if (parser->toksuper != -1)
271				tokens[parser->toksuper].size++;
272			break;
273
274#ifdef JSMN_STRICT
275			/* Unexpected char in strict mode */
276		default:
277			return JSMN_ERROR_INVAL;
278#endif
279		}
280	}
281
282	for (i = parser->toknext - 1; i >= 0; i--) {
283		/* Unmatched opened object or array */
284		if (tokens[i].start != -1 && tokens[i].end == -1)
285			return JSMN_ERROR_PART;
286	}
287
288	return JSMN_SUCCESS;
289}
290
291/*
292 * Creates a new parser based over a given  buffer with an array of tokens
293 * available.
294 */
295void jsmn_init(jsmn_parser *parser)
296{
297	parser->pos = 0;
298	parser->toknext = 0;
299	parser->toksuper = -1;
300}
301
302const char *jsmn_strerror(jsmnerr_t err)
303{
304	switch (err) {
305	case JSMN_ERROR_NOMEM:
306		return "No enough tokens";
307	case JSMN_ERROR_INVAL:
308		return "Invalid character inside JSON string";
309	case JSMN_ERROR_PART:
310		return "The string is not a full JSON packet, more bytes expected";
311	case JSMN_SUCCESS:
312		return "Success";
313	default:
314		return "Unknown json error";
315	}
316}
317