xref: /netbsd-src/crypto/external/bsd/netpgp/dist/src/libmj/mj.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*-
2  * Copyright (c) 2010 Alistair Crooks <agc@NetBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #include <sys/types.h>
26 
27 #include <inttypes.h>
28 #include <regex.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "mj.h"
36 #include "defs.h"
37 
38 /* save 'n' chars of 's' in malloc'd memory */
39 static char *
40 strnsave(const char *s, int n, unsigned esc)
41 {
42 	char	*newc;
43 	char	*cp;
44 	int	 i;
45 
46 	if (n < 0) {
47 		n = (int)strlen(s);
48 	}
49 	NEWARRAY(char, cp, (n * 2) + 1, "strnsave", return NULL);
50 	if (esc) {
51 		newc = cp;
52 		for (i = 0 ; i < n ; i++) {
53 			if (*s == '\\') {
54 				*newc++ = *s++;
55 			} else if (*s == '"') {
56 				*newc++ = '\\';
57 			}
58 			*newc++ = *s++;
59 		}
60 		*newc = 0x0;
61 	} else {
62 		(void) memcpy(cp, s, (unsigned)n);
63 		cp[n] = 0x0;
64 	}
65 	return cp;
66 }
67 
68 /* look in an object for the item */
69 static int
70 findentry(mj_t *atom, const char *name, const unsigned from, const unsigned incr)
71 {
72 	unsigned	i;
73 
74 	for (i = from ; i < atom->c ; i += incr) {
75 		if (strcmp(name, atom->value.v[i].value.s) == 0) {
76 			return i;
77 		}
78 	}
79 	return -1;
80 }
81 
82 /* create a real number */
83 static void
84 create_number(mj_t *atom, double d)
85 {
86 	char	number[128];
87 
88 	atom->type = MJ_NUMBER;
89 	atom->c = snprintf(number, sizeof(number), "%g", d);
90 	atom->value.s = strnsave(number, (int)atom->c, 0);
91 }
92 
93 /* create an integer */
94 static void
95 create_integer(mj_t *atom, int64_t i)
96 {
97 	char	number[128];
98 
99 	atom->type = MJ_NUMBER;
100 	atom->c = snprintf(number, sizeof(number), "%" PRIi64, i);
101 	atom->value.s = strnsave(number, (int)atom->c, 0);
102 }
103 
104 /* create a string */
105 static void
106 create_string(mj_t *atom, const char *s)
107 {
108 	atom->type = MJ_STRING;
109 	atom->value.s = strnsave(s, -1, 1);
110 	atom->c = (unsigned)strlen(atom->value.s);
111 }
112 
113 #define MJ_OPEN_BRACKET		(MJ_OBJECT + 1)		/* 8 */
114 #define MJ_CLOSE_BRACKET	(MJ_OPEN_BRACKET + 1)	/* 9 */
115 #define MJ_OPEN_BRACE		(MJ_CLOSE_BRACKET + 1)	/* 10 */
116 #define MJ_CLOSE_BRACE		(MJ_OPEN_BRACE + 1)	/* 11 */
117 #define MJ_COLON		(MJ_CLOSE_BRACE + 1)	/* 12 */
118 #define MJ_COMMA		(MJ_COLON + 1)		/* 13 */
119 
120 /* return the token type, and start and finish locations in string */
121 static int
122 gettok(const char *s, int *from, int *to, int *tok)
123 {
124 	static regex_t	tokregex;
125 	regmatch_t	matches[15];
126 	static int	compiled;
127 
128 	if (!compiled) {
129 		compiled = 1;
130 		(void) regcomp(&tokregex,
131 			"[ \t\r\n]*(([+-]?[0-9]{1,21}(\\.[0-9]*)?([eE][-+][0-9]+)?)|"
132 			"(\"([^\"]|\\\\.)*\")|(null)|(false)|(true)|([][{}:,]))",
133 			REG_EXTENDED);
134 	}
135 	if (regexec(&tokregex, &s[*from = *to], 15, matches, 0) != 0) {
136 		return *tok = -1;
137 	}
138 	*to = *from + (int)(matches[1].rm_eo);
139 	*tok = (matches[2].rm_so >= 0) ? MJ_NUMBER :
140 		(matches[5].rm_so >= 0) ? MJ_STRING :
141 		(matches[7].rm_so >= 0) ? MJ_NULL :
142 		(matches[8].rm_so >= 0) ? MJ_FALSE :
143 		(matches[9].rm_so >= 0) ? MJ_TRUE :
144 		(matches[10].rm_so < 0) ? -1 :
145 			(s[*from + (int)(matches[10].rm_so)] == '[') ? MJ_OPEN_BRACKET :
146 			(s[*from + (int)(matches[10].rm_so)] == ']') ? MJ_CLOSE_BRACKET :
147 			(s[*from + (int)(matches[10].rm_so)] == '{') ? MJ_OPEN_BRACE :
148 			(s[*from + (int)(matches[10].rm_so)] == '}') ? MJ_CLOSE_BRACE :
149 			(s[*from + (int)(matches[10].rm_so)] == ':') ? MJ_COLON :
150 				MJ_COMMA;
151 	*from += (int)(matches[1].rm_so);
152 	return *tok;
153 }
154 
155 /* minor function used to indent a JSON field */
156 static void
157 indent(FILE *fp, unsigned depth, const char *trailer)
158 {
159 	unsigned	i;
160 
161 	for (i = 0 ; i < depth ; i++) {
162 		(void) fprintf(fp, "    ");
163 	}
164 	if (trailer) {
165 		(void) fprintf(fp, "%s", trailer);
166 	}
167 }
168 
169 /***************************************************************************/
170 
171 /* return the number of entries in the array */
172 int
173 mj_arraycount(mj_t *atom)
174 {
175 	return atom->c;
176 }
177 
178 /* create a new JSON node */
179 int
180 mj_create(mj_t *atom, const char *type, ...)
181 {
182 	va_list	 args;
183 
184 	if (strcmp(type, "false") == 0) {
185 		atom->type = MJ_FALSE;
186 		atom->c = 0;
187 	} else if (strcmp(type, "true") == 0) {
188 		atom->type = MJ_TRUE;
189 		atom->c = 1;
190 	} else if (strcmp(type, "null") == 0) {
191 		atom->type = MJ_NULL;
192 	} else if (strcmp(type, "number") == 0) {
193 		va_start(args, type);
194 		create_number(atom, (double)va_arg(args, double));
195 		va_end(args);
196 	} else if (strcmp(type, "integer") == 0) {
197 		va_start(args, type);
198 		create_integer(atom, (int64_t)va_arg(args, int64_t));
199 		va_end(args);
200 	} else if (strcmp(type, "string") == 0) {
201 		va_start(args, type);
202 		create_string(atom, (char *)va_arg(args, char *));
203 		va_end(args);
204 	} else if (strcmp(type, "array") == 0) {
205 		atom->type = MJ_ARRAY;
206 	} else if (strcmp(type, "object") == 0) {
207 		atom->type = MJ_OBJECT;
208 	} else {
209 		(void) fprintf(stderr, "weird type '%s'\n", type);
210 		return 0;
211 	}
212 	return 1;
213 }
214 
215 /* put a JSON tree into a text string */
216 int
217 mj_snprint(char *buf, size_t size, mj_t *atom)
218 {
219 	unsigned	i;
220 	int		cc;
221 
222 	switch(atom->type) {
223 	case MJ_NULL:
224 		return snprintf(buf, size, "null");
225 	case MJ_FALSE:
226 		return snprintf(buf, size, "false");
227 	case MJ_TRUE:
228 		return snprintf(buf, size, "true");
229 	case MJ_NUMBER:
230 		return snprintf(buf, size, "%s", atom->value.s);
231 	case MJ_STRING:
232 		return snprintf(buf, size, "\"%s\"", atom->value.s);
233 	case MJ_ARRAY:
234 		cc = snprintf(buf, size, "[ ");
235 		for (i = 0 ; i < atom->c ; i++) {
236 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i]);
237 			if (i < atom->c - 1) {
238 				cc += snprintf(&buf[cc], size - cc, ", ");
239 			}
240 		}
241 		return cc + snprintf(&buf[cc], size - cc, "]\n");
242 	case MJ_OBJECT:
243 		cc = snprintf(buf, size, "{ ");
244 		for (i = 0 ; i < atom->c ; i += 2) {
245 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i]);
246 			cc += snprintf(&buf[cc], size - cc, ":");
247 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i + 1]);
248 			if (i + 1 < atom->c - 1) {
249 				cc += snprintf(&buf[cc], size - cc, ", ");
250 			}
251 		}
252 		return cc + snprintf(&buf[cc], size - cc, "}\n");
253 	default:
254 		(void) fprintf(stderr, "mj_snprint: weird type %d\n", atom->type);
255 		return 0;
256 	}
257 }
258 
259 /* allocate and print the atom */
260 int
261 mj_asprint(char **buf, mj_t *atom)
262 {
263 	int	 size;
264 
265 	size = mj_string_size(atom);
266 	if ((*buf = calloc(1, (unsigned)(size + 1))) == NULL) {
267 		return -1;
268 	}
269 	(void) mj_snprint(*buf, (unsigned)(size + 1), atom);
270 	return size + 1;
271 }
272 
273 /* read into a JSON tree from a string */
274 int
275 mj_parse(mj_t *atom, const char *s, int *from, int *to, int *tok)
276 {
277 	int	i;
278 
279 	switch(atom->type = *tok = gettok(s, from, to, tok)) {
280 	case MJ_NUMBER:
281 		atom->value.s = strnsave(&s[*from], *to - *from, 1);
282 		atom->c = atom->size = (unsigned)strlen(atom->value.s);
283 		return gettok(s, from, to, tok);
284 	case MJ_STRING:
285 		atom->value.s = strnsave(&s[*from + 1], *to - *from - 2, 1);
286 		atom->c = atom->size = (unsigned)strlen(atom->value.s);
287 		return gettok(s, from, to, tok);
288 	case MJ_NULL:
289 	case MJ_FALSE:
290 	case MJ_TRUE:
291 		atom->c = (unsigned)*to;
292 		return gettok(s, from, to, tok);
293 	case MJ_OPEN_BRACKET:
294 		mj_create(atom, "array");
295 		ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
296 		while (mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACKET) {
297 			if (*tok != MJ_COMMA) {
298 				(void) fprintf(stderr, "1. expected comma (got %d) at '%s'\n", *tok, &s[*from]);
299 				break;
300 			}
301 			ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
302 		}
303 		return gettok(s, from, to, tok);
304 	case MJ_OPEN_BRACE:
305 		mj_create(atom, "object");
306 		ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
307 		for (i = 0 ; mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACE ; i++) {
308 			if (((i % 2) == 0 && *tok != MJ_COLON) || ((i % 2) == 1 && *tok != MJ_COMMA)) {
309 				(void) fprintf(stderr, "2. expected comma (got %d) at '%s'\n", *tok, &s[*from]);
310 				break;
311 			}
312 			ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
313 		}
314 		return gettok(s, from, to, tok);
315 	default:
316 		return *tok;
317 	}
318 }
319 
320 /* return the index of the item which corresponds to the name in the array */
321 int
322 mj_object_find(mj_t *atom, const char *name, const unsigned from, const unsigned incr)
323 {
324 	return findentry(atom, name, from, incr);
325 }
326 
327 /* find an atom in a composite mj JSON node */
328 mj_t *
329 mj_get_atom(mj_t *atom, ...)
330 {
331 	unsigned	 i;
332 	va_list		 args;
333 	char		*name;
334 	int		 n;
335 
336 	switch(atom->type) {
337 	case MJ_ARRAY:
338 		va_start(args, atom);
339 		i = va_arg(args, int);
340 		va_end(args);
341 		return (i < atom->c) ? &atom->value.v[i] : NULL;
342 	case MJ_OBJECT:
343 		va_start(args, atom);
344 		name = va_arg(args, char *);
345 		va_end(args);
346 		return ((n = findentry(atom, name, 0, 2)) >= 0) ? &atom->value.v[n + 1] : NULL;
347 	default:
348 		return NULL;
349 	}
350 }
351 
352 /* perform a deep copy on an mj JSON atom */
353 int
354 mj_deepcopy(mj_t *dst, mj_t *src)
355 {
356 	unsigned	i;
357 
358 	switch(src->type) {
359 	case MJ_FALSE:
360 	case MJ_TRUE:
361 	case MJ_NULL:
362 		(void) memcpy(dst, src, sizeof(*dst));
363 		return 1;
364 	case MJ_STRING:
365 	case MJ_NUMBER:
366 		(void) memcpy(dst, src, sizeof(*dst));
367 		dst->value.s = strnsave(src->value.s, -1, 0);
368 		dst->c = dst->size = (unsigned)strlen(dst->value.s);
369 		return 1;
370 	case MJ_ARRAY:
371 	case MJ_OBJECT:
372 		(void) memcpy(dst, src, sizeof(*dst));
373 		NEWARRAY(mj_t, dst->value.v, dst->size, "mj_deepcopy()", return 0);
374 		for (i = 0 ; i < src->c ; i++) {
375 			if (!mj_deepcopy(&dst->value.v[i], &src->value.v[i])) {
376 				return 0;
377 			}
378 		}
379 		return 1;
380 	default:
381 		(void) fprintf(stderr, "weird type '%d'\n", src->type);
382 		return 0;
383 	}
384 }
385 
386 /* do a deep delete on the object */
387 void
388 mj_delete(mj_t *atom)
389 {
390 	unsigned	i;
391 
392 	switch(atom->type) {
393 	case MJ_STRING:
394 	case MJ_NUMBER:
395 		free(atom->value.s);
396 		break;
397 	case MJ_ARRAY:
398 	case MJ_OBJECT:
399 		for (i = 0 ; i < atom->c ; i++) {
400 			mj_delete(&atom->value.v[i]);
401 		}
402 		break;
403 	default:
404 		break;
405 	}
406 }
407 
408 /* return the string size needed for the textual output of the JSON node */
409 int
410 mj_string_size(mj_t *atom)
411 {
412 	unsigned	i;
413 	int		cc;
414 
415 	switch(atom->type) {
416 	case MJ_NULL:
417 	case MJ_TRUE:
418 		return 4;
419 	case MJ_FALSE:
420 		return 5;
421 	case MJ_NUMBER:
422 		return atom->c;
423 	case MJ_STRING:
424 		return atom->c + 2;
425 	case MJ_ARRAY:
426 		for (cc = 2, i = 0 ; i < atom->c ; i++) {
427 			cc += mj_string_size(&atom->value.v[i]);
428 			if (i < atom->c - 1) {
429 				cc += 2;
430 			}
431 		}
432 		return cc + 1 + 1;
433 	case MJ_OBJECT:
434 		for (cc = 2, i = 0 ; i < atom->c ; i += 2) {
435 			cc += mj_string_size(&atom->value.v[i]) + 1 + mj_string_size(&atom->value.v[i + 1]);
436 			if (i + 1 < atom->c - 1) {
437 				cc += 2;
438 			}
439 		}
440 		return cc + 1 + 1;
441 	default:
442 		(void) fprintf(stderr, "mj_string_size: weird type %d\n", atom->type);
443 		return 0;
444 	}
445 }
446 
447 /* create a new atom, and append it to the array or object */
448 int
449 mj_append(mj_t *atom, const char *type, ...)
450 {
451 	va_list	 args;
452 
453 	if (atom->type != MJ_ARRAY && atom->type != MJ_OBJECT) {
454 		return 0;
455 	}
456 	ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append()", return 0);
457 	va_start(args, type);
458 	if (strcmp(type, "string") == 0) {
459 		create_string(&atom->value.v[atom->c++], (char *)va_arg(args, char *));
460 	} else if (strcmp(type, "integer") == 0) {
461 		create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t));
462 	} else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) {
463 		mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *));
464 	} else {
465 		(void) fprintf(stderr, "mj_append: weird type '%s'\n", type);
466 	}
467 	va_end(args);
468 	return 1;
469 }
470 
471 /* append a field to an object */
472 int
473 mj_append_field(mj_t *atom, const char *name, const char *type, ...)
474 {
475 	va_list	 args;
476 
477 	if (atom->type != MJ_OBJECT) {
478 		return 0;
479 	}
480 	mj_append(atom, "string", name);
481 	ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append_field()", return 0);
482 	va_start(args, type);
483 	if (strcmp(type, "string") == 0) {
484 		create_string(&atom->value.v[atom->c++], (char *)va_arg(args, char *));
485 	} else if (strcmp(type, "integer") == 0) {
486 		create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t));
487 	} else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) {
488 		mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *));
489 	} else {
490 		(void) fprintf(stderr, "mj_append_field: weird type '%s'\n", type);
491 	}
492 	va_end(args);
493 	return 1;
494 }
495 
496 /* make sure a JSON object is politically correct */
497 int
498 mj_lint(mj_t *obj)
499 {
500 	unsigned	i;
501 	int		ret;
502 
503 	switch(obj->type) {
504 	case MJ_NULL:
505 	case MJ_FALSE:
506 	case MJ_TRUE:
507 		if (obj->value.s != NULL) {
508 			(void) fprintf(stderr, "null/false/true: non zero string\n");
509 			return 0;
510 		}
511 		return 1;
512 	case MJ_NUMBER:
513 	case MJ_STRING:
514 		if (obj->c > obj->size) {
515 			(void) fprintf(stderr, "string/number lint c (%u) > size (%u)\n", obj->c, obj->size);
516 			return 0;
517 		}
518 		return 1;
519 	case MJ_ARRAY:
520 	case MJ_OBJECT:
521 		if (obj->c > obj->size) {
522 			(void) fprintf(stderr, "array/object lint c (%u) > size (%u)\n", obj->c, obj->size);
523 			return 0;
524 		}
525 		for (ret = 1, i = 0 ; i < obj->c ; i++) {
526 			if (!mj_lint(&obj->value.v[i])) {
527 				(void) fprintf(stderr, "array/object lint found at %d of %p\n", i, obj);
528 				ret = 0;
529 			}
530 		}
531 		return ret;
532 	default:
533 		(void) fprintf(stderr, "problem type %d in %p\n", obj->type, obj);
534 		return 0;
535 	}
536 }
537 
538 /* pretty-print a JSON struct - can be called recursively */
539 int
540 mj_pretty(mj_t *mj, void *vp, unsigned depth, const char *trailer)
541 {
542 	unsigned	 i;
543 	FILE		*fp;
544 
545 	fp = (FILE *)vp;
546 	switch(mj->type) {
547 	case MJ_NUMBER:
548 	case MJ_TRUE:
549 	case MJ_FALSE:
550 	case MJ_NULL:
551 		indent(fp, depth, mj->value.s);
552 		break;
553 	case MJ_STRING:
554 		indent(fp, depth, NULL);
555 		(void) fprintf(fp, "\"%s\"", mj->value.s);
556 		break;
557 	case MJ_ARRAY:
558 		indent(fp, depth, "[\n");
559 		for (i = 0 ; i < mj->c ; i++) {
560 			mj_pretty(&mj->value.v[i], fp, depth + 1, (i < mj->c - 1) ? ",\n" : "\n");
561 		}
562 		indent(fp, depth, "]");
563 		break;
564 	case MJ_OBJECT:
565 		indent(fp, depth, "{\n");
566 		for (i = 0 ; i < mj->c ; i += 2) {
567 			mj_pretty(&mj->value.v[i], fp, depth + 1, " : ");
568 			mj_pretty(&mj->value.v[i + 1], fp, 0, (i < mj->c - 2) ? ",\n" : "\n");
569 		}
570 		indent(fp, depth, "}");
571 		break;
572 	}
573 	indent(fp, 0, trailer);
574 	return 1;
575 }
576