libidav/session.c

Sun, 17 Dec 2023 14:25:34 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 14:25:34 +0100
changeset 797
edbb20b1438d
parent 796
81e0f67386a6
child 805
bff983370565
permissions
-rw-r--r--

[Makefile] fix missing rules preventing dry-runs

We have to support dry-runs, because many IDEs are using
dry-runs to collect build information.

Some rules have dependencies that expect certain files or
directories to be just present. We added respective build
rules which invoke the test program. This way, the behavior
when running make normally is exactly the same, but dry-runs
are also not failing now.

/*
 * 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/basic_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 = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
    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, const char *user, const char *password) {
    if(user && password) {
        dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
    }
}

void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) {
    if(user.length > 0 && password.length > 0) {
        size_t upwdlen = user.length + password.length + 2;
        char *upwdbuf = malloc(upwdlen);
        snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr);
        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