Fri, 17 Jan 2020 22:23:30 +0100
implement multistatus writer
/* * 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 "operation.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; } } WebdavBackend *dav = rq->davCollection ? rq->davCollection : &default_backend; // requested uri and path char *path = pblock_findkeyval(pb_key_path, rq->vars); char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); // 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; } // 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; WebdavOperation *op = webdav_operation_create( sn, rq, dav, requestObjects, response); // 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_PROCEED; // create WebdavResource object for requested resource if(!webdav_op_propfind_begin(op, path, NULL, 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) { if(!webdav_op_propfind_children(op, vfs, uri, path)) { ret = REQ_ABORTED; } } } // finish the propfind request // this function should cleanup all resources, therefore we execute it // even if a previous function failed if(webdav_op_propfind_finish(op)) { ret = REQ_ABORTED; } // if propfind was successful, send the result to the client if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) { ret = REQ_ABORTED; // TODO: log error } else { // TODO: log error // TODO: error response } 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(pReq, propfind, 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; } 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, WebdavResource *resource, struct stat *s) { DefaultWebdavData *data = request->userdata; // TODO: rework // 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; } int webdav_plist_add( pool_handle_t *pool, WebdavPList **begin, WebdavPList **end, WebdavProperty *prop) { WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList)); if(!elm) { return 1; } elm->prev = *end; elm->next = NULL; elm->property = prop; if(!*begin) { *begin = elm; *end = elm; return 0; } (*end)->next = elm; *end = elm; return 0; } 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; } } } int webdav_nslist_add( pool_handle_t *pool, WebdavNSList **begin, WebdavNSList **end, WSNamespace *ns) { // same as webdav_plist_add but with different type WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList)); if(!elm) { return 1; } elm->prev = *end; elm->next = NULL; elm->namespace = ns; if(!*begin) { *begin = elm; *end = elm; return 0; } (*end)->next = elm; *end = elm; return 0; } 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; }