libidav/resource.c

Thu, 21 Dec 2017 19:48:27 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 21 Dec 2017 19:48:27 +0100
changeset 359
bacb54502b24
parent 355
5da2cf15eb44
child 361
b6f2462ee055
permissions
-rw-r--r--

davql: allow ANYWHERE keyword in SELECT statements

This may seem pointless, but users might want to be explicit about this and the grammar is more consistent.

This commit also adds some no-ops to the functions body of the SET parser, because some day the grammar might allow more clauses after the WHERE clause.

/*
 * 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 <libxml/tree.h>

#include "utils.h"
#include "session.h"
#include "methods.h"
#include "crypto.h"
#include "ucx/buffer.h"
#include "ucx/utils.h"

#include "resource.h"
#include "xml.h"
#include "davqlexec.h"

#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)

DavResource* dav_resource_new(DavSession *sn, char *path) {
    //char *href = util_url_path(url);
    //DavResource *res = dav_resource_new_href(sn, href);
    char *parent = util_parent_path(path);
    char *name = util_resource_name(path); 
    char *href = dav_session_create_plain_href(sn, path);
    
    DavResource *res = dav_resource_new_full(sn, parent, name, href);
    free(parent);
    return res;
}

DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *name) {
    char *path = util_concat_path(parent->path, name);
    char *href = dav_session_create_plain_href(sn, path);
    DavResource *res = dav_resource_new_full(sn, parent->path, name, href);
    free(path);
    return res;
}


DavResource* dav_resource_new_href(DavSession *sn, char *href) {  
    DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource));
    res->session = sn;
    
    // set name, path and href
    resource_set_info(res, href);
    
    // initialize resource data
    res->data = resource_data_new(sn);
    
    return res;
}

DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href) {
    sstr_t n = sstr(name);
    // the name must not contain path separators
    if(n.length > 0) {
        for(int i=0;i<n.length-1;i++) {
            char c = n.ptr[i];
            if(c == '/' || c == '\\') {
                n = sstr(util_resource_name(href));
                break;
            }
        }
    }
    // remove trailing '/'
    if(n.length > 0 && n.ptr[n.length-1] == '/') {
        n.length--;
    }
    
    DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource));
    res->session = sn;
    
    // set name, path and href
    res->name = sstrdup_a(sn->mp->allocator, n).ptr;
    
    char *path = util_concat_path(parent_path, name); 
    res->path = dav_session_strdup(sn, path);
    
    res->href = href;
    
    // initialize resource data
    res->data = resource_data_new(sn);
    
    // cache href/path
    if(href) {
        dav_session_cache_path(sn, sstr(path), sstr(href));
    }
    free(path);
    
    return res;
}

void resource_free_properties(DavSession *sn, UcxMap *properties) {
    UcxMapIterator i = ucx_map_iterator(properties);
    DavXmlNode *node;
    UCX_MAP_FOREACH(key, node, i) {
        // TODO: free everything
        dav_session_free(sn, node);
    }
    ucx_map_free(properties);
}

void dav_resource_free(DavResource *res) {
    DavSession *sn = res->session;
    
    dav_session_free(sn, res->name);
    dav_session_free(sn, res->path);
    if(res->href) {
        dav_session_free(sn, res->href);
    }
    
    DavResourceData *data = res->data;
    resource_free_properties(sn, data->properties);
    
    UCX_FOREACH(elm, data->set) {
        DavProperty *p = elm->data;
        dav_session_free(sn, p->ns->name);
        if(p->ns->prefix) {
            dav_session_free(sn, p->ns->prefix);
        }
        dav_session_free(sn, p->ns);
        
        dav_session_free(sn, p->name);
        dav_session_free(sn, p->value);
        dav_session_free(sn, p);
    }
    
    UCX_FOREACH(elm, data->remove) {
        DavProperty *p = elm->data;
        dav_session_free(sn, p->ns->name);
        if(p->ns->prefix) {
            dav_session_free(sn, p->ns->prefix);
        }
        dav_session_free(sn, p->ns);
        
        dav_session_free(sn, p->name);
        dav_session_free(sn, p);
    }
    
    if(!data->read && data->content) {
        dav_session_free(sn, data->content);
    }
    dav_session_free(sn, data);
    
    dav_session_free(sn, res);
}

void dav_resource_free_all(DavResource *res) {
    DavResource *child = res->children;
    dav_resource_free(res);
    while(child) {
        DavResource *next = child->next;
        dav_resource_free_all(child);
        child = next;
    }
}

void resource_set_href(DavResource *res, sstr_t href) {
    res->href = sstrdup_a(res->session->mp->allocator, href).ptr;
}

void resource_set_info(DavResource *res, char *href_str) {
    char *url_str = NULL;
    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
    sstr_t name = sstr(util_resource_name(href_str));
    sstr_t href = sstr(href_str);
    
    sstr_t base_href = sstr(util_url_path(res->session->base_url));
    sstr_t path = sstrsubs(href, base_href.length - 1);
    
    UcxAllocator *a = res->session->mp->allocator;
    CURL *handle = res->session->handle;
    
    int nlen = 0;
    char *uname = curl_easy_unescape(handle, name.ptr, name.length , &nlen);
    int plen = 0;
    char *upath = curl_easy_unescape(handle, path.ptr, path.length, &plen); 
    
    res->name = sstrdup_a(a, sstrn(uname, nlen)).ptr;
    res->href = sstrdup_a(a, href).ptr;
    res->path = sstrdup_a(a, sstrn(upath, plen)).ptr;
    
    curl_free(uname);
    curl_free(upath);
}

DavResourceData* resource_data_new(DavSession *sn) {
    DavResourceData *data = ucx_mempool_malloc(
            sn->mp,
            sizeof(DavResourceData));
    if(!data) {
        return NULL;
    }
    data->properties = ucx_map_new_a(sn->mp->allocator, 32);
    data->set = NULL;
    data->remove = NULL;
    data->content = NULL;
    data->read = NULL;
    data->length = 0;
    return data;
}

char* dav_resource_get_href(DavResource *resource) {
    if(!resource->href) {
        resource->href = dav_session_get_href(
                resource->session,
                resource->path);
    }
    return resource->href;
}

void resource_add_property(DavResource *res, char *ns, char *name, xmlNode *val) {
    if(!val) {
        return;
    }
    
    UcxKey key = dav_property_key(ns, name);
    DavXmlNode *v = dav_convert_xml(res->session, val);
    ucx_map_put(((DavResourceData*)res->data)->properties, key, v);
    free(key.data);
}

void resource_add_string_property(DavResource *res, char *ns, char *name, char *val) {
    if(!val) {
        return;
    }
    
    UcxKey key = dav_property_key(ns, name);
    DavXmlNode *v = dav_text_node(res->session, val);
    ucx_map_put(((DavResourceData*)res->data)->properties, key, v);
    free(key.data);
}

DavXmlNode* resource_get_property(DavResource *res, char *ns, char *name) {
    UcxKey key = dav_property_key(ns, name);
    DavXmlNode *property = resource_get_property_k(res, key);
    free(key.data);
    return property;
}

DavXmlNode* resource_get_property_k(DavResource *res, UcxKey key) {
    DavResourceData *data = (DavResourceData*)res->data;
    return ucx_map_get(data->properties, key);
}

UcxKey dav_property_key(char *ns, char *name) {
    return dav_property_key_a(ucx_default_allocator(), ns, name);
}

UcxKey dav_property_key_a(UcxAllocator *a, char *ns, char *name) {
    sstr_t ns_str = sstr(ns);
    sstr_t name_str = sstr(name);
    
    sstr_t key;
    key = sstrcat_a(a, 4, ns_str, S("\0"), name_str, S("\0"));
    
    return ucx_key(key.ptr, key.length);
}




void resource_add_child(DavResource *parent, DavResource *child) {
    child->next = NULL;
    if(parent->children) {
        DavResource *last = parent->children;
        while(last->next) {
            last = last->next;
        }
        last->next = child;
        child->prev = last;
    } else {
        child->prev = NULL;
        parent->children = child;
    }
    child->parent = parent;
}

static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion *cr) {
    if(!(res1 && res2)) {
        return 0;
    }
    
    int ret;
    if(cr->type == 0) {
        switch(cr->column.resprop) {
            case DAVQL_RES_NAME: {
                ret = strcmp(res1->name, res2->name);
                break;
            }
            case DAVQL_RES_PATH: {
                ret = strcmp(res1->path, res2->path);
                break;
            }
            case DAVQL_RES_HREF: {
                ret = strcmp(res1->href, res2->href);
                break;
            }
            case DAVQL_RES_CONTENTLENGTH: {
                int c = res1->contentlength == res2->contentlength;
                ret = c ? 0 : (res1->contentlength < res2->contentlength?-1:1);
                break;
            }
            case DAVQL_RES_CONTENTTYPE: {
                ret = strcmp(res1->contenttype, res2->contenttype);
                break;
            }
            case DAVQL_RES_CREATIONDATE: {
                int c = res1->creationdate == res2->creationdate;
                ret = c ? 0 : (res1->creationdate < res2->creationdate?-1:1);
                break;
            }
            case DAVQL_RES_LASTMODIFIED: {
                int c = res1->lastmodified == res2->lastmodified;
                ret = c ? 0 : (res1->lastmodified < res2->lastmodified?-1:1);
                break;
            }
            case DAVQL_RES_ISCOLLECTION: {
                int c = res1->iscollection == res2->iscollection;
                ret = c ? 0 : (res1->iscollection < res2->iscollection?-1:1);
                break;
            }
            default: ret = 0;
        }
    } else if(cr->type == 1) {
        DavXmlNode *xvalue1 = resource_get_property_k(res1, cr->column.property);
        DavXmlNode *xvalue2 = resource_get_property_k(res2, cr->column.property);
        char *value1 = dav_xml_getstring(xvalue1);
        char *value2 = dav_xml_getstring(xvalue2);
        if(!value1) {
            ret = value2 ? -1 : 0;
        } else if(!value2) {
            ret = value1 ? 1 : 0;
        } else {
            ret = strcmp(value1, value2);
        }
    } else {
        return 0;
    }
    
    return cr->descending ? -ret : ret;
}

void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr) {
    if(!ordercr) {
        resource_add_child(parent, child);
        return;
    }
    
    child->parent = parent;
    
    if(!parent->children) {
        child->next = NULL;
        child->prev = NULL;
        parent->children = child;
    } else {
        DavResource *resource = parent->children;
        while(resource) {
            int r = 0;
            UCX_FOREACH(elm, ordercr) {
                DavOrderCriterion *cr = elm->data;
                r = resource_cmp(child, resource, cr);
                if(r != 0) {
                    break;
                }
            }
            
            if(r < 0) {
                // insert child before resource
                child->prev = resource->prev;
                child->next = resource;
                if(resource->prev) {
                    resource->prev->next = child;
                } else {
                    parent->children = child;
                }
                resource->prev = child;
                break;
            } if(!resource->next) {
                // append child
                child->prev = resource;
                child->next = NULL;
                resource->next = child;
                break;
            } else {
                resource = resource->next;
            }
        }
    }
}

char* dav_get_string_property(DavResource *res, char *name) {
    char *pns;
    char *pname;
    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
    if(!pns || !pname) {
        return NULL;
    }
    return dav_get_string_property_ns(res, pns, pname);
}

char* dav_get_string_property_ns(DavResource *res, char *ns, char *name) {
    DavXmlNode *prop = dav_get_property_ns(res, ns, name);
    if(!prop) {
        return NULL;
    }
    return dav_xml_getstring(prop);
}

DavXmlNode* dav_get_property(DavResource *res, char *name) {
    char *pns;
    char *pname;
    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
    if(!pns || !pname) {
        return NULL;
    }
    return dav_get_property_ns(res, pns, pname);
}

DavXmlNode* dav_get_property_ns(DavResource *res, char *ns, char *name) {
    if(!ns || !name) {
        return NULL;
    }
    DavXmlNode *property = resource_get_property(res, ns, name);
    DavResourceData *data = res->data;
    // resource_get_property only returns persistent properties
    // check the remove and set list
    if(property) {
        // if the property is in the remove list, we return NULL
        UCX_FOREACH(elm, data->remove) {
            DavProperty *p = elm->data;
            if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
                return NULL;
            }
        }
    }
    // the set list contains property updates
    // we return an updated property if possible
    UCX_FOREACH(elm, data->set) {
        DavProperty *p = elm->data;
        if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
            return p->value; // TODO: fix
        }
    }
    // no property update
    return property;
}

void dav_set_property(DavResource *res, char *name, char *value) {
    char *pns;
    char *pname;
    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
    dav_set_property_ns(res, pns, pname, value);
}

void dav_set_property_ns(DavResource *res, char *ns, char *name, char *value) {
    UcxAllocator *a = res->session->mp->allocator;
    DavResourceData *data = res->data;
    
    DavProperty *property = dav_session_malloc(
            res->session,
            sizeof(DavProperty));
    property->name = dav_session_strdup(res->session, name);
    property->value = dav_text_node(res->session, value);
    
    DavNamespace *namespace = dav_session_malloc(
            res->session,
            sizeof(DavNamespace));
    namespace->prefix = NULL;
    namespace->name = dav_session_strdup(res->session, ns);
    property->ns = namespace;
    
    data->set = ucx_list_append_a(a, data->set, property);
}

void dav_remove_property(DavResource *res, char *name) {
    char *pns;
    char *pname;
    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
    dav_remove_property_ns(res, pns, pname);
}

void dav_remove_property_ns(DavResource *res, char *ns, char *name) {
    DavResourceData *data = res->data;
    UcxAllocator *a = res->session->mp->allocator;
    
    DavProperty *property = dav_session_malloc(
            res->session,
            sizeof(DavProperty));
    property->name = sstrdup_a(a, sstr(name)).ptr;
    property->value = NULL;
    
    DavNamespace *namespace = dav_session_malloc(
            res->session,
            sizeof(DavNamespace));
    namespace->prefix = NULL;
    namespace->name = sstrdup_a(a, sstr(ns)).ptr;
    property->ns = namespace;
    
    data->remove = ucx_list_append_a(a, data->remove, property);
}

static int compare_propname(const void *a, const void *b) {
    const DavPropName *p1 = a;
    const DavPropName *p2 = b;
    
    int result = strcmp(p1->ns, p2->ns);
    if(result) {
        return result;
    } else {
        return strcmp(p1->name, p2->name);
    }
}

DavPropName* dav_get_property_names(DavResource *res, size_t *count) {
    DavResourceData *data = res->data;
    
    *count = data->properties->count;
    DavPropName *names = dav_session_calloc(
            res->session,
            *count,
            sizeof(DavPropName));
    
    
    UcxMapIterator i = ucx_map_iterator(data->properties);
    void *value;
    int j = 0;
    UCX_MAP_FOREACH(key, value, i) {
        DavPropName *name = &names[j];
        // the map key is namespace + '\0' + name
        name->ns = key.data;
        for(int k=0;j<key.len;k++) {
            if(((char*)key.data)[k] == '\0') {
                name->name = (char*)key.data + k + 1;
                break;
            }
        }
        j++;
    }
    
    qsort(names, *count, sizeof(DavPropName), compare_propname);
    
    return names;
}


void dav_set_content(DavResource *res, void *stream, dav_read_func read_func) {
    DavResourceData *data = res->data;
    data->content = stream;
    data->read = read_func;
    data->length = 0;
}

void dav_set_content_data(DavResource *res, char *content, size_t length) {
    DavSession *sn = res->session;
    DavResourceData *data = res->data;
    data->content = dav_session_malloc(sn, length);
    memcpy(data->content, content, length);
    data->read = NULL;
    data->length = length;
}

void dav_set_content_length(DavResource *res, size_t length) {
    DavResourceData *data = res->data;
    data->length = length;
}


int dav_load(DavResource *res) {
    UcxBuffer *rqbuf = create_allprop_propfind_request();
    int ret = dav_propfind(res->session, res, rqbuf);
    ucx_buffer_free(rqbuf);
    return ret;
}

int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
    UcxMempool *mp = ucx_mempool_new(64);
    
    UcxList *proplist = NULL;
    for(size_t i=0;i<numprop;i++) {
        DavProperty *p = ucx_mempool_malloc(mp, sizeof(DavProperty));
        p->name = properties[i].name;
        p->ns = ucx_mempool_malloc(mp, sizeof(DavNamespace));
        p->ns->name = properties[i].ns;
        if(!strcmp(properties[i].ns, "DAV:")) {
            p->ns->prefix = "D";
        } else {
            p->ns->prefix = ucx_asprintf(mp->allocator, "x%d", i).ptr;
        }
        p->value = NULL;
        proplist = ucx_list_append_a(mp->allocator, proplist, p);
    }
    
    UcxBuffer *rqbuf = create_propfind_request(res->session, proplist);
    int ret = dav_propfind(res->session, res, rqbuf);
    ucx_buffer_free(rqbuf);
    ucx_mempool_destroy(mp);
    return ret;
}

int dav_store(DavResource *res) {
    DavSession *sn = res->session;
    DavResourceData *data = res->data;
    
    util_set_url(sn, dav_resource_get_href(res));
    
    DavLock *lock = dav_get_lock(sn, res->path);
    char *locktoken = lock ? lock->token : NULL;
    
    // store content
    if(data->content) {
        int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key;
        CURLcode ret;
        if(encryption) {
            AESEncrypter *enc = NULL;
            UcxBuffer *buf = NULL;
            if(data->read) {
                enc = aes_encrypter_new(sn->key, data->content, data->read);
            } else {
                buf = ucx_buffer_new(data->content, data->length, 0);
                buf->size = data->length;
                enc = aes_encrypter_new(
                        sn->key,
                        buf,
                        (dav_read_func)ucx_buffer_read);
            }
              
            // put resource
            ret = do_put_request(
                    sn,
                    locktoken,
                    TRUE,
                    enc,
                    (dav_read_func)aes_read,
                    0);
            
            // get sha256 hash
            unsigned char sha[DAV_SHA256_DIGEST_LENGTH];
            dav_get_hash(&enc->sha256, sha);
            char *enc_hash = aes_encrypt((char*)sha, DAV_SHA256_DIGEST_LENGTH, sn->key);
            
            aes_encrypter_close(enc);
            if(buf) {
                ucx_buffer_free(buf);
            }
            
            // add crypto properties
            // TODO: store the properties later
            if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
                free(enc_hash);
                return 1;
            }
            resource_add_string_property(res, DAV_NS, "crypto-hash", enc_hash);
            free(enc_hash);
        } else {
            ret = do_put_request(
                    sn,
                    locktoken,
                    TRUE,
                    data->content,
                    data->read,
                    data->length);
        }
        
        long status = 0;
        curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &status);
        if(ret == CURLE_OK && (status >= 200 && status < 300)) {
            res->session->error = 0;
            // cleanup node data
            if(!data->read) {
                ucx_mempool_free(sn->mp, data->content);
            }
            data->content = NULL;
            data->read = NULL;
            data->length = 0;
        } else {
            dav_session_set_error(sn, ret, status);
            return 1;
        }
    }
    
    // store properties
    int r = 0;
    sn->error = DAV_OK;
    if(data->set || data->remove) {
        UcxBuffer *request = create_proppatch_request(data);
        UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
        //printf("request:\n%.*s\n\n", request->pos, request->space);

        CURLcode ret = do_proppatch_request(sn, locktoken, request, response);
        long status = 0;
        curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
        if(ret == CURLE_OK && status == 207) {
            //printf("%s\n", response->space);
            // TODO: parse response
            // TODO: cleanup node data correctly
            data->set = NULL;
            data->remove = NULL;
        } else {
            dav_session_set_error(sn, ret, status);
            r = -1;
        }
        
        ucx_buffer_free(request);
        ucx_buffer_free(response);
    }
    
    return r;
}

int dav_get_content(DavResource *res, void *stream, dav_write_func write_fnc) { 
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(res->session, dav_resource_get_href(res));
    
    // check encryption
    AESDecrypter *dec = NULL;
    DavKey *key = NULL;
    if(DAV_DECRYPT_CONTENT(sn)) {
        char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
        if(keyname) {
            key = dav_context_get_key(sn->context, keyname);
            if(key) {
                dec = aes_decrypter_new(key, stream, write_fnc);
                stream = dec;
                write_fnc = (dav_write_func)aes_write;
            }
        }
    }
    
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
    curl_easy_setopt(handle, CURLOPT_PUT, 0L);
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_fnc);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, stream);
    
    if(sn->get_progress) {
        curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
        curl_easy_setopt(handle, CURLOPT_XFERINFODATA, res);
        curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
    }
    
    long status = 0;
    CURLcode ret = dav_session_curl_perform(sn, &status);
    
    if(sn->get_progress) {
        curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, NULL);
        curl_easy_setopt(handle, CURLOPT_XFERINFODATA, NULL);
        curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
    }
    
    char *hash = NULL;
    if(dec) {
        aes_decrypter_shutdown(dec); // get final bytes
        
        // get hash
        unsigned char sha[DAV_SHA256_DIGEST_LENGTH];
        dav_get_hash(&dec->sha256, sha);
        hash = util_hexstr(sha, DAV_SHA256_DIGEST_LENGTH);
        
        aes_decrypter_close(dec);
    }
    
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        int verify_failed = 0;
        if(DAV_DECRYPT_CONTENT(sn) && key) {
            // try to verify the content
            char *res_hash = dav_get_string_property_ns(res, DAV_NS, "crypto-hash");

            if(res_hash) {
                size_t len = 0;
                char *dec_hash = aes_decrypt(res_hash, &len, key);
                char *hex_hash = util_hexstr((unsigned char*)dec_hash, 32);
                if(strcmp(hash, hex_hash)) {
                    verify_failed = 1;
                }
                free(dec_hash);
                free(hex_hash);
            }
        }
        if(hash) {
            free(hash);
        }
        
        if(verify_failed) {
            res->session->error = DAV_CONTENT_VERIFICATION_ERROR;
            return 1;
        }
        
        res->session->error = DAV_OK;
        return 0;
    } else {
        if(hash) {
            free(hash);
        }
        dav_session_set_error(res->session, ret, status);
        return 1;
    }
}

DavResource* dav_create_child(DavResource *parent, char *name) {
    DavResource *res = dav_resource_new_child(parent->session, parent, name);
    if(dav_create(res)) {
        dav_resource_free(res);
        return NULL;
    } else {
        return res;
    }
}

int dav_delete(DavResource *res) {
    CURL *handle = res->session->handle;
    util_set_url(res->session, dav_resource_get_href(res));
    
    DavLock *lock = dav_get_lock(res->session, res->path);
    char *locktoken = lock ? lock->token : NULL;
    
    UcxBuffer *response = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
    CURLcode ret = do_delete_request(res->session, locktoken, response);
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    int r = 0;
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        res->session->error = DAV_OK;
        
        // TODO: parse response
        // TODO: free res
    } else {
        dav_session_set_error(res->session, ret, status);
        r = 1;
    }
    
    ucx_buffer_free(response);
    return r;
}

static int create_ancestors(DavSession *sn, char *href, char *path) {
    CURL *handle = sn->handle;
    CURLcode code;
    
    DavLock *lock = dav_get_lock(sn, path);
    char *locktoken = lock ? lock->token : NULL;
    
    long status = 0;
    int ret = 0;
    
    if(strlen(path) <= 1) {
        return 0;
    }
    
    char *p = util_parent_path(path);
    char *h = util_parent_path(href);
    
    for(int i=0;i<2;i++) {
        util_set_url(sn, h);
        code = do_mkcol_request(sn, locktoken);
        curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
        if(status == 201) {
            // resource successfully created
            char *name = util_resource_name(p);
            int len = strlen(name);
            if(name[len - 1] == '/') {
                name[len - 1] = '\0';
            }
            if(resource_add_crypto_info(sn, h, name, NULL)) {
                sn->error = DAV_ERROR;
                dav_session_set_errstr(sn, "Cannot set crypto properties for ancestor");
            }
            break;
        } else if(status == 405) {
            // parent already exists
            break;
        } else if(status == 409) {
            // parent doesn't exist
            if(create_ancestors(sn, h, p)) {
                ret = 1;
                break;
            }
        } else {
            dav_session_set_error(sn, code, status);
            ret = 1;
            break;
        }
    }
    
    free(p);
    free(h);
    return ret;
}

static int create_resource(DavResource *res, int *status) {
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(sn, dav_resource_get_href(res));
    
    DavLock *lock = dav_get_lock(res->session, res->path);
    char *locktoken = lock ? lock->token : NULL;
    
    CURLcode code;
    if(res->iscollection) {
        code = do_mkcol_request(sn, locktoken);
    } else {
        code = do_put_request(sn, locktoken, TRUE, "", NULL, 0); 
    }
    long s = 0;
    curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &s);
    *status = s;
    if(code == CURLE_OK && (s >= 200 && s < 300)) {
        sn->error = DAV_OK;
        // if the session has encrypted file names, add crypto infos
        if(!resource_add_crypto_info(sn, res->href, res->name, NULL)) {
            // do a minimal propfind request
            UcxBuffer *rqbuf = create_propfind_request(sn, NULL);
            int ret = dav_propfind(sn, res, rqbuf);
            ucx_buffer_free(rqbuf);
            return ret;
        } else {
            return 1;
        }
    } else {
        dav_session_set_error(sn, code, s);
        return 1;
    }
}

int dav_create(DavResource *res) {
    int status;
    if(!create_resource(res, &status)) {
        // resource successfully created
        return 0;
    }
    
    if(status == 403 || status == 409) {
        // create intermediate collections
        if(create_ancestors(res->session, res->href, res->path)) {
            return 1;
        }
    }
    
    return create_resource(res, &status);
}

int dav_exists(DavResource *res) {
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(sn, dav_resource_get_href(res));
    
    CURLcode ret = do_head_request(sn);
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        return 1;
    } else {
        dav_session_set_error(sn, ret, status);
        return 0;
    }
}

static int dav_cp_mv_url(DavResource *res, char *desturl, _Bool copy, _Bool override) {
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(sn, dav_resource_get_href(res));
    
    DavLock *lock = dav_get_lock(sn, res->path);
    char *locktoken = lock ? lock->token : NULL;
    
    CURLcode ret = do_copy_move_request(sn, desturl, locktoken, copy, override);
    
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        return 0;
    } else {
        dav_session_set_error(sn, ret, status);
        return 1;
    }
}

static int dav_cp_mv(DavResource *res, char *newpath, _Bool copy, _Bool override) {
    char *dest = dav_session_get_href(res->session, newpath);
    char *desturl = util_get_url(res->session, dest);
    dav_session_free(res->session, dest);
    
    int ret = dav_cp_mv_url(res, desturl, copy, override);
    free(desturl);
    return ret;
}

int dav_copy(DavResource *res, char *newpath) {
    return dav_cp_mv(res, newpath, true, false);
}

int dav_move(DavResource *res, char *newpath) {
    return dav_cp_mv(res, newpath, false, false);
}

int dav_copy_o(DavResource *res, char *newpath, DavBool override) {
    return dav_cp_mv(res, newpath, true, override);
}

int dav_move_o(DavResource *res, char *newpath, DavBool override) {
    return dav_cp_mv(res, newpath, false, override);
}

int dav_copyto(DavResource *res, char *url, DavBool override) {
    return dav_cp_mv_url(res, url, true, override);
}

int dav_moveto(DavResource *res, char *url, DavBool override) {
    return dav_cp_mv_url(res, url, false, override);
}

int dav_lock(DavResource *res) {
    return dav_lock_t(res, 0);
}

int dav_lock_t(DavResource *res, time_t timeout) {
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(sn, dav_resource_get_href(res));
    
    UcxBuffer *request = create_lock_request();
    UcxBuffer *response = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    CURLcode ret = do_lock_request(sn, request, response, timeout);
    
    //printf("\nlock\n");
    //printf("%.*s\n\n", request->size, request->space);
    //printf("%.*s\n\n", response->size, response->space);
    
    ucx_buffer_free(request);
    
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        LockDiscovery lock;
        if(parse_lock_response(sn, response, &lock)) {
            sn->error = DAV_ERROR;
            ucx_buffer_free(response);
            return -1;
        }
        ucx_buffer_free(response);
        
        DavLock *l = dav_create_lock(sn, lock.locktoken, lock.timeout);
        free(lock.locktoken);
        free(lock.timeout);
        
        int r = 0;
        if(res->iscollection) {
            r = dav_add_collection_lock(sn, res->path, l);
        } else {
            r = dav_add_resource_lock(sn, res->path, l);
        }
        
        if(r == 0) {
            return 0;
        } else {
            (void)dav_unlock(res);
            sn->error = DAV_ERROR;
            dav_destroy_lock(sn, l);
            return -1;
        }
    } else {
        dav_session_set_error(sn, ret, status);
        ucx_buffer_free(response);
        return -1;
    }
}

int dav_unlock(DavResource *res) {
    DavSession *sn = res->session;
    CURL *handle = sn->handle;
    util_set_url(sn, dav_resource_get_href(res));
    
    DavLock *lock = dav_get_lock(res->session, res->path);
    if(!lock) {
        sn->error = DAV_ERROR;
        return -1;
    }
    
    CURLcode ret = do_unlock_request(sn, lock->token);
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        dav_remove_lock(sn, res->path, lock);
    } else {
        dav_session_set_error(sn, ret, status);
        return 1;
    }
    
    return 0;
}


int resource_add_crypto_info(DavSession *sn, char *href, char *name, char *hash) {
    if(!DAV_IS_ENCRYPTED(sn)) {
        return 0;
    }
    
    UcxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
    UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
    
    util_set_url(sn, href);
    // TODO: lock
    CURLcode ret = do_proppatch_request(sn, NULL, request, response);
    ucx_buffer_free(request);
    long status = 0;
    curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && status == 207) {
        // TODO: parse response
        sn->error = DAV_OK;   
        ucx_buffer_free(response);
        return 0;
    } else {
        dav_session_set_error(sn, ret, status);
        ucx_buffer_free(response);
        return 1;
    }
}

mercurial