Sat, 30 Sep 2023 16:33:47 +0200
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);