Coverage Report

Created: 2026-01-10 07:02

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