summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2023-01-08 05:29:22 +0100
committerGravatar Nikias Bassen2023-01-08 05:29:22 +0100
commit60d291941fadb72b66d11502710add5899e21a2d (patch)
treea7ecccf74cd8fb7e2596fd3531fd9cccefa39dcd
parent3f9360e33c13c22648036da42e36f8668e29fb60 (diff)
downloadlibplist-60d291941fadb72b66d11502710add5899e21a2d.tar.gz
libplist-60d291941fadb72b66d11502710add5899e21a2d.tar.bz2
Add support for OpenStep plist format
-rw-r--r--docs/plistutil.18
-rw-r--r--include/plist/plist.h24
-rw-r--r--src/Makefile.am1
-rw-r--r--src/oplist.c861
-rw-r--r--src/plist.c54
-rw-r--r--test/Makefile.am18
-rw-r--r--test/data/o1.ostep45
-rw-r--r--test/data/o2.ostep17
-rw-r--r--test/data/o3.ostep16
-rw-r--r--test/data/test.strings12
-rwxr-xr-xtest/ostep-comments.test20
-rwxr-xr-xtest/ostep-invalid-types.test33
-rwxr-xr-xtest/ostep-strings.test20
-rwxr-xr-xtest/ostep1.test20
-rwxr-xr-xtest/ostep2.test19
-rw-r--r--test/plist_cmp.c15
-rw-r--r--test/plist_otest.c130
-rw-r--r--tools/plistutil.c54
18 files changed, 1322 insertions, 45 deletions
diff --git a/docs/plistutil.1 b/docs/plistutil.1
index eb1b591..5342e91 100644
--- a/docs/plistutil.1
+++ b/docs/plistutil.1
@@ -18,13 +18,17 @@ filename, plistutil will read from stdin.
18Output FILE to convert to. If this argument is omitted or - is passed as 18Output FILE to convert to. If this argument is omitted or - is passed as
19filename, plistutil will write to stdout. 19filename, plistutil will write to stdout.
20.TP 20.TP
21.B \-f, \-\-format [bin|xml|json] 21.B \-f, \-\-format [bin|xml|json|openstep]
22Force output format, regardless of input type. This is useful if the input 22Force output format, regardless of input type. This is useful if the input
23format is not known, but the output format should always be in a specific 23format is not known, but the output format should always be in a specific
24format (like xml or json). 24format (like xml or json).
25 25
26If omitted, XML plist data will be converted to binary and vice-versa. To 26If omitted, XML plist data will be converted to binary and vice-versa. To
27convert to/from JSON the output format needs to specified. 27convert to/from JSON or OpenStep the output format needs to specified.
28.TP
29.B \-c, \-\-compact
30JSON and OpenStep only: Print output in compact form. By default, the output
31will be pretty-printed.
28.TP 32.TP
29.B \-h, \-\-help 33.B \-h, \-\-help
30Prints usage information. 34Prints usage information.
diff --git a/include/plist/plist.h b/include/plist/plist.h
index c0eae1c..0ae8889 100644
--- a/include/plist/plist.h
+++ b/include/plist/plist.h
@@ -698,6 +698,20 @@ extern "C"
698 plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify); 698 plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify);
699 699
700 /** 700 /**
701 * Export the #plist_t structure to OpenStep format.
702 *
703 * @param plist the root node to export
704 * @param plist_openstep a pointer to a char* buffer. This function allocates the memory,
705 * caller is responsible for freeing it.
706 * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
707 * @param prettify pretty print the output if != 0
708 * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
709 * @note Use plist_mem_free() to free the allocated memory.
710 */
711 plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify);
712
713
714 /**
701 * Import the #plist_t structure from XML format. 715 * Import the #plist_t structure from XML format.
702 * 716 *
703 * @param plist_xml a pointer to the xml buffer. 717 * @param plist_xml a pointer to the xml buffer.
@@ -728,6 +742,16 @@ extern "C"
728 plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist); 742 plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist);
729 743
730 /** 744 /**
745 * Import the #plist_t structure from OpenStep plist format.
746 *
747 * @param openstep a pointer to the OpenStep plist buffer.
748 * @param length length of the buffer to read.
749 * @param plist a pointer to the imported plist.
750 * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
751 */
752 plist_err_t plist_from_openstep(const char *openstep, uint32_t length, plist_t * plist);
753
754 /**
731 * Import the #plist_t structure from memory data. 755 * Import the #plist_t structure from memory data.
732 * This method will look at the first bytes of plist_data 756 * This method will look at the first bytes of plist_data
733 * to determine if plist_data contains a binary, JSON, or XML plist 757 * to determine if plist_data contains a binary, JSON, or XML plist
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;
diff --git a/test/Makefile.am b/test/Makefile.am
index cd3b940..66543ea 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -9,7 +9,8 @@ noinst_PROGRAMS = \
9 plist_cmp \ 9 plist_cmp \
10 plist_test \ 10 plist_test \
11 plist_btest \ 11 plist_btest \
12 plist_jtest 12 plist_jtest \
13 plist_otest
13 14
14plist_cmp_SOURCES = plist_cmp.c 15plist_cmp_SOURCES = plist_cmp.c
15plist_cmp_LDADD = \ 16plist_cmp_LDADD = \
@@ -25,6 +26,9 @@ plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la
25plist_jtest_SOURCES = plist_jtest.c 26plist_jtest_SOURCES = plist_jtest.c
26plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la 27plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la
27 28
29plist_otest_SOURCES = plist_otest.c
30plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la
31
28TESTS = \ 32TESTS = \
29 empty.test \ 33 empty.test \
30 small.test \ 34 small.test \
@@ -54,7 +58,12 @@ TESTS = \
54 json2.test \ 58 json2.test \
55 json3.test \ 59 json3.test \
56 json-invalid-types.test \ 60 json-invalid-types.test \
57 json-int64-min-max.test 61 json-int64-min-max.test \
62 ostep1.test \
63 ostep2.test \
64 ostep-strings.test \
65 ostep-comments.test \
66 ostep-invalid-types.test
58 67
59EXTRA_DIST = \ 68EXTRA_DIST = \
60 $(TESTS) \ 69 $(TESTS) \
@@ -102,7 +111,10 @@ EXTRA_DIST = \
102 data/data.bplist \ 111 data/data.bplist \
103 data/j1.json \ 112 data/j1.json \
104 data/j2.json \ 113 data/j2.json \
105 data/int64_min_max.json 114 data/int64_min_max.json \
115 data/o1.ostep \
116 data/o2.ostep \
117 data/test.strings
106 118
107TESTS_ENVIRONMENT = \ 119TESTS_ENVIRONMENT = \
108 top_srcdir=$(top_srcdir) \ 120 top_srcdir=$(top_srcdir) \
diff --git a/test/data/o1.ostep b/test/data/o1.ostep
new file mode 100644
index 0000000..074406a
--- /dev/null
+++ b/test/data/o1.ostep
@@ -0,0 +1,45 @@
1{
2 "test" = (1,1);
3 foo = (
4 (-1337),
5 (1),
6 (1),
7 (1),
8 (
9 (1),
10 (1),
11 (1),
12 (1),
13 (
14 (1),
15 (1),
16 (1),
17 (1)
18 )
19 )
20 );
21 more = {
22 "a" = "yo";
23 "b" = (
24 {
25 "c" = 0.25;
26 },
27 {
28 "a" = "yo";
29 "b" = (
30 {
31 "c" = 0.25;
32 },
33 {
34 "a" = "yo";
35 "b" = (
36 {
37 "cd" = -0.25;
38 }
39 );
40 }
41 );
42 }
43 );
44 };
45}
diff --git a/test/data/o2.ostep b/test/data/o2.ostep
new file mode 100644
index 0000000..5f5f3c2
--- /dev/null
+++ b/test/data/o2.ostep
@@ -0,0 +1,17 @@
1{
2 "Some ASCII string" = "Test ASCII String";
3 "Some UTF8 strings" = (
4 "àéèçù",
5 "日本語",
6 "汉语/漢語",
7 "한국어/조선말",
8 "русский язык",
9 "الْعَرَبيّة",
10 "עִבְרִית",
11 "język polski",
12 "हिन्दी",
13 );
14 "Keys & \"entities\"" = "hello world & others <nodes> are fun!?'";
15 "Some Int" = 32434543632;
16 "Some String with Unicode entity" = "Yeah check this: \U1234 !!!";
17}
diff --git a/test/data/o3.ostep b/test/data/o3.ostep
new file mode 100644
index 0000000..b80444d
--- /dev/null
+++ b/test/data/o3.ostep
@@ -0,0 +1,16 @@
1(
2 {
3 AFirstKey = "A First Value";
4 ASecondKey = "A Second Value";
5 // this is the last entry
6 },
7 /*{
8 BFirstKey = "B First Value";
9 BSecondKey = "B Second Value";
10 },*/
11 {
12 CFirstKey = "C First Value"; // "C First Unused Value";
13 // now here is another comment
14 CSecondKey = /* "C Second Value";*/ "C Second Corrected Value";
15 }
16)
diff --git a/test/data/test.strings b/test/data/test.strings
new file mode 100644
index 0000000..6d6ee43
--- /dev/null
+++ b/test/data/test.strings
@@ -0,0 +1,12 @@
1STRINGS_ENTRY = "Whatever";
2FOO = "BAR";
3BAR = Foo;
4ENTRY0 = "àéèçù";
5ENTRY1 = "日本語";
6ENTRY2 = "汉语/漢語";
7ENTRY3 = "한국어/조선말";
8ENTRY4 = "русский язык";
9ENTRY5 = "الْعَرَبيّة";
10ENTRY6 = "עִבְרִית";
11ENTRY7 = "język polski";
12ENTRY8 = "हिन्दी";
diff --git a/test/ostep-comments.test b/test/ostep-comments.test
new file mode 100755
index 0000000..8f7f629
--- /dev/null
+++ b/test/ostep-comments.test
@@ -0,0 +1,20 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=o3.ostep
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_OSTEP_DEBUG=1
14
15echo "Converting"
16$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
17
18echo "Comparing"
19export PLIST_OSTEP_DEBUG=1
20$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
diff --git a/test/ostep-invalid-types.test b/test/ostep-invalid-types.test
new file mode 100755
index 0000000..9222394
--- /dev/null
+++ b/test/ostep-invalid-types.test
@@ -0,0 +1,33 @@
1## -*- sh -*-
2
3DATASRC=$top_srcdir/test/data
4DATAOUT=$top_builddir/test/data
5TESTFILE0=data.bplist
6TESTFILE1=7.plist
7TESTFILE2=uid.bplist
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_OSTEP_DEBUG=1
14
15echo "Converting (failure expected)"
16$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE0 -o /dev/null
17if [ $? -neq 2 ]; then
18 exit 1
19fi
20
21echo "Converting (failure expected)"
22$top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null
23if [ $? -neq 2 ]; then
24 exit 2
25fi
26
27echo "Converting (failure expected)"
28$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null
29if [ $? -neq 2 ]; then
30 exit 3
31fi
32
33exit 0
diff --git a/test/ostep-strings.test b/test/ostep-strings.test
new file mode 100755
index 0000000..5b0a098
--- /dev/null
+++ b/test/ostep-strings.test
@@ -0,0 +1,20 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=test.strings
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_OSTEP_DEBUG=1
14
15echo "Converting"
16$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
17
18echo "Comparing"
19export PLIST_OSTEP_DEBUG=1
20$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
diff --git a/test/ostep1.test b/test/ostep1.test
new file mode 100755
index 0000000..f0d9b51
--- /dev/null
+++ b/test/ostep1.test
@@ -0,0 +1,20 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=o1.ostep
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_OSTEP_DEBUG=1
14
15echo "Converting"
16$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
17
18echo "Comparing"
19export PLIST_OSTEP_DEBUG=1
20$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
diff --git a/test/ostep2.test b/test/ostep2.test
new file mode 100755
index 0000000..1b991c3
--- /dev/null
+++ b/test/ostep2.test
@@ -0,0 +1,19 @@
1## -*- sh -*-
2
3set -e
4
5DATASRC=$top_srcdir/test/data
6DATAOUT=$top_builddir/test/data
7TESTFILE=o2.ostep
8
9if ! test -d "$DATAOUT"; then
10 mkdir -p $DATAOUT
11fi
12
13export PLIST_OTEST_DEBUG=1
14
15echo "Converting"
16$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
17
18echo "Comparing"
19$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
diff --git a/test/plist_cmp.c b/test/plist_cmp.c
index 2af3f63..1b4a36a 100644
--- a/test/plist_cmp.c
+++ b/test/plist_cmp.c
@@ -127,19 +127,8 @@ int main(int argc, char *argv[])
127 plist_1[size_in1] = '\0'; 127 plist_1[size_in1] = '\0';
128 plist_2[size_in2] = '\0'; 128 plist_2[size_in2] = '\0';
129 129
130 if (memcmp(plist_1, "bplist00", 8) == 0) 130 plist_from_memory(plist_1, size_in1, &root_node1);
131 plist_from_bin(plist_1, size_in1, &root_node1); 131 plist_from_memory(plist_2, size_in2, &root_node2);
132 else if (plist_1[0] == '[' || plist_1[0] == '{')
133 plist_from_json(plist_1, size_in1, &root_node1);
134 else
135 plist_from_xml(plist_1, size_in1, &root_node1);
136
137 if (memcmp(plist_2, "bplist00", 8) == 0)
138 plist_from_bin(plist_2, size_in2, &root_node2);
139 else if (plist_2[0] == '[' || plist_2[0] == '{')
140 plist_from_json(plist_2, size_in2, &root_node2);
141 else
142 plist_from_xml(plist_2, size_in2, &root_node2);
143 132
144 if (!root_node1 || !root_node2) 133 if (!root_node1 || !root_node2)
145 { 134 {
diff --git a/test/plist_otest.c b/test/plist_otest.c
new file mode 100644
index 0000000..14168f8
--- /dev/null
+++ b/test/plist_otest.c
@@ -0,0 +1,130 @@
1/*
2 * plist_otest.c
3 * source libplist regression test
4 *
5 * Copyright (c) 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
23#include "plist/plist.h"
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <sys/stat.h>
29
30#ifdef _MSC_VER
31#pragma warning(disable:4996)
32#endif
33
34
35int main(int argc, char *argv[])
36{
37 FILE *iplist = NULL;
38 plist_t root_node1 = NULL;
39 plist_t root_node2 = NULL;
40 char *plist_ostep = NULL;
41 char *plist_ostep2 = NULL;
42 char *plist_bin = NULL;
43 int size_in = 0;
44 uint32_t size_out = 0;
45 uint32_t size_out2 = 0;
46 char *file_in = NULL;
47 char *file_out = NULL;
48 struct stat filestats;
49 if (argc != 3)
50 {
51 printf("Wrong input\n");
52 return 1;
53 }
54
55 file_in = argv[1];
56 file_out = argv[2];
57 //read input file
58 iplist = fopen(file_in, "rb");
59
60 if (!iplist)
61 {
62 printf("File does not exists\n");
63 return 2;
64 }
65 printf("File %s is open\n", file_in);
66 stat(file_in, &filestats);
67 size_in = filestats.st_size;
68 plist_ostep = (char *) malloc(sizeof(char) * (size_in + 1));
69 fread(plist_ostep, sizeof(char), size_in, iplist);
70 fclose(iplist);
71 plist_ostep[size_in] = 0;
72
73 //convert one format to another
74 plist_from_openstep(plist_ostep, size_in, &root_node1);
75 if (!root_node1)
76 {
77 printf("OpenStep PList parsing failed\n");
78 return 3;
79 }
80
81 printf("OpenStep PList parsing succeeded\n");
82 plist_to_bin(root_node1, &plist_bin, &size_out);
83 if (!plist_bin)
84 {
85 printf("PList BIN writing failed\n");
86 return 4;
87 }
88
89 printf("PList BIN writing succeeded\n");
90 plist_from_bin(plist_bin, size_out, &root_node2);
91 if (!root_node2)
92 {
93 printf("PList BIN parsing failed\n");
94 return 5;
95 }
96
97 printf("PList BIN parsing succeeded\n");
98 plist_to_openstep(root_node2, &plist_ostep2, &size_out2, 0);
99 if (!plist_ostep2)
100 {
101 printf("OpenStep PList writing failed\n");
102 return 8;
103 }
104
105 printf("OpenStep PList writing succeeded\n");
106 if (plist_ostep2)
107 {
108 FILE *oplist = NULL;
109 oplist = fopen(file_out, "wb");
110 fwrite(plist_ostep2, size_out2, sizeof(char), oplist);
111 fclose(oplist);
112 }
113
114 plist_free(root_node1);
115 plist_free(root_node2);
116 free(plist_bin);
117 free(plist_ostep);
118 free(plist_ostep2);
119
120 if ((uint32_t)size_in != size_out2)
121 {
122 printf("Size of input and output is different\n");
123 printf("Input size : %i\n", size_in);
124 printf("Output size : %i\n", size_out2);
125 }
126
127 //success
128 return 0;
129}
130
diff --git a/tools/plistutil.c b/tools/plistutil.c
index 677e432..6254b7c 100644
--- a/tools/plistutil.c
+++ b/tools/plistutil.c
@@ -41,7 +41,9 @@
41typedef struct _options 41typedef struct _options
42{ 42{
43 char *in_file, *out_file; 43 char *in_file, *out_file;
44 uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json 44 uint8_t debug;
45 uint8_t compact;
46 uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep
45} options_t; 47} options_t;
46 48
47static void print_usage(int argc, char *argv[]) 49static void print_usage(int argc, char *argv[])
@@ -50,17 +52,19 @@ static void print_usage(int argc, char *argv[])
50 name = strrchr(argv[0], '/'); 52 name = strrchr(argv[0], '/');
51 printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0])); 53 printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0]));
52 printf("\n"); 54 printf("\n");
53 printf("Convert a plist FILE between binary, XML, and JSON format.\n"); 55 printf("Convert a plist FILE between binary, XML, JSON, and OpenStep format.\n");
54 printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n"); 56 printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n");
55 printf("To convert to/from JSON the output format needs to be specified.\n"); 57 printf("To convert to/from JSON or OpenStep the output format needs to be specified.\n");
56 printf("\n"); 58 printf("\n");
57 printf("OPTIONS:\n"); 59 printf("OPTIONS:\n");
58 printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n"); 60 printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n");
59 printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n"); 61 printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n");
60 printf(" -f, --format FORMAT Force output format, regardless of input type\n"); 62 printf(" -f, --format FORMAT Force output format, regardless of input type\n");
61 printf(" FORMAT is one of xml, bin, or json\n"); 63 printf(" FORMAT is one of xml, bin, json, or openstep\n");
62 printf(" If omitted XML will be converted to binary,\n"); 64 printf(" If omitted, XML will be converted to binary,\n");
63 printf(" and binary to XML.\n"); 65 printf(" and binary to XML.\n");
66 printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
67 printf(" By default, the output will be pretty-printed.\n");
64 printf(" -d, --debug Enable extended debug output\n"); 68 printf(" -d, --debug Enable extended debug output\n");
65 printf(" -v, --version Print version information\n"); 69 printf(" -v, --version Print version information\n");
66 printf("\n"); 70 printf("\n");
@@ -112,6 +116,8 @@ static options_t *parse_arguments(int argc, char *argv[])
112 options->out_fmt = 2; 116 options->out_fmt = 2;
113 } else if (!strncmp(argv[i+1], "json", 4)) { 117 } else if (!strncmp(argv[i+1], "json", 4)) {
114 options->out_fmt = 3; 118 options->out_fmt = 3;
119 } else if (!strncmp(argv[i+1], "openstep", 8) || !strncmp(argv[i+1], "ostep", 5)) {
120 options->out_fmt = 4;
115 } else { 121 } else {
116 fprintf(stderr, "ERROR: Unsupported output format\n"); 122 fprintf(stderr, "ERROR: Unsupported output format\n");
117 free(options); 123 free(options);
@@ -120,6 +126,10 @@ static options_t *parse_arguments(int argc, char *argv[])
120 i++; 126 i++;
121 continue; 127 continue;
122 } 128 }
129 else if (!strcmp(argv[i], "--compact") || !strcmp(argv[i], "-c"))
130 {
131 options->compact = 1;
132 }
123 else if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d")) 133 else if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d"))
124 { 134 {
125 options->debug = 1; 135 options->debug = 1;
@@ -216,13 +226,6 @@ int main(int argc, char *argv[])
216 free(options); 226 free(options);
217 return 1; 227 return 1;
218 } 228 }
219
220 if (read_size < 8) {
221 fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n");
222 free(plist_entire);
223 free(options);
224 return 1;
225 }
226 } 229 }
227 else 230 else
228 { 231 {
@@ -237,13 +240,6 @@ int main(int argc, char *argv[])
237 memset(&filestats, '\0', sizeof(struct stat)); 240 memset(&filestats, '\0', sizeof(struct stat));
238 fstat(fileno(iplist), &filestats); 241 fstat(fileno(iplist), &filestats);
239 242
240 if (filestats.st_size < 8) {
241 fprintf(stderr, "ERROR: Input file is too small to contain valid plist data.\n");
242 free(options);
243 fclose(iplist);
244 return -1;
245 }
246
247 plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1)); 243 plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1));
248 read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist); 244 read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist);
249 plist_entire[read_size] = '\0'; 245 plist_entire[read_size] = '\0';
@@ -276,7 +272,9 @@ int main(int argc, char *argv[])
276 } else if (options->out_fmt == 2) { 272 } else if (options->out_fmt == 2) {
277 output_res = plist_to_xml(root_node, &plist_out, &size); 273 output_res = plist_to_xml(root_node, &plist_out, &size);
278 } else if (options->out_fmt == 3) { 274 } else if (options->out_fmt == 3) {
279 output_res = plist_to_json(root_node, &plist_out, &size, 0); 275 output_res = plist_to_json(root_node, &plist_out, &size, !options->compact);
276 } else if (options->out_fmt == 4) {
277 output_res = plist_to_openstep(root_node, &plist_out, &size, !options->compact);
280 } 278 }
281 } 279 }
282 } 280 }
@@ -316,8 +314,20 @@ int main(int argc, char *argv[])
316 ret = 1; 314 ret = 1;
317 } 315 }
318 } else { 316 } else {
319 fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); 317 switch (input_res) {
320 ret = 1; 318 case PLIST_ERR_PARSE:
319 if (options->out_fmt == 0) {
320 fprintf(stderr, "ERROR: Could not parse plist data, expected XML or binary plist\n");
321 } else {
322 fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
323 }
324 ret = 3;
325 break;
326 default:
327 fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
328 ret = 1;
329 break;
330 }
321 } 331 }
322 332
323 free(options); 333 free(options);