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