From 60d291941fadb72b66d11502710add5899e21a2d Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sun, 8 Jan 2023 05:29:22 +0100 Subject: Add support for OpenStep plist format --- docs/plistutil.1 | 8 +- include/plist/plist.h | 24 ++ src/Makefile.am | 1 + src/oplist.c | 861 ++++++++++++++++++++++++++++++++++++++++++ src/plist.c | 54 ++- test/Makefile.am | 18 +- test/data/o1.ostep | 45 +++ test/data/o2.ostep | 17 + test/data/o3.ostep | 16 + test/data/test.strings | 12 + test/ostep-comments.test | 20 + test/ostep-invalid-types.test | 33 ++ test/ostep-strings.test | 20 + test/ostep1.test | 20 + test/ostep2.test | 19 + test/plist_cmp.c | 15 +- test/plist_otest.c | 130 +++++++ tools/plistutil.c | 54 +-- 18 files changed, 1322 insertions(+), 45 deletions(-) create mode 100644 src/oplist.c create mode 100644 test/data/o1.ostep create mode 100644 test/data/o2.ostep create mode 100644 test/data/o3.ostep create mode 100644 test/data/test.strings create mode 100755 test/ostep-comments.test create mode 100755 test/ostep-invalid-types.test create mode 100755 test/ostep-strings.test create mode 100755 test/ostep1.test create mode 100755 test/ostep2.test create mode 100644 test/plist_otest.c diff --git a/docs/plistutil.1 b/docs/plistutil.1 index eb1b591..5342e91 100644 --- a/docs/plistutil.1 +++ b/docs/plistutil.1 @@ -18,13 +18,17 @@ 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|json] +.B \-f, \-\-format [bin|xml|json|openstep] 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 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. +convert to/from JSON or OpenStep the output format needs to specified. +.TP +.B \-c, \-\-compact +JSON and OpenStep only: Print output in compact form. By default, the output +will be pretty-printed. .TP .B \-h, \-\-help Prints usage information. diff --git a/include/plist/plist.h b/include/plist/plist.h index c0eae1c..0ae8889 100644 --- a/include/plist/plist.h +++ b/include/plist/plist.h @@ -697,6 +697,20 @@ extern "C" */ plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify); + /** + * Export the #plist_t structure to OpenStep format. + * + * @param plist the root node to export + * @param plist_openstep 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_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify); + + /** * Import the #plist_t structure from XML format. * @@ -727,6 +741,16 @@ extern "C" */ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist); + /** + * Import the #plist_t structure from OpenStep plist format. + * + * @param openstep a pointer to the OpenStep plist 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_err_t on failure + */ + plist_err_t plist_from_openstep(const char *openstep, 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 diff --git a/src/Makefile.am b/src/Makefile.am index d4c9e67..02b516c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,7 @@ libplist_2_0_la_SOURCES = \ bplist.c \ jsmn.c jsmn.h \ jplist.c \ + oplist.c \ plist.c plist.h libplist___2_0_la_LIBADD = libplist-2.0.la diff --git a/src/oplist.c b/src/oplist.c new file mode 100644 index 0000000..fa6977a --- /dev/null +++ b/src/oplist.c @@ -0,0 +1,861 @@ +/* + * oplist.c + * OpenStep plist implementation + * + * Copyright (c) 2021-2022 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" + +#ifdef DEBUG +static int plist_ostep_debug = 0; +#define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); } +#define PLIST_OSTEP_WRITE_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepwriter] ERROR: " __VA_ARGS__); } +#else +#define PLIST_OSTEP_ERR(...) +#define PLIST_OSTEP_WRITE_ERR(...) +#endif + +void plist_ostep_init(void) +{ + /* init OpenStep stuff */ +#ifdef DEBUG + char *env_debug = getenv("PLIST_OSTEP_DEBUG"); + if (env_debug && !strcmp(env_debug, "1")) { + plist_ostep_debug = 1; + } +#endif +} + +void plist_ostep_deinit(void) +{ + /* deinit OpenStep plist stuff */ +} + +#ifndef HAVE_STRNDUP +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; +} +#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 const char allowed_unquoted_chars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static int str_needs_quotes(const char* str, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) { + if (!allowed_unquoted_chars[(unsigned char)str[i]]) { + return 1; + } + } + return 0; +} + +static int node_to_openstep(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_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: { + 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; + int needs_quotes; + + len = node_data->length; + + needs_quotes = str_needs_quotes(node_data->strval, len); + + if (needs_quotes) { + str_buf_append(*outbuf, "\"", 1); + } + + 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); + + if (needs_quotes) { + 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_openstep(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_openstep(ch, outbuf, depth+1, prettify); + if (res < 0) { + return res; + } + if (cnt % 2 == 0) { + if (prettify) { + str_buf_append(*outbuf, " = ", 3); + } else { + str_buf_append(*outbuf, "=", 1); + } + } + cnt++; + } + if (cnt > 0) { + str_buf_append(*outbuf, ";", 1); + } + 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: { + size_t j = 0; + size_t len = 0; + str_buf_append(*outbuf, "<", 1); + len = node_data->length; + for (j = 0; j < len; j++) { + char charb[4]; + if (prettify && j > 0 && (j % 4 == 0)) + str_buf_append(*outbuf, " ", 1); + sprintf(charb, "%02x", (unsigned char)node_data->buff[j]); + str_buf_append(*outbuf, charb, 2); + } + str_buf_append(*outbuf, ">", 1); + } break; + case PLIST_BOOLEAN: + PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + case PLIST_NULL: + PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + case PLIST_DATE: + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + case PLIST_UID: + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep 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 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; // number of '=' and ';' + if (prettify) { + *size += n_children*2; // number of '\n' and extra spaces + *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_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_DICT: + case PLIST_ARRAY: + *size += 2; + break; + case PLIST_DATA: + *size += 2; // < and > + *size += data->length*2; + if (prettify) + *size += data->length/4; + break; + case PLIST_BOOLEAN: + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + case PLIST_DATE: + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + case PLIST_UID: + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + default: + PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n"); + return PLIST_ERR_UNKNOWN; + } + } + return PLIST_ERR_SUCCESS; +} + +PLIST_API int plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify) +{ + uint64_t size = 0; + int res; + + if (!plist || !openstep || !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_OSTEP_WRITE_ERR("Could not allocate output buffer"); + return PLIST_ERR_NO_MEM; + } + + res = node_to_openstep(plist, &outbuf, 0, prettify); + if (res < 0) { + str_buf_free(outbuf); + *openstep = NULL; + *length = 0; + return res; + } + if (prettify) { + str_buf_append(outbuf, "\n", 1); + } + + str_buf_append(outbuf, "\0", 1); + + *openstep = outbuf->data; + *length = outbuf->len - 1; + + outbuf->data = NULL; + str_buf_free(outbuf); + + return PLIST_ERR_SUCCESS; +} + +struct _parse_ctx { + const char *start; + const char *pos; + const char *end; + int err; +}; +typedef struct _parse_ctx* parse_ctx; + +static void parse_skip_ws(parse_ctx ctx) +{ + while (ctx->pos < ctx->end) { + // skip comments + if (*ctx->pos == '/' && (ctx->end - ctx->pos > 1)) { + if (*(ctx->pos+1) == '/') { + ctx->pos++; + while (ctx->pos < ctx->end) { + if ((*ctx->pos == '\n') || (*ctx->pos == '\r')) { + break; + } + ctx->pos++; + } + } else if (*(ctx->pos+1) == '*') { + ctx->pos++; + while (ctx->pos < ctx->end) { + if (*ctx->pos == '*' && (ctx->end - ctx->pos > 1)) { + if (*(ctx->pos+1) == '/') { + ctx->pos+=2; + break; + } + } + ctx->pos++; + } + } + } + // break on any char that's not white space + if (!(((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n')))) { + break; + } + ctx->pos++; + } +} + +#define HEX_DIGIT(x) ((x <= '9') ? (x - '0') : ((x <= 'F') ? (x - 'A' + 10) : (x - 'a' + 10))) + +static int node_from_openstep(parse_ctx ctx, plist_t *plist); + +static void parse_dict_data(parse_ctx ctx, plist_t dict) +{ + plist_t key = NULL; + plist_t val = NULL; + while (ctx->pos < ctx->end && !ctx->err) { + parse_skip_ws(ctx); + if (*ctx->pos == '}' || ctx->pos >= ctx->end) { + break; + } + key = NULL; + ctx->err = node_from_openstep(ctx, &key); + if (ctx->err != 0) { + break; + } + if (!PLIST_IS_STRING(key)) { + PLIST_OSTEP_ERR("Invalid type for dictionary key at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + parse_skip_ws(ctx); + if (*ctx->pos != '=') { + PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + ctx->pos++; + if (ctx->pos >= ctx->end) { + PLIST_OSTEP_ERR("EOF while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + val = NULL; + ctx->err = node_from_openstep(ctx, &val); + if (ctx->err != 0) { + plist_free(key); + break; + } + if (!val) { + plist_free(key); + PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + parse_skip_ws(ctx); + if (*ctx->pos != ';') { + plist_free(val); + plist_free(key); + PLIST_OSTEP_ERR("Missing terminating ';' while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + + plist_dict_set_item(dict, plist_get_string_ptr(key, NULL), val); + plist_free(key); + val = NULL; + + ctx->pos++; + } +} + +static int node_from_openstep(parse_ctx ctx, plist_t *plist) +{ + plist_t subnode = NULL; + const char *p = NULL; + while (ctx->pos < ctx->end && !ctx->err) { + parse_skip_ws(ctx); + if (ctx->pos >= ctx->end) { + break; + } + plist_data_t data = plist_new_plist_data(); + if (*ctx->pos == '{') { + data->type = PLIST_DICT; + subnode = plist_new_node(data); + ctx->pos++; + parse_dict_data(ctx, subnode); + if (ctx->err) { + goto err_out; + } + if (*ctx->pos != '}') { + PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + goto err_out; + } + ctx->pos++; + *plist = subnode; + parse_skip_ws(ctx); + break; + } else if (*ctx->pos == '(') { + data->type = PLIST_ARRAY; + subnode = plist_new_node(data); + ctx->pos++; + plist_t tmp = NULL; + while (ctx->pos < ctx->end && !ctx->err) { + parse_skip_ws(ctx); + if (*ctx->pos == ')') { + break; + } + ctx->err = node_from_openstep(ctx, &tmp); + if (ctx->err != 0) { + break; + } + if (!tmp) { + ctx->err++; + break; + } + plist_array_append_item(subnode, tmp); + tmp = NULL; + parse_skip_ws(ctx); + if (*ctx->pos != ',') { + break; + } + ctx->pos++; + } + if (ctx->err) { + goto err_out; + } + if (*ctx->pos != ')') { + PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + goto err_out; + } + ctx->pos++; + *plist = subnode; + parse_skip_ws(ctx); + break; + } else if (*ctx->pos == '<') { + data->type = PLIST_DATA; + ctx->pos++; + bytearray_t *bytes = byte_array_new(256); + while (ctx->pos < ctx->end && !ctx->err) { + parse_skip_ws(ctx); + if (*ctx->pos == '>') { + break; + } + if (!isxdigit(*ctx->pos)) { + PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + uint8_t b = HEX_DIGIT(*ctx->pos); + ctx->pos++; + if (ctx->pos >= ctx->end) { + PLIST_OSTEP_ERR("Unexpected end of data at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + if (!isxdigit(*ctx->pos)) { + PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + b = (b << 4) + HEX_DIGIT(*ctx->pos); + byte_array_append(bytes, &b, 1); + ctx->pos++; + } + if (ctx->err) { + goto err_out; + } + if (*ctx->pos != '>') { + PLIST_OSTEP_ERR("Missing terminating '>' at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + goto err_out; + } + ctx->pos++; + data->buff = bytes->data; + data->length = bytes->len; + bytes->data = NULL; + byte_array_free(bytes); + *plist = plist_new_node(data); + parse_skip_ws(ctx); + break; + } else if (*ctx->pos == '"' || *ctx->pos == '\'') { + char c = *ctx->pos; + ctx->pos++; + p = ctx->pos; + int num_escapes = 0; + while (ctx->pos < ctx->end) { + if (*ctx->pos == '\\') { + num_escapes++; + } + if ((*ctx->pos == c) && (*(ctx->pos-1) != '\\')) { + break; + } + ctx->pos++; + } + if (*ctx->pos != c) { + PLIST_OSTEP_ERR("Missing closing quote (%c) at offset %ld\n", c, ctx->pos - ctx->start); + ctx->err++; + goto err_out; + } + size_t slen = ctx->pos - p; + ctx->pos++; // skip the closing quote + char* strbuf = malloc(slen+1); + if (num_escapes > 0) { + size_t i = 0; + size_t o = 0; + while (i < slen) { + if (p[i] == '\\') { + /* handle escape sequence */ + i++; + switch (p[i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + // max 3 digits octal + unsigned char chr = 0; + int maxd = 3; + while ((i < slen) && (p[i] >= '0' && p[i] <= '7') && --maxd) { + chr = (chr << 3) + p[i] - '0'; + i++; + } + strbuf[o++] = (char)chr; + } break; + case 'U': { + i++; + // max 4 digits hex + uint16_t wchr = 0; + int maxd = 4; + while ((i < slen) && isxdigit(p[i]) && maxd--) { + wchr = (wchr << 4) + ((p[i] <= '9') ? (p[i] - '0') : ((p[i] <= 'F') ? (p[i] - 'A' + 10) : (p[i] - 'a' + 10))); + i++; + } + if (wchr >= 0x800) { + strbuf[o++] = (char)(0xE0 + ((wchr >> 12) & 0xF)); + strbuf[o++] = (char)(0x80 + ((wchr >> 6) & 0x3F)); + strbuf[o++] = (char)(0x80 + (wchr & 0x3F)); + } else if (wchr >= 0x80) { + strbuf[o++] = (char)(0xC0 + ((wchr >> 6) & 0x1F)); + strbuf[o++] = (char)(0x80 + (wchr & 0x3F)); + } else { + strbuf[o++] = (char)(wchr & 0x7F); + } + } break; + case 'a': strbuf[o++] = '\a'; i++; break; + case 'b': strbuf[o++] = '\b'; i++; break; + case 'f': strbuf[o++] = '\f'; i++; break; + case 'n': strbuf[o++] = '\n'; i++; break; + case 'r': strbuf[o++] = '\r'; i++; break; + case 't': strbuf[o++] = '\t'; i++; break; + case 'v': strbuf[o++] = '\v'; i++; break; + case '"': strbuf[o++] = '"'; i++; break; + case '\'': strbuf[o++] = '\''; i++; break; + default: + break; + } + } else { + strbuf[o++] = p[i++]; + } + } + strbuf[o] = '\0'; + slen = o; + } else { + strncpy(strbuf, p, slen); + strbuf[slen] = '\0'; + } + data->type = PLIST_STRING; + data->strval = strbuf; + data->length = slen; + *plist = plist_new_node(data); + parse_skip_ws(ctx); + break; + } else { + // unquoted string + size_t slen = 0; + parse_skip_ws(ctx); + p = ctx->pos; + while (ctx->pos < ctx->end) { + if (!allowed_unquoted_chars[(uint8_t)*ctx->pos]) { + break; + } + ctx->pos++; + } + slen = ctx->pos-p; + if (slen > 0) { + data->type = PLIST_STRING; + data->strval = strndup(p, slen); + data->length = slen; + *plist = plist_new_node(data); + parse_skip_ws(ctx); + break; + } else { + PLIST_OSTEP_ERR("Unexpected character when parsing unquoted string at offset %ld\n", ctx->pos - ctx->start); + ctx->err++; + break; + } + } + ctx->pos++; + } + +err_out: + if (ctx->err) { + plist_free(*plist); + *plist = NULL; + return PLIST_ERR_PARSE; + } + return PLIST_ERR_SUCCESS; +} + +PLIST_API int plist_from_openstep(const char *plist_ostep, uint32_t length, plist_t * plist) +{ + if (!plist) { + return PLIST_ERR_INVALID_ARG; + } + *plist = NULL; + if (!plist_ostep || (length == 0)) { + return PLIST_ERR_INVALID_ARG; + } + + struct _parse_ctx ctx = { plist_ostep, plist_ostep, plist_ostep + length, 0 }; + + int err = node_from_openstep(&ctx, plist); + if (err == 0) { + if (!*plist) { + /* whitespace only file is considered an empty dictionary */ + *plist = plist_new_dict(); + } else if (ctx.pos < ctx.end && *ctx.pos == '=') { + /* attempt to parse this as 'strings' data */ + plist_free(*plist); + plist_t pl = plist_new_dict(); + ctx.pos = plist_ostep; + parse_dict_data(&ctx, pl); + if (ctx.err > 0) { + plist_free(pl); + PLIST_OSTEP_ERR("Failed to parse strings data\n"); + err = PLIST_ERR_PARSE; + } else { + *plist = pl; + } + } + } + + return err; +} diff --git a/src/plist.c b/src/plist.c index 37edfa4..e696f70 100644 --- a/src/plist.c +++ b/src/plist.c @@ -34,6 +34,7 @@ #include #include #include +#include #ifdef WIN32 #include @@ -51,12 +52,15 @@ extern void plist_bin_init(void); extern void plist_bin_deinit(void); extern void plist_json_init(void); extern void plist_json_deinit(void); +extern void plist_ostep_init(void); +extern void plist_ostep_deinit(void); static void internal_plist_init(void) { plist_bin_init(); plist_xml_init(); plist_json_init(); + plist_ostep_init(); } static void internal_plist_deinit(void) @@ -64,6 +68,7 @@ static void internal_plist_deinit(void) plist_bin_deinit(); plist_xml_deinit(); plist_json_deinit(); + plist_ostep_deinit(); } #ifdef WIN32 @@ -186,6 +191,10 @@ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length) return (memcmp(plist_data, "bplist00", 8) == 0); } +#define SKIP_WS(blob, pos, len) \ + while (pos < len && ((blob[pos] == ' ') || (blob[pos] == '\t') || (blob[pos] == '\r') || (blob[pos] == '\n'))) pos++; +#define FIND_NEXT(blob, pos, len, chr) \ + while (pos < len && (blob[pos] != chr)) pos++; PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist) { @@ -194,19 +203,54 @@ PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, return PLIST_ERR_INVALID_ARG; } *plist = NULL; - if (!plist_data || length < 8) { + if (!plist_data || length == 0) { return PLIST_ERR_INVALID_ARG; } if (plist_is_binary(plist_data, length)) { res = plist_from_bin(plist_data, length, plist); } else { - /* skip whitespace before checking */ uint32_t pos = 0; - while (pos < length && ((plist_data[pos] == ' ') || (plist_data[pos] == '\t') || (plist_data[pos] == '\r') || (plist_data[pos] == '\n'))) pos++; - if (plist_data[pos] == '[' || plist_data[pos] == '{') { + int is_json = 0; + int is_xml = 0; + /* skip whitespace */ + SKIP_WS(plist_data, pos, length); + if (plist_data[pos] == '<' && (length-pos > 3) && !isxdigit(plist_data[pos+1]) && !isxdigit(plist_data[pos+2]) && !isxdigit(plist_data[pos+3])) { + is_xml = 1; + } else if (plist_data[pos] == '[') { + /* only valid for json */ + is_json = 1; + } else if (plist_data[pos] == '(') { + /* only valid for openstep */ + } else if (plist_data[pos] == '{') { + /* this could be json or openstep */ + pos++; + SKIP_WS(plist_data, pos, length); + if (plist_data[pos] == '"') { + /* still could be both */ + pos++; + do { + FIND_NEXT(plist_data, pos, length, '"'); + if (plist_data[pos-1] != '\\') { + break; + } + pos++; + } while (pos < length); + if (plist_data[pos] == '"') { + pos++; + SKIP_WS(plist_data, pos, length); + if (plist_data[pos] == ':') { + /* this is definitely json */ + is_json = 1; + } + } + } + } + if (is_xml) { + res = plist_from_xml(plist_data, length, plist); + } else if (is_json) { res = plist_from_json(plist_data, length, plist); } else { - res = plist_from_xml(plist_data, length, plist); + res = plist_from_openstep(plist_data, length, plist); } } return res; diff --git a/test/Makefile.am b/test/Makefile.am index cd3b940..66543ea 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -9,7 +9,8 @@ noinst_PROGRAMS = \ plist_cmp \ plist_test \ plist_btest \ - plist_jtest + plist_jtest \ + plist_otest plist_cmp_SOURCES = plist_cmp.c plist_cmp_LDADD = \ @@ -25,6 +26,9 @@ 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 +plist_otest_SOURCES = plist_otest.c +plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la + TESTS = \ empty.test \ small.test \ @@ -54,7 +58,12 @@ TESTS = \ json2.test \ json3.test \ json-invalid-types.test \ - json-int64-min-max.test + json-int64-min-max.test \ + ostep1.test \ + ostep2.test \ + ostep-strings.test \ + ostep-comments.test \ + ostep-invalid-types.test EXTRA_DIST = \ $(TESTS) \ @@ -102,7 +111,10 @@ EXTRA_DIST = \ data/data.bplist \ data/j1.json \ data/j2.json \ - data/int64_min_max.json + data/int64_min_max.json \ + data/o1.ostep \ + data/o2.ostep \ + data/test.strings TESTS_ENVIRONMENT = \ top_srcdir=$(top_srcdir) \ diff --git a/test/data/o1.ostep b/test/data/o1.ostep new file mode 100644 index 0000000..074406a --- /dev/null +++ b/test/data/o1.ostep @@ -0,0 +1,45 @@ +{ + "test" = (1,1); + foo = ( + (-1337), + (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" = ( + { + "cd" = -0.25; + } + ); + } + ); + } + ); + }; +} diff --git a/test/data/o2.ostep b/test/data/o2.ostep new file mode 100644 index 0000000..5f5f3c2 --- /dev/null +++ b/test/data/o2.ostep @@ -0,0 +1,17 @@ +{ + "Some ASCII string" = "Test ASCII String"; + "Some UTF8 strings" = ( + "àéèçù", + "日本語", + "汉语/漢語", + "한국어/조선말", + "русский язык", + "الْعَرَبيّة", + "עִבְרִית", + "język polski", + "हिन्दी", + ); + "Keys & \"entities\"" = "hello world & others are fun!?'"; + "Some Int" = 32434543632; + "Some String with Unicode entity" = "Yeah check this: \U1234 !!!"; +} diff --git a/test/data/o3.ostep b/test/data/o3.ostep new file mode 100644 index 0000000..b80444d --- /dev/null +++ b/test/data/o3.ostep @@ -0,0 +1,16 @@ +( + { + AFirstKey = "A First Value"; + ASecondKey = "A Second Value"; + // this is the last entry + }, + /*{ + BFirstKey = "B First Value"; + BSecondKey = "B Second Value"; + },*/ + { + CFirstKey = "C First Value"; // "C First Unused Value"; + // now here is another comment + CSecondKey = /* "C Second Value";*/ "C Second Corrected Value"; + } +) diff --git a/test/data/test.strings b/test/data/test.strings new file mode 100644 index 0000000..6d6ee43 --- /dev/null +++ b/test/data/test.strings @@ -0,0 +1,12 @@ +STRINGS_ENTRY = "Whatever"; +FOO = "BAR"; +BAR = Foo; +ENTRY0 = "àéèçù"; +ENTRY1 = "日本語"; +ENTRY2 = "汉语/漢語"; +ENTRY3 = "한국어/조선말"; +ENTRY4 = "русский язык"; +ENTRY5 = "الْعَرَبيّة"; +ENTRY6 = "עִבְרִית"; +ENTRY7 = "język polski"; +ENTRY8 = "हिन्दी"; diff --git a/test/ostep-comments.test b/test/ostep-comments.test new file mode 100755 index 0000000..8f7f629 --- /dev/null +++ b/test/ostep-comments.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o3.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out diff --git a/test/ostep-invalid-types.test b/test/ostep-invalid-types.test new file mode 100755 index 0000000..9222394 --- /dev/null +++ b/test/ostep-invalid-types.test @@ -0,0 +1,33 @@ +## -*- 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_OSTEP_DEBUG=1 + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE0 -o /dev/null +if [ $? -neq 2 ]; then + exit 1 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null +if [ $? -neq 2 ]; then + exit 2 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null +if [ $? -neq 2 ]; then + exit 3 +fi + +exit 0 diff --git a/test/ostep-strings.test b/test/ostep-strings.test new file mode 100755 index 0000000..5b0a098 --- /dev/null +++ b/test/ostep-strings.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=test.strings + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out diff --git a/test/ostep1.test b/test/ostep1.test new file mode 100755 index 0000000..f0d9b51 --- /dev/null +++ b/test/ostep1.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o1.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out diff --git a/test/ostep2.test b/test/ostep2.test new file mode 100755 index 0000000..1b991c3 --- /dev/null +++ b/test/ostep2.test @@ -0,0 +1,19 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o2.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OTEST_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out diff --git a/test/plist_cmp.c b/test/plist_cmp.c index 2af3f63..1b4a36a 100644 --- a/test/plist_cmp.c +++ b/test/plist_cmp.c @@ -127,19 +127,8 @@ int main(int argc, char *argv[]) plist_1[size_in1] = '\0'; plist_2[size_in2] = '\0'; - 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); + plist_from_memory(plist_1, size_in1, &root_node1); + plist_from_memory(plist_2, size_in2, &root_node2); if (!root_node1 || !root_node2) { diff --git a/test/plist_otest.c b/test/plist_otest.c new file mode 100644 index 0000000..14168f8 --- /dev/null +++ b/test/plist_otest.c @@ -0,0 +1,130 @@ +/* + * plist_otest.c + * source libplist regression test + * + * Copyright (c) 2022 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 + */ + + +#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_ostep = NULL; + char *plist_ostep2 = 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; + 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_ostep = (char *) malloc(sizeof(char) * (size_in + 1)); + fread(plist_ostep, sizeof(char), size_in, iplist); + fclose(iplist); + plist_ostep[size_in] = 0; + + //convert one format to another + plist_from_openstep(plist_ostep, size_in, &root_node1); + if (!root_node1) + { + printf("OpenStep PList parsing failed\n"); + return 3; + } + + printf("OpenStep PList 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_openstep(root_node2, &plist_ostep2, &size_out2, 0); + if (!plist_ostep2) + { + printf("OpenStep PList writing failed\n"); + return 8; + } + + printf("OpenStep PList writing succeeded\n"); + if (plist_ostep2) + { + FILE *oplist = NULL; + oplist = fopen(file_out, "wb"); + fwrite(plist_ostep2, size_out2, sizeof(char), oplist); + fclose(oplist); + } + + plist_free(root_node1); + plist_free(root_node2); + free(plist_bin); + free(plist_ostep); + free(plist_ostep2); + + 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/tools/plistutil.c b/tools/plistutil.c index 677e432..6254b7c 100644 --- a/tools/plistutil.c +++ b/tools/plistutil.c @@ -41,7 +41,9 @@ typedef struct _options { char *in_file, *out_file; - uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json + uint8_t debug; + uint8_t compact; + uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep } options_t; static void print_usage(int argc, char *argv[]) @@ -50,17 +52,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 between binary, XML, and JSON format.\n"); + printf("Convert a plist FILE between binary, XML, JSON, and OpenStep 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("To convert to/from JSON or OpenStep 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 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(" FORMAT is one of xml, bin, json, or openstep\n"); + printf(" If omitted, XML will be converted to binary,\n"); printf(" and binary to XML.\n"); + printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n"); + printf(" By default, the output will be pretty-printed.\n"); printf(" -d, --debug Enable extended debug output\n"); printf(" -v, --version Print version information\n"); printf("\n"); @@ -112,6 +116,8 @@ static options_t *parse_arguments(int argc, char *argv[]) options->out_fmt = 2; } else if (!strncmp(argv[i+1], "json", 4)) { options->out_fmt = 3; + } else if (!strncmp(argv[i+1], "openstep", 8) || !strncmp(argv[i+1], "ostep", 5)) { + options->out_fmt = 4; } else { fprintf(stderr, "ERROR: Unsupported output format\n"); free(options); @@ -120,6 +126,10 @@ static options_t *parse_arguments(int argc, char *argv[]) i++; continue; } + else if (!strcmp(argv[i], "--compact") || !strcmp(argv[i], "-c")) + { + options->compact = 1; + } else if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d")) { options->debug = 1; @@ -216,13 +226,6 @@ int main(int argc, char *argv[]) free(options); return 1; } - - if (read_size < 8) { - fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n"); - free(plist_entire); - free(options); - return 1; - } } else { @@ -237,13 +240,6 @@ int main(int argc, char *argv[]) memset(&filestats, '\0', sizeof(struct stat)); fstat(fileno(iplist), &filestats); - if (filestats.st_size < 8) { - fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n"); - free(options); - fclose(iplist); - return -1; - } - plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1)); read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist); plist_entire[read_size] = '\0'; @@ -276,7 +272,9 @@ int main(int argc, char *argv[]) } else if (options->out_fmt == 2) { output_res = plist_to_xml(root_node, &plist_out, &size); } else if (options->out_fmt == 3) { - output_res = plist_to_json(root_node, &plist_out, &size, 0); + output_res = plist_to_json(root_node, &plist_out, &size, !options->compact); + } else if (options->out_fmt == 4) { + output_res = plist_to_openstep(root_node, &plist_out, &size, !options->compact); } } } @@ -316,8 +314,20 @@ int main(int argc, char *argv[]) ret = 1; } } else { - fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); - ret = 1; + switch (input_res) { + case PLIST_ERR_PARSE: + if (options->out_fmt == 0) { + fprintf(stderr, "ERROR: Could not parse plist data, expected XML or binary plist\n"); + } else { + fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); + } + ret = 3; + break; + default: + fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); + ret = 1; + break; + } } free(options); -- cgit v1.1-32-gdbae