src/server/webdav/webdav.c

branch
webdav
changeset 211
2160585200ac
parent 107
7e81699d1f77
child 212
d7e7ea9c6bc6
--- 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;
+}

mercurial