Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _codeql_detected_source_root
4 changes: 3 additions & 1 deletion include/ut_kvp.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName);
* @param[in] pInstance Destination KVP instance (created with `ut_kvp_createInstance()`).
* @param[in] pData Caller-owned buffer containing the serialized KVP payload (text).
* @param[in] length Size of `pData` in bytes (use `strlen(pData)+1` for text).
* @param[in] base_dir Optional base directory for resolving relative include paths (can be NULL).
* If NULL, relative includes will be resolved from CWD.
*
* @retval UT_KVP_STATUS_SUCCESS Parsed successfully.
* @retval UT_KVP_STATUS_INVALID_PARAM Invalid pointer/length.
* @retval UT_KVP_STATUS_PARSING_ERROR Malformed payload.
* @retval UT_KVP_STATUS_INVALID_INSTANCE Invalid destination instance.
*/
ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uint32_t length);
ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uint32_t length, const char *base_dir);

/**!
* @brief Closes a previously opened KVP profile and frees its memory.
Expand Down
182 changes: 150 additions & 32 deletions src/ut_kvp.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <unistd.h>
#include <assert.h>
#include <curl/curl.h>
#include <libgen.h>
#include <stdlib.h>

/* Application Includes */
#include <ut_kvp.h>
Expand Down Expand Up @@ -57,9 +59,9 @@ static bool str_to_bool(const char *string);
static ut_kvp_status_t ut_kvp_getField(ut_kvp_instance_t *pInstance, const char *pszKey, char *pszResult);
static void convert_dot_to_slash(const char *key, char *output);
static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp);
static struct fy_node* process_include(const char *filename, int depth, struct fy_document *doc);
static struct fy_node* process_include(const char *filename, int depth, struct fy_document *doc, const char *base_dir);
static void merge_nodes(struct fy_node *mainNode, struct fy_node *includeNode);
static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_document *dstDoc, int depth);
static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_document *dstDoc, int depth, const char *base_dir);
static const void *find_pattern_from_buffer(const void *buffer, size_t bufferLength, const void *pattern, size_t patternLength);

ut_kvp_instance_t *ut_kvp_createInstance(void)
Expand Down Expand Up @@ -115,15 +117,33 @@ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName)
return UT_KVP_STATUS_FILE_OPEN_ERROR;
}

// Resolve to absolute path
char resolved_path[PATH_MAX];
if (realpath(fileName, resolved_path) == NULL)
{
UT_LOG_ERROR("Failed to resolve path [%s]", fileName);
return UT_KVP_STATUS_FILE_OPEN_ERROR;
}

// Extract base directory from the resolved path for relative includes
char *resolved_copy = strdup(resolved_path);
if (resolved_copy == NULL)
{
UT_LOG_ERROR("Memory allocation failure");
return UT_KVP_STATUS_PARSING_ERROR;
}
char *base_dir = dirname(resolved_copy);

// Load the new document
struct fy_document *newDoc = fy_document_build_from_file(NULL, fileName);
struct fy_document *newDoc = fy_document_build_from_file(NULL, resolved_path);
if (newDoc == NULL || fy_document_resolve(newDoc) != 0)
{
if (newDoc)
{
UT_LOG_ERROR("Error resolving document for anchors, aliases and merge keys");
fy_document_destroy(newDoc);
}
free(resolved_copy);
ut_kvp_close(pInstance);
return UT_KVP_STATUS_PARSING_ERROR;
}
Expand All @@ -134,6 +154,7 @@ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName)
{
UT_LOG_ERROR("Unable to get root node from document");
fy_document_destroy(newDoc);
free(resolved_copy);
ut_kvp_close(pInstance);
return UT_KVP_STATUS_PARSING_ERROR;
}
Expand All @@ -147,17 +168,19 @@ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName)
{
UT_LOG_ERROR("Unable to create doc");
fy_document_destroy(newDoc);
free(resolved_copy);
ut_kvp_close(pInstance);
return UT_KVP_STATUS_PARSING_ERROR;
}
}

// Always process includes via process_node_copy
struct fy_node *copiedRoot = process_node_copy(newRoot, pInternal->fy_handle, 0);
// Always process includes via process_node_copy with base directory context
struct fy_node *copiedRoot = process_node_copy(newRoot, pInternal->fy_handle, 0, base_dir);
if (copiedRoot == NULL)
{
UT_LOG_ERROR("Unable to process node");
fy_document_destroy(newDoc);
free(resolved_copy);
ut_kvp_close(pInstance);
return UT_KVP_STATUS_PARSING_ERROR;
}
Expand All @@ -177,15 +200,16 @@ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName)
}

fy_document_destroy(newDoc);
free(resolved_copy);

