diff options
| author | 2026-02-22 03:39:54 +0100 | |
|---|---|---|
| committer | 2026-02-22 03:39:54 +0100 | |
| commit | f5e74fc1e007b8f625d91e40c160785580de8f60 (patch) | |
| tree | ce9da2c80c13a0718605019c098e2a3b775b814a | |
| parent | 3bdee70a9c1490ea011c6fb65193b2c7e242d26d (diff) | |
| download | libplist-f5e74fc1e007b8f625d91e40c160785580de8f60.tar.gz libplist-f5e74fc1e007b8f625d91e40c160785580de8f60.tar.bz2 | |
xplist: Convert nested {CF$UID:<int>} dicts to PLIST_UID safely
Convert single-entry { "CF$UID" : <integer> } dictionaries to PLIST_UID
nodes when closing a dict in the XML parser.
Refactor node cleanup logic:
- Split plist_free_data() into internal _plist_free_data()
- Introduce plist_free_children() to release child nodes separately
- Update plist_set_element_val() to free children before changing
container node types
- Ensure PLIST_DICT hashtables do not free values (assert + force
free_func = NULL)
This avoids in-place container mutation issues and ensures child
nodes and container metadata are released correctly before
changing node type.
Co-authored-by: Sami Kortelainen <sami.kortelainen@piceasoft.com>
Co-authored-by: Nikias Bassen <nikias@gmx.li>
| -rw-r--r-- | src/plist.c | 112 | ||||
| -rw-r--r-- | src/xplist.c | 20 |
2 files changed, 100 insertions, 32 deletions
diff --git a/src/plist.c b/src/plist.c index 82161f1..7697a75 100644 --- a/src/plist.c +++ b/src/plist.c | |||
| @@ -386,51 +386,80 @@ static int dict_key_compare(const void* a, const void* b) | |||
| 386 | return (strcmp(data_a->strval, data_b->strval) == 0) ? TRUE : FALSE; | 386 | return (strcmp(data_a->strval, data_b->strval) == 0) ? TRUE : FALSE; |
| 387 | } | 387 | } |
| 388 | 388 | ||
| 389 | void plist_free_data(plist_data_t data) | 389 | static void _plist_free_data(plist_data_t data) |
| 390 | { | 390 | { |
| 391 | if (data) | 391 | if (!data) return; |
| 392 | { | 392 | switch (data->type) { |
| 393 | switch (data->type) | ||
| 394 | { | ||
| 395 | case PLIST_KEY: | 393 | case PLIST_KEY: |
| 396 | case PLIST_STRING: | 394 | case PLIST_STRING: |
| 397 | free(data->strval); | 395 | free(data->strval); |
| 396 | data->strval = NULL; | ||
| 398 | break; | 397 | break; |
| 399 | case PLIST_DATA: | 398 | case PLIST_DATA: |
| 400 | free(data->buff); | 399 | free(data->buff); |
| 400 | data->buff = NULL; | ||
| 401 | break; | 401 | break; |
| 402 | case PLIST_ARRAY: | 402 | case PLIST_ARRAY: |
| 403 | ptr_array_free((ptrarray_t*)data->hashtable); | 403 | ptr_array_free((ptrarray_t*)data->hashtable); |
| 404 | data->hashtable = NULL; | ||
| 404 | break; | 405 | break; |
| 405 | case PLIST_DICT: | 406 | case PLIST_DICT: { |
| 406 | hash_table_destroy((hashtable_t*)data->hashtable); | 407 | hashtable_t *ht = (hashtable_t*)data->hashtable; |
| 408 | // PLIST_DICT hashtables must not own/free values; values are freed via node tree. | ||
| 409 | assert(!ht || ht->free_func == NULL); | ||
| 410 | if (ht) ht->free_func = NULL; | ||
| 411 | hash_table_destroy(ht); | ||
| 412 | data->hashtable = NULL; | ||
| 407 | break; | 413 | break; |
| 414 | } | ||
| 408 | default: | 415 | default: |
| 409 | break; | 416 | break; |
| 410 | } | ||
| 411 | free(data); | ||
| 412 | } | 417 | } |
| 413 | } | 418 | } |
| 414 | 419 | ||
| 415 | static int plist_free_node(node_t root) | 420 | void plist_free_data(plist_data_t data) |
| 416 | { | 421 | { |
| 417 | if (!root) return NODE_ERR_INVALID_ARG; | 422 | if (!data) return; |
| 423 | _plist_free_data(data); | ||
| 424 | free(data); | ||
| 425 | } | ||
| 418 | 426 | ||
| 419 | int root_index = -1; | 427 | static int plist_free_children(node_t root) |
| 428 | { | ||
| 429 | if (!root) return NODE_ERR_INVALID_ARG; | ||
| 420 | 430 | ||
| 421 | if (root->parent) { | 431 | if (!node_first_child(root)) { |
| 422 | root_index = node_detach(root->parent, root); | 432 | return NODE_ERR_SUCCESS; |
| 423 | if (root_index < 0) { | ||
| 424 | return root_index; | ||
| 425 | } | ||
| 426 | } | 433 | } |
| 427 | 434 | ||
| 428 | size_t cap = 64, sp = 0; | 435 | size_t cap = 64, sp = 0; |
| 429 | node_t *stack = (node_t*)malloc(cap * sizeof(*stack)); | 436 | node_t *stack = (node_t*)malloc(cap * sizeof(*stack)); |
| 430 | if (!stack) return NODE_ERR_NO_MEM; | 437 | if (!stack) return NODE_ERR_NO_MEM; |
| 431 | 438 | ||
| 432 | stack[sp++] = root; | 439 | // Push *direct* children onto the stack, detached from root. |
| 440 | for (;;) { | ||
| 441 | node_t ch = node_first_child(root); | ||
| 442 | if (!ch) break; | ||
| 443 | |||
| 444 | int di = node_detach(root, ch); | ||
| 445 | if (di < 0) { | ||
| 446 | free(stack); | ||
| 447 | return di; | ||
| 448 | } | ||
| 449 | |||
| 450 | if (sp == cap) { | ||
| 451 | cap += 64; | ||
| 452 | node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack)); | ||
| 453 | if (!tmp) { | ||
| 454 | free(stack); | ||
| 455 | return NODE_ERR_NO_MEM; | ||
| 456 | } | ||
| 457 | stack = tmp; | ||
| 458 | } | ||
| 459 | stack[sp++] = ch; | ||
| 460 | } | ||
| 433 | 461 | ||
| 462 | // Now free the detached subtree nodes (and their descendants). | ||
| 434 | while (sp) { | 463 | while (sp) { |
| 435 | node_t node = stack[sp - 1]; | 464 | node_t node = stack[sp - 1]; |
| 436 | node_t ch = node_first_child(node); | 465 | node_t ch = node_first_child(node); |
| @@ -464,6 +493,34 @@ static int plist_free_node(node_t root) | |||
| 464 | } | 493 | } |
| 465 | 494 | ||
| 466 | free(stack); | 495 | free(stack); |
| 496 | return NODE_ERR_SUCCESS; | ||
| 497 | } | ||
| 498 | |||
| 499 | static int plist_free_node(node_t root) | ||
| 500 | { | ||
| 501 | if (!root) return NODE_ERR_INVALID_ARG; | ||
| 502 | |||
| 503 | int root_index = -1; | ||
| 504 | |||
| 505 | if (root->parent) { | ||
| 506 | root_index = node_detach(root->parent, root); | ||
| 507 | if (root_index < 0) { | ||
| 508 | return root_index; | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | int r = plist_free_children(root); | ||
| 513 | if (r < 0) { | ||
| 514 | // root is already detached; caller should treat as error. | ||
| 515 | return r; | ||
| 516 | } | ||
| 517 | |||
| 518 | plist_data_t data = plist_get_data(root); | ||
| 519 | plist_free_data(data); | ||
| 520 | root->data = NULL; | ||
| 521 | |||
| 522 | node_destroy(root); | ||
| 523 | |||
| 467 | return root_index; | 524 | return root_index; |
| 468 | } | 525 | } |
| 469 | 526 | ||
| @@ -1896,27 +1953,18 @@ char plist_compare_node_value(plist_t node_l, plist_t node_r) | |||
| 1896 | 1953 | ||
| 1897 | static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length) | 1954 | static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length) |
| 1898 | { | 1955 | { |
| 1899 | //free previous allocated buffer | 1956 | //free previous allocated data |
| 1900 | plist_data_t data = plist_get_data(node); | 1957 | plist_data_t data = plist_get_data(node); |
| 1901 | if (!data) { // a node should always have data attached | 1958 | if (!data) { // a node should always have data attached |
| 1902 | PLIST_ERR("%s: Failed to allocate plist data\n", __func__); | 1959 | PLIST_ERR("%s: Failed to allocate plist data\n", __func__); |
| 1903 | return PLIST_ERR_NO_MEM; | 1960 | return PLIST_ERR_NO_MEM; |
| 1904 | } | 1961 | } |
| 1905 | 1962 | ||
| 1906 | switch (data->type) | 1963 | if (node_first_child((node_t)node)) { |
| 1907 | { | 1964 | int r = plist_free_children((node_t)node); |
| 1908 | case PLIST_KEY: | 1965 | if (r < 0) return PLIST_ERR_UNKNOWN; |
| 1909 | case PLIST_STRING: | ||
| 1910 | free(data->strval); | ||
| 1911 | data->strval = NULL; | ||
| 1912 | break; | ||
| 1913 | case PLIST_DATA: | ||
| 1914 | free(data->buff); | ||
| 1915 | data->buff = NULL; | ||
| 1916 | break; | ||
| 1917 | default: | ||
| 1918 | break; | ||
| 1919 | } | 1966 | } |
| 1967 | _plist_free_data(data); | ||
| 1920 | 1968 | ||
| 1921 | //now handle value | 1969 | //now handle value |
| 1922 | 1970 | ||
diff --git a/src/xplist.c b/src/xplist.c index 73e2b9f..a445dc5 100644 --- a/src/xplist.c +++ b/src/xplist.c | |||
| @@ -1562,6 +1562,26 @@ handle_closing: | |||
| 1562 | ctx->err = PLIST_ERR_PARSE; | 1562 | ctx->err = PLIST_ERR_PARSE; |
| 1563 | goto err_out; | 1563 | goto err_out; |
| 1564 | } | 1564 | } |
| 1565 | |||
| 1566 | /* When closing a dictionary, convert a single-entry | ||
| 1567 | { "CF$UID" : <integer> } dictionary into a PLIST_UID node. | ||
| 1568 | Perform the conversion before moving to the parent node. */ | ||
| 1569 | if (!strcmp(node_path->type, XPLIST_DICT) && parent && plist_get_node_type(parent) == PLIST_DICT) { | ||
| 1570 | if (plist_dict_get_size(parent) == 1) { | ||
| 1571 | plist_t uid = plist_dict_get_item(parent, "CF$UID"); | ||
| 1572 | if (uid) { | ||
| 1573 | uint64_t val = 0; | ||
| 1574 | if (plist_get_node_type(uid) != PLIST_INT) { | ||
| 1575 | ctx->err = PLIST_ERR_PARSE; | ||
| 1576 | PLIST_XML_ERR("Invalid node type for CF$UID dict entry (must be PLIST_INT)\n"); | ||
| 1577 | goto err_out; | ||
| 1578 | } | ||
| 1579 | plist_get_uint_val(uid, &val); | ||
| 1580 | plist_set_uid_val(parent, val); | ||
| 1581 | } | ||
| 1582 | } | ||
| 1583 | } | ||
| 1584 | |||
| 1565 | if (depth > 0) depth--; | 1585 | if (depth > 0) depth--; |
| 1566 | struct node_path_item *path_item = node_path; | 1586 | struct node_path_item *path_item = node_path; |
| 1567 | node_path = (struct node_path_item*)node_path->prev; | 1587 | node_path = (struct node_path_item*)node_path->prev; |
