dav/main.c

Thu, 22 Aug 2013 14:27:58 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 22 Aug 2013 14:27:58 +0200
changeset 31
59cdf7b7316f
parent 30
9a5a7a513a96
child 32
c9d37bb97ea8
permissions
-rw-r--r--

fix include

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 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 <errno.h>
#include <unistd.h>
#include <time.h>
#include <libxml/xmlerror.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ucx/string.h>
#include <dirent.h>


#include "utils.h"
#include "config.h"
#include "crypto.h"
#include "main.h"

#include "davql.h"

static DavContext *ctx;

void xmlerrorfnc(void * c, const char * msg, ... ) {
    // nothing
}

int main(int argc, char **argv) {
    xmlGenericErrorFunc fnc = xmlerrorfnc;
    initGenericErrorDefaultFunc(&fnc);
    load_config();
    ctx = dav_context_new();
    dav_add_namespace(ctx, "U", "http://www.uap-core.de/");
    
    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]);
        return -1;
    }
    
    int ret = -1;
    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, "date")) {
        ret = cmd_date(args);
    } else {
        print_usage(argv[0]);
    }
    dav_context_destroy(ctx);
    
    return ret;
}

void print_usage(char *cmd) {
    fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd);
    fprintf(stderr, "Commands:\n");
    fprintf(stderr, "list [-altR] [-u <date>] <url>\n");
    fprintf(
            stderr,
            "        get [-pR] [-k <key>] [-o <file>] [-u <date>] <url>\n");
    fprintf(stderr, "        put [-p] [-k <key>] <url> <file>\n");
    fprintf(stderr, "        mkdir <url>\n");
    fprintf(stderr, "        remove <url>\n");
    fprintf(stderr, "        date [url]\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Options:\n");
    fprintf(stderr,
            "        -k <key>   Key to use for encryption or decryption\n");
    fprintf(stderr, "        -p         Don't encrypt or decrypt files\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, "\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");
}

void url_get_parts(char *url, char **root, char **path) {
    size_t ulen = strlen(url);
    *root = NULL;
    *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;
    }
    
    for(int i=s;i<ulen;i++) {
        char c = url[i];
        if(c == '/') {
            sstr_t r = sstrn(url, i);
            sstr_t p = sstrsubs(sstr(url), i);
            if(p.length == 0) {
                p = sstrn("/", 1);
            }
            *root = sstrdup(r).ptr;
            *path = sstrdup(p).ptr;
            return;
        }
    }
    
    *root = strdup(url);
    *path = strdup("/");
}

void print_resource_error(DavSession *sn, char *path) {
    char *res_url = util_concat_path(sn->base_url, path);
    switch(sn->error) {
        default: {
            fprintf(stderr, "Operation failed for resource %s.\n", res_url);
            break;
        }
        case DAV_NOT_FOUND: {
            fprintf(stderr, "Resource %s not found.\n", res_url);
            break;
        }
        case DAV_UNAUTHORIZED: {
            fprintf(stderr, "Authentication required.\n");
            break;
        }
        case DAV_FORBIDDEN: {
            fprintf(stderr, "Access forbidden.\n");
            break;
        }
        case DAV_METHOD_NOT_ALLOWED: {
            fprintf(stderr, "Method not allowed.\n");
        }
        case DAV_CONFLICT: {
            fprintf(
                    stderr,
                    "Missing intermediate collections for resource %s.\n",
                    res_url);
        }
    }
    free(res_url);
}


