Coverage Report

Created: 2026-08-08 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libplist/src/out-plutil.c
Line
Count
Source
1
/*
2
 * out-plutil.c
3
 * plutil-like *output-only* format - NOT for machine parsing
4
 *
5
 * Copyright (c) 2023 Nikias Bassen All Rights Reserved.
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
 */
21
22
#ifdef HAVE_CONFIG_H
23
#include <config.h>
24
#endif
25
26
#include <string.h>
27
#include <stdlib.h>
28
#include <stdio.h>
29
#include <time.h>
30
31
#include <inttypes.h>
32
#include <ctype.h>
33
#include <math.h>
34
#include <limits.h>
35
36
#include <node.h>
37
38
#include "plist.h"
39
#include "strbuf.h"
40
#include "time64.h"
41
#include "hashtable.h"
42
#include "common.h"
43
44
static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth)
45
0
{
46
0
    plist_data_t node_data = NULL;
47
48
0
    char *val = NULL;
49
0
    int slen = 0;
50
0
    size_t val_len = 0;
51
52
0
    if (!node || !outbuf || !*outbuf) {
53
0
        return PLIST_ERR_INVALID_ARG;
54
0
    }
55
56
0
    node_data = plist_get_data(node);
57
0
    if (!node_data) {
58
0
        return PLIST_ERR_INVALID_ARG;
59
0
    }
60
61
0
    switch (node_data->type)
62
0
    {
63
0
    case PLIST_BOOLEAN:
64
0
    {
65
0
        if (node_data->boolval) {
66
0
            str_buf_append(*outbuf, "1", 1);
67
0
        } else {
68
0
            str_buf_append(*outbuf, "0", 1);
69
0
        }
70
0
    }
71
0
    break;
72
73
0
    case PLIST_NULL:
74
0
        str_buf_append(*outbuf, "<null>", 6);
75
0
  break;
76
77
0
    case PLIST_INT:
78
0
        val = (char*)malloc(64);
79
0
        if (!val) return PLIST_ERR_NO_MEM;
80
0
        if (node_data->length == 16) {
81
0
            slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
82
0
        } else {
83
0
            slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
84
0
        }
85
0
        if (slen < 0) {
86
0
            free(val);
87
0
            return PLIST_ERR_UNKNOWN;
88
0
        }
89
0
        val_len = (size_t)slen;
90
0
        str_buf_append(*outbuf, val, val_len);
91
0
        free(val);
92
0
        break;
93
94
0
    case PLIST_REAL:
95
0
        val = (char*)malloc(64);
96
0
        if (!val) return PLIST_ERR_NO_MEM;
97
0
        val_len = dtostr(val, 64, node_data->realval);
98
0
        str_buf_append(*outbuf, val, val_len);
99
0
        free(val);
100
0
        break;
101
102
0
    case PLIST_STRING:
103
0
    case PLIST_KEY: {
104
0
        if (!node_data->strval && node_data->length > 0) {
105
0
            return PLIST_ERR_INVALID_ARG;
106
0
        }
107
0
        const char *charmap[32] = {
108
0
            "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
109
0
            "\\b",     "\\t",     "\\n",     "\\u000b", "\\f",     "\\r",     "\\u000e", "\\u000f",
110
0
            "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
111
0
            "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
112
0
        };
113
0
        size_t j = 0;
114
0
        size_t len = 0;
115
0
        size_t start = 0;
116
0
        size_t cur = 0;
117
118
0
        str_buf_append(*outbuf, "\"", 1);
119
120
0
        len = node_data->length;
121
0
        for (j = 0; j < len; j++) {
122
0
            unsigned char ch = (unsigned char)node_data->strval[j];
123
0
            if (ch < 0x20) {
124
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
125
0
                str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
126
0
                start = cur+1;
127
0
            } else if (ch == '"') {
128
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
129
0
                str_buf_append(*outbuf, "\\\"", 2);
130
0
                start = cur+1;
131
0
            }
132
0
            cur++;
133
0
        }
134
0
        str_buf_append(*outbuf, node_data->strval + start, cur - start);
135
136
0
        str_buf_append(*outbuf, "\"", 1);
137
0
        } break;
138
139
0
    case PLIST_ARRAY: {
140
0
        str_buf_append(*outbuf, "[", 1);
141
0
        node_t ch;
142
0
        uint32_t cnt = 0;
143
0
        uint32_t i;
144
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
145
0
            str_buf_append(*outbuf, "\n", 1);
146
0
            for (i = 0; i <= depth; i++) {
147
0
                str_buf_append(*outbuf, "  ", 2);
148
0
            }
149
0
            char indexbuf[16];
150
0
            slen = snprintf(indexbuf, sizeof(indexbuf), "%u => ", cnt);
151
0
            if (slen < 0) {
152
0
                return PLIST_ERR_UNKNOWN;
153
0
            }
154
0
            str_buf_append(*outbuf, indexbuf, (size_t)slen);
155
0
            plist_err_t res = node_to_string(ch, outbuf, depth+1);
156
0
            if (res < 0) {
157
0
                return res;
158
0
            }
159
0
            cnt++;
160
0
        }
161
0
        if (cnt > 0) {
162
0
            str_buf_append(*outbuf, "\n", 1);
163
0
            for (i = 0; i < depth; i++) {
164
0
                str_buf_append(*outbuf, "  ", 2);
165
0
            }
166
0
        }
167
0
        str_buf_append(*outbuf, "]", 1);
168
0
        } break;
169
0
    case PLIST_DICT: {
170
0
        str_buf_append(*outbuf, "{", 1);
171
0
        node_t ch;
172
0
        uint32_t cnt = 0;
173
0
        uint32_t i;
174
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
175
0
            if (cnt % 2 == 0) {
176
0
                str_buf_append(*outbuf, "\n", 1);
177
0
                for (i = 0; i <= depth; i++) {
178
0
                    str_buf_append(*outbuf, "  ", 2);
179
0
                }
180
0
            }
181
0
            plist_err_t res = node_to_string(ch, outbuf, depth+1);
182
0
            if (res < 0) {
183
0
                return res;
184
0
            }
185
0
            if (cnt % 2 == 0) {
186
0
                str_buf_append(*outbuf, " => ", 4);
187
0
            }
188
0
            cnt++;
189
0
        }
190
0
        if (cnt > 0) {
191
0
            str_buf_append(*outbuf, "\n", 1);
192
0
            for (i = 0; i < depth; i++) {
193
0
                str_buf_append(*outbuf, "  ", 2);
194
0
            }
195
0
        }
196
0
        str_buf_append(*outbuf, "}", 1);
197
0
        } break;
