dav/main.c

Tue, 12 Dec 2017 23:58:54 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 12 Dec 2017 23:58:54 +0100
branch
v1.1
changeset 343
b9c6e0be5774
parent 342
0b03aa8fb838
permissions
-rw-r--r--

fixes infinite loop when trying to overwrite a collection with a regular file

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

#include <libidav/utils.h>
#include <libidav/crypto.h>
#include <libidav/session.h>
#include "config.h"
#include "error.h"
#include "assistant.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>
void test() {

}

int main(int argc, char **argv) {
    if(argc < 2) {
        fprintf(stderr, "Missing command\n");
        print_usage(argv[0]);
        return -1;
    }
    
    char *cmd = argv[1];
    CmdArgs *args = cmd_parse_args(argc - 2, argv + 2);
    if(!args) {
        print_usage(argv[0]);
        cmd_args_free(args);
        return -1;
    }
    
    xmlGenericErrorFunc fnc = xmlerrorfnc;
    initGenericErrorDefaultFunc(&fnc);
    ctx = dav_context_new();
    dav_add_namespace(ctx, "apache", "http://apache.org/dav/props/");
    int cfgret = load_config(ctx);
    int ret = EXIT_FAILURE;
    printxmlerror = 0;
#ifdef DO_THE_TEST
    test();
    return 0;
#endif
    if(!strcmp(cmd, "check") || !strcmp(cmd, "check-config")) {
        if(!cfgret) {
            fprintf(stdout, "Configuration OK.\n");
            ret = EXIT_SUCCESS;
        } else {
            /* no output, the warnings are written by load_config */
            ret = EXIT_FAILURE;
        }
    } else if(!cfgret) {
        if(!strcasecmp(cmd, "list") || !strcasecmp(cmd, "ls")) {
            ret = cmd_list(args);
        } else if(!strcasecmp(cmd, "get")) {
            ret = cmd_get(args);
        } else if(!strcasecmp(cmd, "put")) {
            ret = cmd_put(args);
        } 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, "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, "add-repository")
                || !strcasecmp(cmd, "add-repo")) {
            ret = cmd_add_repository(args);
        } else if(!strcasecmp(cmd, "list-repositories")
                || !strcasecmp(cmd, "list-repos")) {
            ret = list_repositories();
        } else if(!strcasecmp(cmd, "version") || !strcasecmp(cmd, "-version")
                || !strcasecmp(cmd, "--version")) {
            fprintf(stderr, "dav %s\n", DAV_VERSION);
        } else {
            print_usage(argv[0]);
        }
    }
    
    dav_context_destroy(ctx);
    cmd_args_free(args);
    free_config();
    xmlCleanupParser();
    curl_global_cleanup();
    
    return ret;
}

void print_usage(char *cmd) {
    fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd);
    fprintf(stderr, "Commands:\n");
    fprintf(stderr, "        list [-altdepcR] [-u <date>] <url>\n");
    fprintf(
            stderr,
            "        get [-pcR] [-o <file>] [-u <date>] <url>\n");
    fprintf(stderr, "        put [-pcR] [-k <key>] [-L <lock>] <url> <file>\n");
    fprintf(stderr, "        mkdir [-pc] [-k <key>] [-L <lock>] <url>\n");
    fprintf(stderr, "        remove [-pc] [-L <lock>] <url>\n");
    fprintf(stderr, "        copy [-pcO] [-L <lock>] <url> <url>\n");
    fprintf(stderr, "        move [-pcO] [-L <lock>] <url> <url>\n");
    fprintf(
            stderr,
            "        get-property [-pc] [-n <uri>] <url> <property>\n");
    fprintf(
            stderr,
            "        set-property [-pc] [-L <lock>] [-n <uri>] <url> <property> [value]\n");
    fprintf(
            stderr,
            "        remove-property [-pc] [-n <uri>] <url> <property>\n");
    fprintf(stderr, "        lock [-pc] [-T timeout] <url>\n");
    fprintf(stderr, "        unlock [-pc] [-L <lock>] <url>\n");
    fprintf(stderr, "        info [-pc] <url>\n");
    fprintf(stderr, "        date [url]\n");
    fprintf(stderr, "\n");
    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, "        -o <file>  Write output to file\n");
    fprintf(
            stderr,
            "        -u <date>  "
            "Get resources which are modified since the specified date\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, "        -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, "Config commands:\n");
    fprintf(stderr, "        add-repository\n");
    fprintf(stderr, "        list-repositories\n");
    fprintf(stderr, "        check-config\n");
    fprintf(stderr, "\n");
    fprintf(stderr,
            "Instead of an url you can pass a repository name "
            "with an optional path:\n");
    fprintf(stderr, "        <repository>/path/\n");
    fprintf(stderr, "\n");
}

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