int cmd_list(CmdArgs *a) {
    if(a->argc == 0) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    DavSession *sn = NULL;
    char *url = a->argv[0];
    char *root = NULL;
    char *path = NULL;
    char *base = NULL;
    url_get_parts(url, &root, &path);
    
    Repository *repo = get_repository(root);
    if(repo) {
        base = util_concat_path(repo->url, path);
        sn = dav_session_new_auth(ctx, base, repo->user, repo->password);
    } else {
        base = util_concat_path(root, path);
        sn = dav_session_new(ctx, base);
    }
    
    char *update = cmd_getoption(a, "update");
    time_t t = 0;
    if(update) {
        t = util_parse_lastmodified(update);
    }
    
    DavResource *ls;
    if(cmd_getoption(a, "recursive")) {
        printf("base: %s\n", base);
        if(update) {
            ls = dav_query(
                    sn,
                    "get U:crypto-key from /* where lastmodified > %t",
                    t);
        } else {
            ls = dav_query(sn, "get U:crypto-key from /*");
        }
    } else {
        if(update) {
            ls = dav_query(
                    sn,
                    "get U:crypto-key from / where lastmodified > %t", t);
        } else {
            ls = dav_query(sn, "get U:crypto-key from /");
        }
    }
    if(!ls) {
        print_resource_error(sn, path);
        free(root);
        free(path);
        free(base);
        return -1;
    }
    
    // parameters
    int show_all = cmd_getoption(a, "all") ? 1 : 0;
    void (*print_func)(DavResource*, CmdArgs *);
    if(cmd_getoption(a, "list")) {
        print_func = ls_print_list_elm;
    } else {
        print_func = ls_print_elm;
    }
    DavResource *child = ls->children;
    while(child) {
        if(child->name[0] != '.' || show_all) {
            print_func(child, a);
        }
        child = child->next;
    }
    
    free(root);
    free(path);
    free(base);
    
    return 0;
}

static char* ls_date_str(time_t tm) {
    struct tm t;
    struct tm n;
    time_t now = time(NULL);
    localtime_r(&tm, &t);
    localtime_r(&now, &n);
    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) {
        snprintf(str, 16, "");
    } 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;
}

void ls_print_list_elm(DavResource *res, CmdArgs *a) {
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    
    char flags[16];
    memset(flags, '-', 15);
    flags[2] = '\0';
    
    int type_width = 0;
    char *type = res->contenttype;
    
    if(res->iscollection) {
        flags[0] = 'd';
        type = "";
    }
    char *keyprop = dav_get_property_ns(
            res,
            "http://www.uap-core.de/",
            "crypto-key");
    if(keyprop) {
        flags[1] = 'c';
    }
    
    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);
    char *name = recursive ? res->path+1 : res->name;
    printf(
            "%s %*s %10s  %12s  %s\n",
            flags,
            type_width, type,
            size,
            date,
            name);
    free(date);
    free(size);
    
    if(recursive) {
        DavResource *child = res->children;
        while(child) {
            ls_print_list_elm(child, a);
            child = child->next;
        }
    }
}

void ls_print_elm(DavResource *res, CmdArgs *a) {
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    char *name = recursive ? res->path+1 : res->name;
    printf("%s\n", name);
    if(recursive) {
        DavResource *child = res->children;
        while(child) {
            ls_print_elm(child, a);
            child = child->next;
        }
    }
}

int cmd_get(CmdArgs *a) {
    if(a->argc == 0) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    DavSession *sn = NULL;
    char *url = a->argv[0];
    char *root = NULL;
    char *path = NULL;
    url_get_parts(url, &root, &path);
    
    Repository *repo = get_repository(root);
    if(repo) {
        sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
    } else {
        sn = dav_session_new(ctx, root);
    }
    
    char *update = cmd_getoption(a, "update");
    time_t t = 0;
    if(update) {
        t = util_parse_lastmodified(update);
    }
    
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    DavResource *res;
    
    if(recursive) {
        if(update) {
            res = dav_query(
                    sn,
                    "get U:crypto-key from %s* where lastmodified > %t",
                    path,
                    t);
        } else {
            res = dav_query(sn, "get U:crypto-key from %s*", path);
        }
    } else {
        if(update) {
            res = dav_query(
                    sn,
                    "get U:crypto-key from %s where lastmodified > %t",
                    path,
                    t);
        } else {
            res = dav_query(sn, "get U:crypto-key from %s", path);
        }
    }
    if(!res) {
        print_resource_error(sn, path);
        return -1;
    }
    if(!recursive && res->iscollection) {
        char *res_url = util_concat_path(sn->base_url, path);
        fprintf(stderr, "Resource %s is a collection.\n", res_url);
        fprintf(stderr, "Use the -R option to download collections.\n");
        free(res_url);
        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);
    
    return ret;
}

