dav/main.c

Sat, 25 Jul 2020 11:16:31 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 25 Jul 2020 11:16:31 +0200
changeset 723
5ca174b3247a
parent 720
2b8a65ed6f4c
child 726
8f6ad495f538
permissions
-rw-r--r--

add dav-sync outgoing command

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2019 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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/wait.h>
#endif
#include <ucx/string.h>
#include <ucx/utils.h>
#include <dirent.h>

#include <libidav/utils.h>
#include <libidav/crypto.h>
#include <libidav/session.h>
#include <libidav/xml.h>
#include "config.h"
#include "error.h"
#include "assistant.h"
#include "system.h"
#include "pwd.h"
#include "finfo.h"
#include "main.h"

static DavContext *ctx;

static int printxmlerror = 1;
static void xmlerrorfnc(void * c, const char * msg, ... ) {
    if(printxmlerror) {
        va_list ap;
        va_start(ap, msg);
        vfprintf(stderr, msg, ap);
        va_end(ap);
    }
}

//define DO_THE_TEST
//include <libidav/davqlparser.h>
//include <libidav/davqlexec.h>
//include "tags.h"
//include <libidav/resource.h>

void test() {
    
}

int dav_main(int argc, char **argv);

#ifdef _WIN32
static char* wchar2utf8(const wchar_t *wstr, size_t wlen) {
    size_t maxlen = wlen * 4;
    char *ret = malloc(maxlen + 1);
    int ret_len = WideCharToMultiByte(
        CP_UTF8,
        0,
        wstr,
        wlen,
        ret,
        maxlen,
        NULL,
        NULL);
    ret[ret_len] = 0;
    return ret;
}

int wmain(int argc, wchar_t **argv) {
    char **argv_utf8 = calloc(argc, sizeof(char*));
    for(int i=0;i<argc;i++) {
        argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i]));
    }
    
    int ret = dav_main(argc, argv_utf8);
    
    for(int i=0;i<argc;i++) {
        free(argv_utf8[i]);
    }
    free(argv_utf8);
    
    return ret;
}
#else
int main(int argc, char **argv) {
    return dav_main(argc, argv);
}
#endif


int dav_main(int argc, char **argv) {
    if(argc < 2) {
        fprintf(stderr, "Missing command\n");
        print_usage(argv[0]);
        return -1;
    }
    
    putenv("LC_TIME=C");
       
    char *cmd = argv[1];
    CmdArgs *args = cmd_parse_args(argc - 2, argv + 2);
    if(!args) {
        print_usage(argv[0]);
        return -1;
    }
    
    sys_init();
    xmlGenericErrorFunc fnc = xmlerrorfnc;
    initGenericErrorDefaultFunc(&fnc);
    ctx = dav_context_new();
    dav_add_namespace(ctx, "apache", "http://apache.org/dav/props/");
    int cfgret = load_config(ctx);
    int ret = EXIT_FAILURE;
    printxmlerror = 0;
#ifdef DO_THE_TEST
    test();
    return 0;
#endif
    if(!strcmp(cmd, "check") || !strcmp(cmd, "check-config")) {
        if(!cfgret) {
            fprintf(stdout, "Configuration OK.\n");
            ret = EXIT_SUCCESS;
        } else {
            /* no output, the warnings are written by load_config */
            ret = EXIT_FAILURE;
        }
    } else if(!cfgret) {
        if(!strcasecmp(cmd, "list") || !strcasecmp(cmd, "ls")) {
            ret = cmd_list(args);
        } else if(!strcasecmp(cmd, "get")) {
            ret = cmd_get(args, FALSE);
        } else if(!strcasecmp(cmd, "cat")) {
            ucx_map_cstr_put(args->options, "output", "-");
            ret = cmd_get(args, FALSE);
        } else if(!strcasecmp(cmd, "edit")) {
            ret = cmd_edit(args);
        } else if(!strcasecmp(cmd, "put")) {
            ret = cmd_put(args, FALSE);
        } else if(
                !strcasecmp(cmd, "remove") ||
                !strcasecmp(cmd, "rm") ||
                !strcasecmp(cmd, "delete"))
        {
            ret = cmd_remove(args);
        } else if(!strcasecmp(cmd, "mkdir") || !strcasecmp(cmd, "mkcol")) {
            ret = cmd_mkdir(args);
        } else if(!strcasecmp(cmd, "copy") || !strcasecmp(cmd, "cp")) {
            ret = cmd_move(args, true);
        } else if(!strcasecmp(cmd, "move") || !strcasecmp(cmd, "mv")) {
            ret = cmd_move(args, false);
        } else if(!strcasecmp(cmd, "rename")) {
            ret = cmd_rename(args);
        } else if(!strcasecmp(cmd, "export")) {
            ret = cmd_get(args, TRUE);
        } else if(!strcasecmp(cmd, "import")) {
            ret = cmd_put(args, TRUE);
        } else if(!strcasecmp(cmd, "date")) {
            ret = cmd_date(args);
        } else if(!strcasecmp(cmd, "set-property")) {
            ret = cmd_set_property(args);
        } else if(!strcasecmp(cmd, "get-property")) {
            ret = cmd_get_property(args);
        } else if(!strcasecmp(cmd, "remove-property")) {
            ret = cmd_remove_property(args);
        } else if(!strcasecmp(cmd, "lock")) {
            ret = cmd_lock(args);
        } else if(!strcasecmp(cmd, "unlock")) {
            ret = cmd_unlock(args);
        }  else if(!strcasecmp(cmd, "info")) {
            ret = cmd_info(args);
        } else if(!strcasecmp(cmd, "checkout")) {
            ret = cmd_checkout(args);
        } else if(!strcasecmp(cmd, "checkin")) {
            ret = cmd_checkin(args);
        } else if(!strcasecmp(cmd, "uncheckout")) {
            ret = cmd_uncheckout(args);
        } else if(!strcasecmp(cmd, "versioncontrol")) {
            ret = cmd_versioncontrol(args);
        } else if(!strcasecmp(cmd, "list-versions") || !strcasecmp(cmd, "lsv")) {
            ret = cmd_list_versions(args);
        } else if(!strcasecmp(cmd, "add-repository")
                || !strcasecmp(cmd, "add-repo")) {
            ret = cmd_add_repository(args);
        } else if(!strcasecmp(cmd, "remove-repository")
                || !strcasecmp(cmd, "remove-repo")
                || !strcasecmp(cmd, "rm-repo")) {
            ret = cmd_remove_repository(args);
        } else if(!strcasecmp(cmd, "list-repositories")
                || !strcasecmp(cmd, "list-repos")) {
            ret = list_repositories();
        } else if(!strcasecmp(cmd, "repository-url")
                || !strcasecmp(cmd, "repo-url")) {
            ret = cmd_repository_url(args);
        } else if(!strcasecmp(cmd, "add-user")) {
            ret = cmd_add_user(args);
        } else if(!strcasecmp(cmd, "list-users")) {
            ret = cmd_list_users(args);
        } else if(!strcasecmp(cmd, "remove-user")) {
            ret = cmd_remove_user(args);
        } else if(!strcasecmp(cmd, "edit-user")) {
            ret = cmd_edit_user(args);
        } else if(!strcasecmp(cmd, "version") || !strcasecmp(cmd, "-version")
                || !strcasecmp(cmd, "--version")) {
            fprintf(stderr, "dav %s\n", DAV_VERSION);
        } else if(!strcasecmp(cmd, "complete")) {
            ret = cmd_complete(args);
        } else {
            print_usage(argv[0]);
        }
    }
    
    dav_context_destroy(ctx);
    cmd_args_free(args);
    free_config();
    xmlCleanupParser();
    curl_global_cleanup();
    sys_uninit();
    
    return ret;
}

static char *cmdusageinfo[] = {
    "list [-altdepcR] [-u <date>] <url>",
    "get [-pcRK] [-o <file>] [-u <date>] [-V <version>] <url>",
    "put [-pcR] [-k <key>] [-L <lock>] <url> <file...>",
    "edit [-pc] [-k <key>] [-V <version>] [-L <lock>] <url>",
    "mkdir [-pc] [-k <key>] [-L <lock>] <url> [file...]",
    "remove [-pc] [-L <lock>] <url> [file...]",
    "copy [-pcO] [-L <lock>] <url> <url>",
    "move [-pcO] [-L <lock>] <url> <url>",
    "rename [-pcO] [-L <lock>] <url> <name>",
    "export [-pc] [-o <file>] [-u <date>] <url>",
    "import [-pc] [-k <key>] [-L <lock>] <url> <file>",
    "get-property [-pcx] [-V <version>] [-n <uri>] <url> <property>",
    "set-property [-pcx] [-L <lock>] [-n <uri>] <url> <property> [value]",
    "remove-property [-pc] [-n <uri>] <url> <property>",
    "lock [-pc] [-T timeout] <url>",
    "unlock [-pc] [-L <lock>] <url>",
    "info [-pc] [-V <version>] <url>",
    "date [url]",
    NULL
};

char* find_usage_str(const char *cmd) {
    scstr_t c = scstr(cmd);
    for(int i=0;;i++) {
        char *str = cmdusageinfo[i];
        if(!str) {
            break;
        }
        scstr_t u = scstr(str);
        if(sstrprefix(u, c)) {
            return str;
        }
    }
    return NULL;
}

void print_usage(char *cmd) {
    fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd);
    
    fprintf(stderr, "Commands:\n");
    for(int i=0;;i++) {
        char *str = cmdusageinfo[i];
        if(!str) {
            break;
        }
        fprintf(stderr, "        %s\n", str);
    }
    fprintf(stderr, "Options:\n");
    fprintf(stderr,
            "        -k <key>    Key to use for encryption\n");
    fprintf(stderr, "        -p           Don't encrypt or decrypt files\n");
    fprintf(stderr, "        -c           Enable full encryption\n");
    fprintf(stderr,
            "        -R           "
            "Recursively do the operation for all children\n");
    fprintf(stderr, "        -K           Keep already present files\n");
    fprintf(stderr, "        -o <file>    Write output to file (use '-' for stdout)\n");
    fprintf(
            stderr,
            "        -u <date>    "
            "Get resources which are modified since the specified date\n");
    fprintf(stderr, "        -V <version> Download a specific version of a resource\n");
    fprintf(stderr, "        -a           show all files\n");
    fprintf(stderr, "        -l           print resources in long list format\n");
    fprintf(stderr, "        -t           print content type\n");
    fprintf(stderr, "        -d           order by last modified date\n");
    fprintf(stderr, "        -e           show extended flags\n");
    fprintf(stderr, "        -O           override resources\n");
    fprintf(stderr, "        -L <lock>    specificy lock token\n");
    fprintf(stderr, "        -T <sec>     timeout in seconds\n");
    fprintf(stderr, "        -n <uri>     specify namespace uri\n");
    fprintf(stderr, "        -x           xml property content\n");
    fprintf(stderr, "        -N           disable authentication prompt (all commands)\n");
    fprintf(stderr, "        -i           disable cert verification (all commands)\n");
    fprintf(stderr, "        -v           verbose output (all commands)\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Advanced commands:\n");
    fprintf(stderr, "        versioncontrol list-versions checkout checkin uncheckout\n\n");
    fprintf(stderr, "Config commands:\n");
    fprintf(stderr, "        add-repository remove-repository list-repositories repository-url\n");
    fprintf(stderr, "        add-user remove-user edit-user list-users\n");
    fprintf(stderr, "        check-config\n");
    fprintf(stderr, "\n");
    fprintf(stderr,
            "Instead of an url you can pass a repository name "
            "with an optional path:\n");
    fprintf(stderr, "        <repository>/path/\n");
    fprintf(stderr, "\n");
}


