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)
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;