libidav/config.c

Sun, 17 Dec 2023 14:25:34 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 14:25:34 +0100
changeset 797
edbb20b1438d
parent 796
81e0f67386a6
child 803
008cc66ff9d6
permissions
-rw-r--r--

[Makefile] fix missing rules preventing dry-runs

We have to support dry-runs, because many IDEs are using
dry-runs to collect build information.

Some rules have dependencies that expect certain files or
directories to be just present. We added respective build
rules which invoke the test program. This way, the behavior
when running make normally is exactly the same, but dry-runs
are also not failing now.

/*
 * 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;
}

mercurial