add mkcol and delete interface to webdav backend, move webdav vfs logic to operation webdav

Sun, 02 Feb 2020 17:42:05 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 02 Feb 2020 17:42:05 +0100
branch
webdav
changeset 245
a193c42fc809
parent 244
e59abb210584
child 246
155bdef7fe7e

add mkcol and delete interface to webdav backend, move webdav vfs logic to operation

src/server/public/webdav.h file | annotate | diff | comparison | revisions
src/server/test/main.c file | annotate | diff | comparison | revisions
src/server/test/webdav.c file | annotate | diff | comparison | revisions
src/server/test/webdav.h file | annotate | diff | comparison | revisions
src/server/webdav/operation.c file | annotate | diff | comparison | revisions
src/server/webdav/operation.h file | annotate | diff | comparison | revisions
src/server/webdav/webdav.c file | annotate | diff | comparison | revisions
src/server/webdav/webdav.h file | annotate | diff | comparison | revisions
--- a/src/server/public/webdav.h	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/public/webdav.h	Sun Feb 02 17:42:05 2020 +0100
@@ -57,6 +57,8 @@
 typedef struct WebdavProppatchRequest WebdavProppatchRequest;
 typedef struct WebdavLockRequest      WebdavLockRequest;
 
+typedef struct WebdavVFSRequest       WebdavVFSRequest;
+
 typedef struct WebdavResponse         WebdavResponse;
 typedef struct WebdavResource         WebdavResource;
 
@@ -198,6 +200,18 @@
     void *userdata;
 };
 