int request_auth2(DavSession *sn, void *userdata) {
    Repository *repo = userdata;
    
    char *user = NULL;
    char ubuf[256];
    if(repo->user) {
       user = repo->user; 
    } else {
        fprintf(stderr, "User: ");
        fflush(stderr);
        user = fgets(ubuf, 256, stdin);
    }
    if(!user) {
        return 0;
    }
    
    char *password = util_password_input("Password: ");
    if(!password || strlen(password) == 0) {
        return 0;
    }
    
    size_t ulen = strlen(user);
    if(user[ulen-1] == '\n') {
        user[ulen-1] = '\0';
    }
    
    dav_session_set_auth(sn, user, password);
    free(password);
    
    return 0;
}

static Repository* url2repo_s(sstr_t url, char **path) {
    *path = NULL;
    
    int s;
    if(sstrprefix(url, SC("http://"))) {
        s = 7;
    } else if(sstrprefix(url, SC("https://"))) {
        s = 8;
    } else {
        s = 1;
    }

    // split URL into repository and path
    sstr_t r = sstrsubs(url, s);
    sstr_t p = sstrchr(r, '/');
    r = sstrsubsl(url, 0, url.length-p.length);
    if(p.length == 0) {
        p = sstrn("/", 1);
    }
    
    Repository *repo = get_repository(r);
    if(repo) {
        *path = sstrdup(p).ptr;
    } else {
        // TODO: who is responsible for freeing this repository?
        // how can the callee know, if he has to call free()?
        repo = calloc(1, sizeof(Repository));
        repo->name = strdup("");
        repo->decrypt_content = true;
        repo->verification = true;
        repo->authmethods = CURLAUTH_BASIC;
        if(url.ptr[url.length-1] == '/') {
            repo->url = sstrdup(url).ptr;
            *path = strdup("/");
        } else if (sstrchr(url, '/').length > 0) {
            // TODO: fix the following workaround after
            //       fixing the inconsistent behavior of util_url_*()
            repo->url = util_url_base_s(url);
            sstr_t truncated = sstrdup(url);
            *path = strdup(util_url_path(truncated.ptr));
            free(truncated.ptr);
        } else {
            repo->url = sstrdup(url).ptr;
            *path = strdup("/");
        }
    }
    
    return repo;
}

static Repository* url2repo(char *url, char **path) {
    return url2repo_s(sstr(url), path);
}

static int set_session_config(DavSession *sn, CmdArgs *a) {
    char *plain = cmd_getoption(a, "plain");
    char *crypt = cmd_getoption(a, "crypt");
    
    if(plain && crypt) {
        fprintf(stderr, "Error: -p and -c option set\n");
        return 1;
    }
    
    if (plain) {
        sn->flags &= ~DAV_SESSION_FULL_ENCRYPTION;
    } else if(crypt) {
        sn->flags |= DAV_SESSION_FULL_ENCRYPTION;
    }
    
    if (cmd_getoption(a, "verbose")) {
        curl_easy_setopt(sn->handle, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(sn->handle, CURLOPT_STDERR, stderr);
    }
       
    return 0;
}

static void set_session_lock(DavSession *sn, CmdArgs *a) {
    char *locktoken = cmd_getoption(a, "lock");
    if(locktoken) {
        DavLock *lock = dav_create_lock(sn, locktoken, NULL);
        dav_add_collection_lock(sn, "/", lock);
    }
}

static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) {
    if(cmd_getoption(a, "noinput")) {
        return 1;
    }
    
    char *ps_password = util_password_input("Master password: ");
    if(!ps_password) {
        return 1;
    }
    if(pwdstore_setpassword(secrets, ps_password)) {
        fprintf(stderr, "Error: cannot create key from password\n");
        return 1;
    }
    if(pwdstore_decrypt(secrets)) {
        fprintf(stderr, "Error: cannot decrypt secrets store\n");
        return 1;
    }
    return 0;
}

static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) {
    if(!credid) {
        return 0;
    }
    
    PwdStore *secrets = get_pwdstore();
    if(!secrets) {
        fprintf(stderr, "Error: no secrets store available\n");
        return 0;
    }
    
    if(pwdstore_has_id(secrets, credid)) {
        if(!secrets->isdecrypted) {
            if(decrypt_secrets(a, secrets)) {
                return 0;
            }
        }
        
        PwdEntry *s_cred = pwdstore_get(secrets, credid);
        if(s_cred) {
            *user = s_cred->user;
            *password = s_cred->password;
            return 1;
        }
    } else {
        fprintf(stderr, "Error: credentials id '%s' not found\n", credid);
    }
    
    return 0;
}

typedef struct CredLocation {
    char *id;
    char *location;
} CredLocation;

static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) {
    return strcmp(e2->location, e1->location);
}

static void free_cred_location(CredLocation *c) {
    // c->id is not a copy, therefore we don't have to free it
    free(c->location);
    free(c);
}

static int get_location_credentials(CmdArgs *a, Repository *repo, char *path, char **user, char **password) {
    PwdStore *secrets = get_pwdstore();
    if(!secrets) {
        return 0;
    }
    
    /*
     * The list secrets->location contains urls or repo names as
     * location strings. We need a list, that contains only urls
     */
    UcxList *locations = NULL;
    UCX_FOREACH(elm, secrets->locations) {
        PwdIndexEntry *e = elm->data;
        
        UCX_FOREACH(loc, e->locations) {
            char *path;
            Repository *r = url2repo(loc->data, &path);
            CredLocation *urlentry = calloc(1, sizeof(CredLocation));
            urlentry->id = e->id;
            urlentry->location = util_concat_path(r->url, path);
            locations = ucx_list_append(locations, urlentry);
        }
    }
    // the list must be sorted
    locations = ucx_list_sort(locations, (cmp_func)cmp_url_cred_entry, NULL);
    
    // create full request url string and remove protocol prefix
    sstr_t req_url_proto = sstr(util_concat_path(repo->url, path));
    sstr_t req_url = req_url_proto;
    if(sstrprefix(req_url, S("http://"))) {
        req_url = sstrsubs(req_url, 7);
    } else if(sstrprefix(req_url, S("https://"))) {
        req_url = sstrsubs(req_url, 8);
    }
    
    // iterate over sorted locations and check if a location is a prefix
    // of the requested url
    char *id = NULL;
    int ret = 0;
    UCX_FOREACH(elm, locations) {
        CredLocation *cred = elm->data;
        sstr_t cred_url = sstr(cred->location);
        
        // remove protocol prefix
        if(sstrprefix(cred_url, S("http://"))) {
            cred_url = sstrsubs(cred_url, 7);
        } else if(sstrprefix(cred_url, S("https://"))) {
            cred_url = sstrsubs(cred_url, 8);
        }
        
        if(sstrprefix(req_url, cred_url)) {
            id = cred->id;
            break;
        }
    }
    
    // if an id is found and we can access the decrypted secret store
    // we can set the user/password
    if(id && (secrets->isdecrypted || !decrypt_secrets(a, secrets))) {
        PwdEntry *cred = pwdstore_get(secrets, id);
        if(cred) {
            *user = cred->user;
            *password = cred->password;
            ret = 1;
        }
    }
    
    free(req_url_proto.ptr);
    ucx_list_free_content(locations, (ucx_destructor)free_cred_location);
    ucx_list_free(locations);
    
    return ret;
}

