Mon, 19 Aug 2013 14:23:00 +0200
added more error messages
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013 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 <libxml/tree.h> #include "utils.h" #include "webdav.h" #include "methods.h" #include "davql.h" #include "ucx/buffer.h" #include "ucx/utils.h" #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) DavContext* dav_context_new() { DavContext *context = malloc(sizeof(DavContext)); if(!context) { return NULL; } context->sessions = NULL; context->namespaces = ucx_map_new(16); context->http_proxy = NULL; context->https_proxy = NULL; context->no_proxy = NULL; if(!context->namespaces) { free(context); return NULL; } DavNamespace *davns = malloc(sizeof(DavNamespace)); if(!davns) { ucx_map_free(context->namespaces); free(context); return NULL; } davns->prefix = "D"; davns->name = "DAV:"; if(ucx_map_cstr_put(context->namespaces, "D", davns)) { free(davns); ucx_map_free(context->namespaces); free(context); return NULL; } return context; } void dav_context_destroy(DavContext *ctx) { // destroy all sessions assoziated with this context UCX_FOREACH(elm, ctx->sessions) { dav_session_destroy(elm->data); } UcxMapIterator i = ucx_map_iterator(ctx->namespaces); UcxKey k; DavNamespace *ns; // TODO: free map elements ucx_map_free(ctx->namespaces); free(ctx); } int dav_add_namespace(DavContext *context, char *prefix, char *name) { DavNamespace *namespace = malloc(sizeof(DavNamespace)); if(!namespace) { return 1; } namespace->prefix = strdup(prefix); namespace->name = strdup(name); return ucx_map_cstr_put(context->namespaces, prefix, namespace); } DavNamespace* dav_get_namespace(DavContext *context, char *prefix) { return ucx_map_cstr_get(context->namespaces, prefix); } DavNamespace* dav_get_namespace_s(DavContext *context, sstr_t prefix) { return ucx_map_sstr_get(context->namespaces, prefix); } DavSession* dav_session_new(DavContext *context, char *base_url) { if(!base_url) { return NULL; } sstr_t url = sstr(base_url); if(url.length == 0) { return NULL; } DavSession *sn = malloc(sizeof(DavSession)); sn->errorstr = NULL; sn->error = CURLE_OK; if(url.ptr[url.length - 1] == '/') { sn->base_url = strdup(base_url); } else { char *url_str = malloc(url.length + 2); memcpy(url_str, base_url, url.length); url_str[url.length] = '/'; url_str[url.length + 1] = '\0'; sn->base_url = url_str; } sn->context = context; sn->handle = curl_easy_init(); // set proxy if(sstrprefix(url, S("https"))) { if(context->https_proxy) { //printf("use https_proxy: %s\n", context->https_proxy); curl_easy_setopt(sn->handle, CURLOPT_PROXY, context->https_proxy); } } else { if(context->http_proxy) { //printf("use http_proxy: %s\n", context->http_proxy); curl_easy_setopt(sn->handle, CURLOPT_PROXY, context->http_proxy); } } if(context->no_proxy) { //printf("use no_proxy: %s\n", context->no_proxy); curl_easy_setopt(sn->handle, CURLOPT_NOPROXY, context->no_proxy); } // set url curl_easy_setopt(sn->handle, CURLOPT_URL, base_url); sn->mp = ucx_mempool_new(1024); sn->allocator = ucx_mempool_allocator(sn->mp); context->sessions = ucx_list_append(context->sessions, sn); return sn; } DavSession* dav_session_new_auth(DavContext *context, char *base_url, char *user, char *password) { DavSession *sn = dav_session_new(context, base_url); if(!sn) { return NULL; } dav_session_set_auth(sn, user, password); return sn; } void dav_session_set_auth(DavSession *sn, char *user, char *password) { if(user && password) { size_t ulen = strlen(user); size_t plen = strlen(password); size_t upwdlen = ulen + plen + 2; char *upwdbuf = malloc(upwdlen); snprintf(upwdbuf, upwdlen, "%s:%s\0", user, password); curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf); free(upwdbuf); } } void session_set_error(DavSession *sn, CURLcode c, int status) { if(status > 0) { switch(status) { default: sn->error = DAV_ERROR; break; case 401: sn->error = DAV_UNAUTHORIZED; break; case 403: sn->error = DAV_FORBIDDEN; break; case 404: sn->error = DAV_NOT_FOUND; break; case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break; case 409: sn->error = DAV_CONFLICT; break; } } else { sn->error = DAV_ERROR; } if(c != CURLE_OK) { sn->errorstr = curl_easy_strerror(c); } else { sn->errorstr = NULL; } } void dav_session_destroy(DavSession *sn) { // remove session from context UcxList *sessions = sn->context->sessions; ssize_t i = ucx_list_find(sessions, sn, ucx_ptrcmp, NULL); if(i > 0) { UcxList *elm = ucx_list_get(sessions, i); if(elm) { sn->context->sessions = ucx_list_remove(sessions, elm); } } ucx_mempool_destroy(sn->mp); curl_easy_cleanup(sn->handle); free(sn->base_url); free(sn); } DavResource* dav_get(DavSession *sn, char *path, char *properties) { char *url = util_concat_path(sn->base_url, path); CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); UcxList *proplist = NULL; if(properties) { proplist = parse_properties_string(sn->context, sstr(properties)); } UcxBuffer *rqbuf = create_propfind_request(proplist); UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); //fwrite(rqbuf->space, 1, rqbuf->size, stdout); //printf("\n"); DavResource *resource = NULL; CURLcode ret = do_propfind_request(handle, rqbuf, rpbuf); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && status == 207) { //printf("response\n%s\n", rpbuf->space); resource = parse_propfind_response(sn, NULL, rpbuf); sn->error = DAV_OK; } else { session_set_error(sn, ret, status); } return resource; } DavResource* dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf, char *path) { char *url = util_concat_path(sn->base_url, path); CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); DavResource *resource = root; CURLcode ret = do_propfind_request(handle, rqbuf, rpbuf); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && status == 207) { //printf("response\n%s\n", rpbuf->space); resource = parse_propfind_response(sn, resource, rpbuf); sn->error = DAV_OK; } else { session_set_error(sn, ret, status); resource = NULL; } ucx_buffer_free(rpbuf); return resource; } UcxList* propfind_stack_push(UcxList *stack, DavResource *children) { while(children) { if(children->iscollection) { stack = ucx_list_prepend(stack, children); } children = children->next; } return stack; } DavResource* dav_get2(DavSession *sn, DavGetQuery *query) { char *path; int depth = 0; if(parse_path_query(query->from, &path, &depth)) { sn->error = DAV_ERROR; return NULL; } sstr_t ps = query->properties; UcxBuffer *rqbuf; if(!sstrcmp(ps, S("*"))) { rqbuf = create_allprop_propfind_request(); } else if(!sstrcmp(ps, S("-"))) { rqbuf = create_propfind_request(NULL); } else { UcxList *proplist = parse_properties_string(sn->context, ps); rqbuf = create_propfind_request(proplist); } //fwrite(rqbuf->space, 1, rqbuf->size, stdout); //printf("\n"); DavResource *resource = dav_propfind(sn, NULL, rqbuf, path); free(path); int error = 0; if(resource && depth == -1) { UcxList *stack = NULL; // stack with davResource* elements stack = propfind_stack_push(stack, resource->children); while(stack) { DavResource *sr = stack->data; // get first element from the stack stack = ucx_list_remove(stack, stack); // remove first element // do propfind request for sr sr = dav_propfind(sn, sr, rqbuf, sr->path); if(!sr) { error = 1; printf("subrequest failed\n"); break; } stack = propfind_stack_push(stack, sr->children); // add children } } return resource; } UcxList* parse_properties_string(DavContext *context, sstr_t str) { UcxList *proplist = NULL; size_t nprops; sstr_t *props = sstrsplit(str, S(","), &nprops); for(int i=0;i<nprops;i++) { sstr_t s = props[i]; sstr_t nsname = sstrchr(s, ':'); if(nsname.length > 0) { sstr_t nspre = sstrsubsl(s, 0, nsname.ptr - s.ptr); nsname.ptr++; nsname.length--; DavProperty *dp = malloc(sizeof(DavProperty)); sstr_t pre = sstrdup(sstrtrim(nspre)); dp->ns = dav_get_namespace(context, pre.ptr); free(pre.ptr); dp->name = sstrdup(nsname).ptr; if(dp->ns && dp->name) { proplist = ucx_list_append(proplist, dp); } else { free(dp->name); free(dp); } } free(s.ptr); } free(props); return proplist; } DavResource* dav_query(DavSession *sn, char *query, ...) { va_list ap; va_start(ap, query); DavQuery q = dav_ql_parse(query, ap); va_end(ap); DavResource *res = NULL; switch(q.command) { case DAV_QUERY_GET: { res = dav_get2(sn, q.command_data); free_get_query(q.command_data); break; } } return res; } DavResource* dav_resource_new(DavSession *sn, char *path) { char *url = util_concat_path(sn->base_url, path); char *href = util_url_path(url); DavResource *res = resource_new_href(sn, href); free(url); return res; } DavResource* resource_new_href(DavSession *sn, char *href) { UcxMempool *mp = sn->mp; UcxAllocator *a = sn->allocator; DavResource *res = ucx_mempool_calloc(mp, 1, sizeof(DavResource)); res->session = sn; // set name, path and href resource_set_info(res, href); // initialize node data res->data = node_data_new(sn); return res; } void resource_add_property(DavResource *res, char *ns, char *name, char *value) { if(!value) { return; } UcxMempool *mp = res->session->mp; UcxAllocator *a = res->session->allocator; UcxKey key = dav_property_key(ns, name); sstr_t v = sstrdup_a(a, sstr(value)); ucx_map_put(res->data->properties, key, v.ptr); free(key.data); } char* resource_get_property(DavResource *res, char *ns, char *name) { UcxKey key = dav_property_key(ns, name); return ucx_map_get(res->data->properties, key); } UcxKey dav_property_key(char *ns, char *name) { sstr_t ns_str = sstr(ns); sstr_t name_str = sstr(name); sstr_t key; key.length = ns_str.length + name_str.length + 1; key.ptr = malloc(key.length + 1); key = sstrncat(key, 3, ns_str, S(" "), name_str); return ucx_key(key.ptr, key.length); } void resource_add_child(DavResource *parent, DavResource *child) { child->next = NULL; if(parent->children) { DavResource *last = parent->children; while(last->next) { last = last->next; } last->next = child; child->prev = last; } else { child->prev = NULL; parent->children = child; } } void resource_set_info(DavResource *res, char *href_str) { char *url_str = NULL; curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str); sstr_t name = sstr(util_resource_name(href_str)); sstr_t href = sstr(href_str); sstr_t base_href = sstr(util_url_path(res->session->base_url)); sstr_t path = sstrsubs(href, base_href.length - 1); UcxAllocator *a = res->session->allocator; res->name = sstrdup_a(a, name).ptr; res->href = sstrdup_a(a, href).ptr; res->path = sstrdup_a(a, path).ptr; } DavNodeData* node_data_new(DavSession *sn) { DavNodeData *data = ucx_mempool_malloc(sn->mp, sizeof(DavNodeData)); if(!data) { return NULL; } data->properties = ucx_map_new_a(sn->allocator, 32); data->set = NULL; data->remove = NULL; data->content = NULL; data->read = NULL; data->length = 0; return data; } int dav_load(DavResource *res) { DavSession *sn = res->session; // clean map UcxKey key; void *value; UcxMapIterator i = ucx_map_iterator(res->data->properties); UCX_MAP_FOREACH(key, value, i) { ucx_map_remove(res->data->properties, key); } char *url = util_concat_path(sn->base_url, res->path); CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); UcxBuffer *rqbuf = create_allprop_propfind_request(); UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); //fwrite(rpbuf->space, 1, rpbuf->size, stdout); //printf("\n"); CURLcode ret = do_propfind_request(handle, rqbuf, rpbuf); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK) { //printf("response\n%s\n", rpbuf->space); // TODO: use parse_propfind_response() xmlDoc *doc = xmlReadMemory(rpbuf->space, rpbuf->size, url, NULL, 0); if(!doc) { return 1; } 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(res, node); } } node = node->next; } set_davprops(res); } else { session_set_error(sn, ret, status); } return 0; } int dav_store(DavResource *res) { DavSession *sn = res->session; char *url = util_concat_path(sn->base_url, res->path); CURL *handle = res->session->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); DavNodeData *data = res->data; // store content if(data->content) { CURLcode ret = do_put_request(handle, data->content, data->read, data->length); int status = 0; curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && (status >= 200 && status < 300)) { res->session->error = 0; // cleanup node data if(!data->read) { ucx_mempool_free(sn->mp, data->content); } data->content = NULL; data->read = NULL; data->length = 0; } else { session_set_error(sn, ret, status); return 1; } } // store properties if(data->set || data->remove) { UcxBuffer *request = create_proppatch_request(data); UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); CURLcode ret = do_proppatch_request(handle, request, response); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && status == 207) { //printf("%s\n", response->space); // TODO: parse response // TODO: cleanup node data correctly data->set = NULL; data->remove = NULL; } else { session_set_error(sn, ret, status); return 1; } } sn->error = DAV_OK; return 0; } char* dav_get_property_ns(DavResource *res, char *ns, char *name) { char *property = resource_get_property(res, ns, name); if(property && res->data->remove) { // TODO } else if(!property && res->data->set) { // TODO } return property; } void dav_set_property_ns(DavResource *res, char *ns, char *name, char *value) { UcxAllocator *a = res->session->allocator; DavProperty *property = a->malloc(a->pool, sizeof(DavProperty)); property->name = sstrdup_a(a, sstr(name)).ptr; property->value = sstrdup_a(a, sstr(value)).ptr; DavNamespace *namespace = a->malloc(a->pool, sizeof(DavNamespace)); namespace->prefix = NULL; namespace->name = sstrdup_a(a, sstr(ns)).ptr; property->ns = namespace; res->data->set = ucx_list_append_a(a, res->data->set, property); } void dav_remove_property_ns(DavResource *res, char *ns, char *name, char *value) { UcxAllocator *a = res->session->allocator; DavProperty *property = a->malloc(a->pool, sizeof(DavProperty)); property->name = sstrdup_a(a, sstr(name)).ptr; property->value = NULL; DavNamespace *namespace = a->malloc(a->pool, sizeof(DavNamespace)); namespace->prefix = NULL; namespace->name = sstrdup_a(a, sstr(ns)).ptr; property->ns = namespace; res->data->remove = ucx_list_append_a(a, res->data->remove, property); } void dav_set_content(DavResource *res, void *stream, dav_read_func read_func) { DavNodeData *data = res->data; data->content = stream; data->read = read_func; data->length = 0; } void dav_set_content_data(DavResource *res, char *content, size_t length) { DavSession *sn = res->session; DavNodeData *data = res->data; data->content = content; data->read = NULL; data->length = length; } int dav_get_content(DavResource *res, void *stream, dav_write_func write_func) { char *url = util_concat_path(res->session->base_url, res->path); CURL *handle = res->session->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(handle, CURLOPT_PUT, 0L); curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_func); curl_easy_setopt(handle, CURLOPT_WRITEDATA, stream); CURLcode ret = curl_easy_perform(handle); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && (status >= 200 && status < 300)) { res->session->error = DAV_OK; return 0; } else { session_set_error(res->session, ret, status); return 1; } } int dav_delete(DavResource *res) { char *url = util_concat_path(res->session->base_url, res->path); CURL *handle = res->session->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); UcxBuffer *response = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); CURLcode ret = do_delete_request(handle, response); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && (status >= 200 && status < 300)) { res->session->error = DAV_OK; // TODO: parse response // TODO: free res return 0; } else { session_set_error(res->session, ret, status); return 1; } } int dav_create(DavResource *res) { char *url = util_concat_path(res->session->base_url, res->path); char *parent = util_parent_path(res->path); DavSession *sn = res->session; DavResource *parent_res = dav_get(sn, parent, NULL); if(!parent_res && sn->error == DAV_NOT_FOUND) { parent_res = dav_resource_new(sn, parent); parent_res->iscollection = 1; int r = dav_create(parent_res); if(r) { free(parent); return r; } } else if(parent_res && !res->iscollection) { sn->error = DAV_FORBIDDEN; return 1; } else if(sn->error != DAV_OK) { return 1; } CURL *handle = res->session->handle; curl_easy_setopt(handle, CURLOPT_URL, url); free(url); free(parent); // TODO: check iscollection and create empty resource or collection CURLcode ret = do_mkcol_request(handle); int status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && (status >= 200 && status < 300)) { res->session->error = DAV_OK; return 0; } else { session_set_error(res->session, ret, status); return 1; } }