libidav/webdav.c

changeset 1
b5bb7b3cd597
child 18
af411868ab9b
--- /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);
+}

mercurial