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) return (strcmp(data_a->strval, data_b->strval) == 0) ? TRUE : FALSE; } -void plist_free_data(plist_data_t data) +static void _plist_free_data(plist_data_t data) { - if (data) - { - switch (data->type) - { + if (!data) return; + switch (data->type) { case PLIST_KEY: case PLIST_STRING: free(data->strval); + data->strval = NULL; break; case PLIST_DATA: free(data->buff); + data->buff = NULL; break; case PLIST_ARRAY: ptr_array_free((ptrarray_t*)data->hashtable); + data->hashtable = NULL; break; - case PLIST_DICT: - hash_table_destroy((hashtable_t*)data->hashtable); + case PLIST_DICT: { + hashtable_t *ht = (hashtable_t*)data->hashtable; + // PLIST_DICT hashtables must not own/free values; values are freed via node tree. + assert(!ht || ht->free_func == NULL); + if (ht) ht->free_func = NULL; + hash_table_destroy(ht); + data->hashtable = NULL; break; + } default: break; - } - free(data); } } -static int plist_free_node(node_t root) +void plist_free_data(plist_data_t data) { - if (!root) return NODE_ERR_INVALID_ARG; + if (!data) return; + _plist_free_data(data); + free(data); +} - int root_index = -1; +static int plist_free_children(node_t root) +{ + if (!root) return NODE_ERR_INVALID_ARG; - if (root->parent) { - root_index = node_detach(root->parent, root); - if (root_index < 0) { - return root_index; - } + if (!node_first_child(root)) { + return NODE_ERR_SUCCESS; } size_t cap = 64, sp = 0; node_t *stack = (node_t*)malloc(cap * sizeof(*stack)); if (!stack) return NODE_ERR_NO_MEM; - stack[sp++] = root; + // Push *direct* children onto the stack, detached from root. + for (;;) { + node_t ch = node_first_child(root); + if (!ch) break; + + int di = node_detach(root, ch); + if (di < 0) { + free(stack); + return di; + } + + if (sp == cap) { + cap += 64; + node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack)); + if (!tmp) { + free(stack); + return NODE_ERR_NO_MEM; + } + stack = tmp; + } + stack[sp++] = ch; + } + // Now free the detached subtree nodes (and their descendants). while (sp) { node_t node = stack[sp - 1]; node_t ch = node_first_child(node); @@ -464,6 +493,34 @@ static int plist_free_node(node_t root) } free(stack); + return NODE_ERR_SUCCESS; +} + +static int plist_free_node(node_t root) +{ + if (!root) return NODE_ERR_INVALID_ARG; + + int root_index = -1; + + if (root->parent) { + root_index = node_detach(root->parent, root); + if (root_index < 0) { + return root_index; + } + } + + int r = plist_free_children(root); + if (r < 0) { + // root is already detached; caller should treat as error. + return r; + } + + plist_data_t data = plist_get_data(root); + plist_free_data(data); + root->data = NULL; + + node_destroy(root); + return root_index; } @@ -1896,27 +1953,18 @@ char plist_compare_node_value(plist_t node_l, plist_t node_r) static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length) { - //free previous allocated buffer + //free previous allocated data plist_data_t data = plist_get_data(node); if (!data) { // a node should always have data attached PLIST_ERR("%s: Failed to allocate plist data\n", __func__); return PLIST_ERR_NO_MEM; } - switch (data->type) - { - case PLIST_KEY: - case PLIST_STRING: - free(data->strval); - data->strval = NULL; - break; - case PLIST_DATA: - free(data->buff); - data->buff = NULL; - break; - default: - break; + if (node_first_child((node_t)node)) { + int r = plist_free_children((node_t)node); + if (r < 0) return PLIST_ERR_UNKNOWN; } + _plist_free_data(data); //now handle value 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: ctx->err = PLIST_ERR_PARSE; goto err_out; } + + /* When closing a dictionary, convert a single-entry + { "CF$UID" : <integer> } dictionary into a PLIST_UID node. + Perform the conversion before moving to the parent node. */ + if (!strcmp(node_path->type, XPLIST_DICT) && parent && plist_get_node_type(parent) == PLIST_DICT) { + if (plist_dict_get_size(parent) == 1) { + plist_t uid = plist_dict_get_item(parent, "CF$UID"); + if (uid) { + uint64_t val = 0; + if (plist_get_node_type(uid) != PLIST_INT) { + ctx->err = PLIST_ERR_PARSE; + PLIST_XML_ERR("Invalid node type for CF$UID dict entry (must be PLIST_INT)\n"); + goto err_out; + } + plist_get_uint_val(uid, &val); + plist_set_uid_val(parent, val); + } + } + } + if (depth > 0) depth--; struct node_path_item *path_item = node_path; node_path = (struct node_path_item*)node_path->prev; |
