diff options
Diffstat (limited to 'src/xplist.c')
| -rw-r--r-- | src/xplist.c | 1740 |
1 files changed, 1434 insertions, 306 deletions
diff --git a/src/xplist.c b/src/xplist.c index ba312a1..b2c134e 100644 --- a/src/xplist.c +++ b/src/xplist.c @@ -1,7 +1,9 @@ /* - * plist.c + * xplist.c * XML plist implementation * + * Copyright (c) 2010-2017 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Jonathan Beck All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -19,6 +21,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STRPTIME +#define _XOPEN_SOURCE 600 +#endif #include <string.h> #include <assert.h> @@ -27,437 +36,1556 @@ #include <time.h> #include <inttypes.h> -#include <locale.h> - -#include <libxml/parser.h> -#include <libxml/tree.h> +#include <float.h> +#include <math.h> +#include <limits.h> +#include <errno.h> #include <node.h> -#include <node_list.h> -#include <node_iterator.h> #include "plist.h" #include "base64.h" - -#define XPLIST_TEXT BAD_CAST("text") -#define XPLIST_KEY BAD_CAST("key") -#define XPLIST_FALSE BAD_CAST("false") -#define XPLIST_TRUE BAD_CAST("true") -#define XPLIST_INT BAD_CAST("integer") -#define XPLIST_REAL BAD_CAST("real") -#define XPLIST_DATE BAD_CAST("date") -#define XPLIST_DATA BAD_CAST("data") -#define XPLIST_STRING BAD_CAST("string") -#define XPLIST_ARRAY BAD_CAST("array") -#define XPLIST_DICT BAD_CAST("dict") - -static const char *plist_base = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\ +#include "strbuf.h" +#include "time64.h" +#include "hashtable.h" +#include "common.h" + +#define XPLIST_KEY "key" +#define XPLIST_KEY_LEN 3 +#define XPLIST_FALSE "false" +#define XPLIST_FALSE_LEN 5 +#define XPLIST_TRUE "true" +#define XPLIST_TRUE_LEN 4 +#define XPLIST_INT "integer" +#define XPLIST_INT_LEN 7 +#define XPLIST_REAL "real" +#define XPLIST_REAL_LEN 4 +#define XPLIST_DATE "date" +#define XPLIST_DATE_LEN 4 +#define XPLIST_DATA "data" +#define XPLIST_DATA_LEN 4 +#define XPLIST_STRING "string" +#define XPLIST_STRING_LEN 6 +#define XPLIST_ARRAY "array" +#define XPLIST_ARRAY_LEN 5 +#define XPLIST_DICT "dict" +#define XPLIST_DICT_LEN 4 + +#define MAX_DATA_BYTES_PER_LINE(__i) (((76 - ((__i) << 3)) >> 2) * 3) + +static const char XML_PLIST_PROLOG[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\ <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\ -<plist version=\"1.0\">\n\ -</plist>\0"; +<plist version=\"1.0\">\n"; +static const char XML_PLIST_EPILOG[] = "</plist>\n"; +#ifdef DEBUG +static int plist_xml_debug = 0; +#define PLIST_XML_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlparser] ERROR: " __VA_ARGS__); } +#define PLIST_XML_WRITE_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlwriter] ERROR: " __VA_ARGS__); } +#else +#define PLIST_XML_ERR(...) +#define PLIST_XML_WRITE_ERR(...) +#endif -/** Formats a block of text to be a given indentation and width. - * - * The total width of the return string will be depth + cols. - * - * @param buf The string to format. - * @param cols The number of text columns for returned block of text. - * @param depth The number of tabs to indent the returned block of text. - * - * @return The formatted string. - */ -static char *format_string(const char *buf, size_t len, int cols, int depth) +void plist_xml_init(void) { - if (!buf || !(len > 0)) return NULL; - int colw = depth + cols + 1; - int nlines = len / cols + 1; - char *new_buf = NULL; - int i = 0; - int j = 0; - - assert(cols >= 0); - assert(depth >= 0); - - new_buf = (char*) malloc(nlines * colw + depth + 1); - assert(new_buf != 0); - memset(new_buf, 0, nlines * colw + depth + 1); - - // Inserts new lines and tabs at appropriate locations - for (i = 0; i < nlines; i++) - { - new_buf[i * colw] = '\n'; - for (j = 0; j < depth; j++) - new_buf[i * colw + 1 + j] = '\t'; - memcpy(new_buf + i * colw + 1 + depth, buf + i * cols, (i + 1) * cols <= len ? cols : len - i * cols); + /* init XML stuff */ +#ifdef DEBUG + char *env_debug = getenv("PLIST_XML_DEBUG"); + if (env_debug && !strcmp(env_debug, "1")) { + plist_xml_debug = 1; } - new_buf[len + (1 + depth) * nlines] = '\n'; - - // Inserts final row of indentation and termination character - for (j = 0; j < depth; j++) - new_buf[len + (1 + depth) * nlines + 1 + j] = '\t'; - new_buf[len + (1 + depth) * nlines + depth + 1] = '\0'; - - return new_buf; +#endif } - - -struct xml_node +void plist_xml_deinit(void) { - xmlNodePtr xml; - uint32_t depth; -}; + /* deinit XML stuff */ +} -/** Creates a new plist XML document. - * - * @return The plist XML document. - */ -static xmlDocPtr new_xml_plist(void) +void plist_xml_set_debug(int debug) { - char *plist = strdup(plist_base); - xmlDocPtr plist_xml = xmlParseMemory(plist, strlen(plist)); - - if (!plist_xml) - return NULL; - - free(plist); - - return plist_xml; +#if DEBUG + plist_xml_debug = debug; +#endif } -static void node_to_xml(node_t* node, void *xml_struct) +static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth) { - struct xml_node *xstruct = NULL; plist_data_t node_data = NULL; - xmlNodePtr child_node = NULL; char isStruct = FALSE; + char tagOpen = FALSE; - const xmlChar *tag = NULL; - char *val = NULL; - - //for base64 - char *valtmp = NULL; + const char *tag = NULL; + size_t tag_len = 0; + char val[64] = { 0 }; + size_t val_len = 0; uint32_t i = 0; - if (!node) - return; + if (!node) { + PLIST_XML_WRITE_ERR("Encountered invalid empty node in property list\n"); + return PLIST_ERR_INVALID_ARG; + } - xstruct = (struct xml_node *) xml_struct; node_data = plist_get_data(node); switch (node_data->type) { case PLIST_BOOLEAN: { - if (node_data->boolval) + if (node_data->boolval) { tag = XPLIST_TRUE; - else + tag_len = XPLIST_TRUE_LEN; + } else { tag = XPLIST_FALSE; + tag_len = XPLIST_FALSE_LEN; + } } break; - case PLIST_UINT: + case PLIST_INT: tag = XPLIST_INT; - val = (char*)malloc(64); - (void)snprintf(val, 64, "%"PRIu64, node_data->intval); + tag_len = XPLIST_INT_LEN; + if (node_data->length == 16) { + val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval); + } else { + val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval); + } break; case PLIST_REAL: tag = XPLIST_REAL; - val = (char*)malloc(64); - (void)snprintf(val, 64, "%f", node_data->realval); + tag_len = XPLIST_REAL_LEN; + val_len = dtostr(val, sizeof(val), node_data->realval); break; case PLIST_STRING: tag = XPLIST_STRING; - val = strdup(node_data->strval); + tag_len = XPLIST_STRING_LEN; + /* contents processed directly below */ break; case PLIST_KEY: tag = XPLIST_KEY; - val = strdup((char*) node_data->strval); + tag_len = XPLIST_KEY_LEN; + /* contents processed directly below */ break; case PLIST_DATA: tag = XPLIST_DATA; - if (node_data->length) - { - size_t len = node_data->length; - valtmp = base64encode(node_data->buff, &len); - val = format_string(valtmp, len, 68, xstruct->depth); - free(valtmp); - } + tag_len = XPLIST_DATA_LEN; + /* contents processed directly below */ break; case PLIST_ARRAY: tag = XPLIST_ARRAY; - isStruct = TRUE; + tag_len = XPLIST_ARRAY_LEN; + isStruct = (node->children) ? TRUE : FALSE; break; case PLIST_DICT: tag = XPLIST_DICT; - isStruct = TRUE; + tag_len = XPLIST_DICT_LEN; + isStruct = (node->children) ? TRUE : FALSE; break; case PLIST_DATE: tag = XPLIST_DATE; + tag_len = XPLIST_DATE_LEN; { - time_t time = (time_t)node_data->timeval.tv_sec; - struct tm *btime = localtime(&time); + Time64_T timev; + if (plist_real_to_time64(node_data->realval, &timev) < 0) { + PLIST_XML_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval); + return PLIST_ERR_INVALID_ARG; + } + struct TM _btime; + struct TM *btime = gmtime64_r(&timev, &_btime); if (btime) { - val = (char*)malloc(24); - memset(val, 0, 24); - if (strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", btime) <= 0) { - free (val); - val = NULL; + struct tm _tmcopy; + copy_TM64_to_tm(btime, &_tmcopy); + val_len = strftime(val, sizeof(val), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy); + if (val_len <= 0) { + snprintf(val, sizeof(val), "1970-01-01T00:00:00Z"); } } } break; - default: + case PLIST_UID: + tag = XPLIST_DICT; + tag_len = XPLIST_DICT_LEN; + if (node_data->length == 16) { + val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval); + } else { + val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval); + } break; + case PLIST_NULL: + PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n"); + return PLIST_ERR_FORMAT; + default: + return PLIST_ERR_UNKNOWN; } - for (i = 0; i < xstruct->depth; i++) - { - xmlNodeAddContent(xstruct->xml, BAD_CAST("\t")); + for (i = 0; i < depth; i++) { + str_buf_append(*outbuf, "\t", 1); } - if (node_data->type == PLIST_STRING) { + + /* append tag */ + str_buf_append(*outbuf, "<", 1); + str_buf_append(*outbuf, tag, tag_len); + if ((node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) && node_data->length > 0) { + size_t j; + size_t len; + off_t start = 0; + off_t cur = 0; + + str_buf_append(*outbuf, ">", 1); + tagOpen = TRUE; + /* make sure we convert the following predefined xml entities */ - /* < = < > = > ' = ' " = " & = & */ - child_node = xmlNewTextChild(xstruct->xml, NULL, tag, BAD_CAST(val)); - } else - child_node = xmlNewChild(xstruct->xml, NULL, tag, BAD_CAST(val)); - xmlNodeAddContent(xstruct->xml, BAD_CAST("\n")); - if (val) { - free(val); - } + /* < = < > = > & = & */ + 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, "<", 4); + start = cur+1; + break; + case '>': + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, ">", 4); + start = cur+1; + break; + case '&': + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, "&", 5); + start = cur+1; + break; + default: + break; + } + cur++; + } + str_buf_append(*outbuf, node_data->strval + start, cur - start); + } else if (node_data->type == PLIST_DATA) { + str_buf_append(*outbuf, ">", 1); + tagOpen = TRUE; + str_buf_append(*outbuf, "\n", 1); + if (node_data->length > 0) { + uint32_t j = 0; + uint32_t indent = (depth > 8) ? 8 : depth; + uint32_t maxread = MAX_DATA_BYTES_PER_LINE(indent); + size_t count = 0; + size_t amount = (node_data->length / 3 * 4) + 4 + (((node_data->length / maxread) + 1) * (indent+1)); + if ((*outbuf)->len + amount > (*outbuf)->capacity) { + str_buf_grow(*outbuf, amount); + } + while (j < node_data->length) { + for (i = 0; i < indent; i++) { + str_buf_append(*outbuf, "\t", 1); + } + count = (node_data->length-j < maxread) ? node_data->length-j : maxread; + assert((*outbuf)->len + count < (*outbuf)->capacity); + (*outbuf)->len += base64encode((char*)(*outbuf)->data + (*outbuf)->len, node_data->buff + j, count); + str_buf_append(*outbuf, "\n", 1); + j+=count; + } + } + for (i = 0; i < depth; i++) { + str_buf_append(*outbuf, "\t", 1); + } + } else if (node_data->type == PLIST_UID) { + /* special case for UID nodes: create a DICT */ + str_buf_append(*outbuf, ">", 1); + tagOpen = TRUE; + str_buf_append(*outbuf, "\n", 1); + + /* add CF$UID key */ + for (i = 0; i < depth+1; i++) { + str_buf_append(*outbuf, "\t", 1); + } + str_buf_append(*outbuf, "<key>CF$UID</key>", 17); + str_buf_append(*outbuf, "\n", 1); - //add return for structured types - if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT) - xmlNodeAddContent(child_node, BAD_CAST("\n")); + /* add UID value */ + for (i = 0; i < depth+1; i++) { + str_buf_append(*outbuf, "\t", 1); + } + str_buf_append(*outbuf, "<integer>", 9); + str_buf_append(*outbuf, val, val_len); + str_buf_append(*outbuf, "</integer>", 10); + str_buf_append(*outbuf, "\n", 1); - if (isStruct) - { - struct xml_node child = { child_node, xstruct->depth + 1 }; - node_iterator_t *ni = node_iterator_create(node->children); - node_t *ch; - while ((ch = node_iterator_next(ni))) { - node_to_xml(ch, &child); + for (i = 0; i < depth; i++) { + str_buf_append(*outbuf, "\t", 1); } - node_iterator_destroy(ni); + } else if (val_len > 0) { + str_buf_append(*outbuf, ">", 1); + tagOpen = TRUE; + str_buf_append(*outbuf, val, val_len); + } else if (isStruct) { + tagOpen = TRUE; + str_buf_append(*outbuf, ">", 1); + } else { + tagOpen = FALSE; + str_buf_append(*outbuf, "/>", 2); } - //fix indent for structured types - if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT) - { - for (i = 0; i < xstruct->depth; i++) - { - xmlNodeAddContent(child_node, BAD_CAST("\t")); + if (isStruct) { + /* add newline for structured types */ + str_buf_append(*outbuf, "\n", 1); + + /* add child nodes */ + if (node_data->type == PLIST_DICT && node->children) { + assert((node->children->count % 2) == 0); + } + node_t ch; + for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { + plist_err_t res = node_to_xml(ch, outbuf, depth+1); + if (res < 0) return res; + } + + /* fix indent for structured types */ + for (i = 0; i < depth; i++) { + str_buf_append(*outbuf, "\t", 1); } } - return; + if (tagOpen) { + /* add closing tag */ + str_buf_append(*outbuf, "</", 2); + str_buf_append(*outbuf, tag, tag_len); + str_buf_append(*outbuf, ">", 1); + } + str_buf_append(*outbuf, "\n", 1); + return PLIST_ERR_SUCCESS; } -static void parse_date(const char *strval, struct tm *btime) +static int parse_date(const char *strval, struct TM *btime) { - if (!btime) return; - memset(btime, 0, sizeof(struct tm)); - if (!strval) return; -#ifdef strptime - strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime); + if (!btime) return -1; + memset(btime, 0, sizeof(*btime)); + if (!strval) return -1; +#ifdef HAVE_STRPTIME +#ifdef USE_TM64 + struct tm t = { 0 }; + char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", &t); + if (!r || *r != '\0') { + return -1; + } + copy_tm_to_TM64(&t, btime); +#else + char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime); + if (!r || *r != '\0') { + return -1; + } +#endif #else - sscanf(strval, "%d-%d-%dT%d:%d:%dZ", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec); +#ifdef USE_TM64 + #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ" +#else + #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ" +#endif + int n = 0; + if (sscanf(strval, PLIST_SSCANF_FORMAT "%n", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec, &n) != 6) return -1; + if (strval[n] != '\0') return -1; + if (btime->tm_mon < 1 || btime->tm_mon > 12) return -1; + if (btime->tm_mday < 1 || btime->tm_mday > 31) return -1; + if (btime->tm_hour < 0 || btime->tm_hour > 23) return -1; + if (btime->tm_min < 0 || btime->tm_min > 59) return -1; + if (btime->tm_sec < 0 || btime->tm_sec > 59) return -1; btime->tm_year-=1900; btime->tm_mon--; #endif + btime->tm_isdst=0; + return 0; } -static void xml_to_node(xmlNodePtr xml_node, plist_t * plist_node) +static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited) { - xmlNodePtr node = NULL; - plist_data_t data = NULL; - plist_t subnode = NULL; + plist_data_t data; + if (!node) { + return PLIST_ERR_INVALID_ARG; + } - //for string - long len = 0; - int type = 0; + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_XML_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + return PLIST_ERR_MAX_NESTING; + } - if (!xml_node) - return; + if (hash_table_lookup(visited, node)) { + PLIST_XML_WRITE_ERR("circular reference detected\n"); + return PLIST_ERR_CIRCULAR_REF; + } - for (node = xml_node->children; node; node = node->next) - { + // mark as visited + hash_table_insert(visited, node, (void*)1); - while (node && !xmlStrcmp(node->name, XPLIST_TEXT)) - node = node->next; - if (!node) + data = plist_get_data(node); + if (node->children) { + node_t ch; + for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { + plist_err_t err = _node_estimate_size(ch, size, depth + 1, visited); + if (err != PLIST_ERR_SUCCESS) { + return err; + } + } + switch (data->type) { + case PLIST_DICT: + *size += (XPLIST_DICT_LEN << 1) + 7; + break; + case PLIST_ARRAY: + *size += (XPLIST_ARRAY_LEN << 1) + 7; + break; + default: + break; + } + *size += (depth << 1); + } else { + uint32_t indent = (depth > 8) ? 8 : depth; + switch (data->type) { + case PLIST_DATA: { + uint32_t req_lines = (data->length / MAX_DATA_BYTES_PER_LINE(indent)) + 1; + uint32_t b64len = data->length + (data->length / 3); + b64len += b64len % 4; + *size += b64len; + *size += (XPLIST_DATA_LEN << 1) + 5 + (indent+1) * (req_lines+1) + 1; + } break; + case PLIST_STRING: + *size += data->length; + *size += (XPLIST_STRING_LEN << 1) + 6; + break; + case PLIST_KEY: + *size += data->length; + *size += (XPLIST_KEY_LEN << 1) + 6; + break; + case PLIST_INT: + if (data->length == 16) { + *size += num_digits_u(data->intval); + } else { + *size += num_digits_i((int64_t)data->intval); + } + *size += (XPLIST_INT_LEN << 1) + 6; + break; + case PLIST_REAL: + *size += dtostr(NULL, 0, data->realval); + *size += (XPLIST_REAL_LEN << 1) + 6; + break; + case PLIST_DATE: + *size += 20; /* YYYY-MM-DDThh:mm:ssZ */ + *size += (XPLIST_DATE_LEN << 1) + 6; + break; + case PLIST_BOOLEAN: + *size += ((data->boolval) ? XPLIST_TRUE_LEN : XPLIST_FALSE_LEN) + 4; + break; + case PLIST_DICT: + *size += XPLIST_DICT_LEN + 4; /* <dict/> */ break; + case PLIST_ARRAY: + *size += XPLIST_ARRAY_LEN + 4; /* <array/> */ + break; + case PLIST_UID: + *size += num_digits_i((int64_t)data->intval); + *size += (XPLIST_DICT_LEN << 1) + 7; + *size += indent + ((indent+1) << 1); + *size += 18; /* <key>CF$UID</key> */ + *size += (XPLIST_INT_LEN << 1) + 6; + break; + case PLIST_NULL: + PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n"); + return PLIST_ERR_FORMAT; + default: + PLIST_XML_WRITE_ERR("invalid node type encountered\n"); + return PLIST_ERR_UNKNOWN; + } + *size += indent; + } + return PLIST_ERR_SUCCESS; +} - data = plist_new_plist_data(); - subnode = plist_new_node(data); - if (*plist_node) - node_attach(*plist_node, subnode); - else - *plist_node = subnode; +static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth) +{ + hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL); + if (!visited) return PLIST_ERR_NO_MEM; + plist_err_t err = _node_estimate_size(node, size, depth, visited); + hash_table_destroy(visited); + return err; +} - if (!xmlStrcmp(node->name, XPLIST_TRUE)) - { - data->boolval = TRUE; - data->type = PLIST_BOOLEAN; - data->length = 1; - continue; - } +plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length) +{ + uint64_t size = 0; + plist_err_t res; - if (!xmlStrcmp(node->name, XPLIST_FALSE)) - { - data->boolval = FALSE; - data->type = PLIST_BOOLEAN; - data->length = 1; - continue; - } + if (!plist || !plist_xml || !length) { + return PLIST_ERR_INVALID_ARG; + } - if (!xmlStrcmp(node->name, XPLIST_INT)) - { - xmlChar *strval = xmlNodeGetContent(node); - data->intval = strtoull((char*)strval, NULL, 0); - data->type = PLIST_UINT; - data->length = 8; - xmlFree(strval); - continue; + res = node_estimate_size((node_t)plist, &size, 0); + if (res < 0) { + return res; + } + size += sizeof(XML_PLIST_PROLOG) + sizeof(XML_PLIST_EPILOG) - 1; + + strbuf_t *outbuf = str_buf_new(size); + if (!outbuf) { + PLIST_XML_WRITE_ERR("Could not allocate output buffer\n"); + return PLIST_ERR_NO_MEM; + } + + str_buf_append(outbuf, XML_PLIST_PROLOG, sizeof(XML_PLIST_PROLOG)-1); + + res = node_to_xml((node_t)plist, &outbuf, 0); + if (res < 0) { + str_buf_free(outbuf); + *plist_xml = NULL; + *length = 0; + return res; + } + + str_buf_append(outbuf, XML_PLIST_EPILOG, sizeof(XML_PLIST_EPILOG)); + + *plist_xml = (char*)outbuf->data; + *length = outbuf->len - 1; + + outbuf->data = NULL; + str_buf_free(outbuf); + + return PLIST_ERR_SUCCESS; +} + +struct _parse_ctx { + const char *pos; + const char *end; + plist_err_t err; +}; +typedef struct _parse_ctx* parse_ctx; + +static inline int is_xml_ws(unsigned char c) +{ + return (c == ' ' || c == '\t' || c == '\r' || c == '\n'); +} + +static void parse_skip_ws(parse_ctx ctx) +{ + while (ctx->pos < ctx->end && is_xml_ws(*(ctx->pos))) { + ctx->pos++; + } +} + +static void find_char(parse_ctx ctx, char c, int skip_quotes) +{ + while (ctx->pos < ctx->end && (*(ctx->pos) != c)) { + if (skip_quotes && (c != '"') && (*(ctx->pos) == '"')) { + ctx->pos++; + find_char(ctx, '"', 0); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while looking for matching double quote\n"); + return; + } + if (*(ctx->pos) != '"') { + PLIST_XML_ERR("Unmatched double quote\n"); + return; + } } + ctx->pos++; + } +} - if (!xmlStrcmp(node->name, XPLIST_REAL)) - { - xmlChar *strval = xmlNodeGetContent(node); - data->realval = atof((char *) strval); - data->type = PLIST_REAL; - data->length = 8; - xmlFree(strval); - continue; +static void find_str(parse_ctx ctx, const char *str, size_t len, int skip_quotes) +{ + while (ctx->pos < (ctx->end - len)) { + if (!strncmp(ctx->pos, str, len)) { + break; } + if (skip_quotes && (*(ctx->pos) == '"')) { + ctx->pos++; + find_char(ctx, '"', 0); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while looking for matching double quote\n"); + return; + } + if (*(ctx->pos) != '"') { + PLIST_XML_ERR("Unmatched double quote\n"); + return; + } + } + ctx->pos++; + } +} - if (!xmlStrcmp(node->name, XPLIST_DATE)) - { - xmlChar *strval = xmlNodeGetContent(node); - time_t time = 0; - if (strlen((const char*)strval) >= 11) { - struct tm btime; - parse_date((const char*)strval, &btime); - time = mktime(&btime); - } - data->timeval.tv_sec = (long)time; - data->timeval.tv_usec = 0; - data->type = PLIST_DATE; - data->length = sizeof(struct timeval); - xmlFree(strval); - continue; +static void find_next(parse_ctx ctx, const char *nextchars, int numchars, int skip_quotes) +{ + int i = 0; + while (ctx->pos < ctx->end) { + if (skip_quotes && (*(ctx->pos) == '"')) { + ctx->pos++; + find_char(ctx, '"', 0); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while looking for matching double quote\n"); + return; + } + if (*(ctx->pos) != '"') { + PLIST_XML_ERR("Unmatched double quote\n"); + return; + } + } + for (i = 0; i < numchars; i++) { + if (*(ctx->pos) == nextchars[i]) { + return; + } } + ctx->pos++; + } +} - if (!xmlStrcmp(node->name, XPLIST_STRING)) - { - xmlChar *strval = xmlNodeGetContent(node); - len = strlen((char *) strval); - type = xmlDetectCharEncoding(strval, len); +typedef struct { + const char *begin; + size_t length; + int is_cdata; + void *next; +} text_part_t; - if (XML_CHAR_ENCODING_UTF8 == type || XML_CHAR_ENCODING_ASCII == type || XML_CHAR_ENCODING_NONE == type) - { - data->strval = strdup((char *) strval); - data->type = PLIST_STRING; - data->length = strlen(data->strval); +static text_part_t* text_part_init(text_part_t* part, const char *begin, size_t length, int is_cdata) +{ + part->begin = begin; + part->length = length; + part->is_cdata = is_cdata; + part->next = NULL; + return part; +} + +static void text_parts_free(text_part_t *tp) +{ + while (tp) { + text_part_t *tmp = tp; + tp = (text_part_t*)tp->next; + free(tmp); + } +} + +static text_part_t* text_part_append(text_part_t* parts, const char *begin, size_t length, int is_cdata) +{ + text_part_t* newpart = (text_part_t*)malloc(sizeof(text_part_t)); + assert(newpart); + parts->next = text_part_init(newpart, begin, length, is_cdata); + return newpart; +} + +static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_len, int skip_ws, text_part_t *parts) +{ + const char *p = NULL; + const char *q = NULL; + text_part_t *last = NULL; + + if (skip_ws) { + parse_skip_ws(ctx); + } + do { + p = ctx->pos; + find_char(ctx, '<', 0); + if (ctx->pos >= ctx->end || *ctx->pos != '<') { + PLIST_XML_ERR("EOF while looking for closing tag\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + q = ctx->pos; + ctx->pos++; + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while parsing '%s'\n", p); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + if (*ctx->pos == '!') { + ctx->pos++; + if (ctx->pos >= ctx->end-1) { + PLIST_XML_ERR("EOF while parsing <! special tag\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; } - xmlFree(strval); - continue; + if (*ctx->pos == '-' && *(ctx->pos+1) == '-') { + if (last) { + last = text_part_append(last, p, q-p, 0); + } else if (parts) { + last = text_part_init(parts, p, q-p, 0); + } + ctx->pos += 2; + find_str(ctx, "-->", 3, 0); + if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { + PLIST_XML_ERR("EOF while looking for end of comment\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + ctx->pos += 3; + } else if (*ctx->pos == '[') { + ctx->pos++; + if (ctx->pos >= ctx->end - 8) { + PLIST_XML_ERR("EOF while parsing <[ tag\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + if (strncmp(ctx->pos, "CDATA[", 6) == 0) { + if (q-p > 0) { + if (last) { + last = text_part_append(last, p, q-p, 0); + } else if (parts) { + last = text_part_init(parts, p, q-p, 0); + } + } + ctx->pos+=6; + p = ctx->pos; + find_str(ctx, "]]>", 3, 0); + if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "]]>", 3) != 0) { + PLIST_XML_ERR("EOF while looking for end of CDATA block\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + q = ctx->pos; + if (last) { + last = text_part_append(last, p, q-p, 1); + } else if (parts) { + last = text_part_init(parts, p, q-p, 1); + } + ctx->pos += 3; + } else { + p = ctx->pos; + find_next(ctx, " \r\n\t>", 5, 1); + PLIST_XML_ERR("Invalid special tag <[%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + } else { + p = ctx->pos; + find_next(ctx, " \r\n\t>", 5, 1); + PLIST_XML_ERR("Invalid special tag <!%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + } else if (*ctx->pos == '/') { + break; + } else { + p = ctx->pos; + find_next(ctx, " \r\n\t>", 5, 1); + PLIST_XML_ERR("Invalid tag <%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); + ctx->err = PLIST_ERR_PARSE; + return NULL; } + } while (1); + ctx->pos++; + if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len) != 0) { + PLIST_XML_ERR("EOF or end tag mismatch\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + ctx->pos+=tag_len; + parse_skip_ws(ctx); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while parsing closing tag\n"); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } else if (*ctx->pos != '>') { + PLIST_XML_ERR("Invalid closing tag; expected '>', found '%c'\n", *ctx->pos); + ctx->err = PLIST_ERR_PARSE; + return NULL; + } + ctx->pos++; + if (q > p) { + if (last) { + last = text_part_append(last, p, q-p, 0); + } else if (parts) { + last = text_part_init(parts, p, q-p, 0); + } + } + return parts; +} - if (!xmlStrcmp(node->name, XPLIST_KEY)) - { - xmlChar *strval = xmlNodeGetContent(node); - data->strval = strdup((char *) strval); - data->type = PLIST_KEY; - data->length = strlen(data->strval); - xmlFree(strval); - continue; +static int unescape_entities(char *str, size_t *length) +{ + size_t i = 0; + size_t len = *length; + while (len > 0 && i < len-1) { + if (str[i] == '&') { + char *entp = str + i + 1; + while (i < len && str[i] != ';') { + i++; + } + if (i >= len) { + PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n"); + return -1; + } + size_t entlen = (size_t)(str+i - entp); + if (entlen > 0) { + if (entlen > 256) { + PLIST_XML_ERR("Rejecting absurdly large entity at &%.*s...\n", 8, entp); + return -1; + } + size_t bytelen = 1; + if (entlen == 3 && memcmp(entp, "amp", 3) == 0) { + /* the '&' is already there */ + } else if (entlen == 4 && memcmp(entp, "apos", 4) == 0) { + *(entp-1) = '\''; + } else if (entlen == 4 && memcmp(entp, "quot", 4) == 0) { + *(entp-1) = '"'; + } else if (entlen == 2 && memcmp(entp, "lt", 2) == 0) { + *(entp-1) = '<'; + } else if (entlen == 2 && memcmp(entp, "gt", 2) == 0) { + *(entp-1) = '>'; + } else if (*entp == '#') { + /* numerical character reference */ + uint64_t val = 0; + char* ep = NULL; + if (entlen > 8) { + PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", (int)entlen, entp); + return -1; + } + if (entlen >= 2 && (entp[1] == 'x' || entp[1] == 'X')) { + if (entlen < 3) { + PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp); + return -1; + } + val = strtoull(entp+2, &ep, 16); + } else { + if (entlen < 2) { + PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp); + return -1; + } + val = strtoull(entp+1, &ep, 10); + } + if (val == 0 || val > 0x10FFFF || (size_t)(ep-entp) != entlen) { + PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", (int)entlen, entp); + return -1; + } + if (val >= 0xD800 && val <= 0xDFFF) { + PLIST_XML_ERR("Invalid numerical character reference found (surrogate): &%.*s;\n", (int)entlen, entp); + return -1; + } + /* convert to UTF8 */ + if (val >= 0x10000) { + /* four bytes */ + *(entp-1) = (char)(0xF0 + ((val >> 18) & 0x7)); + *(entp+0) = (char)(0x80 + ((val >> 12) & 0x3F)); + *(entp+1) = (char)(0x80 + ((val >> 6) & 0x3F)); + *(entp+2) = (char)(0x80 + (val & 0x3F)); + entp+=3; + bytelen = 4; + } else if (val >= 0x800) { + /* three bytes */ + *(entp-1) = (char)(0xE0 + ((val >> 12) & 0xF)); + *(entp+0) = (char)(0x80 + ((val >> 6) & 0x3F)); + *(entp+1) = (char)(0x80 + (val & 0x3F)); + entp+=2; + bytelen = 3; + } else if (val >= 0x80) { + /* two bytes */ + *(entp-1) = (char)(0xC0 + ((val >> 6) & 0x1F)); + *(entp+0) = (char)(0x80 + (val & 0x3F)); + entp++; + bytelen = 2; + } else { + /* one byte */ + *(entp-1) = (char)(val & 0x7F); + } + } else { + PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", (int)entlen, entp); + return -1; + } + memmove(entp, str+i+1, len - i); /* include '\0' */ + size_t dec = entlen + 1 - bytelen; + size_t shrink = entlen + 2 - bytelen; + if (i < dec || len < shrink) { + PLIST_XML_ERR("Internal error: length underflow?!\n"); + return -1; + } + i -= dec; + len -= shrink; + continue; + } else { + PLIST_XML_ERR("Invalid empty entity sequence &;\n"); + return -1; + } } + i++; + } + *length = len; + return 0; +} - if (!xmlStrcmp(node->name, XPLIST_DATA)) - { - xmlChar *strval = xmlNodeGetContent(node); - size_t size = 0; - unsigned char *dec = base64decode((char*)strval, &size); - data->buff = (uint8_t *) malloc(size * sizeof(uint8_t)); - memcpy(data->buff, dec, size * sizeof(uint8_t)); - free(dec); - data->length = size; - data->type = PLIST_DATA; - xmlFree(strval); - continue; +static char* text_parts_get_content(text_part_t *tp, int unesc_entities, int trim_ws, size_t *length, int *requires_free) +{ + char *str = NULL; + size_t total_length = 0; + + if (!tp) { + return NULL; + } + char *p; + if (requires_free && !tp->next && !unesc_entities && !trim_ws) { + *requires_free = 0; + if (length) { + *length = tp->length; } + return (char*)tp->begin; + } + text_part_t *tmp = tp; + while (tp && tp->begin) { + total_length += tp->length; + tp = (text_part_t*)tp->next; + } + str = (char*)malloc(total_length + 1); + assert(str); + p = str; + tp = tmp; + while (tp && tp->begin) { + size_t len = tp->length; + memcpy(p, tp->begin, len); + p[len] = '\0'; + if (!tp->is_cdata && unesc_entities) { + if (unescape_entities(p, &len) < 0) { + free(str); + return NULL; + } + } + p += len; + tp = (text_part_t*)tp->next; + } + *p = '\0'; + if (trim_ws) { + char* start = str; + char* end = p; + while (start < end && is_xml_ws((unsigned char)start[0])) start++; + while (end > start && is_xml_ws((unsigned char)end[-1])) end--; + if (start != str) { + size_t newlen = (size_t)(end - start); + memmove(str, start, newlen); + str[newlen] = '\0'; + p = str + newlen; + } else { + *end = '\0'; + p = end; + } + } + if (length) { + *length = p - str; + } + if (requires_free) { + *requires_free = 1; + } + return str; +} - if (!xmlStrcmp(node->name, XPLIST_ARRAY)) - { - data->type = PLIST_ARRAY; - xml_to_node(node, &subnode); - continue; +static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) +{ + char tag[16] = { 0 }; + char *keyname = NULL; + plist_t subnode = NULL; + const char *p = NULL; + plist_t parent = NULL; + + struct node_path_item { + const char *type; + struct node_path_item *prev; + }; + struct node_path_item* node_path = NULL; + int depth = 0; + + while (ctx->pos < ctx->end && !ctx->err) { + parse_skip_ws(ctx); + if (ctx->pos >= ctx->end) { + break; + } + if (*ctx->pos != '<') { + p = ctx->pos; + find_next(ctx, " \t\r\n", 4, 0); + PLIST_XML_ERR("Expected: opening tag, found: %.*s\n", (int)(ctx->pos - p), p); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + ctx->pos++; + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while parsing tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; } - if (!xmlStrcmp(node->name, XPLIST_DICT)) - { - data->type = PLIST_DICT; - xml_to_node(node, &subnode); + if (*(ctx->pos) == '?') { + find_str(ctx, "?>", 2, 1); + if (ctx->pos > ctx->end-2) { + PLIST_XML_ERR("EOF while looking for <? tag closing marker\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (strncmp(ctx->pos, "?>", 2) != 0) { + PLIST_XML_ERR("Couldn't find <? tag closing marker\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + ctx->pos += 2; + continue; + } else if (*(ctx->pos) == '!') { + /* comment or DTD */ + if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) { + ctx->pos += 3; + find_str(ctx, "-->", 3, 0); + if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { + PLIST_XML_ERR("Couldn't find end of comment\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + ctx->pos+=3; + } else if (((ctx->end - ctx->pos) > 8) && !strncmp(ctx->pos, "!DOCTYPE", 8)) { + int embedded_dtd = 0; + ctx->pos+=8; + while (ctx->pos < ctx->end) { + find_next(ctx, " \t\r\n[>", 6, 1); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("EOF while parsing !DOCTYPE\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (*ctx->pos == '[') { + embedded_dtd = 1; + break; + } else if (*ctx->pos == '>') { + /* end of DOCTYPE found already */ + ctx->pos++; + break; + } else { + parse_skip_ws(ctx); + } + } + if (embedded_dtd) { + find_str(ctx, "]>", 2, 1); + if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) { + PLIST_XML_ERR("Couldn't find end of DOCTYPE\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + ctx->pos += 2; + } + } else { + p = ctx->pos; + find_next(ctx, " \r\n\t>", 5, 1); + PLIST_XML_ERR("Invalid or incomplete special tag <%.*s> encountered\n", (int)(ctx->pos - p), p); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } continue; + } else { + int is_empty = 0; + int closing_tag = 0; + p = ctx->pos; + find_next(ctx, " \r\n\t<>", 6, 0); + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + size_t taglen = ctx->pos - p; + if (taglen >= sizeof(tag)) { + PLIST_XML_ERR("Unexpected tag <%.*s> encountered\n", (int)taglen, p); + ctx->pos = ctx->end; + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + memcpy(tag, p, taglen); + tag[taglen] = '\0'; + if (*ctx->pos != '>') { + find_next(ctx, "<>", 2, 1); + } + if (ctx->pos >= ctx->end) { + PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (*ctx->pos != '>') { + PLIST_XML_ERR("Missing '>' for tag <%s\n", tag); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (*(ctx->pos-1) == '/') { + size_t idx = ctx->pos - p - 1; + if (idx < taglen) + tag[idx] = '\0'; + is_empty = 1; + } + ctx->pos++; + if (!strcmp(tag, "plist")) { + if (!node_path && *plist) { + PLIST_XML_ERR("Multiple top-level <plist> elements encountered\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (is_empty) { + PLIST_XML_ERR("Empty plist tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + + struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item)); + if (!path_item) { + PLIST_XML_ERR("out of memory when allocating node path item\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + path_item->type = "plist"; + path_item->prev = node_path; + node_path = path_item; + + continue; + } else if (!strcmp(tag, "/plist")) { + if (!*plist) { + PLIST_XML_ERR("encountered empty plist tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (!node_path) { + PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (strcmp(node_path->type, tag+1) != 0) { + PLIST_XML_ERR("mismatching closing tag <%s> found for opening tag <%s>\n", tag, node_path->type); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + struct node_path_item *path_item = node_path; + node_path = (struct node_path_item*)node_path->prev; + free(path_item); + continue; + } + if (tag[0] == '/') { + closing_tag = 1; + goto handle_closing; + } + plist_data_t data = plist_new_plist_data(); + if (!data) { + PLIST_XML_ERR("failed to allocate plist data\n"); + ctx->err = PLIST_ERR_NO_MEM; + goto err_out; + } + subnode = plist_new_node(data); + if (!subnode) { + PLIST_XML_ERR("failed to create node\n"); + ctx->err = PLIST_ERR_NO_MEM; + goto err_out; + } + + if (!strcmp(tag, XPLIST_DICT)) { + data->type = PLIST_DICT; + } else if (!strcmp(tag, XPLIST_ARRAY)) { + data->type = PLIST_ARRAY; + } else if (!strcmp(tag, XPLIST_INT)) { + if (!is_empty) { + text_part_t first_part = { NULL, 0, 0, NULL }; + text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part); + if (!tp) { + PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (tp->begin) { + char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); + if (!str_content) { + PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + char *str = str_content; + int is_negative = 0; + if ((str[0] == '-') || (str[0] == '+')) { + if (str[0] == '-') { + is_negative = 1; + } + str++; + } + errno = 0; + char* endp = NULL; + data->intval = strtoull(str, &endp, 0); + if (errno == ERANGE) { + PLIST_XML_ERR("Integer overflow detected while parsing '%.20s'\n", str_content); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + if (endp == str || *endp != '\0') { + PLIST_XML_ERR("Invalid characters while parsing integer value '%.20s'\n", str_content); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + if (is_negative && data->intval > ((uint64_t)INT64_MAX + 1)) { + PLIST_XML_ERR("Signed integer value out of range while parsing '%.20s'\n", str_content); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + if (is_negative || (data->intval <= INT64_MAX)) { + uint64_t v = data->intval; + if (is_negative) { + v = -v; + } + data->intval = v; + data->length = 8; + } else { + data->length = 16; + } + free(str_content); + } else { + is_empty = 1; + } + text_parts_free((text_part_t*)first_part.next); + } + if (is_empty) { + PLIST_XML_ERR("Encountered empty " XPLIST_INT " tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + data->type = PLIST_INT; + } else if (!strcmp(tag, XPLIST_REAL)) { + if (!is_empty) { + text_part_t first_part = { NULL, 0, 0, NULL }; + text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part); + if (!tp) { + PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (tp->begin) { + char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); + if (!str_content) { + PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + errno = 0; + char *endp = NULL; + data->realval = strtod(str_content, &endp); + if (errno == ERANGE) { + PLIST_XML_ERR("Invalid range while parsing value for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + if (endp == str_content || *endp != '\0') { + PLIST_XML_ERR("Could not parse value for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + + } + if (!isfinite(data->realval)) { + PLIST_XML_ERR("Invalid real value while parsing '%.20s'\n", str_content); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + free(str_content); + } else { + is_empty = 1; + } + text_parts_free((text_part_t*)first_part.next); + } + if (is_empty) { + PLIST_XML_ERR("Encountered empty " XPLIST_REAL " tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + data->type = PLIST_REAL; + data->length = 8; + } else if (!strcmp(tag, XPLIST_TRUE)) { + if (!is_empty) { + get_text_parts(ctx, tag, taglen, 1, NULL); + } + data->type = PLIST_BOOLEAN; + data->boolval = 1; + data->length = 1; + } else if (!strcmp(tag, XPLIST_FALSE)) { + if (!is_empty) { + get_text_parts(ctx, tag, taglen, 1, NULL); + } + data->type = PLIST_BOOLEAN; + data->boolval = 0; + data->length = 1; + } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) { + if (!is_empty) { + text_part_t first_part = { NULL, 0, 0, NULL }; + text_part_t *tp = get_text_parts(ctx, tag, taglen, 0, &first_part); + char *str = NULL; + size_t length = 0; + if (!tp) { + PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + str = text_parts_get_content(tp, 1, 0, &length, NULL); + text_parts_free((text_part_t*)first_part.next); + if (!str) { + PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) { + keyname = str; + plist_free(subnode); + subnode = NULL; + continue; + } else { + data->strval = str; + data->length = length; + } + } else { + data->strval = strdup(""); + data->length = 0; + } + data->type = PLIST_STRING; + } else if (!strcmp(tag, XPLIST_DATA)) { + if (!is_empty) { + text_part_t first_part = { NULL, 0, 0, NULL }; + text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part); + if (!tp) { + PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (tp->begin) { + int requires_free = 0; + char *str_content = text_parts_get_content(tp, 0, 0, NULL, &requires_free); + if (!str_content) { + PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + size_t size = tp->length; + if (size > 0) { + data->buff = base64decode(str_content, &size); + if (!data->buff) { + text_parts_free((text_part_t*)first_part.next); + PLIST_XML_ERR("failed to decode base64 stream\n"); + ctx->err = PLIST_ERR_NO_MEM; + goto err_out; + } + data->length = size; + } + + if (requires_free) { + free(str_content); + } + } + text_parts_free((text_part_t*)first_part.next); + } + data->type = PLIST_DATA; + } else if (!strcmp(tag, XPLIST_DATE)) { + if (!is_empty) { + text_part_t first_part = { NULL, 0, 0, NULL }; + text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part); + if (!tp) { + PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + Time64_T timev = 0; + if (tp->begin) { + char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); + if (!str_content) { + PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + struct TM btime; + if (parse_date(str_content, &btime) < 0) { + PLIST_XML_ERR("Failed to parse date node\n"); + text_parts_free((text_part_t*)first_part.next); + ctx->err = PLIST_ERR_PARSE; + free(str_content); + goto err_out; + } + timev = timegm64(&btime); + free(str_content); + } else { + is_empty = 1; + } + text_parts_free((text_part_t*)first_part.next); + data->realval = (double)(timev - MAC_EPOCH); + } + if (is_empty) { + PLIST_XML_ERR("Encountered empty " XPLIST_DATE " tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + data->length = sizeof(double); + data->type = PLIST_DATE; + } else { + PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : ""); + ctx->pos = ctx->end; + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (subnode && !closing_tag) { + if (!*plist) { + /* first value node inside <plist> */ + *plist = subnode; + + if (data->type == PLIST_DICT || data->type == PLIST_ARRAY) { + parent = subnode; + } else { + /* scalar root: keep parsing until </plist> */ + parent = NULL; + } + } else if (parent) { + switch (plist_get_node_type(parent)) { + case PLIST_DICT: + if (!keyname) { + PLIST_XML_ERR("missing key name while adding dict item\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + plist_dict_set_item(parent, keyname, subnode); + break; + case PLIST_ARRAY: + plist_array_append_item(parent, subnode); + break; + default: + /* should not happen */ + PLIST_XML_ERR("parent is not a structured node\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + } else { + /* We already produced root, and we're not inside a container */ + PLIST_XML_ERR("Unexpected tag <%s> found while </plist> is expected\n", tag); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) { + if (depth >= PLIST_MAX_NESTING_DEPTH) { + PLIST_XML_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + ctx->err = PLIST_ERR_MAX_NESTING; + goto err_out; + } + struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item)); + if (!path_item) { + PLIST_XML_ERR("out of memory when allocating node path item\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + path_item->type = (data->type == PLIST_DICT) ? XPLIST_DICT : XPLIST_ARRAY; + path_item->prev = node_path; + node_path = path_item; + + depth++; + parent = subnode; + } else { + /* If we inserted a child scalar into a container, nothing to push. */ + } + subnode = NULL; + } +handle_closing: + if (closing_tag) { + if (!node_path) { + PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + if (strcmp(node_path->type, tag+1) != 0) { + PLIST_XML_ERR("unexpected %s found (for opening %s)\n", tag, node_path->type); + ctx->err = PLIST_ERR_PARSE; + goto err_out; + } + + /* When closing a dictionary, convert a single-entry + { "CF$UID" : <integer> } dictionary into a PLIST_UID node. + Perform the conversion before moving to the parent node. */ + if (!strcmp(node_path->type, XPLIST_DICT) && parent && plist_get_node_type(parent) == PLIST_DICT) { + if (plist_dict_get_size(parent) == 1) { + plist_t uid = plist_dict_get_item(parent, "CF$UID"); + if (uid) { + uint64_t val = 0; + if (plist_get_node_type(uid) != PLIST_INT) { + ctx->err = PLIST_ERR_PARSE; + PLIST_XML_ERR("Invalid node type for CF$UID dict entry (must be PLIST_INT)\n"); + goto err_out; + } + plist_get_uint_val(uid, &val); + plist_set_uid_val(parent, val); + } + } + } + + if (depth > 0) depth--; + struct node_path_item *path_item = node_path; + node_path = (struct node_path_item*)node_path->prev; + free(path_item); + parent = (parent) ? ((node_t)parent)->parent : NULL; + } + free(keyname); + keyname = NULL; + plist_free(subnode); + subnode = NULL; } } -} -void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length) -{ - xmlDocPtr plist_doc = NULL; - xmlNodePtr root_node = NULL; - struct xml_node root = { NULL, 0 }; - int size = 0; - - if (!plist || !plist_xml || *plist_xml) - return; - plist_doc = new_xml_plist(); - root_node = xmlDocGetRootElement(plist_doc); - root.xml = root_node; - - char *current_locale = setlocale(LC_NUMERIC, NULL); - char *saved_locale = NULL; - if (current_locale) { - saved_locale = strdup(current_locale); - } - if (saved_locale) { - setlocale(LC_NUMERIC, "POSIX"); - } - node_to_xml(plist, &root); - - xmlChar* tmp = NULL; - xmlDocDumpMemory(plist_doc, &tmp, &size); - if (size >= 0 && tmp) - { - /* make sure to copy the terminating 0-byte */ - *plist_xml = (char*)malloc((size+1) * sizeof(char)); - memcpy(*plist_xml, tmp, size+1); - *length = size; - xmlFree(tmp); - tmp = NULL; + if (node_path) { + PLIST_XML_ERR("EOF encountered while </%s> was expected\n", node_path->type); + ctx->err = PLIST_ERR_PARSE; } - xmlFreeDoc(plist_doc); - if (saved_locale) { - setlocale(LC_NUMERIC, saved_locale); - free(saved_locale); +err_out: + free(keyname); + plist_free(subnode); + + /* clean up node_path if required */ + while (node_path) { + struct node_path_item *path_item = node_path; + node_path = (struct node_path_item*)path_item->prev; + free(path_item); } + + if (ctx->err != PLIST_ERR_SUCCESS) { + plist_free(*plist); + *plist = NULL; + return ctx->err; + } + + /* check if we have a UID "dict" so we can replace it with a proper UID node */ + if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) { + plist_t value = plist_dict_get_item(*plist, "CF$UID"); + if (PLIST_IS_INT(value)) { + uint64_t u64val = 0; + plist_get_uint_val(value, &u64val); + plist_free(*plist); + *plist = plist_new_uid(u64val); + } + } + + return PLIST_ERR_SUCCESS; } -void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist) +plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist) { - xmlDocPtr plist_doc = xmlParseMemory(plist_xml, length); - xmlNodePtr root_node = xmlDocGetRootElement(plist_doc); + if (!plist) { + return PLIST_ERR_INVALID_ARG; + } + *plist = NULL; + if (!plist_xml || (length == 0)) { + return PLIST_ERR_INVALID_ARG; + } + + struct _parse_ctx ctx = { plist_xml, plist_xml + length, PLIST_ERR_SUCCESS }; - xml_to_node(root_node, plist); - xmlFreeDoc(plist_doc); + return node_from_xml(&ctx, plist); } |
