Coverage Report

Created: 2025-10-10 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libplist/src/xplist.c
Line
Count
Source
1
/*
2
 * xplist.c
3
 * XML plist implementation
4
 *
5
 * Copyright (c) 2010-2017 Nikias Bassen All Rights Reserved.
6
 * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved.
7
 * Copyright (c) 2008 Jonathan Beck All Rights Reserved.
8
 *
9
 * This library is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU Lesser General Public
11
 * License as published by the Free Software Foundation; either
12
 * version 2.1 of the License, or (at your option) any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public
20
 * License along with this library; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22
 */
23
24
#ifdef HAVE_CONFIG_H
25
#include <config.h>
26
#endif
27
28
#ifdef HAVE_STRPTIME
29
#define _XOPEN_SOURCE 600
30
#endif
31
32
#include <string.h>
33
#include <assert.h>
34
#include <stdlib.h>
35
#include <stdio.h>
36
#include <time.h>
37
38
#include <inttypes.h>
39
#include <float.h>
40
#include <math.h>
41
#include <limits.h>
42
43
#include <node.h>
44
45
#include "plist.h"
46
#include "base64.h"
47
#include "strbuf.h"
48
#include "time64.h"
49
50
21.5k
#define XPLIST_KEY  "key"
51
0
#define XPLIST_KEY_LEN 3
52
22.3k
#define XPLIST_FALSE  "false"
53
0
#define XPLIST_FALSE_LEN 5
54
23.2k
#define XPLIST_TRUE "true"
55
0
#define XPLIST_TRUE_LEN 4
56
25.5k
#define XPLIST_INT  "integer"
57
0
#define XPLIST_INT_LEN 7
58
24.0k
#define XPLIST_REAL "real"
59
0
#define XPLIST_REAL_LEN 4
60
3.44k
#define XPLIST_DATE "date"
61
0
#define XPLIST_DATE_LEN 4
62
4.81k
#define XPLIST_DATA "data"
63
0
#define XPLIST_DATA_LEN 4
64
21.9k
#define XPLIST_STRING "string"
65
0
#define XPLIST_STRING_LEN 6
66
47.3k
#define XPLIST_ARRAY  "array"
67
0
#define XPLIST_ARRAY_LEN 5
68
35.5k
#define XPLIST_DICT "dict"
69
0
#define XPLIST_DICT_LEN 4
70
71
1.75k
#define MAC_EPOCH 978307200
72
73
0
#define MAX_DATA_BYTES_PER_LINE(__i) (((76 - ((__i) << 3)) >> 2) * 3)
74
75
static const char XML_PLIST_PROLOG[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
76
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
77
<plist version=\"1.0\">\n";
78
static const char XML_PLIST_EPILOG[] = "</plist>\n";
79
80
#ifdef DEBUG
81
static int plist_xml_debug = 0;
82
3.95k
#define PLIST_XML_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlparser] ERROR: " __VA_ARGS__); }
83
0
#define PLIST_XML_WRITE_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlwriter] ERROR: " __VA_ARGS__); }
84
#else
85
#define PLIST_XML_ERR(...)
86
#define PLIST_XML_WRITE_ERR(...)
87
#endif
88
89
void plist_xml_init(void)
90
8
{
91
    /* init XML stuff */
92
8
#ifdef DEBUG
93
8
    char *env_debug = getenv("PLIST_XML_DEBUG");
94
8
    if (env_debug && !strcmp(env_debug, "1")) {
95
0
        plist_xml_debug = 1;
96
0
    }
97
8
#endif
98
8
}
99
100
void plist_xml_deinit(void)
101
0
{
102
    /* deinit XML stuff */
103
0
}
104
105
void plist_xml_set_debug(int debug)
106
0
{
107
0
#if DEBUG
108
0
    plist_xml_debug = debug;
109
0
#endif
110
0
}
111
112
static size_t dtostr(char *buf, size_t bufsize, double realval)
113
0
{
114
0
    size_t len = 0;
115
0
    if (isnan(realval)) {
116
0
        len = snprintf(buf, bufsize, "nan");
117
0
    } else if (isinf(realval)) {
118
0
        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
119
0
    } else if (realval == 0.0f) {
120
0
        len = snprintf(buf, bufsize, "0.0");
121
0
    } else {
122
0
        size_t i = 0;
123
0
        len = snprintf(buf, bufsize, "%.*g", 17, realval);
124
0
        for (i = 0; buf && i < len; i++) {
125
0
            if (buf[i] == ',') {
126
0
                buf[i] = '.';
127
0
                break;
128
0
            } else if (buf[i] == '.') {
129
0
                break;
130
0
            }
131
0
        }
132
0
    }
133
0
    return len;
134
0
}
135
136
static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
137
0
{
138
0
    plist_data_t node_data = NULL;
139
140
0
    char isStruct = FALSE;
141
0
    char tagOpen = FALSE;
142
143
0
    const char *tag = NULL;
144
0
    size_t tag_len = 0;
145
0
    char *val = NULL;
146
0
    size_t val_len = 0;
147
148
0
    uint32_t i = 0;
149
150
0
    if (!node) {
151
0
        PLIST_XML_WRITE_ERR("Encountered invalid empty node in property list\n");
152
0
        return PLIST_ERR_INVALID_ARG;
153
0
    }
154
155
0
    node_data = plist_get_data(node);
156
157
0
    switch (node_data->type)
158
0
    {
159
0
    case PLIST_BOOLEAN:
160
0
    {
161
0
        if (node_data->boolval) {
162
0
            tag = XPLIST_TRUE;
163
0
            tag_len = XPLIST_TRUE_LEN;
164
0
        } else {
165
0
            tag = XPLIST_FALSE;
166
0
            tag_len = XPLIST_FALSE_LEN;
167
0
        }
168
0
    }
169
0
    break;
170
171
0
    case PLIST_INT:
172
0
        tag = XPLIST_INT;
173
0
        tag_len = XPLIST_INT_LEN;
174
0
        val = (char*)malloc(64);
175
0
        if (node_data->length == 16) {
176
0
            val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
177
0
        } else {
178
0
            val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
179
0
        }
180
0
        break;
181
182
0
    case PLIST_REAL:
183
0
        tag = XPLIST_REAL;
184
0
        tag_len = XPLIST_REAL_LEN;
185
0
        val = (char*)malloc(64);
186
0
        val_len = dtostr(val, 64, node_data->realval);
187
0
        break;
188
189
0
    case PLIST_STRING:
190
0
        tag = XPLIST_STRING;
191
0
        tag_len = XPLIST_STRING_LEN;
192
        /* contents processed directly below */
193
0
        break;
194
195
0
    case PLIST_KEY:
196
0
        tag = XPLIST_KEY;
197
0
        tag_len = XPLIST_KEY_LEN;
198
        /* contents processed directly below */
199
0
        break;
200
201
0
    case PLIST_DATA:
202
0
        tag = XPLIST_DATA;
203
0
        tag_len = XPLIST_DATA_LEN;
204
        /* contents processed directly below */
205
0
        break;
206
0
    case PLIST_ARRAY:
207
0
        tag = XPLIST_ARRAY;
208
0
        tag_len = XPLIST_ARRAY_LEN;
209
0
        isStruct = (node->children) ? TRUE : FALSE;
210
0
        break;
211
0
    case PLIST_DICT:
212
0
        tag = XPLIST_DICT;
213
0
        tag_len = XPLIST_DICT_LEN;
214
0
        isStruct = (node->children) ? TRUE : FALSE;
215
0
        break;
216
0
    case PLIST_DATE:
217
0
        tag = XPLIST_DATE;
218
0
        tag_len = XPLIST_DATE_LEN;
219
0
        {
220
0
            Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
221
0
            struct TM _btime;
222
0
            struct TM *btime = gmtime64_r(&timev, &_btime);
223
0
            if (btime) {
224
0
                val = (char*)calloc(1, 24);
225
0
                struct tm _tmcopy;
226
0
                copy_TM64_to_tm(btime, &_tmcopy);
227
0
                val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
228
0
                if (val_len <= 0) {
229
0
                    free (val);
230
0
                    val = NULL;
231
0
                }
232
0
            }
233
0
        }
234
0
        break;
235
0
    case PLIST_UID:
236
0
        tag = XPLIST_DICT;
237
0
        tag_len = XPLIST_DICT_LEN;
238
0
        val = (char*)malloc(64);
239
0
        if (node_data->length == 16) {
240
0
            val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
241
0
        } else {
242
0
            val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
243
0
        }
244
0
        break;
245
0
    case PLIST_NULL:
246
0
        PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
247
0
        return PLIST_ERR_FORMAT;
248
0
    default:
249
0
        return PLIST_ERR_UNKNOWN;
250
0
    }
251
252
0
    for (i = 0; i < depth; i++) {
253
0
        str_buf_append(*outbuf, "\t", 1);
254
0
    }
255
256
    /* append tag */
257
0
    str_buf_append(*outbuf, "<", 1);
258
0
    str_buf_append(*outbuf, tag, tag_len);
259
0
    if (node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) {
260
0
        size_t j;
261
0
        size_t len;
262
0
        off_t start = 0;
263
0
        off_t cur = 0;
264
265
0
        str_buf_append(*outbuf, ">", 1);
266
0
        tagOpen = TRUE;
267
268
        /* make sure we convert the following predefined xml entities */
269
        /* < = &lt; > = &gt; & = &amp; */
270
0
        len = node_data->length;
271
0
        for (j = 0; j < len; j++) {
272
0
            switch (node_data->strval[j]) {
273
0
            case '<':
274
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
275
0
                str_buf_append(*outbuf, "&lt;", 4);
276
0
                start = cur+1;
277
0
                break;
278
0
            case '>':
279
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
280
0
                str_buf_append(*outbuf, "&gt;", 4);
281
0
                start = cur+1;
282
0
                break;
283
0
            case '&':
284
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
285
0
                str_buf_append(*outbuf, "&amp;", 5);
286
0
                start = cur+1;
287
0
                break;
288
0
            default:
289
0
                break;
290
0
            }
291
0
            cur++;
292
0
        }
293
0
        str_buf_append(*outbuf, node_data->strval + start, cur - start);
294
0
    } else if (node_data->type == PLIST_DATA) {
295
0
        str_buf_append(*outbuf, ">", 1);
296
0
        tagOpen = TRUE;
297
0
        str_buf_append(*outbuf, "\n", 1);
298
0
        if (node_data->length > 0) {
299
0
            uint32_t j = 0;
300
0
            uint32_t indent = (depth > 8) ? 8 : depth;
301
0
            uint32_t maxread = MAX_DATA_BYTES_PER_LINE(indent);
302
0
            size_t count = 0;
303
0
            size_t amount = (node_data->length / 3 * 4) + 4 + (((node_data->length / maxread) + 1) * (indent+1));
304
0
            if ((*outbuf)->len + amount > (*outbuf)->capacity) {
305
0
                str_buf_grow(*outbuf, amount);
306
0
            }
307
0
            while (j < node_data->length) {
308
0
                for (i = 0; i < indent; i++) {
309
0
                    str_buf_append(*outbuf, "\t", 1);
310
0
                }
311
0
                count = (node_data->length-j < maxread) ? node_data->length-j : maxread;
312
0
                assert((*outbuf)->len + count < (*outbuf)->capacity);
313
0
                (*outbuf)->len += base64encode((char*)(*outbuf)->data + (*outbuf)->len, node_data->buff + j, count);
314
0
                str_buf_append(*outbuf, "\n", 1);
315
0
                j+=count;
316
0
            }
317
0
        }
318
0
        for (i = 0; i < depth; i++) {
319
0
            str_buf_append(*outbuf, "\t", 1);
320
0
        }
321
0
    } else if (node_data->type == PLIST_UID) {
322
        /* special case for UID nodes: create a DICT */
323
0
        str_buf_append(*outbuf, ">", 1);
324
0
        tagOpen = TRUE;
325
0
        str_buf_append(*outbuf, "\n", 1);
326
327
        /* add CF$UID key */
328
0
        for (i = 0; i < depth+1; i++) {
329
0
            str_buf_append(*outbuf, "\t", 1);
330
0
        }
331
0
        str_buf_append(*outbuf, "<key>CF$UID</key>", 17);
332
0
        str_buf_append(*outbuf, "\n", 1);
333
334
        /* add UID value */
335
0
        for (i = 0; i < depth+1; i++) {
336
0
            str_buf_append(*outbuf, "\t", 1);
337
0
        }
338
0
        str_buf_append(*outbuf, "<integer>", 9);
339
0
        str_buf_append(*outbuf, val, val_len);
340
0
        str_buf_append(*outbuf, "</integer>", 10);
341
0
        str_buf_append(*outbuf, "\n", 1);
342
343
0
        for (i = 0; i < depth; i++) {
344
0
            str_buf_append(*outbuf, "\t", 1);
345
0
        }
346
0
    } else if (val) {
347
0
        str_buf_append(*outbuf, ">", 1);
348
0
        tagOpen = TRUE;
349
0
        str_buf_append(*outbuf, val, val_len);
350
0
    } else if (isStruct) {
351
0
        tagOpen = TRUE;
352
0
        str_buf_append(*outbuf, ">", 1);
353
0
    } else {
354
0
        tagOpen = FALSE;
355
0
        str_buf_append(*outbuf, "/>", 2);
356
0
    }
357
0
    free(val);
358
359
0
    if (isStruct) {
360
        /* add newline for structured types */
361
0
        str_buf_append(*outbuf, "\n", 1);
362
363
        /* add child nodes */
364
0
        if (node_data->type == PLIST_DICT && node->children) {
365
0
            assert((node->children->count % 2) == 0);
366
0
        }
367
0
        node_t ch;
368
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
369
0
            plist_err_t res = node_to_xml(ch, outbuf, depth+1);
370
0
            if (res < 0) return res;
371
0
        }
372
373
        /* fix indent for structured types */
374
0
        for (i = 0; i < depth; i++) {
375
0
            str_buf_append(*outbuf, "\t", 1);
376
0
        }
377
0
    }
378
379
0
    if (tagOpen) {
380
        /* add closing tag */
381
0
        str_buf_append(*outbuf, "</", 2);
382
0
        str_buf_append(*outbuf, tag, tag_len);
383
0
        str_buf_append(*outbuf, ">", 1);
384
0
    }
385
0
    str_buf_append(*outbuf, "\n", 1);
386
0
    return PLIST_ERR_SUCCESS;
387
0
}
388
389
static void parse_date(const char *strval, struct TM *btime)
390
935
{
391
935
    if (!btime) return;
392
935
    memset(btime, 0, sizeof(struct tm));
393
935
    if (!strval) return;
394
935
#ifdef HAVE_STRPTIME
395
935
    strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime);
