diff options
| -rw-r--r-- | docs/plistutil.1 | 8 | ||||
| -rw-r--r-- | include/plist/plist.h | 24 | ||||
| -rw-r--r-- | src/Makefile.am | 1 | ||||
| -rw-r--r-- | src/oplist.c | 861 | ||||
| -rw-r--r-- | src/plist.c | 54 | ||||
| -rw-r--r-- | test/Makefile.am | 18 | ||||
| -rw-r--r-- | test/data/o1.ostep | 45 | ||||
| -rw-r--r-- | test/data/o2.ostep | 17 | ||||
| -rw-r--r-- | test/data/o3.ostep | 16 | ||||
| -rw-r--r-- | test/data/test.strings | 12 | ||||
| -rwxr-xr-x | test/ostep-comments.test | 20 | ||||
| -rwxr-xr-x | test/ostep-invalid-types.test | 33 | ||||
| -rwxr-xr-x | test/ostep-strings.test | 20 | ||||
| -rwxr-xr-x | test/ostep1.test | 20 | ||||
| -rwxr-xr-x | test/ostep2.test | 19 | ||||
| -rw-r--r-- | test/plist_cmp.c | 15 | ||||
| -rw-r--r-- | test/plist_otest.c | 130 | ||||
| -rw-r--r-- | tools/plistutil.c | 54 |
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. | |||
| 18 | Output FILE to convert to. If this argument is omitted or - is passed as | 18 | Output FILE to convert to. If this argument is omitted or - is passed as |
| 19 | filename, plistutil will write to stdout. | 19 | filename, plistutil will write to stdout. |
| 20 | .TP | 20 | .TP |
| 21 | .B \-f, \-\-format [bin|xml|json] | 21 | .B \-f, \-\-format [bin|xml|json|openstep] |
| 22 | Force output format, regardless of input type. This is useful if the input | 22 | Force output format, regardless of input type. This is useful if the input |
| 23 | format is not known, but the output format should always be in a specific | 23 | format is not known, but the output format should always be in a specific |
| 24 | format (like xml or json). | 24 | format (like xml or json). |
| 25 | 25 | ||
| 26 | If omitted, XML plist data will be converted to binary and vice-versa. To | 26 | If omitted, XML plist data will be converted to binary and vice-versa. To |
| 27 | convert to/from JSON the output format needs to specified. | 27 | convert to/from JSON or OpenStep the output format needs to specified. |
| 28 | .TP | ||
| 29 | .B \-c, \-\-compact | ||
| 30 | JSON and OpenStep only: Print output in compact form. By default, the output | ||
| 31 | will be pretty-printed. | ||
| 28 | .TP | 32 | .TP |
| 29 | .B \-h, \-\-help | 33 | .B \-h, \-\-help |
| 30 | Prints usage information. | 34 | Prints 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 | ||
| 29 | libplist___2_0_la_LIBADD = libplist-2.0.la | 30 | libplist___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 | ||
| 43 | static 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 | |||
| 51 | void 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 | |||
| 62 | void plist_ostep_deinit(void) | ||
| 63 | { | ||
| 64 | /* deinit OpenStep plist stuff */ | ||
| 65 | } | ||
| 66 | |||
| 67 | #ifndef HAVE_STRNDUP | ||
| 68 | static 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 | |||
| 79 | static 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 | |||
| 103 | static 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 | |||
| 122 | static 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 | |||
| 133 | static 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 */ | ||
| 314 | static 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 */ | ||
| 335 | static 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 | |||
| 349 | static 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 | |||
| 436 | PLIST_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 | |||
| 478 | struct _parse_ctx { | ||
| 479 | const char *start; | ||
| 480 | const char *pos; | ||
| 481 | const char *end; | ||
| 482 | int err; | ||
| 483 | }; | ||
| 484 | typedef struct _parse_ctx* parse_ctx; | ||
| 485 | |||
| 486 | static 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 | |||
| 522 | static int node_from_openstep(parse_ctx ctx, plist_t *plist); | ||
| 523 | |||
| 524 | static 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 | |||
| 584 | static 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 | |||
| 818 | err_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 | |||
| 827 | PLIST_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); | |||
| 51 | extern void plist_bin_deinit(void); | 52 | extern void plist_bin_deinit(void); |
| 52 | extern void plist_json_init(void); | 53 | extern void plist_json_init(void); |
| 53 | extern void plist_json_deinit(void); | 54 | extern void plist_json_deinit(void); |
| 55 | extern void plist_ostep_init(void); | ||
| 56 | extern void plist_ostep_deinit(void); | ||
| 54 | 57 | ||
| 55 | static void internal_plist_init(void) | 58 | static 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 | ||
| 62 | static void internal_plist_deinit(void) | 66 | static 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 | ||
| 190 | PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist) | 199 | PLIST_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 | ||
| 14 | plist_cmp_SOURCES = plist_cmp.c | 15 | plist_cmp_SOURCES = plist_cmp.c |
| 15 | plist_cmp_LDADD = \ | 16 | plist_cmp_LDADD = \ |
| @@ -25,6 +26,9 @@ plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la | |||
| 25 | plist_jtest_SOURCES = plist_jtest.c | 26 | plist_jtest_SOURCES = plist_jtest.c |
| 26 | plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la | 27 | plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la |
| 27 | 28 | ||
| 29 | plist_otest_SOURCES = plist_otest.c | ||
| 30 | plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la | ||
| 31 | |||
| 28 | TESTS = \ | 32 | TESTS = \ |
| 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 | ||
| 59 | EXTRA_DIST = \ | 68 | EXTRA_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 | ||
| 107 | TESTS_ENVIRONMENT = \ | 119 | TESTS_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 @@ | |||
| 1 | STRINGS_ENTRY = "Whatever"; | ||
| 2 | FOO = "BAR"; | ||
| 3 | BAR = Foo; | ||
| 4 | ENTRY0 = "àéèçù"; | ||
| 5 | ENTRY1 = "日本語"; | ||
| 6 | ENTRY2 = "汉语/漢語"; | ||
| 7 | ENTRY3 = "한국어/조선말"; | ||
| 8 | ENTRY4 = "русский язык"; | ||
| 9 | ENTRY5 = "الْعَرَبيّة"; | ||
| 10 | ENTRY6 = "עִבְרִית"; | ||
| 11 | ENTRY7 = "język polski"; | ||
| 12 | ENTRY8 = "हिन्दी"; | ||
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 | |||
| 3 | set -e | ||
| 4 | |||
| 5 | DATASRC=$top_srcdir/test/data | ||
| 6 | DATAOUT=$top_builddir/test/data | ||
| 7 | TESTFILE=o3.ostep | ||
| 8 | |||
| 9 | if ! test -d "$DATAOUT"; then | ||
| 10 | mkdir -p $DATAOUT | ||
| 11 | fi | ||
| 12 | |||
| 13 | export PLIST_OSTEP_DEBUG=1 | ||
| 14 | |||
| 15 | echo "Converting" | ||
| 16 | $top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out | ||
| 17 | |||
| 18 | echo "Comparing" | ||
| 19 | export 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 | |||
| 3 | DATASRC=$top_srcdir/test/data | ||
| 4 | DATAOUT=$top_builddir/test/data | ||
| 5 | TESTFILE0=data.bplist | ||
| 6 | TESTFILE1=7.plist | ||
| 7 | TESTFILE2=uid.bplist | ||
| 8 | |||
| 9 | if ! test -d "$DATAOUT"; then | ||
| 10 | mkdir -p $DATAOUT | ||
| 11 | fi | ||
| 12 | |||
| 13 | export PLIST_OSTEP_DEBUG=1 | ||
| 14 | |||
| 15 | echo "Converting (failure expected)" | ||
| 16 | $top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE0 -o /dev/null | ||
| 17 | if [ $? -neq 2 ]; then | ||
| 18 | exit 1 | ||
| 19 | fi | ||
| 20 | |||
| 21 | echo "Converting (failure expected)" | ||
| 22 | $top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null | ||
| 23 | if [ $? -neq 2 ]; then | ||
| 24 | exit 2 | ||
| 25 | fi | ||
| 26 | |||
| 27 | echo "Converting (failure expected)" | ||
| 28 | $top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null | ||
| 29 | if [ $? -neq 2 ]; then | ||
| 30 | exit 3 | ||
| 31 | fi | ||
| 32 | |||
| 33 | exit 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 | |||
| 3 | set -e | ||
| 4 | |||
| 5 | DATASRC=$top_srcdir/test/data | ||
| 6 | DATAOUT=$top_builddir/test/data | ||
| 7 | TESTFILE=test.strings | ||
| 8 | |||
| 9 | if ! test -d "$DATAOUT"; then | ||
| 10 | mkdir -p $DATAOUT | ||
| 11 | fi | ||
| 12 | |||
| 13 | export PLIST_OSTEP_DEBUG=1 | ||
| 14 | |||
| 15 | echo "Converting" | ||
| 16 | $top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out | ||
| 17 | |||
| 18 | echo "Comparing" | ||
| 19 | export 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 | |||
| 3 | set -e | ||
| 4 | |||
| 5 | DATASRC=$top_srcdir/test/data | ||
| 6 | DATAOUT=$top_builddir/test/data | ||
| 7 | TESTFILE=o1.ostep | ||
| 8 | |||
| 9 | if ! test -d "$DATAOUT"; then | ||
| 10 | mkdir -p $DATAOUT | ||
| 11 | fi | ||
| 12 | |||
| 13 | export PLIST_OSTEP_DEBUG=1 | ||
| 14 | |||
| 15 | echo "Converting" | ||
| 16 | $top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out | ||
| 17 | |||
| 18 | echo "Comparing" | ||
| 19 | export 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 | |||
| 3 | set -e | ||
| 4 | |||
| 5 | DATASRC=$top_srcdir/test/data | ||
| 6 | DATAOUT=$top_builddir/test/data | ||
| 7 | TESTFILE=o2.ostep | ||
| 8 | |||
| 9 | if ! test -d "$DATAOUT"; then | ||
| 10 | mkdir -p $DATAOUT | ||
| 11 | fi | ||
| 12 | |||
| 13 | export PLIST_OTEST_DEBUG=1 | ||
| 14 | |||
| 15 | echo "Converting" | ||
| 16 | $top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out | ||
| 17 | |||
| 18 | echo "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 | |||
| 35 | int 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 @@ | |||
| 41 | typedef struct _options | 41 | typedef 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 | ||
| 47 | static void print_usage(int argc, char *argv[]) | 49 | static 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); |
