diff -r 2483f517c562 -r b5bb7b3cd597 libidav/resource.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/resource.c Mon Jan 22 17:27:47 2024 +0100 @@ -0,0 +1,1875 @@ +/* + * 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 +#include +#include +#include +#include + +#include "utils.h" +#include "session.h" +#include "methods.h" +#include "crypto.h" +#include +#include +#include +#include +#include +#include + +#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 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 = cxMempoolCreate(64, NULL); + const CxAllocator *a = mp->allocator; + + CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop); + for(size_t i=0;iname = 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, "\n"); + cxBufferPutString(content, "\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, ""); + + 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; +} + +*/