198
0
    case PLIST_DATA:
199
0
        {
200
0
            if (!node_data->buff && node_data->length > 0) {
201
0
                return PLIST_ERR_INVALID_ARG;
202
0
            }
203
0
            val = (char*)calloc(1, 48);
204
0
            if (!val) return PLIST_ERR_NO_MEM;
205
0
            size_t len = node_data->length;
206
0
            slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
207
0
            if (slen < 0) {
208
0
                free(val);
209
0
                return PLIST_ERR_UNKNOWN;
210
0
            }
211
0
            str_buf_append(*outbuf, val, (size_t)slen);
212
0
            size_t j;
213
0
            if (len <= 24) {
214
0
                for (j = 0; j < len; j++) {
215
0
                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
216
0
                    str_buf_append(*outbuf, val, 2);
217
0
                }
218
0
            } else {
219
0
                for (j = 0; j < 16; j++) {
220
0
                    if (j > 0 && (j % 4 == 0))
221
0
                        str_buf_append(*outbuf, " ", 1);
222
0
                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
223
0
                    str_buf_append(*outbuf, val, 2);
224
0
                }
225
0
                str_buf_append(*outbuf, " ... ", 5);
226
0
                for (j = len - 8; j < len; j++) {
227
0
                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
228
0
                    str_buf_append(*outbuf, val, 2);
229
0
                    if (j > 0 && (j % 4 == 0))
230
0
                        str_buf_append(*outbuf, " ", 1);
231
0
                }
232
0
            }
233
0
            free(val);
234
0
            val = NULL;
235
0
            str_buf_append(*outbuf, "}", 1);
236
0
        }
237
0
        break;
238
0
    case PLIST_DATE:
