summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/jplist.c695
-rw-r--r--src/jsmn.c280
-rw-r--r--src/jsmn.h91
-rw-r--r--src/plist.c6
5 files changed, 1074 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 6583add..d4c9e67 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,8 @@ libplist_2_0_la_SOURCES = \
22 time64_limits.h \ 22 time64_limits.h \
23 xplist.c \ 23 xplist.c \
24 bplist.c \ 24 bplist.c \
25 jsmn.c jsmn.h \
26 jplist.c \
25 plist.c plist.h 27 plist.c plist.h
26 28
27libplist___2_0_la_LIBADD = libplist-2.0.la 29libplist___2_0_la_LIBADD = libplist-2.0.la
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..08441c0
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,695 @@
1/*
2 * jplist.c
3 * JSON plist implementation
4 *
5 * Copyright (c) 2019-2021 Nikias Bassen All Rights Reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#include <string.h>
27#include <stdlib.h>
28#include <stdio.h>
29#include <time.h>
30
31#include <inttypes.h>
32#include <ctype.h>
33#include <math.h>
34#include <limits.h>
35
36#include <node.h>
37#include <node_list.h>
38
39#include "plist.h"
40#include "strbuf.h"
41#include "jsmn.h"
42
43#ifdef DEBUG
44static int plist_json_debug = 0;
45#define PLIST_JSON_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonparser] ERROR: " __VA_ARGS__); }
46#define PLIST_JSON_WRITE_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonwriter] ERROR: " __VA_ARGS__); }
47#else
48#define PLIST_JSON_ERR(...)
49#define PLIST_JSON_WRITE_ERR(...)
50#endif
51
52void plist_json_init(void)
53{
54 /* init JSON stuff */
55#ifdef DEBUG
56 char *env_debug = getenv("PLIST_JSON_DEBUG");
57 if (env_debug && !strcmp(env_debug, "1")) {
58 plist_json_debug = 1;
59 }
60#endif
61}
62
63void plist_json_deinit(void)
64{
65 /* deinit JSON stuff */
66}
67
68static size_t dtostr(char *buf, size_t bufsize, double realval)
69{
70 size_t len = 0;
71 if (isnan(realval)) {
72 len = snprintf(buf, bufsize, "nan");
73 } else if (isinf(realval)) {
74 len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
75 } else if (realval == 0.0f) {
76 len = snprintf(buf, bufsize, "0.0");
77 } else {
78 size_t i = 0;
79 len = snprintf(buf, bufsize, "%.*g", 17, realval);
80 for (i = 0; buf && i < len; i++) {
81 if (buf[i] == ',') {
82 buf[i] = '.';
83 break;
84 } else if (buf[i] == '.') {
85 break;
86 }
87 }
88 }
89 return len;
90}
91
92static int node_to_json(node_t* node, bytearray_t **outbuf, uint32_t depth, int prettify)
93{
94 plist_data_t node_data = NULL;
95
96 char *val = NULL;
97 size_t val_len = 0;
98
99 uint32_t i = 0;
100
101 if (!node)
102 return PLIST_ERR_INVALID_ARG;
103
104 node_data = plist_get_data(node);
105
106 switch (node_data->type)
107 {
108 case PLIST_BOOLEAN:
109 {
110 if (node_data->boolval) {
111 str_buf_append(*outbuf, "true", 4);
112 } else {
113 str_buf_append(*outbuf, "false", 5);
114 }
115 }
116 break;
117
118 case PLIST_NULL:
119 str_buf_append(*outbuf, "null", 4);
120 break;
121
122 case PLIST_UINT:
123 val = (char*)malloc(64);
124 if (node_data->length == 16) {
125 val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
126 } else {
127 val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
128 }
129 str_buf_append(*outbuf, val, val_len);
130 free(val);
131 break;
132
133 case PLIST_REAL:
134 val = (char*)malloc(64);
135 val_len = dtostr(val, 64, node_data->realval);
136 str_buf_append(*outbuf, val, val_len);
137 free(val);
138 break;
139
140 case PLIST_STRING:
141 case PLIST_KEY: {
142 size_t j = 0;
143 size_t len = 0;
144 off_t start = 0;
145 off_t cur = 0;
146
147 str_buf_append(*outbuf, "\"", 1);
148
149 len = node_data->length;
150 for (j = 0; j < len; j++) {
151 switch (node_data->strval[j]) {
152 case '"':
153 str_buf_append(*outbuf, node_data->strval + start, cur - start);
154 str_buf_append(*outbuf, "\\\"", 2);
155 start = cur+1;
156 break;
157 default:
158 break;
159 }
160 cur++;
161 }
162 str_buf_append(*outbuf, node_data->strval + start, cur - start);
163
164 str_buf_append(*outbuf, "\"", 1);
165 } break;
166
167 case PLIST_ARRAY: {
168 str_buf_append(*outbuf, "[", 1);
169 node_t *ch;
170 uint32_t cnt = 0;
171 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
172 if (cnt > 0) {
173 str_buf_append(*outbuf, ",", 1);
174 }
175 if (prettify) {
176 str_buf_append(*outbuf, "\n", 1);
177 for (i = 0; i <= depth; i++) {
178 str_buf_append(*outbuf, " ", 2);
179 }
180 }
181 int res = node_to_json(ch, outbuf, depth+1, prettify);
182 if (res < 0) {
183 return res;
184 }
185 cnt++;
186 }
187 if (cnt > 0 && prettify) {
188 str_buf_append(*outbuf, "\n", 1);
189 for (i = 0; i < depth; i++) {
190 str_buf_append(*outbuf, " ", 2);
191 }
192 }
193 str_buf_append(*outbuf, "]", 1);
194 } break;
195 case PLIST_DICT: {
196 str_buf_append(*outbuf, "{", 1);
197 node_t *ch;
198 uint32_t cnt = 0;
199 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
200 if (cnt > 0 && cnt % 2 == 0) {
201 str_buf_append(*outbuf, ",", 1);
202 }
203 if (cnt % 2 == 0 && prettify) {
204 str_buf_append(*outbuf, "\n", 1);
205 for (i = 0; i <= depth; i++) {
206 str_buf_append(*outbuf, " ", 2);
207 }
208 }
209 int res = node_to_json(ch, outbuf, depth+1, prettify);
210 if (res < 0) {
211 return res;
212 }
213 if (cnt % 2 == 0) {
214 str_buf_append(*outbuf, ":", 1);
215 if (prettify) {
216 str_buf_append(*outbuf, " ", 1);
217 }
218 }
219 cnt++;
220 }
221 if (cnt > 0 && prettify) {
222 str_buf_append(*outbuf, "\n", 1);
223 for (i = 0; i < depth; i++) {
224 str_buf_append(*outbuf, " ", 2);
225 }
226 }
227 str_buf_append(*outbuf, "}", 1);
228 } break;
229 case PLIST_DATA:
230 // NOT VALID FOR JSON
231 PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
232 return PLIST_ERR_FORMAT;
233 case PLIST_DATE:
234 // NOT VALID FOR JSON
235 PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
236 return PLIST_ERR_FORMAT;
237 case PLIST_UID:
238 // NOT VALID FOR JSON
239 PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
240 return PLIST_ERR_FORMAT;
241 default:
242 return PLIST_ERR_UNKNOWN;
243 }
244
245 return PLIST_ERR_SUCCESS;
246}
247
248#define PO10i_LIMIT (INT64_MAX/10)
249
250/* based on https://stackoverflow.com/a/4143288 */
251static int num_digits_i(int64_t i)
252{
253 int n;
254 int64_t po10;
255 n=1;
256 if (i < 0) {
257 i = -i;
258 n++;
259 }
260 po10=10;
261 while (i>=po10) {
262 n++;
263 if (po10 > PO10i_LIMIT) break;
264 po10*=10;
265 }
266 return n;
267}
268
269#define PO10u_LIMIT (UINT64_MAX/10)
270
271/* based on https://stackoverflow.com/a/4143288 */
272static int num_digits_u(uint64_t i)
273{
274 int n;
275 uint64_t po10;
276 n=1;
277 po10=10;
278 while (i>=po10) {
279 n++;
280 if (po10 > PO10u_LIMIT) break;
281 po10*=10;
282 }
283 return n;
284}
285
286static int node_estimate_size(node_t *node, uint64_t *size, uint32_t depth, int prettify)
287{
288 plist_data_t data;
289 if (!node) {
290 return PLIST_ERR_INVALID_ARG;
291 }
292 data = plist_get_data(node);
293 if (node->children) {
294 node_t *ch;
295 unsigned int n_children = node_n_children(node);
296 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
297 int res = node_estimate_size(ch, size, depth + 1, prettify);
298 if (res < 0) {
299 return res;
300 }
301 }
302 switch (data->type) {
303 case PLIST_DICT:
304 *size += 2; // '{' and '}'
305 *size += n_children-1; // number of ':' and ','
306 if (prettify) {
307 *size += n_children; // number of '\n' and extra space
308 *size += n_children * (depth+1); // indent for every 2nd child
309 *size += 1; // additional '\n'
310 }
311 break;
312 case PLIST_ARRAY:
313 *size += 2; // '[' and ']'
314 *size += n_children-1; // number of ','
315 if (prettify) {
316 *size += n_children; // number of '\n'
317 *size += n_children * ((depth+1)<<1); // indent for every child
318 *size += 1; // additional '\n'
319 }
320 break;
321 default:
322 break;
323 }
324 if (prettify)
325 *size += (depth << 1); // indent for {} and []
326 } else {
327 switch (data->type) {
328 case PLIST_STRING:
329 case PLIST_KEY:
330 *size += data->length;
331 *size += 2;
332 break;
333 case PLIST_UINT:
334 if (data->length == 16) {
335 *size += num_digits_u(data->intval);
336 } else {
337 *size += num_digits_i((int64_t)data->intval);
338 }
339 break;
340 case PLIST_REAL:
341 *size += dtostr(NULL, 0, data->realval);
342 break;
343 case PLIST_BOOLEAN:
344 *size += ((data->boolval) ? 4 : 5);
345 break;
346 case PLIST_DICT:
347 case PLIST_ARRAY:
348 *size += 2;
349 break;
350 case PLIST_DATA:
351 // NOT VALID FOR JSON
352 PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
353 return PLIST_ERR_FORMAT;
354 case PLIST_DATE:
355 // NOT VALID FOR JSON
356 PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
357 return PLIST_ERR_FORMAT;
358 case PLIST_UID:
359 // NOT VALID FOR JSON
360 PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
361 return PLIST_ERR_FORMAT;
362 default:
363 PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
364 return PLIST_ERR_UNKNOWN;
365 }
366 }
367 return PLIST_ERR_SUCCESS;
368}
369
370PLIST_API int plist_to_json(plist_t plist, char **json, uint32_t* length, int prettify)
371{
372 uint64_t size = 0;
373 int res;
374
375 if (!plist || !json || !length) {
376 return PLIST_ERR_INVALID_ARG;
377 }
378
379 res = node_estimate_size(plist, &size, 0, prettify);
380 if (res < 0) {
381 return res;
382 }
383
384 strbuf_t *outbuf = str_buf_new(size);
385 if (!outbuf) {
386 PLIST_JSON_WRITE_ERR("Could not allocate output buffer");
387 return PLIST_ERR_NO_MEM;
388 }
389
390 res = node_to_json(plist, &outbuf, 0, prettify);
391 if (res < 0) {
392 str_buf_free(outbuf);
393 *json = NULL;
394 *length = 0;
395 return res;
396 }
397
398 str_buf_append(outbuf, "\0", 1);
399
400 *json = outbuf->data;
401 *length = outbuf->len - 1;
402
403 outbuf->data = NULL;
404 str_buf_free(outbuf);
405
406 return PLIST_ERR_SUCCESS;
407}
408
409static plist_t parse_primitive(const char* js, jsmntok_t* tokens, int* index)
410{
411 if (tokens[*index].type != JSMN_PRIMITIVE) {
412 PLIST_JSON_ERR("%s: token type != JSMN_PRIMITIVE\n", __func__);
413 return NULL;
414 }
415 plist_t val = NULL;
416 const char* str_val = js + tokens[*index].start;
417 const char* str_end = js + tokens[*index].end;
418 size_t str_len = tokens[*index].end - tokens[*index].start;
419 if (!strncmp("false", str_val, str_len)) {
420 val = plist_new_bool(0);
421 } else if (!strncmp("true", str_val, str_len)) {
422 val = plist_new_bool(1);
423 } else if (!strncmp("null", str_val, str_len)) {
424 plist_data_t data = plist_new_plist_data();
425 data->type = PLIST_NULL;
426 val = plist_new_node(data);
427 } else if (str_val[0] == '-' || isdigit(str_val[0])) {
428 char* endp = NULL;
429 long long intpart = strtol(str_val, &endp, 10);
430 if (endp >= str_end) {
431 /* integer */
432 val = plist_new_uint((uint64_t)intpart);
433 } else if (*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) {
434 /* float */
435 char* fendp = endp+1;
436 while (isdigit(*fendp) && fendp < str_end) fendp++;
437 if ((fendp > endp+1 && fendp >= str_end) || (fendp+2 < str_end && (*fendp == 'e' || *fendp == 'E') && (*(fendp+1) == '+' || *(fendp+1) == '-') && isdigit(*(fendp+2)))) {
438 double dval = atof(str_val);
439 val = plist_new_real(dval);
440 } else {
441 PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
442 }
443 } else {
444 PLIST_JSON_ERR("%s: invalid character at offset %d when parsing numerical value\n", __func__, (int)(endp - js));
445 }
446 } else {
447 PLIST_JSON_ERR("%s: invalid primitive value '%.*s' encountered\n", __func__, (int)str_len, str_val);
448 }
449 (*index)++;
450 return val;
451}
452
453static plist_t parse_string(const char* js, jsmntok_t* tokens, int* index)
454{
455 if (tokens[*index].type != JSMN_STRING) {
456 PLIST_JSON_ERR("%s: token type != JSMN_STRING\n", __func__);
457 return NULL;
458 }
459
460 const char* str_val = js + tokens[*index].start;
461 size_t str_len = tokens[*index].end - tokens[*index].start;
462 char* strval = strndup(str_val, str_len);
463 plist_t node;
464
465 /* unescape */
466 size_t i = 0;
467 while (i < str_len) {
468 if (strval[i] == '\\' && i < str_len-1) {
469 switch (strval[i+1]) {
470 case '\"': case '/' : case '\\' : case 'b' :
471 case 'f' : case 'r' : case 'n' : case 't' :
472 memmove(strval+i, strval+i+1, str_len - (i+1));
473 str_len--;
474 switch (strval[i]) {
475 case 'b':
476 strval[i] = '\b';
477 break;
478 case 'f':
479 strval[i] = '\f';
480 break;
481 case 'r':
482 strval[i] = '\r';
483 break;
484 case 'n':
485 strval[i] = '\n';
486 break;
487 case 't':
488 strval[i] = '\t';
489 break;
490 default:
491 break;
492 }
493 break;
494 case 'u': {
495 unsigned int val = 0;
496 if (str_len-(i+2) < 4) {
497 free(strval);
498 PLIST_JSON_ERR("%s: invalid escape sequence '%s' (too short)\n", __func__, strval+i);
499 return NULL;
500 }
501 if (!(isxdigit(strval[i+2]) && isxdigit(strval[i+3]) && isxdigit(strval[i+4]) && isxdigit(strval[i+5])) || sscanf(strval+i+2, "%04x", &val) != 1) {
502 free(strval);
503 PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 6, strval+i);
504 return NULL;
505 }
506 int bytelen = 0;
507 if (val >= 0x800) {
508 /* three bytes */
509 strval[i] = (char)(0xE0 + ((val >> 12) & 0xF));
510 strval[i+1] = (char)(0x80 + ((val >> 6) & 0x3F));
511 strval[i+2] = (char)(0x80 + (val & 0x3F));
512 bytelen = 3;
513 } else if (val >= 0x80) {
514 /* two bytes */
515 strval[i] = (char)(0xC0 + ((val >> 6) & 0x1F));
516 strval[i+1] = (char)(0x80 + (val & 0x3F));
517 bytelen = 2;
518 } else {
519 /* one byte */
520 strval[i] = (char)(val & 0x7F);
521 bytelen = 1;
522 }
523 memmove(strval+i+bytelen, strval+i+6, str_len - (i+5));
524 str_len -= (6-bytelen);
525 } break;
526 default:
527 PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 2, strval+i);
528 free(strval);
529 return NULL;
530 }
531 }
532 i++;
533 }
534
535 plist_data_t data = plist_new_plist_data();
536 data->type = PLIST_STRING;
537 data->strval = strval;
538 data->length = str_len;
539 node = plist_new_node(data);
540
541 (*index)++;
542 return node;
543}
544
545static plist_t parse_object(const char* js, jsmntok_t* tokens, int* index);
546
547static plist_t parse_array(const char* js, jsmntok_t* tokens, int* index)
548{
549 if (tokens[*index].type != JSMN_ARRAY) {
550 PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
551 return NULL;
552 }
553 plist_t arr = plist_new_array();
554 int num_tokens = tokens[*index].size;
555 int num;
556 int j = (*index)+1;
557 for (num = 0; num < num_tokens; num++) {
558 plist_t val = NULL;
559 switch (tokens[j].type) {
560 case JSMN_OBJECT:
561 val = parse_object(js, tokens, &j);
562 break;
563 case JSMN_ARRAY:
564 val = parse_array(js, tokens, &j);
565 break;
566 case JSMN_STRING:
567 val = parse_string(js, tokens, &j);
568 break;
569 case JSMN_PRIMITIVE:
570 val = parse_primitive(js, tokens, &j);
571 break;
572 default:
573 break;
574 }
575 if (val) {
576 plist_array_append_item(arr, val);
577 }
578 }
579 *(index) = j;
580 return arr;
581}
582
583static plist_t parse_object(const char* js, jsmntok_t* tokens, int* index)
584{
585 if (tokens[*index].type != JSMN_OBJECT) {
586 PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
587 return NULL;
588 }
589 plist_t obj = plist_new_dict();
590 int num_tokens = tokens[*index].size;
591 int num;
592 int j = (*index)+1;
593 for (num = 0; num < num_tokens; num++) {
594 if (tokens[j].type == JSMN_STRING) {
595 char* key = strndup(js + tokens[j].start, tokens[j].end - tokens[j].start);
596 plist_t val = NULL;
597 j++;
598 num++;
599 switch (tokens[j].type) {
600 case JSMN_OBJECT:
601 val = parse_object(js, tokens, &j);
602 break;
603 case JSMN_ARRAY:
604 val = parse_array(js, tokens, &j);
605 break;
606 case JSMN_STRING:
607 val = parse_string(js, tokens, &j);
608 break;
609 case JSMN_PRIMITIVE:
610 val = parse_primitive(js, tokens, &j);
611 break;
612 default:
613 break;
614 }
615 if (val) {
616 plist_dict_set_item(obj, key, val);
617 }
618 free(key);
619 } else {
620 PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
621 return NULL;
622 }
623 }
624 (*index) = j;
625 return obj;
626}
627
628PLIST_API int plist_from_json(const char *json, uint32_t length, plist_t * plist)
629{
630 if (!plist) {
631 return PLIST_ERR_INVALID_ARG;
632 }
633 *plist = NULL;
634 if (!json || (length == 0)) {
635 return PLIST_ERR_INVALID_ARG;
636 }
637
638 jsmn_parser parser;
639 jsmn_init(&parser);
640 int maxtoks = 256;
641 int r = 0;
642 jsmntok_t *tokens = NULL;
643
644 do {
645 jsmntok_t* newtokens = realloc(tokens, sizeof(jsmntok_t)*maxtoks);
646 if (!newtokens) {
647 PLIST_JSON_ERR("%s: Out of memory\n", __func__);
648 return PLIST_ERR_NO_MEM;
649 }
650 tokens = newtokens;
651
652 r = jsmn_parse(&parser, json, tokens, maxtoks);
653 if (r == JSMN_ERROR_NOMEM) {
654 maxtoks+=16;
655 continue;
656 }
657 } while (0);
658
659 switch(r) {
660 case JSMN_ERROR_NOMEM:
661 PLIST_JSON_ERR("%s: Out of memory...\n", __func__);
662 free(tokens);
663 return PLIST_ERR_NO_MEM;
664 case JSMN_ERROR_INVAL:
665 PLIST_JSON_ERR("%s: Invalid character inside JSON string\n", __func__);
666 free(tokens);
667 return PLIST_ERR_PARSE;
668 case JSMN_ERROR_PART:
669 PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__);
670 free(tokens);
671 return PLIST_ERR_PARSE;
672 default:
673 break;
674 }
675
676 int startindex = 0;
677 switch (tokens[startindex].type) {
678 case JSMN_PRIMITIVE:
679 *plist = parse_primitive(json, tokens, &startindex);
680 break;
681 case JSMN_STRING:
682 *plist = parse_string(json, tokens, &startindex);
683 break;
684 case JSMN_ARRAY:
685 *plist = parse_array(json, tokens, &startindex);
686 break;
687 case JSMN_OBJECT:
688 *plist = parse_object(json, tokens, &startindex);
689 break;
690 default:
691 break;
692 }
693 free(tokens);
694 return PLIST_ERR_SUCCESS;
695}
diff --git a/src/jsmn.c b/src/jsmn.c
new file mode 100644
index 0000000..ff7c818
--- /dev/null
+++ b/src/jsmn.c
@@ -0,0 +1,280 @@
1/*
2 * jsmn.c
3 * Simple JSON parser
4 *
5 * Copyright (c) 2010 Serge A. Zaitsev
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25
26#include <stdlib.h>
27
28#include "jsmn.h"
29
30/**
31 * Allocates a fresh unused token from the token pull.
32 */
33static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
34 jsmntok_t *tokens, int num_tokens) {
35 jsmntok_t *tok;
36 if (parser->toknext >= num_tokens) {
37 return NULL;
38 }
39 tok = &tokens[parser->toknext++];
40 tok->start = tok->end = -1;
41 tok->size = 0;
42#ifdef JSMN_PARENT_LINKS
43 tok->parent = -1;
44#endif
45 return tok;
46}
47
48/**
49 * Fills token type and boundaries.
50 */
51static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
52 int start, int end) {
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 jsmntok_t *tokens, int num_tokens) {
64 jsmntok_t *token;
65 int start;
66
67 start = parser->pos;
68
69 for (; js[parser->pos] != '\0'; parser->pos++) {
70 switch (js[parser->pos]) {
71#ifndef JSMN_STRICT
72 /* In strict mode primitive must be followed by "," or "}" or "]" */
73 case ':':
74#endif
75 case '\t' : case '\r' : case '\n' : case ' ' :
76 case ',' : case ']' : case '}' :
77 goto found;
78 }
79 if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
80 parser->pos = start;
81 return JSMN_ERROR_INVAL;
82 }
83 }
84#ifdef JSMN_STRICT
85 /* In strict mode primitive must be followed by a comma/object/array */
86 parser->pos = start;
87 return JSMN_ERROR_PART;
88#endif
89
90found:
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 JSMN_SUCCESS;
102}
103
104/**
105 * Filsl next token with JSON string.
106 */
107static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
108 jsmntok_t *tokens, int num_tokens) {
109 jsmntok_t *token;
110
111 int start = parser->pos;
112
113 parser->pos++;
114
115 /* Skip starting quote */
116 for (; js[parser->pos] != '\0'; parser->pos++) {
117 char c = js[parser->pos];
118
119 /* Quote: end of string */
120 if (c == '\"') {
121 token = jsmn_alloc_token(parser, tokens, num_tokens);
122 if (token == NULL) {
123 parser->pos = start;
124 return JSMN_ERROR_NOMEM;
125 }
126 jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
127#ifdef JSMN_PARENT_LINKS
128 token->parent = parser->toksuper;
129#endif
130 return JSMN_SUCCESS;
131 }
132
133 /* Backslash: Quoted symbol expected */
134 if (c == '\\') {
135 parser->pos++;
136 switch (js[parser->pos]) {
137 /* Allowed escaped symbols */
138 case '\"': case '/' : case '\\' : case 'b' :
139 case 'f' : case 'r' : case 'n' : case 't' :
140 break;
141 /* Allows escaped symbol \uXXXX */
142 case 'u':
143 /* TODO */
144 break;
145 /* Unexpected symbol */
146 default:
147 parser->pos = start;
148 return JSMN_ERROR_INVAL;
149 }
150 }
151 }
152 parser->pos = start;
153 return JSMN_ERROR_PART;
154}
155
156/**
157 * Parse JSON string and fill tokens.
158 */
159jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
160 unsigned int num_tokens) {
161 jsmnerr_t r;
162 int i;
163 jsmntok_t *token;
164
165 for (; js[parser->pos] != '\0'; parser->pos++) {
166 char c;
167 jsmntype_t type;
168
169 c = js[parser->pos];
170 switch (c) {
171 case '{': case '[':
172 token = jsmn_alloc_token(parser, tokens, num_tokens);
173 if (token == NULL)
174 return JSMN_ERROR_NOMEM;
175 if (parser->toksuper != -1) {
176 tokens[parser->toksuper].size++;
177#ifdef JSMN_PARENT_LINKS
178 token->parent = parser->toksuper;
179#endif
180 }
181 token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
182 token->start = parser->pos;
183 parser->toksuper = parser->toknext - 1;
184 break;
185 case '}': case ']':
186 type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
187#ifdef JSMN_PARENT_LINKS
188 if (parser->toknext < 1) {
189 return JSMN_ERROR_INVAL;
190 }
191 token = &tokens[parser->toknext - 1];
192 for (;;) {
193 if (token->start != -1 && token->end == -1) {
194 if (token->type != type) {
195 return JSMN_ERROR_INVAL;
196 }
197 token->end = parser->pos + 1;
198 parser->toksuper = token->parent;
199 break;
200 }
201 if (token->parent == -1) {
202 break;
203 }
204 token = &tokens[token->parent];
205 }
206#else
207 for (i = parser->toknext - 1; i >= 0; i--) {
208 token = &tokens[i];
209 if (token->start != -1 && token->end == -1) {
210 if (token->type != type) {
211 return JSMN_ERROR_INVAL;
212 }
213 parser->toksuper = -1;
214 token->end = parser->pos + 1;
215 break;
216 }
217 }
218 /* Error if unmatched closing bracket */
219 if (i == -1) return JSMN_ERROR_INVAL;
220 for (; i >= 0; i--) {
221 token = &tokens[i];
222 if (token->start != -1 && token->end == -1) {
223 parser->toksuper = i;
224 break;
225 }
226 }
227#endif
228 break;
229 case '\"':
230 r = jsmn_parse_string(parser, js, tokens, num_tokens);
231 if (r < 0) return r;
232 if (parser->toksuper != -1)
233 tokens[parser->toksuper].size++;
234 break;
235 case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
236 break;
237#ifdef JSMN_STRICT
238 /* In strict mode primitives are: numbers and booleans */
239 case '-': case '0': case '1' : case '2': case '3' : case '4':
240 case '5': case '6': case '7' : case '8': case '9':
241 case 't': case 'f': case 'n' :
242#else
243 /* In non-strict mode every unquoted value is a primitive */
244 default:
245#endif
246 r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
247 if (r < 0) return r;
248 if (parser->toksuper != -1)
249 tokens[parser->toksuper].size++;
250 break;
251
252#ifdef JSMN_STRICT
253 /* Unexpected char in strict mode */
254 default:
255 return JSMN_ERROR_INVAL;
256#endif
257
258 }
259 }
260
261 for (i = parser->toknext - 1; i >= 0; i--) {
262 /* Unmatched opened object or array */
263 if (tokens[i].start != -1 && tokens[i].end == -1) {
264 return JSMN_ERROR_PART;
265 }
266 }
267
268 return JSMN_SUCCESS;
269}
270
271/**
272 * Creates a new parser based over a given buffer with an array of tokens
273 * available.
274 */
275void jsmn_init(jsmn_parser *parser) {
276 parser->pos = 0;
277 parser->toknext = 0;
278 parser->toksuper = -1;
279}
280
diff --git a/src/jsmn.h b/src/jsmn.h
new file mode 100644
index 0000000..f12dc5a
--- /dev/null
+++ b/src/jsmn.h
@@ -0,0 +1,91 @@
1/*
2 * jsmn.h
3 * Simple JSON parser (header file)
4 *
5 * Copyright (c) 2010 Serge A. Zaitsev
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25#ifndef __JSMN_H_
26#define __JSMN_H_
27
28/**
29 * JSON type identifier. Basic types are:
30 * o Object
31 * o Array
32 * o String
33 * o Other primitive: number, boolean (true/false) or null
34 */
35typedef enum {
36 JSMN_PRIMITIVE = 0,
37 JSMN_OBJECT = 1,
38 JSMN_ARRAY = 2,
39 JSMN_STRING = 3
40} jsmntype_t;
41
42typedef enum {
43 /* Not enough tokens were provided */
44 JSMN_ERROR_NOMEM = -1,
45 /* Invalid character inside JSON string */
46 JSMN_ERROR_INVAL = -2,
47 /* The string is not a full JSON packet, more bytes expected */
48 JSMN_ERROR_PART = -3,
49 /* Everything was fine */
50 JSMN_SUCCESS = 0
51} jsmnerr_t;
52
53/**
54 * JSON token description.
55 * @param type type (object, array, string etc.)
56 * @param start start position in JSON data string
57 * @param end end position in JSON data string
58 */
59typedef struct {
60 jsmntype_t type;
61 int start;
62 int end;
63 int size;
64#ifdef JSMN_PARENT_LINKS
65 int parent;
66#endif
67} jsmntok_t;
68
69/**
70 * JSON parser. Contains an array of token blocks available. Also stores
71 * the string being parsed now and current position in that string
72 */
73typedef struct {
74 unsigned int pos; /* offset in the JSON string */
75 int toknext; /* next token to allocate */
76 int toksuper; /* superior token node, e.g parent object or array */
77} jsmn_parser;
78
79/**
80 * Create JSON parser over an array of tokens
81 */
82void jsmn_init(jsmn_parser *parser);
83
84/**
85 * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
86 * a single JSON object.
87 */
88jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js,
89 jsmntok_t *tokens, unsigned int num_tokens);
90
91#endif /* __JSMN_H_ */
diff --git a/src/plist.c b/src/plist.c
index 61b2913..9f3c8f4 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -49,17 +49,21 @@ extern void plist_xml_init(void);
49extern void plist_xml_deinit(void); 49extern void plist_xml_deinit(void);
50extern void plist_bin_init(void); 50extern void plist_bin_init(void);
51extern void plist_bin_deinit(void); 51extern void plist_bin_deinit(void);
52extern void plist_json_init(void);
53extern void plist_json_deinit(void);
52 54
53static void internal_plist_init(void) 55static void internal_plist_init(void)
54{ 56{
55 plist_bin_init(); 57 plist_bin_init();
56 plist_xml_init(); 58 plist_xml_init();
59 plist_json_init();
57} 60}
58 61
59static void internal_plist_deinit(void) 62static void internal_plist_deinit(void)
60{ 63{
61 plist_bin_deinit(); 64 plist_bin_deinit();
62 plist_xml_deinit(); 65 plist_xml_deinit();
66 plist_json_deinit();
63} 67}
64 68
65#ifdef WIN32 69#ifdef WIN32
@@ -195,6 +199,8 @@ PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length,
195 } 199 }
196 if (plist_is_binary(plist_data, length)) { 200 if (plist_is_binary(plist_data, length)) {
197 res = plist_from_bin(plist_data, length, plist); 201 res = plist_from_bin(plist_data, length, plist);
202 } else if (plist_data[0] == '[' || plist_data[0] == '{') {
203 res = plist_from_json(plist_data, length, plist);
198 } else { 204 } else {
199 res = plist_from_xml(plist_data, length, plist); 205 res = plist_from_xml(plist_data, length, plist);
200 } 206 }