396
#else
397
#ifdef USE_TM64
398
    #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ"
399
#else
400
    #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ"
401
#endif
402
    sscanf(strval, PLIST_SSCANF_FORMAT, &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec);
403
    btime->tm_year-=1900;
404
    btime->tm_mon--;
405
#endif
406
935
    btime->tm_isdst=0;
407
935
}
408
409
0
#define PO10i_LIMIT (INT64_MAX/10)
410
411
/* based on https://stackoverflow.com/a/4143288 */
412
static int num_digits_i(int64_t i)
413
0
{
414
0
    int n;
415
0
    int64_t po10;
416
0
    n=1;
417
0
    if (i < 0) {
418
0
        i = (i == INT64_MIN) ? INT64_MAX : -i;
419
0
        n++;
420
0
    }
421
0
    po10=10;
422
0
    while (i>=po10) {
423
0
        n++;
424
0
        if (po10 > PO10i_LIMIT) break;
425
0
        po10*=10;
426
0
    }
427
0
    return n;
428
0
}
429
430
0
#define PO10u_LIMIT (UINT64_MAX/10)
431
432
/* based on https://stackoverflow.com/a/4143288 */
433
static int num_digits_u(uint64_t i)
434
0
{
435
0
    int n;
436
0
    uint64_t po10;
437
0
    n=1;
438
0
    po10=10;
439
0
    while (i>=po10) {
440
0
        n++;
441
0
        if (po10 > PO10u_LIMIT) break;
442
0
        po10*=10;
443
0
    }
444
0
    return n;
445
0
}
446
447
static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
448
0
{
449
0
    plist_data_t data;
450
0
    if (!node) {
451
0
        return PLIST_ERR_INVALID_ARG;
452
0
    }
453
0
    data = plist_get_data(node);
454
0
    if (node->children) {
455
0
        node_t ch;
456
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
457
0
            node_estimate_size(ch, size, depth + 1);
458
0
        }
459
0
        switch (data->type) {
460
0
        case PLIST_DICT:
461
0
            *size += (XPLIST_DICT_LEN << 1) + 7;
462
0
            break;
463
0
        case PLIST_ARRAY:
464
0
            *size += (XPLIST_ARRAY_LEN << 1) + 7;
465
0
            break;
466
0
        default:
467
0
            break;
468
0
  }
469
0
        *size += (depth << 1);
470
0
    } else {
471
0
        uint32_t indent = (depth > 8) ? 8 : depth;
472
0
        switch (data->type) {
473
0
        case PLIST_DATA: {
474
0
            uint32_t req_lines = (data->length / MAX_DATA_BYTES_PER_LINE(indent)) + 1;
475
0
            uint32_t b64len = data->length + (data->length / 3);
476
0
            b64len += b64len % 4;
477
0
            *size += b64len;
478
0
            *size += (XPLIST_DATA_LEN << 1) + 5 + (indent+1) * (req_lines+1) + 1;
479
0
        }   break;
480
0
        case PLIST_STRING:
481
0
            *size += data->length;
482
0
            *size += (XPLIST_STRING_LEN << 1) + 6;
483
0
            break;
484
0
        case PLIST_KEY:
485
0
            *size += data->length;
486
0
            *size += (XPLIST_KEY_LEN << 1) + 6;
487
0
            break;
488
0
        case PLIST_INT:
489
0
            if (data->length == 16) {
490
0
                *size += num_digits_u(data->intval);
491
0
            } else {
492
0
                *size += num_digits_i((int64_t)data->intval);
493
0
            }
494
0
            *size += (XPLIST_INT_LEN << 1) + 6;
495
0
            break;
496
0
        case PLIST_REAL:
497
0
            *size += dtostr(NULL, 0, data->realval);
498
0
            *size += (XPLIST_REAL_LEN << 1) + 6;
499
0
            break;
500
0
        case PLIST_DATE:
501
0
            *size += 20; /* YYYY-MM-DDThh:mm:ssZ */
502
0
            *size += (XPLIST_DATE_LEN << 1) + 6;
503
0
            break;
504
0
        case PLIST_BOOLEAN:
505
0
            *size += ((data->boolval) ? XPLIST_TRUE_LEN : XPLIST_FALSE_LEN) + 4;
506
0
            break;
507
0
        case PLIST_DICT:
508
0
            *size += XPLIST_DICT_LEN + 4; /* <dict/> */
509
0
            break;
510
0
        case PLIST_ARRAY:
511
0
            *size += XPLIST_ARRAY_LEN + 4; /* <array/> */
512
0
            break;
513
0
        case PLIST_UID:
514
0
            *size += num_digits_i((int64_t)data->intval);
515
0
            *size += (XPLIST_DICT_LEN << 1) + 7;
516
0
            *size += indent + ((indent+1) << 1);
517
0
            *size += 18; /* <key>CF$UID</key> */
518
0
            *size += (XPLIST_INT_LEN << 1) + 6;
519
0
            break;
520
0
        case PLIST_NULL:
521
0
            PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
522
0
            return PLIST_ERR_FORMAT;
523
0
        default:
524
0
            PLIST_XML_WRITE_ERR("invalid node type encountered\n");
525
0
            return PLIST_ERR_UNKNOWN;
526
0
        }
527
0
        *size += indent;
528
0
    }