static DavSession* connect_to_repo(Repository *repo, char *path, CmdArgs *a) {
    char *user = repo->user;
    char *password = repo->password;
    
    if(!user && !password) {
        if(!get_stored_credentials(a, repo->stored_user, &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);
    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->verification || cmd_getoption(a, "insecure")) {
        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
    }
    if(!cmd_getoption(a, "noinput")) {
        dav_session_set_authcallback(sn, request_auth2, repo);
    }
    return sn;
}

int update_progress(DavResource *res, int64_t total, int64_t now, Progress *p) {
    int ret = 0;
    if(res != p->last_resource) {
        p->cur += p->last_res_total - p->last_res_cur;
        ret = 1;
    } else {
        p->cur += now - p->last_res_cur;
    }
    
    p->last_resource = res;
    p->last_res_cur = now;
    p->last_res_total = total;
    
    return ret;
}

void download_progress(DavResource *res, int64_t total, int64_t now, void *data) {
    Progress *p = data;
    int newres = update_progress(res, total, now, p);
    
    time_t newts = time(NULL);
    if((p->ts != newts)) {
        fprintf(p->out, "[%s]: %" PRId64 "k/%" PRId64 "k total: %" PRId64 "M/%" PRId64 "M\n", res->name, now/1024, total/1024, p->cur/(1024*1024), p->total/(1024*1024));
        fflush(p->out);
    }
    p->ts = newts;
}


#define LIST_QUERY_ORDER_BY_NAME "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery,apache:executable from %s with depth = %d where lastmodified > %t order by iscollection desc, name"
#define LIST_QUERY_ORDER_BY_DATE "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery,apache:executable from %s with depth = %d where lastmodified > %t order by iscollection desc, lastmodified desc"

int cmd_list(CmdArgs *a) {
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("list"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *update = cmd_getoption(a, "update");
    char *date = cmd_getoption(a, "date");
    time_t t = -1;
    if(update) {
        t = util_parse_lastmodified(update);
    }
    
    int depth = cmd_getoption(a, "recursive") ? -1 : 1;
    int ret = 0;
    DavResource *ls = dav_query(
            sn,
            date ? LIST_QUERY_ORDER_BY_DATE : LIST_QUERY_ORDER_BY_NAME,
            path,
            depth,
            t);
    if(ls) {
        // parameters
        void (*print_func)(DavResource*, char *, CmdArgs *);
        if(cmd_getoption(a, "list") || cmd_getoption(a, "extended")) {
            print_func = ls_print_list_elm;
        } else {
            print_func = ls_print_elm;
        }
        
        DavResource *child = ls->children;
        while(child) {
            print_func(child, path, a);
            child = child->next;
        }
    } else {
        print_resource_error(sn, path);
        ret = -1;
    }
    
    free(path);
    //free(base);
    
    dav_session_destroy(sn);
    
    return ret;
}

static char* ls_name(char *parent, char *path, int *len) {
    if(parent) {
        path += strlen(parent);
    }
    if(path[0] == '/') {
        path++;
    }
    int pathlen = strlen(path);
    if(path[pathlen-1] == '/') {
        pathlen--;
    }
    *len = pathlen;
    return path;
}

void ls_print_list_elm(DavResource *res, char *parent, CmdArgs *a) {
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    int show_all = cmd_getoption(a, "all") ? 1 : 0;
    if(res->name[0] == '.' && !show_all) {
        return;
    }
    
    char flags[16];
    memset(flags, '-', 15);
    
    int type_width = 0;
    char *type = res->contenttype;
    
    if(res->iscollection) {
        flags[0] = 'd';
        type = "";
    }
    char *keyprop = dav_get_string_property_ns(
            res,
            DAV_NS,
            "crypto-key");
    if(keyprop) {
        flags[1] = 'c';
    }
    
    if(cmd_getoption(a, "extended")) {
        flags[6] = '\0';
        if(dav_get_string_property(res, "D:lockdiscovery")) {
            flags[2] = 'l';
        }
        char *executable = dav_get_string_property_ns(
                res,
                "http://apache.org/dav/props/",
                "executable");
        if(executable && util_getboolean(executable)) {
            flags[3] = 'x';
        }
    } else {
        flags[2] = '\0';
    }
    
    if(cmd_getoption(a, "type")) {
        type_width = 20;
    }
    if(type == NULL || type_width == 0) {
        type = "";
    }
    
    char *date = util_date_str(res->lastmodified);
    char *size = util_size_str(res->iscollection, res->contentlength);
    int namelen = strlen(res->name);
    char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name;
    
    //char *name = recursive ? res->path+1 : res->name;
    printf(
            "%s %*s %10s  %12s  %.*s\n",
            flags,
            type_width, type,
            size,
            date,
            namelen,
            name);
    free(date);
    free(size);
    
    if(recursive) {
        DavResource *child = res->children;
        while(child) {
            //ls_print_list_elm(child, a);
            if(child->name[0] != '.' || show_all) {
                ls_print_list_elm(child, parent, a);
            }
            child = child->next;
        }
    }
}

void ls_print_elm(DavResource *res, char *parent, CmdArgs *a) {
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    int show_all = cmd_getoption(a, "all") ? 1 : 0;
    if(res->name[0] == '.' && !show_all) {
        return;
    }
    
    int namelen = strlen(res->name);
    char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name;
    printf("%.*s\n", namelen, name);
    if(recursive) {
        DavResource *child = res->children;
        while(child) {
            ls_print_elm(child, parent, a);
            child = child->next;
        }
    }
}

static void free_getres(void *r) {
    GetResource *getres = r;
    free(getres->path);
    free(getres);
}

int cmd_get(CmdArgs *a, DavBool export) {
    if(a->argc != 1) {
        // TODO: change this, when get supports retrieval of multiple files
        fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("get"));
        return -1;
    }
    if(export) {
        ucx_map_cstr_put(a->options, "recursive", "");
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *progressfile = cmd_getoption(a, "progressfile");
    Progress pdata;
    memset(&pdata, 0, sizeof(Progress));
    if(progressfile) {
        if(!strcmp(progressfile, "-")) {
            pdata.out = stdout;
            pdata.isstdout = 1;
        } else {
            pdata.out = fopen(progressfile, "w");
        }
        if(pdata.out) {
            dav_session_set_progresscallback(sn, download_progress, NULL, &pdata);
        }
    }
    
    char *update = cmd_getoption(a, "update");
    time_t t = -1;
    if(update) {
        t = util_parse_lastmodified(update);
        if (t == 0) {
            fprintf(stderr,
                    "Invalid date format. Possible formats are:\n"
                    "  RFC-1123 - example: Thu, 29 Nov 2012 21:35:35 GMT\n"
                    "  RFC-3339 - example: 2012-11-29T21:35:35Z\n");
            return -1;
        }
    }
    
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    char *version = cmd_getoption(a, "version");
    
    if(recursive && version) {
        fprintf(stderr, "-V option can only be used without -R option\n");
        return -1;
    }
    
    DavResource *res;
    
    int depth = recursive ? -1 : 1;
    res = dav_query(
            sn,
            "select - from %s with depth = %d where iscollection or lastmodified > %t",
            path,
            depth,
            t);
    if(!res) {
        print_resource_error(sn, path);
        return -1;
    }
    if(!recursive && res->iscollection) {
        fprintf(stderr, "Resource %s is a collection.\n", res->path);
        fprintf(stderr, "Use the -R option to download collections.\n");
        return -1;
    }
    
    if(version) {
        DavResource *vres = find_version(res, version);
        if(!vres) {
            fprintf(stderr, "Cannot find version '%s' for resource.\n", version);
            return -1;
        }
        dav_resource_free_all(res);
        res = vres;
    }
    
    /*
     * determine the output file
     * use stdout if the output file is -
     */
    char *outfile = cmd_getoption(a, "output");
    char *basepath = outfile;
    if(!outfile) {
        if(res->iscollection) {
            basepath = "";
        } else {
            basepath = res->name;
        }
        if(export) {
            outfile = "-";
        }
    } else if(export) {
        basepath = "";
    } else if(res->iscollection && !strcmp(outfile, "-")) {
        fprintf(
                stderr,
                "Cannot write output to stdout "
                "if the requested resource is a collection.\n");
        return -1;
    }
    
    // get list of resources
    UcxList *reslist = NULL;
    uint64_t totalsize = 0;
    uint64_t rescount = 0;
    
    GetResource *getres = malloc(sizeof(GetResource));
    getres->res = res;
    getres->path = strdup(basepath);
    
    char *structure = cmd_getoption(a, "structure");
    
    // iterate over resource tree
    UcxList *stack = ucx_list_prepend(NULL, getres);
    while(stack) {
        GetResource *g = stack->data;
        stack = ucx_list_remove(stack, stack);

        if(g->res->iscollection) {
            DavResource *child = g->res->children;
            while(child) {
                // add resource to stack
                size_t pathlen = strlen(g->path);
                GetResource *newres = malloc(sizeof(GetResource));
                newres->res = child;
                newres->path = pathlen > 0 ?
                    util_concat_path(g->path, child->name) : strdup(child->name);

                stack = ucx_list_prepend(stack, newres);
                
                child = child->next;
            }
        } else {
            if(structure) {
                // download only directory structure
                // this is a hidden feature and will be replaced in the future
                continue; // skip non-collection resource
            }
            totalsize += g->res->contentlength;
            rescount++;
        }
        
        if(strlen(g->path) == 0) {
            free_getres(g);
        } else {
            reslist = ucx_list_append(reslist, g);
        }
    }
    
    // download resources
    pdata.total = totalsize;
    
    int ret;
    getfunc get;
    TarOutputStream *tout = NULL;
    if(export) {
        get = (getfunc)resource2tar;
        FILE *tarfile = strcmp(outfile, "-") ? fopen(outfile, "wb") : stdout;
        if(!tarfile) {
            perror("Cannot open tar output file");
            return -1;
        }
        tout = tar_open(tarfile);
    } else {
        get = get_resource;
    }
    UCX_FOREACH(elm, reslist) {
        GetResource *getres = elm->data;
        
        ret = get(repo, getres, a, tout);
        if(ret) {
            break;
        }
    }
    if(export) {
        // close tar stream
        if(tar_close(tout)) {
            fprintf(stderr, "tar stream broken\n");
            ret = -1;
        }
    }
    
    ucx_list_free_content(reslist, free_getres);
    ucx_list_free(reslist);
    free(path);
    
    if(pdata.out && !pdata.isstdout) {
        fclose(pdata.out);
    }
    return ret;
}

static int file_seek(FILE *f, curl_off_t offset, int origin) {
    int ret = fseek(f, offset, origin);
    return ret == 0 ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK;
}

static int check_encryption_key(CmdArgs *a, DavSession *sn) {
    // override the session key if the -k option is specified
    char *keyname = cmd_getoption(a, "key");
    if(keyname) {
        DavKey *key = dav_context_get_key(ctx, keyname);
        if(key) {
            sn->key = key;
        } else {
            fprintf(stderr, "Key %s not found!\nAbort.\n", keyname);
            return 1;
        }
        
        /*
         * If a key is explicitly specified, we can safely assume that the user
         * wants to encrypt. For security reasons we report an error, if no
         * encryption is enabled.
         */
        if(!DAV_IS_ENCRYPTED(sn)) {
            fprintf(stderr, "A key has been explicitly specified, but no "
                "encryption is requested.\n"
                "You have the following options:\n"
                " - pass '-c' as command line argument to request encryption\n"
                " - activate encryption in the config.xml\n"
                " - don't use '-k <key>' "
                "(warning: encryption will NOT happen)\n");
        return 1;
        }
    }

    // if encryption is requested, but we still don't know the key, report error
    if(DAV_IS_ENCRYPTED(sn) && !(sn->key)) {
        fprintf(stderr, "Encryption has been requested, "
                "but no default key is configured.\n"
                "You may specify a custom key with the '-k' option.\n");
        return 1;
    }
    
    return 0;
}

int cmd_edit(CmdArgs *a) {
#ifdef _WIN32
    fprintf(stderr, "This feature is not supported on your platform.\n");
    return -1;
#else
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("edit"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
        
    if(check_encryption_key(a, sn)) {
        return -1;
    }
       
    char *version = cmd_getoption(a, "version");        
    DavResource *res;
    
    res = dav_resource_new(sn, path);
    int fresh_resource = !dav_exists(res);
    if(fresh_resource) {
        // create the resource to check if the resource can be created
        // we don't want the user to invest time in editing and fail afterwards
        if(dav_create(res)) {
            fprintf(stderr, "Resource does not exist and cannot be created.\n");
            return -1;
        }
    } else {
        if(!res) {
            print_resource_error(sn, path);
            return -1;
        }
        if(res->iscollection) {
            fprintf(stderr, "Resource %s is a collection "
                    "and cannot be opened in an editor.\n", res->path);
            return -1;
        }

        if(version) {
            DavResource *vres = find_version(res, version);
            if(!vres) {
                fprintf(stderr, "Cannot find version '%s' for resource.\n", version);
                return -1;
            }
            dav_resource_free_all(res);
            res = vres;
        }
    }
    
    // get temp dir
    char* envtmp = getenv("TMPDIR");
    char* outfile;
    if(envtmp) {
        size_t len = strlen(envtmp);
        outfile = malloc(len+24);
        memcpy(outfile, envtmp, len+1);
        if(outfile[len-1] != '/') {
            outfile[len] = '/';
            outfile[len+1] = 0;
        }
    } else {
        outfile = malloc(24);
        strncpy(outfile, "/tmp/", 24);
    }
    
    // check temp dir and fall back to $HOME
    if(access(outfile, W_OK)) {
        char* home = getenv("HOME");
        if(home) {
            size_t len = strlen(home);
            outfile = malloc(len+24);
            memcpy(outfile, home, len+1);
            if(outfile[len-1] != '/') {
                outfile[len] = '/';
                outfile[len+1] = 0;
            }
        } else {
            // fallback did not work, report last error from access()
            perror("Cannot write to temporary location");
            free(outfile);
            return -1;
        }
    }
    
    // create temp file and open it
    outfile = strncat(outfile, ".dav-edit-XXXXXX", 23);
    int tmp_fd = mkstemp(outfile);
    if(tmp_fd < 0) {
        perror("Cannot open temporary file");
        return -1;
    }
    
    // get resource
    if(!fresh_resource) {
        FILE* tmp_stream = sys_fopen(outfile, "wb");
        if(!tmp_stream) {
            perror("Cannot open temporary file");
            free(outfile);
            close(tmp_fd);
            return -1;
        }
        if(dav_get_content(res, tmp_stream, (dav_write_func)fwrite)) {
            print_resource_error(sn, path);
            free(outfile);
            close(tmp_fd);
            sys_unlink(outfile);
            return -1;
        }
        fclose(tmp_stream);
    }
    
    // remember time s.t. we can later check if the file has changed
    SYS_STAT tmp_stat;
    if(sys_stat(outfile, &tmp_stat)) {
        perror("Cannot stat temporary file");
        free(outfile);
        close(tmp_fd);
        sys_unlink(outfile);
        return -1;
    }
    time_t dl_mtime = tmp_stat.st_mtime;
    
    // open in editor
    char* default_editor = "vi";
    char* editor = getenv("EDITOR");
    if(!editor) editor = default_editor;
    char* viargs[3] = {editor, outfile, NULL};

    int ret = 0;
    pid_t pid = fork();
    if(pid < 0) {
        perror("Cannot create process for editor");
        ret = -1;
    } else if(pid == 0) {
        if(execvp(viargs[0], viargs)) {
            perror("Opening the editor failed");
            return -1;
        }
    } else {
        int status = -1;
        ret = waitpid(pid, &status, 0);
        if(ret < 0) {
            perror("Error waiting for editor");
        } else if(WEXITSTATUS(status)) {
            fprintf(stderr,
                    "Editor closed abnormally - file will not be uploaded.\n");
            ret = -1;
        } else {
            // check if the file has changed
            if (sys_stat(outfile, &tmp_stat)) {
                // very rare case when someone concurrently changes permissions
                perror("Cannot stat temporary file");
                ret = -1;
            } else if (dl_mtime < tmp_stat.st_mtime) {
                // upload changed file
                FILE* tmp_stream = sys_fopen(outfile, "rb");
                if(!tmp_stream) {
                    perror("Cannot open temporary file");
                    ret = -1;
                } else {
                    dav_set_content(res, tmp_stream,
                            (dav_read_func)fread,
                            (dav_seek_func)file_seek);
                    dav_set_content_length(res, tmp_stat.st_size);
                    ret = dav_store(res);
                    fclose(tmp_stream);
                    if(ret) {
                        print_resource_error(sn, path);
                    }
                }
            } else {
                printf("No changes by user - file will not be uploaded.\n");
                ret = 0;
            }
        }
    }
    
    close(tmp_fd);
    if(ret) {
        // if someone went wrong, we are most likely unable to unlink anyway
        fprintf(stderr, "File location: %s\n", outfile);
    } else {
        sys_unlink(outfile);
    }
    free(outfile);
    free(path);
    
    return ret;
#endif
}

int get_resource(Repository *repo, GetResource *getres, CmdArgs *a, void *unused) {
    DavResource *res = getres->res;
    char *out = getres->path;
    
    if(res->iscollection) {
        printf("get: %s\n", res->path);
        
        int ret = sys_mkdir(out);
        if(ret != 0 && errno != EEXIST) {
            fprintf(stderr, "Cannot create directory '%s': ", out);
            perror("");
            return 1;
        }
        
        return 0;
    }
    
    int isstdout = !strcmp(out, "-");
    if(cmd_getoption(a, "keep") && !isstdout) {
        SYS_STAT s;
        if(sys_stat(out, &s)) {
            if(errno != ENOENT) {
                perror("stat");
            }
        } else {
            if(cmd_getoption(a, "recursive")) {
                printf("skip: %s\n", res->path);
            }
            return 0;
        }
    }
    
    // print some status message in recursive mode
    if(cmd_getoption(a, "recursive")) {
        printf("get: %s\n", res->path);
    }
    
    FILE *fout = isstdout ? stdout : sys_fopen(out, "wb");
    if(!fout) {
        fprintf(stderr, "cannot open output file\n");
        return -1;
    }
    
    int ret = dav_get_content(res, fout, (dav_write_func)fwrite);
    fclose(fout);
    if(ret && strcmp(out, "-")) {
        print_resource_error(res->session, res->path);
        //if(strcmp(out, "-")) {
        //    unlink(out);
        //}
    }
    
    return 0;
}

#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) { 
    DavResource *d = res->res;
    
    if(d->iscollection) {
        fprintf(stderr, "add d: %s\n", res->path);
        return tar_add_dir(tar, res->path, DEFAULT_DIR_MODE, d->lastmodified);
    }
    
    fprintf(stderr, "add f: %s\n", res->path);
    
    // add tar file header
    if(tar_begin_file(tar, res->path, DEFAULT_FILE_MODE, d->contentlength, d->lastmodified)) {
        fprintf(stderr, "TAR Error: %s\n", tar_error2str(tar->error));
        return -1;
    }
    
    if(dav_get_content(d, tar, (dav_write_func)tar_fwrite)) {
        print_resource_error(d->session, d->path);
        return -1;
    }
    
    // download content
    
    return tar_end_file(tar);
}

int cmd_put(CmdArgs *a, DavBool import) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr,
                "Usage: dav %s\n",
                find_usage_str(import ? "import" : "put"));
        return -1;
    }
    DavBool use_stdin = FALSE;
    for(int i=1;i<a->argc;i++) {
        if(!strcmp(a->argv[i], "-")) {
            if(use_stdin) {
                fprintf(stderr, "Error: stdin can only occur once in input list\n");
                return -1;
            } else {
                use_stdin = TRUE;
            }
        }
    }
    
    if(import) {
        ucx_map_cstr_put(a->options, "resursive", "");
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    if(check_encryption_key(a, sn)) {
        // TODO: free
        return -1;
    }
    
    DavBool printfile = FALSE;
    DavBool ignoredirerr = FALSE;
    if(a->argc > 2) {
        printfile = TRUE;
        ignoredirerr = TRUE;
    } else if(ucx_map_cstr_get(a->options, "recursive")) {
        printfile = TRUE;
    }
    
    char *finfo_str = cmd_getoption(a, "finfo");
    uint32_t finfo = 0;
    if(finfo_str) {
        finfo = parse_finfo_settings(finfo_str, NULL);
    }
    
    int ret;
    for(int i=1;i<a->argc;i++) {
        char *file = a->argv[i];
        if(!import) {
            if(!strcmp(file, "-")) {
                FILE *in = stdin;
                ret = put_file(repo, a, sn, path, "stdin", 0, NULL, in, 0);
            } else {
                ret = put_entry(
                        repo,
                        a,
                        sn,
                        path,
                        file,
                        finfo,
                        TRUE,
                        printfile,
                        ignoredirerr); 
            }
        } else {
            ret = put_tar(repo, a, sn, file, path);
        }
        if(ret) {
            break;
        }
    }
    
    free(path);
    dav_session_destroy(sn);
    return ret;
}

