libidav/config.c

changeset 795
05647e862a17
child 796
81e0f67386a6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/config.c	Sat Sep 30 16:33:47 2023 +0200
@@ -0,0 +1,780 @@
+/*
+ * 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;
+}
+
+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;
+}

mercurial