libidav/config.c

Sun, 10 Nov 2024 09:12:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 10 Nov 2024 09:12:30 +0100
changeset 75
73c8a3780c72
parent 72
d5307e9ee384
permissions
-rw-r--r--

toolkit update, small UI adjustments

/*
 * 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_node_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_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node) {
    cbool->node = node;
    char *value = util_xml_get_text(node);
    cbool->value = util_getboolean(value);
}

static void set_xml_content(xmlNode *node, const char *content) {
    xmlNode *child = node->children;
    while(child) {
        xmlNode *next = child->next;
        xmlUnlinkNode(child);
        xmlFreeNode(child);
        child = next;
    }
    
    if(content) {
        xmlChar *encoded = xmlEncodeSpecialChars(node->doc, (const xmlChar*)content);
        if(encoded) {
            xmlNodeSetContent(node, encoded);
            xmlFree(encoded);
        }
    }
}

void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) {
    if(str->value.ptr) {
        cxFree(config->mp->allocator, str->value.ptr);
    }
    if(new_value.ptr) {
        str->value = cx_strdup_a(config->mp->allocator, new_value);
    } else {
        str->value = cx_mutstrn(NULL, 0);
    }
    
    if(!str->node) {
        str->node = xmlNewNode(NULL, (const xmlChar*) nodename);
        xmlAddChild(parent, str->node);
    }
    set_xml_content(str->node, new_value.ptr);
}

void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename) {
    const char *content = new_value ? "true" : "false";
    cbool->value = new_value;
    if(!cbool->node) {
        cbool->node = xmlNewNode(NULL, (const xmlChar*) nodename);
        xmlAddChild(parent, cbool->node);
    }
    set_xml_content(cbool->node, content);
}

void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename) {
    char content[32];
    snprintf(content, 32, "%" PRId64, new_value);
    cint->value = new_value;
    if(!cint->node) {
        cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
        xmlAddChild(parent, cint->node);
    }
    set_xml_content(cint->node, content);
}

void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename) {
    char content[32];
    snprintf(content, 32, "%" PRIu64, new_value);
    cint->value = new_value;
    if(!cint->node) {
        cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
        xmlAddChild(parent, cint->node);
    }
    set_xml_content(cint->node, content);
}

void dav_cfg_string_remove(CfgString *str) {
    if(str->node) {
        xmlUnlinkNode(str->node);
        xmlFreeNode(str->node);
        str->node = NULL;
    }
}

void dav_cfg_bool_remove(CfgBool *cbool) {
    if(cbool->node) {
        xmlUnlinkNode(cbool->node);
        xmlFreeNode(cbool->node);
        cbool->node = NULL;
    }
}

void dav_cfg_int_remove(CfgInt *cint) {
    if(cint->node) {
        xmlUnlinkNode(cint->node);
        xmlFreeNode(cint->node);
        cint->node = NULL;
    }
}

void dav_cfg_uint_remove(CfgUInt *cint) {
    if(cint->node) {
        xmlUnlinkNode(cint->node);
        xmlFreeNode(cint->node);
        cint->node = NULL;
    }
}


DavConfig* dav_config_new(xmlDoc *doc) {
    CxMempool *cfg_mp = cxMempoolCreate(128, NULL);
    DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig));
    memset(config, 0, sizeof(DavConfig));
    config->mp = cfg_mp;
    
    if(!doc) {
        doc = xmlNewDoc(BAD_CAST "1.0");
        xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration");
        xmlDocSetRootElement(doc, root);
    }
    config->doc = doc;
    
    return config;
}

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;
    }
    
    DavConfig *config = dav_config_new(doc);
    CxMempool *cfg_mp = config->mp;
    cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc);
    
    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_node_value(config, &repo->name, node);
    } else if(xstreq(key, "url")) {
        dav_cfg_string_set_node_value(config, &repo->url, node);
    } else if(xstreq(key, "user")) {
        dav_cfg_string_set_node_value(config, &repo->user, node);
    } else if(xstreq(key, "password")) {
        dav_cfg_string_set_node_value(config, &repo->password, node);
    } else if(xstreq(key, "stored-user")) {
        dav_cfg_string_set_node_value(config, &repo->stored_user, node);
    } else if(xstreq(key, "default-key")) {
        dav_cfg_string_set_node_value(config, &repo->default_key, node);
    } else if(xstreq(key, "full-encryption")) {
        dav_cfg_bool_set_node_value(config, &repo->full_encryption, node);
    } else if(xstreq(key, "content-encryption")) {
        dav_cfg_bool_set_node_value(config, &repo->content_encryption, node);
    } else if(xstreq(key, "decrypt-content")) {
        dav_cfg_bool_set_node_value(config, &repo->decrypt_content, node);
    } else if(xstreq(key, "decrypt-name")) {
        dav_cfg_bool_set_node_value(config, &repo->decrypt_name, node);
    } else if(xstreq(key, "cert")) {
        dav_cfg_string_set_node_value(config, &repo->cert, node);
    } else if(xstreq(key, "verification")) {
        dav_cfg_bool_set_node_value(config, &repo->verification, node);
    } else if(xstreq(key, "ssl-version")) {
        repo->ssl_version.node = node;
        int ssl_version = dav_str2ssl_version((const char*)value);
        if(ssl_version == -1) {
            print_warning(lineno, "unknown ssl version: %s\n", value);
            repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
        } else {
            repo->ssl_version.value = ssl_version;
        }
    } 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;
}

int dav_str2ssl_version(const char *value) {
    if(xstrEQ(value, "TLSv1")) {
        return CURL_SSLVERSION_TLSv1;
    } else if(xstrEQ(value, "SSLv2")) {
        return CURL_SSLVERSION_SSLv2;
    } else if(xstrEQ(value, "SSLv3")) {
        return CURL_SSLVERSION_SSLv3;
    }
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
    else if(xstrEQ(value, "TLSv1.0")) {
        return CURL_SSLVERSION_TLSv1_0;
    } else if(xstrEQ(value, "TLSv1.1")) {
        return CURL_SSLVERSION_TLSv1_1;
    } else if(xstrEQ(value, "TLSv1.2")) {
        return CURL_SSLVERSION_TLSv1_2;
    }
#endif
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
    else if(xstrEQ(value, "TLSv1.3")) {
        return CURL_SSLVERSION_TLSv1_3;
    }
#endif
    return -1;
}

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);
    repo->node = repoNode;
    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));
    memset(repo, 0, 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;
    if(user.length > 0) {
        repo->user.value = cx_strdup_a(a, user);
    }
    if(password.length > 0) {
        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;
}


void dav_config_add_key(DavConfig *config, DavCfgKey *key) {
    cx_linked_list_add(
            (void**)&config->keys,
            NULL,
            offsetof(DavCfgKey, prev),
            offsetof(DavCfgKey, next),
            key);
    
    if(key->node) {
        fprintf(stderr, "Error: dav_config_add_key: node already exists\n");
        return;
    }
    
    xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key");
    xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
    xmlAddChild(keyNode, rtext1);
    key->node = keyNode;
    
    if(key->name.value.ptr) {
        key->name.node = addXmlNode(keyNode, "name", key->name.value);
    }
    const char *type = dav_config_keytype_str(key->type);
    if(type) {
        key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type));
    }
    if(key->file.value.ptr) {
        key->file.node = addXmlNode(keyNode, "file", key->file.value);
    }
    
     // indent closing tag
    xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
    xmlAddChild(keyNode, rtext2);
    
    // add key 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, keyNode);
    
    xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
    xmlAddChild(xml_root, text2);
}

DavCfgKey* dav_key_new(DavConfig *config) {
    DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
    memset(key, 0, sizeof(DavCfgKey));
    key->type = DAV_KEY_TYPE_AES256;
    return key;
}

void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) {
    cx_linked_list_remove(
            (void**)&config->keys,
            NULL,
            offsetof(DavCfgKey, prev),
            offsetof(DavCfgKey, next),
            key);
    if(key->node) {
        // TODO: remove newline after key node
        
        xmlUnlinkNode(key->node);
        xmlFreeNode(key->node);
    }
}


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;
    key->node = keynode;
    
    int error = 0;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            if(xstreq(node->name, "name")) {
                dav_cfg_string_set_node_value(config, &key->name, node);
            } else if(xstreq(node->name, "file")) {
                dav_cfg_string_set_node_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";
    } else {
        fprintf(stderr, "unknown proxy type\n");
        return 1;
    }
    
    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_node_value(config, &proxy->url, node);
            } else if(xstreq(node->name, "user")) {
                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->user, node);
            } else if(xstreq(node->name, "password")) {
                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->password, node);
            } else if(xstreq(node->name, "no")) {
                reportmissingvalue = dav_cfg_string_set_node_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_node_value(config, &config->secretstore->unlock_cmd, node);
            } else if(xstreq(node->name, "lock-command")) {
                dav_cfg_string_set_node_value(config, &config->secretstore->lock_cmd, node);
            }
        }
        node = node->next;
    }
    
    return error;
}





DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) {
    if(!config) {
        return NULL;
    }
    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;
}

const char* dav_config_keytype_str(DavCfgKeyType type) {
    switch(type) {
        default: break;
        case DAV_KEY_TYPE_AES256: return "aes256";
        case DAV_KEY_TYPE_AES128: return "aes128";
    }
    return NULL;
}

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

int dav_config_register_namespaces(DavConfig *config, DavContext *ctx) {
    DavCfgNamespace *ns = config->namespaces;
    while(ns) {
        dav_add_namespace(ctx, ns->prefix.ptr, ns->uri.ptr);
        ns = ns->next;
    }
    return 0;
}

mercurial