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