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