add new config parser

Sat, 30 Sep 2023 16:33:47 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 30 Sep 2023 16:33:47 +0200
changeset 795
05647e862a17
parent 794
29d544c3c2b8
child 796
81e0f67386a6

add new config parser

dav/config.c file | annotate | diff | comparison | revisions
dav/config.h file | annotate | diff | comparison | revisions
dav/main.c file | annotate | diff | comparison | revisions
dav/main.h file | annotate | diff | comparison | revisions
dav/sync.c file | annotate | diff | comparison | revisions
libidav/Makefile file | annotate | diff | comparison | revisions
libidav/config.c file | annotate | diff | comparison | revisions
libidav/config.h file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
--- a/dav/config.c	Sun Sep 17 13:51:01 2023 +0200
+++ b/dav/config.c	Sat Sep 30 16:33:47 2023 +0200
@@ -31,6 +31,7 @@
 #include <string.h>
 #include <sys/types.h>
 #include <cx/hash_map.h>
+#include <cx/utils.h>
 #include <errno.h>
 #include <libxml/tree.h>
 
@@ -38,7 +39,10 @@
 #include "config.h"
 #include "main.h"
 #include "pwd.h"
+#include "system.h"
+
 #include <libidav/utils.h>
+#include <libidav/config.h>
 
 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
 #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
@@ -64,6 +68,7 @@
 static CxMap *repos;
 static CxMap *keys;
 
+static DavConfig *davconfig;
 static PwdStore *pstore;
 
 static char *secretstore_unlock_cmd;
@@ -101,6 +106,20 @@
     return path;
 }
 
+cxmutstr config_load_file(const char *path) {
+    FILE *file = sys_fopen(path, "r");
+    if(!file) {
+        return (cxmutstr){NULL,0};
+    }
+    
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
+    cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+    fclose(file);
+    
+    return cx_mutstrn(buf.space, buf.size);
+}
+
 int load_config(DavContext *ctx) {
     context = ctx;
     // TODO: free the config somewhere
@@ -127,6 +146,12 @@
     }
     
     xmlDoc *doc = xmlReadFile(file, NULL, 0);
+    
+    cxmutstr config_content = config_load_file(file);
+    int config_error;
+    davconfig = dav_config_load(config_content, &config_error);
+    free(config_content.ptr);
+    
     free(file);
     if(!doc) {
         fprintf(stderr, "Cannot load config.xml\n");
@@ -162,6 +187,39 @@
     return ret;
 }
 
