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