summaryrefslogtreecommitdiffstats
path: root/src/out-default.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/out-default.c')
-rw-r--r--src/out-default.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/src/out-default.c b/src/out-default.c
new file mode 100644
index 0000000..5747097
--- /dev/null
+++ b/src/out-default.c
@@ -0,0 +1,491 @@
1/*
2 * out-default.c
3 * libplist default *output-only* format - NOT for machine parsing
4 *
5 * Copyright (c) 2022-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, uint32_t indent, int partial_data)
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, "true", 4);
88 } else {
89 str_buf_append(*outbuf, "false", 5);
90 }
91 }
92 break;
93
94 case PLIST_NULL:
95 str_buf_append(*outbuf, "null", 4);
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 if (cnt > 0) {
156 str_buf_append(*outbuf, ",", 1);
157 }
158 str_buf_append(*outbuf, "\n", 1);
159 for (i = 0; i <= depth+indent; i++) {
160 str_buf_append(*outbuf, " ", 2);
161 }
162 int res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
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+indent; 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 > 0 && cnt % 2 == 0) {
182 str_buf_append(*outbuf, ",", 1);
183 }
184 if (cnt % 2 == 0) {
185 str_buf_append(*outbuf, "\n", 1);
186 for (i = 0; i <= depth+indent; i++) {
187 str_buf_append(*outbuf, " ", 2);
188 }
189 }
190 int res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
191 if (res < 0) {
192 return res;
193 }
194 if (cnt % 2 == 0) {
195 str_buf_append(*outbuf, ": ", 2);
196 }
197 cnt++;
198 }
199 if (cnt > 0) {
200 str_buf_append(*outbuf, "\n", 1);
201 for (i = 0; i < depth+indent; i++) {
202 str_buf_append(*outbuf, " ", 2);
203 }
204 }
205 str_buf_append(*outbuf, "}", 1);
206 } break;
207 case PLIST_DATA:
208 {
209 str_buf_append(*outbuf, "<", 1);
210 size_t len = node_data->length;
211 char charb[4];
212 if (!partial_data || len <= 24) {
213 for (i = 0; i < len; i++) {
214 if (i > 0 && (i % 4 == 0))
215 str_buf_append(*outbuf, " ", 1);
216 sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
217 str_buf_append(*outbuf, charb, 2);
218 }
219 } else {
220 for (i = 0; i < 16; i++) {
221 if (i > 0 && (i % 4 == 0))
222 str_buf_append(*outbuf, " ", 1);
223 sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
224 str_buf_append(*outbuf, charb, 2);
225 }
226 str_buf_append(*outbuf, " ... ", 5);
227 for (i = len - 8; i < len; i++) {
228 sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
229 str_buf_append(*outbuf, charb, 2);
230 if (i > 0 && i < len-1 && (i % 4 == 0))
231 str_buf_append(*outbuf, " ", 1);
232 }
233 }
234 str_buf_append(*outbuf, ">", 1);
235 }
236 break;
237 case PLIST_DATE:
238 {
239 Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
240 struct TM _btime;
241 struct TM *btime = gmtime64_r(&timev, &_btime);
242 if (btime) {
243 val = (char*)calloc(1, 26);
244 struct tm _tmcopy;
245 copy_TM64_to_tm(btime, &_tmcopy);
246 val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy);
247 if (val_len > 0) {
248 str_buf_append(*outbuf, val, val_len);
249 }
250 free(val);
251 val = NULL;
252 }
253 }
254 break;
255 case PLIST_UID:
256 {
257 str_buf_append(*outbuf, "CF$UID:", 7);
258 val = (char*)malloc(64);
259 if (node_data->length == 16) {
260 val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
261 } else {
262 val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
263 }
264 str_buf_append(*outbuf, val, val_len);
265 free(val);
266 }
267 break;
268 default:
269 return PLIST_ERR_UNKNOWN;
270 }
271
272 return PLIST_ERR_SUCCESS;
273}
274
275#define PO10i_LIMIT (INT64_MAX/10)
276
277/* based on https://stackoverflow.com/a/4143288 */
278static int num_digits_i(int64_t i)
279{
280 int n;
281 int64_t po10;
282 n=1;
283 if (i < 0) {
284 i = (i == INT64_MIN) ? INT64_MAX : -i;
285 n++;
286 }
287 po10=10;
288 while (i>=po10) {
289 n++;
290 if (po10 > PO10i_LIMIT) break;
291 po10*=10;
292 }
293 return n;
294}
295
296#define PO10u_LIMIT (UINT64_MAX/10)
297
298/* based on https://stackoverflow.com/a/4143288 */
299static int num_digits_u(uint64_t i)
300{
301 int n;
302 uint64_t po10;
303 n=1;
304 po10=10;
305 while (i>=po10) {
306 n++;
307 if (po10 > PO10u_LIMIT) break;
308 po10*=10;
309 }
310 return n;
311}
312
313static int node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data)
314{
315 plist_data_t data;
316 if (!node) {
317 return PLIST_ERR_INVALID_ARG;
318 }
319 data = plist_get_data(node);
320 if (node->children) {
321 node_t ch;
322 unsigned int n_children = node_n_children(node);
323 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
324 int res = node_estimate_size(ch, size, depth + 1, indent, partial_data);
325 if (res < 0) {
326 return res;
327 }
328 }
329 switch (data->type) {
330 case PLIST_DICT:
331 *size += 2; // '{' and '}'
332 *size += n_children-1; // number of ':' and ','
333 *size += n_children; // number of '\n' and extra space
334 *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child
335 *size += indent+1; // additional '\n'
336 break;
337 case PLIST_ARRAY:
338 *size += 2; // '[' and ']'
339 *size += n_children-1; // number of ','
340 *size += n_children; // number of '\n'
341 *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child
342 *size += indent+1; // additional '\n'
343 break;
344 default:
345 break;
346 }
347 *size += ((depth+indent) << 1); // indent for {} and []
348 } else {
349 switch (data->type) {
350 case PLIST_STRING:
351 case PLIST_KEY:
352 *size += data->length;
353 *size += 2;
354 break;
355 case PLIST_INT:
356 if (data->length == 16) {
357 *size += num_digits_u(data->intval);
358 } else {
359 *size += num_digits_i((int64_t)data->intval);
360 }
361 break;
362 case PLIST_REAL:
363 *size += dtostr(NULL, 0, data->realval);
364 break;
365 case PLIST_BOOLEAN:
366 *size += ((data->boolval) ? 4 : 5);
367 break;
368 case PLIST_NULL:
369 *size += 4;
370 break;
371 case PLIST_DICT:
372 case PLIST_ARRAY:
373 *size += 2;
374 break;
375 case PLIST_DATA:
376 *size += 2; // < and >
377 if (partial_data) {
378 *size += 58;
379 } else {
380 *size += data->length * 2;
381 *size += data->length / 4; // space between 4 byte groups
382 }
383 break;
384 case PLIST_DATE:
385 *size += 25;
386 break;
387 case PLIST_UID:
388 *size += 7; // "CF$UID:"
389 *size += num_digits_u(data->intval);
390 break;
391 default:
392#ifdef DEBUG
393 fprintf(stderr, "%s: invalid node type encountered\n", __func__);
394#endif
395 return PLIST_ERR_UNKNOWN;
396 }
397 }
398 if (depth == 0) {
399 *size += 1; // final newline
400 }
401 return PLIST_ERR_SUCCESS;
402}
403
404static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
405{
406 uint8_t indent = 0;
407 if (options & PLIST_OPT_INDENT) {
408 indent = (options >> 24) & 0xFF;
409 }
410 uint8_t i;
411 for (i = 0; i < indent; i++) {
412 str_buf_append(outbuf, " ", 2);
413 }
414 int res = node_to_string(plist, &outbuf, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
415 if (res < 0) {
416 return res;
417 }
418 if (!(options & PLIST_OPT_NO_NEWLINE)) {
419 str_buf_append(outbuf, "\n", 1);
420 }
421 return res;
422}
423
424plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
425{
426 uint64_t size = 0;
427 int res;
428
429 if (!plist || !output || !length) {
430 return PLIST_ERR_INVALID_ARG;
431 }
432
433 uint8_t indent = 0;
434 if (options & PLIST_OPT_INDENT) {
435 indent = (options >> 24) & 0xFF;
436 }
437
438 res = node_estimate_size(plist, &size, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
439 if (res < 0) {
440 return res;
441 }
442
443 strbuf_t *outbuf = str_buf_new(size);
444 if (!outbuf) {
445#if DEBUG
446 fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
447#endif
448 return PLIST_ERR_NO_MEM;
449 }
450
451 res = _plist_write_to_strbuf(plist, outbuf, options);
452 if (res < 0) {
453 str_buf_free(outbuf);
454 *output = NULL;
455 *length = 0;
456 return res;
457 }
458 str_buf_append(outbuf, "\0", 1);
459
460 *output = outbuf->data;
461 *length = outbuf->len - 1;
462
463 outbuf->data = NULL;
464 str_buf_free(outbuf);
465
466 return PLIST_ERR_SUCCESS;
467}
468
469plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options)
470{
471 if (!plist || !stream) {
472 return PLIST_ERR_INVALID_ARG;
473 }
474 strbuf_t *outbuf = str_buf_new_for_stream(stream);
475 if (!outbuf) {
476#if DEBUG
477 fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
478#endif
479 return PLIST_ERR_NO_MEM;
480 }
481
482 int res = _plist_write_to_strbuf(plist, outbuf, options);
483 if (res < 0) {
484 str_buf_free(outbuf);
485 return res;
486 }
487
488 str_buf_free(outbuf);
489
490 return PLIST_ERR_SUCCESS;
491}