update ucx, libidav and add first gui code

Sat, 27 Jan 2024 17:50:19 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 27 Jan 2024 17:50:19 +0100
changeset 2
fbdfaacc4182
parent 1
b5bb7b3cd597
child 3
f154867f54dc

update ucx, libidav and add first gui code

application/application.c file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/main.c file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
application/window.h file | annotate | diff | comparison | revisions
libidav/Makefile file | annotate | diff | comparison | revisions
libidav/config.c file | annotate | diff | comparison | revisions
libidav/config.h file | annotate | diff | comparison | revisions
libidav/session.c file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/idav/main.c file | annotate | diff | comparison | revisions
make/vs/idav/main.h file | annotate | diff | comparison | revisions
make/vs/libidav/libidav.vcxproj file | annotate | diff | comparison | revisions
make/vs/libidav/libidav.vcxproj.filters file | annotate | diff | comparison | revisions
ucx/array_list.c file | annotate | diff | comparison | revisions
ucx/buffer.c file | annotate | diff | comparison | revisions
ucx/compare.c file | annotate | diff | comparison | revisions
ucx/cx/array_list.h file | annotate | diff | comparison | revisions
ucx/cx/buffer.h file | annotate | diff | comparison | revisions
ucx/cx/collection.h file | annotate | diff | comparison | revisions
ucx/cx/common.h file | annotate | diff | comparison | revisions
ucx/cx/compare.h file | annotate | diff | comparison | revisions
ucx/cx/hash_key.h file | annotate | diff | comparison | revisions
ucx/cx/hash_map.h file | annotate | diff | comparison | revisions
ucx/cx/iterator.h file | annotate | diff | comparison | revisions
ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
ucx/cx/list.h file | annotate | diff | comparison | revisions
ucx/cx/map.h file | annotate | diff | comparison | revisions
ucx/cx/mempool.h file | annotate | diff | comparison | revisions
ucx/cx/printf.h file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/cx/test.h file | annotate | diff | comparison | revisions
ucx/cx/tree.h file | annotate | diff | comparison | revisions
ucx/cx/utils.h file | annotate | diff | comparison | revisions
ucx/linked_list.c file | annotate | diff | comparison | revisions
ucx/list.c file | annotate | diff | comparison | revisions
ucx/printf.c file | annotate | diff | comparison | revisions
ucx/string.c file | annotate | diff | comparison | revisions
ucx/tree.c file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/application.c	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "application.h"
+
+#include <string.h>
+#include <stdbool.h>
+
+#include "window.h"
+
+
+
+void application_startup(UiEvent* event, void* data) {
+	application_create_menu();
+
+	UiObject* win = window_create();
+	ui_show(win);
+}
+
+void application_create_menu(void) {
+
+}
+
+
+DavApp* application_create_app_document(void) {
+	DavApp* doc = ui_document_new(sizeof(DavApp));
+	return doc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/application.h	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,55 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ui/ui.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libidav/webdav.h>
+#include <libidav/config.h>
+
+
+typedef struct DavApp {
+	DavConfig *dav_config;
+
+} DavApp;
+
+
+/*
+ * startup callback for the ui framework
+ */
+void application_startup(UiEvent* event, void* data);
+
+/*
+ * create the global menu and toolbar
+ */
+void application_create_menu(void);
+
+
+DavApp* application_create_app_document(void);
--- a/application/main.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/application/main.c	Sat Jan 27 17:50:19 2024 +0100
@@ -1,110 +1,58 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Olaf Wintermann. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <ui/ui.h>
-#include <cx/buffer.h>
-#include <cx/utils.h>
-
-typedef struct {
-    UiText *text;
-} MyDocument;
-
-MyDocument *doc1;
-MyDocument *doc2;
-
-
-void action_menu(UiEvent *event, void *userdata) {
-    printf("action_menu: %s\n", (char*)userdata);
-}
-
-void action_button(UiEvent *event, void *userdata) {
-    printf("button test\n");
-    MyDocument *doc = event->document;
-    if(!doc) {
-        printf("no document\n");
-        return;
-    }
-    
-    char *text = doc->text->get(doc->text);
-    printf("text: {\n%s\n}\n", text);
-}
-
-void action_switch(UiEvent *event, void *userdata) {
-    if(event->document == doc1) {
-        ui_set_document(event->obj, doc2);
-    } else {
-        ui_set_document(event->obj, doc1);
-    }
-}
-
-
-MyDocument* create_doc(void) {
-    MyDocument *doc = ui_document_new(sizeof(MyDocument));
-    UiContext *docctx = ui_document_context(doc);
-    doc->text = ui_text_new(docctx, "text");
-    return doc;
-}
-
-void application_startup(UiEvent *event, void *data) {
-    
-    UiObject *obj = ui_window("Test", NULL);
-    ui_textarea_nv(obj, "text");
-    ui_button(obj, "Test", action_button, NULL);
-    ui_button(obj, "Switch Document", action_switch, NULL);
-    
-    doc1 = create_doc();
-    doc2 = create_doc();
-    
-    ui_attach_document(obj->ctx, doc1);
-    
-    ui_show(obj);
-}
-
-int main(int argc, char** argv) { 
-    ui_init("app1", argc, argv);
-    ui_onstartup(application_startup, NULL);
-    
-    // menu
-    ui_menu("_File");
-    ui_menuitem("_Hello", action_menu, NULL);
-    ui_submenu("Submenu1");
-    ui_submenu("Submenu2");
-    ui_menuitem("item2", action_menu, NULL);
-    ui_submenu_end();
-    ui_menuitem("item3", action_menu, NULL);
-    ui_submenu_end();
-    ui_menuitem("item4", action_menu, NULL);
-
-    
-    ui_main();
-    
-    return (EXIT_SUCCESS);
-}
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <Windows.h>
+#endif
+
+#include <ui/ui.h>
+
+#include "application.h"
+
+int idav_main(int argc, char **argv) {
+	ui_init("idav", argc, argv);
+	ui_onstartup(application_startup, NULL);
+
+	ui_main();
+
+	return 0;
+}
+
+#ifndef _WIN32
+int main(int argc, char** argv) {
+	return idav_main(argc, argv);
+}
+#else
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
+	return idav_main(__argc, __argv);
+}
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/window.c	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,56 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "window.h"
+
+#include <ui/stock.h>
+
+
+UiObject* window_create(void) {
+	UiObject* obj = ui_window("iDAV", NULL);
+
+	// navigation bar
+	ui_hbox(obj, .fill = UI_OFF) {
+		// TODO: go back/forward buttons
+
+		ui_path_textfield(obj, .fill = UI_ON);
+	}
+
+	// main content
+	UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING, "Type", UI_STRING, "Last Modified", UI_STRING, "Size", -1);
+	ui_table(obj, .fill = UI_ON, .model = model);
+
+	// status bar
+	ui_hbox(obj, .fill = UI_OFF) {
+		ui_label(obj, .label = "");
+	}
+
+	ui_model_free(obj->ctx, model);
+
+	return obj;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/window.h	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,31 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ui/ui.h>
+
+UiObject* window_create(void);
--- a/libidav/Makefile	Mon Jan 22 17:27:47 2024 +0100
+++ b/libidav/Makefile	Sat Jan 27 17:50:19 2024 +0100
@@ -41,6 +41,7 @@
 SRC += crypto.c
 SRC += xml.c
 SRC += versioning.c
+SRC += config.c
 
 OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
 
@@ -52,6 +53,9 @@
 ../build/libidav/%$(OBJ_EXT): %.c
 	$(CC) -I../ucx $(CFLAGS) $(DAV_CFLAGS) -c -o $@ $<
 
+../build/ucx:
+	test -d '$@'
+
 cppcheck: $(SRC)
 	$(CPPCHECK) $(CPPCHECK_CONFIG) -I../ucx $(CPPCHECK_FLAGS) $+ 2>> ../$(CPPCHECK_LOG)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/config.c	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,816 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <cx/hash_map.h>