static Repository* url2repo(char *url, char **path) {
    size_t ulen = strlen(url);
    *path = NULL;
    
    int s;
    if(ulen > 7 && !strncasecmp(url, "http://", 7)) {
        s = 7;
    } else if(ulen > 8 && !strncasecmp(url, "https://", 8)) {
        s = 8;
    } else {
        s = 1;
    }
    
    sstr_t r = sstr(url);
    sstr_t p = sstr("/");
    for(int i=s;i<ulen;i++) {
        char c = url[i];
        if(c == '/') {
            r = sstrn(url, i);
            p = sstrsubs(sstr(url), i);
            if(p.length == 0) {
                p = sstrn("/", 1);
            }
            break;
        }
    }
    
    Repository *repo = get_repository(r);
    if(repo) {
        *path = sstrdup(p).ptr;
    } else {
        repo = calloc(1, sizeof(Repository));
        repo->name = strdup("");
        repo->decrypt_content = true;
        repo->verification = true;
        repo->authmethods = CURLAUTH_BASIC;
        if(url[ulen-1] == '/') {
            repo->url = strdup(url);
            *path = strdup("/");
        } else if (strchr(url, '/')) {
            repo->url = util_parent_path(url);
            // TODO: check/fix
            *path = strdup(util_resource_name(url)-1);
        } else {
            repo->url = strdup(url);
            *path = strdup("/");
        }
    }
    
    return repo;
}

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

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

static DavSession* connect_to_repo(Repository *repo, CmdArgs *a) {
    DavSession *sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
    sn->flags = get_repository_flags(repo);
    sn->key = dav_context_get_key(ctx, repo->default_key);
    curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
    curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
    if(repo->cert) {
        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert);
    }
    if(!repo->verification || cmd_getoption(a, "insecure")) {
        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
    }
    return sn;
}

#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");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, 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 = -1;
    DavResource *ls;
    while(ret != 0) {
        ls = dav_query(
                sn,
                date ? LIST_QUERY_ORDER_BY_DATE : LIST_QUERY_ORDER_BY_NAME,
                path,
                depth,
                t);
        
        if(!ls) {
            if(sn->error == DAV_UNAUTHORIZED) {
                if(request_auth(repo, sn, a)) {
                    continue;
                }
            }
            print_resource_error(sn, path);
            break;
        }
        
        // 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;
        }
        
        // leave loop
        ret = 0;
    }
    
    free(path);
    //free(base);
    
    dav_session_destroy(sn);
    
    return ret;
}

static char* ls_date_str(time_t tm) {
    struct tm t;
    struct tm n;
    time_t now = time(NULL);
#ifdef _WIN32
    memcpy(&t, localtime(&tm), sizeof(struct tm));
    memcpy(&n, localtime(&now), sizeof(struct tm));
#else
    localtime_r(&tm, &t);
    localtime_r(&now, &n);
#endif /* _WIN32 */
    char *str = malloc(16);
    if(t.tm_year == n.tm_year) {
        strftime(str, 16, "%b %d %H:%M", &t);
    } else {
        strftime(str, 16, "%b %d  %Y", &t);
    }
    return str;
}

