libidav/resource.c

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

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 14:25:34 +0100
changeset 797
edbb20b1438d
parent 789
378b5ab86f77
child 807
b41630ecc481
permissions
-rw-r--r--

[Makefile] fix missing rules preventing dry-runs

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

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

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2018 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 <cx/buffer.h>
#include <cx/utils.h>
#include <cx/hash_map.h>
#include <cx/printf.h>
#include <cx/basic_mempool.h>
#include <cx/array_list.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, const char *path) {
    //char *href = util_url_path(url);
    //DavResource *res = dav_resource_new_href(sn, href);
    char *parent = util_parent_path(path);
    const 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, const 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, const char *href) {  
    DavResource *res = cxCalloc(sn->mp->allocator, 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, const char *parent_path, const char *name, char *href) {
    cxstring n = cx_str(name);
    // the name must not contain path separators
    if(n.length > 0 && href) {
        for(int i=0;i<n.length-1;i++) {
            char c = n.ptr[i];
            if(c == '/' || c == '\\') {
                n = cx_str(util_resource_name(href));
                break;
            }
        }
    }
    // remove trailing '/'
    if(n.length > 0 && n.ptr[n.length-1] == '/') {
        n.length--;
    }
    
    DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
    res->session = sn;
    
    // set name, path and href
    res->name = cx_strdup_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, cx_str(path), cx_str(href));
    }
    free(path);
    
    return res;
}

void resource_free_properties(DavSession *sn, CxMap *properties) {
    if(!properties) return;
    
    CxIterator i = cxMapIteratorValues(properties);
    DavProperty *property;
    cx_foreach(DavProperty*, property, i) {
        // TODO: free everything
        dav_session_free(sn, property);
    }
    cxMapDestroy(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);
    resource_free_properties(sn, data->crypto_properties);
    
    if(data->set) {
        CxIterator i = cxListIterator(data->set);
        cx_foreach(DavProperty *, p, i) {
            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_free_xml_node_sn(sn, p->value);
            dav_session_free(sn, p);
        }
    }
    
    if(data->remove) {
        CxIterator i = cxListIterator(data->remove);
        cx_foreach(DavProperty *, p, i) {
            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->crypto_set) {
        CxIterator i = cxListIterator(data->crypto_set);
        cx_foreach(DavProperty *, p, i) {
            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_free_xml_node_sn(sn, p->value);
            dav_session_free(sn, p);
        }
    }
    
    if(data->crypto_remove) {
        CxIterator i = cxListIterator(data->crypto_remove);
        cx_foreach(DavProperty *, p, i) {
            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, cxstring href) {
    res->href = cx_strdup_a(res->session->mp->allocator, href).ptr;
}

void resource_set_info(DavResource *res, const char *href_str) {
    char *url_str = NULL;
    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
    cxstring name = cx_str(util_resource_name(href_str));
    cxstring href = cx_str(href_str);
    
    cxstring base_href = cx_str(util_url_path(res->session->base_url));
    cxstring path = cx_strsubs(href, base_href.length - 1);
    
    const CxAllocator *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 = cx_strdup_a(a, cx_strn(uname, nlen)).ptr;
    res->href = cx_strdup_a(a, href).ptr;
    res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr;
    
    curl_free(uname);
    curl_free(upath);
}

DavResourceData* resource_data_new(DavSession *sn) {
    DavResourceData *data = cxMalloc(
            sn->mp->allocator,
            sizeof(DavResourceData));
    if(!data) {
        return NULL;
    }
    data->properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
    data->crypto_properties = NULL;
    data->set = NULL;
    data->remove = NULL;
    data->crypto_set = NULL;
    data->crypto_remove = NULL;
    data->read = NULL;
    data->content = NULL;
    data->seek = 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_prop(DavResource *res, const char *ns, const char *name, DavXmlNode *val) {
    DavSession *sn = res->session;
    
    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
    namespace->prefix = NULL;
    namespace->name = dav_session_strdup(sn, ns);
    
    DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
    prop->name = dav_session_strdup(sn, name);
    prop->ns = namespace;
    prop->value = val;
    
    cxmutstr keystr = dav_property_key(ns, name);
    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
    cxMapPut(((DavResourceData*)res->data)->properties, key, prop);
    free(keystr.ptr);
}

void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val) {
    if(!val) {
        return;
    }
    
    resource_add_prop(res, ns, name, dav_convert_xml(res->session, val));
}

void resource_add_string_property(DavResource *res, char *ns, char *name, char *val) {
    if(!val) {
        return;
    }
    
    resource_add_prop(res, ns, name, dav_text_node(res->session, val));
}

void resource_set_crypto_properties(DavResource *res, CxMap *cprops) {
    DavResourceData *data = res->data;
    resource_free_properties(res->session, data->crypto_properties);
    data->crypto_properties = cprops;
}

DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name) {
    cxmutstr keystr = dav_property_key(ns, name);
    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
    DavXmlNode *ret = resource_get_property_k(res, key);
    free(keystr.ptr);
    
    return ret;
}

DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name) {
    cxmutstr keystr = dav_property_key(ns, name);
    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
    DavXmlNode *ret = resource_get_encrypted_property_k(res, key);
    free(keystr.ptr);
    
    return ret;
}

DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key) {
    DavResourceData *data = (DavResourceData*)res->data;
    DavProperty *property = cxMapGet(data->properties, key);
    
    return property ? property->value : NULL;
}

DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) {
    DavResourceData *data = (DavResourceData*)res->data;
    DavProperty *property = cxMapGet(data->crypto_properties, key);
    
    return property ? property->value : NULL;
}

cxmutstr dav_property_key(const char *ns, const char *name) {
    return dav_property_key_a(cxDefaultAllocator, ns, name);
}

cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name) {
    cxstring ns_str = cx_str(ns);
    cxstring name_str = cx_str(name);
    
    return cx_strcat_a(a, 4, ns_str, CX_STR("\0"), name_str, CX_STR("\0"));
}




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, CxList *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;
            CxIterator i = cxListIterator(ordercr);
            cx_foreach(DavOrderCriterion*, cr, i) {
                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);
}

static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const char *ns, const char *name) {
    if(!ns || !name) {
        return NULL;
    }
    
    DavResourceData *data = res->data;
    
    DavXmlNode *property = NULL;
    CxList *remove_list = NULL;
    CxList *set_list = NULL;
    
    if(encrypted) {
        // check if crypto_properties because it will only be created
        // if the resource has encrypted properties
        if(!data->crypto_properties) {
            return NULL;
        }
        property = resource_get_encrypted_property(res, ns, name);
        remove_list = data->crypto_remove;
        set_list = data->crypto_set;
    } else {
        property = resource_get_property(res, ns, name);
        remove_list = data->remove;
        set_list = data->set;
    }
    
    // resource_get_property only returns persistent properties
    // check the remove and set list
    if(property && remove_list) {
        // if the property is in the remove list, we return NULL
        CxIterator i = cxListIterator(remove_list);
        cx_foreach(DavProperty*, p, i) {
            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
    if(set_list) {
        CxIterator i = cxListIterator(set_list);
        cx_foreach(DavProperty*, p, i) {
            if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
                return p->value; // TODO: fix
            }
        }
    }
    
    // no property update
    
    return property;
}

DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name) {
    DavXmlNode *property_value = get_property_ns(res, FALSE, ns, name);
    
    if(!property_value && DAV_DECRYPT_PROPERTIES(res->session)) {
        property_value = get_property_ns(res, TRUE, ns, name);
    }
    
    return property_value;
}

DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name) {
    return get_property_ns(res, TRUE, ns, name);
}

static DavProperty* createprop(DavSession *sn, const char *ns, const char *name) {
    DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
    property->name = dav_session_strdup(sn, name);
    property->value = NULL;
    
    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
    namespace->prefix = NULL;
    namespace->name = dav_session_strdup(sn, ns);
    
    property->ns = namespace;
    
    return property;
}

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

static int add2propertylist(const CxAllocator *a, CxList **list, DavProperty *property) {
    if(!*list) {
        CxList *newlist = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
        if(!newlist) {
            return 1;
        }
        *list = newlist;
    }
    cxListAdd(*list, property);
    return 0;
}

void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
    DavSession *sn = res->session;
    const CxAllocator *a = res->session->mp->allocator;
    DavResourceData *data = res->data;
    
    DavProperty *property = createprop(res->session, ns, name);
    property->value = dav_text_node(res->session, value);
    
    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
        add2propertylist(a, &data->crypto_set, property);
    } else {
        add2propertylist(a, &data->set, property);
    }
}