529
0
    return PLIST_ERR_SUCCESS;
530
0
}
531
532
plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
533
0
{
534
0
    uint64_t size = 0;
535
0
    plist_err_t res;
536
537
0
    if (!plist || !plist_xml || !length) {
538
0
        return PLIST_ERR_INVALID_ARG;
539
0
    }
540
541
0
    res = node_estimate_size((node_t)plist, &size, 0);
542
0
    if (res < 0) {
543
0
        return res;
544
0
    }
545
0
    size += sizeof(XML_PLIST_PROLOG) + sizeof(XML_PLIST_EPILOG) - 1;
546
547
0
    strbuf_t *outbuf = str_buf_new(size);
548
0
    if (!outbuf) {
549
0
        PLIST_XML_WRITE_ERR("Could not allocate output buffer\n");
550
0
        return PLIST_ERR_NO_MEM;
551
0
    }
552
553
0
    str_buf_append(outbuf, XML_PLIST_PROLOG, sizeof(XML_PLIST_PROLOG)-1);
554
555
0
    res = node_to_xml((node_t)plist, &outbuf, 0);
556
0
    if (res < 0) {
557
0
        str_buf_free(outbuf);
558
0
        *plist_xml = NULL;
559
0
        *length = 0;
560
0
        return res;
561
0
    }
562
563
0
    str_buf_append(outbuf, XML_PLIST_EPILOG, sizeof(XML_PLIST_EPILOG));
564
565
0
    *plist_xml = (char*)outbuf->data;
566
0
    *length = outbuf->len - 1;
567
568
0
    outbuf->data = NULL;
569
0
    str_buf_free(outbuf);
570
571
0
    return PLIST_ERR_SUCCESS;
572
0
}
573
574
struct _parse_ctx {
575
    const char *pos;
576
    const char *end;
577
    int err;
578
};
579
typedef struct _parse_ctx* parse_ctx;
580
581
static void parse_skip_ws(parse_ctx ctx)
582
54.2k
{
583
58.2k
    while (ctx->pos < ctx->end && ((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n'))) {
584
4.01k
        ctx->pos++;
585
4.01k
    }
586
54.2k
}
587
588
static void find_char(parse_ctx ctx, char c, int skip_quotes)
589
18.4k
{
590
117k
    while (ctx->pos < ctx->end && (*(ctx->pos) != c)) {
591
99.0k
        if (skip_quotes && (c != '"') && (*(ctx->pos) == '"')) {
592
0
            ctx->pos++;
593
0
            find_char(ctx, '"', 0);
594
0
            if (ctx->pos >= ctx->end) {
595
0
                PLIST_XML_ERR("EOF while looking for matching double quote\n");
596
0
                return;
597
0
            }
598
0
            if (*(ctx->pos) != '"') {
599
0
                PLIST_XML_ERR("Unmatched double quote\n");
600
0
                return;
601
0
            }
602
0
        }
603
99.0k
        ctx->pos++;
604
99.0k
    }
605
18.4k
}
606
607
static void find_str(parse_ctx ctx, const char *str, size_t len, int skip_quotes)
608
7.89k
{
609
13.0k
    while (ctx->pos < (ctx->end - len)) {
610
12.6k
        if (!strncmp(ctx->pos, str, len)) {
611
7.45k
            break;
612
7.45k
        }
613
5.14k
        if (skip_quotes && (*(ctx->pos) == '"')) {
614
263
            ctx->pos++;
615
263
            find_char(ctx, '"', 0);
616
263
            if (ctx->pos >= ctx->end) {
617
12
                PLIST_XML_ERR("EOF while looking for matching double quote\n");
618
12
                return;
619
12
            }
620
251
            if (*(ctx->pos) != '"') {
621
0
                PLIST_XML_ERR("Unmatched double quote\n");
622
0
                return;
623
0
            }
624
251
        }
625
5.13k
        ctx->pos++;
626
5.13k
    }
627
7.89k
}
628
629
static void find_next(parse_ctx ctx, const char *nextchars, int numchars, int skip_quotes)
630
36.8k
{
631
36.8k
    int i = 0;
632
200k
    while (ctx->pos < ctx->end) {
633
200k
        if (skip_quotes && (*(ctx->pos) == '"')) {
634
703
            ctx->pos++;
635
703
            find_char(ctx, '"', 0);
636
703
            if (ctx->pos >= ctx->end) {
637
62
                PLIST_XML_ERR("EOF while looking for matching double quote\n");
638
62
                return;
639
62
            }
640
641
            if (*(ctx->pos) != '"') {
641
0
                PLIST_XML_ERR("Unmatched double quote\n");
642
0
                return;
643
0
            }
644
641
        }
645
1.33M
        for (i = 0; i < numchars; i++) {
646
1.17M
            if (*(ctx->pos) == nextchars[i]) {
647
36.4k
                return;
648
36.4k
            }
649
1.17M
        }
650
164k
        ctx->pos++;
651
164k
    }
652
36.8k
}
653
654
typedef struct {
655
    const char *begin;
656
    size_t length;
657
    int is_cdata;
658
    void *next;
659
} text_part_t;
660
661
static text_part_t* text_part_init(text_part_t* part, const char *begin, size_t length, int is_cdata)
662
12.6k
{
663
12.6k
    part->begin = begin;
664
12.6k
    part->length = length;
665
12.6k
    part->is_cdata = is_cdata;
666
12.6k
    part->next = NULL;
667
12.6k
    return part;
668
12.6k
}
669
670
static void text_parts_free(text_part_t *tp)
671
10.7k
{
672
16.3k
    while (tp) {
673
5.57k
        text_part_t *tmp = tp;
674
5.57k
        tp = (text_part_t*)tp->next;
675
5.57k
        free(tmp);
676
5.57k
    }
677
10.7k
}
678
679
static text_part_t* text_part_append(text_part_t* parts, const char *begin, size_t length, int is_cdata)
680
5.57k
{
681
5.57k
    text_part_t* newpart = (text_part_t*)malloc(sizeof(text_part_t));
682
5.57k
    assert(newpart);
683
5.57k
    parts->next = text_part_init(newpart, begin, length, is_cdata);
684
5.57k
    return newpart;
685
5.57k
}
686
687
static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_len, int skip_ws, text_part_t *parts)
688
11.4k
{
689
11.4k
    const char *p = NULL;
690
11.4k
    const char *q = NULL;
691
11.4k
    text_part_t *last = NULL;
692
693
11.4k
    if (skip_ws) {
694
5.57k
        parse_skip_ws(ctx);
695
5.57k
    }
696
17.4k
    do {
697
17.4k
        p = ctx->pos;
698
17.4k
        find_char(ctx, '<', 0);
699
17.4k
        if (ctx->pos >= ctx->end || *ctx->pos != '<') {
700
188
            PLIST_XML_ERR("EOF while looking for closing tag\n");
701
188
            ctx->err++;
702
188
            return NULL;
703
188
        }
704
17.2k
        q = ctx->pos;
705
17.2k
        ctx->pos++;
706
17.2k
        if (ctx->pos >= ctx->end) {
707
5
            PLIST_XML_ERR("EOF while parsing '%s'\n", p);
708
5
            ctx->err++;
709
5
            return NULL;
710
5
        }
711
17.2k
        if (*ctx->pos == '!') {
712
6.25k
            ctx->pos++;
713
6.25k
            if (ctx->pos >= ctx->end-1) {
714
1
                PLIST_XML_ERR("EOF while parsing <! special tag\n");
715
1
                ctx->err++;
716
1
                return NULL;
717
1
            }
718
6.25k
            if (*ctx->pos == '-' && *(ctx->pos+1) == '-') {
719
2.90k
                if (last) {
720
945
                    last = text_part_append(last, p, q-p, 0);
721
1.95k
                } else if (parts) {
722
1.50k
                    last = text_part_init(parts, p, q-p, 0);
723
1.50k
                }
724
2.90k
                ctx->pos += 2;
725
2.90k
                find_str(ctx, "-->", 3, 0);
726
2.90k
                if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
727
112
                    PLIST_XML_ERR("EOF while looking for end of comment\n");
728
112
                    ctx->err++;
729
112
                    return NULL;
730
112
                }
731
2.79k
                ctx->pos += 3;
732
3.35k
            } else if (*ctx->pos == '[') {
733
3.32k
                ctx->pos++;
734
3.32k
                if (ctx->pos >= ctx->end - 8) {
735
4
                    PLIST_XML_ERR("EOF while parsing <[ tag\n");
736
4
                    ctx->err++;
737
4
                    return NULL;
738
4
                }
739
3.31k
                if (strncmp(ctx->pos, "CDATA[", 6) == 0) {
740
3.26k
                    if (q-p > 0) {
741
1.67k
                        if (last) {
742
1.22k
                            last = text_part_append(last, p, q-p, 0);
743
1.22k
                        } else if (parts) {
744
252
                            last = text_part_init(parts, p, q-p, 0);
745
252
                        }
746
1.67k
                    }
747
3.26k
                    ctx->pos+=6;
748
3.26k
                    p = ctx->pos;
749
3.26k
                    find_str(ctx, "]]>", 3, 0);
750
3.26k
                    if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "]]>", 3) != 0) {
751
85
                        PLIST_XML_ERR("EOF while looking for end of CDATA block\n");
752
85
                        ctx->err++;
753
85
                        return NULL;
754
85
                    }
755
3.18k
                    q = ctx->pos;
756
3.18k
                    if (last) {
757
2.37k
                        last = text_part_append(last, p, q-p, 1);
758
2.37k
                    } else if (parts) {
759
421
                        last = text_part_init(parts, p, q-p, 1);
760
421
                    }
761
3.18k
                    ctx->pos += 3;
762
3.18k
                } else {
763
52
                    p = ctx->pos;
764
52
                    find_next(ctx, " \r\n\t>", 5, 1);
765
52
                    PLIST_XML_ERR("Invalid special tag <[%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
766
52
                    ctx->err++;
767
52
                    return NULL;
768
52
                }
769
3.31k
            } else {
770
31
                p = ctx->pos;
771
31
                find_next(ctx, " \r\n\t>", 5, 1);
772
31
                PLIST_XML_ERR("Invalid special tag <!%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
773
31
                ctx->err++;
774
31
                return NULL;
775
31
            }
776
10.9k
        } else if (*ctx->pos == '/') {
777
10.9k
            break;
778
10.9k
        } else {
779
26
            p = ctx->pos;
780
26
            find_next(ctx, " \r\n\t>", 5, 1);
781
26
            PLIST_XML_ERR("Invalid tag <%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
782
26
            ctx->err++;
783
26
            return NULL;
784
26
        }
785
17.2k
    } while (1);
786
10.9k
    ctx->pos++;
787
10.9k
    if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len) != 0) {
788
64
        PLIST_XML_ERR("EOF or end tag mismatch\n");
789
64
        ctx->err++;
790
64
        return NULL;
791
64
    }
