libidav/methods.c

Thu, 21 Dec 2017 19:48:27 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 21 Dec 2017 19:48:27 +0100
changeset 359
bacb54502b24
parent 354
067ea2315a8a
child 361
b6f2462ee055
permissions
-rw-r--r--

davql: allow ANYWHERE keyword in SELECT statements

This may seem pointless, but users might want to be explicit about this and the grammar is more consistent.

This commit also adds some no-ops to the functions body of the SET parser, because some day the grammar might allow more clauses after the WHERE clause.

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2016 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 <ucx/utils.h>

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

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

CURLcode do_propfind_request(
        DavSession *sn,
        UcxBuffer *request,
        UcxBuffer *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, ucx_buffer_read);
    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    UcxMap *respheaders = ucx_map_new(32);
    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 = ucx_map_cstr_get(respheaders, "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);
    ucx_map_free_content(respheaders, free);
    ucx_map_free(respheaders);
       
    return ret;
}

UcxBuffer* create_allprop_propfind_request(void) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOFREE);
    sstr_t s;
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:propfind xmlns:D=\"DAV:\">\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:allprop/></D:propfind>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    return buf;
}

UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    sstr_t s;
    
    int add_crypto_name = 1;
    int add_crypto_key = 1;
    int add_crypto_hash = 1;
    char *crypto_ns = "idav";
    UcxMap *namespaces = ucx_map_new(8);
    UCX_FOREACH(elm, properties) {
        DavProperty *p = elm->data;
        if(strcmp(p->ns->name, "DAV:")) {
            ucx_map_cstr_put(namespaces, 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)) {
        idav_ns.prefix = "idav";
        idav_ns.name = DAV_NS;
        ucx_map_cstr_put(namespaces, "idav", &idav_ns);
    }
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // write root element and namespaces
    s = S("<D:propfind xmlns:D=\"DAV:\"");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    UcxMapIterator mapi = ucx_map_iterator(namespaces);
    UcxKey key;
    DavNamespace *ns;
    UCX_MAP_FOREACH(key, ns, mapi) {
        s = S(" xmlns:");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstr(ns->prefix);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S("=\"");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstr(ns->name);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S("\"");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    s = S(">\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // default properties
    s = S("<D:prop>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:creationdate />\n<D:getlastmodified />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:getcontentlength />\n<D:getcontenttype />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:resourcetype />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // crypto properties
    if(DAV_CRYPTO(sn)) {
        if(add_crypto_name) {
            ucx_buffer_putc(buf, '<');
            ucx_buffer_puts(buf, crypto_ns);
            s = S(":crypto-name />\n");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
        }
        if(add_crypto_key) {
            ucx_buffer_putc(buf, '<');
            ucx_buffer_puts(buf, crypto_ns);
            s = S(":crypto-key />\n");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
        }
        if(add_crypto_hash) {
            ucx_buffer_putc(buf, '<');
            ucx_buffer_puts(buf, crypto_ns);
            s = S(":crypto-hash />\n");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
        }
    }
    
    // extra properties
    UCX_FOREACH(elm, properties) {
        DavProperty *prop = elm->data;
        s = S("<");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstr(prop->ns->prefix);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S(":");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstr(prop->name);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S(" />\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    
    // end
    s = S("</D:prop>\n</D:propfind>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    ucx_map_free(namespaces);
    return buf;
}

UcxBuffer* create_basic_propfind_request(void) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    sstr_t s;
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
    ucx_buffer_write(s.ptr, 1, s.length, buf);  
    s = S(DAV_NS);
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("\" >\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // properties
    s = S("<D:prop>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("<D:resourcetype />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("<i:crypto-key />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("<i:crypto-name />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("<i:crypto-hash />\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    s = S("</D:prop>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // end
    s = S("</D:propfind>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    return buf;
}

PropfindParser* create_propfind_parser(UcxBuffer *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;
    UcxList *properties = NULL; // xmlNode list
    char *crypto_name = NULL; // name set by crypto-name property
    char *crypto_key = NULL;
    
    result->properties = NULL;
    
    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;
                            }
                            sstr_t status_str = sstr((char*)status_node->content);
                            if(status_str.length < 13) {
                                // error
                                return -1;
                            }
                            status_str = sstrsubsl(status_str, 9, 3);
                            if(!sstrcmp(status_str, S("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) {
                            properties = ucx_list_append(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->properties = properties;
    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) {
        ucx_list_free(result->properties);
    }
}

int hrefeq(DavSession *sn, char *href1, char *href2) {
    sstr_t href_s = sstr(util_url_decode(sn, href1));
    sstr_t href_r = sstr(util_url_decode(sn, href2));
    int ret = 0;
    if(!sstrcmp(href_s, 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(!sstrcmp(href_s, 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(!sstrcmp(href_s, href_r)) {
                ret = 1;
            }
        }
    } 

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


DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *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 {
        sstr_t resname = sstr(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;
    
    // add properties
    UCX_FOREACH(elm, response->properties) {
        xmlNode *prop = elm->data;
        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
    }
    
    set_davprops(res);
}

int parse_response_tag(DavResource *resource, xmlNode *node) {
    DavSession *sn = resource->session;
    
    //DavResource *res = resource;
    DavResource *res = NULL;
    char *href = NULL;
    UcxList *properties = NULL; // 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((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;
                            }
                            sstr_t status_str = sstr((char*)status_node->content);
                            if(status_str.length < 13) {
                                sn->error = DAV_ERROR;
                                return 1;
                            }
                            status_str = sstrsubsl(status_str, 9, 3);
                            if(!sstrcmp(status_str, S("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) {
                            properties = ucx_list_append(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;
    }
    
    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 {
            sstr_t resname = sstr(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);
        }
        
        href = dav_session_strdup(sn, href);
        res = dav_resource_new_full(sn, resource->path, name, href);
        
        dav_session_free(sn, name);
    }
    res->iscollection = iscollection;
    
    // add properties
    UCX_FOREACH(elm, properties) {
        xmlNode *prop = elm->data;
        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
    }
    ucx_list_free(properties);
    
    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,
        UcxBuffer *request,
        UcxBuffer *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 = ucx_sprintf("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, ucx_buffer_read);
    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
    
    ucx_buffer_seek(request, 0, SEEK_SET);
    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
    curl_slist_free_all(headers);
    return ret;
}

UcxBuffer* create_proppatch_request(DavResourceData *data) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    sstr_t s;
    
    UcxMap *namespaces = ucx_map_new(8);
    char prefix[8];
    int pfxnum = 0;
    UCX_FOREACH(elm, data->set) {
        DavProperty *p = elm->data;
        if(strcmp(p->ns->name, "DAV:")) {
            snprintf(prefix, 8, "x%d", pfxnum++);
            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
        }
    }
    UCX_FOREACH(elm, data->remove) {
        DavProperty *p = elm->data;
        if(strcmp(p->ns->name, "DAV:")) {
            snprintf(prefix, 8, "x%d", pfxnum++);
            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
        }
    }
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // write root element and namespaces
    s = S("<D:propertyupdate xmlns:D=\"DAV:\"");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    UcxMapIterator mapi = ucx_map_iterator(namespaces);
    UcxKey key;
    char *pfxval;
    UCX_MAP_FOREACH(key, pfxval, mapi) {
        s = S(" xmlns:");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstr(pfxval);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S("=\"");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = sstrn(key.data, key.len);
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        s = S("\"");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    s = S(">\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    if(data->set) {
        s = S("<D:set>\n<D:prop>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        UCX_FOREACH(elm, data->set) {
            DavProperty *property = elm->data;
            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
            if(!prefix) {
                prefix = "D";
            }
            
            // begin tag
            s = S("<");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(prefix);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(":");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(property->name);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(">");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            
            // content
            DavXmlNode *content = property->value;
            if(content->type == DAV_XML_TEXT) {
                ucx_buffer_write(content->content, 1, content->contentlength, buf);
            } else {
                // TODO: implement
            }
            
            // end tag
            s = S("</");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(prefix);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(":");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(property->name);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(">\n");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
        }
        s = S("</D:prop>\n</D:set>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    if(data->remove) {
        s = S("<D:remove>\n<D:prop>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        UCX_FOREACH(elm, data->remove) {
            DavProperty *property = elm->data;
            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
            
            s = S("<");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(prefix);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(":");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = sstr(property->name);
            ucx_buffer_write(s.ptr, 1, s.length, buf);
            s = S(" />\n");
            ucx_buffer_write(s.ptr, 1, s.length, buf);
        }
        s = S("</D:prop>\n</D:remove>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    
    s = S("</D:propertyupdate>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    // cleanup namespace map
    ucx_map_free_content(namespaces, free);
    ucx_map_free(namespaces);
    
    return buf;
}

UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, char *name, char *hash) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    sstr_t s;
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<D:set>\n<D:prop>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    if(DAV_ENCRYPT_NAME(sn)) {
        s = S("<idav:crypto-name>");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        char *crname = aes_encrypt(name, strlen(name), key);
        ucx_buffer_puts(buf, crname);
        free(crname);
        s = S("</idav:crypto-name>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    
    s = S("<idav:crypto-key>");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    ucx_buffer_puts(buf, key->name);
    s = S("</idav:crypto-key>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    if(hash) {
        s = S("<idav:crypto-hash>");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
        ucx_buffer_puts(buf, hash);
        s = S("</idav:crypto-hash>\n");
        ucx_buffer_write(s.ptr, 1, s.length, buf);
    }
    
    s = S("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
    ucx_buffer_write(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, 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 = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
            free(url);
        } else {
            ltheader = ucx_sprintf("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);
    
    UcxBuffer *buf = NULL;
    if(!read_func) {
        buf = ucx_buffer_new(data, length, 0);
        buf->size = length;
        data = buf;
        read_func = (dav_read_func)ucx_buffer_read;
        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_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) {
        ucx_buffer_free(buf);
    }
    
    return ret;
}

CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *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 = ucx_sprintf("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, ucx_buffer_write);
    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 = ucx_sprintf("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 = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
        headers = curl_slist_append(headers, ltheader);
        free(ltheader);
    }
    //sstr_t deststr = ucx_sprintf("Destination: %s", dest);
    sstr_t deststr = sstrcat(2, S("Destination: "), sstr(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;
}


UcxBuffer* create_lock_request(void) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
    sstr_t s;
    
    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("<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");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    s = S("</D:lockinfo>\n");
    ucx_buffer_write(s.ptr, 1, s.length, buf);
    
    return buf;
}

int parse_lock_response(DavSession *sn, UcxBuffer *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, UcxBuffer *request, UcxBuffer *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) {
        sstr_t thdr;
        if(timeout < 0) {
            thdr = ucx_sprintf("%s", "Timeout: Infinite");
        } else {
            thdr = ucx_sprintf("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, ucx_buffer_read);
    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
    
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
    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
    sstr_t ltheader = ucx_sprintf("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;
}

mercurial