summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/bytearray.c34
-rw-r--r--src/bytearray.h3
-rw-r--r--src/out-default.c491
-rw-r--r--src/out-limd.c449
-rw-r--r--src/out-plutil.c465
-rw-r--r--src/plist.c92
-rw-r--r--src/plist.h6
-rw-r--r--src/strbuf.h1
9 files changed, 1538 insertions, 6 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 02b516c..e4b39ae 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,6 +25,9 @@ libplist_2_0_la_SOURCES = \
25 jsmn.c jsmn.h \ 25 jsmn.c jsmn.h \
26 jplist.c \ 26 jplist.c \
27 oplist.c \ 27 oplist.c \
28 out-default.c \
29 out-plutil.c \
30 out-limd.c \
28 plist.c plist.h 31 plist.c plist.h
29 32
30libplist___2_0_la_LIBADD = libplist-2.0.la 33libplist___2_0_la_LIBADD = libplist-2.0.la
diff --git a/src/bytearray.c b/src/bytearray.c
index 7d0549b..39fad5f 100644
--- a/src/bytearray.c
+++ b/src/bytearray.c
@@ -29,6 +29,17 @@ bytearray_t *byte_array_new(size_t initial)
29 a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE; 29 a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
30 a->data = malloc(a->capacity); 30 a->data = malloc(a->capacity);
31 a->len = 0; 31 a->len = 0;
32 a->stream = NULL;
33 return a;
34}
35
36bytearray_t *byte_array_new_for_stream(FILE *stream)
37{
38 bytearray_t *a = (bytearray_t*)malloc(sizeof(bytearray_t));
39 a->capacity = (size_t)-1;
40 a->data = NULL;
41 a->len = 0;
42 a->stream = stream;
32 return a; 43 return a;
33} 44}
34 45
@@ -43,6 +54,9 @@ void byte_array_free(bytearray_t *ba)
43 54
44void byte_array_grow(bytearray_t *ba, size_t amount) 55void byte_array_grow(bytearray_t *ba, size_t amount)
45{ 56{
57 if (ba->stream) {
58 return;
59 }
46 size_t increase = (amount > PAGE_SIZE) ? (amount+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE; 60 size_t increase = (amount > PAGE_SIZE) ? (amount+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
47 ba->data = realloc(ba->data, ba->capacity + increase); 61 ba->data = realloc(ba->data, ba->capacity + increase);
48 ba->capacity += increase; 62 ba->capacity += increase;
@@ -50,12 +64,20 @@ void byte_array_grow(bytearray_t *ba, size_t amount)
50 64
51void byte_array_append(bytearray_t *ba, void *buf, size_t len) 65void byte_array_append(bytearray_t *ba, void *buf, size_t len)
52{ 66{
53 if (!ba || !ba->data || (len <= 0)) return; 67 if (!ba || (!ba->stream && !ba->data) || (len <= 0)) return;
54 size_t remaining = ba->capacity-ba->len; 68 if (ba->stream) {
55 if (len > remaining) { 69 if (fwrite(buf, 1, len, ba->stream) < len) {
56 size_t needed = len - remaining; 70#if DEBUG
57 byte_array_grow(ba, needed); 71 fprintf(stderr, "ERROR: Failed to write to stream.\n");
72#endif
73 }
74 } else {
75 size_t remaining = ba->capacity-ba->len;
76 if (len > remaining) {
77 size_t needed = len - remaining;
78 byte_array_grow(ba, needed);
79 }
80 memcpy(((char*)ba->data) + ba->len, buf, len);
58 } 81 }
59 memcpy(((char*)ba->data) + ba->len, buf, len);
60 ba->len += len; 82 ba->len += len;
61} 83}
diff --git a/src/bytearray.h b/src/bytearray.h
index 312e2aa..b53e006 100644
--- a/src/bytearray.h
+++ b/src/bytearray.h
@@ -21,14 +21,17 @@
21#ifndef BYTEARRAY_H 21#ifndef BYTEARRAY_H
22#define BYTEARRAY_H 22#define BYTEARRAY_H
23#include <stdlib.h> 23#include <stdlib.h>
24#include <stdio.h>
24 25
25typedef struct bytearray_t { 26typedef struct bytearray_t {
26 void *data; 27 void *data;
27 size_t len; 28 size_t len;
28 size_t capacity; 29 size_t capacity;
30 FILE *stream;
29} bytearray_t; 31} bytearray_t;
30 32
31bytearray_t *byte_array_new(size_t initial); 33bytearray_t *byte_array_new(size_t initial);
34bytearray_t *byte_array_new_for_stream(FILE *stream);
32void byte_array_free(bytearray_t *ba); 35void byte_array_free(bytearray_t *ba);
33void byte_array_grow(bytearray_t *ba, size_t amount); 36void byte_array_grow(bytearray_t *ba, size_t amount);
34void byte_array_append(bytearray_t *ba, void *buf, size_t len); 37void byte_array_append(bytearray_t *ba, void *buf, size_t len);
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}
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}
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}
diff --git a/src/plist.c b/src/plist.c
index 8d57416..01e44df 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -1573,3 +1573,95 @@ PLIST_API void plist_sort(plist_t plist)
1573 } while (swapped); 1573 } while (swapped);
1574 } 1574 }
1575} 1575}
1576
1577PLIST_API plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length, plist_format_t format, plist_write_options_t options)
1578{
1579 plist_err_t err = PLIST_ERR_UNKNOWN;
1580 switch (format) {
1581 case PLIST_FORMAT_XML:
1582 err = plist_to_xml(plist, output, length);
1583 break;
1584 case PLIST_FORMAT_JSON:
1585 err = plist_to_json(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
1586 break;
1587 case PLIST_FORMAT_OSTEP:
1588 err = plist_to_openstep(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
1589 break;
1590 case PLIST_FORMAT_PRINT:
1591 err = plist_write_to_string_default(plist, output, length, options);
1592 break;
1593 case PLIST_FORMAT_LIMD:
1594 err = plist_write_to_string_limd(plist, output, length, options);
1595 break;
1596 case PLIST_FORMAT_PLUTIL:
1597 err = plist_write_to_string_plutil(plist, output, length, options);
1598 break;
1599 default:
1600 // unsupported output format
1601 err = PLIST_ERR_FORMAT;
1602 break;
1603 }
1604 return err;
1605}
1606
1607PLIST_API plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t format, plist_write_options_t options)
1608{
1609 if (!plist || !stream) {
1610 return PLIST_ERR_INVALID_ARG;
1611 }
1612 plist_err_t err = PLIST_ERR_UNKNOWN;
1613 char *output = NULL;
1614 uint32_t length = 0;
1615 switch (format) {
1616 case PLIST_FORMAT_BINARY:
1617 err = plist_to_bin(plist, &output, &length);
1618 break;
1619 case PLIST_FORMAT_XML:
1620 err = plist_to_xml(plist, &output, &length);
1621 break;
1622 case PLIST_FORMAT_JSON:
1623 err = plist_to_json(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
1624 break;
1625 case PLIST_FORMAT_OSTEP:
1626 err = plist_to_openstep(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
1627 break;
1628 case PLIST_FORMAT_PRINT:
1629 err = plist_write_to_stream_default(plist, stream, options);
1630 break;
1631 case PLIST_FORMAT_LIMD:
1632 err = plist_write_to_stream_limd(plist, stream, options);
1633 break;
1634 case PLIST_FORMAT_PLUTIL:
1635 err = plist_write_to_stream_plutil(plist, stream, options);
1636 break;
1637 default:
1638 // unsupported output format
1639 err = PLIST_ERR_FORMAT;
1640 break;
1641 }
1642 if (output && err == PLIST_ERR_SUCCESS) {
1643 if (fwrite(output, 1, length, stream) < length) {
1644 err = PLIST_ERR_IO;
1645 }
1646 }
1647 return err;
1648}
1649
1650PLIST_API plist_err_t plist_write_to_file(plist_t plist, const char* filename, plist_format_t format, plist_write_options_t options)
1651{
1652 if (!plist || !filename) {
1653 return PLIST_ERR_INVALID_ARG;
1654 }
1655 FILE* f = fopen(filename, "wb");
1656 if (!f) {
1657 return PLIST_ERR_IO;
1658 }
1659 plist_err_t err = plist_write_to_stream(plist, f, format, options);
1660 fclose(f);
1661 return err;
1662}
1663
1664PLIST_API void plist_print(plist_t plist)
1665{
1666 plist_write_to_stream(plist, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_PARTIAL_DATA);
1667}
diff --git a/src/plist.h b/src/plist.h
index 95d2a3e..13dc286 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -70,5 +70,11 @@ plist_data_t plist_new_plist_data(void);
70void plist_free_data(plist_data_t data); 70void plist_free_data(plist_data_t data);
71int plist_data_compare(const void *a, const void *b); 71int plist_data_compare(const void *a, const void *b);
72 72
73extern plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
74extern plist_err_t plist_write_to_string_limd(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
75extern plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
76extern plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options);
77extern plist_err_t plist_write_to_stream_limd(plist_t plist, FILE *stream, plist_write_options_t options);
78extern plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options);
73 79
74#endif 80#endif
diff --git a/src/strbuf.h b/src/strbuf.h
index 0d28edf..2fbfe93 100644
--- a/src/strbuf.h
+++ b/src/strbuf.h
@@ -27,6 +27,7 @@
27typedef struct bytearray_t strbuf_t; 27typedef struct bytearray_t strbuf_t;
28 28
29#define str_buf_new(__sz) byte_array_new(__sz) 29#define str_buf_new(__sz) byte_array_new(__sz)
30#define str_buf_new_for_stream(__stream) byte_array_new_for_stream(__stream)
30#define str_buf_free(__ba) byte_array_free(__ba) 31#define str_buf_free(__ba) byte_array_free(__ba)
31#define str_buf_grow(__ba, __am) byte_array_grow(__ba, __am) 32#define str_buf_grow(__ba, __am) byte_array_grow(__ba, __am)
32#define str_buf_append(__ba, __str, __len) byte_array_append(__ba, (void*)(__str), __len) 33#define str_buf_append(__ba, __str, __len) byte_array_append(__ba, (void*)(__str), __len)