libidav/methods.c

Sun, 17 Dec 2023 15:33:50 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 15:33:50 +0100
changeset 800
30d484806c2b
parent 753
8776125fd49c
child 816
839fefbdedc7
permissions
-rw-r--r--

fix faulty string to int conversion utilities

Probably it was expected that errno is set to EINVAL when illegal characters are encountered. But this is not standard and does not happen on every system, allowing illegal strings to be parsed as valid integers.

/*
 * 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 "utils.h"
#include "methods.h"
#include "crypto.h"
#include "session.h"
#include "xml.h"

#include <cx/utils.h>
#include <cx/printf.h>
#include <cx/hash_map.h>

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


int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin) {
    return cxBufferSeek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
}

/* ----------------------------- PROPFIND ----------------------------- */

CURLcode do_propfind_request(
        DavSession *sn,
        CxBuffer *request,
        CxBuffer *response)
{
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND");
    
    // always try to get information about possible children
    int depth = 1;
    
    int maxretry = 2;
    
    struct curl_slist *headers = NULL;
    CURLcode ret = 0;
    
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
    curl_easy_setopt(handle, CURLOPT_READDATA, request);
    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    CxMap *respheaders = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
    respheaders->simple_destructor = free;
    util_capture_header(handle, respheaders);
    
    for(int i=0;i<maxretry;i++) {
        if (depth == 1) {
            headers = curl_slist_append(headers, "Depth: 1");
        } else if (depth == -1) {
            headers = curl_slist_append(headers, "Depth: infinity");
        } else {
            headers = curl_slist_append(headers, "Depth: 0");
        }
        headers = curl_slist_append(headers, "Content-Type: text/xml");
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);

        // reset buffers and perform request
        request->pos = 0;
        response->size = response->pos = 0;
        ret = dav_session_curl_perform_buf(sn, request, response, NULL);
        curl_slist_free_all(headers);
        headers = NULL;
        
        /*
         * Handle two cases:
         * 1. We communicate with IIS and get a X-MSDAVEXT_Error: 589831
         *    => try with depth 0 next time, it's not a collection
         * 2. Other cases
         *    => the server handled our request and we can stop requesting
         */
        char *msdavexterror;
        msdavexterror = cxMapGet(respheaders, cx_hash_key_str("x-msdavext_error"));
        int iishack =  depth == 1 &&
            msdavexterror && !strncmp(msdavexterror, "589831;", 7);
        
        if(iishack) {
            depth = 0;
        } else {
            break;
        }
    }
    
    // deactivate header capturing and free captured map
    util_capture_header(handle, NULL);
    cxMapDestroy(respheaders);
       
    return ret;
}

CxBuffer* create_allprop_propfind_request(void) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:propfind xmlns:D=\"DAV:\">\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:allprop/></D:propfind>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    return buf;
}

CxBuffer* create_cryptoprop_propfind_request(void) {
    CxBuffer *buf = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    return buf;
}

CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    int add_crypto_name = 1;
    int add_crypto_key = 1;
    int add_crypto_hash = 1;
    char *crypto_ns = "idav";
    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
    if(properties) {
        CxIterator i = cxListIterator(properties);
        cx_foreach(DavProperty*, p, i) {
            if(strcmp(p->ns->name, "DAV:")) {
                cxMapPut(namespaces, cx_hash_key_str(p->ns->prefix), p->ns);
            }

            // if the properties list contains the idav properties crypto-name
            // and crypto-key, mark them as existent 
            if(!strcmp(p->ns->name, DAV_NS)) {
                if(!strcmp(p->name, "crypto-name")) {
                    add_crypto_name = 0;
                    crypto_ns = p->ns->prefix;
                } else if(!strcmp(p->name, "crypto-key")) {
                    add_crypto_key = 0;
                    crypto_ns = p->ns->prefix;
                } else if(!strcmp(p->name, "crypto-hash")) {
                    add_crypto_hash = 0;
                    crypto_ns = p->ns->prefix;
                }
            }
        }
    }
    
    DavNamespace idav_ns;
    if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn) && !nocrypt) {
        idav_ns.prefix = "idav";
        idav_ns.name = DAV_NS;
        cxMapPut(namespaces, cx_hash_key_str("idav"), &idav_ns);
    }
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // write root element and namespaces
    cx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
    
    CxIterator mapi = cxMapIteratorValues(namespaces);
    cx_foreach(DavNamespace*, ns, mapi) {
        s = CX_STR(" xmlns:");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = cx_str(ns->prefix);
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = CX_STR("=\"");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = cx_str(ns->name);
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = CX_STR("\"");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    s = CX_STR(">\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // default properties
    s = CX_STR("<D:prop>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:creationdate />\n<D:getlastmodified />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:getcontentlength />\n<D:getcontenttype />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:resourcetype />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // crypto properties
    if(DAV_CRYPTO(sn) && !nocrypt) {
        if(add_crypto_name) {
            cxBufferPut(buf, '<');
            cxBufferPutString(buf, crypto_ns);
            s = CX_STR(":crypto-name />\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
        if(add_crypto_key) {
            cxBufferPut(buf, '<');
            cxBufferPutString(buf, crypto_ns);
            s = CX_STR(":crypto-key />\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
        if(add_crypto_hash) {
            cxBufferPut(buf, '<');
            cxBufferPutString(buf, crypto_ns);
            s = CX_STR(":crypto-hash />\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
    }
    
    // extra properties
    if(properties) {
        CxIterator i = cxListIterator(properties);
        cx_foreach(DavProperty*, prop, i) {
            s = CX_STR("<");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(prop->ns->prefix);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(":");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(prop->name);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(" />\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
    }
    
    // end
    cx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
    
    cxMapDestroy(namespaces);
    return buf;
}

CxBuffer* create_basic_propfind_request(void) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
    cxBufferWrite(s.ptr, 1, s.length, buf);  
    s = CX_STR(DAV_NS);
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("\" >\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // properties
    s = CX_STR("<D:prop>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("<D:resourcetype />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("<i:crypto-key />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("<i:crypto-name />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("<i:crypto-hash />\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    s = CX_STR("</D:prop>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // end
    s = CX_STR("</D:propfind>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    return buf;
}

PropfindParser* create_propfind_parser(CxBuffer *response, char *url) {
    PropfindParser *parser = malloc(sizeof(PropfindParser));
    if(!parser) {
        return NULL;
    }
    parser->document = xmlReadMemory(response->space, response->pos, url, NULL, 0);
    parser->current = NULL;
    if(parser->document) {
        xmlNode *xml_root = xmlDocGetRootElement(parser->document);
        if(xml_root) {
            xmlNode *node = xml_root->children;
            while(node) {
                // find first response tag
                if(node->type == XML_ELEMENT_NODE) {
                    if(xstreq(node->name, "response")) {
                        parser->current = node;
                        break;
                    }
                }
                node = node->next;
            }
            return parser;
        } else {
            xmlFreeDoc(parser->document);
        }
    }
    free(parser);
    return NULL;
}

void destroy_propfind_parser(PropfindParser *parser) {
    if(parser->document) {
        xmlFreeDoc(parser->document);
    }
    free(parser);
}

int get_propfind_response(PropfindParser *parser, ResponseTag *result) {
    if(parser->current == NULL) {
        return 0;
    }
    
    char *href = NULL;
    int iscollection = 0;
    char *crypto_name = NULL; // name set by crypto-name property
    char *crypto_key = NULL;
    
    result->properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
    
    xmlNode *node = parser->current->children;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            if(xstreq(node->name, "href")) {
                xmlNode *href_node = node->children;
                if(href_node->type != XML_TEXT_NODE) {
                    // error
                    return -1;
                }
                href = (char*)href_node->content;
            } else if(xstreq(node->name, "propstat")) {
                xmlNode *n = node->children;
                xmlNode *prop_node = NULL;
                int ok = 0;
                // get the status code
                while(n) {
                    if(n->type == XML_ELEMENT_NODE) {
                        if(xstreq(n->name, "prop")) {
                            prop_node = n;
                        } else if(xstreq(n->name, "status")) {
                            xmlNode *status_node = n->children;
                            if(status_node->type != XML_TEXT_NODE) {
                                // error
                                return -1;
                            }
                            cxstring status_str = cx_str((char*)status_node->content);
                            if(status_str.length < 13) {
                                // error
                                return -1;
                            }
                            status_str = cx_strsubsl(status_str, 9, 3);
                            if(!cx_strcmp(status_str, CX_STR("200"))) {
                                ok = 1;
                            }
                        }
                    }    
                    n = n->next;
                }
                // if status is ok, get all properties
                if(ok) {
                    n = prop_node->children;
                    while(n) {
                        if(n->type == XML_ELEMENT_NODE) {
                            cxListAdd(result->properties, n);
                            if(xstreq(n->name, "resourcetype")) {
                                if(parse_resource_type(n)) {
                                    iscollection = TRUE;
                                }
                            } else if(xstreq(n->ns->href, DAV_NS)) {
                                if(xstreq(n->name, "crypto-name")) {
                                    crypto_name = util_xml_get_text(n);
                                } else if(xstreq(n->name, "crypto-key")) {
                                    crypto_key = util_xml_get_text(n);
                                }
                            }
                        }
                        n = n->next;
                    }
                }
            }
        }
        node = node->next;
    }
    
    result->href = util_url_path(href);
    result->iscollection = iscollection;
    result->crypto_name = crypto_name;
    result->crypto_key = crypto_key;
    
    // find next response tag
    xmlNode *next = parser->current->next;
    while(next) {
        if(next->type == XML_ELEMENT_NODE) {
            if(xstreq(next->name, "response")) {
                break;
            }
        }
        next = next->next;
    }
    parser->current = next;
    
    return 1;
}

void cleanup_response(ResponseTag *result) {
    if(result) {
        cxListDestroy(result->properties);
    }
}

int hrefeq(DavSession *sn, const char *href1, const char *href2) {
    cxmutstr href_s = cx_mutstr(util_url_decode(sn, href1));
    cxmutstr href_r = cx_mutstr(util_url_decode(sn, href2));
    int ret = 0;
    if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
        ret = 1;
    } else if(href_s.length == href_r.length + 1) {
        if(href_s.ptr[href_s.length-1] == '/') {
            href_s.length--;
            if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
                ret = 1;
            }
        }
    } else if(href_r.length == href_s.length + 1) {
        if(href_r.ptr[href_r.length-1] == '/') {
            href_r.length--;
            if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
                ret = 1;
            }
        }
    } 

    free(href_s.ptr);
    free(href_r.ptr);
    
    return ret;
}


DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response) {
    char *url = NULL;
    curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url);
    if(!root) {
        printf("methods.c: TODO: remove\n");
        root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove
    }
    
    //printf("%.*s\n\n", response->size, response->space);
    xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0);
    if(!doc) {
        // TODO: free stuff
        sn->error = DAV_ERROR;
        return NULL;
    }
    
    xmlNode *xml_root = xmlDocGetRootElement(doc);
    xmlNode *node = xml_root->children;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            if(xstreq(node->name, "response")) {
                parse_response_tag(root, node);
            }
        }
        node = node->next;
    }
    xmlFreeDoc(doc);
    
    return root;
}

DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path) {
    // create resource
    char *name = NULL;
    DavKey *key = NULL;
    if(DAV_DECRYPT_NAME(sn) && response->crypto_name && (key = dav_context_get_key(sn->context, response->crypto_key))) {
        if(!response->crypto_key) {
            sn->error = DAV_ERROR;
            dav_session_set_errstr(sn, "Missing crypto-key property");
            return NULL;
        }
        name = util_decrypt_str_k(sn, response->crypto_name, key);
        if(!name) {
            sn->error = DAV_ERROR;
            dav_session_set_errstr(sn, "Cannot decrypt resource name");
            return NULL;
        }
    } else {
        cxstring resname = cx_str(util_resource_name(response->href));
        int nlen = 0;
        char *uname = curl_easy_unescape(
                sn->handle,
                resname.ptr,
                resname.length,
                &nlen);
        name = dav_session_strdup(sn, uname);
        curl_free(uname);
    }

    char *href = dav_session_strdup(sn, response->href);
    DavResource *res = NULL;
    if(parent_path) {
        res = dav_resource_new_full(sn, parent_path, name, href);
    } else {
        res = dav_resource_new_href(sn, href);
    }
    dav_session_free(sn, name);
    
    add_properties(res, response); 
    return res;
}

void add_properties(DavResource *res, ResponseTag *response) {
    res->iscollection = response->iscollection;
    
    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
    xmlNode *crypto_prop = NULL;
    char *crypto_key = NULL;
    
    // add properties
    if(response->properties) {
        CxIterator i = cxListIterator(response->properties);
        cx_foreach(xmlNode*, prop, i) {
            resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
        
            if (decrypt_props &&
                prop->children &&
                prop->children->type == XML_TEXT_NODE &&
                xstreq(prop->ns->href, DAV_NS))
            {
                if(xstreq(prop->name, "crypto-prop")) {
                    crypto_prop = prop;
                } else if(xstreq(prop->name, "crypto-key")) {
                    crypto_key = util_xml_get_text(prop);
                }
            }
        }
    }
    
    if(crypto_prop && crypto_key) {
        char *crypto_prop_content = util_xml_get_text(crypto_prop);
        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
        if(crypto_prop_content) {
            CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
            resource_set_crypto_properties(res, cprops);
        }
    }
    
    set_davprops(res);
}

int parse_response_tag(DavResource *resource, xmlNode *node) {
    DavSession *sn = resource->session;
    
    //DavResource *res = resource;
    DavResource *res = NULL;
    const char *href = NULL;
    CxList *properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
    char *crypto_name = NULL; // name set by crypto-name property
    char *crypto_key = NULL;
    
    int iscollection = 0; // TODO: remove
    
    node = node->children;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            if(xstreq(node->name, "href")) {
                xmlNode *href_node = node->children;
                if(href_node->type != XML_TEXT_NODE) {
                    // error
                    sn->error = DAV_ERROR;
                    return 1;
                }
                //char *href = (char*)href_node->content;
                href = util_url_path((const char*)href_node->content);
                
                char *href_s = util_url_decode(resource->session, href);
                char *href_r = util_url_decode(resource->session, resource->href);
                
                if(hrefeq(sn, href_s, href_r)) {
                    res = resource;
                }   
                
                free(href_s);
                free(href_r);
            } else if(xstreq(node->name, "propstat")) {
                xmlNode *n = node->children;
                xmlNode *prop_node = NULL;
                int ok = 0;
                // get the status code
                while(n) {
                    if(n->type == XML_ELEMENT_NODE) {
                        if(xstreq(n->name, "prop")) {
                            prop_node = n;
                        } else if(xstreq(n->name, "status")) {
                            xmlNode *status_node = n->children;
                            if(status_node->type != XML_TEXT_NODE) {
                                sn->error = DAV_ERROR;
                                return 1;
                            }
                            cxstring status_str = cx_str((char*)status_node->content);
                            if(status_str.length < 13) {
                                sn->error = DAV_ERROR;
                                return 1;
                            }
                            status_str = cx_strsubsl(status_str, 9, 3);
                            if(!cx_strcmp(status_str, CX_STR("200"))) {
                                ok = 1;
                            }
                        }
                    }    
                    n = n->next;
                }
                // if status is ok, get all properties
                if(ok) {
                    n = prop_node->children;
                    while(n) {
                        if(n->type == XML_ELEMENT_NODE) {
                            cxListAdd(properties, n);
                            if(xstreq(n->name, "resourcetype")) {
                                if(parse_resource_type(n)) {
                                    iscollection = TRUE;
                                }
                            } else if(n->ns && xstreq(n->ns->href, DAV_NS)) {
                                if(xstreq(n->name, "crypto-name")) {
                                    crypto_name = util_xml_get_text(n);
                                } else if(xstreq(n->name, "crypto-key")) {
                                    crypto_key = util_xml_get_text(n);
                                }
                            }
                        }
                        n = n->next;
                    }
                }
            }
        }
        
        node = node->next;
    }
    
    if(!res) {
        // create new resource object
        char *name = NULL;
        if(DAV_DECRYPT_NAME(sn) && crypto_name) {
            if(!crypto_key) {
                sn->error = DAV_ERROR;
                dav_session_set_errstr(sn, "Missing crypto-key property");
                return -1;
            }
            name = util_decrypt_str(sn, crypto_name, crypto_key);
            if(!name) {
                sn->error = DAV_ERROR;
                dav_session_set_errstr(sn, "Cannot decrypt resource name");
                return -1;
            }
        } else {
            cxstring resname = cx_str(util_resource_name(href));
            int nlen = 0;
            char *uname = curl_easy_unescape(
                    sn->handle,
                    resname.ptr,
                    resname.length,
                    &nlen);
            name = dav_session_strdup(sn, uname);
            curl_free(uname);
        }
        
        char *href_cp = dav_session_strdup(sn, href);
        res = dav_resource_new_full(sn, resource->path, name, href_cp);
        
        dav_session_free(sn, name);
    }
    res->iscollection = iscollection;
    
    // add properties
    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
    xmlNode *crypto_prop = NULL;
    
    CxIterator i = cxListIterator(properties);
    cx_foreach(xmlNode*, prop, i) {
        if(!prop->ns) {
            continue;
        }
        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
        
        if (decrypt_props &&
            prop->children &&
            prop->children->type == XML_TEXT_NODE &&
            xstreq(prop->ns->href, DAV_NS))
        {
            if(xstreq(prop->name, "crypto-prop")) {
                crypto_prop = prop;
            }
        }
    }
    cxListDestroy(properties);
    
    if(crypto_prop && crypto_key) {
        char *crypto_prop_content = util_xml_get_text(crypto_prop);
        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
        if(crypto_prop_content && key) {
            CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
            resource_set_crypto_properties(res, cprops);
        }
    }
    
    
    set_davprops(res);
    if(res != resource) {
        resource_add_child(resource, res);
    }
    
    return 0;
}

void set_davprops(DavResource *res) {
    char *cl = dav_get_string_property_ns(res, "DAV:", "getcontentlength");
    char *ct = dav_get_string_property_ns(res, "DAV:", "getcontenttype");
    char *cd = dav_get_string_property_ns(res, "DAV:", "creationdate");
    char *lm = dav_get_string_property_ns(res, "DAV:", "getlastmodified");
    
    res->contenttype = ct;
    if(cl) {
        char *end = NULL;
        res->contentlength = strtoull(cl, &end, 0);
    }
    res->creationdate = util_parse_creationdate(cd);
    res->lastmodified = util_parse_lastmodified(lm);
}

int parse_resource_type(xmlNode *node) {
    int collection = FALSE;
    xmlNode *c = node->children;
    while(c) {
        if(c->type == XML_ELEMENT_NODE) {
            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, "collection")) {
                collection = TRUE;
                break;
            }
        }
        c = c->next;
    }
    return collection;
}