int put_entry(
        Repository *repo,
        CmdArgs *a,
        DavSession *sn,
        char *path,
        char *file,
        uint32_t finfo,
        DavBool root,
        DavBool printfile,
        DavBool ignoredirerr)
{
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    struct stat s;
    if(stat(file, &s)) {
        perror("stat");
        fprintf(stderr, "cannot stat file %s\n", file);
        return -1;
    }
    
    int ret = 0;
    if(S_ISDIR(s.st_mode)) {
        if(!recursive) {
            if(ignoredirerr) {
                printf("skip: %s\n", file);
            } else {
                fprintf(
                    stderr,
                    "%s is a directory.\nUse the -R option to upload directories.\n",
                    file);
                return 1;
            }
        }
        
        if(!root) {
            printf("mkcol: %s\n", file);
        }
        
        DIR *dir = opendir(file);
        if(!dir) {
            // error
        }
        struct dirent *entry;
        int nument = 0;
        while((entry = readdir(dir)) != NULL) {
            if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
                continue;
            }
            nument++;
            char *entry_file = util_concat_path(file, entry->d_name);
            char *entry_path = util_concat_path(path, entry->d_name);
            int r = put_entry(
                    repo,
                    a,
                    sn,
                    entry_path,
                    entry_file,
                    finfo,
                    FALSE,
                    printfile,
                    ignoredirerr);
            free(entry_path);
            free(entry_file);
            if(r) {
                ret = 1;
                break;
            }
        }
        closedir(dir);
        
        if(nument == 0) {
            // create empty directory
            DavResource *res = dav_resource_new(sn, path);
            res->iscollection = TRUE;
            if(!dav_exists(res)) {
                if(dav_create(res)) {
                    fprintf(stderr, "Cannot create collection %s\n", path);
                    print_resource_error(sn, res->path);
                    ret = 1;
                }
            }
            dav_resource_free(res);
        }
    } else if(S_ISREG(s.st_mode)) {
        if(printfile) {
            printf("put: %s\n", file);
        }
        
        FILE *in = fopen(file, "rb");
        if(!in) {
            fprintf(stderr, "cannot open input file\n");
            return -1;
        }
        char *filename = util_resource_name(file);
        //path = util_concat_path(path, filename);
        ret = put_file(repo, a, sn, path, filename, finfo, file, in, s.st_size);
        //free(path);
        fclose(in);
    }
    
    return ret;
}

int put_tar(Repository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path) {
    int isstdin = !strcmp(tarfile, "-");
    FILE *in = isstdin ? stdin : fopen(tarfile, "rb");
    if(!in) {
        perror("Cannot open tar file");
        return -1;
    }
    
    DavResource *col = dav_query(sn, "select - from %s", path);
    if(!col) {
        if(sn->error == DAV_NOT_FOUND) {
            col = dav_resource_new(sn, path);
            col->iscollection = TRUE;
            if(dav_create(col)) {
                print_resource_error(sn, path);
                return -1;
            }
        } else {
            print_resource_error(sn, path);
            return -1;
        }
    } else if(!col->iscollection) {
        fprintf(stderr, "%s is not a collection\n", col->href);
        return -1;
    }
    
    
    int ret = 0;
    TarInputStream *tar = tar_inputstream_open(in);
    TarEntry *e = NULL;
    while((e = tar_read_entry(tar)) != NULL) {
        char *newpath = util_concat_path(path, e->path);
        if(e->type == TAR_TYPE_FILE) {
            fprintf(stderr, "put: %s\n", e->path);
            DavResource *res = dav_resource_new(sn, newpath);
            dav_set_content(res, tar, (dav_read_func)tar_fread, (dav_seek_func)tar_seek);
            dav_set_content_length(res, (size_t)e->size);

            if(dav_store(res)) {
                print_resource_error(sn, res->path);
                fprintf(stderr, "Cannot upload file.\n");
                if(sn->errorstr) {
                    fprintf(stderr, "%s\n", sn->errorstr);
                }
                return -1;
            }
            
        } else if(e->type == TAR_TYPE_DIRECTORY) {
            printf("mkcol: %s\n", e->path);
            DavResource *res = dav_resource_new(sn, newpath);
            res->iscollection = TRUE;
            if(!dav_exists(res)) {
                if(dav_create(res)) {
                    fprintf(stderr, "Cannot create collection %s\n", newpath);
                    print_resource_error(sn, res->path);
                    ret = 1;
                    free(newpath);
                    break;
                }
            }
        } else {
            fprintf(stderr, "skip: %s\n", e->path);
        }
        free(newpath);
    }
    if(tar->error != TAR_OK) {
        ret = -1;
    }
    
    if(!isstdin) {
        fclose(in);
    }
    
    return ret;
}