792
10.8k
    ctx->pos+=tag_len;
793
10.8k
    parse_skip_ws(ctx);
794
10.8k
    if (ctx->pos >= ctx->end) {
795
30
        PLIST_XML_ERR("EOF while parsing closing tag\n");
796
30
        ctx->err++;
797
30
        return NULL;
798
10.8k
    } else if (*ctx->pos != '>') {
799
22
        PLIST_XML_ERR("Invalid closing tag; expected '>', found '%c'\n", *ctx->pos);
800
22
        ctx->err++;
801
22
        return NULL;
802
22
    }
803
10.8k
    ctx->pos++;
804
805
10.8k
    if (q-p > 0) {
806
6.18k
        if (last) {
807
1.02k
            last = text_part_append(last, p, q-p, 0);
808
5.15k
        } else if (parts) {
809
4.91k
            last = text_part_init(parts, p, q-p, 0);
810
4.91k
        }
811
6.18k
    }
812
10.8k
    return parts;
813
10.8k
}
814
815
static int unescape_entities(char *str, size_t *length)
816
3.46k
{
817
3.46k
    size_t i = 0;
818
3.46k
    size_t len = *length;
819
18.5k
    while (len > 0 && i < len-1) {
820
15.2k
        if (str[i] == '&') {
821
2.70k
            char *entp = str + i + 1;
822
26.5k
            while (i < len && str[i] != ';') {
823
23.8k
                i++;
824
23.8k
            }
825
2.70k
            if (i >= len) {
826
19
                PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n");
827
19
                return -1;
828
19
            }
829
2.68k
            if (str+i >= entp+1) {
830
2.67k
                int entlen = str+i - entp;
831
2.67k
                int bytelen = 1;
832
2.67k
                if (!strncmp(entp, "amp", 3)) {
833
                    /* the '&' is already there */
834
2.47k
                } else if (!strncmp(entp, "apos", 4)) {
835
194
                    *(entp-1) = '\'';
836
2.28k
                } else if (!strncmp(entp, "quot", 4)) {
837
194
                    *(entp-1) = '"';
838
2.08k
                } else if (!strncmp(entp, "lt", 2)) {
839
205
                    *(entp-1) = '<';
840
1.88k
                } else if (!strncmp(entp, "gt", 2)) {
841
216
                    *(entp-1) = '>';
842
1.66k
                } else if (*entp == '#') {
843
                    /* numerical  character reference */
844
1.57k
                    uint64_t val = 0;
845
1.57k
                    char* ep = NULL;
846
1.57k
                    if (entlen > 8) {
847
17
                        PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", entlen, entp);
848
17
                        return -1;
849
17
                    }
850
1.55k
                    if (*(entp+1) == 'x' || *(entp+1) == 'X') {
851
843
                        if (entlen < 3) {
852
18
                            PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp);
853
18
                            return -1;
854
18
                        }
855
825
                        val = strtoull(entp+2, &ep, 16);
856
825
                    } else {
857
714
                        if (entlen < 2) {
858
1
                            PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp);
859
1
                            return -1;
860
1
                        }
861
713
                        val = strtoull(entp+1, &ep, 10);
862
713
                    }
863
1.53k
                    if (val == 0 || val > 0x10FFFF || ep-entp != entlen) {
864
57
                        PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", entlen, entp);
865
57
                        return -1;
866
57
                    }
867
                    /* convert to UTF8 */
868
1.48k
                    if (val >= 0x10000) {
869
                        /* four bytes */
870
220
                        *(entp-1) = (char)(0xF0 + ((val >> 18) & 0x7));
871
220
                        *(entp+0) = (char)(0x80 + ((val >> 12) & 0x3F));
872
220
                        *(entp+1) = (char)(0x80 + ((val >> 6) & 0x3F));
873
220
                        *(entp+2) = (char)(0x80 + (val & 0x3F));
874
220
                        entp+=3;
875
220
                        bytelen = 4;
876
1.26k
                    } else if (val >= 0x800) {
877
                        /* three bytes */
878
237
                        *(entp-1) = (char)(0xE0 + ((val >> 12) & 0xF));
879
237
                        *(entp+0) = (char)(0x80 + ((val >> 6) & 0x3F));
880
237
                        *(entp+1) = (char)(0x80 + (val & 0x3F));
881
237
                        entp+=2;
882
237
                        bytelen = 3;
883
1.02k
                    } else if (val >= 0x80) {
884
                        /* two bytes */
885
222
                        *(entp-1) = (char)(0xC0 + ((val >> 6) & 0x1F));
886
222
                        *(entp+0) = (char)(0x80 + (val & 0x3F));
887
222
                        entp++;
888
222
                        bytelen = 2;
889
802
                    } else {
890
                        /* one byte */
891
802
                        *(entp-1) = (char)(val & 0x7F);
892
802
                    }
893
1.48k
                } else {
894
94
                    PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", entlen, entp);
895
94
                    return -1;
896
94
                }
897
2.48k
                memmove(entp, str+i+1, len - i);
898
2.48k
                i -= entlen+1 - bytelen;
899
2.48k
                len -= entlen+2 - bytelen;
900
2.48k
                continue;
901
2.67k
            } else {
902
11
                PLIST_XML_ERR("Invalid empty entity sequence &;\n");
903
11
                return -1;
904
11
            }
905
2.68k
        }