return UT_KVP_STATUS_SUCCESS;
}

ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uint32_t length )
ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uint32_t length, const char *base_dir)
{
struct fy_node *node;
ut_kvp_instance_internal_t *pInternal = validateInstance(pInstance);
char *cData = NULL;
size_t actual_length;

if (pInstance == NULL)
{
Expand All @@ -198,19 +222,45 @@ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uin
return UT_KVP_STATUS_INVALID_PARAM;
}

cData = strdup((const char*)pData);
// Handle -1 as a special value meaning "use strlen"
if (length == (uint32_t)-1)
{
actual_length = strlen(pData);
}
else
{
actual_length = length;
}

if (cData == NULL)
// Write pData to a temporary file to avoid memory leak with fy_document_build_from_malloc_string
FILE *tmp = tmpfile();
if (!tmp)
{
UT_LOG_ERROR("Memory allocation failure");
UT_LOG_ERROR("Failed to create temporary file");
return UT_KVP_STATUS_PARSING_ERROR;
}

struct fy_document *srcDoc = fy_document_build_from_malloc_string(NULL, cData, length);
size_t written = fwrite(pData, 1, actual_length, tmp);
if (written != actual_length)
{
UT_LOG_ERROR("Failed to write all data to temporary file");
fclose(tmp);
return UT_KVP_STATUS_PARSING_ERROR;
}
fflush(tmp);
rewind(tmp);

if(fy_document_resolve(srcDoc) != 0)
struct fy_document *srcDoc = fy_document_build_from_fp(NULL, tmp);
//using this instead of fy_document_build_from_malloc_string(), as the malloced string was not getting freed at fy_document_destroy()

if (srcDoc == NULL || fy_document_resolve(srcDoc) != 0)
{
UT_LOG_ERROR("Error resolving document for anchors, aliases and merge keys");
if (srcDoc)
{
UT_LOG_ERROR("Error resolving document for anchors, aliases and merge keys");
fy_document_destroy(srcDoc);
}
fclose(tmp);
ut_kvp_close(pInstance);
return UT_KVP_STATUS_PARSING_ERROR;
}
Expand All @@ -227,23 +277,27 @@ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uin
UT_LOG_ERROR("Unable to parse file/memory");
ut_kvp_close(pInstance);
fy_document_destroy(srcDoc);
fclose(tmp);
return UT_KVP_STATUS_PARSING_ERROR;
}
}

struct fy_node *srcNode = fy_document_root(srcDoc);
node = process_node_copy(srcNode, pInternal->fy_handle, 0);
// Pass base_dir for resolving relative includes; if NULL, will fall back to CWD
node = process_node_copy(srcNode, pInternal->fy_handle, 0, base_dir);

if (node == NULL)
{
UT_LOG_ERROR("Unable to process node");
ut_kvp_close(pInstance);
fy_document_destroy(srcDoc);
fclose(tmp);
return UT_KVP_STATUS_PARSING_ERROR;
}

fy_document_set_root(pInternal->fy_handle, node);
fy_document_destroy(srcDoc);
fclose(tmp);

return UT_KVP_STATUS_SUCCESS;
}
Expand Down Expand Up @@ -910,7 +964,7 @@ static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, v
return realsize;
}

static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_document *dstDoc, int depth)
static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_document *dstDoc, int depth, const char *base_dir)
{
if (srcNode == NULL || dstDoc == NULL)
{
Expand All @@ -932,10 +986,10 @@ static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_docu
const char *filepath = fy_node_get_scalar(srcNode, NULL);
if (filepath)
{
struct fy_node *included = process_include(filepath, depth, dstDoc);
struct fy_node *included = process_include(filepath, depth, dstDoc, base_dir);
if (included)
{
return process_node_copy(included, dstDoc, depth + 1);
return process_node_copy(included, dstDoc, depth + 1, base_dir);
}
}
return NULL;
Expand Down Expand Up @@ -971,16 +1025,16 @@ static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_docu
const char *filepath = fy_node_get_scalar(incl, NULL);
if (filepath)
{
struct fy_node *included = process_include(filepath, depth, dstDoc);
struct fy_node *included = process_include(filepath, depth, dstDoc, base_dir);
if (included)
copied_entry = process_node_copy(included, dstDoc, depth + 1);
copied_entry = process_node_copy(included, dstDoc, depth + 1, base_dir);
}
}
}

if (copied_entry == NULL)
{
copied_entry = process_node_copy(entry, dstDoc, depth);
copied_entry = process_node_copy(entry, dstDoc, depth, base_dir);
}