int put_file(
        Repository *repo,
        CmdArgs *a,
        DavSession *sn,
        char *path,
        char *name,
        uint32_t finfo,
        const char *fpath,
        FILE *in,
        off_t len)
{
    DavResource *res = dav_query(sn, "select - from %s", path);
    
    if(!res) {
        if(sn->error == DAV_NOT_FOUND) {
            res = dav_resource_new(sn, path);
            if(dav_create(res)) {
                fprintf(stderr, "Cannot create resource.\n");
                return -1;
            }
        } else {
            print_resource_error(sn, path);
            return -1;
        }
    } else if(res->iscollection) {
        // TODO: free res
        char *newpath = util_concat_path(path, name);
        
        if (!strcmp(path, newpath)) {
            // TODO: free res
            fprintf(stderr, "Cannot put file, because a collection with "
                    "that name already exists.\n");
            free(newpath);
            return -1;
        }
        
        path = newpath;
        res = dav_resource_new(sn, path);
        int ret = put_file(repo, a, sn, res->path, NULL, finfo, fpath, in, len);
        // TODO: free res
        free(newpath);
        return ret;
    }
       
    if(resource_set_finfo(fpath, res, finfo)) {
        fprintf(stderr, "Cannot set finfo: %s.\n", strerror(errno));
    }
    if((finfo & FINFO_XATTR) == FINFO_XATTR) {
        XAttributes *xattr = file_get_attributes(fpath, NULL, NULL);
        if(xattr) {
            resource_set_xattr(res, xattr);
        }
    }
    
    dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)file_seek);
    if(len > 0 && len < 0x7d000000) {
        dav_set_content_length(res, (size_t)len);
    }
    
    if(dav_store(res)) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot upload file.\n");
        if(sn->errorstr) {
            fprintf(stderr, "%s\n", sn->errorstr);
        }
        return -1;
    }
    return 0;
}

static int dav_create_col(DavResource *res) {
    res->iscollection = 1;
    return dav_create(res);
}

static void print_cannoterr(DavSession *sn, const char *message) {
    switch(sn->error) {
        case DAV_UNSUPPORTED_PROTOCOL:
        case DAV_COULDNT_RESOLVE_PROXY:
        case DAV_COULDNT_RESOLVE_HOST:
        case DAV_COULDNT_CONNECT:
        case DAV_TIMEOUT:
        case DAV_SSL_ERROR: break;
        default: fprintf(stderr, "Cannot %s.\n", message);
    }
}

static int cmd_operation_on_resources(CmdArgs* a,
                                      int(*operation)(DavResource*),
                                      const char* command,
                                      const char* message,
                                      DavBool check_key)
{
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str(command));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    int exit_code = -1;
    assert(!!path && !!sn);
    
    if(set_session_config(sn, a)) {
        goto cmd_oponres_exit;
    }
    
    set_session_lock(sn, a);
    
    if(check_key && check_encryption_key(a, sn)) {
        goto cmd_oponres_exit;
    }
    
    DavResource *res = dav_resource_new(sn, path);
    assert(!!res);
    res->iscollection = 1;
    
    if(a->argc == 1) {
        if(operation(res)) {
            print_cannoterr(sn, message);
            print_resource_error(sn, res->path);
            goto cmd_oponres_exit;
        }
    } else {
        for(int i = 1 ; i < a->argc ;++i) {
            DavResource *child = dav_resource_new_child(sn, res, a->argv[i]);
            assert(!!child);
            child->iscollection = 1;
            if(operation(child)) {
                print_cannoterr(sn, message);
                print_resource_error(sn, child->path);
                goto cmd_oponres_exit;
            }
        }
    }
    
    exit_code = 0;
cmd_oponres_exit:
    free(path);
    dav_session_destroy(sn);
    return exit_code;
}

int cmd_remove(CmdArgs *a) {
    return cmd_operation_on_resources(a, dav_delete,
            "remove", "delete resource", FALSE);
}

int cmd_mkdir(CmdArgs *a) {
    return cmd_operation_on_resources(a, dav_create_col,
            "mkdir", "create collection", TRUE);
}

int cmd_move(CmdArgs *a, int cp) {
    const char* actionstr = cp ? "copy" : "move";
    
    if(a->argc != 2) {
        // TODO: change, when creation of multiple dirs is supported
        fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str(actionstr));
        return -1;
    }
    
    char *srcurl = a->argv[0];
    char *srcpath = NULL;
    Repository *srcrepo = url2repo(srcurl, &srcpath);
    
    DavSession *srcsn = connect_to_repo(srcrepo, srcpath, a);
    if(set_session_config(srcsn, a)) {
        return -1;
    }
    set_session_lock(srcsn, a);
    
    DavBool override = cmd_getoption(a, "override") ? true : false;
    
    char *desturl = a->argv[1];
    char *destpath = NULL;
    Repository *destrepo = url2repo(desturl, &destpath);
    
    if(srcrepo == destrepo) {
        DavResource *res = dav_resource_new(srcsn, srcpath);
        int err = cp ? dav_copy_o(res, destpath, override)
                     : dav_move_o(res, destpath, override);
        if(err) {
            print_resource_error(srcsn, res->path);
            fprintf(stderr, "Cannot %s resource.\n", actionstr);
            return -1;
        }
    } else {
        char *srchost = util_url_base(srcrepo->url);
        char *desthost = util_url_base(destrepo->url);     
        if(!strcmp(srchost, desthost)) {
            DavSession *destsn = connect_to_repo(destrepo, destpath, a);
            if(set_session_config(destsn, a)) {
                return -1;
            }
            DavResource *dest = dav_resource_new(destsn, destpath);
            char *desthref = dav_resource_get_href(dest);
            char *desturl = util_get_url(destsn, desthref);
            
            DavResource *res = dav_resource_new(srcsn, srcpath);
            int err = cp ? dav_copyto(res, desturl, override)
                     : dav_moveto(res, desturl, override);
            
            free(desturl);
            dav_session_destroy(destsn);
            
            if(err) {
                print_resource_error(srcsn, res->path);
                fprintf(stderr, "Cannot %s resource.\n", actionstr);
                return -1;
            }
        } else {
            fprintf(stderr, "Cannot %s between different hosts.\n", actionstr);
            return -1;
        }
    }
    
    dav_session_destroy(srcsn);
    
    return 0;
}

int cmd_rename(CmdArgs *a) {
    if(a->argc != 2) {
        // TODO: change, when creation of multiple dirs is supported
        fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("rename"));
        return -1;
    }
    
    char *name = a->argv[1];
    size_t namelen = strlen(name);
    for(size_t i=0;i<namelen;i++) {
        char c = name[i];
        if(c == '/') {
            fprintf(stderr, "Illegal character in name: '/'\n");
            return 1;
        }
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    int ret = 0;
    DavResource *res = dav_get(sn, path, NULL);
    if(res) {
        char *cryptoname = dav_get_string_property_ns(res, DAV_NS, "crypto-name");
        char *cryptokey = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
        if(cryptoname && cryptokey) {
            // encrypted resource with an encrypted name
            // renaming is done by simply setting the crypto-name property
            
            DavKey *key = dav_context_get_key(ctx, cryptokey);
            if(key) {
                // check if a resource with this name already exists
                char *parent = util_parent_path(res->path);
                char *newpath = util_concat_path(parent, name);
                DavResource *testres = dav_resource_new(sn, newpath);
                if(dav_exists(testres)) {
                    fprintf(stderr, "A resource with this name already exists.\nAbort.\n");
                    ret = 1;
                } else {
                    char *crname = aes_encrypt(name, namelen, key);
                    dav_set_string_property_ns(res, DAV_NS, "crypto-name", crname);
                    free(crname);
                    if(dav_store(res)) {
                        print_resource_error(sn, res->path);
                        fprintf(stderr, "Cannot store crypto-name property.\n");
                        ret = 1;
                    }
                }
                free(parent);
                free(newpath);
            } else {
                fprintf(stderr, "Key %s not found.\n", cryptokey);
            }
        } else {
            // rename the resource by changing the url mapping with MOVE
            
            char *parent = util_parent_path(res->href);
            char *new_href = util_concat_path(parent, name);
            char *dest = util_get_url(sn, new_href);
            free(parent);
            free(new_href);
            if(dav_moveto(res, dest, false)) {
                print_resource_error(sn, path);
                fprintf(stderr, "Cannot rename resource.\n");
                ret = 1;
            }
            free(dest);
        }
    } else {
        print_resource_error(sn, path);
        fprintf(stderr, "Cannot rename resource.\n");
        ret = 1;
    }
    
    
    dav_session_destroy(sn);
    free(path);
    return ret;
}


static size_t get_date_header_cb(void *header, int s, int n, void *data) {
    char **date_str = (char**)data;
    
    //printf("header: %.*s\n", s*n, header);
    sstr_t h = sstrn(header, s*n);
    if(sstrprefix(h, S("Date:"))) {
        sstr_t v = sstrsubs(h, 5);
        v = sstrdup(sstrtrim(v));
        *date_str = v.ptr;
    }
    return s*n;
}