void dav_set_property(DavResource *res, char *name, DavXmlNode *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, DavXmlNode *value) {
    DavSession *sn = res->session;
    const CxAllocator *a = sn->mp->allocator; 
    DavResourceData *data = res->data;
    
    DavProperty *property = createprop(sn, ns, name);
    // TODO: this function should copy the value
    //       but we also need a function, that doesn't create a copy
    property->value = value;
    
    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
        add2propertylist(a, &data->crypto_set, property);
    } else {
        add2propertylist(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) {
    DavSession *sn = res->session;
    DavResourceData *data = res->data;
    const CxAllocator *a = res->session->mp->allocator;
    
    DavProperty *property = createprop(res->session, ns, name);
    
    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
        add2propertylist(a, &data->crypto_remove, property);
    } else {
        add2propertylist(a, &data->remove, property);
    }
}

void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
    const CxAllocator *a = res->session->mp->allocator;
    DavResourceData *data = res->data;
    
    DavProperty *property = createprop(res->session, ns, name);
    property->value = value; // TODO: copy node?
    
    add2propertylist(a, &data->crypto_set, property);
}

void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
    const CxAllocator *a = res->session->mp->allocator;
    DavResourceData *data = res->data;
    
    DavProperty *property = createprop(res->session, ns, name);
    property->value = dav_text_node(res->session, value);
    
    add2propertylist(a, &data->crypto_set, property);
}

void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
    DavResourceData *data = res->data;
    const CxAllocator *a = res->session->mp->allocator;
    
    DavProperty *property = createprop(res->session, ns, name);
    
    add2propertylist(a, &data->crypto_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->size;
    DavPropName *names = dav_session_calloc(
            res->session,
            *count,
            sizeof(DavPropName));
    
    
    CxIterator i = cxMapIteratorValues(data->properties);
    DavProperty *value;
    int j = 0;
    cx_foreach(DavProperty*, value, i) {
        DavPropName *name = &names[j];
        
        name->ns = value->ns->name;
        name->name = value->name;
        
        j++;
    }
    
    qsort(names, *count, sizeof(DavPropName), compare_propname);
    
    return names;
}


void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
    DavResourceData *data = res->data;
    data->content = stream;
    data->read = read_func;
    data->seek = seek_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->seek = 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) {
    CxBuffer *rqbuf = create_allprop_propfind_request();
    int ret = dav_propfind(res->session, res, rqbuf);
    cxBufferFree(rqbuf);
    return ret;
}

int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
    CxMempool *mp = cxBasicMempoolCreate(64);
    const CxAllocator *a = mp->allocator;
    
    CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop);
    for(size_t i=0;i<numprop;i++) {
        DavProperty p;
        p.name = properties[i].name;
        p.ns = cxMalloc(a, sizeof(DavNamespace));
        p.ns->name = properties[i].ns;
        if(!strcmp(properties[i].ns, "DAV:")) {
            p.ns->prefix = "D";
        } else {
            p.ns->prefix = cx_asprintf_a(a, "x%d", (int)i).ptr;
        }
        p.value = NULL;
        cxListAdd(proplist, &p);
    }
    
    CxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
    int ret = dav_propfind(res->session, res, rqbuf);
    cxBufferFree(rqbuf);
    cxMempoolDestroy(mp);
    return ret;
}


static void init_hash_stream(HashStream *hstr, void *stream, dav_read_func readfn, dav_seek_func seekfn) {
    hstr->sha = NULL;
    hstr->stream = stream;
    hstr->read = readfn;
    hstr->seek = seekfn;
    hstr->error = 0;
}

static size_t dav_read_h(void *buf, size_t size, size_t nelm, void *stream) {
    HashStream *s = stream;
    if(!s->sha) {
        s->sha = dav_hash_init();
    }
     
    size_t r = s->read(buf, size, nelm, s->stream);
    dav_hash_update(s->sha, buf, r);
    return r;
}