int get_resource(Repository *repo, DavResource *res, CmdArgs *a, char *out) {
    size_t outlen = strlen(out);
    // print some status message in recursive mode
    if(cmd_getoption(a, "recursive")) {
        char *res_url = util_concat_path(res->session->base_url, res->path);
        printf("get: %s\n", res_url);
        free(res_url);
    }
    
    if(res->iscollection) {
        // create directory
        if(outlen != 0) {
            mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
            int ret = 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;
    }
     
    FILE *fout = !strcmp(out, "-") ? stdout : fopen(out, "w");
    if(!fout) {
        fprintf(stderr, "cannot open output file\n");
        return -1;
    }
    
    /*
     * if the -p (plain) option is specified we don't decrypt files
     * use a key specified with the -k (key) option, a key from the
     * key property or the repository default key
     */
    void *out_stream = fout;
    dav_write_func write_func = (dav_write_func)fwrite;
    AESDecrypter *dec = NULL;
    char *plain = cmd_getoption(a, "plain");
    char *keyname = cmd_getoption(a, "key");
    if(!plain) {
        char *keyprop = dav_get_property_ns(
                res,
                "http://www.uap-core.de/",
                "crypto-key");
        Key *key = NULL;
        char *kn = NULL;
        if(keyname) {
            kn = keyname;
        } else if(keyprop) {
            kn = keyprop;
        } else if(repo && repo->decrypt) {
            kn = repo->default_key;
        }
        if(kn) {
            key = get_key(kn);
            if(!key) {
                fprintf(stderr, "Key %s not found!\n", kn);
                // TODO: free
                if(cmd_getoption(a, "recursive")) {
                    // skip the file in recursive mode
                    char *res_url = util_concat_path(
                            res->session->base_url,
                            res->path);
                    printf("Skipping resource: %s\n", res_url);
                    free(res_url);
                    return 0;
                } else {
                    printf("Abort.\n");
                    // abort
                    return 1;
                }
            }
        }
        
        if(key) {
            dec = aes_decrypter_new(key, fout, (dav_write_func)fwrite);
            out_stream = dec;
            write_func = (dav_write_func)aes_write;
        }
    }
    
    int ret = dav_get_content(res, out_stream, write_func);
    if(dec) {
        aes_decrypter_close(dec);
    }
    fclose(fout);
    if(ret && strcmp(out, "-")) {
        unlink(out);
    }
    
    return 0;
}

int cmd_put(CmdArgs *a) {
    if(a->argc < 2) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    DavSession *sn = NULL;
    char *url = a->argv[0];
    char *file = a->argv[1];
    char *root = NULL;
    char *path = NULL;
    url_get_parts(url, &root, &path);
    
    Repository *repo = get_repository(root);
    if(repo) {
        sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
    } else {
        sn = dav_session_new(ctx, root);
    }
    
    int ret;
    if(!strcmp(file, "-")) {
        FILE *in = stdin;
        ret = put_file(repo, a, sn, path, "stdin", in);
    } else {
        ret = put_entry(repo, a, sn, path, file);        
    }
    
    return ret;
}

int put_entry(Repository *repo, CmdArgs *a, DavSession *sn, char *path, char *file) {
    int recursive = cmd_getoption(a, "recursive") ? 1 : 0;
    if(recursive) {
        printf("put: %s\n", file);
    }
    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) {
            return 1;
        }
        DIR *dir = opendir(file);
        if(!dir) {
            // error
        }
        struct dirent *entry;
        while((entry = readdir(dir)) != NULL) {
            if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
                continue;
            }
            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);
            free(entry_path);
            free(entry_file);
            if(r) {
                ret = 1;
                break;
            }
        }
        closedir(dir);
    } else if(S_ISREG(s.st_mode)) {
        /*
         * use stdin if the input file is -
         */
        FILE *in = fopen(file, "r");
        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);
        free(path);
        fclose(in);
    }
    
    return ret;
}

