Coverage Report

Created: 2026-02-26 06:07

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
43
0
#define MAC_EPOCH 978307200
44
45
static size_t dtostr(char *buf, size_t bufsize, double realval)
46
0
{
47
0
    size_t len = 0;
48
0
    if (isnan(realval)) {
49
0
        len = snprintf(buf, bufsize, "nan");
50
0
    } else if (isinf(realval)) {
51
0
        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
52
0
    } else if (realval == 0.0f) {
53
0
        len = snprintf(buf, bufsize, "0.0");
54
0
    } else {
55
0
        size_t i = 0;
56
0
        len = snprintf(buf, bufsize, "%.*g", 17, realval);
57
0
        for (i = 0; buf && i < len; i++) {
58
0
            if (buf[i] == ',') {
59
0
                buf[i] = '.';
60
0
                break;
61
0
            } else if (buf[i] == '.') {
62
0
                break;
63
0
            }
64
0
        }
65
0
    }
66
0
    return len;
67
0
}
68
69
static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth)
70
0
{
71
0
    plist_data_t node_data = NULL;
72
73
0
    char *val = NULL;
74
0
    size_t val_len = 0;
75
76
0
    uint32_t i = 0;
77
78
0
    if (!node)
79
0
        return PLIST_ERR_INVALID_ARG;
80
81
0
    node_data = plist_get_data(node);
82
83
0
    switch (node_data->type)
84
0
    {
85
0
    case PLIST_BOOLEAN:
86
0
    {
87
0
        if (node_data->boolval) {
88
0
            str_buf_append(*outbuf, "1", 1);
89
0
        } else {
90
0
            str_buf_append(*outbuf, "0", 1);
91
0
        }
92
0
    }
93
0
    break;
94
95
0
    case PLIST_NULL:
96
0
        str_buf_append(*outbuf, "<null>", 6);
97
0
  break;
98
99
0
    case PLIST_INT:
100
0
        val = (char*)malloc(64);
101
0
        if (node_data->length == 16) {
102
0
            val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
103
0
        } else {
104
0
            val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
105
0
        }
106
0
        str_buf_append(*outbuf, val, val_len);
107
0
        free(val);
108
0
        break;
109
110
0
    case PLIST_REAL:
111
0
        val = (char*)malloc(64);
112
0
        val_len = dtostr(val, 64, node_data->realval);
113
0
        str_buf_append(*outbuf, val, val_len);
114
0
        free(val);
115
0
        break;
116
117
0
    case PLIST_STRING:
118
0
    case PLIST_KEY: {
119
0
        const char *charmap[32] = {
120
0
            "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
121
0
            "\\b",     "\\t",     "\\n",     "\\u000b", "\\f",     "\\r",     "\\u000e", "\\u000f",
122
0
            "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
123
0
            "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
124
0
        };
125
0
        size_t j = 0;
126
0
        size_t len = 0;
127
0
        off_t start = 0;
128
0
        off_t cur = 0;
129
130
0
        str_buf_append(*outbuf, "\"", 1);
131
132
0
        len = node_data->length;
133
0
        for (j = 0; j < len; j++) {
134
0
            unsigned char ch = (unsigned char)node_data->strval[j];
135
0
            if (ch < 0x20) {
136
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
137
0
                str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
138
0
                start = cur+1;
139
0
            } else if (ch == '"') {
140
0
                str_buf_append(*outbuf, node_data->strval + start, cur - start);
141
0
                str_buf_append(*outbuf, "\\\"", 2);
142
0
                start = cur+1;
143
0
            }
144
0
            cur++;
145
0
        }
146
0
        str_buf_append(*outbuf, node_data->strval + start, cur - start);
147
148
0
        str_buf_append(*outbuf, "\"", 1);
149
0
        } break;
150
151
0
    case PLIST_ARRAY: {
152
0
        str_buf_append(*outbuf, "[", 1);
153
0
        node_t ch;
154
0
        uint32_t cnt = 0;
155
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
156
0
            str_buf_append(*outbuf, "\n", 1);
157
0
            for (i = 0; i <= depth; i++) {
158
0
                str_buf_append(*outbuf, "  ", 2);
159
0
            }
160
0
            char indexbuf[16];
161
0
            int l = sprintf(indexbuf, "%u => ", cnt);
162
0
            str_buf_append(*outbuf, indexbuf, l);
163
0
            plist_err_t res = node_to_string(ch, outbuf, depth+1);
164
0
            if (res < 0) {
165
0
                return res;
166
0
            }
167
0
            cnt++;
168
0
        }
169
0
        if (cnt > 0) {
170
0
            str_buf_append(*outbuf, "\n", 1);
171
0
            for (i = 0; i < depth; i++) {
172
0
                str_buf_append(*outbuf, "  ", 2);
173
0
            }
174
0
        }
175
0
        str_buf_append(*outbuf, "]", 1);
176
0
        } break;
177
0
    case PLIST_DICT: {
178
0
        str_buf_append(*outbuf, "{", 1);
179
0
        node_t ch;
180
0
        uint32_t cnt = 0;
181
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
182
0
            if (cnt % 2 == 0) {
183
0
                str_buf_append(*outbuf, "\n", 1);
184
0
                for (i = 0; i <= depth; i++) {
185
0
                    str_buf_append(*outbuf, "  ", 2);
186
0
                }
187
0
            }
188
0
            plist_err_t res = node_to_string(ch, outbuf, depth+1);
189
0
            if (res < 0) {
190
0
                return res;
191
0
            }
192
0
            if (cnt % 2 == 0) {
193
0
                str_buf_append(*outbuf, " => ", 4);
194
0
            }
195
0
            cnt++;
196
0
        }
197
0
        if (cnt > 0) {
198
0
            str_buf_append(*outbuf, "\n", 1);
199
0
            for (i = 0; i < depth; i++) {
200
0
                str_buf_append(*outbuf, "  ", 2);
201
0
            }
202
0
        }
203
0
        str_buf_append(*outbuf, "}", 1);
204
0
        } break;
