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
23#include <bmk-core/null.h>
24#include <bmk-core/jsmn.h>
25
26/**
27 * Allocates a fresh unused token from the token pull.
28 */
29static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
30		jsmntok_t *tokens, unsigned long num_tokens) {
31	jsmntok_t *tok;
32	if (parser->toknext >= num_tokens) {
33		return NULL;
34	}
35	tok = &tokens[parser->toknext++];
36	tok->start = tok->end = -1;
37	tok->size = 0;
38#ifdef JSMN_PARENT_LINKS
39	tok->parent = -1;
40#endif
41	return tok;
42}
43
44/**
45 * Fills token type and boundaries.
46 */
47static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
48                            int start, int end) {
49	token->type = type;
50	token->start = start;
51	token->end = end;
52	token->size = 0;
53}
54
55/**
56 * Fills next available token with JSON primitive.
57 */
58static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
59		unsigned long len, jsmntok_t *tokens, unsigned long num_tokens) {
60	jsmntok_t *token;
61	int start;
62
63	start = parser->pos;
64
65	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
66		switch (js[parser->pos]) {
67#ifndef JSMN_STRICT
68			/* In strict mode primitive must be followed by "," or "}" or "]" */
69			case ':':
70#endif
71			case '\t' : case '\r' : case '\n' : case ' ' :
72			case ','  : case ']'  : case '}' :
73				goto found;
74		}
75		if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
76			parser->pos = start;
77			return JSMN_ERROR_INVAL;
78		}
79	}
80#ifdef JSMN_STRICT
81	/* In strict mode primitive must be followed by a comma/object/array */
82	parser->pos = start;
83	return JSMN_ERROR_PART;
84#endif
85
86found:
87	if (tokens == NULL) {
88		parser->pos--;
89		return 0;
90	}
91	token = jsmn_alloc_token(parser, tokens, num_tokens);
92	if (token == NULL) {
93		parser->pos = start;
94		return JSMN_ERROR_NOMEM;
95	}
96	jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
97#ifdef JSMN_PARENT_LINKS
98	token->parent = parser->toksuper;
99#endif
100	parser->pos--;
101	return 0;
102}
103
104/**
105 * Filsl next token with JSON string.
106 */
107static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
108		unsigned long len, jsmntok_t *tokens, unsigned long num_tokens) {
109	jsmntok_t *token;
110
111	int start = parser->pos;
112
113	parser->pos++;
114
115	/* Skip starting quote */
116	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
117		char c = js[parser->pos];
118
119		/* Quote: end of string */
120		if (c == '\"') {
121			if (tokens == NULL) {
122				return 0;
123			}
124			token = jsmn_alloc_token(parser, tokens, num_tokens);
125			if (token == NULL) {
126				parser->pos = start;
127				return JSMN_ERROR_NOMEM;
128			}
129			jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
130#ifdef JSMN_PARENT_LINKS
131			token->parent = parser->toksuper;
132#endif
133			return 0;
134		}
135
136		/* Backslash: Quoted symbol expected */
137		if (c == '\\' && parser->pos + 1 < len) {
138			int i;
139			parser->pos++;
140			switch (js[parser->pos]) {
141				/* Allowed escaped symbols */
142				case '\"': case '/' : case '\\' : case 'b' :
143				case 'f' : case 'r' : case 'n'  : case 't' :
144					break;
145				/* Allows escaped symbol \uXXXX */
146				case 'u':
147					parser->pos++;
148					for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
149						/* If it isn't a hex character we have an error */
150						if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
151									(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
152									(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
153							parser->pos = start;
154							return JSMN_ERROR_INVAL;
155						}
156						parser->pos++;
157					}
158					parser->pos--;
159					break;
160				/* Unexpected symbol */
161				default:
162					parser->pos = start;
163					return JSMN_ERROR_INVAL;
164			}
165		}
166	}
167	parser->pos = start;
168	return JSMN_ERROR_PART;
169}
170
171/**
172 * Parse JSON string and fill tokens.
173 */
174jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned long len,
175		jsmntok_t *tokens, unsigned int num_tokens) {
176	jsmnerr_t r;
177	int i;
178	jsmntok_t *token;
179	int count = 0;
180
181	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
182		char c;
183		jsmntype_t type;
184
185		c = js[parser->pos];
186		switch (c) {
187			case '{': case '[':
188				count++;
189				if (tokens == NULL) {
190					break;
191				}
192				token = jsmn_alloc_token(parser, tokens, num_tokens);
193				if (token == NULL)
194					return JSMN_ERROR_NOMEM;
195				if (parser->toksuper != -1) {
196					tokens[parser->toksuper].size++;
197#ifdef JSMN_PARENT_LINKS
198					token->parent = parser->toksuper;
199#endif
200				}
201				token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
202				token->start = parser->pos;
203				parser->toksuper = parser->toknext - 1;
204				break;
205			case '}': case ']':
206				if (tokens == NULL)
207					break;
208				type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
209#ifdef JSMN_PARENT_LINKS
210				if (parser->toknext < 1) {
211					return JSMN_ERROR_INVAL;
212				}
213				token = &tokens[parser->toknext - 1];
214				for (;;) {
215					if (token->start != -1 && token->end == -1) {
216						if (token->type != type) {
217							return JSMN_ERROR_INVAL;
218						}
219						token->end = parser->pos + 1;
220						parser->toksuper = token->parent;
221						break;
222					}
223					if (token->parent == -1) {
224						break;
225					}
226					token = &tokens[token->parent];
227				}
228#else
229				for (i = parser->toknext - 1; i >= 0; i--) {
230					token = &tokens[i];
231					if (token->start != -1 && token->end == -1) {
232						if (token->type != type) {
233							return JSMN_ERROR_INVAL;
234						}
235						parser->toksuper = -1;
236						token->end = parser->pos + 1;
237						break;
238					}
239				}
240				/* Error if unmatched closing bracket */
241				if (i == -1) return JSMN_ERROR_INVAL;
242				for (; i >= 0; i--) {
243					token = &tokens[i];
244					if (token->start != -1 && token->end == -1) {
245						parser->toksuper = i;
246						break;
247					}
248				}
249#endif
250				break;
251			case '\"':
252				r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
253				if (r < 0) return r;
254				count++;
255				if (parser->toksuper != -1 && tokens != NULL)
256					tokens[parser->toksuper].size++;
257				break;
258			case '\t' : case '\r' : case '\n' : case ' ':
259				break;
260			case ':':
261				parser->toksuper = parser->toknext - 1;
262				break;
263			case ',':
264				if (tokens != NULL &&
265						tokens[parser->toksuper].type != JSMN_ARRAY &&
266						tokens[parser->toksuper].type != JSMN_OBJECT) {
267#ifdef JSMN_PARENT_LINKS
268					parser->toksuper = tokens[parser->toksuper].parent;
269#else
270					for (i = parser->toknext - 1; i >= 0; i--) {
271						if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
272							if (tokens[i].start != -1 && tokens[i].end == -1) {
273								parser->toksuper = i;
274								break;
275							}
276						}
277					}
278#endif
279				}
280				break;
281#ifdef JSMN_STRICT
282			/* In strict mode primitives are: numbers and booleans */
283			case '-': case '0': case '1' : case '2': case '3' : case '4':
284			case '5': case '6': case '7' : case '8': case '9':
285			case 't': case 'f': case 'n' :
286				/* And they must not be keys of the object */
287				if (tokens != NULL) {
288					jsmntok_t *t = &tokens[parser->toksuper];
289					if (t->type == JSMN_OBJECT ||
290							(t->type == JSMN_STRING && t->size != 0)) {
291						return JSMN_ERROR_INVAL;
292					}
293				}
294#else
295			/* In non-strict mode every unquoted value is a primitive */
296			default:
297#endif
298				r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
299				if (r < 0) return r;
300				count++;
301				if (parser->toksuper != -1 && tokens != NULL)
302					tokens[parser->toksuper].size++;
303				break;
304
305#ifdef JSMN_STRICT
306			/* Unexpected char in strict mode */
307			default:
308				return JSMN_ERROR_INVAL;
309#endif
310		}
311	}
312
313	for (i = parser->toknext - 1; i >= 0; i--) {
314		/* Unmatched opened object or array */
315		if (tokens[i].start != -1 && tokens[i].end == -1) {
316			return JSMN_ERROR_PART;
317		}
318	}
319
320	return count;
321}
322
323/**
324 * Creates a new parser based over a given  buffer with an array of tokens
325 * available.
326 */
327void jsmn_init(jsmn_parser *parser) {
328	parser->pos = 0;
329	parser->toknext = 0;
330	parser->toksuper = -1;
331}
332
333