int cmd_date(CmdArgs *a) {
    if(a->argc < 1) {
        time_t now = time(NULL);
        struct tm *date = gmtime(&now);
        char str[32];
        putenv("LC_TIME=C");
        size_t len = strftime(str, 32, "%a, %d %b %Y %H:%M:%S GMT\n", date);
        fwrite(str, 1, len, stdout);
    } else if (a->argc == 1) {
        char *url = a->argv[0];
        char *path = NULL;
        Repository *repo = url2repo(url, &path);
        DavSession *sn = connect_to_repo(repo, path, a);

        DavResource *res = dav_resource_new(sn, path);
        char *date = NULL;
        curl_easy_setopt(sn->handle, CURLOPT_HEADERFUNCTION, get_date_header_cb);
        curl_easy_setopt(sn->handle, CURLOPT_WRITEHEADER, &date);
        if(dav_exists(res) && date) {
            printf("%s\n", date);
        } else {
            return -1;
        }
        free(path);
        return 0;
    } else {
        fprintf(stderr, "Too many arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("date"));
        return -1;
    }
    return 0;
}

int cmd_get_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("get-property"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *namespace = cmd_getoption(a, "namespace");
    char *property = a->argv[1];
    
    char *version = cmd_getoption(a, "version");
    
    DavPropName propname;
    if(namespace) {
        propname.ns = namespace;
        propname.name = property;
    } else {
        dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name);
        if(!propname.ns || !propname.name) {
            fprintf(stderr, "Error: unknown namespace prefix\n");
            return -1;
        }
    }
    
    DavResource *res = dav_resource_new(sn, path);
    if(version) {
        DavResource *vres = find_version(res, version);
        if(!vres) {
            fprintf(stderr, "Cannot find version '%s' for resource.\n", version);
            return -1;
        }
        dav_resource_free_all(res);
        res = vres;
    }
    
    if(dav_load_prop(res, &propname, 1)) {
        print_resource_error(sn, res->path);
        return -1;
    }
    free(path);
    
    DavXmlNode *x = dav_get_property_ns(res, propname.ns, propname.name);
    if(!x) {
        fprintf(stderr, "Error: no property value.\n");
        return -1;
    }
    
    if(cmd_getoption(a, "xml")) {
        // print a real xml document on stdout
        printxmldoc(stdout, propname.name, propname.ns, x);
    } else {
        // in this mode a simple string is printed on stdout
        // or simplified and nicely formatted xml is printed on stderr 
        if(dav_xml_isstring(x)) {
            printf("%s\n", dav_xml_getstring(x));
        } else {
            char *str = xml2str(x);
            fprintf(stderr, "%s", str);
            free(str);
        }
    }
    
    return 0;
}

int cmd_set_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("set-property"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    DavResource *res = dav_resource_new(sn, path);
    if(!dav_exists(res)) {
        print_resource_error(sn, res->path);
        return -1;
    }
    
    char *namespace = cmd_getoption(a, "namespace");
    char *xml = cmd_getoption(a, "xml");
    
    char *property = a->argv[1];
    char *value = a->argc > 2 ? a->argv[2] : stdin2str();
    
    int ret = 0;
    if(xml) {
        DavXmlNode *xmlvalue = dav_parse_xml(sn, value, strlen(value));
        if(xmlvalue) {
            if(namespace) {
                dav_set_property_ns(res, namespace, property, xmlvalue->children);
            } else {
                dav_set_property(res, property, xmlvalue->children);
            }
        } else {
            fprintf(stderr, "Error: property content is not valid xml\n");
            ret = 1;
        }
    } else {
        if(namespace) {
            dav_set_string_property_ns(res, namespace, property, value);
        } else {
            dav_set_string_property(res, property, value);
        }
    }
    
    if(ret == 0) {
        if(dav_store(res)) {
            print_resource_error(sn, res->path);
            fprintf(stderr, "Cannot set property.\n");
            ret = -1;
        }
    } else 
    
    free(path);
    dav_session_destroy(sn);
    return ret;
}

int cmd_remove_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("remove-property"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *namespace = cmd_getoption(a, "namespace");
    char *property = a->argv[1];
    
    DavPropName propname;
    if(namespace) {
        propname.ns = namespace;
        propname.name = property;
    } else {
        dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name);
    }
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    dav_remove_property_ns(res, propname.ns, propname.name);
    
    if(dav_store(res)) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot set property.\n");
        ret = -1;
    }
    
    free(path);
    return ret;
}

int cmd_lock(CmdArgs *a) {
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("lock"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    ucx_mempool_reg_destr(sn->mp, path, free);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    time_t timeout = 0;
    char *timeoutstr = cmd_getoption(a, "timeout");
    if(timeoutstr) {
        if(!sstrcasecmp(sstr(timeoutstr), S("infinite"))) {
            timeout = -1;
        } else {
            uint64_t i;
            if(util_strtouint(timeoutstr, &i)) {
                timeout = (time_t)i;
            } else {
                fprintf(stderr, "Error: -T option has invalid value\n");
                return -1;
            }
        }
    }
    
    DavResource *res = dav_resource_new(sn, path);
    if(dav_lock_t(res, timeout)) {
        print_resource_error(sn, res->path);
        return -1;
    }
    
    DavLock *lock = dav_get_lock(sn, res->path);
    if(!lock) {
        // this should really not happen
        // do some damage control
        dav_unlock(res);
        fprintf(stderr, "Error: Cannot find lock token for %s\n", res->path);
        return -1;
    }
    
    printf("%s\n", lock->token);
    
    dav_session_destroy(sn);
    return 0;
}

static char* read_line() {
    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
    int c;
    while((c = getchar()) != EOF) {
        if(c == '\n') {
            break;
        }
        ucx_buffer_putc(buf, c);
    } 
    char *str = NULL;
    sstr_t line = sstrtrim(sstrn(buf->space, buf->size));
    if(line.length != 0) {
        str = sstrdup(line).ptr;
    }
    ucx_buffer_free(buf);
    return str;
}

int cmd_unlock(CmdArgs *a) {
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("unlock"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    ucx_mempool_reg_destr(sn->mp, path, free);
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *locktoken = cmd_getoption(a, "lock");
    if(locktoken) {
        DavLock *lock = dav_create_lock(sn, locktoken, NULL);
        dav_add_collection_lock(sn, "/", lock);
    } else {
        locktoken = read_line();
        if(!locktoken) {
            fprintf(stderr, "No lock token specified.\nAbort.\n");
            return -1;
        }
        DavLock *lock = dav_create_lock(sn, locktoken, NULL);
        dav_add_collection_lock(sn, "/", lock);
        free(locktoken);
    }
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    if(dav_unlock(res)) {
        print_resource_error(sn, res->path);
        ret = -1;
    }
    
    dav_session_destroy(sn);
    return ret;
}

static int count_children(DavResource *res) {
    DavResource *child = res->children;
    int count = 0;
    while(child) {
        count++;
        child = child->next;
    }
    return count;
}

void print_xml_infostr(DavXmlNode *xml) {
    if(xml->children) {
        printf("<%s>...</%s>", xml->name, xml->name);
    } else {
        printf("<%s/>", xml->name);
    }
}

int cmd_info(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", find_usage_str("info"));
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *version = cmd_getoption(a, "version");
    
    DavResource *res = dav_resource_new(sn, path);
    if(version) {
        DavResource *vres = find_version(res, version);
        if(!vres) {
            fprintf(stderr, "Cannot find version '%s' for resource.\n", version);
            return -1;
        }
        dav_resource_free_all(res);
        res = vres;
    }
    
    if(!dav_load(res)) {
        printf("name: %s\n", res->name);
        printf("path: %s\n", res->path);

        char *server = util_url_base(sn->base_url);
        char *url = util_concat_path(server, res->href);
        printf("url:  %s\n", url);
        free(url);
        free(server);

        if(res->iscollection) {
            printf("type: collection\n");
            printf("size: %d\n", count_children(res));
        } else {
            printf("type: resource\n");
            char *len = util_size_str(res->iscollection, res->contentlength);
            printf("size: %s\n", len);
            free(len);
        }

        size_t count = 0;
        DavPropName *properties = dav_get_property_names(res, &count);

        char *last_ns = NULL;
        for(int i=0;i<count;i++) {
            DavPropName p = properties[i];
            if(!last_ns || strcmp(last_ns, p.ns)) {
                printf("\nnamespace: %s\n", p.ns);
                last_ns = p.ns;
            }

            DavXmlNode *xval = dav_get_property_ns(res, p.ns, p.name);
            if(dav_xml_isstring(xval)) {
                sstr_t value = sstr(dav_xml_getstring(xval));
                printf("  %s: %.*s\n", p.name, (int)value.length, value.ptr);
            } else {
                // find some xml elements
                printf("  %s: ", p.name);
                DavXmlNode *x = xval->type == DAV_XML_ELEMENT ? xval : dav_xml_nextelm(xval);
                for(int i=0;i<3;i++) {
                    if(x) {
                        if(i == 2) {
                            printf(" ...");
                            break;
                        } else {
                            print_xml_infostr(x);
                        }
                    } else {
                        break;
                    }
                    x = dav_xml_nextelm(x);
                }
                printf("\n");
                
                
            }
        }

        dav_session_free(sn, properties);
        return 0;
    } else {
        print_resource_error(sn, res->path);
    }
        
    return -1;
}

int cmd_checkout(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", "checkout [-pc] <url>");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    if(dav_checkout(res)) {
        print_resource_error(sn, res->path);
        ret = 1;
    }
    
    return ret;
}

int cmd_checkin(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", "checkin [-pc] <url>");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    if(dav_checkin(res)) {
        print_resource_error(sn, res->path);
        ret = 1;
    }
    
    return ret;
}

int cmd_uncheckout(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", "uncheckout [-pc] <url>");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    if(dav_uncheckout(res)) {
        print_resource_error(sn, res->path);
        ret = 1;
    }
    
    return ret;
}
int cmd_versioncontrol(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", "versioncontrol [-pc] <url>");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a); 
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    if(dav_versioncontrol(res)) {
        print_resource_error(sn, res->path);
        ret = 1;
    }
    
    return ret;
}

int cmd_list_versions(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav %s\n", "list-versions [-pc] <url>");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, path, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    DavResource *res = dav_resource_new(sn, path);
    
    int ret = 0;
    DavResource *list = dav_versiontree(res, NULL);
    if(list) {
        char* longlist = cmd_getoption(a, "list");
        
        DavResource *v = list;
        int addnl = 0;
        while(v) {
            char *vname = dav_get_string_property(v, "D:version-name");
            
            if(longlist) {
                if(addnl) {
                    putchar('\n');
                }
                printf("name: %s\n", vname);
                printf("href: %s\n", v->href);
                addnl = 1;
            } else {
                printf("%s\n", vname);
            }
            v = v->next;
        }
    } else if(sn->error != DAV_OK) {
        print_resource_error(sn, path);
        ret = 1;
    }
    
    return ret;
}

DavResource* find_version(DavResource *res, char *version) {
    DavResource *list = dav_versiontree(res, NULL);
    DavResource *ret = NULL;
    while(list) {
        DavResource *next = list->next;
        if(!ret) {
            char *vname = dav_get_string_property(list, "D:version-name");
            if(vname && !strcmp(vname, version)) {
                ret = list;
            }
        }
        if(list != ret) {
            dav_resource_free(list);
        }
        list = next;
    }
    return ret;
}


char* stdin2str() {
    UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
    size_t size = ucx_stream_copy(stdin, buf, fread, ucx_buffer_write);
    if(size == 0) {
        ucx_buffer_free(buf);
        return NULL;
    } else {
        ucx_buffer_putc(buf, '\0');
        char *str = buf->space;
        free(buf);
        return str;
    }
}