static int dav_seek_h(void *stream, long offset, int whence) {
    HashStream *s = stream;
    if(offset == 0 && whence == SEEK_SET) {
        unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
        dav_hash_final(s->sha, buf);
        s->sha = NULL;
    } else {
        s->error = 1;
    }
    return s->seek(s->stream, offset, whence);
}


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;
            CxBuffer *buf = NULL;
            if(data->read) {
                enc = aes_encrypter_new(
                        sn->key,
                        data->content,
                        data->read,
                        data->seek);
            } else {
                buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
                buf->size = data->length;
                enc = aes_encrypter_new(
                        sn->key,
                        buf,
                        (dav_read_func)cxBufferRead,
                        (dav_seek_func)cxBufferSeek);
            }
              
            // put resource
            ret = do_put_request(
                    sn,
                    locktoken,
                    TRUE,
                    enc,
                    (dav_read_func)aes_read,
                    (dav_seek_func)aes_encrypter_reset,
                    0);
            
            // get sha256 hash
            dav_get_hash(&enc->sha256, (unsigned char*)data->hash);
            char *enc_hash = aes_encrypt(data->hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
            
            aes_encrypter_close(enc);
            if(buf) {
                cxBufferFree(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 if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
            HashStream hstr;
            CxBuffer *iobuf = NULL;
            if(!data->read) {
                iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
                iobuf->size = data->length;
                init_hash_stream(
                        &hstr,
                        iobuf,
                        (dav_read_func)cxBufferRead,
                        (dav_seek_func)cxBufferSeek);
            } else {
                init_hash_stream(
                        &hstr,
                        data->content,
                        data->read,
                        data->seek);
            }
            
            ret = do_put_request(
                    sn,
                    locktoken,
                    TRUE,
                    &hstr,
                    dav_read_h,
                    (dav_seek_func)dav_seek_h,
                    data->length);
            
            if(hstr.sha) {
                dav_hash_final(hstr.sha, (unsigned char*)data->hash);
                char *hash = util_hexstr((unsigned char*)data->hash, 32);
                dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
                free(hash);
            }
        } else {
            ret = do_put_request(
                    sn,
                    locktoken,
                    TRUE,
                    data->content,
                    data->read,
                    data->seek,
                    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) {
                cxFree(sn->mp->allocator, data->content);
            }
            data->content = NULL;
            data->read = NULL;
            data->length = 0;
        } else {
            dav_session_set_error(sn, ret, status);
            return 1;
        }
    }
    
    // generate crypto-prop content
    if(DAV_ENCRYPT_PROPERTIES(sn) && sn->key && (data->crypto_set || data->crypto_remove)) {
        DavResource *crypto_res = dav_resource_new_href(sn, res->href);
        int ret = 1;
        
        if(crypto_res) {
            CxBuffer *rqbuf = create_cryptoprop_propfind_request();
            ret = dav_propfind(res->session, res, rqbuf);
            cxBufferFree(rqbuf);
        }
        
        if(!ret) {
            DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
            CxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
            if(!crypto_props) {
                // resource hasn't encrypted properties yet
                crypto_props = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); // create new map
            }
            
            // remove all properties
            if(data->crypto_remove) {
                CxIterator i = cxListIterator(data->crypto_remove);
                cx_foreach(DavProperty *, property, i) {
                    if(crypto_props->size == 0) {
                        break; // map already empty, can't remove any more
                    }

                    cxmutstr key = dav_property_key(property->ns->name, property->name);
                    DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length));
                    if(existing_prop) {
                        // TODO: free existing_prop
                    }                
                    free(key.ptr);
                }
            }
            
            // set properties
            if(data->crypto_set) {
                CxIterator i = cxListIterator(data->crypto_set);
                cx_foreach(DavProperty *, property, i) {
                    cxmutstr keystr = dav_property_key(property->ns->name, property->name);
                    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
                    DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key);
                    cxMapPut(crypto_props, key, property);
                    if(existing_prop) {
                        // TODO: free existing_prop
                    }  
                    free(keystr.ptr);
                }
            }
            
            DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props);
            if(crypto_prop_value) {
                DavProperty *new_crypto_prop = createprop(sn, DAV_NS, "crypto-prop");
                new_crypto_prop->value = crypto_prop_value;
                add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop);
            }
            
            dav_resource_free(crypto_res);
        }
        
        if(ret) {
            return 1;
        }
    }
    
    // store properties
    int r = 0;
    sn->error = DAV_OK;
    if(data->set || data->remove) {
        CxBuffer *request = create_proppatch_request(data);
        CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
        //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;
        }
        
        cxBufferFree(request);
        cxBufferFree(response);
    }
     
    return r;
}