+struct WebdavVFSRequest {
+    Session *sn;
+    Request *rq;
+    
+    char *path;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
 struct WebdavLockRequest {
     Session *sn;
     Request *rq;
@@ -320,38 +334,36 @@
             WSBool);
     
     /*
-     * int opt_mkcol(
-     *     Session *sn,
-     *     Request *rq,
-     *     const char *path,
-     *     WSBool *out_created
+     * int opt_mkcol(WebdavVFSRequest *request, WSBool *out_created);
      * 
      * Optional mkcol callback that is called before vfs_mkdir. If the function
      * sets out_created to TRUE, vfs_mkdir will not be executed.
      */
-    int (*opt_mkcol)(
-            Session *,
-            Request *,
-            const char *,
-            WSBool *);
+    int (*opt_mkcol)(WebdavVFSRequest *, WSBool *);
     
     /*
-     * int opt_delete(
-     *     Session *sn,
-     *     Request *rq,
-     *     const char *path,
-     *     WSBool *out_deleted
+     * int opt_mkcol_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a MKCOL request.
+     */
+    int(*opt_mkcol_finish)(WebdavVFSRequest *, WSBool);
+    
+    /*
+     * int opt_delete(WebdavVFSRequest *request, WSBool *out_deleted);
      * 
      * Optional delete callback that is called once before any VFS deletions.
      * When the callback sets out_deleted to TRUE, no VFS unlink operations
      * will be done.
      * 
      */
-    int (*opt_delete)(
-            Session *,
-            Request *,
-            const char *,
-            WSBool *);
+    int (*opt_delete)(WebdavVFSRequest *, WSBool *);
+    
+    /*
+     * int opt_delete_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a DELETE request.
+     */
+    int (*opt_delete_finish)(WebdavVFSRequest *, WSBool);
     
     /*
      * See the WS_WEBDAV_* macros for informations about the settings
--- a/src/server/test/main.c	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/test/main.c	Sun Feb 02 17:42:05 2020 +0100
@@ -96,6 +96,7 @@
     ucx_test_register(suite, test_proppatch_msresponse);
     ucx_test_register(suite, test_msresponse_addproperty_with_errors);
     ucx_test_register(suite, test_webdav_op_proppatch);
+    ucx_test_register(suite, test_webdav_vfs_op_do);
        
     // webdav methods
     ucx_test_register(suite, test_webdav_propfind);
--- a/src/server/test/webdav.c	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/test/webdav.c	Sun Feb 02 17:42:05 2020 +0100
@@ -124,7 +124,9 @@
     backend2_proppatch_do,
     backend2_proppatch_finish,
     NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
     NULL, // opt_delete
+    NULL, // opt_delete_finish
     0,
     NULL
 };
@@ -224,7 +226,9 @@
     backend1_proppatch_do,
     backend1_proppatch_finish,
     NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
     NULL, // opt_delete
+    NULL, // opt_delete_finish
     0,
     &backend2
 };
@@ -1102,7 +1106,9 @@
     st = testutil_iostream(2048, TRUE);
     sn->csd = st;
     
-    testutil_request_body(sn, rq, request_body, strlen(request_body));
+    if(request_body) {
+        testutil_request_body(sn, rq, request_body, strlen(request_body));
+    }
     
     pb = pblock_create_pool(sn->pool, 4);
     
@@ -1472,3 +1478,153 @@
     
     UCX_TEST_END;
 }
+
+
+/* -------------------------------------------------------------------------
+ * 
+ *                          WEBDAV VFS TESTS
+ * 
+ * ------------------------------------------------------------------------ */
+
+static int mkcol_data1 = 10;
+static int mkcol_data2 = 20;
+static int mkcol_data3 = 30;
+static int mkcol_data4 = 40;
+
+static int mkcol_count = 0;
+static int mkcol_finish_count = 0;
+
+static int mkcol_err = 0;
+
+static int set_created = 0;
+
+static int test_webdav_mkcol(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    
+    switch(mkcol_count) {
+        case 1: {
+            req->userdata = &mkcol_data1;
+            break;
+        }
+        case 2: {
+            req->userdata = &mkcol_data2;
+            break;
+        }
+        case 3: {
+            req->userdata = &mkcol_data3;
+            break;
+        }
+        case 4: {
+            req->userdata = &mkcol_data4;
+            break;
+        }
+        default: break;
+    }
+    
+    if(set_created) {
+        *created = TRUE;
+        set_created = 0;
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_finish(WebdavVFSRequest *req, WSBool success) {
+    mkcol_finish_count++;
+    
+    if(mkcol_finish_count == 1) {
+        int *data = req->userdata;
+        if(data != &mkcol_data1) {
+            mkcol_err = 1;
+        }
+    } else if(mkcol_finish_count == 3) {
+        int *data = req->userdata;
+        if(data != &mkcol_data3) {
+            mkcol_err = 1;
+        }
+    } else {
+        int *data = req->userdata;
+        // data4 should never be used
+        if(data == &mkcol_data4) {
+            mkcol_err = 1;
+        }
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_fail(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    return 1;
+}
+
+
+
+UCX_TEST(test_webdav_vfs_op_do) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "MKCOL", NULL);
+    rq->vfs = testvfs_create(sn);
+    
+    WebdavBackend dav1;
+    ZERO(&dav1, sizeof(WebdavBackend));
+    dav1.opt_mkcol = test_webdav_mkcol;
+    dav1.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    WebdavBackend dav2;
+    ZERO(&dav2, sizeof(WebdavBackend));
+    dav2.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    WebdavBackend dav3;
+    ZERO(&dav3, sizeof(WebdavBackend));
+    dav3.opt_mkcol = test_webdav_mkcol;
+    
+    WebdavBackend dav4;
+    ZERO(&dav4, sizeof(WebdavBackend));
+    dav4.opt_mkcol = test_webdav_mkcol;
+    dav4.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    dav1.next = &dav2;
+    dav2.next = &dav3;
+    dav3.next = &dav4;
+    
+    rq->davCollection = &dav1;
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavVFSOperation *op1 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    
+    int ret = webdav_vfs_op_do(op1, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "webdav_vfs_op_do failed");
+    UCX_TEST_ASSERT(mkcol_count == 3, "wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 3, "wrong mkcol_finish_count");
+    UCX_TEST_ASSERT(mkcol_err == 0, "mkcol_err");
+    
+    // test without VFS, but set *created to TRUE to skip VFS usage
+    rq->vfs = NULL;
+    set_created = 1;
+    
+    WebdavVFSOperation *op2 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op2, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "op2 failed");
+    
+    // test 3: abort after first backend
+    mkcol_count = 0;
+    mkcol_finish_count = 0;
+    dav1.opt_mkcol = test_webdav_mkcol_fail;
+    
+    WebdavVFSOperation *op3 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op3, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(ret, "op3 should fail");
+    UCX_TEST_ASSERT(mkcol_count == 1, "op3: wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 1, "op3: wrong mkcol_finish_count");
+    
+    
+    UCX_TEST_END;
+}
--- a/src/server/test/webdav.h	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/test/webdav.h	Sun Feb 02 17:42:05 2020 +0100
@@ -64,6 +64,8 @@
 
 UCX_TEST(test_webdav_proppatch);
 
+UCX_TEST(test_webdav_vfs_op_do);
+
 /* --------------------------- PROPFIND --------------------------- */
 
 #define TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
--- a/src/server/webdav/operation.c	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/webdav/operation.c	Sun Feb 02 17:42:05 2020 +0100
@@ -28,10 +28,12 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <errno.h>
 
 #include <ucx/list.h>
 
 #include "../daemon/session.h"
+#include "../util/pblock.h"
 
 #include "operation.h"
 
@@ -523,3 +525,196 @@
     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;
+}
+
+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
+    // deletion of simple files can be done just here,
+    // whereas deleting directories is more complicated
+    if(webdav_vfs_stat(op)) {
+        return 1; // error
+    } else {
+        int r = 0;
+        if(!S_ISDIR(op->stat->st_mode)) {
+            return vfs_unlink(op->vfs, op->path);
+        }
+    }
+    
+    // delete directory:
+    
+    
+    
+    return 0;
+}
--- a/src/server/webdav/operation.h	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/webdav/operation.h	Sun Feb 02 17:42:05 2020 +0100
@@ -36,6 +36,8 @@
 #endif
 
 typedef int(*response_close_func)(WebdavOperation *, WebdavResource *);
+
+typedef struct WebdavVFSOperation WebdavVFSOperation;
     
 struct WebdavOperation {
     WebdavBackend          *dav;
@@ -54,6 +56,28 @@
     struct stat            *stat;         /* current stat object */
 };
 
+struct WebdavVFSOperation {
+    WebdavBackend          *dav;
+    Request                *rq;
+    Session                *sn;
+    
+    VFSContext             *vfs;
+    
+    char                   *path;
+    struct stat            *stat;
+    int                    stat_errno;
+};
+
+enum WebdavVFSOpType {
+    WEBDAV_VFS_MKDIR = 0,
+    WEBDAV_VFS_DELETE
+};
+
+typedef enum WebdavVFSOpType WebdavVFSOpType;
+
+typedef int(*vfs_op_func)(WebdavVFSRequest *, WSBool *);
+typedef int(*vfs_op_finish_func)(WebdavVFSRequest *, WSBool);
+
 /*
  * counts the number of backends
  */
@@ -101,6 +125,19 @@
         WebdavOperation *op,
         WebdavResource *resource);
 
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition);
+
+int webdav_vfs_stat(WebdavVFSOperation *op);
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type);
+
+int webdav_vfs_unlink(WebdavVFSOperation *op);
+
 #ifdef __cplusplus
 }
 #endif