+DavConfig* get_config(void) {
+    return davconfig;
+}
+
+int store_config(void) {
+    if(check_config_dir()) {
+        return 1;
+    }
+    
+    CxBuffer *buf = dav_config2buf(davconfig);
+    if(!buf) {
+        return 1;
+    }
+    
+    char *file = util_concat_path(ENV_HOME, ".dav/config.xml");
+    FILE *cout = sys_fopen(file, "w");
+    if(!cout) {
+        cxBufferFree(buf);
+        return 1;
+    }
+    
+    // should only fail if we run out of disk space or something like that
+    // in that case, the config file is only destroyed
+    // could only be prevented, if we write to a temp file first and than
+    // rename it
+    fwrite(buf->space, buf->size, 1, cout);
+    
+    cxBufferFree(buf);
+    fclose(cout);
+    
+    return 0;
+}
+
 void free_config(void) {
     if(repos) {
         CxIterator i = cxMapIteratorValues(repos);
@@ -813,7 +871,7 @@
 
 
 
-
+/*
 Repository* url2repo_s(cxstring url, char **path) {
     *path = NULL;
     
@@ -867,6 +925,7 @@
 Repository* url2repo(const char *url, char **path) {
     return url2repo_s(cx_str(url), path);
 }
+*/
 
 static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) {
     if(cmd_getoption(a, "noinput")) {
@@ -960,7 +1019,7 @@
 }
 
 
-static int get_location_credentials(CmdArgs *a, Repository *repo, char *path, char **user, char **password) {
+static int get_location_credentials(CmdArgs *a, DavCfgRepository *repo, const char *path, char **user, char **password) {
     PwdStore *secrets = get_pwdstore();
     if(!secrets) {
         return 0;
@@ -976,19 +1035,20 @@
     cx_foreach(PwdIndexEntry*, e, i) {
         CxIterator entry_iter = cxListIterator(e->locations);
         cx_foreach(char *, loc, entry_iter) {
-            char *path;
-            Repository *r = url2repo(loc, &path);
+            cxmutstr rpath;
+            DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath);
             CredLocation *urlentry = calloc(1, sizeof(CredLocation));
             urlentry->id = e->id;
-            urlentry->location = util_concat_path(r->url, path);
+            urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr;
             cxListAdd(locations, urlentry);
+            free(rpath.ptr);
         }
     }
     // the list must be sorted
     cxListSort(locations);
     
     // create full request url string and remove protocol prefix
-    cxmutstr req_url_proto = cx_mutstr(util_concat_path(repo->url, path));
+    cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path));
     cxstring req_url = cx_strcast(req_url_proto);
     if(cx_strprefix(req_url, CX_STR("http://"))) {
         req_url = cx_strsubs(req_url, 7);
@@ -1034,25 +1094,31 @@
     return ret;
 }
 
-DavSession* connect_to_repo(DavContext *ctx, Repository *repo, char *path, dav_auth_func authfunc, CmdArgs *a) {
-    char *user = repo->user;
-    char *password = repo->password;
+DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a) {
+    cxmutstr decodedpw = dav_repository_get_decodedpassword(repo);
+    
+    char *user = repo->user.value.ptr;
+    char *password = decodedpw.ptr;
     
     if(!user && !password) {
-        if(!get_stored_credentials(a, repo->stored_user, &user, &password)) {
+        if(!get_stored_credentials(a, repo->stored_user.value.ptr, &user, &password)) {
             get_location_credentials(a, repo, path, &user, &password);
         }
     }
     
-    DavSession *sn = dav_session_new_auth(ctx, repo->url, user, password);
-    sn->flags = get_repository_flags(repo);
-    sn->key = dav_context_get_key(ctx, repo->default_key);
+    DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password);
+    if(password) {
+        free(password);
+    }
+    
+    sn->flags = dav_repository_get_flags(repo);
+    sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr);
     curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
     curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
-    if(repo->cert) {
-        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert);
+    if(repo->cert.value.ptr) {
+        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr);
     }
-    if(!repo->verification || cmd_getoption(a, "insecure")) {
+    if(!repo->verification.value || cmd_getoption(a, "insecure")) {
         curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
         curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
     }
--- a/dav/config.h	Sun Sep 17 13:51:01 2023 +0200
+++ b/dav/config.h	Sat Sep 30 16:33:47 2023 +0200
@@ -35,6 +35,8 @@
 #include "pwd.h"
 #include "opt.h"
 
+#include <libidav/config.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -75,8 +77,13 @@
 int check_config_dir(void);
 
 char* config_file_path(char *name);
+
+cxmutstr config_load_file(const char *path);
     
 int load_config(DavContext *ctx);
+DavConfig* get_config(void);
+int store_config(void);
+
 void free_config(void);
 int load_repository(const xmlNode *reponode);
 int load_key(const xmlNode *keynode);
@@ -101,10 +108,10 @@
 int pwdstore_save(PwdStore *pwdstore);
 
 
-Repository* url2repo_s(cxstring url, char **path);
-Repository* url2repo(const char *url, char **path);
+//Repository* url2repo_s(cxstring url, char **path);
+//Repository* url2repo(const char *url, char **path);
 
-DavSession* connect_to_repo(DavContext *ctx, Repository *repo, char *path, dav_auth_func authfunc, CmdArgs *a);
+DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a);
 
 int request_auth(DavSession *sn, void *userdata);
 
--- a/dav/main.c	Sun Sep 17 13:51:01 2023 +0200
+++ b/dav/main.c	Sat Sep 30 16:33:47 2023 +0200
@@ -424,7 +424,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -603,7 +603,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -850,7 +850,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1038,7 +1038,7 @@
 #endif
 }
 
