libidav/session.c

changeset 1
b5bb7b3cd597
child 2
fbdfaacc4182
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/session.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,622 @@
+/*
+ * 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 <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+
+#include "utils.h"
+#include "session.h"
+#include "resource.h"
+#include "methods.h"
+
+DavSession* dav_session_new(DavContext *context, char *base_url) {
+    if(!base_url) {
+        return NULL;
+    }
+    cxstring url = cx_str(base_url);
+    if(url.length == 0) {
+        return NULL;
+    }
+    DavSession *sn = malloc(sizeof(DavSession));
+    memset(sn, 0, sizeof(DavSession));
+    sn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL);
+    sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    sn->key = NULL;
+    sn->errorstr = NULL;
+    sn->error = DAV_OK;
+    sn->flags = 0;
+    
+    dav_session_set_baseurl(sn, base_url);
+    
+    sn->handle = curl_easy_init();
+    curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
+    
+    // lock manager is created on-demand
+    sn->locks = NULL;
+
+    // set proxy
+    DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy
+                                                  : context->http_proxy;
+    
+    if (proxy->url) {
+        curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
+        if (proxy->username) {
+            curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
+                proxy->username);
+            if (proxy->password) {
+                curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
+                    proxy->password);
+            } else {
+                // TODO: prompt
+            }
+        }
+        if(proxy->no_proxy) {
+            curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
+                proxy->no_proxy);
+        }
+    }
+    
+    // set url
+#if LIBCURL_VERSION_NUM >= 0x072D00
+    curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
+#endif
+    curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
+    
+    // add to context
+    cxListAdd(context->sessions, sn);
+    sn->context = context;
+    
+    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", user, password);
+        curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
+        free(upwdbuf);
+    }
+}
+
+void dav_session_set_baseurl(DavSession *sn, char *base_url) {
+    const CxAllocator *a = sn->mp->allocator;
+    if(sn->base_url) {
+        cxFree(a, sn->base_url);
+    }
+    
+    cxstring url = cx_str(base_url);
+    if(url.ptr[url.length - 1] == '/') {
+        cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
+        sn->base_url = url_m.ptr;
+    } else {
+        char *url_str = cxMalloc(a, 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;
+    }
+}
+
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
+    sn->key = key;
+    // TODO: review sanity
+    if(flags != 0) {
+        sn->flags |= flags;
+    } else {
+        sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
+    }
+}
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
+    sn->auth_prompt = func;
+    sn->authprompt_userdata = userdata;
+}
+
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) {
+    sn->get_progress = get;
+    sn->put_progress = put;
+    sn->progress_userdata = userdata;
+}
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
+    return dav_session_curl_perform_buf(sn, NULL, NULL, status);
+}
+
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) {
+    CURLcode ret = curl_easy_perform(sn->handle);
+    long http_status;
+    curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+    if(ret == CURLE_OK) {
+        if(sn->logfunc) {
+            char *log_method;
+            char *log_url;
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url);
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method);
+            char *log_reqbody = NULL;
+            size_t log_reqbodylen = 0;
+            char *log_rpbody = NULL;
+            size_t log_rpbodylen = 0;
+            if(request) {
+                log_reqbody = request->space;
+                log_reqbodylen = request->size;
+            }
+            if(response) {
+                log_rpbody = response->space;
+                log_rpbodylen = response->size;
+            }
+            sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
+        }
+        
+        if(http_status == 401 && sn->auth_prompt) {
+            if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
+                if(request) {
+                    cxBufferSeek(request, 0, SEEK_SET);
+                }
+                if(response) {
+                    cxBufferSeek(response, 0, SEEK_SET);
+                }
+                ret = curl_easy_perform(sn->handle);
+                curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+            }
+        }
+        
+    }
+    
+    if(status) {
+        *status = http_status;
+    }
+    return ret;
+}
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->get_progress) {
+        sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->put_progress) {
+        sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
+    if(status > 0) {
+        switch(status) {
+            default: {
+                switch(c) {
+                    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 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
+            case 409: sn->error = DAV_CONFLICT; break;
+            case 412: sn->error = DAV_PRECONDITION_FAILED; break;
+            case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
+            case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
+            case 423: sn->error = DAV_LOCKED; break;
+            case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
+        }
+    } else {
+        switch(c) {
+            case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
+            case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
+            case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
+            case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
+            case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
+            case CURLE_SSL_CONNECT_ERROR:
+            case CURLE_PEER_FAILED_VERIFICATION:
+            case CURLE_SSL_ENGINE_NOTFOUND:
+            case CURLE_SSL_ENGINE_SETFAILED:
+            case CURLE_SSL_CERTPROBLEM:
+            case CURLE_SSL_CIPHER:
+//#ifndef CURLE_SSL_CACERT
+//            case CURLE_SSL_CACERT:
+//#endif
+            case CURLE_SSL_CACERT_BADFILE:
+            case CURLE_SSL_SHUTDOWN_FAILED:
+            case CURLE_SSL_CRL_BADFILE:
+            case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
+            default: sn->error = DAV_ERROR; break;
+        }
+    }
+    if(c != CURLE_OK) {
+        dav_session_set_errstr(sn, curl_easy_strerror(c));
+    } else {
+        dav_session_set_errstr(sn, NULL);
+    }
+}
+
+void dav_session_set_errstr(DavSession *sn, const char *str) {
+    if(sn->errorstr) {
+        dav_session_free(sn, sn->errorstr);
+    }
+    char *errstr = NULL;
+    if(str) {
+        errstr = dav_session_strdup(sn, str);
+    }
+    sn->errorstr = errstr;
+}
+
+void dav_session_destroy(DavSession *sn) { 
+    // remove session from context
+    CxList *sessions = sn->context->sessions;
+    ssize_t i = cxListFind(sessions, sn);
+    if(i >= 0) {
+        cxListRemove(sessions, i);
+    } else {
+        printf("Error: session not found in ctx->sessions\n");
+        dav_session_destructor(sn);
+    }
+}
+
+void dav_session_destructor(DavSession *sn) {
+    cxMempoolDestroy(sn->mp);
+    curl_easy_cleanup(sn->handle);
+    free(sn);
+}
+
+
+void* dav_session_malloc(DavSession *sn, size_t size) {
+    return cxMalloc(sn->mp->allocator, size);
+}
+
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
+    return cxCalloc(sn->mp->allocator, nelm, size);
+}
+
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
+    return cxRealloc(sn->mp->allocator, ptr, size);
+}
+
+void  dav_session_free(DavSession *sn, void *ptr) {
+    cxFree(sn->mp->allocator, ptr);
+}
+
+char* dav_session_strdup(DavSession *sn, const char *str) {
+    return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr;
+}
+
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path) {
+    if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
+        // non encrypted file names
+        char *url = util_path_to_url(sn, path);
+        char *href = dav_session_strdup(sn, util_url_path(url));
+        free(url);
+        return href;
+    } else {
+        return NULL;
+    }
+}
+
+char* dav_session_get_href(DavSession *sn, const char *path) {
+    if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
+        cxstring p = cx_str(path);
+        CxBuffer href;
+        CxBuffer pbuf;
+        cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        int start = 0;
+        int begin = 0;
+        
+        // check path cache
+        char *cp = strdup(path);
+        //printf("cp: %s\n", cp);
+        while(strlen(cp) > 1) {
+            char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
+            if(cached) {
+                start = strlen(cp);
+                begin = start;
+                cxBufferPutString(&href, cached);
+                break;
+            } else {
+                // check, if the parent path is cached
+                char *f = cp;
+                cp = util_parent_path(cp);
+                free(f);
+            }
+        }
+        free(cp);
+        if(href.pos == 0) {
+            // if there are no cached elements we have to add the base url path
+            // to the href buffer
+            cxBufferPutString(&href, util_url_path(sn->base_url));
+        }
+        
+        // create resource for name lookup
+        cxmutstr rp = cx_strdup(cx_strn(path, start));
+        DavResource *root = dav_resource_new(sn, rp.ptr);
+        free(rp.ptr);
+        resource_set_href(root, cx_strn(href.space, href.pos));
+        
+        // create request buffer for propfind requests
+        CxBuffer *rqbuf = create_basic_propfind_request();
+        
+        cxstring remaining = cx_strsubs(p, start);
+        CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX);
+        DavResource *res = root;
+        cxBufferPutString(&pbuf, res->path);
+        // iterate over all remaining path elements
+        cxstring elm;
+        while(cx_strtok_next(&elms, &elm)) {
+            if(elm.length > 0) {
+                //printf("elm: %.*s\n", elm.length, elm.ptr);
+                DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
+                
+                // if necessary add a path separator
+                if(pbuf.space[pbuf.pos-1] != '/') {
+                    if(href.space[href.pos-1] != '/') {
+                        cxBufferPut(&href, '/');
+                    }
+                    cxBufferPut(&pbuf, '/');
+                }
+                // add last path/href to the cache
+                cxstring pp = cx_strn(pbuf.space, pbuf.size);
+                cxstring hh = cx_strn(href.space, href.size);
+                dav_session_cache_path(sn, pp, hh);
+                
+                cxBufferWrite(elm.ptr, 1, elm.length, &pbuf);
+                if(child) {
+                    // href is already URL encoded, so don't encode again
+                    cxBufferPutString(&href, util_resource_name(child->href));
+                    res = child;
+                } else if(DAV_ENCRYPT_NAME(sn)) {
+                    char *random_name = util_random_str();
+                    cxBufferPutString(&href, random_name);
+                    free(random_name);
+                } else {
+                    // path is not URL encoded, so we have to do this here
+                    cxstring resname = cx_str(util_resource_name((const char*)path));
+                    // the name of collections ends with
+                    // a trailing slash, which MUST NOT be encoded
+                    if(resname.ptr[resname.length-1] == '/') {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length-1);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        cxBufferPut(&href, '/');
+                        curl_free(esc);
+                    } else  {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        curl_free(esc);
+                    }
+                }
+            }
+        }
+        
+        // if necessary add a path separator
+        if(p.ptr[p.length-1] == '/') {
+            if(href.space[href.pos-1] != '/') {
+                cxBufferPut(&href, '/');
+            }
+            cxBufferPut(&pbuf, '/');
+        }
+        // add the final path to the cache
+        cxstring pp = cx_strn(pbuf.space, pbuf.size);
+        cxstring hh = cx_strn(href.space, href.size);
+        dav_session_cache_path(sn, pp, hh);
+        
+        cxmutstr href_str = cx_strdup_a(
+                sn->mp->allocator,
+                cx_strn(href.space, href.size));
+        
+        // cleanup
+        dav_resource_free_all(root);
+        cxBufferFree(rqbuf);
+        
+        cxBufferDestroy(&pbuf);
+        cxBufferDestroy(&href);
+        
+        return href_str.ptr;
+    } else {
+        return dav_session_create_plain_href(sn, path);
+    }
+}
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) {
+    if(res && !dav_propfind(sn, res, rqbuf)) {
+        DavResource *child = res->children;
+        while(child) {
+            if(!strcmp(child->name, name)) {
+                return child;
+            }
+            child = child->next;
+        }
+    }
+    return NULL;
+}
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
+    CxHashKey path_key = cx_hash_key(path.ptr, path.length);
+    char *elm = cxMapGet(sn->pathcache, path_key);
+    if(!elm) {
+        cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
+        cxMapPut(sn->pathcache, path_key, href_s.ptr);
+    }
+}
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) {
+    DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
+    lock->path = NULL;
+    lock->token = dav_session_strdup(sn, token);
+    
+    // TODO: timeout
+    
+    return lock;
+}
+
+void dav_destroy_lock(DavSession *sn, DavLock *lock) {
+    dav_session_free(sn, lock->token);
+    if(lock->path) {
+        dav_session_free(sn, lock->path);
+    }
+    dav_session_free(sn, lock);
+}
+
+
+static int dav_lock_cmp(void const *left, void const *right) {
+    const DavLock *l = left;
+    const DavLock *r = right;
+    return strcmp(l->path, r->path);
+}
+
+static int create_lock_manager(DavSession *sn) {
+    // create lock manager
+    DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager));
+    locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16);
+    locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS);
+    sn->locks = locks;
+    return 0;
+}
+
+static DavLockManager* get_lock_manager(DavSession *sn) {
+    DavLockManager *locks = sn->locks;
+    if(!locks) {
+        if(create_lock_manager(sn)) {
+            return NULL;
+        }
+        locks = sn->locks;
+    }
+    return locks;
+}
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    CxHashKey path_key = cx_hash_key_str(path);
+    if(cxMapGet(locks->resource_locks, path_key)) {
+        return -1;
+    }
+    
+    cxMapPut(locks->resource_locks, path_key, lock);
+    return 0;
+}
+
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    lock->path = dav_session_strdup(sn, path);
+    cxListAdd(locks->collection_locks, lock);
+    cxListSort(locks->collection_locks);
+    
+    return 0;
+}
+
+DavLock* dav_get_lock(DavSession *sn, const char *path) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return NULL;
+    }
+    
+    cxstring p = cx_str(path);
+    
+    DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
+    if(lock) {
+        return lock;
+    }
+    
+    CxIterator i = cxListIterator(locks->collection_locks);
+    cx_foreach(DavLock*, col_lock, i) {
+        int cmd = strcmp(path, col_lock->path);
+        if(cmd == 0) {
+            return col_lock;
+        } else if(cx_strprefix(p, cx_str(col_lock->path)))  {
+            return col_lock;
+        } else if(cmd > 0) {
+            break;
+        }
+    }
+    
+    return NULL;
+}
+
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return;
+    }
+    
+    if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
+        return;
+    }
+    
+    CxMutIterator i = cxListMutIterator(locks->collection_locks);
+    int rm = 0;
+    cx_foreach(DavLock* , cl, i) {
+        if(rm) {
+            break;
+        }
+        if(cl == lock) {
+            cxIteratorFlagRemoval(i);
+            rm = 1;
+        }
+    }
+}

mercurial