+#include <errno.h>
+#include <libxml/tree.h>
+#include "utils.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+#define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
+
+#define print_error(lineno, ...) \
+    do {\
+        fprintf(stderr, "Error (config.xml line %u): ", lineno); \
+        fprintf(stderr, __VA_ARGS__); \
+        fprintf(stderr, "Abort.\n"); \
+    } while(0);
+#define print_warning(lineno, ...) \
+    do {\
+        fprintf(stderr, "Warning (config.xml line %u): ", lineno); \
+        fprintf(stderr, __VA_ARGS__); \
+    } while(0);
+
+#ifdef _WIN32
+#define ENV_HOME getenv("USERPROFILE")
+#else
+#define ENV_HOME getenv("HOME")
+#endif /* _WIN32 */
+
+
+static int load_repository(
+        DavConfig *config,
+        DavCfgRepository **list_begin,
+        DavCfgRepository **list_end,
+        xmlNode *reponode);
+static int load_key(
+        DavConfig *config,
+        DavCfgKey **list_begin,
+        DavCfgKey **list_end,
+        xmlNode *keynode);
+static int load_proxy(
+        DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type);
+static int load_namespace(
+        DavConfig *config,
+        DavCfgNamespace **list_begin,
+        DavCfgNamespace **list_end,
+        xmlNode *node);
+static int load_secretstore(DavConfig *config, xmlNode *node);
+
+
+int dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *node) {
+    str->node = node;
+    char *value = util_xml_get_text(node);
+    if(value) {
+        str->value = cx_strdup_a(config->mp->allocator, cx_str(value));
+        return 0;
+    } else {
+        str->value = (cxmutstr){NULL, 0};
+        return 1;
+    }
+}
+
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *node) {
+    cbool->node = node;
+    char *value = util_xml_get_text(node);
+    cbool->value = util_getboolean(value);
+}
+
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) {
+    xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0);
+    if(!doc) {
+        if(error) {
+            *error = DAV_CONFIG_ERROR_XML;
+        }
+        return NULL;
+    }
+    
+    CxMempool *cfg_mp = cxMempoolCreate(128, NULL);
+    cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc);
+    DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig));
+    memset(config, 0, sizeof(DavConfig));
+    config->mp = cfg_mp;
+    config->doc = doc;
+    
+    DavCfgRepository *repos_begin = NULL;
+    DavCfgRepository *repos_end = NULL;
+    DavCfgKey *keys_begin = NULL;
+    DavCfgKey *keys_end = NULL;
+    DavCfgNamespace *namespaces_begin = NULL;
+    DavCfgNamespace *namespaces_end = NULL;
+    
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    xmlNode *node = xml_root->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "repository")) {
+                ret = load_repository(config, &repos_begin, &repos_end, node);
+            } else if(xstreq(node->name, "key")) {
+                ret = load_key(config, &keys_begin, &keys_end, node);
+            } else if (xstreq(node->name, "http-proxy")) {
+                config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+                ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY);
+            } else if (xstreq(node->name, "https-proxy")) {
+                config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+                ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY);
+            } else if (xstreq(node->name, "namespace")) {
+                ret = load_namespace(config, &namespaces_begin, &namespaces_end, node);
+            } else if (xstreq(node->name, "secretstore")) {
+                ret = load_secretstore(config, node);
+            } else {
+                fprintf(stderr, "Unknown config element: %s\n", node->name);
+                ret = 1;
+            }
+        }
+        node = node->next;
+    }
+    
+    config->repositories = repos_begin;
+    config->keys = keys_begin;
+    config->namespaces = namespaces_begin;
+    
+    if(ret != 0 && error) {
+        *error = ret;
+        cxMempoolDestroy(cfg_mp);
+    } 
+    
+    return config;
+}
+
+void dav_config_free(DavConfig *config) {
+    cxMempoolDestroy(config->mp);
+}
+
+CxBuffer* dav_config2buf(DavConfig *config) {
+    xmlChar* xmlText = NULL;
+    int textLen = 0;
+    xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen, 1);
+    
+    if(!xmlText) {
+        return NULL;
+    }
+    
+    CxBuffer *buf = cxBufferCreate(NULL, textLen, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+    cxBufferWrite(xmlText, 1, textLen, buf);
+    xmlFree(xmlText);
+    return buf;
+}
+
+
+static int repo_add_config(
+        DavConfig *config,
+        DavCfgRepository *repo,
+        xmlNode* node)
+{
+    unsigned short lineno = node->line;
+    char *key = (char*)node->name;
+    char *value = util_xml_get_text(node);
+
+    /* every key needs a value */
+    if(!value) {
+        /* TODO: maybe this should only be reported, if the key is valid
+         * But this makes the code very ugly.
+         */
+        print_error(lineno, "missing value for config element: %s\n", key);
+        return 1;
+    }
+    
+    if(xstreq(key, "name")) {
+        dav_cfg_string_set_value(config, &repo->name, node);
+    } else if(xstreq(key, "url")) {
+        dav_cfg_string_set_value(config, &repo->url, node);
+    } else if(xstreq(key, "user")) {
+        dav_cfg_string_set_value(config, &repo->user, node);
+    } else if(xstreq(key, "password")) {
+        dav_cfg_string_set_value(config, &repo->password, node);
+    } else if(xstreq(key, "stored-user")) {
+        dav_cfg_string_set_value(config, &repo->stored_user, node);
+    } else if(xstreq(key, "default-key")) {
+        dav_cfg_string_set_value(config, &repo->default_key, node);
+    } else if(xstreq(key, "full-encryption")) {
+        dav_cfg_bool_set_value(config, &repo->full_encryption, node);
+    } else if(xstreq(key, "content-encryption")) {
+        dav_cfg_bool_set_value(config, &repo->content_encryption, node);
+    } else if(xstreq(key, "decrypt-content")) {
+        dav_cfg_bool_set_value(config, &repo->decrypt_content, node);
+    } else if(xstreq(key, "decrypt-name")) {
+        dav_cfg_bool_set_value(config, &repo->decrypt_name, node);
+    } else if(xstreq(key, "cert")) {
+        dav_cfg_string_set_value(config, &repo->cert, node);
+    } else if(xstreq(key, "verification")) {
+        dav_cfg_bool_set_value(config, &repo->verification, node);
+    } else if(xstreq(key, "ssl-version")) {
+        repo->ssl_version.node = node;
+        if(xstrEQ(value, "TLSv1")) {
+            repo->ssl_version.value = CURL_SSLVERSION_TLSv1;
+        } else if(xstrEQ(value, "SSLv2")) {
+            repo->ssl_version.value = CURL_SSLVERSION_SSLv2;
+        } else if(xstrEQ(value, "SSLv3")) {
+            repo->ssl_version.value = CURL_SSLVERSION_SSLv3;
+        }
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
+        else if(xstrEQ(value, "TLSv1.0")) {
+            repo->ssl_version.value = CURL_SSLVERSION_TLSv1_0;
+        } else if(xstrEQ(value, "TLSv1.1")) {
+            repo->ssl_version.value = CURL_SSLVERSION_TLSv1_1;
+        } else if(xstrEQ(value, "TLSv1.2")) {
+            repo->ssl_version.value = CURL_SSLVERSION_TLSv1_2;
+        }
+#endif
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
+        else if(xstrEQ(value, "TLSv1.3")) {
+            repo->ssl_version.value = CURL_SSLVERSION_TLSv1_3;
+        }
+#endif
+        else {
+            print_warning(lineno, "unknown ssl version: %s\n", value);
+            repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+        }
+    } else if(xstreq(key, "authmethods")) {
+        repo->authmethods.node = node;
+        repo->authmethods.value = CURLAUTH_NONE;
+        const char *delims = " \t\r\n";
+        char *meths = strdup(value);
+        char *meth = strtok(meths, delims);
+        while (meth) {
+            if(xstrEQ(meth, "basic")) {
+                repo->authmethods.value |= CURLAUTH_BASIC;
+            } else if(xstrEQ(meth, "digest")) {
+                repo->authmethods.value |= CURLAUTH_DIGEST;
+            } else if(xstrEQ(meth, "negotiate")) {
+                repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE;
+            } else if(xstrEQ(meth, "ntlm")) {
+                repo->authmethods.value |= CURLAUTH_NTLM;
+            } else if(xstrEQ(meth, "any")) {
+                repo->authmethods.value = CURLAUTH_ANY;
+            } else if(xstrEQ(meth, "none")) {
+                /* skip */
+            } else {
+                print_warning(lineno,
+                        "unknown authentication method: %s\n", meth);
+            }
+            meth = strtok(NULL, delims);
+        }
+        free(meths);
+    } else {
+        print_error(lineno, "unkown repository config element: %s\n", key);
+        return 1;
+    }
+    return 0;
+}
+
+static int load_repository(
+        DavConfig *config,
+        DavCfgRepository **list_begin,
+        DavCfgRepository **list_end,
+        xmlNode *reponode)
+{
+    DavCfgRepository *repo = dav_repository_new(config);
+    repo->node = reponode;
+    
+    // add repo config from child nodes
+    xmlNode *node = reponode->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            ret = repo_add_config(config, repo, node);
+        }
+        node = node->next;
+    }
+    
+    // success: add repo to the configuration, error: free repo
+    if(ret) {
+        return 1;
+    } else {
+        cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgRepository, prev),
+                offsetof(DavCfgRepository, next),
+                repo);
+    }
+    
+    return 0;
+}
+
+static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) {
+    xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t");
+    xmlAddChild(node, text1);
+    
+    cxmutstr ctn = cx_strdup(cx_strcast(content));
+    xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr);
+    free(ctn.ptr);
+    
+    xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n");
+    xmlAddChild(node, text2);
+    
+    return newNode;
+}
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) {
+    if(repo->node) {
+        fprintf(stderr, "Error: dav_config_add_repository: node already exists\n");
+        return;
+    }
+    
+    xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository");
+    xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(repoNode, rtext1);
+    
+    if(repo->name.value.ptr) {
+        repo->name.node = addXmlNode(repoNode, "name", repo->name.value);
+    }
+    if(repo->url.value.ptr) {
+        repo->url.node = addXmlNode(repoNode, "url", repo->url.value);
+    }
+    if(repo->user.value.ptr) {
+        repo->user.node = addXmlNode(repoNode, "user", repo->user.value);
+    }
+    if(repo->password.value.ptr) {
+        repo->password.node = addXmlNode(repoNode, "password", repo->password.value);
+    }
+    
+    if(repo->stored_user.value.ptr) {
+        repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value);
+    }
+    if(repo->default_key.value.ptr) {
+        repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value);
+    }
+    if(repo->cert.value.ptr) {
+        repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value);
+    }
+    
+    // TODO: implement booleans
+    
+    // indent closing tag
+    xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+    xmlAddChild(repoNode, rtext2);
+    
+    // add repository to internal list
+    DavCfgRepository **list_begin = &config->repositories;
+    cx_linked_list_add(
+                (void**)list_begin,
+                NULL,
+                offsetof(DavCfgRepository, prev),
+                offsetof(DavCfgRepository, next),
+                repo);
+    
+    // add repository element to the xml document
+    xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+    
+    xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+    xmlAddChild(xml_root, text1);
+    
+    xmlAddChild(xml_root, repoNode);
+    
+    xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(xml_root, text2);
+}
+
+DavCfgRepository* dav_repository_new(DavConfig *config) {
+    DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository));
+    repo->decrypt_name.value = false;
+    repo->decrypt_content.value = true;
+    repo->decrypt_properties.value = false;
+    repo->verification.value = true;
+    repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+    repo->authmethods.value = CURLAUTH_BASIC;
+    return repo;
+}
+
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo) {
+    // TODO
+}
+
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) {
+    if(repo->prev) {
+        repo->prev->next = repo->next;
+    }
+    if(repo->next) {
+        repo->next->prev = repo->prev;
+    }
+    
+    if(repo->node) {
+        // TODO: remove newline after repo node
+        
+        xmlUnlinkNode(repo->node);
+        xmlFreeNode(repo->node);
+    }
+}
+
+int dav_repository_get_flags(DavCfgRepository *repo) {
+    int flags = 0;
+    
+    DavBool encrypt_content = FALSE;
+    DavBool encrypt_name = FALSE;
+    DavBool encrypt_properties = FALSE;
+    DavBool decrypt_content = FALSE;
+    DavBool decrypt_name = FALSE;
+    DavBool decrypt_properties = FALSE;
+    if(repo->full_encryption.value) {
+        encrypt_content = TRUE;
+        encrypt_name = TRUE;
+        encrypt_properties = TRUE;
+        decrypt_content = TRUE;
+        decrypt_name = TRUE;
+        decrypt_properties = TRUE;
+    } else if(repo->content_encryption.value) {
+        encrypt_content = TRUE;
+        decrypt_content = TRUE;
+    }
+    
+    if(decrypt_content) {
+        flags |= DAV_SESSION_DECRYPT_CONTENT;
+    }
+    if(decrypt_name) {
+        flags |= DAV_SESSION_DECRYPT_NAME;
+    }
+    if(decrypt_properties) {
+        flags |= DAV_SESSION_DECRYPT_PROPERTIES;
+    }
+    if(encrypt_content) {
+        flags |= DAV_SESSION_ENCRYPT_CONTENT;
+    }
+    if(encrypt_name) {
+        flags |= DAV_SESSION_ENCRYPT_NAME;
+    }
+    if(encrypt_properties) {
+        flags |= DAV_SESSION_ENCRYPT_PROPERTIES;
+    }
+    return flags;
+}
+
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) {
+    if(repo->url.value.ptr) {
+        cxFree(config->mp->allocator, repo->url.value.ptr);
+    }
+    repo->url.value = cx_strdup_a(config->mp->allocator, newurl);
+}
+
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) {
+    const CxAllocator *a = config->mp->allocator;
+    repo->user.value = cx_strdup_a(a, user);
+    char *pwenc = util_base64encode(password.ptr, password.length);
+    repo->password.value = cx_strdup_a(a, cx_str(pwenc));
+    free(pwenc);
+}
+
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) {
+    cxmutstr pw = { NULL, 0 };
+    if(repo->password.value.ptr) {
+        pw = cx_mutstr(util_base64decode(repo->password.value.ptr));
+    }
+    return pw;
+}
+
+
+static int load_key(
+        DavConfig *config,
+        DavCfgKey **list_begin,
+        DavCfgKey **list_end,
+        xmlNode *keynode)
+{
+    xmlNode *node = keynode->children;
+    DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+    memset(key, 0, sizeof(DavCfgKey));
+    key->type = DAV_KEY_TYPE_AES256;
+    
+    int error = 0;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "name")) {
+                dav_cfg_string_set_value(config, &key->name, node);
+            } else if(xstreq(node->name, "file")) {
+                dav_cfg_string_set_value(config, &key->file, node);
+            } else if(xstreq(node->name, "type")) {
+                const char *value = util_xml_get_text(node);
+                key->type_node = node;
+                if(!strcmp(value, "aes128")) {
+                    key->type = DAV_KEY_TYPE_AES128;
+                } else if(!strcmp(value, "aes256")) {
+                    key->type = DAV_KEY_TYPE_AES256;
+                } else {
+                    print_error(node->line, "unknown key type %s\n", value);
+                    error = 1;
+                }
+            } else {
+                key->unknown_elements++;
+            }
+                
+        }
+        node = node->next;
+    }
+    
+    if(!key->name.value.ptr) {
+        error = 1;
+    }
+    
+    if(!error) {
+        error = 0;
+        size_t expected_length = 0;
+        if(key->type == DAV_KEY_TYPE_AES128) {
+            expected_length = 16;
+        }
+        if(key->type == DAV_KEY_TYPE_AES256) {
+            expected_length = 32;
+        }
+        /*
+        if(key->length < expected_length) {
+            print_error(keynode->line, "key %s is too small (%zu < %zu)\n",
+                    key->name,
+                    key->length,
+                    expected_length);
+            error = 1;
+        }
+        
+        // add key to context
+        if(!error) {
+            cxMapPut(keys, cx_hash_key_str(key->name), key);
+            dav_context_add_key(context, key);
+        }
+        */
+    }
+    
+    // cleanup
+    if(error) {
+        return 1;
+    } else {
+        // add key to the configuration
+        cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgKey, prev),
+                offsetof(DavCfgKey, next),
+                key);
+        
+        return 0;
+    }
+}
+
+static int load_proxy(
+        DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type)
+{
+    const char *stype;
+    if(type == DAV_HTTPS_PROXY) {
+        stype = "https";
+    } else if(type == DAV_HTTP_PROXY) {
+        stype = "http";
+    }
+    
+    if(!proxy) {
+        // no xml error - so report this directly via fprintf
+        fprintf(stderr, "no memory reserved for %s proxy.\n", stype);
+        return 1;
+    }
+    
+    xmlNode *node = proxynode->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            int reportmissingvalue = 0;
+            if(xstreq(node->name, "url")) {
+                reportmissingvalue = dav_cfg_string_set_value(config, &proxy->url, node);
+            } else if(xstreq(node->name, "user")) {
+                reportmissingvalue = dav_cfg_string_set_value(config, &proxy->user, node);
+            } else if(xstreq(node->name, "password")) {
+                reportmissingvalue = dav_cfg_string_set_value(config, &proxy->password, node);
+            } else if(xstreq(node->name, "no")) {
+                reportmissingvalue = dav_cfg_string_set_value(config, &proxy->noproxy, node);
+            } else {
+                proxy->unknown_elements++;
+            }
+            
+            if (reportmissingvalue) {
+                print_error(node->line,
+                        "missing value for proxy configuration element: %s\n",
+                        node->name);
+                ret = 1;
+                break;
+            }
+        }
+        node = node->next;
+    }
+    
+    if(!ret && !proxy->url.value.ptr) {
+        print_error(proxynode->line, "missing url for %s proxy.\n", stype);
+        return 1;
+    }
+    
+    return ret;
+}
+
+static char* get_attr_content(xmlNode *node) {
+    // TODO: remove code duplication (util_xml_get_text)
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+static int load_namespace(
+        DavConfig *config,
+        DavCfgNamespace **list_begin,
+        DavCfgNamespace **list_end,
+        xmlNode *node)
+{
+    const char *prefix = NULL;
+    const char *uri = NULL;
+    xmlAttr *attr = node->properties;
+    while(attr) {
+        if(attr->type == XML_ATTRIBUTE_NODE) {
+            char *value = get_attr_content(attr->children);
+            if(!value) {
+                print_error(
+                        node->line,
+                        "missing value for attribute %s\n", (char*)attr->name);
+                return 1;
+            }
+            if(xstreq(attr->name, "prefix")) {
+                prefix = value;
+            } else if(xstreq(attr->name, "uri")) {
+                uri = value;
+            } else {
+                print_error(
+                        node->line,
+                        "unexpected attribute %s\n", (char*)attr->name);
+                return 1;
+            }
+        }
+        attr = attr->next;
+    }
+    
+    if(!prefix) {
+        print_error(node->line, "missing prefix attribute\n");
+        return 1;
+    }
+    if(!uri) {
+        print_error(node->line, "missing uri attribute\n");
+        return 1;
+    }
+    
+    DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace));
+    memset(ns, 0, sizeof(DavCfgNamespace));
+    ns->node = node;
+    ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix));
+    ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri));
+    cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgNamespace, prev),
+                offsetof(DavCfgNamespace, next),
+                ns);
+    
+    return 0;
+}
+
+static int load_secretstore(DavConfig *config, xmlNode *node) {
+    // currently only one secretstore is supported
+    
+    if(config->secretstore) {
+        return 1;
+    }
+    
+    config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore));
+    
+    node = node->children;
+    int error = 0;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "unlock-command")) {
+                dav_cfg_string_set_value(config, &config->secretstore->unlock_cmd, node);
+            } else if(xstreq(node->name, "lock-command")) {
+                dav_cfg_string_set_value(config, &config->secretstore->lock_cmd, node);
+            }
+        }
+        node = node->next;
+    }
+    
+    return error;
+}
+
+
+
+
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) {
+    DavCfgRepository *repo = config->repositories;
+    while(repo) {
+        if(!cx_strcmp(cx_strcast(repo->name.value), name)) {
+            return repo;
+        }
+        repo = repo->next;
+    }
+    return NULL;
+}
+
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) {
+    cxmutstr p;
+    DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p);
+    *path = p.ptr;
+    return repo;
+}
+
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) {
+    path->ptr = NULL;
+    path->length = 0;
+    
+    int s;
+    if(cx_strprefix(url, CX_STR("http://"))) {
+        s = 7;
+    } else if(cx_strprefix(url, CX_STR("https://"))) {
+        s = 8;
+    } else {
+        s = 1;
+    }
+
+    // split URL into repository and path
+    cxstring r = cx_strsubs(url, s);
+    cxstring p = cx_strchr(r, '/');
+    r = cx_strsubsl(url, 0, url.length-p.length);
+    if(p.length == 0) {
+        p = cx_strn("/", 1);
+    }
+    
+    DavCfgRepository *repo = dav_config_get_repository(config, r);
+    if(repo) {
+        *path = cx_strdup(p);
+    } else {
+        // TODO: who is responsible for freeing this repository?
+        // how can the callee know, if he has to call free()?
+        repo = dav_repository_new(config);
+        repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR(""));
+        if(url.ptr[url.length-1] == '/') {
+            repo->url.value = cx_strdup_a(config->mp->allocator, url);
+            *path = cx_strdup(CX_STR("/"));
+        } else if (cx_strchr(url, '/').length > 0) {
+            // TODO: fix the following workaround after
+            //       fixing the inconsistent behavior of util_url_*()
+            cxstring repo_url = util_url_base_s(url);
+            repo->url.value = cx_strdup_a(config->mp->allocator, repo_url);
+            *path = cx_strdup(util_url_path_s(url));
+        } else {
+            repo->url.value = cx_strdup(url);
+            *path = cx_strdup(CX_STR("/"));
+        }
+    }
+    
+    return repo;
+}
+
+int dav_config_keytype(DavCfgKeyType type) {
+    switch(type) {
+        default: break;
+        case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256;
+        case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128;
+    }
+    return 0;
+}
+
+int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) {
+    for(DavCfgKey *key=config->keys;key;key=key->next) {
+        char *file = cx_strdup_m(key->file.value).ptr;
+        cxmutstr keycontent = loadkey(file);
+        free(file);
+        
+        // TODO: check key length
+        
+        if(!keycontent.ptr) {
+            return 1;
+        }
+        
+        DavKey *davkey = calloc(1, sizeof(DavKey));
+        davkey->name = cx_strdup_m(key->name.value).ptr;
+        davkey->type = dav_config_keytype(key->type);
+        davkey->data = keycontent.ptr;
+        davkey->length = keycontent.length;
+        
+        dav_context_add_key(ctx, davkey);
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/config.h	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,194 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBIDAV_CONFIG_H
+#define LIBIDAV_CONFIG_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavConfig         DavConfig;
+typedef struct DavCfgRepository  DavCfgRepository;
+typedef struct DavCfgProxy       DavCfgProxy;
+typedef struct DavCfgKey         DavCfgKey;
+typedef struct DavCfgNamespace   DavCfgNamespace;
+typedef struct DavCfgSecretStore DavCfgSecretStore;
+
+typedef struct CfgString  CfgString;
+typedef struct CfgInt     CfgInt;
+typedef struct CfgUInt    CfgUInt;
+typedef struct CfgBool    CfgBool;
+
+typedef enum dav_cfg_key_type DavCfgKeyType;
+
+typedef cxmutstr (*dav_loadkeyfile_func)(const char *filename);
+
+#define DAV_HTTP_PROXY 1
+#define DAV_HTTPS_PROXY 2
+    
+enum dav_cfg_key_type {
+    DAV_KEY_TYPE_AES256 = 0,
+    DAV_KEY_TYPE_AES128,
+    DAV_KEY_TYPE_UNKNOWN
+};
+
+struct DavConfig {
+    CxMempool         *mp;
+    
+    DavCfgRepository  *repositories;
+    DavCfgKey         *keys;
+    DavCfgNamespace   *namespaces;
+    DavCfgProxy       *http_proxy;
+    DavCfgProxy       *https_proxy;
+    DavCfgSecretStore *secretstore;
+    
+    xmlDoc *doc;
+};
+
+struct CfgString {
+    cxmutstr value;
+    xmlNode *node;
+};
+
+struct CfgInt {
+    int64_t value;
+    xmlNode *node;
+};
+
+struct CfgUInt {
+    uint64_t value;
+    xmlNode *node;
+};
+
+struct CfgBool {
+    bool value;
+    xmlNode *node;
+};
+
+
+struct DavCfgRepository {
+    xmlNode *node;
+    
+    CfgString     name;
+    CfgString     url;
+    CfgString     user;
+    CfgString     password;
+    CfgString     stored_user;
+    CfgString     default_key;
+    CfgString     cert;
+    CfgBool       verification;
+    
+    CfgBool       full_encryption;
+    CfgBool       content_encryption;
+    CfgBool       decrypt_content;
+    CfgBool       decrypt_name;
+    CfgBool       decrypt_properties;
+    
+    CfgInt        ssl_version;
+    CfgUInt       authmethods;
+    
+    int           unknown_elements;
+    
+    DavCfgRepository    *prev;
+    DavCfgRepository    *next;
+};
+
+struct DavCfgProxy {
+    CfgString  url;
+    CfgString  user;
+    CfgString  password;
+    CfgString  noproxy;
+    
+    int     unknown_elements;
+};
+
+struct DavCfgKey {
+    CfgString  name;
+    CfgString  file;
+    DavCfgKeyType type;
+    xmlNode *type_node;
+    
+    DavCfgKey *prev;
+    DavCfgKey *next;
+    
+    int       unknown_elements;
+};
+
+struct DavCfgNamespace {
+    xmlNode *node;
+    cxmutstr prefix;
+    cxmutstr uri;
+    
+    DavCfgNamespace *prev;
+    DavCfgNamespace *next;
+};
+
+struct DavCfgSecretStore {
+    CfgString unlock_cmd;
+    CfgString lock_cmd;
+};
+
+enum DavConfigError {
+    DAV_CONFIG_ERROR_XML = 0
+};
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error);
+
+void dav_config_free(DavConfig *config);
+
+CxBuffer* dav_config2buf(DavConfig *config);
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo);
+
+DavCfgRepository* dav_repository_new(DavConfig *config);
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo);
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo);
+int dav_repository_get_flags(DavCfgRepository *repo);
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl);
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password);
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo);
+
+int dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *node);
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *node);
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name);
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path);
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path);
+
+int dav_config_keytype(DavCfgKeyType type);
+int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_CONFIG_H */
+
--- a/libidav/session.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/libidav/session.c	Sat Jan 27 17:50:19 2024 +0100
@@ -114,13 +114,17 @@
     return sn;
 }
 