-int get_resource(Repository *repo, GetResource *getres, CmdArgs *a, void *unused) {
+int get_resource(DavCfgRepository *repo, GetResource *getres, CmdArgs *a, void *unused) {
     DavResource *res = getres->res;
     char *out = getres->path;
     
@@ -1096,7 +1096,7 @@
 #define DEFAULT_DIR_MODE T_IRUSR | T_IWUSR | T_IXUSR | T_IRGRP | T_IXGRP | T_IROTH | T_IXOTH
 #define DEFAULT_FILE_MODE T_IRUSR | T_IWUSR | T_IRGRP | T_IROTH
 
-int resource2tar(Repository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar) { 
+int resource2tar(DavCfgRepository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar) { 
     DavResource *d = res->res;
     
     if(d->iscollection) {
@@ -1148,7 +1148,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1221,7 +1221,7 @@
 
 
 int put_entry(
-        Repository *repo,
+        DavCfgRepository *repo,
         CmdArgs *a,
         DavSession *sn,
         char *path,
@@ -1319,7 +1319,7 @@
     return ret;
 }
 
-int put_tar(Repository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path) {
+int put_tar(DavCfgRepository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path) {
     int isstdin = !strcmp(tarfile, "-");
     FILE *in = isstdin ? stdin : fopen(tarfile, "rb");
     if(!in) {
@@ -1396,7 +1396,7 @@
 }
 
 int put_file(
-        Repository *repo,
+        DavCfgRepository *repo,
         CmdArgs *a,
         DavSession *sn,
         const char *path,
@@ -1496,7 +1496,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     int exit_code = -1;
@@ -1564,7 +1564,7 @@
     
     char *srcurl = a->argv[0];
     char *srcpath = NULL;
-    Repository *srcrepo = url2repo(srcurl, &srcpath);
+    DavCfgRepository *srcrepo = dav_config_url2repo(get_config(), srcurl, &srcpath);
     
     DavSession *srcsn = connect_to_repo(ctx, srcrepo, srcpath, request_auth, a);
     if(set_session_config(srcsn, a)) {
@@ -1576,7 +1576,7 @@
     
     char *desturl = a->argv[1];
     char *destpath = NULL;
-    Repository *destrepo = url2repo(desturl, &destpath);
+    DavCfgRepository *destrepo = dav_config_url2repo(get_config(), desturl, &destpath);
     
     if(srcrepo == destrepo) {
         DavResource *res = dav_resource_new(srcsn, srcpath);
@@ -1588,8 +1588,8 @@
             return -1;
         }
     } else {
-        char *srchost = util_url_base(srcrepo->url);
-        char *desthost = util_url_base(destrepo->url);     
+        char *srchost = util_url_base(srcrepo->url.value.ptr);
+        char *desthost = util_url_base(destrepo->url.value.ptr);     
         if(!strcmp(srchost, desthost)) {
             DavSession *destsn = connect_to_repo(ctx, destrepo, destpath, request_auth, a);
             if(set_session_config(destsn, a)) {
@@ -1642,7 +1642,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1734,7 +1734,7 @@
     } else if (a->argc == 1) {
         char *url = a->argv[0];
         char *path = NULL;
-        Repository *repo = url2repo(url, &path);
+        DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
         DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
 
         DavResource *res = dav_resource_new(sn, path);
@@ -1765,7 +1765,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1839,7 +1839,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1902,7 +1902,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -1943,7 +1943,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     cxMempoolRegister(sn->mp, path, free);
     
@@ -2016,7 +2016,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     cxMempoolRegister(sn->mp, path, free);
     if(set_session_config(sn, a)) {
@@ -2076,7 +2076,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2172,7 +2172,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2199,7 +2199,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2226,7 +2226,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2252,7 +2252,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2279,7 +2279,7 @@
     
     char *url = a->argv[0];
     char *path = NULL;
-    Repository *repo = url2repo(url, &path);
+    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
     DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
     
     if(set_session_config(sn, a)) {
@@ -2442,15 +2442,18 @@
     }
     printf("\n");
     
-    Repository repo;
-    memset(&repo, 0, sizeof(Repository));
-    repo.name = name;
-    repo.url = url;
-    repo.user = user;
-    repo.password = password;
+    DavConfig *config = get_config();
+    const CxAllocator *a = config->mp->allocator;
+    DavCfgRepository *repo = dav_repository_new(config);
+    
+    repo->name.value = cx_strdup_a(a, cx_str(name));
+    repo->url.value = cx_strdup_a(a, cx_str(url));
+    dav_repository_set_auth(config, repo, cx_str(user), cx_str(password));
+    
+    dav_config_add_repository(config, repo);
     
     int ret = 0;
-    if(add_repository(&repo)) {
+    if(store_config()) {
         fprintf(stderr, "Cannot write config.xml\n");
         ret = -1;
     } else {
@@ -2476,14 +2479,15 @@
         return -1;
     }
     
+    DavConfig *config = get_config();
+    
+    DavBool store = FALSE;
     for(int i = 0 ; i < args->argc ; i++) {
         cxstring reponame = cx_str(args->argv[i]);
-        Repository* repo = get_repository(reponame);
+        DavCfgRepository* repo = dav_config_get_repository(config, reponame);
         if(repo) {
-            if(remove_repository(repo)) {
-                fprintf(stderr, "Cannot write config.xml\n");
-                return -1;
-            }
+            dav_repository_remove_and_free(config, repo);
+            store = TRUE;
         } else {
             fprintf(stderr, "Repository %s does not exist - skipped.\n",
                     reponame.ptr);
@@ -2491,7 +2495,11 @@
         }
     }
     
-    return -1;
+    if(store) {
+        return store_config();
+    } else {
+        return -1;
+    }
 }
 
 int cmd_repository_url(CmdArgs *args) {
@@ -2502,10 +2510,10 @@
     }
     
     cxstring reponame = cx_str(args->argv[0]);
-    Repository* repo = get_repository(reponame);
+    DavCfgRepository* repo = dav_config_get_repository(get_config(), reponame);
     if(repo) {
-        cxstring url = cx_str(repo->url);
-        if(repo->user && !cmd_getoption(args, "plain")) {
+        cxstring url = cx_strcast(repo->url.value);
+        if(repo->user.value.ptr && !cmd_getoption(args, "plain")) {
             int hostindex = 0;
             if(cx_strprefix(url, CX_STR("https://"))) {
                 printf("https://");
@@ -2514,13 +2522,13 @@
                 printf("http://");
                 hostindex = 7;
             }
-            printf("%s", repo->user);
-            if(repo->password) {
+            printf("%.*s", (int)repo->user.value.length, repo->user.value.ptr);
+            if(repo->password.value.ptr) {
                 CURL *curl = curl_easy_init();
                 char *pw = curl_easy_escape(
                         curl,
-                        repo->password,
-                        strlen(repo->password));
+                        repo->password.value.ptr,
+                        repo->password.value.length);
                 printf(":%s", pw);
                 curl_free(pw);
                 curl_easy_cleanup(curl);
@@ -3125,7 +3133,7 @@
         cxMapPut(args->options, cx_hash_key_str("noinput"), "");
         
         char *path = NULL;
-        Repository *repo = url2repo_s(url, &path);
+        DavCfgRepository *repo = dav_config_url2repo(get_config(), url.ptr, &path);
         DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, args);
         if(!sn) {
             return 0;
@@ -3160,7 +3168,7 @@
                 }
                 
                 CxBuffer *out = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-                cxBufferPutString(out, repo->name);
+                cxBufferWrite(repo->name.value.ptr, repo->name.value.length, 1, out);
                 if(space) {
                     size_t l = strlen(elm->path);
                     for(int i=0;i<l;i++) {
--- a/dav/main.h	Sun Sep 17 13:51:01 2023 +0200
+++ b/dav/main.h	Sat Sep 30 16:33:47 2023 +0200
@@ -57,7 +57,7 @@
     time_t      ts;
 } Progress;
 
-typedef int(*getfunc)(Repository *, GetResource *, CmdArgs *, void *);
+typedef int(*getfunc)(DavCfgRepository *, GetResource *, CmdArgs *, void *);
     
 void print_usage(char *cmd);
 
@@ -69,12 +69,12 @@
 void ls_print_elm(DavResource *res, char *parent, CmdArgs *args);
 
 int cmd_get(CmdArgs *args, DavBool export);
-int get_resource(Repository *repo, GetResource *res, CmdArgs *a, void *unused);
-int resource2tar(Repository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar);
+int get_resource(DavCfgRepository *repo, GetResource *res, CmdArgs *a, void *unused);
+int resource2tar(DavCfgRepository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar);
 
 int cmd_put(CmdArgs *args, DavBool import);
 int put_entry(
-        Repository *repo,
+        DavCfgRepository *repo,
         CmdArgs *a,
         DavSession *sn,
         char *path,
@@ -83,9 +83,9 @@
         DavBool root,
         DavBool printfile,
         DavBool ignoredirerr);
-int put_tar(Repository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path);
+int put_tar(DavCfgRepository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path);
 int put_file(
-        Repository *repo,
+        DavCfgRepository *repo,
         CmdArgs *a,
         DavSession *sn,
         const char *path,
--- a/dav/sync.c	Sun Sep 17 13:51:01 2023 +0200
+++ b/dav/sync.c	Sat Sep 30 16:33:47 2023 +0200
@@ -598,13 +598,13 @@
     return -strcmp(a->path, b->path);
 }
 
-static DavSession* create_session(CmdArgs *a, DavContext *ctx, Repository *repo, char *collection) {
-    int flags = get_repository_flags(repo);
+static DavSession* create_session(CmdArgs *a, DavContext *ctx, DavCfgRepository *repo, char *collection) {
+    int flags = dav_repository_get_flags(repo);
     DavBool find_collection = TRUE;
     if((flags & DAV_SESSION_DECRYPT_NAME) != DAV_SESSION_DECRYPT_NAME) {
-        char *url = util_concat_path(repo->url, collection);
-        free(repo->url);
-        repo->url = url;
+        char *url = util_concat_path(repo->url.value.ptr, collection);
+        dav_repository_set_url(get_config(), repo, cx_str(url));
+        free(url);
         collection = NULL;
         find_collection = FALSE;
     }
@@ -618,13 +618,13 @@
     DavSession *sn = connect_to_repo(ctx, repo, collection, request_auth, a);
     
     sn->flags = flags;
-    sn->key = dav_context_get_key(ctx, repo->default_key);
+    sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr);
     curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
     curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
-    if(repo->cert) {
-        curl_easy_setopt(sn->handle, CURLOPT_CAPATH, repo->cert);
-    }
-    if(!repo->verification) {
+    if(repo->cert.value.ptr) {
+        curl_easy_setopt(sn->handle, CURLOPT_CAPATH, repo->cert.value.ptr);
+    }
+    if(!repo->verification.value) {
         curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
         curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
     }
@@ -635,7 +635,7 @@
         // we actually don't care what the result is
         // if it doesn't exists, an error will occur later
         // and we can't handle it here
-        char *newurl = util_concat_path(repo->url, util_resource_name(col->href));
+        char *newurl = util_concat_path(repo->url.value.ptr, util_resource_name(col->href));
         dav_session_set_baseurl(sn, newurl);
         free(newurl);
     }
@@ -731,7 +731,7 @@
         return -1;
     }
     
-    Repository *repo = get_repository(cx_str(dir->repository));
+    DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
     if(!repo) {
         fprintf(stderr, "Unknown repository %s\n", dir->repository);
         return -1;
@@ -2076,7 +2076,7 @@
         return -1;
     }
     
-    Repository *repo = get_repository(cx_str(dir->repository));
+    DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
     if(!repo) {
         fprintf(stderr, "Unkown repository %s\n", dir->name);
         return -1;
@@ -2754,7 +2754,7 @@
     int ret = 0;
     
     // create DavSession
-    Repository *repo = get_repository(cx_str(dir->repository));
+    DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
     if(!repo) {
         log_error("Unkown repository %s\n", dir->name);
         return -1;
@@ -5134,7 +5134,7 @@
         fprintf(stderr, "No versioning configured for syncdir %s\n", dir->name);
     }
     
-    Repository *repo = get_repository(cx_str(dir->repository));
+    DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
     if(!repo) {
         fprintf(stderr, "Unknown repository %s\n", dir->repository);
         return -1;
@@ -5729,12 +5729,12 @@
     CxIterator iter = cxListIterator(reponames);
     cx_foreach(char *, reponame, iter) {
         log_printf("Checking %s... ", reponame);
-        Repository* repo = get_repository(cx_str(reponame));
+        DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(reponame));
         if (!repo) {
             log_printf(" not found in config.xml!\n");
             ret = EXIT_FAILURE;
         } else {
-            DavSession *sn = create_session(a, ctx, repo, repo->url);
+            DavSession *sn = create_session(a, ctx, repo, repo->url.value.ptr);
             if (sn) {
                 DavResource *res = dav_query(sn,
                         "select - from / with depth = 0");
--- a/libidav/Makefile	Sun Sep 17 13:51:01 2023 +0200
+++ b/libidav/Makefile	Sat Sep 30 16:33:47 2023 +0200
@@ -41,6 +41,7 @@
 SRC += crypto.c
 SRC += xml.c
 SRC += versioning.c
+SRC += config.c
 
 OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
 
--- /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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/config.h	Sat Sep 30 16:33:47 2023 +0200
@@ -0,0 +1,188 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBIDAV_CONFIG_H
+#define LIBIDAV_CONFIG_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavConfig         DavConfig;
+typedef struct DavCfgRepository  DavCfgRepository;
+typedef struct DavCfgProxy       DavCfgProxy;
+typedef struct DavCfgKey         DavCfgKey;
+typedef struct DavCfgNamespace   DavCfgNamespace;
+typedef struct DavCfgSecretStore DavCfgSecretStore;
+
+typedef struct CfgString  CfgString;
+typedef struct CfgInt     CfgInt;
+typedef struct CfgUInt    CfgUInt;
+typedef struct CfgBool    CfgBool;
+
+typedef enum dav_cfg_key_type DavCfgKeyType;
+
+#define DAV_HTTP_PROXY 1
+#define DAV_HTTPS_PROXY 2
+    
+enum dav_cfg_key_type {
+    DAV_KEY_TYPE_AES256 = 0,
+    DAV_KEY_TYPE_AES128,
+    DAV_KEY_TYPE_UNKNOWN
+};
+
+struct DavConfig {
+    CxMempool         *mp;
+    
+    DavCfgRepository  *repositories;
+    DavCfgKey         *keys;
+    DavCfgNamespace   *namespaces;
+    DavCfgProxy       *http_proxy;
+    DavCfgProxy       *https_proxy;
+    DavCfgSecretStore *secretstore;
+    
+    xmlDoc *doc;
+};
+
+struct CfgString {
+    cxmutstr value;
+    xmlNode *node;
+};
+
+struct CfgInt {
+    int64_t value;
+    xmlNode *node;
+};
+
+struct CfgUInt {
+    uint64_t value;
+    xmlNode *node;
+};
+
+struct CfgBool {
+    bool value;
+    xmlNode *node;
+};
+
+
+struct DavCfgRepository {
+    xmlNode *node;
+    
+    CfgString     name;
+    CfgString     url;
+    CfgString     user;
+    CfgString     password;
+    CfgString     stored_user;
+    CfgString     default_key;
+    CfgString     cert;
+    CfgBool       verification;
+    
+    CfgBool       full_encryption;
+    CfgBool       content_encryption;
+    CfgBool       decrypt_content;
+    CfgBool       decrypt_name;
+    CfgBool       decrypt_properties;
+    
+    CfgInt        ssl_version;
+    CfgUInt       authmethods;
+    
+    int           unknown_elements;
+    
+    DavCfgRepository    *prev;
+    DavCfgRepository    *next;
+};
+
+struct DavCfgProxy {
+    CfgString  url;
+    CfgString  user;
+    CfgString  password;
+    CfgString  noproxy;
+    
+    int     unknown_elements;
+};
+
+struct DavCfgKey {
+    CfgString  name;
+    CfgString  file;
+    DavCfgKeyType type;
+    xmlNode *type_node;
+    
+    DavCfgKey *prev;
+    DavCfgKey *next;
+    
+    int       unknown_elements;
+};
+
+struct DavCfgNamespace {
+    xmlNode *node;
+    cxmutstr prefix;
+    cxmutstr uri;
+    
+    DavCfgNamespace *prev;
+    DavCfgNamespace *next;
+};
+
+struct DavCfgSecretStore {
+    CfgString unlock_cmd;
+    CfgString lock_cmd;
+};
+
+enum DavConfigError {
+    DAV_CONFIG_ERROR_XML = 0
+};
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error);
+
+CxBuffer* dav_config2buf(DavConfig *config);
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo);
+
+DavCfgRepository* dav_repository_new(DavConfig *config);
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo);
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo);
+int dav_repository_get_flags(DavCfgRepository *repo);
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl);
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password);
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo);
+
+int dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *node);
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *node);
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name);
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path);
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_CONFIG_H */
+
--- a/libidav/utils.c	Sun Sep 17 13:51:01 2023 +0200
+++ b/libidav/utils.c	Sat Sep 30 16:33:47 2023 +0200
@@ -281,7 +281,7 @@
     }
 }
 
-char* util_url_base_s(cxstring url) {
+cxstring util_url_base_s(cxstring url) {
     size_t i = 0;
     if(url.length > 0) {
         int slmax;
@@ -303,12 +303,11 @@
             }
         }
     }
-    cxstring server = cx_strsubsl(url, 0, i);
-    return cx_strdup(server).ptr;
+    return cx_strsubsl(url, 0, i);
 }
 
-char* util_url_base(char *url) {
-    return util_url_base_s(cx_str(url));
+char* util_url_base(const char *url) {
+    return cx_strdup(util_url_base_s(cx_str(url))).ptr;
 }
 
 #ifdef _WIN32
@@ -316,30 +315,30 @@
 #endif
 
 const char* util_url_path(const char *url) {
-    const char *path = NULL;
-    size_t len = strlen(url);
+    return util_url_path_s(cx_str(url)).ptr;
+}
+
+cxstring util_url_path_s(cxstring url) {
+    cxstring path = { "", 0 };
     int slashcount = 0;
     int slmax;
-    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+    if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) {
         slmax = 3;
-    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+    } else if(url.length > 8 && !strncasecmp(url.ptr, "https://", 8)) {
         slmax = 3;
     } else {
         slmax = 1;
     }
     char c;
-    for(int i=0;i<len;i++) {
-        c = url[i];
+    for(int i=0;i<url.length;i++) {
+        c = url.ptr[i];
         if(c == '/') {
             slashcount++;
             if(slashcount == slmax) {
-                path = url + i;
+                path = cx_strsubs(url, i);
                 break;
             }
         }
-    } 
-    if(!path) {
-        path = url + len; // empty string
     }
     return path;
 }
@@ -617,7 +616,7 @@
 }
 
 char* util_concat_path(const char *url_base, const char *p) {
-    cxstring base = cx_str((char*)url_base);
+    cxstring base = cx_str(url_base);
     cxstring path;
     if(p) {
         path = cx_str((char*)p);
@@ -625,6 +624,14 @@
         path = CX_STR("");
     }
     
+    return util_concat_path_s(base, path).ptr;
+}
+
+cxmutstr util_concat_path_s(cxstring base, cxstring path) {
+    if(!path.ptr) {
+        path = CX_STR("");
+    }
+    
     int add_separator = 0;
     if(base.length != 0 && base.ptr[base.length-1] == '/') {
         if(path.ptr[0] == '/') {
@@ -643,7 +650,7 @@
         url = cx_strcat(2, base, path);
     }
     
-    return url.ptr;
+    return url;
 }
 
 char* util_get_url(DavSession *sn, const char *href) {
--- a/libidav/utils.h	Sun Sep 17 13:51:01 2023 +0200
+++ b/libidav/utils.h	Sat Sep 30 16:33:47 2023 +0200
@@ -72,12 +72,14 @@
 
 int util_mkdir(char *path, mode_t mode);
 
-char* util_url_base(char *url);
-char* util_url_base_s(cxstring url);
+char* util_url_base(const char *url);
+cxstring util_url_base_s(cxstring url);
 const char* util_url_path(const char *url);
+cxstring util_url_path_s(cxstring url);
 char* util_url_decode(DavSession *sn, const char *url);
 const char* util_resource_name(const char *url);
 char* util_concat_path(const char *url_base, const char *path);
+cxmutstr util_concat_path_s(cxstring url_base, cxstring path);
 char* util_get_url(DavSession *sn, const char *href);
 void util_set_url(DavSession *sn, const char *href);
 
--- a/libidav/webdav.h	Sun Sep 17 13:51:01 2023 +0200
+++ b/libidav/webdav.h	Sat Sep 30 16:33:47 2023 +0200
@@ -30,6 +30,7 @@
 #define	WEBDAV_H
 
 #include <inttypes.h>
+#include <stdbool.h>
 #include <cx/map.h>
 #include <cx/mempool.h>
 #include <cx/linked_list.h>

mercurial