libidav/webdav.c

Wed, 07 Feb 2024 17:11:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 07 Feb 2024 17:11:55 +0100
changeset 807
b41630ecc481
parent 805
bff983370565
child 816
839fefbdedc7
permissions
-rw-r--r--

add support for progress callbacks in dav_store()

/*
 * 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);
}

#ifndef _WIN32

void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
    if (enable) {
        pthread_mutex_init(&ctx->mutex, NULL);
    } else {
        pthread_mutex_destroy(&ctx->mutex);
    }
    ctx->mtsafe = enable;
}

void dav_context_lock(DavContext *ctx) {
    if (ctx->mtsafe) {
        pthread_mutex_lock(&ctx->mutex);
    }
}

void dav_context_unlock(DavContext *ctx) {
    if (ctx->mtsafe) {
        pthread_mutex_unlock(&ctx->mutex);
    }
}

#else

void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
    if (enable) {
        ctx->mutex = CreateMutex(NULL, FALSE, NULL);
    } else {
        CloseHandle(ctx->mutex);
    }
    ctx->mtsafe = enable;
}

void dav_context_lock(DavContext *ctx) {
    if (ctx->mtsafe) {
        WaitForSingleObject(ctx->mutex, INFINITE);
    }
}

void dav_context_unlock(DavContext *ctx) {
    if (ctx->mtsafe) {
        ReleaseMutex(ctx->mutex);
    }
}

#endif

void dav_context_add_key(DavContext *context, DavKey *key) {
    dav_context_lock(context);
    cxMapPut(context->keys, cx_hash_key_str(key->name), key);
    dav_context_unlock(context);
}

DavKey* dav_context_get_key(DavContext *context, const char *name) {
    DavKey *key = NULL;
    dav_context_lock(context);
    if(name) {
        key = cxMapGet(context->keys, cx_hash_key_str(name));
    }
    dav_context_unlock(context);
    return key;
}

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);
    if (!p) {
        free(namespace);
        return 1;
    }
    char *n = strdup(name);
    if (!n) {
        free(namespace);
        free(p);
        return 1;
    }

    dav_context_lock(context);

    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);
    }

    dav_context_unlock(context);
    
    return err;
}

DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
    dav_context_lock(context);
    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix));
    dav_context_unlock(context);
    return ns;
}

DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) {
    dav_context_lock(context);
    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
    dav_context_unlock(context);
    return ns;
}

int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
    dav_context_lock(context);

    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;
    }

    dav_context_unlock(context);
    return 0;
}

int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
    int ret = 0;
    dav_context_lock(context);
    
    DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns));
    if(info) {
        ret = info->encrypt;
    }
    dav_context_unlock(context);
    return ret;
}

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"));
    }
}

int dav_context_add_session(DavContext *context, DavSession *sn) {
    dav_context_lock(context);
    int ret = cxListAdd(context->sessions, sn);
    dav_context_unlock(context);
    return ret;
}

int dav_context_remove_session(DavContext *context, DavSession *sn) {
    int ret = 0;
    dav_context_lock(context);
    CxList *sessions = context->sessions;
    ssize_t i = cxListFind(sessions, sn);
    if(i >= 0) {
        cxListRemove(sessions, i);
    } else {
        ret = 1;
    }
    dav_context_unlock(context);
    return ret;
}


// 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