libidav/webdav.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 790
7110b37f2a6b
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 <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);
}

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

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

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);
    char *n = strdup(name);
    
    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);
    }
    
    return err;
}

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

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

int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
    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;
    }
    return 0;
}

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

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

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