Sun, 02 Feb 2020 17:42:05 +0100
add mkcol and delete interface to webdav backend, move webdav vfs logic to operation
--- 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,