summaryrefslogtreecommitdiffstats
path: root/src/xplist.c
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2016-10-22 04:39:47 +0200
committerGravatar Nikias Bassen2016-10-22 04:39:47 +0200
commit392135c7db4d9cb4a14ff5935d7c4c6e21363847 (patch)
tree0e19125ed99b6b2ced754d1b9b3f4bc5245f8c39 /src/xplist.c
parenta3263ad344ff315ac1cba96f0b84b9afff6da787 (diff)
downloadlibplist-392135c7db4d9cb4a14ff5935d7c4c6e21363847.tar.gz
libplist-392135c7db4d9cb4a14ff5935d7c4c6e21363847.tar.bz2
Remove libxml2 dependency in favor of custom XML parsing
Diffstat (limited to 'src/xplist.c')
-rw-r--r--src/xplist.c1002
1 files changed, 652 insertions, 350 deletions
diff --git a/src/xplist.c b/src/xplist.c
index fc665ac..87b21bb 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -2,8 +2,8 @@
2 * xplist.c 2 * xplist.c
3 * XML plist implementation 3 * XML plist implementation
4 * 4 *
5 * Copyright (c) 2010-2016 Nikias Bassen All Rights Reserved.
5 * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. 6 * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved.
6 * Copyright (c) 2010-2014 Nikias Bassen All Rights Reserved.
7 * Copyright (c) 2008 Jonathan Beck All Rights Reserved. 7 * Copyright (c) 2008 Jonathan Beck All Rights Reserved.
8 * 8 *
9 * This library is free software; you can redistribute it and/or 9 * This library is free software; you can redistribute it and/or
@@ -21,6 +21,9 @@
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */ 22 */
23 23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
24 27
25#include <string.h> 28#include <string.h>
26#include <assert.h> 29#include <assert.h>
@@ -31,121 +34,55 @@
31#include <inttypes.h> 34#include <inttypes.h>
32#include <math.h> 35#include <math.h>
33 36
34#include <libxml/xmlIO.h>
35#include <libxml/parser.h>
36#include <libxml/tree.h>
37
38#include <node.h> 37#include <node.h>
39#include <node_list.h> 38#include <node_list.h>
40#include <node_iterator.h> 39#include <node_iterator.h>
41 40
42#include "plist.h" 41#include "plist.h"
43#include "base64.h" 42#include "base64.h"
43#include "strbuf.h"
44#include "time64.h" 44#include "time64.h"
45 45
46#define XPLIST_TEXT BAD_CAST("text") 46#define XPLIST_TEXT "text"
47#define XPLIST_KEY BAD_CAST("key") 47#define XPLIST_KEY "key"
48#define XPLIST_FALSE BAD_CAST("false") 48#define XPLIST_FALSE "false"
49#define XPLIST_TRUE BAD_CAST("true") 49#define XPLIST_TRUE "true"
50#define XPLIST_INT BAD_CAST("integer") 50#define XPLIST_INT "integer"
51#define XPLIST_REAL BAD_CAST("real") 51#define XPLIST_REAL "real"
52#define XPLIST_DATE BAD_CAST("date") 52#define XPLIST_DATE "date"
53#define XPLIST_DATA BAD_CAST("data") 53#define XPLIST_DATA "data"
54#define XPLIST_STRING BAD_CAST("string") 54#define XPLIST_STRING "string"
55#define XPLIST_ARRAY BAD_CAST("array") 55#define XPLIST_ARRAY "array"
56#define XPLIST_DICT BAD_CAST("dict") 56#define XPLIST_DICT "dict"
57 57
58#define MAC_EPOCH 978307200 58#define MAC_EPOCH 978307200
59 59
60static const char *plist_base = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\ 60static const char XML_PLIST_PROLOG[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
61<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\ 61<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
62<plist version=\"1.0\">\n\ 62<plist version=\"1.0\">\n";
63</plist>\0"; 63static const char XML_PLIST_EPILOG[] = "</plist>\n";
64 64
65#ifdef DEBUG
66static int plist_xml_debug = 0;
67#define PLIST_XML_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlparser] ERROR: " __VA_ARGS__); }
68#else
69#define PLIST_XML_ERR(...)
70#endif
65 71
66/** Formats a block of text to be a given indentation and width. 72void plist_xml_init(void)
67 *
68 * The total width of the return string will be depth + cols.
69 *
70 * @param buf The string to format.
71 * @param cols The number of text columns for returned block of text.
72 * @param depth The number of tabs to indent the returned block of text.
73 *
74 * @return The formatted string.
75 */
76static char *format_string(const char *buf, size_t len, int cols, int depth)
77{ 73{
78 if (!buf || !(len > 0)) return NULL; 74 /* init XML stuff */
79 int colw = depth + cols + 1; 75#ifdef DEBUG
80 int nlines = len / cols + 1; 76 char *env_debug = getenv("PLIST_XML_DEBUG");
81 char *new_buf = NULL; 77 if (env_debug && !strcmp(env_debug, "1")) {
82 int i = 0; 78 plist_xml_debug = 1;
83 int j = 0;
84
85 assert(cols >= 0);
86 assert(depth >= 0);
87
88 new_buf = (char*) malloc(nlines * colw + depth + 1);
89 assert(new_buf != 0);
90 memset(new_buf, 0, nlines * colw + depth + 1);
91
92 // Inserts new lines and tabs at appropriate locations
93 for (i = 0; i < nlines; i++)
94 {
95 new_buf[i * colw] = '\n';
96 for (j = 0; j < depth; j++)
97 new_buf[i * colw + 1 + j] = '\t';
98 memcpy(new_buf + i * colw + 1 + depth, buf + i * cols, (size_t)(i + 1) * cols <= len ? (size_t)cols : len - i * cols);
99 } 79 }
100 new_buf[len + (1 + depth) * nlines] = '\n'; 80#endif
101
102 // Inserts final row of indentation and termination character
103 for (j = 0; j < depth; j++)
104 new_buf[len + (1 + depth) * nlines + 1 + j] = '\t';
105 new_buf[len + (1 + depth) * nlines + depth + 1] = '\0';
106
107 return new_buf;
108}
109
110
111
112struct xml_node
113{
114 xmlNodePtr xml;
115 uint32_t depth;
116};
117
118/** Creates a new plist XML document.
119 *
120 * @return The plist XML document.
121 */
122static xmlDocPtr new_xml_plist(void)
123{
124 char *plist = strdup(plist_base);
125 xmlDocPtr plist_xml = xmlParseMemory(plist, strlen(plist));
126
127 free(plist);
128
129 return plist_xml;
130}
131
132static struct node_t* new_key_node(const char* name)
133{
134 plist_data_t data = plist_new_plist_data();
135 data->type = PLIST_KEY;
136 int size = strlen(name);
137 data->strval = strdup(name);
138 data->length = size;
139 return node_create(NULL, data);
140} 81}
141 82
142static struct node_t* new_uint_node(uint64_t value) 83void plist_xml_deinit(void)
143{ 84{
144 plist_data_t data = plist_new_plist_data(); 85 /* deinit XML stuff */
145 data->type = PLIST_UINT;
146 data->intval = value;
147 data->length = sizeof(uint64_t);
148 return node_create(NULL, data);
149} 86}
150 87
151static void dtostr(char *buf, size_t bufsize, double realval) 88static void dtostr(char *buf, size_t bufsize, double realval)
@@ -178,27 +115,21 @@ static void dtostr(char *buf, size_t bufsize, double realval)
178 buf[p] = '\0'; 115 buf[p] = '\0';
179} 116}
180 117
181static void node_to_xml(node_t* node, void *xml_struct) 118static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
182{ 119{
183 struct xml_node *xstruct = NULL;
184 plist_data_t node_data = NULL; 120 plist_data_t node_data = NULL;
185 121
186 xmlNodePtr child_node = NULL;
187 char isStruct = FALSE; 122 char isStruct = FALSE;
188 char isUIDNode = FALSE; 123 char tagOpen = FALSE;
189 124
190 const xmlChar *tag = NULL; 125 const char *tag = NULL;
191 char *val = NULL; 126 char *val = NULL;
192 127
193 //for base64
194 char *valtmp = NULL;
195
196 uint32_t i = 0; 128 uint32_t i = 0;
197 129
198 if (!node) 130 if (!node)
199 return; 131 return;
200 132
201 xstruct = (struct xml_node *) xml_struct;
202 node_data = plist_get_data(node); 133 node_data = plist_get_data(node);
203 134
204 switch (node_data->type) 135 switch (node_data->type)
@@ -230,23 +161,17 @@ static void node_to_xml(node_t* node, void *xml_struct)
230 161
231 case PLIST_STRING: 162 case PLIST_STRING:
232 tag = XPLIST_STRING; 163 tag = XPLIST_STRING;
233 val = strdup((char*) node_data->strval); 164 /* contents processed directly below */
234 break; 165 break;
235 166
236 case PLIST_KEY: 167 case PLIST_KEY:
237 tag = XPLIST_KEY; 168 tag = XPLIST_KEY;
238 val = strdup((char*) node_data->strval); 169 /* contents processed directly below */
239 break; 170 break;
240 171
241 case PLIST_DATA: 172 case PLIST_DATA:
242 tag = XPLIST_DATA; 173 tag = XPLIST_DATA;
243 if (node_data->length) 174 /* contents processed directly below */
244 {
245 size_t len = node_data->length;
246 valtmp = base64encode(node_data->buff, &len);
247 val = format_string(valtmp, len, 68, xstruct->depth);
248 free(valtmp);
249 }
250 break; 175 break;
251 case PLIST_ARRAY: 176 case PLIST_ARRAY:
252 tag = XPLIST_ARRAY; 177 tag = XPLIST_ARRAY;
@@ -275,77 +200,161 @@ static void node_to_xml(node_t* node, void *xml_struct)
275 } 200 }
276 break; 201 break;
277 case PLIST_UID: 202 case PLIST_UID:
278 // special case for keyed encoding
279 tag = XPLIST_DICT; 203 tag = XPLIST_DICT;
280 isStruct = TRUE; 204 val = (char*)malloc(64);
281 isUIDNode = TRUE; 205 if (node_data->length == 16) {
282 node_data->type = PLIST_DICT; 206 (void)snprintf(val, 64, "%"PRIu64, node_data->intval);
283 node_attach(node, new_key_node("CF$UID")); 207 } else {
284 node_attach(node, new_uint_node(node_data->intval)); 208 (void)snprintf(val, 64, "%"PRIi64, node_data->intval);
209 }
285 break; 210 break;
286 default: 211 default:
287 break; 212 break;
288 } 213 }
289 214
290 for (i = 0; i < xstruct->depth; i++) 215 for (i = 0; i < depth; i++) {
291 { 216 str_buf_append(*outbuf, "\t", 1);
292 xmlNodeAddContent(xstruct->xml, BAD_CAST("\t"));
293 } 217 }
218
219 /* append tag */
220 str_buf_append(*outbuf, "<", 1);
221 str_buf_append(*outbuf, tag, strlen(tag));
294 if (node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) { 222 if (node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) {
223 size_t j;
224 size_t len;
225 off_t start = 0;
226 off_t cur = 0;
227
228 str_buf_append(*outbuf, ">", 1);
229 tagOpen = TRUE;
230
295 /* make sure we convert the following predefined xml entities */ 231 /* make sure we convert the following predefined xml entities */
296 /* < = &lt; > = &gt; ' = &apos; " = &quot; & = &amp; */ 232 /* < = &lt; > = &gt; ' = &apos; " = &quot; & = &amp; */
297 child_node = xmlNewTextChild(xstruct->xml, NULL, tag, BAD_CAST(val)); 233 len = strlen(node_data->strval);
298 } else 234 for (j = 0; j < len; j++) {
299 child_node = xmlNewChild(xstruct->xml, NULL, tag, BAD_CAST(val)); 235 switch (node_data->strval[j]) {
300 xmlNodeAddContent(xstruct->xml, BAD_CAST("\n")); 236 case '<':
301 if (val) { 237 str_buf_append(*outbuf, node_data->strval + start, cur - start);
302 free(val); 238 str_buf_append(*outbuf, "&lt;", 4);
303 } 239 start = cur+1;
240 break;
241 case '>':
242 str_buf_append(*outbuf, node_data->strval + start, cur - start);
243 str_buf_append(*outbuf, "&gt;", 4);
244 start = cur+1;
245 break;
246 case '\'':
247 str_buf_append(*outbuf, node_data->strval + start, cur - start);
248 str_buf_append(*outbuf, "&apos;", 6);
249 start = cur+1;
250 break;
251 case '"':
252 str_buf_append(*outbuf, node_data->strval + start, cur - start);
253 str_buf_append(*outbuf, "&quot;", 6);
254 start = cur+1;
255 break;
256 case '&':
257 str_buf_append(*outbuf, node_data->strval + start, cur - start);
258 str_buf_append(*outbuf, "&amp;", 5);
259 start = cur+1;
260 break;
261 default:
262 break;
263 }
264 cur++;
265 }
266 str_buf_append(*outbuf, node_data->strval + start, cur - start);
267 } else if (node_data->type == PLIST_DATA) {
268 str_buf_append(*outbuf, ">", 1);
269 tagOpen = TRUE;
270 str_buf_append(*outbuf, "\n", 1);
271 if (node_data->length > 0) {
272 char *buf = malloc(80);
273 uint32_t j = 0;
274 uint32_t indent = (depth > 8) ? 8 : depth;
275 uint32_t maxread = ((76 - indent*8) / 4) * 3;
276 size_t count = 0;
277 size_t b64count = 0;
278 while (j < node_data->length) {
279 for (i = 0; i < indent; i++) {
280 str_buf_append(*outbuf, "\t", 1);
281 }
282 count = (node_data->length-j < maxread) ? node_data->length-j : maxread;
283 b64count = base64encode(buf, node_data->buff + j, count);
284 str_buf_append(*outbuf, buf, b64count);
285 str_buf_append(*outbuf, "\n", 1);
286 j+=count;
287 }
288 free(buf);
289 }
290 for (i = 0; i < depth; i++) {
291 str_buf_append(*outbuf, "\t", 1);
292 }
293 } else if (node_data->type == PLIST_UID) {
294 /* special case for UID nodes: create a DICT */
295 str_buf_append(*outbuf, ">", 1);
296 tagOpen = TRUE;
297 str_buf_append(*outbuf, "\n", 1);
298
299 /* add CF$UID key */
300 for (i = 0; i < depth+1; i++) {
301 str_buf_append(*outbuf, "\t", 1);
302 }
303 str_buf_append(*outbuf, "<key>CF$UID</key>", 17);
304 str_buf_append(*outbuf, "\n", 1);
304 305
305 //add return for structured types 306 /* add UID value */
306 if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT) 307 for (i = 0; i < depth+1; i++) {
307 xmlNodeAddContent(child_node, BAD_CAST("\n")); 308 str_buf_append(*outbuf, "\t", 1);
309 }
310 str_buf_append(*outbuf, "<integer>", 9);
311 str_buf_append(*outbuf, val, strlen(val));
312 str_buf_append(*outbuf, "</integer>", 10);
313 str_buf_append(*outbuf, "\n", 1);
308 314
309 //make sure we don't produce <data/> if it's empty 315 for (i = 0; i < depth; i++) {
310 if ((node_data->type == PLIST_DATA) && !val) { 316 str_buf_append(*outbuf, "\t", 1);
311 xmlNodeAddContent(child_node, BAD_CAST("\n"));
312 for (i = 0; i < xstruct->depth; i++)
313 {
314 xmlNodeAddContent(child_node, BAD_CAST("\t"));
315 } 317 }
318 } else if (val) {
319 str_buf_append(*outbuf, ">", 1);
320 tagOpen = TRUE;
321 str_buf_append(*outbuf, val, strlen(val));
322 } else if (isStruct) {
323 tagOpen = TRUE;
324 str_buf_append(*outbuf, ">", 1);
325 } else {
326 tagOpen = FALSE;
327 str_buf_append(*outbuf, "/>", 2);
316 } 328 }
329 free(val);
317 330
318 if (isStruct) 331 /* add return for structured types */
319 { 332 if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
320 struct xml_node child = { child_node, xstruct->depth + 1 }; 333 str_buf_append(*outbuf, "\n", 1);
334
335 if (isStruct) {
321 node_iterator_t *ni = node_iterator_create(node->children); 336 node_iterator_t *ni = node_iterator_create(node->children);
322 node_t *ch; 337 node_t *ch;
323 while ((ch = node_iterator_next(ni))) { 338 while ((ch = node_iterator_next(ni))) {
324 node_to_xml(ch, &child); 339 node_to_xml(ch, outbuf, depth+1);
325 } 340 }
326 node_iterator_destroy(ni); 341 node_iterator_destroy(ni);
327 } 342 }
328 //fix indent for structured types
329 if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
330 {
331 343
332 for (i = 0; i < xstruct->depth; i++) 344 /* fix indent for structured types */
333 { 345 if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT) {
334 xmlNodeAddContent(child_node, BAD_CAST("\t")); 346 for (i = 0; i < depth; i++) {
347 str_buf_append(*outbuf, "\t", 1);
335 } 348 }
336 } 349 }
337 if (isUIDNode) 350
338 { 351 if (tagOpen) {
339 unsigned int num = node_n_children(node); 352 /* add closing tag */
340 unsigned int j; 353 str_buf_append(*outbuf, "</", 2);
341 for (j = num; j > 0; j--) { 354 str_buf_append(*outbuf, tag, strlen(tag));
342 node_t* ch = node_nth_child(node, j-1); 355 str_buf_append(*outbuf, ">", 1);
343 node_detach(node, ch);
344 plist_free_data((plist_data_t)((node_t*)ch)->data);
345 node_destroy(ch);
346 }
347 node_data->type = PLIST_UID;
348 } 356 }
357 str_buf_append(*outbuf, "\n", 1);
349 358
350 return; 359 return;
351} 360}
@@ -370,233 +379,526 @@ static void parse_date(const char *strval, struct TM *btime)
370 btime->tm_isdst=0; 379 btime->tm_isdst=0;
371} 380}
372 381
373static void xml_to_node(xmlNodePtr xml_node, plist_t * plist_node) 382PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
374{ 383{
375 xmlNodePtr node = NULL; 384 strbuf_t *outbuf = str_buf_new();
376 plist_data_t data = NULL;
377 plist_t subnode = NULL;
378 385
379 //for string 386 str_buf_append(outbuf, XML_PLIST_PROLOG, sizeof(XML_PLIST_PROLOG)-1);
380 long len = 0;
381 int type = 0;
382 387
383 if (!xml_node) 388 node_to_xml(plist, &outbuf, 0);
384 return;
385 389
386 for (node = xml_node->children; node; node = node->next) 390 str_buf_append(outbuf, XML_PLIST_EPILOG, sizeof(XML_PLIST_EPILOG));
387 {
388
389 while (node && !xmlStrcmp(node->name, XPLIST_TEXT))
390 node = node->next;
391 if (!node)
392 break;
393 391
394 if (!xmlStrcmp(node->name, BAD_CAST("comment"))) { 392 *plist_xml = outbuf->data;
395 continue; 393 *length = outbuf->len - 1;
396 }
397 394
398 data = plist_new_plist_data(); 395 outbuf->data = NULL;
399 subnode = plist_new_node(data); 396 str_buf_free(outbuf);
400 if (*plist_node) 397}
401 node_attach(*plist_node, subnode);
402 else
403 *plist_node = subnode;
404 398
405 if (!xmlStrcmp(node->name, XPLIST_TRUE)) 399struct _parse_ctx {
406 { 400 const char *pos;
407 data->boolval = TRUE; 401 const char *end;
408 data->type = PLIST_BOOLEAN; 402 int err;
409 data->length = 1; 403};
410 continue; 404typedef struct _parse_ctx* parse_ctx;
411 }
412 405
413 if (!xmlStrcmp(node->name, XPLIST_FALSE)) 406static void parse_skip_ws(parse_ctx ctx)
414 { 407{
415 data->boolval = FALSE; 408 while (ctx->pos < ctx->end && ((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n'))) {
416 data->type = PLIST_BOOLEAN; 409 ctx->pos++;
417 data->length = 1; 410 }
418 continue; 411}
419 }
420 412
421 if (!xmlStrcmp(node->name, XPLIST_INT)) 413static void find_char(parse_ctx ctx, char c, int skip_quotes)
422 { 414{
423 xmlChar *strval = xmlNodeGetContent(node); 415 while (ctx->pos < ctx->end && (*(ctx->pos) != c)) {
424 int is_negative = 0; 416 if (skip_quotes && (c != '"') && (*(ctx->pos) == '"')) {
425 char *str = (char*)strval; 417 ctx->pos++;
426 if ((str[0] == '-') || (str[0] == '+')) { 418 find_char(ctx, '"', 0);
427 if (str[0] == '-') { 419 if (*(ctx->pos) != '"') {
428 is_negative = 1; 420 PLIST_XML_ERR("Unmatched double quote\n");
429 } 421 return;
430 str++;
431 }
432 char* endp = NULL;
433 data->intval = strtoull((char*)str, &endp, 0);
434 if ((endp != NULL) && (strlen(endp) > 0)) {
435 fprintf(stderr, "%s: integer parse error: string contains invalid characters: '%s'\n", __func__, endp);
436 }
437 if (is_negative || (data->intval <= INT64_MAX)) {
438 int64_t v = data->intval;
439 if (is_negative) {
440 v = -v;
441 }
442 data->intval = (uint64_t)v;
443 data->length = 8;
444 } else {
445 data->length = 16;
446 } 422 }
447 data->type = PLIST_UINT;
448 xmlFree(strval);
449 continue;
450 } 423 }
424 ctx->pos++;
425 }
426}
451 427
452 if (!xmlStrcmp(node->name, XPLIST_REAL)) 428static void find_str(parse_ctx ctx, const char *str, int skip_quotes)
453 { 429{
454 xmlChar *strval = xmlNodeGetContent(node); 430 size_t len = strlen(str);
455 data->realval = atof((char *) strval); 431 while (ctx->pos < (ctx->end - len)) {
456 data->type = PLIST_REAL; 432 if (!strncmp(ctx->pos, str, len)) {
457 data->length = 8; 433 break;
458 xmlFree(strval);
459 continue;
460 } 434 }
461 435 if (skip_quotes && (*(ctx->pos) == '"')) {
462 if (!xmlStrcmp(node->name, XPLIST_DATE)) 436 ctx->pos++;
463 { 437 find_char(ctx, '"', 0);
464 xmlChar *strval = xmlNodeGetContent(node); 438 if (*(ctx->pos) != '"') {
465 Time64_T timev = 0; 439 PLIST_XML_ERR("Unmatched double quote\n");
466 if (strlen((const char*)strval) >= 11) { 440 return;
467 struct TM btime;
468 parse_date((const char*)strval, &btime);
469 timev = timegm64(&btime);
470 } 441 }
471 data->realval = (double)(timev - MAC_EPOCH);
472 data->type = PLIST_DATE;
473 data->length = sizeof(double);
474 xmlFree(strval);
475 continue;
476 } 442 }
443 ctx->pos++;
444 }
445}
477 446
478 if (!xmlStrcmp(node->name, XPLIST_STRING)) 447static void find_next(parse_ctx ctx, const char *nextchars, int skip_quotes)
479 { 448{
480 xmlChar *strval = xmlNodeGetContent(node); 449 int numchars = strlen(nextchars);
481 len = strlen((char *) strval); 450 int i = 0;
482 type = xmlDetectCharEncoding(strval, len); 451 while (ctx->pos < ctx->end) {
483 452 if (skip_quotes && (*(ctx->pos) == '"')) {
484 if (XML_CHAR_ENCODING_UTF8 == type || XML_CHAR_ENCODING_ASCII == type || XML_CHAR_ENCODING_NONE == type) 453 ctx->pos++;
485 { 454 find_char(ctx, '"', 0);
486 data->strval = strdup((char *) strval); 455 if (*(ctx->pos) != '"') {
487 data->type = PLIST_STRING; 456 PLIST_XML_ERR("Unmatched double quote\n");
488 data->length = strlen(data->strval); 457 return;
489 } 458 }
490 xmlFree(strval);
491 continue;
492 } 459 }
493 460 for (i = 0; i < numchars; i++) {
494 if (!xmlStrcmp(node->name, XPLIST_KEY)) 461 if (*(ctx->pos) == nextchars[i]) {
495 { 462 return;
496 xmlChar *strval = xmlNodeGetContent(node);
497 len = strlen((char *) strval);
498 type = xmlDetectCharEncoding(strval, len);
499
500 if (XML_CHAR_ENCODING_UTF8 == type || XML_CHAR_ENCODING_ASCII == type || XML_CHAR_ENCODING_NONE == type)
501 {
502 data->strval = strdup((char *) strval);
503 data->type = PLIST_KEY;
504 data->length = strlen(data->strval);
505 } 463 }
506 xmlFree(strval);
507 continue;
508 } 464 }
465 ctx->pos++;
466 }
467}
509 468
510 if (!xmlStrcmp(node->name, XPLIST_DATA)) 469static char* get_text_content(parse_ctx ctx, const char* tag, int skip_ws, int unescape_entities)
511 { 470{
512 xmlChar *strval = xmlNodeGetContent(node); 471 const char *p;
513 size_t size = 0; 472 const char *q;
514 unsigned char *dec = base64decode((char*)strval, &size); 473 int taglen;
515 data->buff = (uint8_t *) malloc(size * sizeof(uint8_t)); 474 char *str;
516 memcpy(data->buff, dec, size * sizeof(uint8_t)); 475 int i = 0;
517 free(dec);
518 data->length = size;
519 data->type = PLIST_DATA;
520 xmlFree(strval);
521 continue;
522 }
523 476
524 if (!xmlStrcmp(node->name, XPLIST_ARRAY)) 477 if (skip_ws) {
525 { 478 parse_skip_ws(ctx);
526 data->type = PLIST_ARRAY; 479 }
527 xml_to_node(node, &subnode); 480 p = ctx->pos;
528 continue; 481 find_char(ctx, '<', 1);
529 } 482 if (*ctx->pos != '<') { PLIST_XML_ERR("didn't find <\n"); return NULL; }
483 q = ctx->pos;
484 ctx->pos++;
485 if (ctx->pos >= ctx->end || *ctx->pos != '/') { PLIST_XML_ERR("EOF or empty tag while parsing '%s'\n",p); return NULL; }
486 ctx->pos++;
487 taglen = strlen(tag);
488 if (ctx->pos >= ctx->end-taglen || strncmp(ctx->pos, tag, taglen)) { PLIST_XML_ERR("EOF or end tag mismatch\n"); return NULL;}
489 ctx->pos+=taglen;
490 if (ctx->pos >= ctx->end || *ctx->pos != '>') { PLIST_XML_ERR("EOF or no '>' after tag name\n"); return NULL;}
491 ctx->pos++;
492 int len = q - p;
493 if (len < 0) {
494 PLIST_XML_ERR("Couldn't find matching '%s' end tag\n", tag);
495 return NULL;
496 }
497 str = malloc(len+1);
498 strncpy(str, p, len);
499 str[len] = 0;
530 500
531 if (!xmlStrcmp(node->name, XPLIST_DICT)) 501 if (!unescape_entities) {
532 { 502 return str;
533 data->type = PLIST_DICT; 503 }
534 xml_to_node(node, &subnode); 504
535 if (plist_get_node_type(subnode) == PLIST_DICT) { 505 /* unescape entities */
536 if (plist_dict_get_size(subnode) == 1) { 506 while (i < len-1) {
537 plist_t uid = plist_dict_get_item(subnode, "CF$UID"); 507 if (str[i] == '&') {
538 if (uid) { 508 char *entp = str + i + 1;
539 uint64_t val = 0; 509 while (i < len && str[i] != ';') {
540 plist_get_uint_val(uid, &val); 510 i++;
541 plist_dict_remove_item(subnode, "CF$UID"); 511 }
542 plist_data_t nodedata = plist_get_data((node_t*)subnode); 512 if (i >= len) {
543 free(nodedata->buff); 513 break;
544 nodedata->type = PLIST_UID; 514 }
545 nodedata->length = sizeof(uint64_t); 515 if (str+i > entp+1) {
546 nodedata->intval = val; 516 int entlen = str+i - entp;
547 } 517 if (!strncmp(entp, "amp", 3)) {
518 /* the '&' is already there */
519 } else if (!strncmp(entp, "apos", 4)) {
520 *(entp-1) = '\'';
521 } else if (!strncmp(entp, "quot", 4)) {
522 *(entp-1) = '"';
523 } else if (!strncmp(entp, "lt", 2)) {
524 *(entp-1) = '<';
525 } else if (!strncmp(entp, "gt", 2)) {
526 *(entp-1) = '>';
527 } else {
528 /* unexpected entity, replace with ? */
529 *(entp-1) = '?';
548 } 530 }
531 memmove(entp, str+i+1, len - i);
532 i -= entlen;
533 continue;
549 } 534 }
550 continue;
551 } 535 }
536 i++;
552 } 537 }
538
539 return str;
553} 540}
554 541
555PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length) 542static void skip_text_content(parse_ctx ctx, const char* tag)
556{ 543{
557 xmlDocPtr plist_doc = NULL; 544 int taglen;
558 xmlNodePtr root_node = NULL; 545 find_char(ctx, '<', 1);
559 struct xml_node root = { NULL, 0 }; 546 if (*ctx->pos != '<') return;
560 int size = 0; 547 ctx->pos++;
548 taglen = strlen(tag);
549 if (ctx->pos >= ctx->end-taglen || strncmp(ctx->pos, tag, taglen)) return;
550 ctx->pos+=taglen;
551 if (ctx->pos >= ctx->end || *ctx->pos != '>') return;
552 ctx->pos++;
553}
561 554
562 if (!plist || !plist_xml || *plist_xml) 555static void node_from_xml(parse_ctx ctx, plist_t *plist)
563 return; 556{
564 plist_doc = new_xml_plist(); 557 char *keyname = NULL;
565 root_node = xmlDocGetRootElement(plist_doc); 558 while (ctx->pos < ctx->end && !ctx->err) {
566 root.xml = root_node; 559#ifdef DEBUG
560 const char *start = ctx->pos;
561#endif
562 parse_skip_ws(ctx);
563 if (ctx->pos >= ctx->end) {
564 break;
565 }
566 if (*ctx->pos != '<') {
567 PLIST_XML_ERR("Failed to parse XML. Expected: opening tag, found: '%s', pos: %s\n", start, ctx->pos);
568 ctx->pos = ctx->end;
569 break;
570 }
571 ctx->pos++;
572 if (ctx->pos >= ctx->end) {
573 break;
574 }
567 575
568 node_to_xml(plist, &root); 576 if (*(ctx->pos) == '?') {
577 find_str(ctx, "?>", 1);
578 if (ctx->pos >= ctx->end) {
579 break;
580 }
581 if (strncmp(ctx->pos, "?>", 2)) {
582 PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
583 ctx->pos = ctx->end;
584 return;
585 }
586 ctx->pos += 2;
587 continue;
588 } else if (*(ctx->pos) == '!') {
589 /* comment or DTD */
590 if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) {
591 ctx->pos += 3;
592 find_str(ctx,"-->", 0);
593 if (strncmp(ctx->pos, "-->", 3)) {
594 PLIST_XML_ERR("Couldn't find end of comment\n");
595 ctx->pos = ctx->end;
596 return;
597 }
598 ctx->pos+=3;
599 } else if (((ctx->end - ctx->pos) > 8) && !strncmp(ctx->pos, "!DOCTYPE", 8)) {
600 int embedded_dtd = 0;
601 ctx->pos+=8;
602 while (ctx->pos < ctx->end) {
603 find_next(ctx, " \t\r\n[>", 1);
604 if (*ctx->pos == '[') {
605 embedded_dtd = 1;
606 break;
607 } else if (*ctx->pos == '>') {
608 /* end of DOCTYPE found already */
609 ctx->pos++;
610 break;
611 } else {
612 parse_skip_ws(ctx);
613 }
614 }
615 if (embedded_dtd) {
616 find_str(ctx, "]>", 1);
617 if (strncmp(ctx->pos, "]>", 2)) {
618 PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
619 ctx->pos = ctx->end;
620 return;
621 }
622 ctx->pos += 2;
623 }
624 } else {
625 PLIST_XML_ERR("Unknown ! special tag encountered\n");
626 ctx->err++;
627 }
628 continue;
629 } else {
630 int is_empty = 0;
631 int closing_tag = 0;
632 const char *p = ctx->pos;
633 find_next(ctx," \r\n\t<>",0);
634 if (ctx->pos >= ctx->end) {
635 PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
636 ctx->pos = ctx->end;
637 ctx->err++;
638 free(keyname);
639 return;
640 }
641 int taglen = ctx->pos - p;
642 char *tag = malloc(taglen + 1);
643 strncpy(tag, p, taglen);
644 tag[taglen] = '\0';
645 if (*ctx->pos != '>') {
646 find_next(ctx, "<>", 1);
647 }
648 if (ctx->pos >= ctx->end) {
649 PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
650 ctx->pos = ctx->end;
651 ctx->err++;
652 free(tag);
653 free(keyname);
654 return;
655 }
656 if (*ctx->pos != '>') {
657 PLIST_XML_ERR("Missing '>' for tag '%s'\n", tag);
658 ctx->pos = ctx->end;
659 ctx->err++;
660 free(tag);
661 free(keyname);
662 return;
663 }
664 if (*(ctx->pos-1) == '/') {
665 tag[ctx->pos - p - 1] = '\0';
666 is_empty = 1;
667 }
668 ctx->pos++;
669 if (!strcmp(tag, "plist")) {
670 free(tag);
671 if (is_empty) {
672 return;
673 }
674 if (!*plist) {
675 /* only process first plist node found */
676 node_from_xml(ctx, plist);
677 }
678 continue;
679 }
569 680
570 xmlChar* tmp = NULL; 681 plist_data_t data = plist_new_plist_data();
571 xmlDocDumpMemory(plist_doc, &tmp, &size); 682 plist_t subnode = plist_new_node(data);
572 if (size >= 0 && tmp) 683
573 { 684 if (!strcmp(tag, XPLIST_DICT)) {
574 /* make sure to copy the terminating 0-byte */ 685 data->type = PLIST_DICT;
575 *plist_xml = (char*)malloc((size+1) * sizeof(char)); 686 } else if (!strcmp(tag, XPLIST_ARRAY)) {
576 memcpy(*plist_xml, tmp, size+1); 687 data->type = PLIST_ARRAY;
577 *length = size; 688 } else if (!strcmp(tag, XPLIST_INT)) {
578 xmlFree(tmp); 689 if (!is_empty) {
579 tmp = NULL; 690 char *str_content = get_text_content(ctx, tag, 1, 0);
691 if (!str_content) {
692 PLIST_XML_ERR("Couldn't find end tag for '%s'\n", tag);
693 ctx->pos = ctx->end;
694 ctx->err++;
695 free(tag);
696 free(keyname);
697 return;
698 }
699 char *str = str_content;
700 int is_negative = 0;
701 if ((str[0] == '-') || (str[0] == '+')) {
702 if (str[0] == '-') {
703 is_negative = 1;
704 }
705 str++;
706 }
707 char* endp = NULL;
708 data->intval = strtoull((char*)str, &endp, 0);
709 if ((endp != NULL) && (strlen(endp) > 0)) {
710 PLIST_XML_ERR("integer parse error: string contains invalid characters: '%s'\n", endp);
711 }
712 if (is_negative || (data->intval <= INT64_MAX)) {
713 int64_t v = data->intval;
714 if (is_negative) {
715 v = -v;
716 }
717 data->intval = (uint64_t)v;
718 data->length = 8;
719 } else {
720 data->length = 16;
721 }
722 free(str_content);
723 } else {
724 data->intval = 0;
725 data->length = 8;
726 }
727 data->type = PLIST_UINT;
728 } else if (!strcmp(tag, XPLIST_REAL)) {
729 if (!is_empty) {
730 char *strval = get_text_content(ctx, tag, 1, 0);
731 data->realval = atof((char *) strval);
732 free(strval);
733 }
734 data->type = PLIST_REAL;
735 data->length = 8;
736 } else if (!strcmp(tag, XPLIST_TRUE)) {
737 if (!is_empty) {
738 skip_text_content(ctx, tag);
739 }
740 data->type = PLIST_BOOLEAN;
741 data->boolval = 1;
742 data->length = 1;
743 } else if (!strcmp(tag, XPLIST_FALSE)) {
744 if (!is_empty) {
745 skip_text_content(ctx, tag);
746 }
747 data->type = PLIST_BOOLEAN;
748 data->boolval = 0;
749 data->length = 1;
750 } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) {
751 if (!is_empty) {
752 char *str = get_text_content(ctx, tag, 0, 1);
753 if (!str) {
754 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag);
755 ctx->pos = ctx->end;
756 ctx->err++;
757 free(tag);
758 free(keyname);
759 return;
760 }
761 if (!strcmp(tag, "key") && !keyname && *plist && (plist_get_node_type(*plist) == PLIST_DICT)) {
762 keyname = str;
763 free(tag);
764 plist_free(subnode);
765 subnode = NULL;
766 ctx->pos++;
767 continue;
768 } else {
769 data->strval = str;
770 data->length = strlen(str);
771 }
772 } else {
773 data->strval = strdup("");
774 data->length = 0;
775 }
776 data->type = PLIST_STRING;
777 } else if (!strcmp(tag, XPLIST_DATA)) {
778 if (!is_empty) {
779 char *strval = get_text_content(ctx, tag, 1, 0);
780 if (!strval) {
781 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag);
782 ctx->pos = ctx->end;
783 ctx->err++;
784 free(tag);
785 free(keyname);
786 return;
787 }
788 size_t size = 0;
789 data->buff = base64decode((char*)strval, &size);
790 free(strval);
791 data->length = size;
792 }
793 data->type = PLIST_DATA;
794 } else if (!strcmp(tag, XPLIST_DATE)) {
795 if (!is_empty) {
796 char *strval = get_text_content(ctx, tag, 1, 0);
797 if (!strval) {
798 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag);
799 ctx->pos = ctx->end;
800 ctx->err++;
801 free(tag);
802 free(keyname);
803 return;
804 }
805 Time64_T timev = 0;
806 if (strlen((const char*)strval) >= 11) {
807 struct TM btime;
808 parse_date((const char*)strval, &btime);
809 timev = timegm64(&btime);
810 }
811 data->realval = (double)(timev - MAC_EPOCH);
812 free(strval);
813 }
814 data->length = sizeof(double);
815 data->type = PLIST_DATE;
816 } else if (tag[0] == '/') {
817 closing_tag = 1;
818 } else {
819 PLIST_XML_ERR("Unexpected tag '<%s%s>' encountered while parsing XML\n", tag, (is_empty) ? "/" : "");
820 ctx->pos = ctx->end;
821 ctx->err++;
822 free(tag);
823 free(keyname);
824 return;
825 }
826 free(tag);
827 if (subnode && !closing_tag) {
828 /* parse sub nodes for structured types */
829 if (data->type == PLIST_DICT || data->type == PLIST_ARRAY) {
830 if (!is_empty) {
831 /* only if not empty */
832 node_from_xml(ctx, &subnode);
833 if ((data->type == PLIST_DICT) && (plist_dict_get_size(subnode) == 1)) {
834 /* convert XML CF$UID dictionaries to PLIST_UID nodes */
835 plist_t uid = plist_dict_get_item(subnode, "CF$UID");
836 if (uid) {
837 uint64_t val = 0;
838 plist_get_uint_val(uid, &val);
839 plist_dict_remove_item(subnode, "CF$UID");
840 plist_data_t nodedata = plist_get_data((node_t*)subnode);
841 free(nodedata->buff);
842 nodedata->type = PLIST_UID;
843 nodedata->length = sizeof(uint64_t);
844 nodedata->intval = val;
845 }
846 }
847 }
848 }
849 if (!*plist) {
850 /* no parent? make this node the new parent node */
851 *plist = subnode;
852 subnode = NULL;
853 } else {
854 switch (plist_get_node_type(*plist)) {
855 case PLIST_DICT:
856 if (!keyname || *keyname == '\0') {
857 PLIST_XML_ERR("missing or empty key name while adding dict item\n");
858 ctx->err++;
859 break;
860 }
861 plist_dict_set_item(*plist, keyname, subnode);
862 subnode = NULL;
863 break;
864 case PLIST_ARRAY:
865 plist_array_append_item(*plist, subnode);
866 subnode = NULL;
867 break;
868 default:
869 /* should not happen */
870 PLIST_XML_ERR("while parsing XML plist: parent is not a structered node.\n");
871 ctx->err++;
872 break;
873 }
874 }
875 } else if (closing_tag && *plist && plist_get_node_type(*plist) == PLIST_DICT && keyname) {
876 PLIST_XML_ERR("missing value node in dict\n");
877 ctx->err++;
878 }
879 free(keyname);
880 keyname = NULL;
881 plist_free(subnode);
882 if (closing_tag) {
883 break;
884 }
885 }
886 ctx->pos++;
887 }
888 if (ctx->err) {
889 plist_free(*plist);
890 *plist = NULL;
580 } 891 }
581 xmlFreeDoc(plist_doc);
582}
583
584static xmlParserInputPtr plist_xml_external_entity_loader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt)
585{
586 return NULL;
587} 892}
588 893
589PLIST_API void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist) 894PLIST_API void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
590{ 895{
591 /* CVE-2013-0339: disable external entity loading to prevent XXE vulnerability */ 896 if (!plist_xml || (length == 0)) {
592 xmlSetExternalEntityLoader(plist_xml_external_entity_loader); 897 *plist = NULL;
898 return;
899 }
593 900
594 /* read XML from memory and disable network access for security reasons */ 901 struct _parse_ctx ctx = { plist_xml, plist_xml + length, 0 };
595 xmlDocPtr plist_doc = xmlReadMemory(plist_xml, length, "plist_from_xml:memory", NULL, XML_PARSE_NONET);
596 if (plist_doc) {
597 xmlNodePtr root_node = xmlDocGetRootElement(plist_doc);
598 902
599 xml_to_node(root_node, plist); 903 node_from_xml(&ctx, plist);
600 xmlFreeDoc(plist_doc);
601 }
602} 904}