src/server/webdav/operation.c

Fri, 17 Jan 2020 22:23:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 17 Jan 2020 22:23:30 +0100
branch
webdav
changeset 231
4714468b9b7e
parent 222
5f05e56cb8e2
child 236
e81d3e517b57
permissions
-rw-r--r--

implement multistatus writer

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2019 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 <ucx/list.h>

#include "../daemon/session.h"

#include "operation.h"

#define WEBDAV_PATH_MAX 8192

WebdavOperation* webdav_operation_create(
        Session *sn,
        Request *rq,
        WebdavBackend *dav,
        UcxList *requests,
        WebdavResponse *response)
{
    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
    ZERO(op, sizeof(WebdavOperation));
    op->dav = dav;
    op->sn = sn;
    op->rq = rq;
    op->requests = requests;
    op->response = response;
    
    response->op = op;
    
    return op;
}

int webdav_op_propfind_begin(
        WebdavOperation *op,
        const char *href,
        VFS_DIR parent,
        struct stat *s)
{
    // create WebdavResource object for requested resource
    WebdavResource *resource = op->response->addresource(op->response, href);
    if(!resource) {
        return REQ_ABORTED;
    }
    
    // store data that we need when the resource will be closed
    op->stat = s;
    op->parent = parent;
    
    // get first propfind object
    WebdavPropfindRequest *propfind = op->requests->data;
    
    // execute propfind_do of the first backend for the first resource
    int ret = REQ_PROCEED;
    if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) {
        ret = REQ_ABORTED;
    } else {
        // propfind_do successful, close resource if needed
        // closing the resource will execute propfind_do of all remaining
        // backends
        if(!resource->isclosed) {
            ret = resource->close(resource);
        }
    }
    
    return ret;
}

typedef struct PathSearchElm {
    char   *href;
    char   *path;
    size_t hreflen;
    size_t pathlen;
} PathSearchElm;

/*
 * concats base + / + elm
 * if baseinit is true, only elm is copied
 */
static int path_buf_concat(
        pool_handle_t *pool,
        char **buf,
        size_t * restrict len,
        WSBool * restrict baseinit,
        const char *base,
        size_t baselen,
        const char *elm,
        size_t elmlen)
{
    if(base[baselen-1] == '/') {
        baselen--;
    }
    
    size_t newlen = baselen + elmlen + 1;
    if(newlen > WEBDAV_PATH_MAX) {
        log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
        return 1;
    }
    
    // check if new path + terminator fits in the buffer
    if(newlen + 1 > *len) {
        *len = newlen + 128;
        char *newbuf = pool_realloc(pool, *buf, newlen);
        if(newbuf) {
            log_ereport(LOG_FAILURE, "webdav: path memory allocation failed");
            return 1;
        }
        *baseinit = FALSE;
        
        *buf = newbuf;
    }
    
    // if baseinit is true, the parent is already in the buffer
    // and we don't need to memcpy it again
    if(!(*baseinit)) {
        memcpy(*buf, base, baselen);
        (*buf)[baselen] = '/';
        *baseinit = TRUE;
    }
    // copy child and terminate string
    memcpy((*buf) + baselen + 1, elm, elmlen);
    (*buf)[newlen] = '\0';
    
    return 0;
}