static char* ls_size_str(DavResource *res) {
    char *str = malloc(16);
    uint64_t size = res->contentlength;
    
    if(res->iscollection) {
        str[0] = '\0'; // currently no information for collections
    } else if(size < 0x400) {
        snprintf(str, 16, "%" PRIu64 " bytes", size);
    } else if(size < 0x100000) {
        float s = (float)size/0x400;
        int diff = (s*100 - (int)s*100);
        if(diff > 90) {
            diff = 0;
            s += 0.10f;
        }
        if(size < 0x2800 && diff != 0) {
            // size < 10 KiB
            snprintf(str, 16, "%.1f KiB", s);
        } else {
            snprintf(str, 16, "%.0f KiB", s);
        }
    } else if(size < 0x40000000) {
        float s = (float)size/0x100000;
        int diff = (s*100 - (int)s*100);
        if(diff > 90) {
            diff = 0;
            s += 0.10f;
        }
        if(size < 0xa00000 && diff != 0) {
            // size < 10 MiB
            snprintf(str, 16, "%.1f MiB", s);
        } else {
            size /= 0x100000;
            snprintf(str, 16, "%.0f MiB", s);
        }
    } else if(size < 0x1000000000ULL) {
        float s = (float)size/0x40000000;
        int diff = (s*100 - (int)s*100);
        if(diff > 90) {
            diff = 0;
            s += 0.10f;
        }
        if(size < 0x280000000 && diff != 0) {
            // size < 10 GiB
            snprintf(str, 16, "%.1f GiB", s);
        } else {
            size /= 0x40000000;
            snprintf(str, 16, "%.0f GiB", s);
        }
    } else {
        size /= 1024;
        float s = (float)size/0x40000000;
        int diff = (s*100 - (int)s*100);
        if(diff > 90) {
            diff = 0;
            s += 0.10f;
        }
        if(size < 0x280000000 && diff != 0) {
            // size < 10 TiB
            snprintf(str, 16, "%.1f TiB", s);
        } else {
            size /= 0x40000000;
            snprintf(str, 16, "%.0f TiB", s);
        }
    }
    return str;
}

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_property_ns(
            res,
            DAV_NS,
            "crypto-key");
    if(keyprop) {
        flags[1] = 'c';
    }
    
    if(cmd_getoption(a, "extended")) {
        flags[6] = '\0';
        if(dav_get_property(res, "D:lockdiscovery")) {
            flags[2] = 'l';
        }
        char *executable = dav_get_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 = ls_date_str(res->lastmodified);
    char *size = ls_size_str(res);
    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;
        }
    }
}

int cmd_get(CmdArgs *a) {
    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");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *update = cmd_getoption(a, "update");
    time_t t = -1;
    if(update) {
        t = util_parse_lastmodified(update);
    }
    
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    DavResource *res;
    
    int depth = recursive ? -1 : 1;
    for(int i=0;i<2;i++) {
        res = dav_query(
                sn,
                "select - from %s with depth = %d where iscollection or lastmodified > %t",
                path,
                depth,
                t);
        if(!res && sn->error == DAV_UNAUTHORIZED) {
            if(request_auth(repo, sn, a)) {
                continue;
            }
        }
        break;
    }
    
    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;
    }
    
    /*
     * determine the output file
     * use stdout if the output file is -
     */
    char *outfile = cmd_getoption(a, "output");
    if(!outfile) {
        if(res->iscollection) {
            outfile = "";
        } else {
            outfile = res->name;
        }
    } else if(res->iscollection && !strcmp(outfile, "-")) {
        fprintf(
                stderr,
                "Cannot write output to stdout "
                "if the requested resource is a collection.\n");
        return -1;
    }
    
    int ret = get_resource(repo, res, a, outfile);
    
    free(path);
    return ret;
}

int get_resource(Repository *repo, DavResource *res, CmdArgs *a, char *out) {
    size_t outlen = strlen(out);
    
    if(res->iscollection) {
        printf("get: %s\n", res->path);
        
        // create directory
        if(outlen != 0) {
            mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
            int ret = util_mkdir(out, mode);
            if(ret != 0 && errno != EEXIST) {
                return 1;
            }
        }
        
        DavResource *child = res->children;
        while(child) {
            char *newout = outlen > 0 ?
                    util_concat_path(out, child->name) : child->name;
            int ret = get_resource(repo, child, a, newout);
            if(outlen > 0) {
                free(newout);
            }
            if(ret) {
                return 1;
            }
            child = child->next;
        }
        
        return 0;
    } else if(cmd_getoption(a, "structure")) {
        // download only directory structure
        // this is a hidden feature and will be replaced in the future
        return 0;
    }
    
    // print some status message in recursive mode
    if(cmd_getoption(a, "recursive")) {
        printf("get: %s\n", res->path);
    }
     
    FILE *fout = !strcmp(out, "-") ? stdout : 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;
}

int cmd_put(CmdArgs *a) {
    if(a->argc != 2) {
        // TODO: change, when put supports multiple files (however it should do)
        fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many");
        return -1;
    }
    
    char *url = a->argv[0];
    char *file = a->argv[1];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    // 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);
            // TODO: free
            return -1;
        }
    }
    
    // if encryption is requested, but we still don't know the key, abort
    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;
    }
    
    int ret;
    if(!strcmp(file, "-")) {
        FILE *in = stdin;
        ret = put_file(repo, a, sn, path, "stdin", in, 0);
    } else {
        ret = put_entry(repo, a, sn, path, file, TRUE); 
    }
    
    free(path);
    return ret;
}

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

