libidav/methods.c

changeset 1
b5bb7b3cd597
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/methods.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1365 @@
+/*
+ * 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