if (copied_entry)
Expand Down Expand Up @@ -1023,7 +1077,7 @@ static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_docu
const char *filepath = fy_node_get_scalar(val_node, NULL);
if (filepath)
{
struct fy_node *included = process_include(filepath, depth, dstDoc);
struct fy_node *included = process_include(filepath, depth, dstDoc, base_dir);
if (included)
{
// If the included node is a mapping, merge it into the new_map
Expand All @@ -1035,7 +1089,7 @@ static struct fy_node* process_node_copy(struct fy_node *srcNode, struct fy_docu

// Regular case: recursive copy
struct fy_node *copied_key = fy_node_copy(dstDoc, key_node);
struct fy_node *copied_val = process_node_copy(val_node, dstDoc, depth);
struct fy_node *copied_val = process_node_copy(val_node, dstDoc, depth, base_dir);

if (copied_key && copied_val)
{
Expand Down Expand Up @@ -1099,7 +1153,7 @@ static void merge_nodes(struct fy_node *mainNode, struct fy_node *includeNode)
}
}

static struct fy_node* process_include(const char *filename, int depth, struct fy_document *doc)
static struct fy_node* process_include(const char *filename, int depth, struct fy_document *doc, const char *base_dir)
{
ut_kvp_download_memory_internal_t mChunk;

Expand Down Expand Up @@ -1185,7 +1239,7 @@ static struct fy_node* process_include(const char *filename, int depth, struct f
}

struct fy_node *srcNode = fy_document_root(srcDoc);
struct fy_node *root = process_node_copy(srcNode, doc, depth + 1);
struct fy_node *root = process_node_copy(srcNode, doc, depth + 1, base_dir);

fy_document_destroy(srcDoc);
fclose(tmp);
Expand All @@ -1195,28 +1249,92 @@ static struct fy_node* process_include(const char *filename, int depth, struct f
}
else
{
// Local file include
FILE *file = fopen(filename, "r");
// Local file include - resolve relative paths
char resolved_path[PATH_MAX];
char *final_path = NULL;

// Check if the path is absolute
if (filename[0] == '/')
{
// Absolute path - use realpath to normalize it
if (realpath(filename, resolved_path) != NULL)
{
final_path = resolved_path;
}
else
{
UT_LOG_ERROR("Failed to resolve absolute path '%s'", filename);
return NULL;
}
}
else
{
// Relative path - resolve relative to base directory
if (base_dir != NULL)
{
// Construct full path: base_dir/filename
char temp_path[PATH_MAX];
snprintf(temp_path, sizeof(temp_path), "%s/%s", base_dir, filename);

// Resolve to absolute path
if (realpath(temp_path, resolved_path) != NULL)
{
final_path = resolved_path;
}
else
{
UT_LOG_ERROR("Failed to resolve relative path '%s' from base directory '%s'", filename, base_dir);
return NULL;
}
}
else
{
// No base directory - try resolving relative to CWD as fallback
if (realpath(filename, resolved_path) != NULL)
{
final_path = resolved_path;
}
else
{
UT_LOG_ERROR("Failed to resolve path '%s'", filename);
return NULL;
}
}
}

FILE *file = fopen(final_path, "r");
if (!file)
{
UT_LOG_ERROR("Error: Cannot open include file '%s'.\n", filename);
UT_LOG_ERROR("Cannot open include file '%s'", final_path);
return NULL;
}

// struct fy_document *doc;
struct fy_document *srcDoc = fy_document_build_from_file(NULL, filename);
struct fy_document *srcDoc = fy_document_build_from_file(NULL, final_path);
if (srcDoc == NULL)
{
UT_LOG_ERROR("Error: Cannot parse include file '%s'.\n", filename);
UT_LOG_ERROR("Cannot parse include file '%s'", final_path);
fclose(file);
return NULL;
}

struct fy_node *root;
// Extract base directory from the resolved include file path for nested includes
char *resolved_copy = strdup(final_path);
if (resolved_copy == NULL)
{
UT_LOG_ERROR("Memory allocation failure");
fclose(file);
fy_document_destroy(srcDoc);
return NULL;
}
char *include_base_dir = dirname(resolved_copy);

struct fy_node *srcNode = fy_document_root(srcDoc);
root = process_node_copy(srcNode, doc, depth + 1);
struct fy_node *root = process_node_copy(srcNode, doc, depth + 1, include_base_dir);

fclose(file);
fy_document_destroy(srcDoc);
free(resolved_copy);

return root;
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/src/assets/include/2d.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
"2":
value: true
include: assets/include/3d.yaml
include: 3d.yaml
2 changes: 1 addition & 1 deletion tests/src/assets/include/3d.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
"3":
value: true
include: assets/include/4d.yaml
include: 4d.yaml
Loading
Loading