/* ----------------------------- PROPPATCH ----------------------------- */

CURLcode do_proppatch_request(
        DavSession *sn,
        char *lock,
        CxBuffer *request,
        CxBuffer *response)
{       
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH");
    
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: text/xml"); 
    if(lock) {
        char *url = NULL;
        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    }
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
    curl_easy_setopt(handle, CURLOPT_READDATA, request);
    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    
    cxBufferSeek(request, 0, SEEK_SET);
    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
    curl_slist_free_all(headers);
    
    //printf("proppatch: \n%.*s\n", request->size, request->space);
    
    return ret;
}

CxBuffer* create_proppatch_request(DavResourceData *data) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
    namespaces->simple_destructor = free;
    
    char prefix[8];
    int pfxnum = 0;
    if(data->set) {
        CxIterator i = cxListIterator(data->set);
        cx_foreach(DavProperty*, p, i) {
            if(strcmp(p->ns->name, "DAV:")) {
                snprintf(prefix, 8, "x%d", pfxnum++);
                cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
            }
        }
    }
    if(data->remove) {
        CxIterator i = cxListIterator(data->remove);
        cx_foreach(DavProperty*, p, i) {
            if(strcmp(p->ns->name, "DAV:")) {
                snprintf(prefix, 8, "x%d", pfxnum++);
                cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
            }
        }
    }
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // write root element and namespaces
    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\"");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    CxIterator mapi = cxMapIterator(namespaces);
    cx_foreach(CxMapEntry*, entry, mapi) {
        s = CX_STR(" xmlns:");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = cx_str(entry->value);
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = CX_STR("=\"");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = cx_strn(entry->key->data, entry->key->len);
        cxBufferWrite(s.ptr, 1, s.length, buf);
        s = CX_STR("\"");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    s = CX_STR(">\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    if(data->set) {
        s = CX_STR("<D:set>\n<D:prop>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        CxIterator i = cxListIterator(data->set);
        cx_foreach(DavProperty*, property, i) {
            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
            if(!prefix) {
                prefix = "D";
            }
            
            // begin tag
            s = CX_STR("<");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(prefix);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(":");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(property->name);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(">");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            
            // content
            DavXmlNode *content = property->value;
            if(content->type == DAV_XML_TEXT && !content->next) {
                cxBufferWrite(content->content, 1, content->contentlength, buf);
            } else {
                dav_print_node(buf, (cx_write_func)cxBufferWrite, namespaces, content);
            }
            
            // end tag
            s = CX_STR("</");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(prefix);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(":");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(property->name);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(">\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
        s = CX_STR("</D:prop>\n</D:set>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    if(data->remove) {
        s = CX_STR("<D:remove>\n<D:prop>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        CxIterator i = cxListIterator(data->remove);
        cx_foreach(DavProperty*, property, i) {
            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
            
            s = CX_STR("<");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(prefix);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(":");
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = cx_str(property->name);
            cxBufferWrite(s.ptr, 1, s.length, buf);
            s = CX_STR(" />\n");
            cxBufferWrite(s.ptr, 1, s.length, buf);
        }
        s = CX_STR("</D:prop>\n</D:remove>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    
    s = CX_STR("</D:propertyupdate>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    // cleanup namespace map
    cxMapDestroy(namespaces);
    
    return buf;
}

CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:set>\n<D:prop>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    if(DAV_ENCRYPT_NAME(sn)) {
        s = CX_STR("<idav:crypto-name>");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        char *crname = aes_encrypt(name, strlen(name), key);
        cxBufferPutString(buf, crname);
        free(crname);
        s = CX_STR("</idav:crypto-name>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    
    s = CX_STR("<idav:crypto-key>");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    cxBufferPutString(buf, key->name);
    s = CX_STR("</idav:crypto-key>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    if(hash) {
        s = CX_STR("<idav:crypto-hash>");
        cxBufferWrite(s.ptr, 1, s.length, buf);
        cxBufferPutString(buf, hash);
        s = CX_STR("</idav:crypto-hash>\n");
        cxBufferWrite(s.ptr, 1, s.length, buf);
    }
    
    s = CX_STR("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    return buf;
}

/* ----------------------------- PUT ----------------------------- */

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

CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, dav_read_func read_func, dav_seek_func seek_func, size_t length) {
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
    
    // clear headers
    struct curl_slist *headers = NULL;
    if(lock) {
        char *url = NULL;
        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
        char *ltheader = NULL;
        if(create) {
            url = util_parent_path(url);
            ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
            free(url);
        } else {
            ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
        }
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    }
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    CxBuffer *buf = NULL;
    if(!read_func) {
        buf = cxBufferCreate(data, length, cxDefaultAllocator, 0);
        buf->size = length;
        data = buf;
        read_func = (dav_read_func)cxBufferRead;
        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
    } else if(length == 0) {
        headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)1);
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    } else {
        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
    }
    
    curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func);
    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, seek_func);
    curl_easy_setopt(handle, CURLOPT_SEEKDATA, data);
    curl_easy_setopt(handle, CURLOPT_READDATA, data);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    curl_slist_free_all(headers);
    if(buf) {
        cxBufferFree(buf);
    }
    
    return ret;
}

CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response) { 
    CURL *handle = sn->handle;
    struct curl_slist *headers = NULL;
    if(lock) {
        char *url = NULL;
        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    } else {
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
    }
    
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    curl_slist_free_all(headers);
    return ret;
}

CURLcode do_mkcol_request(DavSession *sn, char *lock) {
    CURL *handle = sn->handle;
    struct curl_slist *headers = NULL;
    if(lock) {
        char *url = NULL;
        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
        url = util_parent_path(url);
        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
        free(url);
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    } else {
        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
    }
    
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL");
    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    curl_slist_free_all(headers);
    return ret;
}


CURLcode do_head_request(DavSession *sn) {
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "HEAD");
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
    
    // clear headers
    struct curl_slist *headers = NULL;
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    curl_easy_setopt(handle, CURLOPT_NOBODY, 0L);
    return ret;
}


CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override) { 
    CURL *handle = sn->handle;
    if(copy) {
        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "COPY");
    } else {
        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MOVE");
    }
    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    struct curl_slist *headers = NULL;
    if(lock) {
        char *url = NULL;
        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
    }
    //cxstring deststr = ucx_sprintf("Destination: %s", dest);
    cxmutstr deststr = cx_strcat(2, CX_STR("Destination: "), cx_str(dest));
    headers = curl_slist_append(headers, deststr.ptr);
    if(override) {
        headers = curl_slist_append(headers, "Overwrite: T");
    } else {
        headers = curl_slist_append(headers, "Overwrite: F");
    }
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    free(deststr.ptr);
    curl_slist_free_all(headers);
    headers = NULL;
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    return ret;
}


CxBuffer* create_lock_request(void) {
    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
    cxstring s;
    
    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("<D:lockinfo xmlns:D=\"DAV:\">\n"
          "<D:lockscope><D:exclusive/></D:lockscope>\n"
          "<D:locktype><D:write/></D:locktype>\n"
          "<D:owner><D:href>http://davutils.org/libidav/</D:href></D:owner>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    s = CX_STR("</D:lockinfo>\n");
    cxBufferWrite(s.ptr, 1, s.length, buf);
    
    return buf;
}

int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock) {
    lock->locktoken = NULL;
    lock->timeout = NULL;
    
    xmlDoc *doc = xmlReadMemory(response->space, response->size, NULL, NULL, 0);
    if(!doc) {
        sn->error = DAV_ERROR;
        return -1;
    }
    
    char *timeout = NULL;
    char *locktoken = NULL;
    
    int ret = -1;
    xmlNode *xml_root = xmlDocGetRootElement(doc);
    DavBool lockdiscovery = 0;
    if(xml_root) {
        xmlNode *node = xml_root->children;
        while(node) {
            if(node->type == XML_ELEMENT_NODE) {
                if(xstreq(node->name, "lockdiscovery")) {
                    node = node->children;
                    lockdiscovery = 1;
                    continue;
                }
                
                if(xstreq(node->name, "activelock")) {
                    node = node->children;
                    continue;
                }
                
                if(lockdiscovery) {
                    if(xstreq(node->name, "timeout")) {
                        timeout = util_xml_get_text(node);
                    } else if(xstreq(node->name, "locktoken")) {
                        xmlNode *n = node->children;
                        while(n) {
                            if(xstreq(n->name, "href")) {
                                locktoken = util_xml_get_text(n);
                                break;
                            }
                            n = n->next;
                        }
                    }
                }
            }
            node = node->next;
        }
    }
    
    if(timeout && locktoken) {
        lock->timeout = strdup(timeout);
        lock->locktoken = strdup(locktoken);
        ret = 0;
    }
    
    xmlFreeDoc(doc);
    return ret;
}

CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout) { 
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK");  
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
    request->pos = 0;
    
    // clear headers    
    struct curl_slist *headers = NULL;
    
    if(timeout != 0) {
        cxmutstr thdr;
        if(timeout < 0) {
            thdr = cx_asprintf("%s", "Timeout: Infinite");
        } else {
            thdr = cx_asprintf("Timeout: Second-%u", (unsigned int)timeout);
        }
        headers = curl_slist_append(headers, thdr.ptr);
        free(thdr.ptr);
    }
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    
    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
    
    if(headers) {
        curl_slist_free_all(headers);
    }
    
    return ret;
}

CURLcode do_unlock_request(DavSession *sn, char *locktoken) {   
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "UNLOCK");
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    // set lock-token header
    cxmutstr ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
    struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr);
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    curl_slist_free_all(headers);
    free(ltheader.ptr);
    
    return ret;
}

CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) {
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method);
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
    
    // set lock-token header
    cxmutstr ltheader;
    struct curl_slist *headers = NULL;
    if(locktoken) {
        ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
        headers = curl_slist_append(NULL, ltheader.ptr);
    }
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    CURLcode ret = dav_session_curl_perform(sn, NULL);
    if(locktoken) {
        curl_slist_free_all(headers);
        free(ltheader.ptr);
    }
    
    return ret;
}


CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response) {
    CURL *handle = sn->handle;
    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT");
    
    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
    curl_easy_setopt(handle, CURLOPT_READDATA, request);
    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: text/xml");
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
    
    request->pos = 0;
    response->size = response->pos = 0;
    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
    
    curl_slist_free_all(headers);
    
    return ret;
}

mercurial