-void dav_session_set_auth(DavSession *sn, char *user, char *password) {
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password) {
     if(user && password) {
-        size_t ulen = strlen(user);
-        size_t plen = strlen(password);
-        size_t upwdlen = ulen + plen + 2;
+        dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
+    }
+}
+
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) {
+    if(user.length > 0 && password.length > 0) {
+        size_t upwdlen = user.length + password.length + 2;
         char *upwdbuf = malloc(upwdlen);
-        snprintf(upwdbuf, upwdlen, "%s:%s", user, password);
+        snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr);
         curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
         free(upwdbuf);
     }
--- a/libidav/utils.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/libidav/utils.c	Sat Jan 27 17:50:19 2024 +0100
@@ -213,10 +213,11 @@
 }
 
 int util_strtouint(const char *str, uint64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
     char *end;
     errno = 0;
     uint64_t val = strtoull(str, &end, 0);
-    if(errno == 0) {
+    if(errno == 0 && *end == '\0') {
         *value = val;
         return 1;
     } else {
@@ -225,10 +226,11 @@
 }
 
 int util_strtoint(const char *str, int64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
     char *end;
     errno = 0;
     int64_t val = strtoll(str, &end, 0);
-    if(errno == 0) {
+    if(errno == 0 && *end == '\0') {
         *value = val;
         return 1;
     } else {
@@ -237,11 +239,14 @@
 }
 
 int util_szstrtouint(const char *str, uint64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
     char *end;
     errno = 0;
     size_t len = strlen(str);
     uint64_t val = strtoull(str, &end, 0);
-    if(end == str+len) {
+    if(errno != 0) {
+        return 0;
+    } if(end == str+len) {
         *value = val;
         return 1;
     } else if(end == str+len-1) {
@@ -281,7 +286,7 @@
     }
 }
 
-char* util_url_base_s(cxstring url) {
+cxstring util_url_base_s(cxstring url) {
     size_t i = 0;
     if(url.length > 0) {
         int slmax;
@@ -303,12 +308,11 @@
             }
         }
     }
-    cxstring server = cx_strsubsl(url, 0, i);
-    return cx_strdup(server).ptr;
+    return cx_strsubsl(url, 0, i);
 }
 
-char* util_url_base(char *url) {
-    return util_url_base_s(cx_str(url));
+char* util_url_base(const char *url) {
+    return cx_strdup(util_url_base_s(cx_str(url))).ptr;
 }
 
 #ifdef _WIN32
@@ -316,30 +320,30 @@
 #endif
 
 const char* util_url_path(const char *url) {
-    const char *path = NULL;
-    size_t len = strlen(url);
+    return util_url_path_s(cx_str(url)).ptr;
+}
+
+cxstring util_url_path_s(cxstring url) {
+    cxstring path = { "", 0 };
     int slashcount = 0;
     int slmax;
-    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+    if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) {
         slmax = 3;
-    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+    } else if(url.length > 8 && !strncasecmp(url.ptr, "https://", 8)) {
         slmax = 3;
     } else {
         slmax = 1;
     }
     char c;
-    for(int i=0;i<len;i++) {
-        c = url[i];
+    for(int i=0;i<url.length;i++) {
+        c = url.ptr[i];
         if(c == '/') {
             slashcount++;
             if(slashcount == slmax) {
-                path = url + i;
+                path = cx_strsubs(url, i);
                 break;
             }
         }
-    } 
-    if(!path) {
-        path = url + len; // empty string
     }
     return path;
 }
@@ -617,7 +621,7 @@
 }
 
 char* util_concat_path(const char *url_base, const char *p) {
-    cxstring base = cx_str((char*)url_base);
+    cxstring base = cx_str(url_base);
     cxstring path;
     if(p) {
         path = cx_str((char*)p);
@@ -625,6 +629,14 @@
         path = CX_STR("");
     }
     
+    return util_concat_path_s(base, path).ptr;
+}
+
+cxmutstr util_concat_path_s(cxstring base, cxstring path) {
+    if(!path.ptr) {
+        path = CX_STR("");
+    }
+    
     int add_separator = 0;
     if(base.length != 0 && base.ptr[base.length-1] == '/') {
         if(path.ptr[0] == '/') {
@@ -643,7 +655,7 @@
         url = cx_strcat(2, base, path);
     }
     
-    return url.ptr;
+    return url;
 }
 
 char* util_get_url(DavSession *sn, const char *href) {
--- a/libidav/utils.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/libidav/utils.h	Sat Jan 27 17:50:19 2024 +0100
@@ -72,12 +72,14 @@
 
 int util_mkdir(char *path, mode_t mode);
 
-char* util_url_base(char *url);
-char* util_url_base_s(cxstring url);
+char* util_url_base(const char *url);
+cxstring util_url_base_s(cxstring url);
 const char* util_url_path(const char *url);
+cxstring util_url_path_s(cxstring url);
 char* util_url_decode(DavSession *sn, const char *url);
 const char* util_resource_name(const char *url);
 char* util_concat_path(const char *url_base, const char *path);
+cxmutstr util_concat_path_s(cxstring url_base, cxstring path);
 char* util_get_url(DavSession *sn, const char *href);
 void util_set_url(DavSession *sn, const char *href);
 
--- a/libidav/webdav.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/libidav/webdav.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,6 +30,7 @@
 #define	WEBDAV_H
 
 #include <inttypes.h>
+#include <stdbool.h>
 #include <cx/map.h>
 #include <cx/mempool.h>
 #include <cx/linked_list.h>
@@ -267,7 +268,8 @@
         char *base_url,
         char *user,
         char *password);
-void dav_session_set_auth(DavSession *sn, char *user, char *password);
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password);
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password);
 void dav_session_set_baseurl(DavSession *sn, char *base_url);
 void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags);
 
--- a/make/vs/idav/idav.vcxproj	Mon Jan 22 17:27:47 2024 +0100
+++ b/make/vs/idav/idav.vcxproj	Sat Jan 27 17:50:19 2024 +0100
@@ -115,7 +115,8 @@
       <SDLCheck>true</SDLCheck>
       <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ConformanceMode>true</ConformanceMode>
-      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -139,9 +140,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="main.c" />
-  </ItemGroup>
-  <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
@@ -156,7 +154,13 @@
     <Manifest Include="app.manifest" />
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="main.h" />
+    <ClCompile Include="..\..\..\application\application.c" />
+    <ClCompile Include="..\..\..\application\main.c" />
+    <ClCompile Include="..\..\..\application\window.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\application\application.h" />
+    <ClInclude Include="..\..\..\application\window.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
--- a/make/vs/idav/idav.vcxproj.filters	Mon Jan 22 17:27:47 2024 +0100
+++ b/make/vs/idav/idav.vcxproj.filters	Sat Jan 27 17:50:19 2024 +0100
@@ -1,18 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <Filter Include="Quelldateien">
-      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
-      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
-    </Filter>
-    <Filter Include="Headerdateien">
-      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
-      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
-    </Filter>
     <Filter Include="Ressourcendateien">
       <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
       <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
     </Filter>
+    <Filter Include="src">
+      <UniqueIdentifier>{46525c70-7cfc-45fe-ae78-54123ad9bd7e}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
@@ -21,16 +16,25 @@
     <Manifest Include="app.manifest" />
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="main.c">
-      <Filter>Quelldateien</Filter>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\application\application.c">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\application\main.c">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\application\window.c">
+      <Filter>src</Filter>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="main.h">
-      <Filter>Headerdateien</Filter>
+    <ClInclude Include="..\..\..\application\application.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\application\window.h">
+      <Filter>src</Filter>
     </ClInclude>
   </ItemGroup>
-  <ItemGroup>
-    <None Include="packages.config" />
-  </ItemGroup>
 </Project>
\ No newline at end of file
--- a/make/vs/idav/main.c	Mon Jan 22 17:27:47 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#ifdef _WIN32
-#include <Windows.h>
-#endif
-
-#include <ui/ui.h>
-
-int idav_main(void) {
-	return 0;
-}
-
-#ifndef _WIN32
-int main(int argc, char** argv) {
-	return idav_main();
-}
-#else
-int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
-	return idav_main();
-}
-#endif
-
--- a/make/vs/idav/main.h	Mon Jan 22 17:27:47 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-#pragma once
-
--- a/make/vs/libidav/libidav.vcxproj	Mon Jan 22 17:27:47 2024 +0100
+++ b/make/vs/libidav/libidav.vcxproj	Sat Jan 27 17:50:19 2024 +0100
@@ -109,7 +109,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
       <WarningLevel>Level3</WarningLevel>
-      <SDLCheck>true</SDLCheck>
+      <SDLCheck>false</SDLCheck>
       <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ConformanceMode>true</ConformanceMode>
       <LanguageStandard_C>stdc11</LanguageStandard_C>
@@ -144,6 +144,7 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="..\..\..\libidav\config.c" />
     <ClCompile Include="..\..\..\libidav\crypto.c" />
     <ClCompile Include="..\..\..\libidav\davqlexec.c" />
     <ClCompile Include="..\..\..\libidav\davqlparser.c" />
@@ -156,6 +157,7 @@
     <ClCompile Include="..\..\..\libidav\xml.c" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="..\..\..\libidav\config.h" />
     <ClInclude Include="..\..\..\libidav\crypto.h" />
     <ClInclude Include="..\..\..\libidav\davqlexec.h" />
     <ClInclude Include="..\..\..\libidav\davqlparser.h" />
--- a/make/vs/libidav/libidav.vcxproj.filters	Mon Jan 22 17:27:47 2024 +0100
+++ b/make/vs/libidav/libidav.vcxproj.filters	Sat Jan 27 17:50:19 2024 +0100
@@ -45,6 +45,9 @@
     <ClCompile Include="..\..\..\libidav\xml.c">
       <Filter>Quelldateien</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\libidav\config.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\libidav\crypto.h">
@@ -77,5 +80,8 @@
     <ClInclude Include="..\..\..\libidav\xml.h">
       <Filter>Headerdateien</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\libidav\config.h">
+      <Filter>Quelldateien</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file
--- a/ucx/array_list.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/array_list.c	Sat Jan 27 17:50:19 2024 +0100
@@ -27,12 +27,30 @@
  */
 
 #include "cx/array_list.h"
+#include "cx/compare.h"
 #include <assert.h>
 #include <string.h>
 
+// Default array reallocator
+
+static void *cx_array_default_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        __attribute__((__unused__)) struct cx_array_reallocator_s *alloc
+) {
+    return realloc(array, capacity * elem_size);
+}
+
+struct cx_array_reallocator_s cx_array_default_reallocator_impl = {
+        cx_array_default_realloc, NULL, NULL, 0, 0
+};
+
+struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+
 // LOW LEVEL ARRAY LIST FUNCTIONS
 