int put_file(Repository *repo, CmdArgs *a, DavSession *sn, char *path, char *name, FILE *in, off_t len) {
    DavResource *res = NULL;
    for(int i=0;i<2;i++) {
        res = dav_query(sn, "select - from %s", path);
        if(!res && sn->error == DAV_UNAUTHORIZED) {
            if(request_auth(repo, sn, a)) {
                continue;
            }
        }
        break;
    }
    
    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);
        free(newpath);
        int ret = put_file(repo, a, sn, res->path, NULL, in, len);
        // TODO: free res
        return ret;
    }
    
    dav_set_content(res, in, (dav_read_func)fread);
    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;
}


int cmd_remove(CmdArgs *a) {
    if(a->argc != 1) {
        // TODO: change, when removal of multiple files is supported
        fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    DavResource *res = dav_resource_new(sn, path);
    if(!res) {
        fprintf(stderr, "error\n");
        return -1;
    }
    
    int err = 0;
    for(int i=0;i<2;i++) {
        err = dav_delete(res);
        if(err && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
            continue;
        } else {
            break;
        }
    }
    
    if(err) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot delete resource.\n");
        return -1;
    }
    
    free(path);
    return 0;
}

int cmd_mkdir(CmdArgs *a) {
    if(a->argc != 1) {
        // TODO: change, when creation of multiple dirs is supported
        fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    // 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);
            // TODO: free
            return -1;
        }
    }
    
    DavResource *res = dav_resource_new(sn, path);
    if(!res) {
        fprintf(stderr, "error\n");
        return -1;
    }
    res->iscollection = 1;
    
    int err = 0;
    for(int i=0;i<2;i++) {
        err = dav_create(res);
        if(err && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
            continue;
        } else {
            break;
        }
    }
    
    if(err) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot create collection.\n");
        return -1;
    }
    
    free(path);
    return 0;
}

int cmd_move(CmdArgs *a, int cp) {
    if(a->argc != 2) {
        // TODO: change, when creation of multiple dirs is supported
        fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many");
        return -1;
    }
    
    char *url1 = a->argv[0];
    char *path1 = NULL;
    Repository *repo1 = url2repo(url1, &path1);
    
    char *url2 = a->argv[1];
    char *path2 = NULL;
    Repository *repo2 = url2repo(url2, &path2);
    
    DavSession *sn = connect_to_repo(repo1, a);
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    DavBool override = cmd_getoption(a, "override") ? true : false;
    
    if(repo1 == repo2) {
        DavResource *res = dav_resource_new(sn, path1);
        int err = cp ? dav_copy_o(res, path2, override)
                     : dav_move_o(res, path2, override);
        if(err) {
            print_resource_error(sn, res->path);
            fprintf(stderr, "Cannot %s resource.\n", cp ? "copy" : "move");
            return -1;
        }
    } else {
        char *server1 = util_url_base(repo1->url);
        char *server2 = util_url_base(repo2->url);     
        if(!strcmp(server1, server2)) {
            DavSession *sn2 = connect_to_repo(repo2, a);
            if(set_session_config(sn2, a)) {
                return -1;
            }
            DavResource *dest = dav_resource_new(sn2, path2);
            char *desthref = dav_resource_get_href(dest);
            char *desturl = util_get_url(sn2, desthref);
            
            DavResource *res = dav_resource_new(sn, path1);
            int err = cp ? dav_copyto(res, desturl, override)
                     : dav_moveto(res, desturl, override);
            
            free(desturl);
            dav_session_destroy(sn2);
            
            if(err) {
                print_resource_error(sn, res->path);
                fprintf(stderr, "Cannot %s resource.\n", cp ? "copy" : "move");
                return -1;
            }
        } else {
            fprintf(stderr, "Copy or Move not supported for different hosts.\n");
            return -1;
        }
    }
    
    dav_session_destroy(sn);
    
    return 0;
}


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

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

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

int cmd_get_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, 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);
        if(!propname.ns || !propname.name) {
            fprintf(stderr, "Error: unknown namespace prefix\n");
            return -1;
        }
    }
    
    DavResource *res = dav_resource_new(sn, path);
    for(int i=0;i<2;i++) {     
        if(dav_load_prop(res, &propname, 1)) {
            if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
                continue;
            }
            print_resource_error(sn, res->path);
            return -1;
        }
        break;
    }
    
    char *value = dav_get_property_ns(res, propname.ns, propname.name);
    if(!value) {
        fprintf(stderr, "Error: no property value.\n");
        return -1;
    }
    
    int ret = 0;
    if(value) {
        printf("%s\n", value);
    } else {
        // TODO: correct error message
        fprintf(stderr, "Error: property not found.\n");
        ret = -1;
    }
    
    free(path);
    return ret;
}