906
12.5k
        i++;
907
12.5k
    }
908
3.24k
    *length = len;
909
3.24k
    return 0;
910
3.46k
}
911
912
static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t *length, int *requires_free)
913
9.45k
{
914
9.45k
    char *str = NULL;
915
9.45k
    size_t total_length = 0;
916
917
9.45k
    if (!tp) {
918
0
        return NULL;
919
0
    }
920
9.45k
    char *p;
921
9.45k
    if (requires_free && !tp->next) {
922
3.18k
        if (tp->is_cdata || !unesc_entities) {
923
3.18k
            *requires_free = 0;
924
3.18k
            if (length) {
925
1.35k
                *length = tp->length;
926
1.35k
            }
927
3.18k
            return (char*)tp->begin;
928
3.18k
        }
929
3.18k
    }
930
6.26k
    text_part_t *tmp = tp;
931
13.0k
    while (tp && tp->begin) {
932
6.82k
        total_length += tp->length;
933
6.82k
        tp = (text_part_t*)tp->next;
934
6.82k
    }
935
6.26k
    str = (char*)malloc(total_length + 1);
936
6.26k
    assert(str);
937
6.26k
    p = str;
938
6.26k
    tp = tmp;
939
12.8k
    while (tp && tp->begin) {
940
6.82k
        size_t len = tp->length;
941
6.82k
        strncpy(p, tp->begin, len);
942
6.82k
        p[len] = '\0';
943
6.82k
        if (!tp->is_cdata && unesc_entities) {
944
3.46k
            if (unescape_entities(p, &len) < 0) {
945
217
                free(str);
946
217
                return NULL;
947
217
            }
948
3.46k
        }
949
6.60k
        p += len;
950
6.60k
        tp = (text_part_t*)tp->next;
951
6.60k
    }
952
6.04k
    *p = '\0';
953
6.04k
    if (length) {
954
5.43k
        *length = p - str;
955
5.43k
    }
956
6.04k
    if (requires_free) {
957
816
        *requires_free = 1;
958
816
    }
959
6.04k
    return str;
960
6.26k
}
961
962
static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist)
963
3.24k
{
964
3.24k
    char *tag = NULL;
965
3.24k
    char *keyname = NULL;
966
3.24k
    plist_t subnode = NULL;
967
3.24k
    const char *p = NULL;
968
3.24k
    plist_t parent = NULL;
969
3.24k
    int has_content = 0;
970
971
3.24k
    struct node_path_item {
972
3.24k
        const char *type;
973
3.24k
        void *prev;
974
3.24k
    };
975
3.24k
    struct node_path_item* node_path = NULL;
976
977
38.0k
    while (ctx->pos < ctx->end && !ctx->err) {
978
37.2k
        parse_skip_ws(ctx);
979
37.2k
        if (ctx->pos >= ctx->end) {
980
30
            break;
981
30
        }
982
37.2k
        if (*ctx->pos != '<') {
983
61
            p = ctx->pos;
984
61
            find_next(ctx, " \t\r\n", 4, 0);
985
61
            PLIST_XML_ERR("Expected: opening tag, found: %.*s\n", (int)(ctx->pos - p), p);
986
61
            ctx->err++;
987
61
            goto err_out;
988
61
        }
989
37.1k
        ctx->pos++;
990
37.1k
        if (ctx->pos >= ctx->end) {
991
10
            PLIST_XML_ERR("EOF while parsing tag\n");
992
10
            ctx->err++;
993
10
            goto err_out;
994
10
        }
995
996
37.1k
        if (*(ctx->pos) == '?') {
997
825
            find_str(ctx, "?>", 2, 1);
998
825
            if (ctx->pos > ctx->end-2) {
999
29
                PLIST_XML_ERR("EOF while looking for <? tag closing marker\n");
1000
29
                ctx->err++;
1001
29
                goto err_out;
1002
29
            }
1003
796
            if (strncmp(ctx->pos, "?>", 2) != 0) {
1004
42
                PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
1005
42
                ctx->err++;
1006
42
                goto err_out;
1007
42
            }
1008
754
            ctx->pos += 2;
1009
754
            continue;
1010
36.3k
        } else if (*(ctx->pos) == '!') {
1011
            /* comment or DTD */
1012
1.39k
            if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) {
1013
482
                ctx->pos += 3;
1014
482
                find_str(ctx,"-->", 3, 0);
1015
482
                if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
1016
59
                    PLIST_XML_ERR("Couldn't find end of comment\n");
1017
59
                    ctx->err++;
1018
59
                    goto err_out;
1019
59
                }
1020
423
                ctx->pos+=3;
1021
911
            } else if (((ctx->end - ctx->pos) > 8) && !strncmp(ctx->pos, "!DOCTYPE", 8)) {
1022
768
                int embedded_dtd = 0;
1023
768
                ctx->pos+=8;
1024
1.28k
                while (ctx->pos < ctx->end) {
1025
1.25k
                    find_next(ctx, " \t\r\n[>", 6, 1);
1026
1.25k
                    if (ctx->pos >= ctx->end) {
1027
64
                        PLIST_XML_ERR("EOF while parsing !DOCTYPE\n");
1028
64
                        ctx->err++;
1029
64
                        goto err_out;
1030
64
                    }
1031
1.19k
                    if (*ctx->pos == '[') {
1032
412
                        embedded_dtd = 1;
1033
412
                        break;
1034
778
                    } else if (*ctx->pos == '>') {
1035
                        /* end of DOCTYPE found already */
1036
266
                        ctx->pos++;
1037
266
                        break;
1038
512
                    } else {
1039
512
                        parse_skip_ws(ctx);
1040
512
                    }
1041
1.19k
                }
1042
704
                if (embedded_dtd) {
1043
412
                    find_str(ctx, "]>", 2, 1);
1044
412
                    if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) {
1045
31
                        PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
1046
31
                        ctx->err++;
1047
31
                        goto err_out;
1048
31
                    }
1049
381
                    ctx->pos += 2;
1050
381
                }
1051
704
            } else {
1052
143
                p = ctx->pos;
1053
143
                find_next(ctx, " \r\n\t>", 5, 1);
1054
143
                PLIST_XML_ERR("Invalid or incomplete special tag <%.*s> encountered\n", (int)(ctx->pos - p), p);
1055
143
                ctx->err++;
1056
143
                goto err_out;
1057
143
            }
