dav/main.c

Sun, 17 Dec 2023 14:25:34 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 14:25:34 +0100
changeset 797
edbb20b1438d
parent 796
81e0f67386a6
child 798
d7f5067a27ce
permissions
-rw-r--r--

[Makefile] fix missing rules preventing dry-runs

We have to support dry-runs, because many IDEs are using
dry-runs to collect build information.

Some rules have dependencies that expect certain files or
directories to be just present. We added respective build
rules which invoke the test program. This way, the behavior
when running make normally is exactly the same, but dry-runs
are also not failing now.

/*
 * 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 <time.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/wait.h>
#include <unistd.h>
#endif
#include <cx/string.h>
#include <cx/utils.h>
#include <cx/printf.h>
#include <cx/hash_map.h>
#include <cx/linked_list.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(CmdArgs *a) {
    
}

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

#ifdef _WIN32

#define strcasecmp _stricmp

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(args);
    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")) {
            cxMapPut(args->options, cx_hash_key_str("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, "set-master-password") || !strcasecmp(cmd, "set-master-pw")) {
            ret = cmd_set_master_password(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) {
    cxstring c = cx_str(cmd);
    for(int i=0;;i++) {
        char *str = cmdusageinfo[i];
        if(!str) {
            break;
        }
        cxstring u = cx_str(str);
        if(cx_strprefix(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 set-master-password\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");
}



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);
        sn->logfunc = dav_verbose_log;
    }
       
    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);
    }
}


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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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) {
        cxMapPut(a->options, cx_hash_key_str("recursive"), "");
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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
    CxList *reslist = cxLinkedListCreateSimple(CX_STORE_POINTERS);
    reslist->simple_destructor = (cx_destructor_func)free_getres;
    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
    CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS);
    cxListInsert(stack, 0, getres);
    while(stack->size > 0) {
        GetResource *g = cxListAt(stack, 0);
        cxListRemove(stack, 0);

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

                cxListInsert(stack, 0, 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 {
            cxListAdd(reslist, g);
        }
    }
    cxListDestroy(stack);
    
    // 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;
    }
    CxIterator i = cxListIterator(reslist);
    cx_foreach(GetResource *, getres, i) {
        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;
        }
    }
    
    cxListDestroy(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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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(DavCfgRepository *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(DavCfgRepository *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) {
        cxMapPut(a->options, cx_hash_key_str("resursive"), "");
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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(cmd_getoption(a, "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;
}

#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif

#ifdef _WIN32
#ifndef S_ISDIR
#define S_ISDIR(mode) ((mode) & _S_IFMT) == _S_IFDIR
#define S_ISREG(mode) ((mode) & _S_IFMT) == _S_IFREG
#endif
#endif


int put_entry(
        DavCfgRepository *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;
    SYS_STAT s;
    if(sys_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);
            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);
                    dav_resource_free(res);
                    return 1;
                }
            }
            dav_resource_free(res);
        }
        
        SYS_DIR dir = sys_opendir(file);
        if(!dir) {
            // error
        }
        SysDirEnt *entry;
        int nument = 0;
        while((entry = sys_readdir(dir)) != NULL) {
            if(!strcmp(entry->name, ".") || !strcmp(entry->name, "..")) {
                continue;
            }
            nument++;
            char *entry_file = util_concat_path(file, entry->name);
            char *entry_path = util_concat_path(path, entry->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;
            }
        }
        sys_closedir(dir);
    } else if(S_ISREG(s.st_mode)) {
        if(printfile) {
            printf("put: %s\n", file);
        }
        
        FILE *in = sys_fopen(file, "rb");
        if(!in) {
            fprintf(stderr, "cannot open input file\n");
            return -1;
        }
        const 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(DavCfgRepository *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(
        DavCfgRepository *repo,
        CmdArgs *a,
        DavSession *sn,
        const char *path,
        const 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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, 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;
    DavCfgRepository *srcrepo = dav_config_url2repo(get_config(), srcurl, &srcpath);
    
    DavSession *srcsn = connect_to_repo(ctx, srcrepo, srcpath, request_auth, a);
    if(set_session_config(srcsn, a)) {
        return -1;
    }
    set_session_lock(srcsn, a);
    
    DavBool override = cmd_getoption(a, "override") ? true : false;
    
    char *desturl = a->argv[1];
    char *destpath = NULL;
    DavCfgRepository *destrepo = dav_config_url2repo(get_config(), 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.value.ptr);
        char *desthost = util_url_base(destrepo->url.value.ptr);     
        if(!strcmp(srchost, desthost)) {
            DavSession *destsn = connect_to_repo(ctx, destrepo, destpath, request_auth, a);
            if(set_session_config(destsn, a)) {
                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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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);
    cxstring h = cx_strn(header, s*n);
    if(cx_strprefix(h, CX_STR("Date:"))) {
        cxstring v = cx_strsubs(h, 5);
        *date_str = cx_strdup(cx_strtrim(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;
        DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
        DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);

        DavResource *res = dav_resource_new(sn, path);
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    cxMempoolRegister(sn->mp, path, free);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    time_t timeout = 0;
    char *timeoutstr = cmd_getoption(a, "timeout");
    if(timeoutstr) {
        if(!cx_strcasecmp(cx_str(timeoutstr), CX_STR("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() {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    int c;
    while((c = getchar()) != EOF) {
        if(c == '\n') {
            break;
        }
        cxBufferPut(&buf, c);
    } 
    char *str = NULL;
    cxstring line = cx_strtrim(cx_strn(buf.space, buf.size));
    if(line.length != 0) {
        str = cx_strdup(line).ptr;
    }
    cxBufferDestroy(&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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    cxMempoolRegister(sn->mp, path, free);
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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)) {
                cxstring value = cx_str(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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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;
    DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path);
    DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a);
    
    if(set_session_config(sn, a)) {
        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() {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    size_t size = cx_stream_copy(stdin, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
    if(size == 0) {
        cxBufferDestroy(&buf);
        return NULL;
    } else {
        cxBufferPut(&buf, '\0');
        return buf.space;
    }
}

static void xml2str_i(DavXmlNode *node, CxBuffer *buf, int indent) {
    while(node) {
        if(node->type == DAV_XML_ELEMENT) {
            if(node->children) {
                if(dav_xml_isstring(node->children)) {
                    cxstring s = cx_strtrim(cx_str(dav_xml_getstring(node->children)));
                    cx_bprintf(
                            buf,
                            "%*s<%s>%.*s</%s>\n",
                            indent,
                            "",
                            node->name,
                            (int)s.length,
                            s.ptr,
                            node->name);
                } else {
                    cx_bprintf(buf, "%*s<%s>\n", indent, "", node->name);
                    xml2str_i(node->children, buf, indent+2);
                    cx_bprintf(buf, "%*s</%s>\n", indent, "", node->name);
                }
            } else {
                cx_bprintf(buf, "%*s<%s />", indent, "", node->name);
                cxBufferPut(buf, '\n');
            }
        } else if(node->type == DAV_XML_TEXT) {
            cxstring val = cx_strtrim(cx_strn(node->content, node->contentlength));
            if(val.length > 0) {
                cx_bprintf(buf, "%*.*s", indent, (int)val.length, val.ptr);
            }
        }
        
        node = node->next;
    }
}

char* xml2str(DavXmlNode *node) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
    xml2str_i(node, &buf, 0);
    cxBufferPut(&buf, 0);
    return buf.space;
}

void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) {
    CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
    nsmap->simple_destructor = free;
    
    cxMapPut(nsmap, cx_hash_key_str(rootns), "x0");
    fprintf(out, "%s", "<?xml version=\"1.0\"?>\n");
    fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns);
    
    dav_print_node(out, (cx_write_func)fwrite, nsmap, content);
    
    fprintf(out, "</x0:%s>\n", root);
    
    // cleanup namespace map
    cxMapRemove(nsmap, cx_hash_key_str(rootns));
    cxMapDestroy(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(dav_config_get_repository(get_config(), cx_str(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");
    
    DavConfig *config = get_config();
    const CxAllocator *a = config->mp->allocator;
    DavCfgRepository *repo = dav_repository_new(config);
    
    repo->name.value = cx_strdup_a(a, cx_str(name));
    repo->url.value = cx_strdup_a(a, cx_str(url));
    dav_repository_set_auth(config, repo, cx_str(user), cx_str(password));
    
    dav_config_add_repository(config, repo);
    
    int ret = 0;
    if(store_config()) {
        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;
    }
    
    DavConfig *config = get_config();
    
    DavBool store = FALSE;
    for(int i = 0 ; i < args->argc ; i++) {
        cxstring reponame = cx_str(args->argv[i]);
        DavCfgRepository* repo = dav_config_get_repository(config, reponame);
        if(repo) {
            dav_repository_remove_and_free(config, repo);
            store = TRUE;
        } else {
            fprintf(stderr, "Repository %s does not exist - skipped.\n",
                    reponame.ptr);
            return -1;
        }
    }
    
    if(store) {
        return store_config();
    } else {
        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;
    }
    
    cxstring reponame = cx_str(args->argv[0]);
    DavCfgRepository* repo = dav_config_get_repository(get_config(), reponame);
    if(repo) {
        cxstring url = cx_strcast(repo->url.value);
        if(repo->user.value.ptr && !cmd_getoption(args, "plain")) {
            int hostindex = 0;
            if(cx_strprefix(url, CX_STR("https://"))) {
                printf("https://");
                hostindex = 8;
            } else if(cx_strprefix(url, CX_STR("http://"))) {
                printf("http://");
                hostindex = 7;
            }
            printf("%.*s", (int)repo->user.value.length, repo->user.value.ptr);
            if(repo->password.value.ptr) {
                CURL *curl = curl_easy_init();
                char *pw = curl_easy_escape(
                        curl,
                        repo->password.value.ptr,
                        repo->password.value.length);
                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;
    CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
    locations->simple_destructor = free;
    while((location = assistant_getoptcfg("Location"))) {
        cxListAdd(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);
    
    cxListDestroy(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->size == 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;
    
    CxList *list = secrets->locations;
    for(int i=0;i<2;i++) {
        if(list) {
            CxIterator i = cxListIterator(list);
            cx_foreach(PwdIndexEntry*, index, i) {
                PwdEntry *e = cxMapGet(secrets->ids, cx_hash_key_str(index->id));
                if(e) {
                    printf("Id: %s\n", e->id);
                    printf("User: %s\n", e->user);
                    if(index->locations) {
                        CxIterator loc_iter = cxListIterator(index->locations);
                        cx_foreach(char *, location, loc_iter) {
                            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 = cxMapGet(secrets->index, cx_hash_key_str(id));
    if(!index) {
        return;
    }
    
    printf("Id: %s\n", entry->id);
    printf("User: %s\n", entry->user);
    if(index->locations) {
        CxIterator loc_iter = cxListIterator(index->locations);
        cx_foreach(char *, location, loc_iter) {
            printf("Location: %s\n", location);
        }
    }
}

static void secrets_remove_location(PwdIndexEntry *index) {
    if(!index->locations || index->locations->size == 0) {
        printf("no locations\n");
        return;
    }
    
    printf("0: abort\n");
    int i = 1;
    CxIterator loc_iter = cxListIterator(index->locations);
    cx_foreach(char *, location, loc_iter) {
        printf("%d: %s\n", i, location);
        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 {
            char *location = cxListAt(index->locations, ln - 1);
            if(location) {
                free(location);
                cxListRemove(index->locations, ln - 1);
            }
        }
    } 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 = cxMapGet(secrets->index, cx_hash_key_str(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) {
                        cxListAdd(index->locations, location);
                    }
                    break;
                }
                case 3: {
                    // remove location
                    secrets_remove_location(index);
                    break;
                }
                case 4: {
                    // list locations
                    if(!index->locations || index->locations->size == 0) {
                        printf("no locations\n");
                    } else {
                        CxIterator i = cxListIterator(index->locations);
                        cx_foreach(char *, location, i) {
                            printf("Location: %s\n", location);
                        }
                    }
                    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 int cmd_ss_set_master_pw(CmdArgs *args, PwdStore *secrets, void *ud) {
    char *new_master_pw = util_password_input("New master password: ");
    int ret = pwdstore_setpassword(secrets, new_master_pw);
    if(ret) {
        fprintf(stderr, "Error: failed to set new master password\n");
    }
    
    ret = pwdstore_save(secrets);
    if(ret) {
        fprintf(stderr, "Error: saving srcrets store failed.\n");
    }
    return ret;
}

int cmd_set_master_password(CmdArgs *args) {
    return secretstore_cmd(args, FALSE, NULL, cmd_ss_set_master_pw, NULL);
}

static char** read_args_from_stdin(int *argc) {
    // read stdin into buffer
    CxBuffer *in = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cx_stream_copy(stdin, in, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
    
    // split input into lines
    ssize_t count = 0;
    cxmutstr *lines; 
    count = cx_strsplit_ma(cxDefaultAllocator, cx_mutstrn(in->space, in->pos), CX_STR("\n"), INT_MAX, &lines);
    
    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
    cxBufferFree(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) {
        cxstring prefix = { NULL, 0 };
        if(cmd) {
            prefix = cx_str(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(!cx_strprefix(cx_strn(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) {   
    cxstring 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) {
        DavConfig *config = get_config();
        for(DavCfgRepository *repo=config->repositories; repo; repo=repo->next) {
            if(cx_strprefix(cx_strcast(repo->name.value), url)) {
                if(quote == '\0') {
                    printf("%s/\n", repo->name.value.ptr);
                } else {
                    printf("%c%s/%c\n", quote, repo->name.value.ptr, quote);
                }
            }
            
        }
    } else {
        // url completion
        cxMapPut(args->options, cx_hash_key_str("noinput"), "");
        
        char *path = NULL;
        DavCfgRepository *repo = dav_config_url2repo(get_config(), url.ptr, &path);
        DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, args);
        if(!sn) {
            return 0;
        }
        if(set_session_config(sn, args)) {
            return 0;
        }
        
        size_t plen = strlen(path);
        
        cxstring filter;
        char *lspath = NULL;
        if(path[plen-1] == '/') {
            lspath = strdup(path);
            filter = CX_STR("");
        } else {
            lspath = util_parent_path(path);
            filter = cx_str(util_resource_name(path));
        }
        
        DavResource *ls = dav_query(sn, "select - from %s order by name", lspath);
        DavResource *elm = ls ? ls->children : NULL;
        while(elm) {
            cxstring name = cx_str(elm->name); 
            if(cx_strprefix(name, filter)) {
                int space = 0;
                for(int i=0;i<name.length;i++) {
                    if(name.ptr[i] == ' ') {
                        space = 1;
                        break;
                    }
                }
                
                CxBuffer *out = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
                cxBufferWrite(repo->name.value.ptr, repo->name.value.length, 1, out);
                if(space) {
                    size_t l = strlen(elm->path);
                    for(int i=0;i<l;i++) {
                        // only if we do not quote, we have to escape
                        char nextc = elm->path[i];
                        if(quote == '\0' && NULL != strchr(
                                "!\"#$&'()*,;<>?[\\]^`{|}~ ", nextc)) {
                            cxBufferPut(out, '\\');
                        }
                        cxBufferPut(out, nextc);
                    }
                } else {
                    cxBufferPutString(out, elm->path);
                }
                if(elm->iscollection) {
                    if(out->space[out->pos-1] != '/') {
                        cxBufferPut(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);
                }
                
                cxBufferFree(out);
            }
            elm = elm->next;
        }
        
        free(lspath);
        
        dav_session_destroy(sn);
    }
    
    return 10;
}

mercurial