205
0
    case PLIST_DATA:
206
0
        {
207
0
            val = (char*)calloc(1, 48);
208
0
            size_t len = node_data->length;
209
0
            size_t slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
210
0
            str_buf_append(*outbuf, val, slen);
211
0
            if (len <= 24) {
212
0
                for (i = 0; i < len; i++) {
213
0
                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
214
0
                    str_buf_append(*outbuf, val, 2);
215
0
                }
216
0
            } else {
217
0
                for (i = 0; i < 16; i++) {
218
0
                    if (i > 0 && (i % 4 == 0))
219
0
                        str_buf_append(*outbuf, " ", 1);
220
0
                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
221
0
                    str_buf_append(*outbuf, val, 2);
222
0
                }
223
0
                str_buf_append(*outbuf, " ... ", 5);
224
0
                for (i = len - 8; i < len; i++) {
225
0
                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
226
0
                    str_buf_append(*outbuf, val, 2);
227
0
                    if (i > 0 && (i % 4 == 0))
228
0
                        str_buf_append(*outbuf, " ", 1);
229
0
                }
230
0
            }
231
0
            free(val);
232
0
            val = NULL;
233
0
            str_buf_append(*outbuf, "}", 1);
234
0
        }
235
0
        break;
236
0
    case PLIST_DATE:
237
0
        {
238
0
            Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
239
0
            struct TM _btime;
240
0
            struct TM *btime = gmtime64_r(&timev, &_btime);
241
0
            if (btime) {
242
0
                val = (char*)calloc(1, 26);
243
0
                struct tm _tmcopy;
244
0
                copy_TM64_to_tm(btime, &_tmcopy);
245
0
                val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy);
246
0
                if (val_len > 0) {
247
0
                    str_buf_append(*outbuf, val, val_len);
248
0
                }
249
0
                free(val);
250
0
                val = NULL;
251
0
            }
252
0
        }
253
0
        break;
254
0
    case PLIST_UID:
