libidav/session.c

Sun, 24 Feb 2019 15:06:25 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 24 Feb 2019 15:06:25 +0100
changeset 509
0576dd6409f3
parent 505
481802342fdf
child 591
793af8666b3a
permissions
-rw-r--r--

fixed TODO: use strtol

/*
 * 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 <ucx/buffer.h>
#include <ucx/utils.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;
    }
    sstr_t url = sstr(base_url);
    if(url.length == 0) {
        return NULL;
    }
    DavSession *sn = malloc(sizeof(DavSession));
    memset(sn, 0, sizeof(DavSession));
    sn->mp = ucx_mempool_new(DAV_SESSION_MEMPOOL_SIZE);
    sn->pathcache = ucx_map_new_a(sn->mp->allocator, 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);
    
    // create lock manager
    DavLockManager *locks = ucx_mempool_malloc(sn->mp, sizeof(DavLockManager));
    locks->resource_locks = ucx_map_new_a(sn->mp->allocator, 16);
    locks->collection_locks = NULL;
    sn->locks = locks;

    // set proxy
    DavProxy *proxy = sstrprefix(url, S("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
    context->sessions = ucx_list_append(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) {
    if(sn->base_url) {
        ucx_mempool_free(sn->mp, sn->base_url);
    }
    
    sstr_t url = sstr(base_url);
    if(url.ptr[url.length - 1] == '/') {
        sstr_t url = sstrdup_a(sn->mp->allocator, sstr(base_url));
        sn->base_url = url.ptr;
    } else {
        char *url_str = ucx_mempool_malloc(sn->mp, 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, UcxBuffer *request, UcxBuffer *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 && http_status == 401 && sn->auth_prompt) {
        if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
            if(request) {
                ucx_buffer_seek(request, 0, SEEK_SET);
            }
            if(response) {
                ucx_buffer_seek(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:
            case CURLE_SSL_CACERT:
            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
    UcxList *sessions = sn->context->sessions;
    ssize_t i = ucx_list_find(sessions, sn, ucx_cmp_ptr, 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);
}


void* dav_session_malloc(DavSession *sn, size_t size) {
    return ucx_mempool_malloc(sn->mp, size);
}

void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
    return ucx_mempool_calloc(sn->mp, nelm, size);
}

void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
    return ucx_mempool_realloc(sn->mp, ptr, size);
}

void  dav_session_free(DavSession *sn, void *ptr) {
    ucx_mempool_free(sn->mp, ptr);
}

char* dav_session_strdup(DavSession *sn, const char *str) {
    return sstrdup_a(sn->mp->allocator, sstr((char*)str)).ptr;
}


char* dav_session_create_plain_href(DavSession *sn, 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, char *path) {
    if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
        sstr_t p = sstr(path);
        UcxBuffer *href = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
        UcxBuffer *pbuf = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
        int start = 0;
        int begin = 0;
        
        // check path cache
        char *cp = strdup(path);
        //printf("cp: %s\n", cp);
        while(strlen(cp) > 1) {
            char *cached = ucx_map_cstr_get(sn->pathcache, cp);
            if(cached) {
                start = strlen(cp);
                begin = start;
                ucx_buffer_puts(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
            ucx_buffer_puts(href, util_url_path(sn->base_url));
        }
        
        // create resource for name lookup
        sstr_t rp = sstrdup(sstrn(path, start));
        DavResource *root = dav_resource_new(sn, rp.ptr);
        free(rp.ptr);
        resource_set_href(root, sstrn(href->space, href->pos));
        
        // create request buffer for propfind requests
        UcxBuffer *rqbuf = create_basic_propfind_request();
        
        sstr_t remaining = sstrsubs(p, start);
        ssize_t nelm = 0;
        sstr_t *elms = sstrsplit(remaining, S("/"), &nelm);
        DavResource *res = root;
        ucx_buffer_puts(pbuf, res->path);
        // iterate over all remaining path elements
        for(int i=0;i<nelm;i++) {
            sstr_t elm = elms[i];
            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] != '/') {
                        ucx_buffer_putc(href, '/');
                    }
                    ucx_buffer_putc(pbuf, '/');
                }
                // add last path/href to the cache
                sstr_t pp = sstrn(pbuf->space, pbuf->size);
                sstr_t hh = sstrn(href->space, href->size);
                dav_session_cache_path(sn, pp, hh);
                
                ucx_buffer_write(elm.ptr, 1, elm.length, pbuf);
                if(child) {
                    ucx_buffer_puts(href, util_resource_name(child->href));
                    res = child;
                } else if(DAV_ENCRYPT_NAME(sn)) {
                    char *random_name = util_random_str();
                    ucx_buffer_puts(href, random_name);
                    free(random_name);
                } else {
                    ucx_buffer_puts(href, util_resource_name(path));
                }
            }
            
            // cleanup
            free(elm.ptr);
        }
        free(elms);
        
        // if necessary add a path separator
        if(p.ptr[p.length-1] == '/') {
            if(href->space[href->pos-1] != '/') {
                ucx_buffer_putc(href, '/');
            }
            ucx_buffer_putc(pbuf, '/');
        }
        // add the final path to the cache
        sstr_t pp = sstrn(pbuf->space, pbuf->size);
        sstr_t hh = sstrn(href->space, href->size);
        dav_session_cache_path(sn, pp, hh);
        
        sstr_t href_str = sstrdup_a(
                sn->mp->allocator,
                sstrn(href->space,
                href->size));
        
        // cleanup
        dav_resource_free_all(root);
        ucx_buffer_free(rqbuf);
        ucx_buffer_free(pbuf);
        ucx_buffer_free(href);
        
        return href_str.ptr;
    } else {
        return dav_session_create_plain_href(sn, path);
    }
}

DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, 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, sstr_t path, sstr_t href) {
    char *elm = ucx_map_sstr_get(sn->pathcache, path);
    if(!elm) {
        href = sstrdup_a(sn->mp->allocator, href);
        ucx_map_sstr_put(sn->pathcache, path, href.ptr);
    }
}


DavLock* dav_create_lock(DavSession *sn, 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);
}

int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock) {
    DavLockManager *locks = sn->locks;
    if(ucx_map_cstr_get(locks->resource_locks, path)) {
        return -1;
    }
    
    ucx_map_cstr_put(locks->resource_locks, path, lock);
    return 0;
}

static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) {
    UcxList *next = elm->next;
    if(next) {
        next->prev = newelm;
        newelm->next = next;
    }
    newelm->prev = elm;
    elm->next = newelm;
}

int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock) {
    DavLockManager *locks = sn->locks;
    if(!locks->collection_locks) {
        locks->collection_locks = ucx_list_append_a(
                sn->mp->allocator,
                NULL,
                lock);
        lock->path = dav_session_strdup(sn, path);
        return 0;
    }
    
    UcxList *elm = locks->collection_locks;
    for(;;) {
        DavLock *l = elm->data;
        int cmp = strcmp(path, l->path);
        if(cmp > 0) {
            UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
            lock->path = dav_session_strdup(sn, path);
            insert_lock(sn, elm, newelm);
        } else if(cmp == 0) {
            return -1;
        }
        
        if(elm->next) {
            elm = elm->next;
        } else {
            UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
            lock->path = dav_session_strdup(sn, path);
            ucx_list_concat(elm, newelm);
            break;
        }
    }
    
    return 0;
}

DavLock* dav_get_lock(DavSession *sn, char *path) {
    DavLockManager *locks = sn->locks;
    
    DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path);
    if(lock) {
        return lock;
    }
    
    sstr_t p = sstr(path);
    UCX_FOREACH(elm, locks->collection_locks) {
        DavLock *cl = elm->data;
        int cmd = strcmp(path, cl->path);
        if(cmd == 0) {
            return cl;
        } else if(sstrprefix(p, sstr(cl->path)))  {
            return cl;
        } else if(cmd > 0) {
            break;
        }
    }
    
    return NULL;
}

void dav_remove_lock(DavSession *sn, char *path, DavLock *lock) {
    DavLockManager *locks = sn->locks;
    
    if(ucx_map_cstr_remove(locks->resource_locks, path)) {
        return;
    }
    
    UcxList *rm = NULL;
    UCX_FOREACH(elm, locks->collection_locks) {
        DavLock *cl = elm->data;
        if(cl == lock) {
            rm = elm;
            break;
        }
    }
    
    if(rm) {
        locks->collection_locks = ucx_list_remove_a(
                sn->mp->allocator,
                locks->collection_locks,
                rm);
    }
}

mercurial