Sun, 26 Jan 2020 10:13:11 +0100
implement webdav_proppatch
/* * 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 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; } 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, newhref, 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; } /**************************************************************************** * * 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->set = set; req->setcount = set_count; req->remove = remove; req->removecount = remove_count; req->userdata = NULL; // check if we need to open the file because the backend want's 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 }