--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/webdav.c Mon Jan 22 17:27:47 2024 +0100 @@ -0,0 +1,428 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libxml/tree.h> + +#include "utils.h" +#include "webdav.h" +#include "session.h" +#include "methods.h" +#include <cx/buffer.h> +#include <cx/utils.h> +#include <cx/linked_list.h> +#include <cx/hash_map.h> +#include <cx/compare.h> +#include "davqlparser.h" +#include "davqlexec.h" + + +DavContext* dav_context_new(void) { + // initialize + DavContext *context = calloc(1, sizeof(DavContext)); + if(!context) { + return NULL; + } + context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_intptr, CX_STORE_POINTERS); + context->sessions->destructor_data = dav_session_destructor; + context->http_proxy = calloc(1, sizeof(DavProxy)); + if(!context->http_proxy) { + dav_context_destroy(context); + return NULL; + } + context->https_proxy = calloc(1, sizeof(DavProxy)); + if(!context->https_proxy) { + dav_context_destroy(context); + return NULL; + } + context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + if(!context->namespaces) { + dav_context_destroy(context); + return NULL; + } + context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + if(!context->namespaceinfo) { + dav_context_destroy(context); + } + context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + if(!context->keys) { + dav_context_destroy(context); + return NULL; + } + + // add DAV: namespace + if(dav_add_namespace(context, "D", "DAV:")) { + dav_context_destroy(context); + return NULL; + } + + + // add idav namespace + if(dav_add_namespace(context, "idav", DAV_NS)) { + dav_context_destroy(context); + return NULL; + } + + // add idavprops namespace + if(dav_add_namespace(context, "idavprops", DAV_PROPS_NS)) { + dav_context_destroy(context); + return NULL; + } + + return context; +} + +void dav_context_destroy(DavContext *ctx) { + // destroy all sessions assoziated with this context + // ctx->sessions destructor must be dav_session_destructor + cxListDestroy(ctx->sessions); + + if(ctx->http_proxy) { + free(ctx->http_proxy); + } + if(ctx->https_proxy) { + free(ctx->https_proxy); + } + + if(ctx->namespaces) { + CxIterator i = cxMapIteratorValues(ctx->namespaces); + cx_foreach(DavNamespace*, ns, i) { + if(!ns) continue; + if(ns->prefix) { + free(ns->prefix); + } + if(ns->name) { + free(ns->name); + } + free(ns); + } + cxMapDestroy(ctx->namespaces); + } + if(ctx->namespaceinfo) { + // TODO: implement + } + if(ctx->keys) { + CxIterator i = cxMapIteratorValues(ctx->keys); + cx_foreach(DavKey*, key, i) { + if(!key) continue; + if(key->name) { + free(key->name); + } + if(key->data) { + free(key->data); + } + free(key); + } + cxMapDestroy(ctx->keys); + } + + free(ctx); +} + +void dav_context_add_key(DavContext *context, DavKey *key) { + cxMapPut(context->keys, cx_hash_key_str(key->name), key); +} + +DavKey* dav_context_get_key(DavContext *context, const char *name) { + if(name) { + return cxMapGet(context->keys, cx_hash_key_str(name)); + } + return NULL; +} + +int dav_add_namespace(DavContext *context, const char *prefix, const char *name) { + DavNamespace *namespace = malloc(sizeof(DavNamespace)); + if(!namespace) { + return 1; + } + + char *p = strdup(prefix); + char *n = strdup(name); + + int err = 0; + if(p && n) { + namespace->prefix = p; + namespace->name = n; + err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace); + } + + if(err) { + free(namespace); + if(p) free(p); + if(n) free(n); + } + + return err; +} + +DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) { + return cxMapGet(context->namespaces, cx_hash_key_str(prefix)); +} + +DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) { + return cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length)); +} + +int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) { + CxHashKey hkey = cx_hash_key_str(ns); + DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey); + if(!info) { + info = calloc(1, sizeof(DavNSInfo)); + info->encrypt = encrypt; + cxMapPut(context->namespaceinfo, hkey, info); + } else { + info->encrypt = encrypt; + } + return 0; +} + +int dav_namespace_is_encrypted(DavContext *context, const char *ns) { + DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns)); + if(info) { + return info->encrypt; + } + return 0; +} + +void dav_get_property_namespace_str( + DavContext *ctx, + char *prefixed_name, + char **ns, + char **name) +{ + // TODO: rewrite using dav_get_property_ns + + char *pname = strchr(prefixed_name, ':'); + char *pns = "DAV:"; + if(pname) { + DavNamespace *ns = dav_get_namespace_s( + ctx, + cx_strn(prefixed_name, pname-prefixed_name)); + if(ns) { + pns = ns->name; + pname++; + } else { + pns = NULL; + pname = NULL; + } + } else { + pname = prefixed_name; + } + *ns = pns; + *name = pname; +} + +DavNamespace* dav_get_property_namespace( + DavContext *ctx, + char *prefixed_name, + char **name) +{ + char *pname = strchr(prefixed_name, ':'); + if(pname) { + DavNamespace *ns = dav_get_namespace_s( + ctx, + cx_strn(prefixed_name, pname-prefixed_name)); + if(ns) { + *name = pname +1; + return ns; + } else { + *name = NULL; + return NULL; + } + } else { + *name = prefixed_name; + return dav_get_namespace_s(ctx, cx_str("D")); + } +} + +// TODO: add sstr_t version of dav_get_property_ns + +void dav_set_effective_href(DavSession *sn, DavResource *resource) { + char *eff_url; + curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url); + if(eff_url) { + const char *href = util_url_path(eff_url); + if(strcmp(href, resource->href)) { + dav_session_free(sn, resource->href); + resource->href = dav_session_strdup(sn, href); + } + } +} + +DavResource* dav_get(DavSession *sn, char *path, const char *properties) { + CURL *handle = sn->handle; + DavResource *resource = dav_resource_new(sn, path); + util_set_url(sn, dav_resource_get_href(resource)); + + CxList *proplist = NULL; + if(properties) { + proplist = parse_properties_string(sn->context, cx_str(properties)); + } + CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0); + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + + //fwrite(rqbuf->space, 1, rqbuf->size, stdout); + //printf("\n"); + + CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); + long status = 0; + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); + if(ret == CURLE_OK && status == 207) { + dav_set_effective_href(sn, resource); + + //printf("response\n%s\n", rpbuf->space); + // TODO: use PropfindParser + resource = parse_propfind_response(sn, resource, rpbuf); + resource->exists = 1; + sn->error = DAV_OK; + } else { + dav_session_set_error(sn, ret, status); + dav_resource_free(resource); + resource = NULL; + } + + cxBufferFree(rqbuf); + cxBufferFree(rpbuf); + + if(proplist) { + CxIterator i = cxListIterator(proplist); + cx_foreach(DavProperty*, p, i) { + free(p->name); + } + cxListDestroy(proplist); + } + + return resource; +} + + +int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) { + // clean resource properties + DavResourceData *data = root->data; + cxMapClear(data->properties); // TODO: free existing content + + CURL *handle = sn->handle; + util_set_url(sn, dav_resource_get_href(root)); + + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + DavResource *resource = root; + CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); + long status = 0; + long error = 0; + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); + if(ret == CURLE_OK && status == 207) { + //printf("response\n%s\n", rpbuf->space); + dav_set_effective_href(sn, resource); + // TODO: use PropfindParser + resource = parse_propfind_response(sn, resource, rpbuf); + sn->error = DAV_OK; + root->exists = 1; + } else { + dav_session_set_error(sn, ret, status); + error = 1; + } + cxBufferFree(rpbuf); + return error; +} + +CxList* parse_properties_string(DavContext *context, cxstring str) { + CxList *proplist = cxLinkedListCreateSimple(sizeof(DavProperty)); + + CxStrtokCtx tok = cx_strtok(str, cx_str(","), INT_MAX); + cxstring s; + while(cx_strtok_next(&tok, &s)) { + cxstring nsname = cx_strchr(s, ':'); + if(nsname.length > 0) { + cxstring nspre = cx_strsubsl(s, 0, nsname.ptr - s.ptr); + nsname.ptr++; + nsname.length--; + + DavProperty dp; + cxstring pre = cx_strtrim(nspre); + dp.ns = dav_get_namespace_s(context, pre); + dp.name = cx_strdup(nsname).ptr; + dp.value = NULL; + if(dp.ns && dp.name) { + cxListAdd(proplist, &dp); + } else { + free(dp.name); + } + } + } + + return proplist; +} + +DavResource* dav_query(DavSession *sn, char *query, ...) { + DavQLStatement *stmt = dav_parse_statement(cx_str(query)); + if(!stmt) { + sn->error = DAV_ERROR; + return NULL; + } + if(stmt->errorcode != 0) { + sn->error = DAV_QL_ERROR; + dav_free_statement(stmt); + return NULL; + } + + va_list ap; + va_start(ap, query); + DavResult result = dav_statement_execv(sn, stmt, ap); + va_end(ap); + + dav_free_statement(stmt); + + if(result.status == -1) { + if(result.result) { + dav_resource_free(result.result); + result.result = NULL; + } + } + + return result.result; +} + + + + +void dav_verbose_log( + DavSession *sn, + const char *method, + const char *url, + const char *request_body, + size_t request_bodylen, + int status, + const char *response_body, + size_t response_bodylen) +{ + fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status); + fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body); + fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body); +}