1058
1.09k
            continue;
1059
34.9k
        } else {
1060
34.9k
            int is_empty = 0;
1061
34.9k
            int closing_tag = 0;
1062
34.9k
            p = ctx->pos;
1063
34.9k
            find_next(ctx," \r\n\t<>", 6, 0);
1064
34.9k
            if (ctx->pos >= ctx->end) {
1065
15
                PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
1066
15
                ctx->err++;
1067
15
                goto err_out;
1068
15
            }
1069
34.9k
            int taglen = ctx->pos - p;
1070
34.9k
            tag = (char*)malloc(taglen + 1);
1071
34.9k
            strncpy(tag, p, taglen);
1072
34.9k
            tag[taglen] = '\0';
1073
34.9k
            if (*ctx->pos != '>') {
1074
361
                find_next(ctx, "<>", 2, 1);
1075
361
            }
1076
34.9k
            if (ctx->pos >= ctx->end) {
1077
53
                PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
1078
53
                ctx->err++;
1079
53
                goto err_out;
1080
53
            }
1081
34.8k
            if (*ctx->pos != '>') {
1082
6
                PLIST_XML_ERR("Missing '>' for tag <%s\n", tag);
1083
6
                ctx->err++;
1084
6
                goto err_out;
1085
6
            }
1086
34.8k
            if (*(ctx->pos-1) == '/') {
1087
13.1k
                int idx = ctx->pos - p - 1;
1088
13.1k
                if (idx < taglen)
1089
12.8k
                    tag[idx] = '\0';
1090
13.1k
                is_empty = 1;
1091
13.1k
            }
1092
34.8k
            ctx->pos++;
1093
34.8k
            if (!strcmp(tag, "plist")) {
1094
594
                free(tag);
1095
594
                tag = NULL;
1096
594
                has_content = 0;
1097
1098
594
                if (!node_path && *plist) {
1099
                    /* we don't allow another top-level <plist> */
1100
1
                    break;
1101
1
                }
1102
593
                if (is_empty) {
1103
1
                    PLIST_XML_ERR("Empty plist tag\n");
1104
1
                    ctx->err++;
1105
1
                    goto err_out;
1106
1
                }
1107
1108
592
                struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
1109
592
                if (!path_item) {
1110
0
                    PLIST_XML_ERR("out of memory when allocating node path item\n");
1111
0
                    ctx->err++;
1112
0
                    goto err_out;
1113
0
                }
1114
592
                path_item->type = "plist";
1115
592
                path_item->prev = node_path;
1116
592
                node_path = path_item;
1117
1118
592
                continue;
1119
34.2k
            } else if (!strcmp(tag, "/plist")) {
1120
197
                if (!has_content) {
1121
1
                    PLIST_XML_ERR("encountered empty plist tag\n");
1122
1
                    ctx->err++;
1123
1
                    goto err_out;
1124
1
                }
1125
196
                if (!node_path) {
1126
1
                    PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
1127
1
                    ctx->err++;
1128
1
                    goto err_out;
1129
1
                }
1130
195
                if (strcmp(node_path->type, tag+1) != 0) {
1131
1
                    PLIST_XML_ERR("mismatching closing tag <%s> found for opening tag <%s>\n", tag, node_path->type);
1132
1
                    ctx->err++;
1133
1
                    goto err_out;
1134
1
                }
1135
194
                struct node_path_item *path_item = node_path;
1136
194
                node_path = (struct node_path_item*)node_path->prev;
1137
194
                free(path_item);
1138
1139
194
                free(tag);
1140
194
                tag = NULL;
1141
1142
194
                continue;
1143
195
            }
1144
1145
34.0k
            plist_data_t data = plist_new_plist_data();
1146
34.0k
            subnode = plist_new_node(data);
1147
34.0k
            has_content = 1;
1148
1149
34.0k
            if (!strcmp(tag, XPLIST_DICT)) {
1150
1.68k
                data->type = PLIST_DICT;
1151
32.3k
            } else if (!strcmp(tag, XPLIST_ARRAY)) {
1152
6.80k
                data->type = PLIST_ARRAY;
1153
25.5k
            } else if (!strcmp(tag, XPLIST_INT)) {
1154
1.51k
                if (!is_empty) {
1155
1.31k
                    text_part_t first_part = { NULL, 0, 0, NULL };
1156
1.31k
                    text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
1157
1.31k
                    if (!tp) {
1158
20
                        PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
1159
20
                        text_parts_free((text_part_t*)first_part.next);
1160
20
                        ctx->err++;
1161
20
                        goto err_out;
1162
20
                    }
1163
1.29k
                    if (tp->begin) {
1164
1.10k
                        int requires_free = 0;
1165
1.10k
                        char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
1166
1.10k
                        if (!str_content) {
1167
0
                            PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1168
0
                            text_parts_free((text_part_t*)first_part.next);
1169
0
                            ctx->err++;
1170
0
                            goto err_out;
1171
0
                        }
1172
1.10k
                        char *str = str_content;
1173
1.10k
                        int is_negative = 0;
1174
1.10k
                        if ((str[0] == '-') || (str[0] == '+')) {
1175
619
                            if (str[0] == '-') {
1176
197
                                is_negative = 1;
1177
197
                            }
1178
619
                            str++;
1179
619
                        }
1180
1.10k
                        data->intval = strtoull(str, NULL, 0);
1181
1.10k
                        if (is_negative || (data->intval <= INT64_MAX)) {
1182
832
                            uint64_t v = data->intval;
1183
832
                            if (is_negative) {
1184
197
                                v = -v;
1185
197
                            }
1186
832
                            data->intval = v;
1187
832
                            data->length = 8;
1188
832
                        } else {
1189
270
                            data->length = 16;
1190
270
                        }
1191
1.10k
                        if (requires_free) {
1192
202
                            free(str_content);
1193
202
                        }
1194
1.10k
                    } else {
1195
194
                        is_empty = 1;
1196
194
                    }
1197
1.29k
                    text_parts_free((text_part_t*)tp->next);
1198
1.29k
                }
1199
1.49k
                if (is_empty) {
1200
389
                    data->intval = 0;
1201
389
                    data->length = 8;
1202
389
                }
1203
1.49k
                data->type = PLIST_INT;
1204
24.0k
            } else if (!strcmp(tag, XPLIST_REAL)) {
1205
817
                if (!is_empty) {
1206
623
                    text_part_t first_part = { NULL, 0, 0, NULL };
1207
623
                    text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
1208
623
                    if (!tp) {
1209
27
                        PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
1210
27
                        text_parts_free((text_part_t*)first_part.next);
1211
27
                        ctx->err++;
1212
27
                        goto err_out;
1213
27
                    }
1214
596
                    if (tp->begin) {
1215
401
                        int requires_free = 0;
1216
401
                        char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
1217
401
                        if (!str_content) {
1218
0
                            PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1219
0
                            text_parts_free((text_part_t*)first_part.next);
1220
0
                            ctx->err++;
1221
0
                            goto err_out;
1222
0
                        }
1223
401
                        data->realval = atof(str_content);
1224
401
                        if (requires_free) {
1225
207
                            free(str_content);
1226
207
                        }
1227
401
                    }
1228
596
                    text_parts_free((text_part_t*)tp->next);
1229
596
                }
1230
790
                data->type = PLIST_REAL;
1231
790
                data->length = 8;
1232
23.2k
            } else if (!strcmp(tag, XPLIST_TRUE)) {
1233
887
                if (!is_empty) {
1234
476
                    get_text_parts(ctx, tag, taglen, 1, NULL);
1235
476
                }
1236
887
                data->type = PLIST_BOOLEAN;
1237
887
                data->boolval = 1;
1238
887
                data->length = 1;
1239
22.3k
            } else if (!strcmp(tag, XPLIST_FALSE)) {
1240
399
                if (!is_empty) {
1241
203
                    get_text_parts(ctx, tag, taglen, 1, NULL);
1242
203
                }
1243
399
                data->type = PLIST_BOOLEAN;
1244
399
                data->boolval = 0;
1245
399
                data->length = 1;
1246
21.9k
            } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) {
1247
17.1k
                if (!is_empty) {
1248
5.89k
                    text_part_t first_part = { NULL, 0, 0, NULL };
1249
5.89k
                    text_part_t *tp = get_text_parts(ctx, tag, taglen, 0, &first_part);
1250
5.89k
                    char *str = NULL;
1251
5.89k
                    size_t length = 0;
1252
5.89k
                    if (!tp) {
1253
446
                        PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
1254
446
                        text_parts_free((text_part_t*)first_part.next);
1255
446
                        ctx->err++;
1256
446
                        goto err_out;
1257
446
                    }
1258
5.44k
                    str = text_parts_get_content(tp, 1, &length, NULL);
1259
5.44k
                    text_parts_free((text_part_t*)first_part.next);
1260
5.44k
                    if (!str) {
1261
217
                        PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1262
217
                        ctx->err++;
1263
217
                        goto err_out;
1264
217
                    }
1265
5.23k
                    if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) {
1266
3.54k
                        keyname = str;
1267
3.54k
                        free(tag);
1268
3.54k
                        tag = NULL;
1269
3.54k
                        plist_free(subnode);
1270
3.54k
                        subnode = NULL;
1271
3.54k
                        continue;
1272
3.54k
                    } else {
1273
1.68k
                        data->strval = str;
1274
1.68k
                        data->length = length;
1275
1.68k
                    }
1276
11.2k
                } else {
1277
11.2k
                    data->strval = strdup("");
1278
11.2k
                    data->length = 0;
1279
11.2k
                }