255
0
        {
256
0
            val = (char*)malloc(88);
257
0
            val_len = sprintf(val, "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}", node, node_data, node_data->intval);
258
0
            str_buf_append(*outbuf, val, val_len);
259
0
            free(val);
260
0
            val = NULL;
261
0
        }
262
0
        break;
263
0
    default:
264
0
        return PLIST_ERR_UNKNOWN;
265
0
    }
266
267
0
    return PLIST_ERR_SUCCESS;
268
0
}
269
270
0
#define PO10i_LIMIT (INT64_MAX/10)
271
272
/* based on https://stackoverflow.com/a/4143288 */
273
static int num_digits_i(int64_t i)
274
0
{
275
0
    int n;
276
0
    int64_t po10;
277
0
    n=1;
278
0
    if (i < 0) {
279
0
        i = (i == INT64_MIN) ? INT64_MAX : -i;
280
0
        n++;
281
0
    }
282
0
    po10=10;
283
0
    while (i>=po10) {
284
0
        n++;
285
0
        if (po10 > PO10i_LIMIT) break;
286
0
        po10*=10;
287
0
    }
288
0
    return n;
289
0
}
290
291
0
#define PO10u_LIMIT (UINT64_MAX/10)
292
293
/* based on https://stackoverflow.com/a/4143288 */
294
static int num_digits_u(uint64_t i)
295
0
{
296
0
    int n;
297
0
    uint64_t po10;
298
0
    n=1;
299
0
    po10=10;
300
0
    while (i>=po10) {
301
0
        n++;
302
0
        if (po10 > PO10u_LIMIT) break;
303
0
        po10*=10;
304
0
    }
305
0
    return n;
306
0
}
307
308
static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
309
0
{
310
0
    plist_data_t data;
311
0
    if (!node) {
312
0
        return PLIST_ERR_INVALID_ARG;
313
0
    }
314
315
0
    if (depth > PLIST_MAX_NESTING_DEPTH) {
316
0
#if DEBUG
317
0
        fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
318
0
#endif
319
0
        return PLIST_ERR_MAX_NESTING;
320
0
    }
321
322
0
    if (hash_table_lookup(visited, node)) {
323
0
#if DEBUG
324
0
        fprintf(stderr, "libplist: ERROR: circular reference detected\n");
325
0
#endif
326
0
        return PLIST_ERR_CIRCULAR_REF;
327
0
    }
328
329
    // mark as visited
330
0
    hash_table_insert(visited, node, (void*)1);
331
332
0
    data = plist_get_data(node);
333
0
    if (node->children) {
334
0
        node_t ch;
335
0
        unsigned int n_children = node_n_children(node);
336
0
        for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
337
0
            plist_err_t res = _node_estimate_size(ch, size, depth + 1, visited);
338
0
            if (res != PLIST_ERR_SUCCESS) {
339
0
                return res;
340
0
            }
341
0
        }
342
0
        switch (data->type) {
343
0
        case PLIST_DICT:
344
0
            *size += 2; // '{' and '}'
345
0
            *size += n_children-1; // number of ':' and ','
346
0
            *size += n_children; // number of '\n' and extra space
347
0
            *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
348
0
            *size += 1; // additional '\n'
349
0
            break;
350
0
        case PLIST_ARRAY:
351
0
            *size += 2; // '[' and ']'
352
0
            *size += n_children-1; // number of ','
353
0
            *size += n_children; // number of '\n'
354
0
            *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
355
0
            *size += 1; // additional '\n'
356
0
            break;
357
0
        default:
358
0
            break;
359
0
  }
360
0
        *size += (depth << 1); // indent for {} and []
361
0
    } else {
362
0
        switch (data->type) {
363
0
        case PLIST_STRING:
364
0
        case PLIST_KEY:
365
0
            *size += data->length;
366
0
            *size += 2;
367
0
            break;
368
0
        case PLIST_INT:
369
0
            if (data->length == 16) {
370
0
                *size += num_digits_u(data->intval);
371
0
            } else {
372
0
                *size += num_digits_i((int64_t)data->intval);
373
0
            }
374
0
            break;
375
0
        case PLIST_REAL:
376
0
            *size += dtostr(NULL, 0, data->realval);
377
0
            break;
378
0
        case PLIST_BOOLEAN:
379
0
            *size += 1;
380
0
            break;
381
0
        case PLIST_NULL:
382
0
            *size += 6;
383
0
            break;
384
0
        case PLIST_DICT:
385
0
        case PLIST_ARRAY:
386
0
            *size += 2;
387
0
            break;
388
0
        case PLIST_DATA:
389
0
            *size = (data->length <= 24) ? 73 : 100;
390
0
            break;
391
0
        case PLIST_DATE:
392
0
            *size += 25;
393
0
            break;
394
0
        case PLIST_UID:
395
0
            *size += 88;
396
0
            break;
397
0
        default:
398
0
#ifdef DEBUG
399
0
            fprintf(stderr, "invalid node type encountered\n");
400
0
#endif
401
0
            return PLIST_ERR_UNKNOWN;
402
0
        }
403
0
    }