int webdav_op_propfind_children(
        WebdavOperation *op,
        VFSContext *vfs,
        const char *href,
        const char *path)
{
    WebdavPropfindRequest *request = op->requests->data;
    
    UcxAllocator *a = session_get_allocator(request->sn);
    pool_handle_t *pool = request->sn->pool;
    
    PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm));
    start_elm->href = pool_strdup(pool, href);
    start_elm->path = pool_strdup(pool, path);
    start_elm->hreflen = strlen(href);
    start_elm->pathlen = strlen(path);
    
    UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm);
    UcxList *stack_end = stack;
    if(!stack) {
        return 1;
    }
    
    // reusable buffer for full child path and href
    char *newpath = pool_malloc(pool, 256);
    size_t newpathlen = 256;
    
    char *newhref = pool_malloc(pool, 256);
    size_t newhreflen = 256;
    
    int err = 0;
    while(stack && !err) {
        PathSearchElm *cur_elm = stack->data;
        
        // when newpath is initialized with the parent path
        // set path_buf_init to TRUE
        WSBool href_buf_init = FALSE;
        WSBool path_buf_init = FALSE;
        
        VFS_DIR dir = vfs_opendir(vfs, cur_elm->path);
        if(!dir) {
            log_ereport(
                    LOG_FAILURE,
                    "webdav: propfind: cannot open directory %d",
                    vfs->vfs_errno);
            err = 1;
            break;
        }
        
        VFS_ENTRY f;
        while(vfs_readdir_stat(dir, &f)) {
            if(f.stat_errno != 0) {
                continue;
            }
            
            size_t child_len = strlen(f.name);
            
            // create new path and href for the child
            if(path_buf_concat(
                    pool,
                    &newhref,
                    &newhreflen,
                    &href_buf_init,
                    cur_elm->href,
                    cur_elm->hreflen,
                    f.name,
                    child_len))
            {
                err = 1;
                break;
            }
            if(path_buf_concat(
                    pool,
                    &newpath,
                    &newpathlen,
                    &path_buf_init,
                    cur_elm->path,
                    cur_elm->pathlen,
                    f.name,
                    child_len))
            {
                err = 1;
                break;
            }
            size_t childhreflen = cur_elm->hreflen + 1 + child_len;
            size_t childpathlen = cur_elm->pathlen + 1 + child_len;
            
            // propfind for this child
            if(webdav_op_propfind_begin(op, newpath, dir, &f.stat)) {
                err = 1;
                break;
            }
            
            // depth of -1 means infinity
            if(request->depth == -1 && S_ISDIR(f.stat.st_mode)) {
                char *hrefcp = pool_malloc(pool, childhreflen + 1);
                memcpy(hrefcp, newhref, childhreflen + 1);
                hrefcp[childhreflen] = '\0';
                
                char *pathcp = pool_malloc(pool, childpathlen + 1);
                memcpy(pathcp, newpath, childpathlen + 1);
                pathcp[childpathlen] = '\0';
                
                PathSearchElm *new_elm = pool_malloc(pool,
                                            sizeof(PathSearchElm));
                new_elm->href = hrefcp;
                new_elm->path = pathcp;
                new_elm->hreflen = childhreflen;
                new_elm->pathlen = childpathlen;
                
                // add the new_elm to the stack
                // stack_end is always not NULL here, because we remove
                // the first stack element at the end of the loop
                UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm);
                if(!newlistelm) {
                    err = 1;
                    break;
                }
                stack_end = newlistelm;
            }
        }
        
        vfs_closedir(dir);
        
        pool_free(pool, cur_elm->path);
        pool_free(pool, cur_elm->href);
        pool_free(pool, cur_elm);
        
        stack = ucx_list_remove_a(a, stack, stack);
    }
    
    // in case of an error, we have to free all remaining stack elements
    UCX_FOREACH(elm, stack) {
        char *data = elm->data;
        if(data != path) {
            pool_free(pool, data);
        }
    }
    
    return err;
}

int webdav_op_propfiond_close_resource(
        WebdavOperation *op,
        WebdavResource *resource)
{
    // start with second backend and request, because
    // the first one was already called by webdav_op_propfind_begin
    WebdavBackend *dav = op->dav->next;
    UcxList *request = op->requests->next;
    
    // call propfind_do of all remaining backends
    int ret = REQ_PROCEED;
    while(dav && request) {
        if(dav->propfind_do(
                request->data,
                op->response,
                op->parent,
                resource,
                op->stat))
        {
            ret = REQ_ABORTED;
        }
        
        dav = dav->next;
        request = request->next;
    }
    return ret;
}

/*
 * Executes propfind_finish for each Backend
 */
int webdav_op_propfind_finish(WebdavOperation *op) {
    WebdavBackend *dav = op->dav;
    UcxList *requests = op->requests;
    
    int ret = REQ_PROCEED;
    while(dav && requests) {
        if(dav->propfind_finish(requests->data)) {
            ret = REQ_ABORTED;
        }
        
        dav = dav->next;
        requests = requests->next;
    }
    return ret;
}

mercurial