summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sami Kortelainen2026-02-22 03:39:54 +0100
committerGravatar Nikias Bassen2026-02-22 03:39:54 +0100
commitf5e74fc1e007b8f625d91e40c160785580de8f60 (patch)
treece9da2c80c13a0718605019c098e2a3b775b814a
parent3bdee70a9c1490ea011c6fb65193b2c7e242d26d (diff)
downloadlibplist-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.c112
-rw-r--r--src/xplist.c20
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
389void plist_free_data(plist_data_t data) 389static 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
415static int plist_free_node(node_t root) 420void 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; 427static 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
499static 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
1897static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length) 1954static 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;