diff options
Diffstat (limited to 'src/oplist.c')
| -rw-r--r-- | src/oplist.c | 861 |
1 files changed, 861 insertions, 0 deletions
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 | } | ||
