Coverage Report

Created: 2026-05-30 06:21

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