summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/oplist.c861
-rw-r--r--src/plist.c54
3 files changed, 911 insertions, 5 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index d4c9e67..02b516c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ libplist_2_0_la_SOURCES = \
24 bplist.c \ 24 bplist.c \
25 jsmn.c jsmn.h \ 25 jsmn.c jsmn.h \
26 jplist.c \ 26 jplist.c \
27 oplist.c \
27 plist.c plist.h 28 plist.c plist.h
28 29
29libplist___2_0_la_LIBADD = libplist-2.0.la 30libplist___2_0_la_LIBADD = libplist-2.0.la
diff --git a/src/oplist.c b/src/oplist.c
new file mode 100644
index 0000000..fa6977a
--- /dev/null
+++ b/src/oplist.c
@@ -0,0 +1,861 @@
1/*
2 * oplist.c
3 * OpenStep plist implementation
4 *
5 * Copyright (c) 2021-2022 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#include <node_list.h>
38
39#include "plist.h"
40#include "strbuf.h"
41
42#ifdef DEBUG
43static int plist_ostep_debug = 0;
44#define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); }
45#define PLIST_OSTEP_WRITE_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepwriter] ERROR: " __VA_ARGS__); }
46#else
47#define PLIST_OSTEP_ERR(...)
48#define PLIST_OSTEP_WRITE_ERR(...)
49#endif
50
51void plist_ostep_init(void)
52{
53 /* init OpenStep stuff */
54#ifdef DEBUG
55 char *env_debug = getenv("PLIST_OSTEP_DEBUG");
56 if (env_debug && !strcmp(env_debug, "1")) {
57 plist_ostep_debug = 1;
58 }
59#endif
60}
61
62void plist_ostep_deinit(void)
63{
64 /* deinit OpenStep plist stuff */
65}
66
67#ifndef HAVE_STRNDUP
68static char* strndup(const char* str, size_t len)
69{
70 char *newstr = (char *)malloc(len+1);
71 if (newstr) {
72 strncpy(newstr, str, len);
73 newstr[len]= '\0';
74 }
75 return newstr;
76}
77#endif
78
79static size_t dtostr(char *buf, size_t bufsize, double realval)
80{
81 size_t len = 0;
82 if (isnan(realval)) {
83 len = snprintf(buf, bufsize, "nan");
84 } else if (isinf(realval)) {
85 len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
86 } else if (realval == 0.0f) {
87 len = snprintf(buf, bufsize, "0.0");
88 } else {
89 size_t i = 0;
90 len = snprintf(buf, bufsize, "%.*g", 17, realval);
91 for (i = 0; buf && i < len; i++) {
92 if (buf[i] == ',') {
93 buf[i] = '.';
94 break;
95 } else if (buf[i] == '.') {
96 break;
97 }
98 }
99 }
100 return len;
101}
102
103static const char allowed_unquoted_chars[256] = {
104 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
105 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
106 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
107 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
108 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
109 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
110 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
111 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
112 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
113 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
114 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
115 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
116 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
117 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
118 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
120};
121
122static int str_needs_quotes(const char* str, size_t len)
123{
124 size_t i;
125 for (i = 0; i < len; i++) {
126 if (!allowed_unquoted_chars[(unsigned char)str[i]]) {
127 return 1;
128 }
129 }
130 return 0;
131}
132
133static int node_to_openstep(node_t* node, bytearray_t **outbuf, uint32_t depth, int prettify)
134{
135 plist_data_t node_data = NULL;
136
137 char *val = NULL;
138 size_t val_len = 0;
139
140 uint32_t i = 0;
141
142 if (!node)
143 return PLIST_ERR_INVALID_ARG;
144
145 node_data = plist_get_data(node);
146
147 switch (node_data->type)
148 {
149 case PLIST_UINT:
150 val = (char*)malloc(64);
151 if (node_data->length == 16) {
152 val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
153 } else {
154 val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
155 }
156 str_buf_append(*outbuf, val, val_len);
157 free(val);
158 break;
159
160 case PLIST_REAL:
161 val = (char*)malloc(64);
162 val_len = dtostr(val, 64, node_data->realval);
163 str_buf_append(*outbuf, val, val_len);
164 free(val);
165 break;
166
167 case PLIST_STRING:
168 case PLIST_KEY: {
169 const char *charmap[32] = {
170 "\\U0000", "\\U0001", "\\U0002", "\\U0003", "\\U0004", "\\U0005", "\\U0006", "\\U0007",
171 "\\b", "\\t", "\\n", "\\U000b", "\\f", "\\r", "\\U000e", "\\U000f",
172 "\\U0010", "\\U0011", "\\U0012", "\\U0013", "\\U0014", "\\U0015", "\\U0016", "\\U0017",
173 "\\U0018", "\\U0019", "\\U001a", "\\U001b", "\\U001c", "\\U001d", "\\U001e", "\\U001f",
174 };
175 size_t j = 0;
176 size_t len = 0;
177 off_t start = 0;
178 off_t cur = 0;
179 int needs_quotes;
180
181 len = node_data->length;
182
183 needs_quotes = str_needs_quotes(node_data->strval, len);
184
185 if (needs_quotes) {
186 str_buf_append(*outbuf, "\"", 1);
187 }
188
189 for (j = 0; j < len; j++) {
190 unsigned char ch = (unsigned char)node_data->strval[j];
191 if (ch < 0x20) {
192 str_buf_append(*outbuf, node_data->strval + start, cur - start);
193 str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
194 start = cur+1;
195 } else if (ch == '"') {
196 str_buf_append(*outbuf, node_data->strval + start, cur - start);
197 str_buf_append(*outbuf, "\\\"", 2);
198 start = cur+1;
199 }
200 cur++;
201 }
202 str_buf_append(*outbuf, node_data->strval + start, cur - start);
203
204 if (needs_quotes) {
205 str_buf_append(*outbuf, "\"", 1);
206 }
207
208 } break;
209
210 case PLIST_ARRAY: {
211 str_buf_append(*outbuf, "(", 1);
212 node_t *ch;
213 uint32_t cnt = 0;
214 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
215 if (cnt > 0) {
216 str_buf_append(*outbuf, ",", 1);
217 }
218 if (prettify) {
219 str_buf_append(*outbuf, "\n", 1);
220 for (i = 0; i <= depth; i++) {
221 str_buf_append(*outbuf, " ", 2);
222 }
223 }
224 int res = node_to_openstep(ch, outbuf, depth+1, prettify);
225 if (res < 0) {
226 return res;
227 }
228 cnt++;
229 }
230 if (cnt > 0 && prettify) {
231 str_buf_append(*outbuf, "\n", 1);
232 for (i = 0; i < depth; i++) {
233 str_buf_append(*outbuf, " ", 2);
234 }
235 }
236 str_buf_append(*outbuf, ")", 1);
237 } break;
238 case PLIST_DICT: {
239 str_buf_append(*outbuf, "{", 1);
240 node_t *ch;
241 uint32_t cnt = 0;
242 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
243 if (cnt > 0 && cnt % 2 == 0) {
244 str_buf_append(*outbuf, ";", 1);
245 }
246 if (cnt % 2 == 0 && prettify) {
247 str_buf_append(*outbuf, "\n", 1);
248 for (i = 0; i <= depth; i++) {
249 str_buf_append(*outbuf, " ", 2);
250 }
251 }
252 int res = node_to_openstep(ch, outbuf, depth+1, prettify);
253 if (res < 0) {
254 return res;
255 }
256 if (cnt % 2 == 0) {
257 if (prettify) {
258 str_buf_append(*outbuf, " = ", 3);
259 } else {
260 str_buf_append(*outbuf, "=", 1);
261 }
262 }
263 cnt++;
264 }
265 if (cnt > 0) {
266 str_buf_append(*outbuf, ";", 1);
267 }
268 if (cnt > 0 && prettify) {
269 str_buf_append(*outbuf, "\n", 1);
270 for (i = 0; i < depth; i++) {
271 str_buf_append(*outbuf, " ", 2);
272 }
273 }
274 str_buf_append(*outbuf, "}", 1);
275 } break;
276 case PLIST_DATA: {
277 size_t j = 0;
278 size_t len = 0;
279 str_buf_append(*outbuf, "<", 1);
280 len = node_data->length;
281 for (j = 0; j < len; j++) {
282 char charb[4];
283 if (prettify && j > 0 && (j % 4 == 0))
284 str_buf_append(*outbuf, " ", 1);
285 sprintf(charb, "%02x", (unsigned char)node_data->buff[j]);
286 str_buf_append(*outbuf, charb, 2);
287 }
288 str_buf_append(*outbuf, ">", 1);
289 } break;
290 case PLIST_BOOLEAN:
291 PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
292 return PLIST_ERR_FORMAT;
293 case PLIST_NULL:
294 PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
295 return PLIST_ERR_FORMAT;
296 case PLIST_DATE:
297 // NOT VALID FOR OPENSTEP
298 PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
299 return PLIST_ERR_FORMAT;
300 case PLIST_UID:
301 // NOT VALID FOR OPENSTEP
302 PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
303 return PLIST_ERR_FORMAT;
304 default:
305 return PLIST_ERR_UNKNOWN;
306 }
307
308 return PLIST_ERR_SUCCESS;
309}
310
311#define PO10i_LIMIT (INT64_MAX/10)
312
313/* based on https://stackoverflow.com/a/4143288 */
314static int num_digits_i(int64_t i)
315{
316 int n;
317 int64_t po10;
318 n=1;
319 if (i < 0) {
320 i = (i == INT64_MIN) ? INT64_MAX : -i;
321 n++;
322 }
323 po10=10;
324 while (i>=po10) {
325 n++;
326 if (po10 > PO10i_LIMIT) break;
327 po10*=10;
328 }
329 return n;
330}
331
332#define PO10u_LIMIT (UINT64_MAX/10)
333
334/* based on https://stackoverflow.com/a/4143288 */
335static int num_digits_u(uint64_t i)
336{
337 int n;
338 uint64_t po10;
339 n=1;
340 po10=10;
341 while (i>=po10) {
342 n++;
343 if (po10 > PO10u_LIMIT) break;
344 po10*=10;
345 }
346 return n;
347}
348
349static int node_estimate_size(node_t *node, uint64_t *size, uint32_t depth, int prettify)
350{
351 plist_data_t data;
352 if (!node) {
353 return PLIST_ERR_INVALID_ARG;
354 }
355 data = plist_get_data(node);
356 if (node->children) {
357 node_t *ch;
358 unsigned int n_children = node_n_children(node);
359 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
360 int res = node_estimate_size(ch, size, depth + 1, prettify);
361 if (res < 0) {
362 return res;
363 }
364 }
365 switch (data->type) {
366 case PLIST_DICT:
367 *size += 2; // '{' and '}'
368 *size += n_children; // number of '=' and ';'
369 if (prettify) {
370 *size += n_children*2; // number of '\n' and extra spaces
371 *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
372 *size += 1; // additional '\n'
373 }
374 break;
375 case PLIST_ARRAY:
376 *size += 2; // '(' and ')'
377 *size += n_children-1; // number of ','
378 if (prettify) {
379 *size += n_children; // number of '\n'
380 *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
381 *size += 1; // additional '\n'
382 }
383 break;
384 default:
385 break;
386 }
387 if (prettify)
388 *size += (depth << 1); // indent for {} and ()
389 } else {
390 switch (data->type) {
391 case PLIST_STRING:
392 case PLIST_KEY:
393 *size += data->length;
394 *size += 2;
395 break;
396 case PLIST_UINT:
397 if (data->length == 16) {
398 *size += num_digits_u(data->intval);
399 } else {
400 *size += num_digits_i((int64_t)data->intval);
401 }
402 break;
403 case PLIST_REAL:
404 *size += dtostr(NULL, 0, data->realval);
405 break;
406 case PLIST_DICT:
407 case PLIST_ARRAY:
408 *size += 2;
409 break;
410 case PLIST_DATA:
411 *size += 2; // < and >
412 *size += data->length*2;
413 if (prettify)
414 *size += data->length/4;
415 break;
416 case PLIST_BOOLEAN:
417 // NOT VALID FOR OPENSTEP
418 PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
419 return PLIST_ERR_FORMAT;
420 case PLIST_DATE:
421 // NOT VALID FOR OPENSTEP
422 PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
423 return PLIST_ERR_FORMAT;
424 case PLIST_UID:
425 // NOT VALID FOR OPENSTEP
426 PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
427 return PLIST_ERR_FORMAT;
428 default:
429 PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n");
430 return PLIST_ERR_UNKNOWN;
431 }
432 }
433 return PLIST_ERR_SUCCESS;
434}
435
436PLIST_API int plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify)
437{
438 uint64_t size = 0;
439 int res;
440
441 if (!plist || !openstep || !length) {
442 return PLIST_ERR_INVALID_ARG;
443 }
444
445 res = node_estimate_size(plist, &size, 0, prettify);
446 if (res < 0) {
447 return res;
448 }
449
450 strbuf_t *outbuf = str_buf_new(size);
451 if (!outbuf) {
452 PLIST_OSTEP_WRITE_ERR("Could not allocate output buffer");
453 return PLIST_ERR_NO_MEM;
454 }
455
456 res = node_to_openstep(plist, &outbuf, 0, prettify);
457 if (res < 0) {
458 str_buf_free(outbuf);
459 *openstep = NULL;
460 *length = 0;
461 return res;
462 }
463 if (prettify) {
464 str_buf_append(outbuf, "\n", 1);
465 }
466
467 str_buf_append(outbuf, "\0", 1);
468
469 *openstep = outbuf->data;
470 *length = outbuf->len - 1;
471
472 outbuf->data = NULL;
473 str_buf_free(outbuf);
474
475 return PLIST_ERR_SUCCESS;
476}
477
478struct _parse_ctx {
479 const char *start;
480 const char *pos;
481 const char *end;
482 int err;
483};
484typedef struct _parse_ctx* parse_ctx;
485
486static void parse_skip_ws(parse_ctx ctx)
487{
488 while (ctx->pos < ctx->end) {
489 // skip comments
490 if (*ctx->pos == '/' && (ctx->end - ctx->pos > 1)) {
491 if (*(ctx->pos+1) == '/') {
492 ctx->pos++;
493 while (ctx->pos < ctx->end) {
494 if ((*ctx->pos == '\n') || (*ctx->pos == '\r')) {
495 break;
496 }
497 ctx->pos++;
498 }
499 } else if (*(ctx->pos+1) == '*') {
500 ctx->pos++;
501 while (ctx->pos < ctx->end) {
502 if (*ctx->pos == '*' && (ctx->end - ctx->pos > 1)) {
503 if (*(ctx->pos+1) == '/') {
504 ctx->pos+=2;
505 break;
506 }
507 }
508 ctx->pos++;
509 }
510 }
511 }
512 // break on any char that's not white space
513 if (!(((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n')))) {
514 break;
515 }
516 ctx->pos++;
517 }
518}
519
520#define HEX_DIGIT(x) ((x <= '9') ? (x - '0') : ((x <= 'F') ? (x - 'A' + 10) : (x - 'a' + 10)))
521
522static int node_from_openstep(parse_ctx ctx, plist_t *plist);
523
524static void parse_dict_data(parse_ctx ctx, plist_t dict)
525{
526 plist_t key = NULL;
527 plist_t val = NULL;
528 while (ctx->pos < ctx->end && !ctx->err) {
529 parse_skip_ws(ctx);
530 if (*ctx->pos == '}' || ctx->pos >= ctx->end) {
531 break;
532 }
533 key = NULL;
534 ctx->err = node_from_openstep(ctx, &key);
535 if (ctx->err != 0) {
536 break;
537 }
538 if (!PLIST_IS_STRING(key)) {
539 PLIST_OSTEP_ERR("Invalid type for dictionary key at offset %ld\n", ctx->pos - ctx->start);
540 ctx->err++;
541 break;
542 }
543 parse_skip_ws(ctx);
544 if (*ctx->pos != '=') {
545 PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start);
546 ctx->err++;
547 break;
548 }
549 ctx->pos++;
550 if (ctx->pos >= ctx->end) {
551 PLIST_OSTEP_ERR("EOF while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start);
552 ctx->err++;
553 break;
554 }
555 val = NULL;
556 ctx->err = node_from_openstep(ctx, &val);
557 if (ctx->err != 0) {
558 plist_free(key);
559 break;
560 }
561 if (!val) {
562 plist_free(key);
563 PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", ctx->pos - ctx->start);
564 ctx->err++;
565 break;
566 }
567 parse_skip_ws(ctx);
568 if (*ctx->pos != ';') {
569 plist_free(val);
570 plist_free(key);
571 PLIST_OSTEP_ERR("Missing terminating ';' while parsing dictionary item at offset %ld\n", ctx->pos - ctx->start);
572 ctx->err++;
573 break;
574 }
575
576 plist_dict_set_item(dict, plist_get_string_ptr(key, NULL), val);
577 plist_free(key);
578 val = NULL;
579
580 ctx->pos++;
581 }
582}
583
584static int node_from_openstep(parse_ctx ctx, plist_t *plist)
585{
586 plist_t subnode = NULL;
587 const char *p = NULL;
588 while (ctx->pos < ctx->end && !ctx->err) {
589 parse_skip_ws(ctx);
590 if (ctx->pos >= ctx->end) {
591 break;
592 }
593 plist_data_t data = plist_new_plist_data();
594 if (*ctx->pos == '{') {
595 data->type = PLIST_DICT;
596 subnode = plist_new_node(data);
597 ctx->pos++;
598 parse_dict_data(ctx, subnode);
599 if (ctx->err) {
600 goto err_out;
601 }
602 if (*ctx->pos != '}') {
603 PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", ctx->pos - ctx->start);
604 ctx->err++;
605 goto err_out;
606 }
607 ctx->pos++;
608 *plist = subnode;
609 parse_skip_ws(ctx);
610 break;
611 } else if (*ctx->pos == '(') {
612 data->type = PLIST_ARRAY;
613 subnode = plist_new_node(data);
614 ctx->pos++;
615 plist_t tmp = NULL;
616 while (ctx->pos < ctx->end && !ctx->err) {
617 parse_skip_ws(ctx);
618 if (*ctx->pos == ')') {
619 break;
620 }
621 ctx->err = node_from_openstep(ctx, &tmp);
622 if (ctx->err != 0) {
623 break;
624 }
625 if (!tmp) {
626 ctx->err++;
627 break;
628 }
629 plist_array_append_item(subnode, tmp);
630 tmp = NULL;
631 parse_skip_ws(ctx);
632 if (*ctx->pos != ',') {
633 break;
634 }
635 ctx->pos++;
636 }
637 if (ctx->err) {
638 goto err_out;
639 }
640 if (*ctx->pos != ')') {
641 PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", ctx->pos - ctx->start);
642 ctx->err++;
643 goto err_out;
644 }
645 ctx->pos++;
646 *plist = subnode;
647 parse_skip_ws(ctx);
648 break;
649 } else if (*ctx->pos == '<') {
650 data->type = PLIST_DATA;
651 ctx->pos++;
652 bytearray_t *bytes = byte_array_new(256);
653 while (ctx->pos < ctx->end && !ctx->err) {
654 parse_skip_ws(ctx);
655 if (*ctx->pos == '>') {
656 break;
657 }
658 if (!isxdigit(*ctx->pos)) {
659 PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", ctx->pos - ctx->start);
660 ctx->err++;
661 break;
662 }
663 uint8_t b = HEX_DIGIT(*ctx->pos);
664 ctx->pos++;
665 if (ctx->pos >= ctx->end) {
666 PLIST_OSTEP_ERR("Unexpected end of data at offset %ld\n", ctx->pos - ctx->start);
667 ctx->err++;
668 break;
669 }
670 if (!isxdigit(*ctx->pos)) {
671 PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", ctx->pos - ctx->start);
672 ctx->err++;
673 break;
674 }
675 b = (b << 4) + HEX_DIGIT(*ctx->pos);
676 byte_array_append(bytes, &b, 1);
677 ctx->pos++;
678 }
679 if (ctx->err) {
680 goto err_out;
681 }
682 if (*ctx->pos != '>') {
683 PLIST_OSTEP_ERR("Missing terminating '>' at offset %ld\n", ctx->pos - ctx->start);
684 ctx->err++;
685 goto err_out;
686 }
687 ctx->pos++;
688 data->buff = bytes->data;
689 data->length = bytes->len;
690 bytes->data = NULL;
691 byte_array_free(bytes);
692 *plist = plist_new_node(data);
693 parse_skip_ws(ctx);
694 break;
695 } else if (*ctx->pos == '"' || *ctx->pos == '\'') {
696 char c = *ctx->pos;
697 ctx->pos++;
698 p = ctx->pos;
699 int num_escapes = 0;
700 while (ctx->pos < ctx->end) {
701 if (*ctx->pos == '\\') {
702 num_escapes++;
703 }
704 if ((*ctx->pos == c) && (*(ctx->pos-1) != '\\')) {
705 break;
706 }
707 ctx->pos++;
708 }
709 if (*ctx->pos != c) {
710 PLIST_OSTEP_ERR("Missing closing quote (%c) at offset %ld\n", c, ctx->pos - ctx->start);
711 ctx->err++;
712 goto err_out;
713 }
714 size_t slen = ctx->pos - p;
715 ctx->pos++; // skip the closing quote
716 char* strbuf = malloc(slen+1);
717 if (num_escapes > 0) {
718 size_t i = 0;
719 size_t o = 0;
720 while (i < slen) {
721 if (p[i] == '\\') {
722 /* handle escape sequence */
723 i++;
724 switch (p[i]) {
725 case '0':
726 case '1':
727 case '2':
728 case '3':
729 case '4':
730 case '5':
731 case '6':
732 case '7': {
733 // max 3 digits octal
734 unsigned char chr = 0;
735 int maxd = 3;
736 while ((i < slen) && (p[i] >= '0' && p[i] <= '7') && --maxd) {
737 chr = (chr << 3) + p[i] - '0';
738 i++;
739 }
740 strbuf[o++] = (char)chr;
741 } break;
742 case 'U': {
743 i++;
744 // max 4 digits hex
745 uint16_t wchr = 0;
746 int maxd = 4;
747 while ((i < slen) && isxdigit(p[i]) && maxd--) {
748 wchr = (wchr << 4) + ((p[i] <= '9') ? (p[i] - '0') : ((p[i] <= 'F') ? (p[i] - 'A' + 10) : (p[i] - 'a' + 10)));
749 i++;
750 }
751 if (wchr >= 0x800) {
752 strbuf[o++] = (char)(0xE0 + ((wchr >> 12) & 0xF));
753 strbuf[o++] = (char)(0x80 + ((wchr >> 6) & 0x3F));
754 strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
755 } else if (wchr >= 0x80) {
756 strbuf[o++] = (char)(0xC0 + ((wchr >> 6) & 0x1F));
757 strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
758 } else {
759 strbuf[o++] = (char)(wchr & 0x7F);
760 }
761 } break;
762 case 'a': strbuf[o++] = '\a'; i++; break;
763 case 'b': strbuf[o++] = '\b'; i++; break;
764 case 'f': strbuf[o++] = '\f'; i++; break;
765 case 'n': strbuf[o++] = '\n'; i++; break;
766 case 'r': strbuf[o++] = '\r'; i++; break;
767 case 't': strbuf[o++] = '\t'; i++; break;
768 case 'v': strbuf[o++] = '\v'; i++; break;
769 case '"': strbuf[o++] = '"'; i++; break;
770 case '\'': strbuf[o++] = '\''; i++; break;
771 default:
772 break;
773 }
774 } else {
775 strbuf[o++] = p[i++];
776 }
777 }
778 strbuf[o] = '\0';
779 slen = o;
780 } else {
781 strncpy(strbuf, p, slen);
782 strbuf[slen] = '\0';
783 }
784 data->type = PLIST_STRING;
785 data->strval = strbuf;
786 data->length = slen;
787 *plist = plist_new_node(data);
788 parse_skip_ws(ctx);
789 break;
790 } else {
791 // unquoted string
792 size_t slen = 0;
793 parse_skip_ws(ctx);
794 p = ctx->pos;
795 while (ctx->pos < ctx->end) {
796 if (!allowed_unquoted_chars[(uint8_t)*ctx->pos]) {
797 break;
798 }
799 ctx->pos++;
800 }
801 slen = ctx->pos-p;
802 if (slen > 0) {
803 data->type = PLIST_STRING;
804 data->strval = strndup(p, slen);
805 data->length = slen;
806 *plist = plist_new_node(data);
807 parse_skip_ws(ctx);
808 break;
809 } else {
810 PLIST_OSTEP_ERR("Unexpected character when parsing unquoted string at offset %ld\n", ctx->pos - ctx->start);
811 ctx->err++;
812 break;
813 }
814 }
815 ctx->pos++;
816 }
817
818err_out:
819 if (ctx->err) {
820 plist_free(*plist);
821 *plist = NULL;
822 return PLIST_ERR_PARSE;
823 }
824 return PLIST_ERR_SUCCESS;
825}
826
827PLIST_API int plist_from_openstep(const char *plist_ostep, uint32_t length, plist_t * plist)
828{
829 if (!plist) {
830 return PLIST_ERR_INVALID_ARG;
831 }
832 *plist = NULL;
833 if (!plist_ostep || (length == 0)) {
834 return PLIST_ERR_INVALID_ARG;
835 }
836
837 struct _parse_ctx ctx = { plist_ostep, plist_ostep, plist_ostep + length, 0 };
838
839 int err = node_from_openstep(&ctx, plist);
840 if (err == 0) {
841 if (!*plist) {
842 /* whitespace only file is considered an empty dictionary */
843 *plist = plist_new_dict();
844 } else if (ctx.pos < ctx.end && *ctx.pos == '=') {
845 /* attempt to parse this as 'strings' data */
846 plist_free(*plist);
847 plist_t pl = plist_new_dict();
848 ctx.pos = plist_ostep;
849 parse_dict_data(&ctx, pl);
850 if (ctx.err > 0) {
851 plist_free(pl);
852 PLIST_OSTEP_ERR("Failed to parse strings data\n");
853 err = PLIST_ERR_PARSE;
854 } else {
855 *plist = pl;
856 }
857 }
858 }
859
860 return err;
861}
diff --git a/src/plist.c b/src/plist.c
index 37edfa4..e696f70 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -34,6 +34,7 @@
34#include <assert.h> 34#include <assert.h>
35#include <limits.h> 35#include <limits.h>
36#include <float.h> 36#include <float.h>
37#include <ctype.h>
37 38
38#ifdef WIN32 39#ifdef WIN32
39#include <windows.h> 40#include <windows.h>
@@ -51,12 +52,15 @@ extern void plist_bin_init(void);
51extern void plist_bin_deinit(void); 52extern void plist_bin_deinit(void);
52extern void plist_json_init(void); 53extern void plist_json_init(void);
53extern void plist_json_deinit(void); 54extern void plist_json_deinit(void);
55extern void plist_ostep_init(void);
56extern void plist_ostep_deinit(void);
54 57
55static void internal_plist_init(void) 58static void internal_plist_init(void)
56{ 59{
57 plist_bin_init(); 60 plist_bin_init();
58 plist_xml_init(); 61 plist_xml_init();
59 plist_json_init(); 62 plist_json_init();
63 plist_ostep_init();
60} 64}
61 65
62static void internal_plist_deinit(void) 66static void internal_plist_deinit(void)
@@ -64,6 +68,7 @@ static void internal_plist_deinit(void)
64 plist_bin_deinit(); 68 plist_bin_deinit();
65 plist_xml_deinit(); 69 plist_xml_deinit();
66 plist_json_deinit(); 70 plist_json_deinit();
71 plist_ostep_deinit();
67} 72}
68 73
69#ifdef WIN32 74#ifdef WIN32
@@ -186,6 +191,10 @@ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length)
186 return (memcmp(plist_data, "bplist00", 8) == 0); 191 return (memcmp(plist_data, "bplist00", 8) == 0);
187} 192}
188 193
194#define SKIP_WS(blob, pos, len) \
195 while (pos < len && ((blob[pos] == ' ') || (blob[pos] == '\t') || (blob[pos] == '\r') || (blob[pos] == '\n'))) pos++;
196#define FIND_NEXT(blob, pos, len, chr) \
197 while (pos < len && (blob[pos] != chr)) pos++;
189 198
190PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist) 199PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist)
191{ 200{
@@ -194,19 +203,54 @@ PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length,
194 return PLIST_ERR_INVALID_ARG; 203 return PLIST_ERR_INVALID_ARG;
195 } 204 }
196 *plist = NULL; 205 *plist = NULL;
197 if (!plist_data || length < 8) { 206 if (!plist_data || length == 0) {
198 return PLIST_ERR_INVALID_ARG; 207 return PLIST_ERR_INVALID_ARG;
199 } 208 }
200 if (plist_is_binary(plist_data, length)) { 209 if (plist_is_binary(plist_data, length)) {
201 res = plist_from_bin(plist_data, length, plist); 210 res = plist_from_bin(plist_data, length, plist);
202 } else { 211 } else {
203 /* skip whitespace before checking */
204 uint32_t pos = 0; 212 uint32_t pos = 0;
205 while (pos < length && ((plist_data[pos] == ' ') || (plist_data[pos] == '\t') || (plist_data[pos] == '\r') || (plist_data[pos] == '\n'))) pos++; 213 int is_json = 0;
206 if (plist_data[pos] == '[' || plist_data[pos] == '{') { 214 int is_xml = 0;
215 /* skip whitespace */
216 SKIP_WS(plist_data, pos, length);
217 if (plist_data[pos] == '<' && (length-pos > 3) && !isxdigit(plist_data[pos+1]) && !isxdigit(plist_data[pos+2]) && !isxdigit(plist_data[pos+3])) {
218 is_xml = 1;
219 } else if (plist_data[pos] == '[') {
220 /* only valid for json */
221 is_json = 1;
222 } else if (plist_data[pos] == '(') {
223 /* only valid for openstep */
224 } else if (plist_data[pos] == '{') {
225 /* this could be json or openstep */
226 pos++;
227 SKIP_WS(plist_data, pos, length);
228 if (plist_data[pos] == '"') {
229 /* still could be both */
230 pos++;
231 do {
232 FIND_NEXT(plist_data, pos, length, '"');
233 if (plist_data[pos-1] != '\\') {
234 break;
235 }
236 pos++;
237 } while (pos < length);
238 if (plist_data[pos] == '"') {
239 pos++;
240 SKIP_WS(plist_data, pos, length);
241 if (plist_data[pos] == ':') {
242 /* this is definitely json */
243 is_json = 1;
244 }
245 }
246 }
247 }
248 if (is_xml) {
249 res = plist_from_xml(plist_data, length, plist);
250 } else if (is_json) {
207 res = plist_from_json(plist_data, length, plist); 251 res = plist_from_json(plist_data, length, plist);
208 } else { 252 } else {
209 res = plist_from_xml(plist_data, length, plist); 253 res = plist_from_openstep(plist_data, length, plist);
210 } 254 }
211 } 255 }
212 return res; 256 return res;