src/server/webdav/operation.c

changeset 385
a1f4cb076d2f
parent 378
0344108db255
child 413
6afaebf003ea
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/operation.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,753 @@
+/*
+ * 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 <errno.h>
+
+#include <ucx/list.h>
+
+#include "../daemon/session.h"
+#include "../util/pblock.h"
+
+#include "operation.h"
+
+#define WEBDAV_PATH_MAX 8192
+
+
+size_t webdav_num_backends(WebdavBackend *dav) {
+    size_t count = 0;
+    while(dav) {
+        count++;
+        dav = dav->next;
+    }
+    return count;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPFIND OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_propfind_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavPList *reqprops,
+        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->reqprops = reqprops;
+    op->requests = requests;
+    op->response = response;
+    op->response_close = webdav_op_propfiond_close_resource;
+    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;
+}
+
+static int propfind_child_cb(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *op)
+{
+    return webdav_op_propfind_begin(op, href, parent, s);
+}
+
+int webdav_op_propfind_children(
+        WebdavOperation *op,
+        VFSContext *vfs,
+        const char *href,
+        const char *path)
+{
+    WebdavPropfindRequest *request = op->requests->data;
+    return webdav_op_iterate_children(
+            vfs, request->depth, href, path, propfind_child_cb, op);
+}
+
+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;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPPATCH OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_proppatch_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavProppatchRequest *proppatch,
+        WebdavResponse *response)
+{
+    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
+    ZERO(op, sizeof(WebdavOperation));
+    op->dav = dav;
+    op->sn = sn;
+    op->rq = rq;
+    op->reqprops = NULL;
+    op->response = response;
+    op->proppatch = proppatch;
+    op->response_close = webdav_op_proppatch_close_resource;
+    response->op = op;
+    
+    return op;
+}
+
+
+
+int webdav_op_proppatch(
+        WebdavOperation *op,
+        const char *href,
+        const char *path)
+{
+    WebdavProppatchRequest *orig_request = op->proppatch;
+    UcxAllocator *a = session_get_allocator(op->sn);
+    
+    // create WebdavResource object for the requested resource
+    WebdavResource *resource = op->response->addresource(op->response, href);
+    if(!resource) {
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *ctx = NULL;
+    VFSFile *file = NULL;
+    
+    // requests for each backends
+    WebdavProppatchRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavProppatchRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavPList *prev_set = orig_request->set;
+    WebdavPList *prev_remove = orig_request->remove;
+    size_t set_count = orig_request->setcount;
+    size_t remove_count = orig_request->removecount;
+    
+    int ret = REQ_PROCEED;
+    
+    // iterate backends and execute proppatch_do
+    WebdavBackend *dav = op->dav;
+    size_t numrequests = 0;
+    while(dav) {
+        WebdavPList *set = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_set,
+                &set_count);
+        WebdavPList *remove = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_remove,
+                &remove_count);
+        if((prev_set && !set) || (prev_remove && !remove)) {
+            // clone failed, OOM
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // create new WebdavProppatchRequest object for this backend
+        WebdavProppatchRequest *req = pool_malloc(
+                op->sn->pool,
+                sizeof(WebdavProppatchRequest));
+        memcpy(req, orig_request, sizeof(WebdavProppatchRequest));
+        req->dav = dav;
+        req->set = orig_request->set;
+        req->setcount = orig_request->setcount;
+        req->remove = orig_request->remove;
+        req->removecount = orig_request->removecount;
+        req->userdata = NULL;
+        
+        // check if we need to open the file because the backend wants it
+        if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS)
+                         == WS_WEBDAV_PROPPATCH_USE_VFS)
+        {
+            ctx = vfs_request_context(op->sn, op->rq);
+            if(!ctx) {
+                ret = REQ_ABORTED;
+                break;
+            }
+            
+            file = vfs_open(ctx, path, O_RDONLY);
+            if(!file) {
+                protocol_status(
+                        op->sn,
+                        op->rq,
+                        util_errno2status(ctx->vfs_errno),
+                        NULL);
+                ret = REQ_ABORTED;
+            }
+        }
+        
+        // execute proppatch_do
+        if(dav->proppatch_do(req, resource, file, &set, &remove)) {
+            // return later, because we need do execute proppatch_finish
+            // for all successfully called backends
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // proppatch_do should remove all handled props from set and remove
+        // in the next iteration, the backend must use these reduced lists
+        prev_set = set;
+        prev_remove = remove;
+        
+        requests[numrequests++] = req;
+        
+        // continue with next backend
+        dav = dav->next;
+    }
+    
+    WSBool commit = FALSE;
+    if(ret == REQ_PROCEED && resource->err == 0) {
+        // no errors, no properties with errors -> save the changes
+        commit = TRUE; 
+    }
+    
+    // call proppatch_finish for each successfully called proppatch_do
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < numrequests) {
+        if(dav->proppatch_finish(requests[i], resource, file, commit)) {
+            ret = REQ_ABORTED;
+        }
+        i++;
+        dav = dav->next;
+    }
+    
+    if(file) {
+        vfs_close(file);
+    }
+    
+    if(resource->close(resource)) {
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+int webdav_op_proppatch_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource)
+{
+    return 0; // NOP
+}
+
+
+/****************************************************************************
+ * 
+ *                             VFS OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition)
+{
+    WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation));
+    if(!op) {
+        return NULL;
+    }
+    ZERO(op, sizeof(WebdavVFSOperation));
+    
+    op->sn = sn;
+    op->rq = rq;
+    op->dav = dav;
+    op->stat = NULL;
+    op->stat_errno = 0;
+    
+    // create VFS context
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        pool_free(sn->pool, op);
+        return NULL;
+    }
+    op->vfs = vfs;
+    
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    op->path = path;  
+    
+    return op;
+}
+
+WebdavVFSOperation webdav_vfs_sub_op(
+        WebdavVFSOperation *op,
+        char *path,
+        struct stat *s)
+{
+    WebdavVFSOperation sub;
+    sub.dav = op->dav;
+    sub.path = path;
+    sub.sn = op->sn;
+    sub.vfs = op->vfs;
+    sub.path = path;
+    sub.stat = s;
+    sub.stat_errno = 0;
+    return sub;
+}
+
+int webdav_op_iterate_children(
+        VFSContext *vfs,
+        int depth,
+        const char *href,
+        const char *path,
+        vfs_op_child_func func,
+        void *userdata)
+{
+    UcxAllocator *a = session_get_allocator(vfs->sn);
+    pool_handle_t *pool = vfs->sn->pool;
+    
+    PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm));
+    start_elm->href = pool_strdup(pool, href ? href : "");
+    start_elm->path = pool_strdup(pool, path ? path : "");
+    start_elm->hreflen = href ? strlen(href) : 0;
+    start_elm->pathlen = path ? strlen(path) : 0;
+    
+    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;
+            
+            // execute callback func for this file
+            if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) {
+                err = 1;
+                break;
+            }
+            
+            // depth of -1 means infinity
+            if(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_vfs_stat(WebdavVFSOperation *op) {
+    if(op->stat) {
+        return 0;
+    } else if(op->stat_errno != 0) {
+        // previous stat failed
+        return 1;
+    }
+    
+    // stat file
+    struct stat sbuf;
+    int ret = vfs_stat(op->vfs, op->path, &sbuf);
+    if(!ret) {
+        // save result in op->stat and in s
+        op->stat = pool_malloc(op->sn->pool, sizeof(struct stat));
+        if(op->stat) {
+            memcpy(op->stat, &sbuf, sizeof(struct stat));
+        } else {
+            ret = 1;
+            op->stat_errno = ENOMEM;
+        }
+    } else {
+        op->stat_errno = errno;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) {
+    WSBool exec_vfs = TRUE;
+    
+    // requests for each backends
+    WebdavVFSRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavVFSRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = REQ_PROCEED;
+    
+    // call opt_* func for each backend
+    WebdavBackend *dav = op->dav;
+    int called_backends = 0;
+    while(dav) {    
+        WebdavVFSRequest *request = NULL;
+        
+        // get vfs operation functions
+        vfs_op_func        op_func        = NULL;
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_func        = dav->opt_mkcol;
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_func        = dav->opt_delete;
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_func || op_finish_func) {
+            // we need a request object
+            request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest));
+            if(!request) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+            request->sn = op->sn;
+            request->rq = op->rq;
+            request->path = op->path;
+            request->userdata = NULL;
+            
+            requests[called_backends] = request;
+        }
+        
+        // exec backend func for this operation
+        // this will set 'done' to TRUE, if no further vfs call is required
+        WSBool done = FALSE;
+        called_backends++;
+        if(op_func) {
+            if(op_func(request, &done)) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+        }
+        if(done) {
+            exec_vfs = FALSE;
+        }
+        
+        dav = dav->next;
+    }
+    
+    // if needed, call vfs func for this operation
+    if(exec_vfs) {
+        int r = 0;
+        if(type == WEBDAV_VFS_MKDIR) {
+            r = vfs_mkdir(op->vfs, op->path);
+        } else if(type == WEBDAV_VFS_DELETE) {
+            r = webdav_vfs_unlink(op);
+        }
+        
+        if(r) {
+            ret = REQ_ABORTED;
+        }
+    }
+    
+    WSBool success = ret == REQ_PROCEED ? TRUE : FALSE;
+    
+    // finish mkcol (cleanup) by calling opt_*_finish for each backend
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < called_backends) {
+        // get vfs operation functions
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_finish_func) {
+            if(op_finish_func(requests[i], success)) {
+                ret = REQ_ABORTED; // don't exit loop
+            }
+        }
+        
+        dav = dav->next;
+        i++;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_unlink(WebdavVFSOperation *op) {
+    // stat the file first, to check if the file is a directory
+    if(webdav_vfs_stat(op)) {
+        return 1; // error
+    } else {
+        if(!S_ISDIR(op->stat->st_mode)) {
+            return vfs_unlink(op->vfs, op->path);
+        } else {
+            return vfs_rmdir(op->vfs, op->path);
+        }
+    }
+}

mercurial