int put_file(Repository *repo, CmdArgs *a, DavSession *sn, char *path, char *name, FILE *in) {
    DavResource *res = dav_query(sn, "get - 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 {
            if(sn->errorstr) {
                fprintf(stderr, "Error: %s\n", sn->errorstr);
            } else {
                fprintf(stderr, "Error\n");
            }
            return -1;
        }
    } else if(res->iscollection) {
        // TODO: free res
        char *newpath = util_concat_path(path, name);
        free(path);
        path = newpath;
        res = dav_resource_new(sn, path);
    }
    
    AESEncrypter *enc = NULL;
    char *keyname = cmd_getoption(a, "key");
    char *kn = NULL;
    char *plain = cmd_getoption(a, "plain");
    if(!plain && (keyname || repo)) {
        kn = keyname ? keyname : repo->default_key;
        if(kn) {
            Key *key = get_key(kn);
            if(!key) {
                fprintf(stderr, "Key %s not found!\nAbort.\n", kn);
                // TODO: free
                return -1;
            }
            if(keyname || repo->encrypt) {
                enc = aes_encrypter_new(key, in, (dav_read_func)fread);
            }
        }
    }
    if(enc) {
        dav_set_content(res, enc, (dav_read_func)aes_read);
        dav_set_property_ns(res, "http://www.uap-core.de/", "crypto-key", kn);
    } else {
        dav_set_content(res, in, (dav_read_func)fread);
    }
    
    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);
        }
        fclose(in);
        return -1;
    }
    if(enc) {
        aes_encrypter_close(enc);
    }
    return 0;
}


int cmd_remove(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    DavSession *sn = NULL;
    char *url = a->argv[0];
    char *root = NULL;
    char *path = NULL;
    url_get_parts(url, &root, &path);
    
    Repository *repo = get_repository(root);
    if(repo) {
        sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
    } else {
        sn = dav_session_new(ctx, root);
    }
    
    DavResource *res = dav_resource_new(sn, path);
    if(!res) {
        fprintf(stderr, "error\n");
        return -1;
    }
    
    if(dav_delete(res)) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot delete resource.\n");
        return -1;
    }
    
    return 0;
}

int cmd_mkdir(CmdArgs *a) {
    if(a->argc < 1) {
        fprintf(stderr, "Too few arguments\n");
        return -1;
    }
    
    DavSession *sn = NULL;
    char *url = a->argv[0];
    char *root = NULL;
    char *path = NULL;
    url_get_parts(url, &root, &path);
    
    Repository *repo = get_repository(root);
    if(repo) {
        sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
    } else {
        sn = dav_session_new(ctx, root);
    }
    
    DavResource *res = dav_resource_new(sn, path);
    if(!res) {
        fprintf(stderr, "error\n");
        return -1;
    }
    res->iscollection = 1;
    
    if(dav_create(res)) {
        print_resource_error(sn, res->path);
        fprintf(stderr, "Cannot create collection.\n");
        return -1;
    }
    
    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 {
        DavSession *sn = NULL;
        char *url = a->argv[0];
        char *root = NULL;
        char *path = NULL;
        url_get_parts(url, &root, &path);

        Repository *repo = get_repository(root);
        if(repo) {
            sn = dav_session_new_auth(ctx, repo->url, repo->user, repo->password);
        } else {
            sn = dav_session_new(ctx, root);
        }

        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;
        }
    }
    return 0;
}

mercurial