1280
12.9k
                data->type = PLIST_STRING;
1281
12.9k
            } else if (!strcmp(tag, XPLIST_DATA)) {
1282
1.36k
                if (!is_empty) {
1283
1.16k
                    text_part_t first_part = { NULL, 0, 0, NULL };
1284
1.16k
                    text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
1285
1.16k
                    if (!tp) {
1286
33
                        PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
1287
33
                        text_parts_free((text_part_t*)first_part.next);
1288
33
                        ctx->err++;
1289
33
                        goto err_out;
1290
33
                    }
1291
1.13k
                    if (tp->begin) {
1292
941
                        int requires_free = 0;
1293
941
                        char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
1294
941
                        if (!str_content) {
1295
0
                            PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1296
0
                            text_parts_free((text_part_t*)first_part.next);
1297
0
                            ctx->err++;
1298
0
                            goto err_out;
1299
0
                        }
1300
941
                        size_t size = tp->length;
1301
941
                        if (size > 0) {
1302
504
                            data->buff = base64decode(str_content, &size);
1303
504
                            data->length = size;
1304
504
                        }
1305
1306
941
                        if (requires_free) {
1307
204
                            free(str_content);
1308
204
                        }
1309
941
                    }
1310
1.13k
                    text_parts_free((text_part_t*)tp->next);
1311
1.13k
                }
1312
1.33k
                data->type = PLIST_DATA;
1313
3.44k
            } else if (!strcmp(tag, XPLIST_DATE)) {
1314
2.16k
                if (!is_empty) {
1315
1.78k
                    text_part_t first_part = { NULL, 0, 0, NULL };
1316
1.78k
                    text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
1317
1.78k
                    if (!tp) {
1318
30
                        PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
1319
30
                        text_parts_free((text_part_t*)first_part.next);
1320
30
                        ctx->err++;
1321
30
                        goto err_out;
1322
30
                    }
1323
1.75k
                    Time64_T timev = 0;
1324
1.75k
                    if (tp->begin) {
1325
1.56k
                        int requires_free = 0;
1326
1.56k
                        size_t length = 0;
1327
1.56k
                        char *str_content = text_parts_get_content(tp, 0, &length, &requires_free);
1328
1.56k
                        if (!str_content) {
1329
0
                            PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1330
0
                            text_parts_free((text_part_t*)first_part.next);
1331
0
                            ctx->err++;
1332
0
                            goto err_out;
1333
0
                        }
1334
1335
1.56k
                        if ((length >= 11) && (length < 32)) {
1336
                            /* we need to copy here and 0-terminate because sscanf will read the entire string (whole rest of XML data) which can be huge */
1337
935
                            char strval[32];
1338
935
                            struct TM btime;
1339
935
                            strncpy(strval, str_content, length);
1340
935
                            strval[tp->length] = '\0';
1341
935
                            parse_date(strval, &btime);
1342
935
                            timev = timegm64(&btime);
1343
935
                        } else {
1344
626
                            PLIST_XML_ERR("Invalid text content in date node\n");
1345
626
                        }
1346
1.56k
                        if (requires_free) {
1347
203
                            free(str_content);
1348
203
                        }
1349
1.56k
                    }
1350
1.75k
                    text_parts_free((text_part_t*)tp->next);
1351
1.75k
                    data->realval = (double)(timev - MAC_EPOCH);
1352
1.75k
                }
1353
2.13k
                data->length = sizeof(double);
1354
2.13k
                data->type = PLIST_DATE;
1355
2.13k
            } else if (tag[0] == '/') {
1356
852
                 closing_tag = 1;
1357
852
            } else {
1358
430
                PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : "");
