summaryrefslogtreecommitdiffstats
path: root/src/xplist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/xplist.c')
-rw-r--r--src/xplist.c1740
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 */
- /* < = &lt; > = &gt; ' = &apos; " = &quot; & = &amp; */
- 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);
- }
+ /* < = &lt; > = &gt; & = &amp; */
+ 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, "&lt;", 4);
+ start = cur+1;
+ break;
+ case '>':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&gt;", 4);
+ start = cur+1;
+ break;
+ case '&':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&amp;", 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);
}