libidav/methods.c

Mon, 11 Nov 2024 22:42:09 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 11 Nov 2024 22:42:09 +0100
changeset 78
ad7ced6cf00b
parent 49
2f71f4ee247a
permissions
-rw-r--r--

implement resourceviewer textfile save button

/*
 * 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);
    cxDefineDestructor(respheaders, 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);
    cxDefineDestructor(namespaces, 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