1359
430
                ctx->pos = ctx->end;
1360
430
                ctx->err++;
1361
430
                goto err_out;
1362
430
            }
1363
29.3k
            if (subnode && !closing_tag) {
1364
28.4k
                if (!*plist) {
1365
                    /* first node, make this node the parent node */
1366
1.48k
                    *plist = subnode;
1367
1.48k
                    if (data->type != PLIST_DICT && data->type != PLIST_ARRAY) {
1368
                        /* if the first node is not a structered node, we're done */
1369
629
                        subnode = NULL;
1370
629
                        goto err_out;
1371
629
                    }
1372
853
                    parent = subnode;
1373
27.0k
                } else if (parent) {
1374
27.0k
                    switch (plist_get_node_type(parent)) {
1375
3.49k
                    case PLIST_DICT:
1376
3.49k
                        if (!keyname) {
1377
8
                            PLIST_XML_ERR("missing key name while adding dict item\n");
1378
8
                            ctx->err++;
1379
8
                            goto err_out;
1380
8
                        }
1381
3.49k
                        plist_dict_set_item(parent, keyname, subnode);
1382
3.49k
                        break;
1383
23.5k
                    case PLIST_ARRAY:
1384
23.5k
                        plist_array_append_item(parent, subnode);
1385
23.5k
                        break;
1386
0
                    default:
1387
                        /* should not happen */
1388
0
                        PLIST_XML_ERR("parent is not a structured node\n");
1389
0
                        ctx->err++;
1390
0
                        goto err_out;
1391
27.0k
                    }
1392
27.0k
                }
1393
27.8k
                if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) {
1394
8.19k
                    struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
1395
8.19k
                    if (!path_item) {
1396
0
                        PLIST_XML_ERR("out of memory when allocating node path item\n");
1397
0
                        ctx->err++;
1398
0
                        goto err_out;
1399
0
                    }
1400
8.19k
                    path_item->type = (data->type == PLIST_DICT) ? XPLIST_DICT : XPLIST_ARRAY;
1401
8.19k
                    path_item->prev = node_path;
1402
8.19k
                    node_path = path_item;
1403
1404
8.19k
                    parent = subnode;
1405
8.19k
                }
1406
27.8k
                subnode = NULL;
1407
27.8k
            } else if (closing_tag) {
1408
852
                if (!node_path) {
1409
46
                    PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
1410
46
                    ctx->err++;
1411
46
                    goto err_out;
1412
46
                }
1413
806
                if (strcmp(node_path->type, tag+1) != 0) {
1414
53
                    PLIST_XML_ERR("unexpected %s found (for opening %s)\n", tag, node_path->type);
1415
53
                    ctx->err++;
1416
53
                    goto err_out;
1417
53
                }
1418
753
                struct node_path_item *path_item = node_path;
1419
753
                node_path = (struct node_path_item*)node_path->prev;
1420
753
                free(path_item);
1421
1422
753
                parent = ((node_t)parent)->parent;
1423
753
                if (!parent) {
1424
5
                    goto err_out;
1425
5
                }
1426
753
            }
1427
1428
28.5k
            free(tag);
1429
28.5k
            tag = NULL;
1430
28.5k
            free(keyname);
1431
28.5k
            keyname = NULL;
1432
28.5k
            plist_free(subnode);
1433
28.5k
            subnode = NULL;
1434
28.5k
        }
1435
37.1k
    }
1436
1437
781
    if (node_path) {
1438
591
        PLIST_XML_ERR("EOF encountered while </%s> was expected\n", node_path->type);
1439
591
        ctx->err++;
1440
591
    }
1441
1442
3.24k
err_out:
1443
3.24k
    free(tag);
1444
3.24k
    free(keyname);
1445
3.24k
    plist_free(subnode);
1446
1447
    /* clean up node_path if required */
1448
11.0k
    while (node_path) {
1449
7.84k
        struct node_path_item *path_item = node_path;
1450
7.84k
        node_path = (struct node_path_item*)path_item->prev;
1451
7.84k
        free(path_item);
1452
7.84k
    }
1453
1454
3.24k
    if (ctx->err) {
1455
2.45k
        plist_free(*plist);
1456
2.45k
        *plist = NULL;
1457
2.45k
        return PLIST_ERR_PARSE;
1458
2.45k
    }
1459
1460
    /* check if we have a UID "dict" so we can replace it with a proper UID node */
1461
785
    if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) {
1462
66
        plist_t value = plist_dict_get_item(*plist, "CF$UID");
1463
66
        if (PLIST_IS_UINT(value)) {
1464
2
            uint64_t u64val = 0;
1465
2
            plist_get_uint_val(value, &u64val);
1466
2
            plist_free(*plist);
1467
2
            *plist = plist_new_uid(u64val);
1468
2
        }
1469
66
    }
1470
1471
785
    return PLIST_ERR_SUCCESS;
1472
3.24k
}
1473
1474
plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
1475
3.24k
{
1476
3.24k
    if (!plist) {
1477
0
        return PLIST_ERR_INVALID_ARG;
1478
0
    }
1479
3.24k
    *plist = NULL;
1480
3.24k
    if (!plist_xml || (length == 0)) {
1481
0
        return PLIST_ERR_INVALID_ARG;
1482
0
    }
1483
1484
3.24k
    struct _parse_ctx ctx = { plist_xml, plist_xml + length, 0 };
1485
1486
3.24k
    return node_from_xml(&ctx, plist);
1487
3.24k
}