From 429cbc660ae14d4998715803b44c71abf0e4a339 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Thu, 23 Dec 2021 03:09:07 +0100 Subject: Add support for JSON format --- docs/plistutil.1 | 14 +- include/plist/plist.h | 43 ++- src/Makefile.am | 2 + src/jplist.c | 695 +++++++++++++++++++++++++++++++++++++++++++ src/jsmn.c | 280 +++++++++++++++++ src/jsmn.h | 91 ++++++ src/plist.c | 6 + test/Makefile.am | 15 +- test/amp.test | 8 +- test/data/data.bplist | Bin 0 -> 3718 bytes test/data/j1.plist | 1 + test/invalid_tag.test | 8 +- test/json-invalid-types.test | 36 +++ test/json1.test | 19 ++ test/json2.test | 22 ++ test/malformed_dict.test | 8 +- test/plist_cmp.c | 4 + test/plist_jtest.c | 131 ++++++++ test/recursion.test | 8 +- tools/plistutil.c | 78 +++-- 20 files changed, 1403 insertions(+), 66 deletions(-) create mode 100644 src/jplist.c create mode 100644 src/jsmn.c create mode 100644 src/jsmn.h create mode 100644 test/data/data.bplist create mode 100644 test/data/j1.plist create mode 100755 test/json-invalid-types.test create mode 100755 test/json1.test create mode 100755 test/json2.test create mode 100644 test/plist_jtest.c 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 @@ .TH "plistutil" 1 .SH NAME -plistutil \- Convert a plist FILE from binary to XML format or vice-versa +plistutil \- Convert a plist FILE between binary, XML, and JSON format .SH SYNOPSIS .B plistutil [OPTIONS] [-i FILE] [-o FILE] .SH DESCRIPTION -plistutil allows converting a file in Property List format from binary to XML format or vice-versa. +plistutil allows converting a Property List file between binary, XML, and JSON format. .SH OPTIONS .TP .B \-i, \-\-infile FILE @@ -18,10 +18,13 @@ filename, plistutil will read from stdin. Output FILE to convert to. If this argument is omitted or - is passed as filename, plistutil will write to stdout. .TP -.B \-f, \-\-format [bin|xml] +.B \-f, \-\-format [bin|xml|json] Force output format, regardless of input type. This is useful if the input format is not known, but the output format should always be in a specific -format (like xml). +format (like xml or json). + +If omitted, XML plist data will be converted to binary and vice-versa. To +convert to/from JSON the output format needs to specified. .TP .B \-h, \-\-help Prints usage information. @@ -47,6 +50,9 @@ Print test.plist as XML plist, regardless of the input format. .B plistutil -i test.plist -f xml -o - Same as before. .TP +.B plistutil -i test.plist -f json +Print test.plist as JSON plist, regardless of the input format. +.TP .B cat test.plist |plistutil -f xml Take plist data from stdin - piped via cat - and write the output as XML to 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 @@ -681,6 +681,19 @@ extern "C" */ plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length); + /** + * Export the #plist_t structure to JSON format. + * + * @param plist the root node to export + * @param json a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param prettify pretty print the output if != 0 + * @return PLIST_ERR_SUCCESS on success or a #plist_error on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify); + /** * Import the #plist_t structure from XML format. * @@ -701,10 +714,26 @@ extern "C" */ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist); + /** + * Import the #plist_t structure from JSON format. + * + * @param json a pointer to the JSON buffer. + * @param length length of the buffer to read. + * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_error on failure + */ + plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist); + /** * Import the #plist_t structure from memory data. * This method will look at the first bytes of plist_data - * to determine if plist_data contains a binary or XML plist. + * to determine if plist_data contains a binary, JSON, or XML plist + * and tries to parse the data in the appropriate format. + * @note This is just a convenience function and the format detection is + * very basic. It checks with plist_is_binary() if the data supposedly + * contains binary plist data, if not it checks if the first byte is + * either '{' or '[' and assumes JSON format, otherwise it will try + * to parse the data as XML. * * @param plist_data a pointer to the memory buffer containing plist data. * @param length length of the buffer to read. @@ -714,12 +743,12 @@ extern "C" plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist); /** - * Test if in-memory plist data is binary or XML - * This method will look at the first bytes of plist_data - * to determine if plist_data contains a binary or XML plist. - * This method is not validating the whole memory buffer to check if the - * content is truly a plist, it's only using some heuristic on the first few - * bytes of plist_data. + * Test if in-memory plist data is in binary format. + * This function will look at the first bytes of plist_data to determine + * if it supposedly contains a binary plist. + * @note The function is not validating the whole memory buffer to check + * if the content is truly a plist, it is only using some heuristic on + * the first few bytes of plist_data. * * @param plist_data a pointer to the memory buffer containing plist data. * @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 = \ time64_limits.h \ xplist.c \ bplist.c \ + jsmn.c jsmn.h \ + jplist.c \ plist.c plist.h libplist___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 @@ +/* + * 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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 */ +} + +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 int 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_UINT: + 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: { + 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++) { + switch (node_data->strval[j]) { + case '"': + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, "\\\"", 2); + start = cur+1; + break; + default: + break; + } + 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); + } + } + int 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); + } + } + int 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; + 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 int 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)) { + int 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 += 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 += 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_UINT: + 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_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_API int plist_to_json(plist_t plist, char **json, uint32_t* length, int prettify) +{ + uint64_t size = 0; + int res; + + if (!plist || !json || !length) { + return PLIST_ERR_INVALID_ARG; + } + + res = node_estimate_size(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"); + return PLIST_ERR_NO_MEM; + } + + res = node_to_json(plist, &outbuf, 0, prettify); + if (res < 0) { + str_buf_free(outbuf); + *json = NULL; + *length = 0; + return res; + } + + str_buf_append(outbuf, "\0", 1); + + *json = outbuf->data; + *length = outbuf->len - 1; + + outbuf->data = NULL; + str_buf_free(outbuf); + + return PLIST_ERR_SUCCESS; +} + +static plist_t parse_primitive(const char* js, jsmntok_t* tokens, int* index) +{ + if (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 + tokens[*index].start; + const char* str_end = js + tokens[*index].end; + size_t str_len = tokens[*index].end - 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 (str_val[0] == '-' || isdigit(str_val[0])) { + char* endp = NULL; + long long intpart = strtol(str_val, &endp, 10); + if (endp >= str_end) { + /* integer */ + val = plist_new_uint((uint64_t)intpart); + } else if (*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) { + /* float */ + char* fendp = endp+1; + while (isdigit(*fendp) && fendp < str_end) fendp++; + if ((fendp > endp+1 && fendp >= str_end) || (fendp+2 < str_end && (*fendp == 'e' || *fendp == 'E') && (*(fendp+1) == '+' || *(fendp+1) == '-') && isdigit(*(fendp+2)))) { + double dval = atof(str_val); + val = plist_new_real(dval); + } else { + PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js)); + } + } 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 plist_t parse_string(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_STRING) { + PLIST_JSON_ERR("%s: token type != JSMN_STRING\n", __func__); + return NULL; + } + + const char* str_val = js + tokens[*index].start; + size_t str_len = tokens[*index].end - tokens[*index].start; + char* strval = strndup(str_val, str_len); + plist_t node; + + /* unescape */ + 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) { + free(strval); + PLIST_JSON_ERR("%s: invalid escape sequence '%s' (too short)\n", __func__, strval+i); + 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) { + free(strval); + PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 6, strval+i); + 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++; + } + + 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_t* tokens, int* index); + +static plist_t parse_array(const char* js, jsmntok_t* tokens, int* index) +{ + if (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 = tokens[*index].size; + int num; + int j = (*index)+1; + for (num = 0; num < num_tokens; num++) { + plist_t val = NULL; + switch (tokens[j].type) { + case JSMN_OBJECT: + val = parse_object(js, tokens, &j); + break; + case JSMN_ARRAY: + val = parse_array(js, tokens, &j); + break; + case JSMN_STRING: + val = parse_string(js, tokens, &j); + break; + case JSMN_PRIMITIVE: + val = parse_primitive(js, tokens, &j); + break; + default: + break; + } + if (val) { + plist_array_append_item(arr, val); + } + } + *(index) = j; + return arr; +} + +static plist_t parse_object(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_OBJECT) { + PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__); + return NULL; + } + plist_t obj = plist_new_dict(); + int num_tokens = tokens[*index].size; + int num; + int j = (*index)+1; + for (num = 0; num < num_tokens; num++) { + if (tokens[j].type == JSMN_STRING) { + char* key = strndup(js + tokens[j].start, tokens[j].end - tokens[j].start); + plist_t val = NULL; + j++; + num++; + switch (tokens[j].type) { + case JSMN_OBJECT: + val = parse_object(js, tokens, &j); + break; + case JSMN_ARRAY: + val = parse_array(js, tokens, &j); + break; + case JSMN_STRING: + val = parse_string(js, tokens, &j); + break; + case JSMN_PRIMITIVE: + val = parse_primitive(js, tokens, &j); + break; + default: + break; + } + if (val) { + plist_dict_set_item(obj, key, val); + } + free(key); + } else { + PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__); + return NULL; + } + } + (*index) = j; + return obj; +} + +PLIST_API int 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 r = 0; + jsmntok_t *tokens = NULL; + + do { + jsmntok_t* newtokens = realloc(tokens, sizeof(jsmntok_t)*maxtoks); + if (!newtokens) { + PLIST_JSON_ERR("%s: Out of memory\n", __func__); + return PLIST_ERR_NO_MEM; + } + tokens = newtokens; + + r = jsmn_parse(&parser, json, tokens, maxtoks); + if (r == JSMN_ERROR_NOMEM) { + maxtoks+=16; + continue; + } + } while (0); + + 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; + switch (tokens[startindex].type) { + case JSMN_PRIMITIVE: + *plist = parse_primitive(json, tokens, &startindex); + break; + case JSMN_STRING: + *plist = parse_string(json, tokens, &startindex); + break; + case JSMN_ARRAY: + *plist = parse_array(json, tokens, &startindex); + break; + case JSMN_OBJECT: + *plist = parse_object(json, tokens, &startindex); + break; + default: + break; + } + free(tokens); + return PLIST_ERR_SUCCESS; +} 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 @@ +/* + * jsmn.c + * Simple JSON parser + * + * Copyright (c) 2010 Serge A. Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, int num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, int num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return JSMN_SUCCESS; +} + +/** + * Filsl next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, int num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, + unsigned int num_tokens) { + jsmnerr_t r; + int i; + jsmntok_t *token; + + for (; js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return JSMN_SUCCESS; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + 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 @@ +/* + * jsmn.h + * Simple JSON parser (header file) + * + * Copyright (c) 2010 Serge A. Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3, + /* Everything was fine */ + JSMN_SUCCESS = 0 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, unsigned int num_tokens); + +#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); extern void plist_xml_deinit(void); extern void plist_bin_init(void); extern void plist_bin_deinit(void); +extern void plist_json_init(void); +extern void plist_json_deinit(void); static void internal_plist_init(void) { plist_bin_init(); plist_xml_init(); + plist_json_init(); } static void internal_plist_deinit(void) { plist_bin_deinit(); plist_xml_deinit(); + plist_json_deinit(); } #ifdef WIN32 @@ -195,6 +199,8 @@ PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, } if (plist_is_binary(plist_data, length)) { res = plist_from_bin(plist_data, length, plist); + } else if (plist_data[0] == '[' || plist_data[0] == '{') { + res = plist_from_json(plist_data, length, plist); } else { res = plist_from_xml(plist_data, length, plist); } 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 = noinst_PROGRAMS = \ plist_cmp \ plist_test \ - plist_btest + plist_btest \ + plist_jtest plist_cmp_SOURCES = plist_cmp.c plist_cmp_LDADD = \ @@ -21,6 +22,9 @@ plist_test_LDADD = $(top_builddir)/src/libplist-2.0.la plist_btest_SOURCES = plist_btest.c plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la +plist_jtest_SOURCES = plist_jtest.c +plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la + TESTS = \ empty.test \ small.test \ @@ -45,7 +49,10 @@ TESTS = \ offsetsize.test \ refsize.test \ malformed_dict.test \ - uid.test + uid.test \ + json1.test \ + json2.test \ + json-invalid-types.test EXTRA_DIST = \ $(TESTS) \ @@ -89,7 +96,9 @@ EXTRA_DIST = \ data/signedunsigned.plist \ data/unsigned.bplist \ data/unsigned.plist \ - data/uid.bplist + data/uid.bplist \ + data/data.bplist \ + data/j1.plist TESTS_ENVIRONMENT = \ 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 @@ ## -*- sh -*- -set -e - DATASRC=$top_srcdir/test/data TESTFILE=amp.plist DATAIN0=$DATASRC/$TESTFILE @@ -9,6 +7,10 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out rm -rf $DATAOUT0 $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 -if test -f $DATAOUT0; then + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then exit 1 +else + exit 0 fi diff --git a/test/data/data.bplist b/test/data/data.bplist new file mode 100644 index 0000000..955993f Binary files /dev/null and b/test/data/data.bplist 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 @@ ## -*- sh -*- -set -e - DATASRC=$top_srcdir/test/data TESTFILE=invalid_tag.plist DATAIN0=$DATASRC/$TESTFILE @@ -9,6 +7,10 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out rm -rf $DATAOUT0 $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 -if test -f $DATAOUT0; then + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then exit 1 +else + exit 0 fi 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 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE0=data.bplist +TESTFILE1=7.plist +TESTFILE2=uid.bplist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting (failure expected)" +STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE0 -o /dev/null 2>&1` +echo "$STDERR" +if ! echo "$STDERR" |grep "PLIST_DATA type is not valid for JSON format"; then + exit 1 +fi + +echo "Converting (failure expected)" +STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE1 -o /dev/null 2>&1` +echo "$STDERR" +if ! echo "$STDERR" |grep "PLIST_DATE type is not valid for JSON format"; then + exit 2 +fi + +echo "Converting (failure expected)" +STDERR=`$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE2 -o /dev/null 2>&1` +echo "$STDERR" +if ! echo "$STDERR" |grep "PLIST_UID type is not valid for JSON format"; then + exit 3 +fi + +exit 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 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=j1.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out + +echo "Comparing" +$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 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=entities.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting input file to JSON" +$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE -o $DATASRC/$TESTFILE.json + +echo "Converting to binary and back to JSON" +$top_builddir/test/plist_jtest $DATASRC/$TESTFILE.json $DATAOUT/$TESTFILE.json.out + +echo "Comparing" +$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 @@ ## -*- sh -*- -set -e - DATASRC=$top_srcdir/test/data TESTFILE=malformed_dict.bplist DATAIN0=$DATASRC/$TESTFILE @@ -9,3 +7,9 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi 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[]) if (memcmp(plist_1, "bplist00", 8) == 0) plist_from_bin(plist_1, size_in1, &root_node1); + else if (plist_1[0] == '[' || plist_1[0] == '{') + plist_from_json(plist_1, size_in1, &root_node1); else plist_from_xml(plist_1, size_in1, &root_node1); if (memcmp(plist_2, "bplist00", 8) == 0) plist_from_bin(plist_2, size_in2, &root_node2); + else if (plist_2[0] == '[' || plist_2[0] == '{') + plist_from_json(plist_2, size_in2, &root_node2); else plist_from_xml(plist_2, size_in2, &root_node2); 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 @@ +/* + * backup_test.c + * source libplist regression test + * + * Copyright (c) 2009 Jonathan Beck 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 + */ + + +#include "plist/plist.h" + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + +int main(int argc, char *argv[]) +{ + FILE *iplist = NULL; + plist_t root_node1 = NULL; + plist_t root_node2 = NULL; + char *plist_json = NULL; + char *plist_json2 = NULL; + char *plist_bin = NULL; + int size_in = 0; + uint32_t size_out = 0; + uint32_t size_out2 = 0; + char *file_in = NULL; + char *file_out = NULL; + struct stat *filestats = (struct stat *) malloc(sizeof(struct stat)); + if (argc != 3) + { + printf("Wrong input\n"); + return 1; + } + + file_in = argv[1]; + file_out = argv[2]; + //read input file + iplist = fopen(file_in, "rb"); + + if (!iplist) + { + printf("File does not exists\n"); + return 2; + } + printf("File %s is open\n", file_in); + stat(file_in, filestats); + size_in = filestats->st_size; + plist_json = (char *) malloc(sizeof(char) * (size_in + 1)); + fread(plist_json, sizeof(char), size_in, iplist); + fclose(iplist); + plist_json[size_in] = 0; + + //convert one format to another + plist_from_json(plist_json, size_in, &root_node1); + if (!root_node1) + { + printf("PList JSON parsing failed\n"); + return 3; + } + + printf("PList JSON parsing succeeded\n"); + plist_to_bin(root_node1, &plist_bin, &size_out); + if (!plist_bin) + { + printf("PList BIN writing failed\n"); + return 4; + } + + printf("PList BIN writing succeeded\n"); + plist_from_bin(plist_bin, size_out, &root_node2); + if (!root_node2) + { + printf("PList BIN parsing failed\n"); + return 5; + } + + printf("PList BIN parsing succeeded\n"); + plist_to_json(root_node2, &plist_json2, &size_out2, 0); + if (!plist_json2) + { + printf("PList JSON writing failed\n"); + return 8; + } + + printf("PList JSON writing succeeded\n"); + if (plist_json2) + { + FILE *oplist = NULL; + oplist = fopen(file_out, "wb"); + fwrite(plist_json2, size_out2, sizeof(char), oplist); + fclose(oplist); + } + + plist_free(root_node1); + plist_free(root_node2); + free(plist_bin); + free(plist_json); + free(plist_json2); + free(filestats); + + if ((uint32_t)size_in != size_out2) + { + printf("Size of input and output is different\n"); + printf("Input size : %i\n", size_in); + printf("Output size : %i\n", size_out2); + } + + //success + return 0; +} + 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 @@ ## -*- sh -*- -set -e - DATASRC=$top_srcdir/test/data TESTFILE=recursion.bplist DATAIN0=$DATASRC/$TESTFILE @@ -9,3 +7,9 @@ DATAOUT0=$top_builddir/test/data/$TESTFILE.out $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi 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 @@ typedef struct _options { char *in_file, *out_file; - uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json someday + uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json } options_t; static void print_usage(int argc, char *argv[]) @@ -50,14 +50,19 @@ static void print_usage(int argc, char *argv[]) name = strrchr(argv[0], '/'); printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0])); printf("\n"); - printf("Convert a plist FILE from binary to XML format or vice-versa.\n"); + printf("Convert a plist FILE between binary, XML, and JSON format.\n"); + printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n"); + printf("To convert to/from JSON the output format needs to be specified.\n"); printf("\n"); printf("OPTIONS:\n"); - printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n"); - printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n"); - printf(" -f, --format [bin|xml] Force output format, regardless of input type\n"); - printf(" -d, --debug Enable extended debug output\n"); - printf(" -v, --version Print version information\n"); + printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n"); + printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n"); + printf(" -f, --format FORMAT Force output format, regardless of input type\n"); + printf(" FORMAT is one of xml, bin, or json\n"); + printf(" If omitted XML will be converted to binary,\n"); + printf(" and binary to XML.\n"); + printf(" -d, --debug Enable extended debug output\n"); + printf(" -v, --version Print version information\n"); printf("\n"); printf("Homepage: <" PACKAGE_URL ">\n"); printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n"); @@ -105,8 +110,10 @@ static options_t *parse_arguments(int argc, char *argv[]) options->out_fmt = 1; } else if (!strncmp(argv[i+1], "xml", 3)) { options->out_fmt = 2; + } else if (!strncmp(argv[i+1], "json", 4)) { + options->out_fmt = 3; } else { - printf("ERROR: Unsupported output format\n"); + fprintf(stderr, "ERROR: Unsupported output format\n"); free(options); return NULL; } @@ -129,7 +136,7 @@ static options_t *parse_arguments(int argc, char *argv[]) } else { - printf("ERROR: Invalid option '%s'\n", argv[i]); + fprintf(stderr, "ERROR: Invalid option '%s'\n", argv[i]); free(options); return NULL; } @@ -140,6 +147,7 @@ static options_t *parse_arguments(int argc, char *argv[]) int main(int argc, char *argv[]) { + int ret = 0; FILE *iplist = NULL; plist_t root_node = NULL; char *plist_out = NULL; @@ -162,7 +170,7 @@ int main(int argc, char *argv[]) plist_entire = malloc(sizeof(char) * read_capacity); if(plist_entire == NULL) { - printf("ERROR: Failed to allocate buffer to read from stdin"); + fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin"); free(options); return 1; } @@ -176,7 +184,7 @@ int main(int argc, char *argv[]) plist_entire = realloc(plist_entire, sizeof(char) * read_capacity); if (plist_entire == NULL) { - printf("ERROR: Failed to reallocate stdin buffer\n"); + fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n"); free(old); free(options); return 1; @@ -190,7 +198,7 @@ int main(int argc, char *argv[]) plist_entire = realloc(plist_entire, sizeof(char) * (read_capacity+1)); if (plist_entire == NULL) { - printf("ERROR: Failed to reallocate stdin buffer\n"); + fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n"); free(old); free(options); return 1; @@ -201,14 +209,14 @@ int main(int argc, char *argv[]) // Not positive we need this, but it doesnt seem to hurt lol if(ferror(stdin)) { - printf("ERROR: reading from stdin.\n"); + fprintf(stderr, "ERROR: reading from stdin.\n"); free(plist_entire); free(options); return 1; } if (read_size < 8) { - printf("ERROR: Input file is too small to contain valid plist data.\n"); + fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n"); free(plist_entire); free(options); return 1; @@ -219,7 +227,7 @@ int main(int argc, char *argv[]) // read input file iplist = fopen(options->in_file, "rb"); if (!iplist) { - printf("ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno)); + fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno)); free(options); return 1; } @@ -228,7 +236,7 @@ int main(int argc, char *argv[]) fstat(fileno(iplist), &filestats); if (filestats.st_size < 8) { - printf("ERROR: Input file is too small to contain valid plist data.\n"); + fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n"); free(options); fclose(iplist); return -1; @@ -240,7 +248,7 @@ int main(int argc, char *argv[]) } if (options->out_fmt == 0) { - // convert from binary to xml or vice-versa
+ // convert from binary to xml or vice-versa if (plist_is_binary(plist_entire, read_size)) { plist_from_bin(plist_entire, read_size, &root_node); @@ -254,29 +262,13 @@ int main(int argc, char *argv[]) } else { + plist_from_memory(plist_entire, read_size, &root_node); if (options->out_fmt == 1) { - if (plist_is_binary(plist_entire, read_size)) - { - plist_out = malloc(sizeof(char) * read_size); - memcpy(plist_out, plist_entire, read_size); - size = read_size; - } - else - { - plist_from_xml(plist_entire, read_size, &root_node); - plist_to_bin(root_node, &plist_out, &size); - } + plist_to_bin(root_node, &plist_out, &size); } else if (options->out_fmt == 2) { - if (plist_is_binary(plist_entire, read_size)) { - plist_from_bin(plist_entire, read_size, &root_node); - plist_to_xml(root_node, &plist_out, &size); - } - else - { - plist_out = malloc(sizeof(char) * read_size); - memcpy(plist_out, plist_entire, read_size); - size = read_size; - } + plist_to_xml(root_node, &plist_out, &size); + } else if (options->out_fmt == 3) { + plist_to_json(root_node, &plist_out, &size, 0); } } plist_free(root_node); @@ -288,7 +280,7 @@ int main(int argc, char *argv[]) { FILE *oplist = fopen(options->out_file, "wb"); if (!oplist) { - printf("ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno)); + fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno)); free(options); return 1; } @@ -301,9 +293,11 @@ int main(int argc, char *argv[]) free(plist_out); } - else - printf("ERROR: Failed to convert input file.\n"); + else { + fprintf(stderr, "ERROR: Failed to convert input file.\n"); + ret = 2; + } free(options); - return 0; + return ret; } -- cgit v1.1-32-gdbae