static void xml2str_i(DavXmlNode *node, UcxBuffer *buf, int indent) {
    while(node) {
        if(node->type == DAV_XML_ELEMENT) {
            if(node->children) {
                if(dav_xml_isstring(node->children)) {
                    sstr_t s = sstrtrim(sstr(dav_xml_getstring(node->children)));
                    ucx_bprintf(
                            buf,
                            "%*s<%s>%.*s</%s>\n",
                            indent,
                            "",
                            node->name,
                            (int)s.length,
                            s.ptr,
                            node->name);
                } else {
                    ucx_bprintf(buf, "%*s<%s>\n", indent, "", node->name);
                    xml2str_i(node->children, buf, indent+2);
                    ucx_bprintf(buf, "%*s</%s>\n", indent, "", node->name);
                }
            } else {
                ucx_bprintf(buf, "%*s<%s />", indent, "", node->name);
                ucx_buffer_putc(buf, '\n');
            }
        } else if(node->type == DAV_XML_TEXT) {
            sstr_t val = sstrtrim(sstrn(node->content, node->contentlength));
            if(val.length > 0) {
                ucx_bprintf(buf, "%*.*s", indent, (int)val.length, val.ptr);
            }
        }
        
        node = node->next;
    }
}

char* xml2str(DavXmlNode *node) {
    char *str = malloc(256);
    UcxBuffer *buf = ucx_buffer_new(str, 256, UCX_BUFFER_AUTOEXTEND);
    xml2str_i(node, buf, 0);
    ucx_buffer_putc(buf, 0);
    char *space = buf->space;
    ucx_buffer_free(buf);
    return space;
}

void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) {
    UcxMap *nsmap = ucx_map_new(16);
    
    ucx_map_cstr_put(nsmap, rootns, "x0");
    fprintf(out, "%s", "<?xml version=\"1.0\"?>\n");
    fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns);
    
    dav_print_node(out, (write_func)fwrite, nsmap, content);
    
    fprintf(out, "</x0:%s>\n", root);
    
    // cleanup namespace map
    ucx_map_cstr_remove(nsmap, rootns);
    ucx_map_free_content(nsmap, free);
    ucx_map_free(nsmap);
}


/* ---------- config commands ---------- */

int cmd_add_repository(CmdArgs *args) {
    printf("Each repository must have an unique name.\n");
    char *name = assistant_getcfg("name");
    if(!name) {
        fprintf(stderr, "Abort\n");
        return -1;
    }
    if(get_repository(sstr(name))) {
        fprintf(stderr, "Repository %s already exists.\nAbort\n", name);
        return -1;
    }
    
    printf("\nSpecify the repository base url.\n");
    char *url = assistant_getcfg("url");
    if(!url) {
        fprintf(stderr, "Abort\n");
        return -1;
    }
    
    printf("\nUser for HTTP authentication.\n");
    char *user = assistant_getoptcfg("user");
    
    char *password = NULL;
    if(user) {
        password = assistant_gethiddenoptcfg("password");
    }
    printf("\n");
    
    Repository repo;
    memset(&repo, 0, sizeof(Repository));
    repo.name = name;
    repo.url = url;
    repo.user = user;
    repo.password = password;
    
    int ret = 0;
    if(add_repository(&repo)) {
        fprintf(stderr, "Cannot write config.xml\n");
        ret = -1;
    } else {
        printf("\nAdded repository: %s (%s)\n", name, url);
    }
    
    free(name);
    free(url);
    if(user) {
        free(user);
    }
    if(password) {
        free(password);
    }
    
    return ret;
}

int cmd_remove_repository(CmdArgs *args) {
    if(args->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav remove-repository <name...>\n");
        return -1;
    }
    
    for(int i = 0 ; i < args->argc ; i++) {
        sstr_t reponame = sstr(args->argv[i]);
        Repository* repo = get_repository(reponame);
        if(repo) {
            if(remove_repository(repo)) {
                fprintf(stderr, "Cannot write config.xml\n");
                return -1;
            }
        } else {
            fprintf(stderr, "Repository %s does not exist - skipped.\n",
                    reponame.ptr);
            return -1;
        }
    }
    
    return -1;
}

int cmd_repository_url(CmdArgs *args) {
    if(args->argc != 1) {
        fprintf(stderr, "Too few arguments\n");
        fprintf(stderr, "Usage: dav repository-url [-p] <name>\n");
        return -1;
    }
    
    sstr_t reponame = sstr(args->argv[0]);
    Repository* repo = get_repository(reponame);
    if(repo) {
        sstr_t url = sstr(repo->url);
        if(repo->user && !cmd_getoption(args, "plain")) {
            int hostindex = 0;
            if(sstrprefix(url, S("https://"))) {
                printf("https://");
                hostindex = 8;
            } else if(sstrprefix(url, S("http://"))) {
                printf("http://");
                hostindex = 7;
            }
            printf("%s", repo->user);
            if(repo->password) {
                CURL *curl = curl_easy_init();
                char *pw = curl_easy_escape(
                        curl,
                        repo->password,
                        strlen(repo->password));
                printf(":%s", pw);
                curl_free(pw);
                curl_easy_cleanup(curl);
            }
            putchar('@');
            printf("%.*s", (int)url.length-hostindex, url.ptr+hostindex);
        } else {
            printf("%s", url.ptr);
        }
        if(url.ptr[url.length-1] != '/') {
            putchar('/');
        }
        putchar('\n');
    } else {
        fprintf(stderr, "Repository %s does not exist.\n", reponame.ptr);
        return -1;
    }
    return 0;
}


typedef int(*sscmd_func)(CmdArgs *, PwdStore *, void *userdata);

static int secretstore_after_decrypt(
    CmdArgs *args,
    PwdStore *secrets,
    sscmd_func cb,
    void *userdata);


/*
 * opens the secret store, executes a callback func before and after
 * decryption
 * Aborts if a callback returns 1
 */
static int secretstore_cmd(
    CmdArgs *args,
    DavBool create,
    sscmd_func beforedecrypt,
    sscmd_func afterdecrypt,
    void *userdata)
{
    PwdStore *secrets = get_pwdstore();
    if(!secrets) {
        if(create) {
            secrets = pwdstore_new();
        } else {
            return 1;
        }
    }
    
    int ret = 0;
    if(beforedecrypt) {
        ret = beforedecrypt(args, secrets, userdata);
        if(ret) {
            afterdecrypt = NULL; // exit
        }
    }
    
    if(afterdecrypt) {
        ret = secretstore_after_decrypt(args, secrets, afterdecrypt, userdata);
    }
    
    pwdstore_free(secrets);
    
    return ret;
}

static int secretstore_after_decrypt(
    CmdArgs *args,
    PwdStore *secrets,
    sscmd_func cb,
    void *userdata)
{
    char *master_pw = util_password_input("Master password: ");
    if(!master_pw) {
        fprintf(stderr, "Error: master password required.\nAbort.\n");
        return 1;
    }
    
    int err = pwdstore_setpassword(secrets, master_pw);
    free(master_pw);
    if(err) {
        fprintf(stderr, "Error: Cannot generate key from password.\nAbort.\n");
        return 1;
    }
    
    if(pwdstore_decrypt(secrets)) {
        fprintf(stderr, "Error: Cannot decrypt secrets store.\nAbort.\n");
        return 1;
    }
    
    return cb(args, secrets, userdata);
}

static int cmd_ss_add_user(CmdArgs *Args, PwdStore *secrets, void *userdata) {
    char *id = assistant_getcfg("Credentials identifier");
    if(!id) {
        fprintf(stderr, "Identifier required.\n");
        return 1;
    }
    if(pwdstore_get(secrets, id)) {
        fprintf(stderr, "Credentials with this id already exist.\n");
        return 1;
    }
    
    // get user name and password (required)
    char *user = assistant_getcfg("User");
    char *password = util_password_input("Password: ");
    
    // optionally, get one or more locations
    char *location = NULL;
    UcxList *locations = NULL;
    while((location = assistant_getoptcfg("Location"))) {
        locations = ucx_list_append(locations, location);
    }
    
    int ret = 1;
    if(user && password) {
        pwdstore_put_index(secrets, id, locations);
        pwdstore_put(secrets, id, user, password);
        ret = pwdstore_save(secrets);
        if(ret) {
            fprintf(stderr, "Error: saving srcrets store failed.\n");
        }
    }
    
    if(id) free(id);
    if(user) free(user);
    if(password) free(password);
    
    ucx_list_free_content(locations, free);
    ucx_list_free(locations);
    
    return ret;
}

int cmd_add_user(CmdArgs *args) { 
    return secretstore_cmd(args, TRUE, NULL, cmd_ss_add_user, NULL);
}

/*
 * called before the secret store is decrypted
 */
static int cmd_ss_list_users_bc(CmdArgs *Args, PwdStore *secrets, int *ret) {
    if(secrets->index->count == 0) {
        return 1; // abort, because the secret store is empty
    }
    // set ret to 1, because decrypt could fail and this should be an error
    *ret = 1;
    return 0;
}

/*
 * called after the secret store is decrypted
 */
static int cmd_ss_list_users(CmdArgs *args, PwdStore *secrets, int *ret) {
    *ret = 0;
    
    UcxList *list = secrets->locations;
    for(int i=0;i<2;i++) {
        UCX_FOREACH(elm, list) {
            PwdIndexEntry *index = elm->data;
            PwdEntry *e = ucx_map_cstr_get(secrets->ids, index->id);
            if(e) {
                printf("Id: %s\n", e->id);
                printf("User: %s\n", e->user);
                UCX_FOREACH(loc, index->locations) {
                    char *location = loc->data;
                    printf("Location: %s\n", location);
                }
                printf("\n");
            } else {
                // broken index
                fprintf(stderr,
                        "Warning: id '%s' not in secret store.\n",
                        index->id);
            }
        }
        list = secrets->noloc;
    }
    
    
    return 0;
}

int cmd_list_users(CmdArgs *args) {
    int ret = 0;
    secretstore_cmd(args, FALSE, (sscmd_func)cmd_ss_list_users_bc, (sscmd_func)cmd_ss_list_users, &ret);
    return ret;
}


static int cmd_ss_remove_user(CmdArgs *args, PwdStore *secrets, void *ud) {
    char *id = assistant_getcfg("Credentials identifier");
    if(!id) {
        fprintf(stderr, "Identifier required.\n");
        return 1;
    }
    if(!pwdstore_get(secrets, id)) {
        fprintf(stderr, "Credentials with this id doesn't exist.\n");
        free(id);
        return 1;
    }
    
    pwdstore_remove_entry(secrets, id);
    
    int ret = pwdstore_save(secrets);
    if(ret) {
        fprintf(stderr, "Error: saving srcrets store failed.\n");
    }
    free(id);
    return ret;
}

int cmd_remove_user(CmdArgs *args) {
    return secretstore_cmd(args, FALSE, NULL, cmd_ss_remove_user, NULL);
}

static void secrets_print_user_info(PwdStore *secrets, const char *id) {
    PwdEntry *entry = pwdstore_get(secrets, id);
    if(!entry) {
        return;
    }
    
    PwdIndexEntry *index = ucx_map_cstr_get(secrets->index, id);
    if(!index) {
        return;
    }
    
    printf("Id: %s\n", entry->id);
    printf("User: %s\n", entry->user);
    UCX_FOREACH(elm, index->locations) {
        printf("Location: %s\n", (char*)elm->data);
    }
}

