libidav/session.c

Thu, 21 Dec 2017 19:48:27 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 21 Dec 2017 19:48:27 +0100
changeset 359
bacb54502b24
parent 355
5da2cf15eb44
child 404
5c08b8e14df8
permissions
-rw-r--r--

davql: allow ANYWHERE keyword in SELECT statements

This may seem pointless, but users might want to be explicit about this and the grammar is more consistent.

This commit also adds some no-ops to the functions body of the SET parser, because some day the grammar might allow more clauses after the WHERE clause.

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2016 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;
    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;
    }
    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
    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_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 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_ptrcmp, 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)) {
        // 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_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 {
                    //printf("random\n");
                    char *random_name = util_random_str();
                    ucx_buffer_puts(href, random_name);
                    free(random_name);
                }
            }
            
            // 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