239
0
        {
240
0
            Time64_T timev;
241
0
            if (plist_real_to_time64(node_data->realval, &timev) < 0) {
242
0
#if DEBUG
243
0
                fprintf(stderr, "libplist: ERROR: Encountered invalid date value %f\n", node_data->realval);
244
0
#endif
245
0
                return PLIST_ERR_INVALID_ARG;
246
0
            }
247
0
            struct TM _btime;
248
0
            struct TM *btime = gmtime64_r(&timev, &_btime);
249
0
            if (btime) {
250
0
                val = (char*)calloc(1, 26);
251
0
                if (!val) return PLIST_ERR_NO_MEM;
252
0
                struct tm _tmcopy;
253
0
                copy_TM64_to_tm(btime, &_tmcopy);
254
0
                val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy);
255
0
                if (val_len > 0) {
256
0
                    str_buf_append(*outbuf, val, val_len);
257
0
                }
258
0
                free(val);
259
0
                val = NULL;
260
0
            }
261
0
        }
262
0
        break;
263
0
    case PLIST_UID:
264
0
        {
265
0
#define UID_FMT "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}"
266
0
            slen = snprintf(NULL, 0, UID_FMT, node, node_data, node_data->intval);
267
0
            if (slen < 0) {
268
0
                return PLIST_ERR_UNKNOWN;
269
0
            }
270
0
            val_len = (size_t)slen;
271
0
            val = (char*)malloc(val_len + 1);
272
0
            if (!val) return PLIST_ERR_NO_MEM;
273
0
            slen = snprintf(val, val_len+1, UID_FMT, node, node_data, node_data->intval);
274
0
            if (slen < 0 || (size_t)slen > val_len) {
275
0
                free(val);
276
0
                return PLIST_ERR_UNKNOWN;
277
0
            }
278
0
            val_len = (size_t)slen;
279
0
            str_buf_append(*outbuf, val, val_len);
280
0
            free(val);
281
0
            val = NULL;
282
0
#undef UID_FMT
283
0
        }
284
0
        break;
285
0
    default:
286
0
        return PLIST_ERR_UNKNOWN;
287
0
    }
288
289
0
    return PLIST_ERR_SUCCESS;
290
0
}
291
292
static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
293
0
{
294
0
    plist_data_t data;
295
0
    if (!node) {
296
0
        return PLIST_ERR_INVALID_ARG;
297
0
    }
298
299
0
    if (depth > PLIST_MAX_NESTING_DEPTH) {
300
0
#if DEBUG
301
0
        fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
302
0
#endif
303
0
        return PLIST_ERR_MAX_NESTING;
304
0
    }
305
306
0
    if (hash_table_lookup(visited, node)) {
307
0
#if DEBUG
308
0
        fprintf(stderr, "libplist: ERROR: circular reference detected\n");
309
0
#endif
310
0
        return PLIST_ERR_CIRCULAR_REF;
311
0
    }
312
313
    // mark as visited
314
0
    hash_table_insert(visited, node, (void*)1);
315
316
0
    data = plist_get_data(node);
317
0
    if (node->children) {
318
0
        node_t ch;
319
0
        unsigned int n_children = node_n_children(node);
320
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
321
0
            plist_err_t res = _node_estimate_size(ch, size, depth + 1, visited);
322
0
            if (res != PLIST_ERR_SUCCESS) {
323
0
                return res;
324
0
            }
325
0
        }
326
0
        switch (data->type) {
327
0
        case PLIST_DICT:
328
0
            *size += 2; // '{' and '}'
329
0
            *size += n_children-1; // number of ':' and ','
330
0
            *size += n_children; // number of '\n' and extra space
331
0
            *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
332
0
            *size += 1; // additional '\n'
333
0
            break;
334
0
        case PLIST_ARRAY:
335
0
            *size += 2; // '[' and ']'
336
0
            *size += n_children-1; // number of ','
337
0
            *size += n_children; // number of '\n'
338
0
            *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
339
0
            *size += 1; // additional '\n'
340
0
            break;
341
0
        default:
342
0
            break;
343
0
  }
344
0
        *size += (depth << 1); // indent for {} and []
345
0
    } else {
346
0
        switch (data->type) {
347
0
        case PLIST_STRING:
348
0
        case PLIST_KEY:
349
0
            *size += data->length;
350
0
            *size += 2;
351
0
            break;
352
0
        case PLIST_INT:
353
0
            if (data->length == 16) {
354
0
                *size += num_digits_u(data->intval);
355
0
            } else {
356
0
                *size += num_digits_i((int64_t)data->intval);
357
0
            }
358
0
            break;
359
0
        case PLIST_REAL:
360
0
            *size += dtostr(NULL, 0, data->realval);
361
0
            break;
362
0
        case PLIST_BOOLEAN:
363
0
            *size += 1;
364
0
            break;
365
0
        case PLIST_NULL:
366
0
            *size += 6;
367
0
            break;
368
0
        case PLIST_DICT:
369
0
        case PLIST_ARRAY:
370
0
            *size += 2;
371
0
            break;
372
0
        case PLIST_DATA:
373
0
            *size = (data->length <= 24) ? 73 : 100;
374
0
            break;
375
0
        case PLIST_DATE:
376
0
            *size += 25;
377
0
            break;
378
0
        case PLIST_UID:
379
0
            *size += 88;
380
0
            break;
381
0
        default:
382
0
#ifdef DEBUG
383
0
            fprintf(stderr, "invalid node type encountered\n");
384
0
#endif
385
0
            return PLIST_ERR_UNKNOWN;
386
0
        }
387
0
    }