static void secrets_remove_location(PwdIndexEntry *index) {
    if(!index->locations) {
        printf("no locations\n");
        return;
    }
    
    printf("0: abort\n");
    int i = 1;
    UCX_FOREACH(elm, index->locations) {
        printf("%d: %s\n", i, (char*)elm->data);
        i++;
    }
    
    char *input = assistant_getcfg("Choose location");
    if(!input) {
        return;
    }
    
    int64_t ln = 0;
    if(util_strtoint(input, &ln) && (ln >= 0 && ln < i)) {
        if(ln == 0) {
            return;
        } else {
            UcxList *elm = ucx_list_get(index->locations, ln - 1);
            if(elm) {
                free(elm->data);
                index->locations = ucx_list_remove(index->locations, elm);
            }
        }
    } else {
        printf("illegal input, choose 0 - %d\n", i-1);
        secrets_remove_location(index); // try again
    }
}

static int cmd_ss_edit_user(CmdArgs *args, PwdStore *secrets, void *ud) {
    char *id = assistant_getcfg("Credentials identifier");
    if(!id) {
        fprintf(stderr, "Identifier required.\n");
        return 1;
    }
    PwdEntry *entry = pwdstore_get(secrets, id);
    PwdIndexEntry *index = ucx_map_cstr_get(secrets->index, id);
    if(!entry || !index) {
        fprintf(stderr, "Credentials with this id doesn't exist.\n");
        return 1;
    }
    
    secrets_print_user_info(secrets, id);
    
    int save = 0;
    int loop = 1;
    while(loop) {
        printf("\n");
        printf("0: change user name\n");
        printf("1: change password\n");
        printf("2: add location\n");
        printf("3: remove location\n");
        printf("4: list locations\n");
        printf("5: save and exit\n");
        printf("6: exit without saving\n");
        
        char *opt = assistant_getcfg("Choose action");
        if(!opt) {
            break;
        }
        int64_t mnu = 0;
        if(util_strtoint(opt, &mnu) && (mnu >= 0 && mnu <= 6)) {
            printf("\n");
            switch(mnu) {
                case 0: {
                    // change user name
                    char *user = assistant_getcfg("User");
                    if(user) {
                        if(entry->user) {
                            free(entry->user);
                        }
                        entry->user = user;
                    }
                    break;
                }
                case 1: {
                    // change password
                    char *password = util_password_input("Password: ");
                    if(password) {
                        if(entry->password) {
                            free(entry->password);
                        }
                        entry->password = password;
                    }
                    break;
                }
                case 2: {
                    // add location
                    char *location = assistant_getoptcfg("Location");
                    if(location) {
                        index->locations = ucx_list_append(index->locations, location);
                    }
                    break;
                }
                case 3: {
                    // remove location
                    secrets_remove_location(index);
                    break;
                }
                case 4: {
                    // list locations
                    if(!index->locations) {
                        printf("no locations\n");
                    } else {
                        UCX_FOREACH(elm, index->locations) {
                            printf("Location: %s\n", (char*)elm->data);
                        }
                    }
                    break;
                }
                case 5: {
                    // save and exit
                    loop = 0;
                    save = 1;
                    break;
                }
                case 6: {
                    // exit without saving
                    loop = 0;
                    break;
                }
            }
        } else {
            printf("illegal input, choose 0 - 5\n");
        }
        free(opt);
    }
    
    int ret = 0;
    if(save) {
        ret = pwdstore_save(secrets);
        if(ret) {
            fprintf(stderr, "Error: saving srcrets store failed.\n");
        }
    }
    return ret;
}

int cmd_edit_user(CmdArgs *args) {
    return secretstore_cmd(args, FALSE, NULL, cmd_ss_edit_user, NULL);
}


static char** read_args_from_stdin(int *argc) {
    // read stdin into buffer
    UcxBuffer *in = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
    ucx_stream_copy(stdin, in, (read_func)fread, (write_func)ucx_buffer_write);
    
    // split input into lines
    ssize_t count = 0;
    sstr_t *lines = scstrsplit(scstrn(in->space, in->pos), SC("\n"), &count);
    
    char **args = NULL;
    if(count > 0) {
        args = calloc(count, sizeof(char*));
        for(int i=0;i<count;i++) {
            args[i] = lines[i].ptr;
        }
        free(lines);
        
        *argc = count;
    } else {
        *argc = 0;
    }
    
    // cleanup
    ucx_buffer_free(in);
    
    return args;
}

int cmd_complete(CmdArgs *args) {
    if(args->argc != 1) {
        return 1;
    }
    char *index_str = args->argv[0];
    int64_t index = 0;
    if(!util_strtoint(index_str, &index)) {
        return 1;
    }
    
    // The completion bash script passes the input words to stdin
    int comp_argc;
    char **comp_argv = read_args_from_stdin(&comp_argc);
        
    // Try to parse the args
    char *cmd = NULL;
    if(comp_argc > 1) {
        cmd = comp_argv[1];
    }
    CmdArgs *comp_args = cmd_parse_args(comp_argc - 2, comp_argv + 2);
    if(comp_args) {
        // check whether the arglist contains options
        if(comp_args->argc + 2 != comp_argc) {
            // index points to the arg in the raw arglist, however we have to
            // know the index for this item in comp_args->argv
            // any arg that is an option or an option value creates a
            // difference between the two lists
            
            // adjust index to comp_args->argv
            int j = 0;
            for(int i=0;i<comp_argc-2;i++) {
                if(index == i-2) {
                    break;
                }
                
                if(strcmp(comp_argv[i+2], comp_args->argv[j])) {
                    index--;
                } else {
                    j++;
                }
            }
        }
    } else {
        comp_args = NULL;
    }
    
    // generate output for shell completion
    int ret = 1;
    if(comp_args) {
        ret = shell_completion(cmd, comp_args, index);
    }
    
    // cleanup
    cmd_args_free(comp_args);
    free(comp_argv);
    return ret;
    
}

int shell_completion(char *cmd, CmdArgs *args, int index) { 
    if(index == 1) {
        sstr_t prefix = { NULL, 0 };
        if(cmd) {
            prefix = sstr(cmd);
        }
        for(int i=0;;i++) {
            char *str = cmdusageinfo[i];
            if(!str) {
                break;
            }
            int len = (int)strlen(str);
            int maxlen = len;
            for(int w=0;w<len;w++) {
                if(str[w] == ' ') {
                    maxlen = w;
                    break;
                }
            }
            if(prefix.ptr) {
                if(!sstrprefix(sstrn(str, maxlen), prefix)) {
                    continue;
                }
            }
            printf("%.*s\n", (int)maxlen, str);
        }
        return 0;
    }
    
    if(!strcmp(cmd, "date")) {
        return 0;
    }

    // get already typed URL or NULL, if the user hasn't started typing yet
    char *url = args->argc > 0 ? args->argv[0] : NULL;
    
    //printf("index: {%s}\n", args->argv[0]);
    //printf("dav: {%s}\n", args->argv[1]);
    //printf("cmd: {%s}\n", cmd);
    //printf("url: {%s}\n", url);
    
    if(index == 2) {
        // url completion
        return url_completion(args, url);
    } else if (index == 3) {
        if(!strcmp(cmd, "put") || !strcmp(cmd, "import")) {
            // file completion
            return 12;            
        } else if(!strcmp(cmd, "copy") || !strcmp(cmd, "cp") || !strcmp(cmd, "move") || !strcmp(cmd, "mv")) {
            // url completion
            return url_completion(args, url);
        }
    }
    
    return 0;
}

int url_completion(CmdArgs *args, char *u) {   
    sstr_t url;
    url.ptr = u;
    url.length = u ? strlen(u) : 0;
    
    // if the user wants the URL to be quoted, we conform to their wish
    // a null-char is an indicator, that the strings shall not be quoted
    char quote = '\0';
    if(url.length > 0 && (url.ptr[0] == '\'' || url.ptr[0] == '\"' )) {
        quote = url.ptr[0];
        
        // for completing the url, we want to proceed without the quote
        url.ptr++;
        url.length--;
        
        // the user may have also prepared the ending quote, remove it for now
        if (url.ptr[url.length-1] == quote) {
            url.length--;
        }
    }
    
    // repo completion
    int repocomp = 1;
    for(int i=0;i<url.length;i++) {
        if(url.ptr[i] == '/') {
            repocomp = 0;
            break;
        }
    }
    if(repocomp) {
        UcxList *repos = get_repositories();
        UCX_FOREACH(elm, repos) {
            Repository *repo = elm->data;
            if(sstrprefix(sstr(repo->name), url)) {
                if(quote == '\0') {
                    printf("%s/\n", repo->name);
                } else {
                    printf("%c%s/%c\n", quote, repo->name, quote);
                }
            }
            
        }
    } else {
        // url completion
        ucx_map_cstr_put(args->options, "noinput", "");
        
        char *path = NULL;
        Repository *repo = url2repo_s(url, &path);
        DavSession *sn = connect_to_repo(repo, path, args);
        if(!sn) {
            return 0;
        }
        if(set_session_config(sn, args)) {
            return 0;
        }
        
        size_t plen = strlen(path);
        
        sstr_t filter;
        char *lspath = NULL;
        if(path[plen-1] == '/') {
            lspath = strdup(path);
            filter = S("");
        } else {
            lspath = util_parent_path(path);
            filter = sstr(util_resource_name(path));
        }
        
        DavResource *ls = dav_query(sn, "select - from %s order by name", lspath);
        DavResource *elm = ls ? ls->children : NULL;
        while(elm) {
            sstr_t name = sstr(elm->name); 
            if(sstrprefix(name, filter)) {
                int space = 0;
                for(int i=0;i<name.length;i++) {
                    if(name.ptr[i] == ' ') {
                        space = 1;
                        break;
                    }
                }
                
                UcxBuffer *out = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
                ucx_buffer_puts(out, repo->name);
                if(space) {
                    size_t l = strlen(elm->path);
                    for(int i=0;i<l;i++) {
                        // only if we do not quote, we have to escape spaces
                        if(elm->path[i] == ' ' && quote == '\0') {
                            ucx_buffer_puts(out, "\\ ");
                        } else {
                            ucx_buffer_putc(out, elm->path[i]);
                        }
                    }
                } else {
                    ucx_buffer_puts(out, elm->path);
                }
                if(elm->iscollection) {
                    if(out->space[out->pos-1] != '/') {
                        ucx_buffer_putc(out, '/');
                    }
                }
                if (quote == '\0') {
                    printf("%.*s\n", (int)out->pos, out->space);
                } else {
                    printf("%c%.*s%c\n",
                            quote, (int)out->pos, out->space, quote);
                }
                
                ucx_buffer_free(out);
            }
            elm = elm->next;
        }
        
        free(lspath);
        
        dav_session_destroy(sn);
    }
    
    return 10;
}

mercurial