Mon, 30 Dec 2019 16:33:20 +0100
add webdav_plist_iterator_remove_current tests and fix some plist related bugs
/* * 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 <string.h> #include <ucx/buffer.h> #include <ucx/list.h> #include "webdav.h" #include "search.h" #include "versioning.h" #include "multistatus.h" #include "requestparser.h" #include "../util/pblock.h" #include "../util/util.h" #include "../daemon/session.h" #include "../daemon/http.h" static UcxMap *method_handler_map; static WebdavBackend default_backend; static WSNamespace dav_namespace; static WebdavProperty dav_resourcetype_empty; static WebdavProperty dav_resourcetype_collection; static WSXmlNode dav_resourcetype_collection_value; // TODO: change type to WSXmlData static void init_default_backend(void) { memset(&default_backend, 0, sizeof(WebdavBackend)); default_backend.propfind_init = default_propfind_init; default_backend.propfind_do = default_propfind_do; default_backend.propfind_finish = default_propfind_finish; } int webdav_init(pblock *pb, Session *sn, Request *rq) { init_default_backend(); method_handler_map = ucx_map_new(64); ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options); ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind); ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch); ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol); ucx_map_cstr_put(method_handler_map, "POST", webdav_post); ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete); ucx_map_cstr_put(method_handler_map, "PUT", webdav_put); ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy); ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move); ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock); ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock); ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report); ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl); ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search); ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control); ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout); ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin); ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout); ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace); ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update); ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label); ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge); dav_namespace.href = (xmlChar*)"DAV:"; dav_namespace.prefix = (xmlChar*)"D"; dav_resourcetype_empty.namespace = &dav_namespace; dav_resourcetype_empty.name = "resourcetype"; dav_resourcetype_collection.namespace = &dav_namespace; dav_resourcetype_collection.name = "resourcetype"; dav_resourcetype_collection.value.node = &dav_resourcetype_collection_value; dav_resourcetype_collection.vtype = WS_VALUE_XML_NODE; dav_resourcetype_collection_value.content = (xmlChar*)"<D:collection/>"; dav_resourcetype_collection_value.type = XML_TEXT_NODE; return REQ_PROCEED; } int webdav_service(pblock *pb, Session *sn, Request *rq) { if(!method_handler_map) { log_ereport(LOG_FAILURE, "WebDAV module not initialized"); protocol_status(sn, rq, 500, NULL); return REQ_ABORTED; } char *method = pblock_findkeyval(pb_key_method, rq->reqpb); FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method); if(!saf) { return REQ_NOACTION; } return saf(pb, sn, rq); } UcxBuffer* rqbody2buffer(Session *sn, Request *rq) { if(!sn->inbuf) { //request body required, set http response code protocol_status(sn, rq, 400, NULL); return NULL; } UcxBuffer *buf = ucx_buffer_new( NULL, sn->inbuf->maxsize, UCX_BUFFER_AUTOEXTEND); if(!buf) { protocol_status(sn, rq, 500, NULL); return NULL; } char in[2048]; int r; while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { if(ucx_buffer_write(in, 1, r, buf) != r) { protocol_status(sn, rq, 500, NULL); ucx_buffer_free(buf); return NULL; } } return buf; } int webdav_options(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_propfind(pblock *pb, Session *sn, Request *rq) { UcxBuffer *reqbody = rqbody2buffer(sn, rq); if(!reqbody) { return REQ_ABORTED; } UcxAllocator *a = session_get_allocator(sn); int error = 0; WebdavPropfindRequest *propfind = propfind_parse( sn, rq, reqbody->space, reqbody->size, &error); ucx_buffer_free(reqbody); if(!propfind) { switch(error) { // TODO: handle all errors default: return REQ_ABORTED; } } // The multistatus response object contains responses for all // requested resources. At the end the Multistatus object will be // serialized to xml Multistatus *ms = multistatus_response(sn, rq); if(!ms) { return REQ_ABORTED; } // WebdavResponse is the public interface used by Backends // for adding resources to the response WebdavResponse *response = (WebdavResponse*)ms; WebdavBackend *dav = rq->davCollection ? rq->davCollection : &default_backend; // requested uri path char *path = pblock_findkeyval(pb_key_path, rq->vars); // VFS settings are only taken from the first backend uint32_t settings = dav->settings; // list of individual WebdavPropfindRequest objects for each Backend UcxList *requestObjects = NULL; // Initialize all Webdav Backends if(webdav_propfind_init(dav, propfind, path, &requestObjects)) { return REQ_ABORTED; } // some Backends can list all children by themselves, but some // require the VFS for this WSBool usevfs = (settings & WS_PROPFIND_NO_VFS) != WS_PROPFIND_NO_VFS; struct stat s; struct stat *statptr = NULL; VFSContext *vfs = NULL; if(usevfs) { vfs = vfs_request_context(sn, rq); if(vfs_stat(vfs, path, &s)) { protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); return REQ_ABORTED; } statptr = &s; if(!S_ISDIR(s.st_mode)) { // the file is not a directory, therefore we don't need the VFS usevfs = FALSE; } } if(propfind->depth == 0) { usevfs = FALSE; } int ret = REQ_ABORTED; if(!webdav_propfind_do(dav, requestObjects, response, NULL, path, statptr)) { // propfind for the requested resource was successful // usevfsdir is TRUE if // the webdav backend has not disabled vfs usage // the file is a directory // depth is not 0 // in this case we need to execute propfind_do for all children if(usevfs && !propfind_children(dav, requestObjects, response, vfs, path)) { ret = REQ_PROCEED; } } // finish the propfind request // this function should cleanup all resources, therefore we execute it // even if a previous function failed if(dav->propfind_finish(propfind)) { ret = REQ_ABORTED; } return ret; } /* * Initializes Backend Chain * * Calls propfind_init of each Backend and generates a list of custom * WebdavPropfindRequest objects for each backend */ int webdav_propfind_init( WebdavBackend *dav, WebdavPropfindRequest *propfind, const char *path, UcxList **out_req) { pool_handle_t *pool = propfind->sn->pool; UcxAllocator *a = session_get_allocator(propfind->sn); // list of individual WebdavPropfindRequest objects for each Backend UcxList *requestObjects = NULL; // new properties after init, start with clone of original plist WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties); size_t newPropCount = propfind->propcount; // Call propfind_init for each Backend // propfind_init can return a new property list, which // will be passed to the next backend WebdavBackend *davList = dav; while(davList) { // create WebdavPropfindRequest copy WebdavPropfindRequest *pReq = pool_malloc( pool, sizeof(WebdavPropfindRequest)); memcpy(propfind, pReq, sizeof(WebdavPropfindRequest)); // use new plist after previous init (or orig. plist in the first run) pReq->properties = newProp; pReq->propcount = newPropCount; // add new WebdavPropfindRequest object to list for later use requestObjects = ucx_list_append_a(a, requestObjects, pReq); if(!requestObjects) { return REQ_ABORTED; // OOM } // create plist copy as out-plist for init newProp = webdav_plist_clone(pool, newProp); // run init: this can generate a new properties list (newProp) // which will be passed to the next backend if(davList->propfind_init(pReq, path, &newProp)) { return REQ_ABORTED; } newPropCount = webdav_plist_size(newProp); davList = davList->next; } *out_req = requestObjects; return REQ_PROCEED; } /* * Executes propfind_do for each Backend * The list requests must contain all WebdavPropfindRequest objects * of all backends */ int webdav_propfind_do( WebdavBackend *dav, UcxList *requests, WebdavResponse *response, VFS_DIR parent, const char *path, struct stat *s) { while(dav && requests) { if(dav->propfind_do(requests->data, response, parent, path, s)) { return REQ_ABORTED; } dav = dav->next; requests = requests->next; } return REQ_PROCEED; } /* * Executes propfind_finish for each Backend */ int webdav_propfind_finish(WebdavBackend *dav, UcxList *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; } /* * Uses the VFS to iterate over all children of the requsted resource * and executes propfind for each child */ int propfind_children( WebdavBackend *dav, UcxList *requests, WebdavResponse *response, VFSContext *vfs, char *path) { WebdavPropfindRequest *request = requests->data; UcxAllocator *a = session_get_allocator(request->sn); pool_handle_t *pool = request->sn->pool; UcxList *stack = ucx_list_prepend_a(a, NULL, path); UcxList *stack_end = stack; if(!stack) { return 1; } // reusable buffer for full child path char *newpath = NULL; size_t newpathlen = 0; int err = 0; while(stack && !err) { char *cur_path = stack->data; size_t parent_len = strlen(cur_path); if(parent_len > WEBDAV_PATH_MAX) { log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded"); err = 1; break; } if(cur_path[parent_len-1] == '/') { parent_len--; } size_t max_child_len = WEBDAV_PATH_MAX - parent_len; // when newpath is initialized with the parent path // set path_buf_init to TRUE WSBool path_buf_init = FALSE; VFS_DIR dir = vfs_opendir(vfs, 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); if(child_len > max_child_len) { log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded"); err = 1; break; } size_t childpathlen = parent_len + child_len + 1; // +1 '/' if(childpathlen > newpathlen) { // we're gonna need a bigger boa^H^H^Hbuffer if(newpath) { pool_free(pool, newpath); } newpath = pool_malloc(pool, childpathlen + 1); if(!newpath) { err = 1; break; } newpathlen = childpathlen; path_buf_init = FALSE; } // create full path string for this child if(!path_buf_init) { memcpy(newpath, cur_path, parent_len); newpath[parent_len] = '/'; } memcpy(newpath+parent_len+1, f.name, child_len); newpath[childpathlen] = 0; // propfind for this child if(webdav_propfind_do(dav, requests, response, dir, newpath, &f.stat)) { err = 1; break; } // depth of -1 means infinity if(request->depth == -1 && S_ISDIR(f.stat.st_mode)) { char *pathcp = pool_malloc(pool, childpathlen + 1); memcpy(pathcp, newpath, childpathlen + 1); // add the newpath copy 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, pathcp); if(!newlistelm) { err = 1; break; } stack_end = newlistelm; } } vfs_closedir(dir); if(cur_path != path) { pool_free(pool, cur_path); } 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_proppatch(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_mkcol(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_post(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_delete(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_put(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_copy(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_move(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_lock(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_unlock(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_report(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } int webdav_acl(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } /* ------------------------ default webdav backend ------------------------ */ int default_propfind_init( WebdavPropfindRequest *rq, const char* path, WebdavPList **outplist) { DefaultWebdavData *data = pool_malloc( rq->sn->pool, sizeof(DefaultWebdavData)); if(!data) { return 1; } rq->userdata = data; data->vfsproperties = webdav_vfs_properties(rq, TRUE, 0); return 0; } int default_propfind_do( WebdavPropfindRequest *request, WebdavResponse *response, VFS_DIR parent, const char *path, struct stat *s) { DefaultWebdavData *data = request->userdata; // add a resource to the response // usually this will lead to a <response> ... </response> tag in the // multistatus response WebdavResource *resource = response->addresource(response, path); if(!resource) { return 1; } // add all requested vfs properties like getcontentlength ... if(webdav_add_vfs_properties( resource, request->sn->pool, data->vfsproperties, s)) { return 1; } // all remaining properties are not available WebdavPList *p = request->properties; while(p) { resource->addproperty(resource, p->property, 404); p = p->next; } return 0; } int default_propfind_finish(WebdavPropfindRequest *rq) { return 0; } /* ------------------------------ public API ------------------------------ */ int webdav_getdepth(Request *rq) { char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers); int depth = 0; if(depth_str) { size_t dlen = strlen(depth_str); if(!memcmp(depth_str, "infinity", dlen)) { depth = -1; } else if(dlen == 1 && depth_str[0] == '1') { depth = 1; } } return depth; } WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) { WebdavPList *new_list = NULL; // start of the new list WebdavPList *new_list_end = NULL; // end of the new list WebdavPList *elm = list; while(elm) { // copy list item WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList)); if(!new_elm) { return NULL; } new_elm->property = elm->property; // new list contains original ptr new_elm->prev = new_list_end; new_elm->next = NULL; if(new_list_end) { new_list_end->next = new_elm; } else { new_list = new_elm; } new_list_end = new_elm; elm = elm->next; } return new_list; } size_t webdav_plist_size(WebdavPList *list) { size_t count = 0; WebdavPList *elm = list; while(elm) { count++; elm = elm->next; } return count; } WebdavPListIterator webdav_plist_iterator(WebdavPList **list) { WebdavPListIterator i; i.list = list; i.cur = NULL; i.next = *list; i.index = 0; return i; } int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur) { if(i->cur) { i->index++; } i->cur = i->next; i->next = i->cur ? i->cur->next : NULL; *cur = i->cur; return i->cur != NULL; } void webdav_plist_iterator_remove_current(WebdavPListIterator *i) { WebdavPList *cur = i->cur; if(cur->prev) { cur->prev->next = cur->next; if(cur->next) { cur->next->prev = cur->prev; } } else { *i->list = cur->next; if(cur->next) { cur->next->prev = NULL; } } } WSNamespace* webdav_dav_namespace(void) { return &dav_namespace; } WebdavProperty* webdav_dav_property( pool_handle_t *pool, const char *name) { WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); if(!property) { return NULL; } memset(property, 0, sizeof(WebdavProperty)); property->namespace = &dav_namespace; property->name = name; return property; } int webdav_property_set_value( WebdavProperty *p, pool_handle_t *pool, char *value) { WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode)); if(!node) { return 1; } ZERO(node, sizeof(WSXmlNode)); node->content = (xmlChar*)value; node->type = XML_TEXT_NODE; p->value.node = node; p->vtype = WS_VALUE_XML_NODE; return 0; } WebdavVFSProperties webdav_vfs_properties( WebdavPropfindRequest *rq, WSBool removefromlist, uint32_t flags) { WebdavVFSProperties ret; ZERO(&ret, sizeof(WebdavVFSProperties)); WSBool etag = 1; WSBool creationdate = 1; WebdavPList *property = rq->properties; WebdavPList *prev = NULL; while(property) { WebdavPList *next = property->next; WSNamespace *ns = property->property->namespace; if(ns && !strcmp((char*)ns->href, "DAV:")) { const char *name = property->property->name; WebdavPList *removethis = property; if(!strcmp(name, "getlastmodified")) { ret.getlastmodified = 1; } else if(!strcmp(name, "getcontentlength")) { ret.getcontentlength = 1; } else if(!strcmp(name, "resourcetype")) { ret.getresourcetype = 1; } else if(etag && !strcmp(name, "getetag")) { ret.getetag = 1; } else if(creationdate && !strcmp(name, "creationdate")) { ret.creationdate = 1; } else { removethis = NULL; } if(removefromlist && removethis) { if(prev) { prev->next = next; } else { rq->properties = next; } } } prev = property; property = next; } return ret; } static inline int w_addprop( WebdavResource *res, pool_handle_t *pool, const char *name, char *value) { WebdavProperty *p = webdav_dav_property(pool, name); if(!p) { return 1; } if(webdav_property_set_value(p, pool, value)) { return 1; } return res->addproperty(res, p, 200); } int webdav_add_vfs_properties( WebdavResource *res, pool_handle_t *pool, WebdavVFSProperties properties, struct stat *s) { if(properties.getresourcetype) { if(S_ISDIR(s->st_mode)) { res->addproperty(res, &dav_resourcetype_collection, 200); } else { res->addproperty(res, &dav_resourcetype_empty, 200); } } if(properties.getcontentlength) { char *buf = pool_malloc(pool, 64); if(!buf) { return 1; } uint64_t contentlength = s->st_size; snprintf(buf, 64, "%" PRIu64 "\0", contentlength); if(w_addprop(res, pool, "getcontentlength", buf)) { return 1; } } if(properties.getlastmodified) { char *buf = pool_malloc(pool, HTTP_DATE_LEN+1); if(!buf) { return 1; } buf[HTTP_DATE_LEN] = 0; struct tm mtms; struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms); if(mtm) { strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm); if(w_addprop(res, pool, "getlastmodified", buf)) { return 1; } } else { return 1; } } if(properties.creationdate) { // TODO } if(properties.getetag) { char *buf = pool_malloc(pool, 96); if(!buf) { return 1; } snprintf(buf, 96, "\"%x-%x\"\0", (int)s->st_size, (int)s->st_mtim.tv_sec); if(w_addprop(res, pool, "getetag", buf)) { return 1; } } return 0; }