388
0
    if (depth == 0) {
389
0
        *size += 1; // final newline
390
0
    }
391
0
    return PLIST_ERR_SUCCESS;
392
0
}
393
394
static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
395
0
{
396
0
    hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
397
0
    if (!visited) return PLIST_ERR_NO_MEM;
398
0
    plist_err_t err = _node_estimate_size(node, size, depth, visited);
399
0
    hash_table_destroy(visited);
400
0
    return err;
401
0
}
402
403
static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
404
0
{
405
0
    plist_err_t res = node_to_string((node_t)plist, &outbuf, 0);
406
0
    if (res < 0) {
407
0
        return res;
408
0
    }
409
0
    if (!(options & PLIST_OPT_NO_NEWLINE)) {
410
0
        str_buf_append(outbuf, "\n", 1);
411
0
    }
412
0
    return res;
413
0
}
414
415
plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
416
0
{
417
0
    uint64_t size = 0;
418
0
    plist_err_t res;
419
420
0
    if (!plist || !output || !length) {
421
0
        return PLIST_ERR_INVALID_ARG;
422
0
    }
423
424
0
    res = node_estimate_size((node_t)plist, &size, 0);
425
0
    if (res < 0) {
426
0
        return res;
427
0
    }
428
429
0
    strbuf_t *outbuf = str_buf_new(size);
430
0
    if (!outbuf) {
431
0
#if DEBUG
432
0
        fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
433
0
#endif
434
0
        return PLIST_ERR_NO_MEM;
435
0
    }
436
437
0
    res = _plist_write_to_strbuf(plist, outbuf, options);
438
0
    if (res < 0) {
439
0
        str_buf_free(outbuf);
440
0
        *output = NULL;
441
0
        *length = 0;
442
0
        return res;
443
0
    }
444
0
    str_buf_append(outbuf, "\0", 1);
445
446
0
    *output = (char*)outbuf->data;
447
0
    *length = outbuf->len - 1;
448
449
0
    outbuf->data = NULL;
450
0
    str_buf_free(outbuf);
451
452
0
    return PLIST_ERR_SUCCESS;
453
0
}
454
455
plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options)
456
0
{
457
0
    if (!plist || !stream) {
458
0
        return PLIST_ERR_INVALID_ARG;
459
0
    }
460
0
    strbuf_t *outbuf = str_buf_new_for_stream(stream);
461
0
    if (!outbuf) {
462
0
#if DEBUG
463
0
        fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
464
0
#endif
465
0
        return PLIST_ERR_NO_MEM;
466
0
    }
467
468
0
    plist_err_t res = _plist_write_to_strbuf(plist, outbuf, options);
469
0
    if (res < 0) {
470
0
        str_buf_free(outbuf);
471
0
        return res;
472
0
    }
473
474
0
    str_buf_free(outbuf);
475
476
0
    return PLIST_ERR_SUCCESS;
477
0
}