diff -r 29d544c3c2b8 -r 05647e862a17 libidav/config.c --- /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 +#include +#include +#include +#include +#include +#include +#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; +}