404
0
    if (depth == 0) {
405
0
        *size += 1; // final newline
406
0
    }
407
0
    return PLIST_ERR_SUCCESS;
408
0
}
409
410
static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
411
0
{
412
0
    hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
413
0
    if (!visited) return PLIST_ERR_NO_MEM;
414
0
    plist_err_t err = _node_estimate_size(node, size, depth, visited);
415
0
    hash_table_destroy(visited);
416
0
    return err;
417
0
}
418
419
static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
420
0
{
421
0
    plist_err_t res = node_to_string((node_t)plist, &outbuf, 0);
422
0
    if (res < 0) {
423
0
        return res;
424
0
    }
425
0
    if (!(options & PLIST_OPT_NO_NEWLINE)) {
426
0
        str_buf_append(outbuf, "\n", 1);
427
0
    }
428
0
    return res;
429
0
}
430
431
plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
432
0
{
433
0
    uint64_t size = 0;
434
0
    plist_err_t res;
435
436
0
    if (!plist || !output || !length) {
437
0
        return PLIST_ERR_INVALID_ARG;
438
0
    }
439
440
0
    res = node_estimate_size((node_t)plist, &size, 0);
441
0
    if (res < 0) {
442
0
        return res;
443
0
    }
444
445
0
    strbuf_t *outbuf = str_buf_new(size);
446
0
    if (!outbuf) {
447
0
#if DEBUG
448
0
        fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
449
0
#endif
450
0
        return PLIST_ERR_NO_MEM;
451
0
    }
452
453
0
    res = _plist_write_to_strbuf(plist, outbuf, options);
454
0
    if (res < 0) {
455
0
        str_buf_free(outbuf);
456
0
        *output = NULL;
457
0
        *length = 0;
458
0
        return res;
459
0
    }
460
0
    str_buf_append(outbuf, "\0", 1);
461
462
0
    *output = (char*)outbuf->data;
463
0
    *length = outbuf->len - 1;
464
465
0
    outbuf->data = NULL;
466
0
    str_buf_free(outbuf);
467
468
0
    return PLIST_ERR_SUCCESS;
469
0
}
470
471
plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options)
472
0
{
473
0
    if (!plist || !stream) {
474
0
        return PLIST_ERR_INVALID_ARG;
475
0
    }
476
0
    strbuf_t *outbuf = str_buf_new_for_stream(stream);
477
0
    if (!outbuf) {
478
0
#if DEBUG
479
0
        fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
480
0
#endif
481
0
        return PLIST_ERR_NO_MEM;
482
0
    }
483
484
0
    plist_err_t res = _plist_write_to_strbuf(plist, outbuf, options);
485
0
    if (res < 0) {
486
0
        str_buf_free(outbuf);
487
0
        return res;
488
0
    }
489
490
0
    str_buf_free(outbuf);
491
492
0
    return PLIST_ERR_SUCCESS;
493
0
}