diff -r 2483f517c562 -r b5bb7b3cd597 libidav/methods.c --- /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 +#include +#include + +#include "utils.h" +#include "methods.h" +#include "crypto.h" +#include "session.h" +#include "xml.h" + +#include +#include +#include + +#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;ipos = 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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // write root element and namespaces + cx_bprintf(buf, "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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\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, "\n\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // properties + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // end + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // write root element and namespaces + s = CX_STR("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("\n\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("name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(">\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + if(data->remove) { + s = CX_STR("\n\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("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + if(DAV_ENCRYPT_NAME(sn)) { + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + char *crname = aes_encrypt(name, strlen(name), key); + cxBufferPutString(buf, crname); + free(crname); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, key->name); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + if(hash) { + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, hash); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR("\n\n\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n" + "\n" + "\n" + "http://davutils.org/libidav/\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\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; +}