#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
static void set_progressfunc(DavResource *res) {
    CURL *handle = res->session->handle;
    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, res);
    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
}

static void unset_progressfunc(DavResource *res) {
    CURL *handle = res->session->handle;
    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, NULL);
    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, NULL);
    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
}
#else
static void set_progressfunc(DavResource *res) {
    
}
static void unset_progressfunc(DavResource *res) {
    
}
#endif

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) {
        set_progressfunc(res);
    }
    
    long status = 0;
    CURLcode ret = dav_session_curl_perform(sn, &status);
    
    if(sn->get_progress) {
        unset_progressfunc(res);
    }
    
    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, len);
                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;
    
    CxBuffer *response = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    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;
        res->exists = 0;
        
        // TODO: parse response
        // TODO: free res
    } else {
        dav_session_set_error(res->session, ret, status);
        r = 1;
    }
    
    cxBufferFree(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 = (char*)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, 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
            CxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0);
            int ret = dav_propfind(sn, res, rqbuf);
            cxBufferFree(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
        res->exists = 1;
        return 0;
    }
    
    if(status == 403 || status == 409 || status == 404) {
        // create intermediate collections
        if(create_ancestors(res->session, res->href, res->path)) {
            return 1;
        }
    }
    
    return create_resource(res, &status);
}

int dav_exists(DavResource *res) {
    if(!dav_load_prop(res, NULL, 0)) {
        res->exists = 1;
        return 1;
    } else {
        if(res->session->error == DAV_NOT_FOUND) {
            res->exists = 0;
        }
        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));
    
    CxBuffer *request = create_lock_request();
    CxBuffer *response = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    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);
    
    cxBufferFree(request);
    
    long status = 0;
    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
        LockDiscovery lock;
        int parse_error = parse_lock_response(sn, response, &lock);
        cxBufferFree(response);
        if(parse_error) {
            sn->error = DAV_ERROR;
            return -1;
        }
        
        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);
        cxBufferFree(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, const char *href, const char *name, const char *hash) {
    if(!DAV_IS_ENCRYPTED(sn)) {
        return 0;
    }
    
    CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
    CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    
    util_set_url(sn, href);
    // TODO: lock
    CURLcode ret = do_proppatch_request(sn, NULL, request, response);
    cxBufferFree(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;   
        cxBufferFree(response);
        return 0;
    } else {
        dav_session_set_error(sn, ret, status);
        cxBufferFree(response);
        return 1;
    }
}

/* ----------------------------- crypto-prop  ----------------------------- */

DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) {
    if(!sn->key) {
        return NULL;
    }
    
    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    
    // create an xml document containing all properties
    CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
    nsmap->simple_destructor = free;
    cxMapPut(nsmap, cx_hash_key_str("DAV:"), strdup("D"));
    
    cxBufferPutString(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    cxBufferPutString(content, "<D:prop xmlns:D=\"DAV:\">\n");
    
    CxIterator i = cxMapIteratorValues(properties);
    DavProperty *prop;
    cx_foreach(DavProperty*, prop, i) {
        DavXmlNode pnode;
        pnode.type = DAV_XML_ELEMENT;
        pnode.namespace = prop->ns->name;
        pnode.name = prop->name;
        pnode.prev = NULL;
        pnode.next = NULL;
        pnode.children = prop->value;
        pnode.parent = NULL;
        pnode.attributes = NULL;
        pnode.content = NULL;
        pnode.contentlength = 0;
        
        dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode);
        cxBufferPut(content, '\n');
    }
    
    cxBufferPutString(content, "</D:prop>");
    
    cxMapDestroy(nsmap);
    
    // encrypt xml document
    char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
    cxBufferDestroy(content);
    
    DavXmlNode *ret = NULL;
    if(crypto_prop_content) {
        ret = dav_text_node(sn, crypto_prop_content);
        free(crypto_prop_content);
    }    
    return ret;
}

CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) {
    if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) {
        return NULL;
    }
    
    return parse_crypto_prop_str(sn, key, node->content);
}

CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) {
    size_t len = 0;
    char *dec_str = aes_decrypt(content, &len, key);
    
    xmlDoc *doc = xmlReadMemory(dec_str, len, NULL, NULL, 0);
    free(dec_str);
    if(!doc) {
        return NULL;
    }
    
    int err = 0;
    xmlNode *xml_root = xmlDocGetRootElement(doc);
    if(xml_root) {
        if(
                !xml_root->ns ||
                !xstreq(xml_root->name, "prop") ||
                !xstreq(xml_root->ns->href, "DAV:"))
        {
            err = 1;
        }
    } else {
        err = 1;
    }
    
    if(err) {
        xmlFreeDoc(doc);
        return NULL;
    }
    
    // ready to get the properties
    CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
    xmlNode *n = xml_root->children;
    while(n) {
        if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) {
            DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
            property->name = dav_session_strdup(sn, (const char*)n->name);
            property->ns = dav_session_malloc(sn, sizeof(DavNamespace));
            property->ns->name = dav_session_strdup(sn, (const char*)n->ns->href);
            property->ns->prefix = n->ns->prefix ?
                    dav_session_strdup(sn, (const char*)n->ns->prefix) : NULL;
            property->value = n->children ? dav_convert_xml(sn, n->children) : NULL;
            
            cxmutstr key = dav_property_key(property->ns->name, property->name);
            cxMapPut(map, cx_hash_key(key.ptr, key.length), property);
            free(key.ptr);
        }
        n = n->next;
    }
    
    xmlFreeDoc(doc);
    if(map->size == 0) {
        cxMapDestroy(map);
        return NULL;
    }
    return map;
}


/* ----------------------------- streams  ----------------------------- */

static size_t in_write(const char *ptr, size_t size, size_t nitems, void *in_stream) {
    DavInputStream *in = in_stream;
    size_t len = size * nitems;
    
    if(in->alloc < len) {
        char *newb = realloc(in->buffer, len);
        if(!newb) {
            if(in->buffer) free(in->buffer);
            in->eof = 1;
            return 0;
        }
        
        in->buffer = newb;
        in->alloc = len;
    }
    
    memcpy(in->buffer, ptr, len);
    
    in->size = len;
    in->pos = 0;
    
    return nitems;
}