-enum cx_array_copy_result cx_array_copy(
+enum cx_array_result cx_array_copy(
         void **target,
         size_t *size,
         size_t *capacity,
@@ -59,7 +77,7 @@
     if (needrealloc) {
         // a reallocator and a capacity variable must be available
         if (reallocator == NULL || capacity == NULL) {
-            return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
+            return CX_ARRAY_REALLOC_NOT_SUPPORTED;
         }
 
         // check, if we need to repair the src pointer
@@ -77,7 +95,7 @@
                 *target, cap, elem_size, reallocator
         );
         if (newmem == NULL) {
-            return CX_ARRAY_COPY_REALLOC_FAILED;
+            return CX_ARRAY_REALLOC_FAILED;
         }
 
         // repair src pointer, if necessary
@@ -99,12 +117,13 @@
     *size = newsize;
 
     // return successfully
-    return CX_ARRAY_COPY_SUCCESS;
+    return CX_ARRAY_SUCCESS;
 }
 
 #ifndef CX_ARRAY_SWAP_SBO_SIZE
 #define CX_ARRAY_SWAP_SBO_SIZE 128
 #endif
+unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
 
 void cx_array_swap(
         void *arr,
@@ -208,7 +227,7 @@
         size_t elems_to_move = list->size - index;
         size_t start_of_moved = index + n;
 
-        if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
+        if (CX_ARRAY_SUCCESS != cx_array_copy(
                 &arl->data,
                 &list->size,
                 &arl->capacity,
@@ -228,7 +247,7 @@
     // therefore, it is impossible to leave this function with an invalid array
 
     // place the new elements
-    if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
+    if (CX_ARRAY_SUCCESS == cx_array_copy(
             &arl->data,
             &list->size,
             &arl->capacity,
@@ -308,11 +327,14 @@
             list->size - index - 1,
             &arl->reallocator
     );
-    if (result == 0) {
-        // decrease the size
-        list->size--;
-    }
-    return result;
+
+    // cx_array_copy cannot fail, array cannot grow
+    assert(result == 0);
+
+    // decrease the size
+    list->size--;
+
+    return 0;
 }
 
 static void cx_arl_clear(struct cx_list_s *list) {
@@ -362,9 +384,10 @@
     }
 }
 
-static ssize_t cx_arl_find(
-        struct cx_list_s const *list,
-        void const *elem
+static ssize_t cx_arl_find_remove(
+        struct cx_list_s *list,
+        void const *elem,
+        bool remove
 ) {
     assert(list->cmpfunc != NULL);
     assert(list->size < SIZE_MAX / 2);
@@ -372,7 +395,15 @@
 
     for (ssize_t i = 0; i < (ssize_t) list->size; i++) {
         if (0 == list->cmpfunc(elem, cur)) {
-            return i;
+            if (remove) {
+                if (0 == cx_arl_remove(list, i)) {
+                    return i;
+                } else {
+                    return -1;
+                }
+            } else {
+                return i;
+            }
         }
         cur += list->item_size;
     }
@@ -500,7 +531,7 @@
         cx_arl_clear,
         cx_arl_swap,
         cx_arl_at,
-        cx_arl_find,
+        cx_arl_find_remove,
         cx_arl_sort,
         cx_arl_compare,
         cx_arl_reverse,
@@ -522,13 +553,14 @@
 
     list->base.cl = &cx_array_list_class;
     list->base.allocator = allocator;
-    list->base.cmpfunc = comparator;
     list->capacity = initial_capacity;
 
     if (item_size > 0) {
         list->base.item_size = item_size;
+        list->base.cmpfunc = comparator;
     } else {
         item_size = sizeof(void *);
+        list->base.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
         cxListStorePointers((CxList *) list);
     }
 
--- a/ucx/buffer.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/buffer.c	Sat Jan 27 17:50:19 2024 +0100
@@ -135,6 +135,11 @@
     buffer->pos = 0;
 }
 
+void cxBufferReset(CxBuffer *buffer) {
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
 int cxBufferEof(CxBuffer const *buffer) {
     return buffer->pos >= buffer->size;
 }
--- a/ucx/compare.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/compare.c	Sat Jan 27 17:50:19 2024 +0100
@@ -199,3 +199,15 @@
     }
 }
 
+int cx_cmp_ptr(
+        void const *ptr1,
+        void const *ptr2
+) {
+    uintptr_t p1 = (uintptr_t) ptr1;
+    uintptr_t p2 = (uintptr_t) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
--- a/ucx/cx/array_list.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/array_list.h	Sat Jan 27 17:50:19 2024 +0100
@@ -31,7 +31,6 @@
  * \details Also provides several low-level functions for custom array list implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -46,6 +45,11 @@
 #endif
 
 /**
+ * The maximum item size in an array list that fits into stack buffer when swapped.
+ */
+extern unsigned cx_array_swap_sbo_size;
+
+/**
  * Defines a reallocation mechanism for arrays.
  */
 struct cx_array_reallocator_s {
@@ -54,8 +58,9 @@
      *
      * Implementations are not required to free the original array.
      * This allows re-allocation of static memory by allocating heap memory
-     * and copying the array contents. The information in \p data can keep
-     * track of the state of the memory or other additional allocator info.
+     * and copying the array contents. The information in the custom fields of
+     * the referenced allocator can be used to track the state of the memory
+     * or to transport other additional data.
      *
      * @param array the array to reallocate
      * @param capacity the new capacity (number of elements)
@@ -89,12 +94,17 @@
 };
 
 /**
- * Return codes for cx_array_copy().
+ * A default stdlib-based array reallocator.
  */
-enum cx_array_copy_result {
-    CX_ARRAY_COPY_SUCCESS,
-    CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED,
-    CX_ARRAY_COPY_REALLOC_FAILED,
+extern struct cx_array_reallocator_s *cx_array_default_reallocator;
+
+/**
+ * Return codes for array functions.
+ */
+enum cx_array_result {
+    CX_ARRAY_SUCCESS,
+    CX_ARRAY_REALLOC_NOT_SUPPORTED,
+    CX_ARRAY_REALLOC_FAILED,
 };
 
 /**
@@ -107,7 +117,7 @@
  * capacity is used.
  *
  * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made, unless the allocator is set to \c NULL, in which case
+ * attempt is made, unless the \p reallocator is set to \c NULL, in which case
  * this function ultimately returns a failure.
  *
  * @param target the target array
@@ -122,7 +132,7 @@
  * if re-allocation shall not happen
  * @return zero on success, non-zero error code on failure
  */
-enum cx_array_copy_result cx_array_copy(
+enum cx_array_result cx_array_copy(
         void **target,
         size_t *size,
         size_t *capacity,
@@ -133,6 +143,28 @@
         struct cx_array_reallocator_s *reallocator
 ) __attribute__((__nonnull__(1, 2, 5)));
 
+/**
+ * Adds an element to an array with the possibility of allocating more space.
+ *
+ * The element \p elem is added to the end of the \p target array which containing
+ * \p size elements, already. The \p capacity must not be \c NULL and point a
+ * variable holding the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, and the optional
+ * \p reallocator is not \c NULL, an attempt increase the \p capacity is made
+ * and the new capacity is written back.
+ *
+ * @param target the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity - must not be \c NULL
+ * @param elem_size the size of one element
+ * @param elem the element to add
+ * @param reallocator the array re-allocator to use, or \c NULL
+ * if re-allocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
+    cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator)
 
 /**
  * Swaps two array elements.
@@ -153,12 +185,14 @@
  * Allocates an array list for storing elements with \p item_size bytes each.
  *
  * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
  *
  * @param allocator the allocator for allocating the list memory
  * (if \c NULL the cxDefaultAllocator will be used)
  * @param comparator the comparator for the elements
- * (if \c NULL sort and find functions will not work)
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
  * @param item_size the size of each element in bytes
  * @param initial_capacity the initial number of elements the array can store
  * @return the created list
@@ -178,7 +212,8 @@
  * set it immediately after creation or use cxArrayListCreate().
  *
  * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
  *
  * @param item_size the size of each element in bytes
  * @param initial_capacity the initial number of elements the array can store
--- a/ucx/cx/buffer.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/buffer.h	Sat Jan 27 17:50:19 2024 +0100
@@ -40,7 +40,6 @@
  *
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -312,18 +311,32 @@
  * Clears the buffer by resetting the position and deleting the data.
  *
  * The data is deleted by zeroing it with a call to memset().
+ * If you do not need that, you can use the faster cxBufferReset().
  *
  * @param buffer the buffer to be cleared
+ * @see cxBufferReset()
  */
 __attribute__((__nonnull__))
 void cxBufferClear(CxBuffer *buffer);
 
 /**
- * Tests, if the buffer position has exceeded the buffer capacity.
+ * Resets the buffer by resetting the position and size to zero.
+ *
+ * The data in the buffer is not deleted. If you need a safe
+ * reset of the buffer, use cxBufferClear().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferClear()
+ */
+__attribute__((__nonnull__))
+void cxBufferReset(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer size.
  *
  * @param buffer the buffer to test
  * @return non-zero, if the current buffer position has exceeded the last
- * available byte of the buffer.
+ * byte of the buffer's contents.
  */
 __attribute__((__nonnull__))
 int cxBufferEof(CxBuffer const *buffer);
--- a/ucx/cx/collection.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/collection.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Common definitions for various collection implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -49,6 +48,8 @@
  */
 #define CX_STORE_POINTERS 0
 
+#ifndef CX_COMPARE_FUNC_DEFINED
+#define CX_COMPARE_FUNC_DEFINED
 /**
  * A comparator function comparing two collection elements.
  */
@@ -56,6 +57,7 @@
         void const *left,
         void const *right
 );
+#endif // CX_COMPARE_FUNC_DEFINED
 
 /**
  * Use this macro to declare common members for a collection structure.
--- a/ucx/cx/common.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/common.h	Sat Jan 27 17:50:19 2024 +0100
@@ -33,7 +33,6 @@
  *
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  *
  * \mainpage UAP Common Extensions
@@ -84,7 +83,7 @@
 #define UCX_VERSION_MAJOR   3
 
 /** Minor UCX version as integer constant. */
-#define UCX_VERSION_MINOR   0
+#define UCX_VERSION_MINOR   1
 
 /** Version constant which ensures to increase monotonically. */
 #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
@@ -97,6 +96,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#ifndef UCX_TEST_H
 /**
  * Function pointer compatible with fwrite-like functions.
  */
@@ -106,6 +106,7 @@
         size_t,
         void *
 );
+#endif // UCX_TEST_H
 
 /**
  * Function pointer compatible with fread-like functions.
--- a/ucx/cx/compare.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/compare.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief A collection of simple compare functions.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -43,6 +42,17 @@
 extern "C" {
 #endif
 
+#ifndef CX_COMPARE_FUNC_DEFINED
+#define CX_COMPARE_FUNC_DEFINED
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+        void const *left,
+        void const *right
+);
+#endif // CX_COMPARE_FUNC_DEFINED
+
 /**
  * Compares two integers of type int.
  *
@@ -213,6 +223,19 @@
         void const *ptr2
 );
 
+/**
+ * Compares the pointers specified in the arguments without de-referencing.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int cx_cmp_ptr(
+        void const *ptr1,
+        void const *ptr2
+);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
--- a/ucx/cx/hash_key.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/hash_key.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Interface for map implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/cx/hash_map.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/hash_map.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Hash map implementation.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/cx/iterator.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/iterator.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Interface for iterator implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/cx/linked_list.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/linked_list.h	Sat Jan 27 17:50:19 2024 +0100
@@ -31,7 +31,6 @@
  * \details Also provides several low-level functions for custom linked list implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -46,21 +45,22 @@
 #endif
 
 /**
- * Set this flag to true, if you want to disable the use of SBO for
- * linked list swap operations.
+ * The maximum item size that uses SBO swap instead of relinking.
  */
-extern bool CX_DISABLE_LINKED_LIST_SWAP_SBO;
+extern unsigned cx_linked_list_swap_sbo_size;
 
 /**
  * Allocates a linked list for storing elements with \p item_size bytes each.
  *
  * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
  *
  * @param allocator the allocator for allocating the list nodes
  * (if \c NULL the cxDefaultAllocator will be used)
  * @param comparator the comparator for the elements
- * (if \c NULL sort and find functions will not work)
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
  * @param item_size the size of each element in bytes
  * @return the created list
  */
@@ -78,7 +78,8 @@
  * after list creation or use cxLinkedListCreate().
  *
  * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
  *
  * @param item_size the size of each element in bytes
  * @return the created list
@@ -129,6 +130,27 @@
 ) __attribute__((__nonnull__));
 
 /**
+ * Finds the node containing an element within a linked list.
+ *
+ * @param result a pointer to the memory where the node pointer (or \c NULL if the element
+ * could not be found) shall be stored to
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+ssize_t cx_linked_list_find_node(
+        void **result,
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) __attribute__((__nonnull__));
+
+/**
  * Finds the first node in a linked list.
  *
  * The function starts with the pointer denoted by \p node and traverses the list
--- a/ucx/cx/list.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/list.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Interface for list implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -137,11 +136,12 @@
     );
 
     /**
-     * Member function for finding an element.
+     * Member function for finding and optionally removing an element.
      */
-    ssize_t (*find)(
-            struct cx_list_s const *list,
-            void const *elem
+    ssize_t (*find_remove)(
+            struct cx_list_s *list,
+            void const *elem,
+            bool remove
     );
 
     /**
@@ -580,7 +580,25 @@
         CxList const *list,
         void const *elem
 ) {
-    return list->cl->find(list, elem);
+    return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+/**
+ * Removes and returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find and remove
+ * @return the index of the now removed element or a negative
+ * value when the element is not found or could not be removed
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFindRemove(
+        CxList *list,
+        void const *elem
+) {
+    return list->cl->find_remove(list, elem, true);
 }
 
 /**
--- a/ucx/cx/map.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/map.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Interface for map implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/cx/mempool.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/mempool.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Interface for memory pool implementations.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/cx/printf.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/printf.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Wrapper for write functions with a printf-like interface.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -45,6 +44,12 @@
 extern "C" {
 #endif
 
+
+/**
+ * The maximum string length that fits into stack memory.
+ */
+extern unsigned const cx_printf_sbo_size;
+
 /**
  * A \c fprintf like function which writes the output to a stream by
  * using a write_func.
@@ -159,6 +164,170 @@
 #define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
     (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
 
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4), __format__(printf, 4, 5)))
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... );
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap);
+
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ * 
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... );
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap);
+
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
--- a/ucx/cx/string.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/string.h	Sat Jan 27 17:50:19 2024 +0100
@@ -30,7 +30,6 @@
  * \brief Strings that know their length.
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
@@ -41,6 +40,11 @@
 #include "allocator.h"
 
 /**
+ * The maximum length of the "needle" in cx_strstr() that can use SBO.
+ */
+extern unsigned const cx_strstr_sbo_size;
+
+/**
  * The UCX string structure.
  */
 struct cx_mutstr_s {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/test.h	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,330 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+ 
+/**
+ * @file: test.h
+ * 
+ * UCX Test Framework.
+ * 
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with CX_TEST_ASSERT()
+ * }
+ * 
+ * CX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #CX_TEST_DO {
+ *     // tests with CX_TEST_ASSERT() and/or
+ *     // calls with CX_TEST_CALL_SUBROUTINE() here
+ *   }
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ * 
+ * @attention Do not call own functions within a test, that use
+ * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define	UCX_TEST_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+//
+#if !defined(__clang__) && __GNUC__ > 3
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+
+#ifndef UCX_COMMON_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        void const *,
+        size_t,
+        size_t,
+        void *
+);
+#endif // UCX_COMMON_H
+
+/** Type for the CxTestSuite. */
+typedef struct CxTestSuite CxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func);
+
+/** Type for the internal list of test cases. */
+typedef struct CxTestSet CxTestSet;
+
+/** Structure for the internal list of test cases. */
+struct CxTestSet {
+    
+    /** Test case. */
+    CxTest test;
+    
+    /** Pointer to the next list element. */
+    CxTestSet *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct CxTestSuite {
+    
+    /** The number of successful tests after the suite has been run. */
+    unsigned int success;
+    
+    /** The number of failed tests after the suite has been run. */
+    unsigned int failure;
+
+    /** The optional name of this test suite. */
+    char const *name;
+    
+    /**
+     * Internal list of test cases.
+     * Use cx_test_register() to add tests to this list.
+     */
+    CxTestSet *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @param name optional name of the suite
+ * @return a new test suite
+ */
+static inline CxTestSuite* cx_test_suite_new(char const *name) {
+    CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
+    if (suite != NULL) {
+        suite->name = name;
+        suite->success = 0;
+        suite->failure = 0;
+        suite->tests = NULL;
+    }
+
+    return suite;
+}
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+static inline void cx_test_suite_free(CxTestSuite* suite) {
+    CxTestSet *l = suite->tests;
+    while (l != NULL) {
+        CxTestSet *e = l;
+        l = l->next;
+        free(e);
+    }
+    free(suite);
+}
+
+/**
+ * Registers a test function with the specified test suite.
+ * 
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return zero on success or non-zero on failure
+ */
+static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+    CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
+    if (t) {
+        t->test = test;
+        t->next = NULL;
+        if (suite->tests == NULL) {
+            suite->tests = t;
+        } else {
+            CxTestSet *last = suite->tests;
+            while (last->next) {
+                last = last->next;
+            }
+            last->next = t;
+        }
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param out_target the target buffer or file to write the output to
+ * @param out_writer the write function writing to \p out_target
+ */
+static inline void cx_test_run(CxTestSuite *suite,
+                               void *out_target, cx_write_func out_writer) {
+    if (suite->name == NULL) {
+        out_writer("*** Test Suite ***\n", 1, 19, out_target);
+    } else {
+        out_writer("*** Test Suite : ", 1, 17, out_target);
+        out_writer(suite->name, 1, strlen(suite->name), out_target);
+        out_writer(" ***\n", 1, 5, out_target);
+    }
+    suite->success = 0;
+    suite->failure = 0;
+    for (CxTestSet *elem = suite->tests; elem; elem = elem->next) {
+        elem->test(suite, out_target, out_writer);
+    }
+    out_writer("\nAll test completed.\n", 1, 21, out_target);
+    char total[80];
+    int len = snprintf(
+            total, 80,
+            "  Total:   %u\n  Success: %u\n  Failure: %u\n\n",
+            suite->success + suite->failure, suite->success, suite->failure
+    );
+    out_writer(total, 1, len, out_target);
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified FILE stream.
+ * @param suite the test suite to run
+ * @param file the target file to write the output to
+ */
+#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
+
+/**
+ * Runs a test suite and writes the test log to stdout.
+ * @param suite the test suite to run
+ */
+#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
+
+/**
+ * Macro for a #CxTest function header.
+ * 
+ * Use this macro to declare and/or define a #CxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_)
+
+/**
+ * Defines the scope of a test.
+ * @attention Any CX_TEST_ASSERT() calls must be performed in scope of
+ * #CX_TEST_DO.
+ */
+#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
+        _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        _writefnc_("... ", 1, 4, _output_);\
+        jmp_buf _env_;\
+        for (unsigned int _cx_test_loop_ = 0 ;\
+             _cx_test_loop_ == 0 && !setjmp(_env_);\
+             _writefnc_("success.\n", 1, 9, _output_),\
+             _suite_->success++, _cx_test_loop_++)
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
+        char const* _assert_msg_ = message; \
+        _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \
+        _writefnc_(".\n", 1, 2, _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    } (void) 0
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ */
+#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * CX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see CX_TEST_CALL_SUBROUTINE()
+ */
+#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\
+        void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see CX_TEST_SUBROUTINE()
+ */
+#define CX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__)
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_TEST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/tree.h	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,69 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file tree.h
+ * \brief Interface for tree implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_TREE_H
+#define UCX_TREE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+__attribute__((__nonnull__))
+void cx_tree_link(
+        void * restrict parent,
+        void * restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+__attribute__((__nonnull__))
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_TREE_H
--- a/ucx/cx/utils.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/cx/utils.h	Sat Jan 27 17:50:19 2024 +0100
@@ -33,7 +33,6 @@
  *
  * \author Mike Becker
  * \author Olaf Wintermann
- * \version 3.0
  * \copyright 2-Clause BSD License
  */
 
--- a/ucx/linked_list.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/linked_list.c	Sat Jan 27 17:50:19 2024 +0100
@@ -28,6 +28,7 @@
 
 #include "cx/linked_list.h"
 #include "cx/utils.h"
+#include "cx/compare.h"
 #include <string.h>
 #include <assert.h>
 
@@ -63,6 +64,23 @@
         cx_compare_func cmp_func,
         void const *elem
 ) {
+    void *dummy;
+    return cx_linked_list_find_node(
+            &dummy, start,
+            loc_advance, loc_data,
+            cmp_func, elem
+    );
+}
+
+ssize_t cx_linked_list_find_node(
+        void **result,
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) {
+    assert(result != NULL);
     assert(start != NULL);
     assert(loc_advance >= 0);
     assert(loc_data >= 0);
@@ -73,11 +91,13 @@
     do {
         void *current = ll_data(node);
         if (cmp_func(current, elem) == 0) {
+            *result = (void*) node;
             return index;
         }
         node = ll_advance(node);
         index++;
     } while (node != NULL);
+    *result = NULL;
     return -1;
 }
 
@@ -460,8 +480,6 @@
 
 // HIGH LEVEL LINKED LIST IMPLEMENTATION
 
-bool CX_DISABLE_LINKED_LIST_SWAP_SBO = false;
-
 typedef struct cx_linked_list_node cx_linked_list_node;
 struct cx_linked_list_node {
     cx_linked_list_node *prev;
@@ -609,6 +627,7 @@
 #ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
 #define CX_LINKED_LIST_SWAP_SBO_SIZE 128
 #endif
+unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
 
 static int cx_ll_swap(
         struct cx_list_s *list,
@@ -633,6 +652,7 @@
     if (left < mid && right < mid) {
         // case 1: both items left from mid
         nleft = cx_ll_node_at(ll, left);
+        assert(nleft != NULL);
         nright = nleft;
         for (size_t c = left; c < right; c++) {
             nright = nright->next;
@@ -640,6 +660,7 @@
     } else if (left >= mid && right >= mid) {
         // case 2: both items right from mid
         nright = cx_ll_node_at(ll, right);
+        assert(nright != NULL);
         nleft = nright;
         for (size_t c = right; c > left; c--) {
             nleft = nleft->prev;
@@ -686,7 +707,7 @@
         }
     }
 
-    if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE || CX_DISABLE_LINKED_LIST_SWAP_SBO) {
+    if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE) {
         cx_linked_list_node *prev = nleft->prev;
         cx_linked_list_node *next = nright->next;
         cx_linked_list_node *midstart = nleft->next;
@@ -735,13 +756,35 @@
     return node == NULL ? NULL : node->payload;
 }
 
-static ssize_t cx_ll_find(
-        struct cx_list_s const *list,
-        void const *elem
+static ssize_t cx_ll_find_remove(
+        struct cx_list_s *list,
+        void const *elem,
+        bool remove
 ) {
-    return cx_linked_list_find(((cx_linked_list *) list)->begin,
-                               CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
-                               list->cmpfunc, elem);
+    if (remove) {
+        cx_linked_list *ll = ((cx_linked_list *) list);
+        cx_linked_list_node *node;
+        ssize_t index = cx_linked_list_find_node(
+                (void **) &node,
+                ll->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->cmpfunc, elem
+        );
+        if (node != NULL) {
+            cx_invoke_destructor(list, node->payload);
+            cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                                  CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+            list->size--;
+            cxFree(list->allocator, node);
+        }
+        return index;
+    } else {
+        return cx_linked_list_find(
+                ((cx_linked_list *) list)->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->cmpfunc, elem
+        );
+    }
 }
 
 static void cx_ll_sort(struct cx_list_s *list) {
@@ -894,7 +937,7 @@
         cx_ll_clear,
         cx_ll_swap,
         cx_ll_at,
-        cx_ll_find,
+        cx_ll_find_remove,
         cx_ll_sort,
         cx_ll_compare,
         cx_ll_reverse,
@@ -915,11 +958,12 @@
 
     list->base.cl = &cx_linked_list_class;
     list->base.allocator = allocator;
-    list->base.cmpfunc = comparator;
 
     if (item_size > 0) {
         list->base.item_size = item_size;
+        list->base.cmpfunc = comparator;
     } else {
+        list->base.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
         cxListStorePointers((CxList *) list);
     }
 
--- a/ucx/list.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/list.c	Sat Jan 27 17:50:19 2024 +0100
@@ -115,12 +115,13 @@
     return ptr == NULL ? NULL : *ptr;
 }
 
-static ssize_t cx_pl_find(
-        struct cx_list_s const *list,
-        void const *elem
+static ssize_t cx_pl_find_remove(
+        struct cx_list_s *list,
+        void const *elem,
+        bool remove
 ) {
     cx_pl_hack_cmpfunc(list);
-    ssize_t ret = list->climpl->find(list, &elem);
+    ssize_t ret = list->climpl->find_remove(list, &elem, remove);
     cx_pl_unhack_cmpfunc(list);
     return ret;
 }
@@ -171,7 +172,7 @@
         cx_pl_clear,
         cx_pl_swap,
         cx_pl_at,
-        cx_pl_find,
+        cx_pl_find_remove,
         cx_pl_sort,
         cx_pl_compare,
         cx_pl_reverse,
@@ -208,9 +209,10 @@
     return NULL;
 }
 
-static ssize_t cx_emptyl_find(
-        __attribute__((__unused__)) struct cx_list_s const *list,
-        __attribute__((__unused__)) void const *elem
+static ssize_t cx_emptyl_find_remove(
+        __attribute__((__unused__)) struct cx_list_s *list,
+        __attribute__((__unused__)) void const *elem,
+        __attribute__((__unused__)) bool remove
 ) {
     return -1;
 }
@@ -248,7 +250,7 @@
         cx_emptyl_noop,
         NULL,
         cx_emptyl_at,
-        cx_emptyl_find,
+        cx_emptyl_find_remove,
         cx_emptyl_noop,
         cx_emptyl_compare,
         cx_emptyl_noop,
@@ -293,8 +295,8 @@
     ) {
         // lists are definitely different - cannot use internal compare function
         if (list->size == other->size) {
-            CxIterator left = cxListIterator(list);
-            CxIterator right = cxListIterator(other);
+            CxIterator left = list->cl->iterator(list, 0, false);
+            CxIterator right = other->cl->iterator(other, 0, false);
             for (size_t i = 0; i < list->size; i++) {
                 void *leftValue = cxIteratorCurrent(left);
                 void *rightValue = cxIteratorCurrent(right);
--- a/ucx/printf.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/printf.c	Sat Jan 27 17:50:19 2024 +0100
@@ -34,6 +34,7 @@
 #ifndef CX_PRINTF_SBO_SIZE
 #define CX_PRINTF_SBO_SIZE 512
 #endif
+unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
 
 int cx_fprintf(
         void *stream,
@@ -60,17 +61,21 @@
     va_copy(ap2, ap);
     int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
     if (ret < 0) {
+        va_end(ap2);
         return ret;
     } else if (ret < CX_PRINTF_SBO_SIZE) {
+        va_end(ap2);
         return (int) wfc(buf, 1, ret, stream);
     } else {
         int len = ret + 1;
         char *newbuf = malloc(len);
         if (!newbuf) {
+            va_end(ap2);
             return -1;
         }
 
         ret = vsnprintf(newbuf, len, fmt, ap2);
+        va_end(ap2);
         if (ret > 0) {
             ret = (int) wfc(newbuf, 1, ret, stream);
         }
@@ -85,9 +90,8 @@
         ...
 ) {
     va_list ap;
-    cxmutstr ret;
     va_start(ap, fmt);
-    ret = cx_vasprintf_a(allocator, fmt, ap);
+    cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap);
     va_end(ap);
     return ret;
 }
@@ -104,7 +108,7 @@
     va_list ap2;
     va_copy(ap2, ap);
     int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
-    if (ret > 0 && ret < CX_PRINTF_SBO_SIZE) {
+    if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) {
         s.ptr = cxMalloc(a, ret + 1);
         if (s.ptr) {
             s.length = (size_t) ret;
@@ -124,6 +128,65 @@
             }
         }
     }
+    va_end(ap2);
     return s;
 }
 
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(*str, len, fmt, ap);
+    if ((unsigned) ret >= len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxRealloc(alloc, *str, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
+
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, len, fmt, ap);
+    *str = buf;
+    if ((unsigned) ret >= len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxMalloc(alloc, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
--- a/ucx/string.c	Mon Jan 22 17:27:47 2024 +0100
+++ b/ucx/string.c	Sat Jan 27 17:50:19 2024 +0100
@@ -236,6 +236,7 @@
 #ifndef CX_STRSTR_SBO_SIZE
 #define CX_STRSTR_SBO_SIZE 512
 #endif
+unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
 
 cxstring cx_strstr(
         cxstring haystack,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/tree.c	Sat Jan 27 17:50:19 2024 +0100
@@ -0,0 +1,87 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/tree.h"
+
+#include <assert.h>
+
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
+#define tree_children(node) CX_TREE_PTR(node, loc_children)
+#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
+#define tree_next(node) CX_TREE_PTR(node, loc_next)
+
+void cx_tree_link(
+        void *restrict parent,
+        void *restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    void *current_parent = tree_parent(node);
+    if (current_parent == parent) return;
+    if (current_parent != NULL) {
+        cx_tree_unlink(node, loc_parent, loc_children,
+                       loc_prev, loc_next);
+    }
+
+    if (tree_children(parent) == NULL) {
+        tree_children(parent) = node;
+    } else {
+        void *children = tree_children(parent);
+        tree_prev(children) = node;
+        tree_next(node) = children;
+        tree_children(parent) = node;
+    }
+    tree_parent(node) = parent;
+}
+
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    if (tree_parent(node) == NULL) return;
+
+    void *left = tree_prev(node);
+    void *right = tree_next(node);
+    assert(left == NULL || tree_children(tree_parent(node)) != node);
+    if (left == NULL) {
+        tree_children(tree_parent(node)) = right;
+    } else {
+        tree_next(left) = right;
+    }
+    if (right != NULL) tree_prev(right) = left;
+    tree_parent(node) = NULL;
+    tree_prev(node) = NULL;
+    tree_next(node) = NULL;
+}
--- a/ui/ui/toolkit.h	Mon Jan 22 17:27:47 2024 +0100
+++ b/ui/ui/toolkit.h	Sat Jan 27 17:50:19 2024 +0100
@@ -69,6 +69,8 @@
 
 #elif UI_WINUI
 
+#include <Windows.h>
+
 #define UIEXPORT __declspec(dllexport) 
 
 #ifdef __cplusplus

mercurial