int cmd_set_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    set_session_lock(sn, a);
    
    DavResource *res = dav_resource_new(sn, path);
    for(int i=0;i<2;i++) {
        if(!dav_exists(res)) {
            if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
                continue;
            }
            print_resource_error(sn, res->path);
            return -1;
        }
    }
    
    char *namespace = cmd_getoption(a, "namespace");
    char *property = a->argv[1];
    char *value = a->argc > 2 ? a->argv[2] : stdin2str();
    
    if(namespace) {
        dav_set_property_ns(res, namespace, property, value);
    } else {
        dav_set_property(res, property, value);
    }
    
    int ret = 0;
    for(int i=0;i<2;i++) {
        if(dav_store(res)) {
            if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
                continue;
            }
            print_resource_error(sn, res->path);
            fprintf(stderr, "Cannot set property.\n");
            ret = -1;
        }
        break;
    }
    
    free(path);
    return ret;
}

int cmd_remove_property(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, 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);
    for(int i=0;i<2;i++) {     
        if(dav_store(res)) {
            if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
                continue;
            }
            print_resource_error(sn, res->path);
            fprintf(stderr, "Cannot set property.\n");
            ret = -1;
        }
        break;
    }
    
    free(path);
    return ret;
}

int cmd_lock(CmdArgs *a) {
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    ucx_mempool_reg_destr(sn->mp, path, free);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    time_t timeout = 0;
    char *timeoutstr = cmd_getoption(a, "timeout");
    if(timeoutstr) {
        if(!sstrcasecmp(sstr(timeoutstr), S("infinite"))) {
            timeout = -1;
        } else {
            timeout = (time_t)atoi(timeoutstr); // TODO: use strtol
        }
    }
    
    DavResource *res = dav_resource_new(sn, path);
    for(int i=0;i<2;i++) {
        if(!dav_lock_t(res, timeout)) {
            break;
        }
        if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
            continue;
        }
        print_resource_error(sn, res->path);
        return -1;
    }
    
    DavLock *lock = dav_get_lock(sn, res->path);
    if(!lock) {
        // this should really not happen
        // do some damage control
        dav_unlock(res);
        fprintf(stderr, "Error: Cannot find lock token for %s\n", res->path);
        return -1;
    }
    
    printf("%s\n", lock->token);
    
    dav_session_destroy(sn);
    return 0;
}

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

int cmd_unlock(CmdArgs *a) {
    if(a->argc != 1) {
        fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(url2repo(url, &path), a);
    ucx_mempool_reg_destr(sn->mp, path, free);
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    char *locktoken = cmd_getoption(a, "lock");
    if(locktoken) {
        DavLock *lock = dav_create_lock(sn, locktoken, NULL);
        dav_add_collection_lock(sn, "/", lock);
    } else {
        locktoken = read_line();
        if(!locktoken) {
            fprintf(stderr, "No lock token specified.\nAbort.\n");
            return -1;
        }
        DavLock *lock = dav_create_lock(sn, locktoken, NULL);
        dav_add_collection_lock(sn, "/", lock);
        free(locktoken);
    }
    
    int ret = 0;
    DavResource *res = dav_resource_new(sn, path);
    for(int i=0;i<2;i++) {
        if(!dav_unlock(res)) {
            break;
        }
        if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
            continue;
        }
        print_resource_error(sn, res->path);
        ret = -1;
        break;
    }
    
    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;
}

int cmd_info(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    char *url = a->argv[0];
    char *path = NULL;
    Repository *repo = url2repo(url, &path);
    DavSession *sn = connect_to_repo(repo, a);
    
    if(set_session_config(sn, a)) {
        return -1;
    }
    
    DavResource *res = dav_resource_new(sn, path);
    for(int i=0;i<2;i++) {
        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 = ls_size_str(res);
                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;
                }

                sstr_t value = sstr(dav_get_property_ns(res, p.ns, p.name));
                value = sstrtrim(value);
                printf("  %s: %.*s\n", p.name, (int)value.length, value.ptr);
            }

            dav_session_free(sn, properties);
            return 0;
        } else {
            if(i == 0 && sn->error == DAV_UNAUTHORIZED && request_auth(repo, sn, a)) {
                continue;
            }
            print_resource_error(sn, res->path);
            break;
        }
    }
    return -1;
}


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


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

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

mercurial