summaryrefslogtreecommitdiffstats
path: root/src/jplist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/jplist.c')
-rw-r--r--src/jplist.c856
1 files changed, 856 insertions, 0 deletions
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..782d2b3
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,856 @@
+/*
+ * jplist.c
+ * JSON plist implementation
+ *
+ * Copyright (c) 2019-2021 Nikias Bassen All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "jsmn.h"
+
+#ifdef DEBUG
+static int plist_json_debug = 0;
+#define PLIST_JSON_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonparser] ERROR: " __VA_ARGS__); }
+#define PLIST_JSON_WRITE_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_JSON_ERR(...)
+#define PLIST_JSON_WRITE_ERR(...)
+#endif
+
+void plist_json_init(void)
+{
+ /* init JSON stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_JSON_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_json_debug = 1;
+ }
+#endif
+}
+
+void plist_json_deinit(void)
+{
+ /* deinit JSON stuff */
+}
+
+void plist_json_set_debug(int debug)
+{
+#ifdef DEBUG
+ plist_json_debug = debug;
+#endif
+}
+
+#ifndef HAVE_STRNDUP
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+static char* strndup(const char* str, size_t len)
+{
+ char *newstr = (char *)malloc(len+1);
+ if (newstr) {
+ strncpy(newstr, str, len);
+ newstr[len]= '\0';
+ }
+ return newstr;
+}
+#pragma GCC diagnostic pop
+#endif
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node)
+ return PLIST_ERR_INVALID_ARG;
+
+ node_data = plist_get_data(node);
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ break;
+
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (node_data->length == 16) {
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ const char *charmap[32] = {
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+ "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f",
+ "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
+ };
+ size_t j = 0;
+ size_t len = 0;
+ off_t start = 0;
+ off_t cur = 0;
+
+ str_buf_append(*outbuf, "\"", 1);
+
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ } else if (ch == '"') {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "\\\"", 2);
+ start = cur+1;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+
+ str_buf_append(*outbuf, "\"", 1);
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "[", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "]", 1);
+ } break;
+ case PLIST_DICT: {
+ str_buf_append(*outbuf, "{", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (cnt % 2 == 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, ":", 1);
+ if (prettify) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "}", 1);
+ } break;
+ case PLIST_DATA:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = node_estimate_size(ch, size, depth + 1, prettify);
+ if (res < 0) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ if (prettify) {
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ }
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ if (prettify) {
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ }
+ break;
+ default:
+ break;
+ }
+ if (prettify)
+ *size += (depth << 1); // indent for {} and []
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ *size += 2;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !plist_json || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (!PLIST_IS_DICT(plist) && !PLIST_IS_ARRAY(plist)) {
+ PLIST_JSON_WRITE_ERR("plist data is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_JSON_WRITE_ERR("Could not allocate output buffer\n");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = node_to_json((node_t)plist, &outbuf, 0, prettify);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *plist_json = NULL;
+ *length = 0;
+ return res;
+ }
+ if (prettify) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+
+ str_buf_append(outbuf, "\0", 1);
+
+ *plist_json = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+typedef struct {
+ jsmntok_t* tokens;
+ int count;
+} jsmntok_info_t;
+
+static int64_t parse_decimal(const char* str, const char* str_end, char** endp)
+{
+ uint64_t MAX = INT64_MAX;
+ uint64_t x = 0;
+ int is_neg = 0;
+ *endp = (char*)str;
+
+ if (str[0] == '-') {
+ is_neg = 1;
+ (*endp)++;
+ }
+ if (is_neg) {
+ MAX++;
+ }
+ while (*endp < str_end && isdigit(**endp)) {
+ if (x > PO10i_LIMIT) {
+ x = MAX;
+ break;
+ }
+ x = x * 10;
+ unsigned int add = (**endp - '0');
+ if (x + add > MAX) {
+ x = MAX;
+ break;
+ }
+ x += add;
+ (*endp)++;
+ }
+
+ // swallow the rest of the digits in case we dropped out early
+ while (*endp < str_end && isdigit(**endp)) (*endp)++;
+
+ int64_t result = x;
+ if (is_neg) {
+ if (x == MAX) {
+ result = INT64_MIN;
+ } else {
+ result = -(int64_t)x;
+ }
+ }
+ return result;
+}
+
+static plist_t parse_primitive(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_PRIMITIVE) {
+ PLIST_JSON_ERR("%s: token type != JSMN_PRIMITIVE\n", __func__);
+ return NULL;
+ }
+ plist_t val = NULL;
+ const char* str_val = js + ti->tokens[*index].start;
+ const char* str_end = js + ti->tokens[*index].end;
+ size_t str_len = ti->tokens[*index].end - ti->tokens[*index].start;
+ if (!strncmp("false", str_val, str_len)) {
+ val = plist_new_bool(0);
+ } else if (!strncmp("true", str_val, str_len)) {
+ val = plist_new_bool(1);
+ } else if (!strncmp("null", str_val, str_len)) {
+ plist_data_t data = plist_new_plist_data();
+ data->type = PLIST_NULL;
+ val = plist_new_node(data);
+ } else if (isdigit(str_val[0]) || (str_val[0] == '-' && str_val+1 < str_end && isdigit(str_val[1]))) {
+ char* endp = (char*)str_val;
+ int is_neg = (str_val[0] == '-');
+ int64_t intpart = parse_decimal(str_val, str_end, &endp);
+ if (endp >= str_end) {
+ /* integer */
+ if (is_neg || intpart <= INT64_MAX) {
+ val = plist_new_int(intpart);
+ } else {
+ val = plist_new_uint((uint64_t)intpart);
+ }
+ } else if ((*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) || ((*endp == 'e' || *endp == 'E') && endp+1 < str_end && (isdigit(*(endp+1)) || ((*(endp+1) == '-') && endp+2 < str_end && isdigit(*(endp+2)))))) {
+ /* floating point */
+ double dval = (double)intpart;
+ char* fendp = endp;
+ int err = 0;
+ do {
+ if (*endp == '.') {
+ fendp++;
+ double frac = 0;
+ double p = 0.1;
+ while (fendp < str_end && isdigit(*fendp)) {
+ frac = frac + (*fendp - '0') * p;
+ p *= 0.1;
+ fendp++;
+ }
+ if (is_neg) {
+ dval -= frac;
+ } else {
+ dval += frac;
+ }
+ }
+ if (fendp >= str_end) {
+ break;
+ }
+ if (fendp+1 < str_end && (*fendp == 'e' || *fendp == 'E') && (isdigit(*(fendp+1)) || ((*(fendp+1) == '-') && fendp+2 < str_end && isdigit(*(fendp+2))))) {
+ int64_t exp = parse_decimal(fendp+1, str_end, &fendp);
+ dval = dval * pow(10, (double)exp);
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
+ err++;
+ }
+ } while (0);
+ if (!err) {
+ if (isinf(dval) || isnan(dval)) {
+ PLIST_JSON_ERR("%s: unrepresentable floating point value at offset %d when parsing numerical value\n", __func__, (int)(str_val - js));
+ } else {
+ val = plist_new_real(dval);
+ }
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing numerical value\n", __func__, (int)(endp - js));
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid primitive value '%.*s' encountered\n", __func__, (int)str_len, str_val);
+ }
+ (*index)++;
+ return val;
+}
+
+static char* unescape_string(const char* str_val, size_t str_len, size_t *new_len)
+{
+ char* strval = strndup(str_val, str_len);
+ size_t i = 0;
+ while (i < str_len) {
+ if (strval[i] == '\\' && i < str_len-1) {
+ switch (strval[i+1]) {
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ memmove(strval+i, strval+i+1, str_len - (i+1));
+ str_len--;
+ switch (strval[i]) {
+ case 'b':
+ strval[i] = '\b';
+ break;
+ case 'f':
+ strval[i] = '\f';
+ break;
+ case 'r':
+ strval[i] = '\r';
+ break;
+ case 'n':
+ strval[i] = '\n';
+ break;
+ case 't':
+ strval[i] = '\t';
+ break;
+ default:
+ break;
+ }
+ break;
+ case 'u': {
+ unsigned int val = 0;
+ if (str_len-(i+2) < 4) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%s' (too short)\n", __func__, strval+i);
+ free(strval);
+ return NULL;
+ }
+ if (!(isxdigit(strval[i+2]) && isxdigit(strval[i+3]) && isxdigit(strval[i+4]) && isxdigit(strval[i+5])) || sscanf(strval+i+2, "%04x", &val) != 1) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 6, strval+i);
+ free(strval);
+ return NULL;
+ }
+ int bytelen = 0;
+ if (val >= 0x800) {
+ /* three bytes */
+ strval[i] = (char)(0xE0 + ((val >> 12) & 0xF));
+ strval[i+1] = (char)(0x80 + ((val >> 6) & 0x3F));
+ strval[i+2] = (char)(0x80 + (val & 0x3F));
+ bytelen = 3;
+ } else if (val >= 0x80) {
+ /* two bytes */
+ strval[i] = (char)(0xC0 + ((val >> 6) & 0x1F));
+ strval[i+1] = (char)(0x80 + (val & 0x3F));
+ bytelen = 2;
+ } else {
+ /* one byte */
+ strval[i] = (char)(val & 0x7F);
+ bytelen = 1;
+ }
+ memmove(strval+i+bytelen, strval+i+6, str_len - (i+5));
+ str_len -= (6-bytelen);
+ } break;
+ default:
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 2, strval+i);
+ free(strval);
+ return NULL;
+ }
+ }
+ i++;
+ }
+ strval[str_len] = '\0';
+ if (new_len) {
+ *new_len = str_len;
+ }
+ return strval;
+}
+
+static plist_t parse_string(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_STRING) {
+ PLIST_JSON_ERR("%s: token type != JSMN_STRING\n", __func__);
+ return NULL;
+ }
+
+ size_t str_len = 0; ;
+ char* strval = unescape_string(js + ti->tokens[*index].start, ti->tokens[*index].end - ti->tokens[*index].start, &str_len);
+ if (!strval) {
+ return NULL;
+ }
+ plist_t node;
+
+ plist_data_t data = plist_new_plist_data();
+ data->type = PLIST_STRING;
+ data->strval = strval;
+ data->length = str_len;
+ node = plist_new_node(data);
+
+ (*index)++;
+ return node;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index);
+
+static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_ARRAY) {
+ PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
+ return NULL;
+ }
+ plist_t arr = plist_new_array();
+ int num_tokens = ti->tokens[*index].size;
+ int num;
+ int j = (*index)+1;
+ for (num = 0; num < num_tokens; num++) {
+ if (j >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(arr);
+ return NULL;
+ }
+ plist_t val = NULL;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_array_append_item(arr, val);
+ } else {
+ plist_free(arr);
+ return NULL;
+ }
+ }
+ *(index) = j;
+ return arr;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_OBJECT) {
+ PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
+ return NULL;
+ }
+ int num_tokens = ti->tokens[*index].size;
+ int num;
+ int j = (*index)+1;
+ if (num_tokens % 2 != 0) {
+ PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
+ return NULL;
+ }
+ plist_t obj = plist_new_dict();
+ for (num = 0; num < num_tokens; num++) {
+ if (j+1 >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(obj);
+ return NULL;
+ }
+ if (ti->tokens[j].type == JSMN_STRING) {
+ char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL);
+ if (!key) {
+ plist_free(obj);
+ return NULL;
+ }
+ plist_t val = NULL;
+ j++;
+ num++;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_dict_set_item(obj, key, val);
+ } else {
+ free(key);
+ plist_free(obj);
+ return NULL;
+ }
+ free(key);
+ } else {
+ PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
+ plist_free(obj);
+ return NULL;
+ }
+ }
+ (*index) = j;
+ return obj;
+}
+
+plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
+{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!json || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+ int maxtoks = 256;
+ int curtoks = 0;
+ int r = 0;
+ jsmntok_t *tokens = NULL;
+
+ do {
+ jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
+ if (!newtokens) {
+ PLIST_JSON_ERR("%s: Out of memory\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
+ memset((unsigned char*)newtokens + sizeof(jsmntok_t)*curtoks, '\0', sizeof(jsmntok_t)*(maxtoks-curtoks));
+ tokens = newtokens;
+ curtoks = maxtoks;
+
+ r = jsmn_parse(&parser, json, length, tokens, maxtoks);
+ if (r == JSMN_ERROR_NOMEM) {
+ maxtoks+=16;
+ continue;
+ }
+ } while (r == JSMN_ERROR_NOMEM);
+
+ switch(r) {
+ case JSMN_ERROR_NOMEM:
+ PLIST_JSON_ERR("%s: Out of memory...\n", __func__);
+ free(tokens);
+ return PLIST_ERR_NO_MEM;
+ case JSMN_ERROR_INVAL:
+ PLIST_JSON_ERR("%s: Invalid character inside JSON string\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ case JSMN_ERROR_PART:
+ PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ default:
+ break;
+ }
+
+ int startindex = 0;
+ jsmntok_info_t ti = { tokens, parser.toknext };
+ switch (tokens[startindex].type) {
+ case JSMN_PRIMITIVE:
+ *plist = parse_primitive(json, &ti, &startindex);
+ break;
+ case JSMN_STRING:
+ *plist = parse_string(json, &ti, &startindex);
+ break;
+ case JSMN_ARRAY:
+ *plist = parse_array(json, &ti, &startindex);
+ break;
+ case JSMN_OBJECT:
+ *plist = parse_object(json, &ti, &startindex);
+ break;
+ default:
+ break;
+ }
+ free(tokens);
+ return PLIST_ERR_SUCCESS;
+}