--- a/src/server/webdav/webdav.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/webdav/webdav.c Thu Oct 31 10:26:35 2019 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * 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: @@ -30,6 +30,622 @@ #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; + +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 = &dav_resourcetype_collection_value; + 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) { + 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; + } + + 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; + } + } + + + Multistatus *ms = multistatus_response(sn, rq); + if(!ms) { + return REQ_ABORTED; + } + WebdavResponse *response = (WebdavResponse*)ms; + + WebdavBackend *dav = + rq->davCollection ? rq->davCollection : &default_backend; + + char *path = pblock_findkeyval(pb_key_path, rq->vars); + + uint32_t settings = dav->settings; + if(dav->propfind_init(propfind, path)) { + return REQ_ABORTED; + } + + 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)) { + return REQ_ABORTED; + } + statptr = &s; + if(!S_ISDIR(s.st_mode)) { + usevfs = FALSE; + } + } + if(propfind->depth == 0) { + usevfs = FALSE; + } + + int ret = REQ_ABORTED; + if(!dav->propfind_do(propfind, 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, propfind, 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; +} + +int propfind_children( + WebdavBackend *dav, + WebdavPropfindRequest *request, + WebdavResponse *response, + VFSContext *vfs, + char *path) +{ + 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(dav->propfind_do(request, 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) +{ + 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; +} + +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; + } + + property->namespace = &dav_namespace; + property->lang = NULL; + property->name = name; + property->value = NULL; + 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; + 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; +}