/*
DavInputStream* dav_inputstream_open(DavResource *res) {
    DavSession *sn = res->session;
    
    DavInputStream *in = dav_session_malloc(sn, sizeof(DavInputStream));
    if(!in) {
        return NULL;
    }
    memset(in, 0, sizeof(DavInputStream));
    
    in->res = res;
    
    in->c = curl_easy_duphandle(sn->handle); 
    char *url = util_get_url(sn, dav_resource_get_href(res));
    curl_easy_setopt(in->c, CURLOPT_URL, url);
    free(url);
    
    in->m = curl_multi_init();
    
    curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL);
    curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL);
    curl_easy_setopt(in->c, CURLOPT_PUT, 0L);
    curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L);
    
    curl_multi_add_handle(in->m, in->c);
    
    dav_write_func write_fnc = (dav_write_func)in_write;
    void *stream = in;
    
    // 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(in->c, CURLOPT_WRITEFUNCTION, write_fnc);
    curl_easy_setopt(in->c, CURLOPT_WRITEDATA, stream);
    
    in->dec = dec;
    
    return in;
}

size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in) {
    size_t len = in->size - in->pos;
    size_t rl = size * nitems;
    if(len > 0) {
        len = rl > len ? len : rl;
        len -= len % size;
        memcpy(buf, in->buffer + in->pos, len);
        in->pos += len;
        return len / size;
    }
    in->size = 0;
    
    if(in->eof) {
        if(in->dec) {
            aes_decrypter_shutdown(in->dec); // get final bytes
            aes_decrypter_close(in->dec);
            in->dec = NULL;
        } else {
            return 0;
        }
    } else {
        int running;
        while(!in->eof && in->size == 0) {
            CURLMcode r = curl_multi_perform(in->m, &running);
            if(r != CURLM_OK || running == 0) {
                in->eof = 1;
                break;
            }

            int numfds;
            if(curl_multi_poll(in->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
                in->eof = 1;
            }
        }
    }
    
    return in->size > 0 ? dav_read(buf, size, nitems, in) : 0;
}

void dav_inputstream_close(DavInputStream *in) {
    curl_multi_cleanup(in->m);
    curl_easy_cleanup(in->c);
    if(in->buffer) free(in->buffer);
    dav_session_free(in->res->session, in);
}


static size_t out_read(char *ptr, size_t size, size_t nitems, void *out_stream) {
    DavOutputStream *out = out_stream;
    size_t len = size * nitems;
    size_t available = out->size - out->pos;
    if(available == 0) {
        return 0;
    }
    
    size_t r = len > available ? available : len;
    r -= r % size;
    memcpy(ptr, out->buffer + out->pos, r);
    
    out->pos += r;

    return r / size;
}

static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
    return s*n;
}

DavOutputStream* dav_outputstream_open(DavResource *res) {
    DavSession *sn = res->session;
    
    DavOutputStream *out = dav_session_malloc(sn, sizeof(DavOutputStream));
    if(!out) {
        return NULL;
    }
    memset(out, 0, sizeof(DavOutputStream));
    
    out->res = res;
    
    out->c = curl_easy_duphandle(sn->handle); 
    char *url = util_get_url(sn, dav_resource_get_href(res));
    curl_easy_setopt(out->c, CURLOPT_URL, url);
    free(url);
    
    out->m = curl_multi_init();
    curl_multi_add_handle(out->m, out->c);
    
    void *stream = out;
    dav_read_func read_fnc = (dav_read_func)out_read;
    
    // if encryption or hashing in enabled, we need a stream wrapper
    if(DAV_ENCRYPT_CONTENT(sn) && sn->key) {
        AESEncrypter *enc = aes_encrypter_new(sn->key, out, (dav_read_func)out_read, NULL);
        out->enc = enc;
        stream = enc;
        read_fnc = (dav_read_func)aes_read;
    } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
        HashStream *hstr = dav_session_malloc(sn, sizeof(HashStream));
        out->hstr = hstr;
        init_hash_stream(hstr, out, (dav_read_func)out_read, NULL);
        stream = hstr;
        read_fnc = (dav_read_func)dav_read_h;
    }
    
    curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL);
    curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL);
    curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL);
    curl_easy_setopt(out->c, CURLOPT_PUT, 1L);
    curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc);
    curl_easy_setopt(out->c, CURLOPT_READDATA, stream);
    curl_easy_setopt(out->c, CURLOPT_SEEKFUNCTION, NULL);
    curl_easy_setopt(out->c, CURLOPT_INFILESIZE, -1);
    curl_easy_setopt(out->c, CURLOPT_INFILESIZE_LARGE, -1L);
    
    curl_easy_setopt(out->c, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(out->c, CURLOPT_WRITEDATA, NULL);
    
    return out;
}

size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out) {
    if(out->eof) return 0;
    
    out->buffer = buf;
    out->size = size * nitems;
    out->pos = 0;
    
    int running;
    while(!out->eof && (out->size == 0 || out->size - out->pos > 0)) {
        CURLMcode r = curl_multi_perform(out->m, &running);
        if(r != CURLM_OK || running == 0) {
            out->eof = 1;
            break;
        }
        
        int numfds;
        if(curl_multi_poll(out->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
            out->eof = 1;
        }
    }
    
    return (out->size - out->pos) / size;
}

int dav_outputstream_close(DavOutputStream *out) {
    DavSession *sn = out->res->session;
    DavResource *res = out->res;
    DavResourceData *data = res->data;
    
    int ret = 0;
    
    dav_write(NULL, 1, 0, out);
    
    curl_multi_cleanup(out->m);
    curl_easy_cleanup(out->c);
    
    int store = 0;
    if(out->enc) {
        // get sha256 hash
        char hash[32];
        dav_get_hash(&out->enc->sha256, (unsigned char*)data->hash);
        aes_encrypter_close(out->enc);
        char *enc_hash = aes_encrypt(hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
        // add crypto properties
        if(resource_add_crypto_info(sn, out->res->href, out->res->name, enc_hash)) {
            ret = 1;
        }
        free(enc_hash);
    } else if(out->hstr) {
        dav_hash_final(out->hstr->sha, (unsigned char*)data->hash);
        char *hash = util_hexstr((unsigned char*)data->hash, 32);
        dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
        free(hash);
        dav_session_free(sn, out->hstr);
        store = 1;
    }
    
    if(store) {
        ret = dav_store(out->res);
    }
    
    dav_session_free(out->res->session, out);
    
    return ret;
}

*/

mercurial