summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2021-12-23 03:09:07 +0100
committerGravatar Nikias Bassen2021-12-23 03:09:07 +0100
commit429cbc660ae14d4998715803b44c71abf0e4a339 (patch)
tree12fe08f5dcb00a380536198bac3fffd4eb7dd19b
parent70002721443dabaa99b56301b537980e137b6249 (diff)
downloadlibplist-429cbc660ae14d4998715803b44c71abf0e4a339.tar.gz
libplist-429cbc660ae14d4998715803b44c71abf0e4a339.tar.bz2
Add support for JSON format
-rw-r--r--docs/plistutil.114
-rw-r--r--include/plist/plist.h43
-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
-rw-r--r--test/Makefile.am15
-rwxr-xr-xtest/amp.test8
-rw-r--r--test/data/data.bplistbin0 -> 3718 bytes
-rw-r--r--test/data/j1.plist1
-rwxr-xr-xtest/invalid_tag.test8
-rwxr-xr-xtest/json-invalid-types.test36
-rwxr-xr-xtest/json1.test19
-rwxr-xr-xtest/json2.test22
-rwxr-xr-xtest/malformed_dict.test8
-rw-r--r--test/plist_cmp.c4
-rw-r--r--test/plist_jtest.c131
-rwxr-xr-xtest/recursion.test8
-rw-r--r--tools/plistutil.c78
20 files changed, 1403 insertions, 66 deletions
diff --git a/docs/plistutil.1 b/docs/plistutil.1
index e502bd7..eb1b591 100644
--- a/docs/plistutil.1
+++ b/docs/plistutil.1
@@ -1,13 +1,13 @@
1.TH "plistutil" 1 1.TH "plistutil" 1
2.SH NAME 2.SH NAME
3plistutil \- Convert a plist FILE from binary to XML format or vice-versa 3plistutil \- Convert a plist FILE between binary, XML, and JSON format
4.SH SYNOPSIS 4.SH SYNOPSIS
5.B plistutil 5.B plistutil
6[OPTIONS] 6[OPTIONS]
7[-i FILE] 7[-i FILE]
8[-o FILE] 8[-o FILE]
9.SH DESCRIPTION 9.SH DESCRIPTION
10plistutil allows converting a file in Property List format from binary to XML format or vice-versa. 10plistutil allows converting a Property List file between binary, XML, and JSON format.
11.SH OPTIONS 11.SH OPTIONS
12.TP 12.TP
13.B \-i, \-\-infile FILE 13.B \-i, \-\-infile FILE
@@ -18,10 +18,13 @@ filename, plistutil will read from stdin.
18Output FILE to convert to. If this argument is omitted or - is passed as 18Output FILE to convert to. If this argument is omitted or - is passed as
19filename, plistutil will write to stdout. 19filename, plistutil will write to stdout.
20.TP 20.TP
21.B \-f, \-\-format [bin|xml] 21.B \-f, \-\-format [bin|xml|json]
22Force output format, regardless of input type. This is useful if the input 22Force output format, regardless of input type. This is useful if the input
23format is not known, but the output format should always be in a specific 23format is not known, but the output format should always be in a specific
24format (like xml). 24format (like xml or json).
25
26If omitted, XML plist data will be converted to binary and vice-versa. To
27convert to/from JSON the output format needs to specified.
25.TP 28.TP
26.B \-h, \-\-help 29.B \-h, \-\-help
27Prints usage information. 30Prints usage information.
@@ -47,6 +50,9 @@ Print test.plist as XML plist, regardless of the input format.
47.B plistutil -i test.plist -f xml -o - 50.B plistutil -i test.plist -f xml -o -
48Same as before. 51Same as before.
49.TP 52.TP
53.B plistutil -i test.plist -f json
54Print test.plist as JSON plist, regardless of the input format.
55.TP
50.B cat test.plist |plistutil -f xml 56.B cat test.plist |plistutil -f xml
51Take plist data from stdin - piped via cat - and write the output as XML 57Take plist data from stdin - piped via cat - and write the output as XML
52to stdout. 58to stdout.
diff --git a/include/plist/plist.h b/include/plist/plist.h
index 21fd8bd..ac15568 100644
--- a/include/plist/plist.h
+++ b/include/plist/plist.h
@@ -682,6 +682,19 @@ extern "C"
682 plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length); 682 plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
683 683
684 /** 684 /**
685 * Export the #plist_t structure to JSON format.
686 *
687 * @param plist the root node to export
688 * @param json a pointer to a char* buffer. This function allocates the memory,
689 * caller is responsible for freeing it.
690 * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
691 * @param prettify pretty print the output if != 0
692 * @return PLIST_ERR_SUCCESS on success or a #plist_error on failure
693 * @note Use plist_mem_free() to free the allocated memory.
694 */
695 plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify);
696
697 /**
685 * Import the #plist_t structure from XML format. 698 * Import the #plist_t structure from XML format.
686 * 699 *
687 * @param plist_xml a pointer to the xml buffer. 700 * @param plist_xml a pointer to the xml buffer.
@@ -702,9 +715,25 @@ extern "C"
702 plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist); 715 plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
703 716
704 /** 717 /**
718 * Import the #plist_t structure from JSON format.
719 *
720 * @param json a pointer to the JSON buffer.
721 * @param length length of the buffer to read.
722 * @param plist a pointer to the imported plist.
723 * @return PLIST_ERR_SUCCESS on success or a #plist_error on failure
724 */
725 plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist);
726
727 /**
705 * Import the #plist_t structure from memory data. 728 * Import the #plist_t structure from memory data.
706 * This method will look at the first bytes of plist_data 729 * This method will look at the first bytes of plist_data
707 * to determine if plist_data contains a binary or XML plist. 730 * to determine if plist_data contains a binary, JSON, or XML plist
731 * and tries to parse the data in the appropriate format.
732 * @note This is just a convenience function and the format detection is
733 * very basic. It checks with plist_is_binary() if the data supposedly
734 * contains binary plist data, if not it checks if the first byte is
735 * either '{' or '[' and assumes JSON format, otherwise it will try
736 * to parse the data as XML.
708 * 737 *
709 * @param plist_data a pointer to the memory buffer containing plist data. 738 * @param plist_data a pointer to the memory buffer containing plist data.
710 * @param length length of the buffer to read. 739 * @param length length of the buffer to read.
@@ -714,12 +743,12 @@ extern "C"
714 plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist); 743 plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist);
715 744
716 /** 745 /**
717 * Test if in-memory plist data is binary or XML 746 * Test if in-memory plist data is in binary format.
718 * This method will look at the first bytes of plist_data 747 * This function will look at the first bytes of plist_data to determine
719 * to determine if plist_data contains a binary or XML plist. 748 * if it supposedly contains a binary plist.
720 * This method is not validating the whole memory buffer to check if the 749 * @note The function is not validating the whole memory buffer to check
721 * content is truly a plist, it's only using some heuristic on the first few 750 * if the content is truly a plist, it is only using some heuristic on
722 * bytes of plist_data. 751 * the first few bytes of plist_data.
723 * 752 *
724 * @param plist_data a pointer to the memory buffer containing plist data. 753 * @param plist_data a pointer to the memory buffer containing plist data.
725 * @param length length of the buffer to read. 754 * @param length length of the buffer to read.
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 }
diff --git a/test/Makefile.am b/test/Makefile.am
index 3fca55f..b70a85d 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,7 +8,8 @@ AM_LDFLAGS =
8noinst_PROGRAMS = \ 8noinst_PROGRAMS = \
9 plist_cmp \ 9 plist_cmp \
10 plist_test \ 10 plist_test \
11 plist_btest 11 plist_btest \
12 plist_jtest
12 13
13plist_cmp_SOURCES = plist_cmp.c 14plist_cmp_SOURCES = plist_cmp.c
14plist_cmp_LDADD = \ 15plist_cmp_LDADD = \
@@ -21,6 +22,9 @@ plist_test_LDADD = $(top_builddir)/src/libplist-2.0.la
21plist_btest_SOURCES = plist_btest.c 22plist_btest_SOURCES = plist_btest.c
22plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la 23plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la
23 24
25plist_jtest_SOURCES = plist_jtest.c
26plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la
27
24TESTS = \ 28TESTS = \
25 empty.test \ 29 empty.test \
26 small.test \ 30 small.test \
@@ -45,7 +49,10 @@ TESTS = \
45 offsetsize.test \ 49 offsetsize.test \
46 refsize.test \ 50 refsize.test \
47 malformed_dict.test \ 51 malformed_dict.test \
48 uid.test 52 uid.test \
53 json1.test \
54 json2.test \
55 json-invalid-types.test
49 56
50EXTRA_DIST = \ 57EXTRA_DIST = \
51 $(TESTS) \ 58 $(TESTS) \
@@ -89,7 +96,9 @@ EXTRA_DIST = \
89 data/signedunsigned.plist \ 96 data/signedunsigned.plist \
90 data/unsigned.bplist \ 97 data/unsigned.bplist \
91 data/unsigned.plist \ 98 data/unsigned.plist \
92 data/uid.bplist 99 data/uid.bplist \
100 data/data.bplist \
101 data/j1.plist
93 102
94TESTS_ENVIRONMENT = \ 103TESTS_ENVIRONMENT = \
95 top_srcdir=$(top_srcdir) \ 104 top_srcdir=$(top_srcdir) \
diff --git a/test/amp.test b/test/amp.test
index 0815391..76b32ff 100755
--- a/test/amp.test
+++ b/test/amp.test
@@ -1,7 +1,5 @@
1## -*- sh -*- 1## -*- sh -*-
2 2
3set -e
4
5DATASRC=$top_srcdir/test/data 3DATASRC=$top_srcdir/test/data
6TESTFILE=amp.plist 4TESTFILE=amp.plist
7DATAIN0=$DATASRC/$TESTFILE 5DATAIN0=$DATASRC/$TESTFILE
@@ -9,6 +7,10 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out
9 7
10rm -rf $DATAOUT0 8rm -rf $DATAOUT0
11$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 9$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
12if test -f $DATAOUT0; then 10
11# test succeeds if plistutil fails
12if [ $? -eq 0 ]; then
13 exit 1 13 exit 1
14else
15 exit 0
14fi 16fi
diff --git a/test/data/data.bplist b/test/data/data.bplist
new file mode 100644
index 0000000..955993f
--- /dev/null
+++ b/test/data/data.bplist
Binary files differ
diff --git a/test/data/j1.plist b/test/data/j1.plist
new file mode 100644
index 0000000..2ae9acb
--- /dev/null
+++ b/test/data/j1.plist
@@ -0,0 +1 @@
{"test":[1,1],"foo":[[1],[1],[1],[1],[[1],[1],[1],[1],[[1],[1],[1],[1]]]],"more":{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":0.25}]}]}]}} \ No newline at end of file
diff --git a/test/invalid_tag.test b/test/invalid_tag.test
index 079bcdd..2c42a53 100755
--- a/test/invalid_tag.test
+++ b/test/invalid_tag.test
@@ -1,7 +1,5 @@
1## -*- sh -*- 1## -*- sh -*-
2 2
3set -e
4
5DATASRC=$top_srcdir/test/data 3DATASRC=$top_srcdir/test/data
6TESTFILE=invalid_tag.plist 4TESTFILE=invalid_tag.plist
7DATAIN0=$DATASRC/$TESTFILE 5DATAIN0=$DATASRC/$TESTFILE
@@ -9,6 +7,10 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out
9 7
10rm -rf $DATAOUT0 8rm -rf $DATAOUT0
11$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 9$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
12if test -f $DATAOUT0; then 10
11# test succeeds if plistutil fails
12if [ $? -eq 0 ]; then
13 exit 1 13 exit 1
14else
15 exit 0
14fi 16fi
diff --git a/test/json-invalid-types.test b/test/json-invalid-types.test
new file mode 100755
index 0000000..0397c05
--- /dev/null
+++ b/test/json-invalid-types.test
@@ -0,0 +1,36 @@
1## -*- sh -*-
2
3DATASRC=$top_srcdir/test/data
4DATAOUT=$top_builddir/test/data
5TESTFILE0=data.bplist
6TESTFILE1=7.plist
7TESTFILE2=uid.bplist
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_JSON_DEBUG=1
14
15echo "Converting (failure expected)"
16STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE0 -o /dev/null 2>&1`
17echo "$STDERR"
18if ! echo "$STDERR" |grep "PLIST_DATA type is not valid for JSON format"; then
19 exit 1
20fi
21
22echo "Converting (failure expected)"
23STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE1 -o /dev/null 2>&1`
24echo "$STDERR"
25if ! echo "$STDERR" |grep "PLIST_DATE type is not valid for JSON format"; then
26 exit 2
27fi
28
29echo "Converting (failure expected)"
30STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE2 -o /dev/null 2>&1`
31echo "$STDERR"
32if ! echo "$STDERR" |grep "PLIST_UID type is not valid for JSON format"; then
33 exit 3
34fi
35
36exit 0
diff --git a/test/json1.test b/test/json1.test
new file mode 100755
index 0000000..dba4912
--- /dev/null
+++ b/test/json1.test
@@ -0,0 +1,19 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=j1.plist
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_JSON_DEBUG=1
14
15echo "Converting"
16$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
17
18echo "Comparing"
19$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
diff --git a/test/json2.test b/test/json2.test
new file mode 100755
index 0000000..06a7007
--- /dev/null
+++ b/test/json2.test
@@ -0,0 +1,22 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=entities.plist
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_JSON_DEBUG=1
14
15echo "Converting input file to JSON"
16$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE -o $DATASRC/$TESTFILE.json
17
18echo "Converting to binary and back to JSON"
19$top_builddir/test/plist_jtest $DATASRC/$TESTFILE.json $DATAOUT/$TESTFILE.json.out
20
21echo "Comparing"
22$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.json.out
diff --git a/test/malformed_dict.test b/test/malformed_dict.test
index f45ae7e..cbad2bd 100755
--- a/test/malformed_dict.test
+++ b/test/malformed_dict.test
@@ -1,7 +1,5 @@
1## -*- sh -*- 1## -*- sh -*-
2 2
3set -e
4
5DATASRC=$top_srcdir/test/data 3DATASRC=$top_srcdir/test/data
6TESTFILE=malformed_dict.bplist 4TESTFILE=malformed_dict.bplist
7DATAIN0=$DATASRC/$TESTFILE 5DATAIN0=$DATASRC/$TESTFILE
@@ -9,3 +7,9 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out
9 7
10$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 8$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
11 9
10# test succeeds if plistutil fails
11if [ $? -eq 0 ]; then
12 exit 1
13else
14 exit 0
15fi
diff --git a/test/plist_cmp.c b/test/plist_cmp.c
index c854446..85b92ee 100644
--- a/test/plist_cmp.c
+++ b/test/plist_cmp.c
@@ -126,11 +126,15 @@ int main(int argc, char *argv[])
126 126
127 if (memcmp(plist_1, "bplist00", 8) == 0) 127 if (memcmp(plist_1, "bplist00", 8) == 0)
128 plist_from_bin(plist_1, size_in1, &root_node1); 128 plist_from_bin(plist_1, size_in1, &root_node1);
129 else if (plist_1[0] == '[' || plist_1[0] == '{')
130 plist_from_json(plist_1, size_in1, &root_node1);
129 else 131 else
130 plist_from_xml(plist_1, size_in1, &root_node1); 132 plist_from_xml(plist_1, size_in1, &root_node1);
131 133
132 if (memcmp(plist_2, "bplist00", 8) == 0) 134 if (memcmp(plist_2, "bplist00", 8) == 0)
133 plist_from_bin(plist_2, size_in2, &root_node2); 135 plist_from_bin(plist_2, size_in2, &root_node2);
136 else if (plist_2[0] == '[' || plist_2[0] == '{')
137 plist_from_json(plist_2, size_in2, &root_node2);
134 else 138 else
135 plist_from_xml(plist_2, size_in2, &root_node2); 139 plist_from_xml(plist_2, size_in2, &root_node2);
136 140
diff --git a/test/plist_jtest.c b/test/plist_jtest.c
new file mode 100644
index 0000000..130e3c7
--- /dev/null
+++ b/test/plist_jtest.c
@@ -0,0 +1,131 @@
1/*
2 * backup_test.c
3 * source libplist regression test
4 *
5 * Copyright (c) 2009 Jonathan Beck 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
23#include "plist/plist.h"
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <sys/stat.h>
29
30#ifdef _MSC_VER
31#pragma warning(disable:4996)
32#endif
33
34
35int main(int argc, char *argv[])
36{
37 FILE *iplist = NULL;
38 plist_t root_node1 = NULL;
39 plist_t root_node2 = NULL;
40 char *plist_json = NULL;
41 char *plist_json2 = NULL;
42 char *plist_bin = NULL;
43 int size_in = 0;
44 uint32_t size_out = 0;
45 uint32_t size_out2 = 0;
46 char *file_in = NULL;
47 char *file_out = NULL;
48 struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
49 if (argc != 3)
50 {
51 printf("Wrong input\n");
52 return 1;
53 }
54
55 file_in = argv[1];
56 file_out = argv[2];
57 //read input file
58 iplist = fopen(file_in, "rb");
59
60 if (!iplist)
61 {
62 printf("File does not exists\n");
63 return 2;
64 }
65 printf("File %s is open\n", file_in);
66 stat(file_in, filestats);
67 size_in = filestats->st_size;
68 plist_json = (char *) malloc(sizeof(char) * (size_in + 1));
69 fread(plist_json, sizeof(char), size_in, iplist);
70 fclose(iplist);
71 plist_json[size_in] = 0;
72
73 //convert one format to another
74 plist_from_json(plist_json, size_in, &root_node1);
75 if (!root_node1)
76 {
77 printf("PList JSON parsing failed\n");
78 return 3;
79 }
80
81 printf("PList JSON parsing succeeded\n");
82 plist_to_bin(root_node1, &plist_bin, &size_out);
83 if (!plist_bin)
84 {
85 printf("PList BIN writing failed\n");
86 return 4;
87 }
88
89 printf("PList BIN writing succeeded\n");
90 plist_from_bin(plist_bin, size_out, &root_node2);
91 if (!root_node2)
92 {
93 printf("PList BIN parsing failed\n");
94 return 5;
95 }
96
97 printf("PList BIN parsing succeeded\n");
98 plist_to_json(root_node2, &plist_json2, &size_out2, 0);
99 if (!plist_json2)
100 {
101 printf("PList JSON writing failed\n");
102 return 8;
103 }
104
105 printf("PList JSON writing succeeded\n");
106 if (plist_json2)
107 {
108 FILE *oplist = NULL;
109 oplist = fopen(file_out, "wb");
110 fwrite(plist_json2, size_out2, sizeof(char), oplist);
111 fclose(oplist);
112 }
113
114 plist_free(root_node1);
115 plist_free(root_node2);
116 free(plist_bin);
117 free(plist_json);
118 free(plist_json2);
119 free(filestats);
120
121 if ((uint32_t)size_in != size_out2)
122 {
123 printf("Size of input and output is different\n");
124 printf("Input size : %i\n", size_in);
125 printf("Output size : %i\n", size_out2);
126 }
127
128 //success
129 return 0;
130}
131
diff --git a/test/recursion.test b/test/recursion.test
index 0120a12..9946d81 100755
--- a/test/recursion.test
+++ b/test/recursion.test
@@ -1,7 +1,5 @@
1## -*- sh -*- 1## -*- sh -*-
2 2
3set -e
4
5DATASRC=$top_srcdir/test/data 3DATASRC=$top_srcdir/test/data
6TESTFILE=recursion.bplist 4TESTFILE=recursion.bplist
7DATAIN0=$DATASRC/$TESTFILE 5DATAIN0=$DATASRC/$TESTFILE
@@ -9,3 +7,9 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out
9 7
10$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 8$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
11 9
10# test succeeds if plistutil fails
11if [ $? -eq 0 ]; then
12 exit 1
13else
14 exit 0
15fi
diff --git a/tools/plistutil.c b/tools/plistutil.c
index 71972f9..bd83e92 100644
--- a/tools/plistutil.c
+++ b/tools/plistutil.c
@@ -41,7 +41,7 @@
41typedef struct _options 41typedef struct _options
42{ 42{
43 char *in_file, *out_file; 43 char *in_file, *out_file;
44 uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json someday 44 uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json
45} options_t; 45} options_t;
46 46
47static void print_usage(int argc, char *argv[]) 47static void print_usage(int argc, char *argv[])
@@ -50,14 +50,19 @@ static void print_usage(int argc, char *argv[])
50 name = strrchr(argv[0], '/'); 50 name = strrchr(argv[0], '/');
51 printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0])); 51 printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0]));
52 printf("\n"); 52 printf("\n");
53 printf("Convert a plist FILE from binary to XML format or vice-versa.\n"); 53 printf("Convert a plist FILE between binary, XML, and JSON format.\n");
54 printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n");
55 printf("To convert to/from JSON the output format needs to be specified.\n");
54 printf("\n"); 56 printf("\n");
55 printf("OPTIONS:\n"); 57 printf("OPTIONS:\n");
56 printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n"); 58 printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n");
57 printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n"); 59 printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n");
58 printf(" -f, --format [bin|xml] Force output format, regardless of input type\n"); 60 printf(" -f, --format FORMAT Force output format, regardless of input type\n");
59 printf(" -d, --debug Enable extended debug output\n"); 61 printf(" FORMAT is one of xml, bin, or json\n");
60 printf(" -v, --version Print version information\n"); 62 printf(" If omitted XML will be converted to binary,\n");
63 printf(" and binary to XML.\n");
64 printf(" -d, --debug Enable extended debug output\n");
65 printf(" -v, --version Print version information\n");
61 printf("\n"); 66 printf("\n");
62 printf("Homepage: <" PACKAGE_URL ">\n"); 67 printf("Homepage: <" PACKAGE_URL ">\n");
63 printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n"); 68 printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n");
@@ -105,8 +110,10 @@ static options_t *parse_arguments(int argc, char *argv[])
105 options->out_fmt = 1; 110 options->out_fmt = 1;
106 } else if (!strncmp(argv[i+1], "xml", 3)) { 111 } else if (!strncmp(argv[i+1], "xml", 3)) {
107 options->out_fmt = 2; 112 options->out_fmt = 2;
113 } else if (!strncmp(argv[i+1], "json", 4)) {
114 options->out_fmt = 3;
108 } else { 115 } else {
109 printf("ERROR: Unsupported output format\n"); 116 fprintf(stderr, "ERROR: Unsupported output format\n");
110 free(options); 117 free(options);
111 return NULL; 118 return NULL;
112 } 119 }
@@ -129,7 +136,7 @@ static options_t *parse_arguments(int argc, char *argv[])
129 } 136 }
130 else 137 else
131 { 138 {
132 printf("ERROR: Invalid option '%s'\n", argv[i]); 139 fprintf(stderr, "ERROR: Invalid option '%s'\n", argv[i]);
133 free(options); 140 free(options);
134 return NULL; 141 return NULL;
135 } 142 }
@@ -140,6 +147,7 @@ static options_t *parse_arguments(int argc, char *argv[])
140 147
141int main(int argc, char *argv[]) 148int main(int argc, char *argv[])
142{ 149{
150 int ret = 0;
143 FILE *iplist = NULL; 151 FILE *iplist = NULL;
144 plist_t root_node = NULL; 152 plist_t root_node = NULL;
145 char *plist_out = NULL; 153 char *plist_out = NULL;
@@ -162,7 +170,7 @@ int main(int argc, char *argv[])
162 plist_entire = malloc(sizeof(char) * read_capacity); 170 plist_entire = malloc(sizeof(char) * read_capacity);
163 if(plist_entire == NULL) 171 if(plist_entire == NULL)
164 { 172 {
165 printf("ERROR: Failed to allocate buffer to read from stdin"); 173 fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin");
166 free(options); 174 free(options);
167 return 1; 175 return 1;
168 } 176 }
@@ -176,7 +184,7 @@ int main(int argc, char *argv[])
176 plist_entire = realloc(plist_entire, sizeof(char) * read_capacity); 184 plist_entire = realloc(plist_entire, sizeof(char) * read_capacity);
177 if (plist_entire == NULL) 185 if (plist_entire == NULL)
178 { 186 {
179 printf("ERROR: Failed to reallocate stdin buffer\n"); 187 fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
180 free(old); 188 free(old);
181 free(options); 189 free(options);
182 return 1; 190 return 1;
@@ -190,7 +198,7 @@ int main(int argc, char *argv[])
190 plist_entire = realloc(plist_entire, sizeof(char) * (read_capacity+1)); 198 plist_entire = realloc(plist_entire, sizeof(char) * (read_capacity+1));
191 if (plist_entire == NULL) 199 if (plist_entire == NULL)
192 { 200 {
193 printf("ERROR: Failed to reallocate stdin buffer\n"); 201 fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
194 free(old); 202 free(old);
195 free(options); 203 free(options);
196 return 1; 204 return 1;
@@ -201,14 +209,14 @@ int main(int argc, char *argv[])
201 // Not positive we need this, but it doesnt seem to hurt lol 209 // Not positive we need this, but it doesnt seem to hurt lol
202 if(ferror(stdin)) 210 if(ferror(stdin))
203 { 211 {
204 printf("ERROR: reading from stdin.\n"); 212 fprintf(stderr, "ERROR: reading from stdin.\n");
205 free(plist_entire); 213 free(plist_entire);
206 free(options); 214 free(options);
207 return 1; 215 return 1;
208 } 216 }
209 217
210 if (read_size < 8) { 218 if (read_size < 8) {
211 printf("ERROR: Input file is too small to contain valid plist data.\n"); 219 fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n");
212 free(plist_entire); 220 free(plist_entire);
213 free(options); 221 free(options);
214 return 1; 222 return 1;
@@ -219,7 +227,7 @@ int main(int argc, char *argv[])
219 // read input file 227 // read input file
220 iplist = fopen(options->in_file, "rb"); 228 iplist = fopen(options->in_file, "rb");
221 if (!iplist) { 229 if (!iplist) {
222 printf("ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno)); 230 fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
223 free(options); 231 free(options);
224 return 1; 232 return 1;
225 } 233 }
@@ -228,7 +236,7 @@ int main(int argc, char *argv[])
228 fstat(fileno(iplist), &filestats); 236 fstat(fileno(iplist), &filestats);
229 237
230 if (filestats.st_size < 8) { 238 if (filestats.st_size < 8) {
231 printf("ERROR: Input file is too small to contain valid plist data.\n"); 239 fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n");
232 free(options); 240 free(options);
233 fclose(iplist); 241 fclose(iplist);
234 return -1; 242 return -1;
@@ -240,7 +248,7 @@ int main(int argc, char *argv[])
240 } 248 }
241 249
242 if (options->out_fmt == 0) { 250 if (options->out_fmt == 0) {
243 // convert from binary to xml or vice-versa<br> 251 // convert from binary to xml or vice-versa
244 if (plist_is_binary(plist_entire, read_size)) 252 if (plist_is_binary(plist_entire, read_size))
245 { 253 {
246 plist_from_bin(plist_entire, read_size, &root_node); 254 plist_from_bin(plist_entire, read_size, &root_node);
@@ -254,29 +262,13 @@ int main(int argc, char *argv[])
254 } 262 }
255 else 263 else
256 { 264 {
265 plist_from_memory(plist_entire, read_size, &root_node);
257 if (options->out_fmt == 1) { 266 if (options->out_fmt == 1) {
258 if (plist_is_binary(plist_entire, read_size)) 267 plist_to_bin(root_node, &plist_out, &size);
259 {
260 plist_out = malloc(sizeof(char) * read_size);
261 memcpy(plist_out, plist_entire, read_size);
262 size = read_size;
263 }
264 else
265 {
266 plist_from_xml(plist_entire, read_size, &root_node);
267 plist_to_bin(root_node, &plist_out, &size);
268 }
269 } else if (options->out_fmt == 2) { 268 } else if (options->out_fmt == 2) {
270 if (plist_is_binary(plist_entire, read_size)) { 269 plist_to_xml(root_node, &plist_out, &size);
271 plist_from_bin(plist_entire, read_size, &root_node); 270 } else if (options->out_fmt == 3) {
272 plist_to_xml(root_node, &plist_out, &size); 271 plist_to_json(root_node, &plist_out, &size, 0);
273 }
274 else
275 {
276 plist_out = malloc(sizeof(char) * read_size);
277 memcpy(plist_out, plist_entire, read_size);
278 size = read_size;
279 }
280 } 272 }
281 } 273 }
282 plist_free(root_node); 274 plist_free(root_node);
@@ -288,7 +280,7 @@ int main(int argc, char *argv[])
288 { 280 {
289 FILE *oplist = fopen(options->out_file, "wb"); 281 FILE *oplist = fopen(options->out_file, "wb");
290 if (!oplist) { 282 if (!oplist) {
291 printf("ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno)); 283 fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
292 free(options); 284 free(options);
293 return 1; 285 return 1;
294 } 286 }
@@ -301,9 +293,11 @@ int main(int argc, char *argv[])
301 293
302 free(plist_out); 294 free(plist_out);
303 } 295 }
304 else 296 else {
305 printf("ERROR: Failed to convert input file.\n"); 297 fprintf(stderr, "ERROR: Failed to convert input file.\n");
298 ret = 2;
299 }
306 300
307 free(options); 301 free(options);
308 return 0; 302 return ret;
309} 303}