--- a/src/server/webdav/webdav.c	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/webdav/webdav.c	Sun Feb 02 17:42:05 2020 +0100
@@ -446,26 +446,13 @@
 }
 
 int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
-    UcxBuffer *reqbuf;
-    VFSContext *vfs;
-    char *path;
-    
-    if(webdav_init_vfs_op(sn, rq, &reqbuf, &vfs, &path)) {
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
         return REQ_ABORTED;
     }
     
-    int ret = REQ_PROCEED;
-    if(vfs_mkdir(vfs, path)) {
-        protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
-        return REQ_ABORTED;
-    }
+    int ret = webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR);
     
-    protocol_status(sn, rq, 201, NULL);
-    
-    // cleanup and return
-    if(reqbuf) {
-        ucx_buffer_free(reqbuf);
-    }
     return ret;
 }
 
@@ -482,27 +469,14 @@
     
 }
 
-int webdav_delete(pblock *pb, Session *sn, Request *rq) {
-    UcxBuffer *reqbuf;
-    VFSContext *vfs;
-    char *path;
-    
-    if(webdav_init_vfs_op(sn, rq, &reqbuf, &vfs, &path)) {
+int webdav_delete(pblock *pb, Session *sn, Request *rq) { 
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
         return REQ_ABORTED;
     }
     
-    int ret = REQ_PROCEED;
-    
-    // TODO
+    int ret = webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR);
     
-    if(ret == REQ_PROCEED) {
-        protocol_status(sn, rq, 200, NULL);
-    }
-    
-    // cleanup and return
-    if(reqbuf) {
-        ucx_buffer_free(reqbuf);
-    }
     return ret;
 }
 
@@ -535,50 +509,6 @@
 }
 
 
-int webdav_init_vfs_op(
-        Session *sn,
-        Request *rq,
-        UcxBuffer **out_reqbuf,
-        VFSContext **out_vfs,
-        char **out_path)
-{
-    *out_reqbuf = NULL;
-    *out_vfs = NULL;
-    *out_path = NULL;
-    
-    // create VFS context
-    VFSContext *vfs = vfs_request_context(sn, rq);
-    if(!vfs) {
-        return REQ_ABORTED;
-    } 
-    
-    // read request body, if it exists
-    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
-    if(expect) {
-        if(!strcasecmp(expect, "100-continue")) {
-            if(http_send_continue(sn)) {
-                return REQ_ABORTED;
-            }
-        }
-    }
-    
-    UcxBuffer *reqbody = NULL;
-    if(sn->inbuf) {
-        reqbody = rqbody2buffer(sn, rq);
-        if(!reqbody) {
-            return REQ_ABORTED;
-        }
-    }
-    
-    // requested uri and path
-    char *path = pblock_findkeyval(pb_key_path, rq->vars);
-    
-    *out_reqbuf = reqbody;
-    *out_vfs = vfs;
-    *out_path = path;
-    
-    return REQ_PROCEED;
-}
 
 /* ------------------------ default webdav backend  ------------------------ */
 
--- a/src/server/webdav/webdav.h	Sat Feb 01 18:44:31 2020 +0100
+++ b/src/server/webdav/webdav.h	Sun Feb 02 17:42:05 2020 +0100
@@ -82,24 +82,6 @@
 int webdav_acl(pblock *pb, Session *sn, Request *rq);
 int webdav_search (pblock *pb, Session *sn, Request *rq);
 
-/*
- * Initialize a WebDAV VFS operation
- * 
- * sn: WS Session
- * rq: WS Request
- * out_reqbuf: func returns a pointer to the request body or NULL
- * out_vfs: func returns a pointer to the newly created VFSContext
- * out_path: func returns a pointer to the path string
- * 
- * return: on success REQ_PROCEED
- *         on error   REQ_ABORTED
- */
-int webdav_init_vfs_op(
-        Session *sn,
-        Request *rq,
-        UcxBuffer **out_reqbuf,
-        VFSContext **out_vfs,
-        char **out_path);
 
 int default_propfind_init(
         WebdavPropfindRequest *rq,

mercurial