src/server/webdav/webdav.c

changeset 385
a1f4cb076d2f
parent 381
7d55d60e1fe2
child 395
224c4e858125
--- a/src/server/webdav/webdav.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/webdav.c	Sat Sep 24 16:26:10 2022 +0200
@@ -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,1268 @@
 #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"
+#include "../daemon/protocol.h"
+#include "../daemon/vfs.h"
+
+/*
+ * http method fptr mapping
+ * key: http method name (string)
+ * value: SAF fptr
+ */
+static UcxMap *method_handler_map;
+
+/*
+ * webdav backend types
+ * key: backend name (string)
+ * value: WebdavBackend*
+ */
+static UcxMap *webdav_type_map;
+
+static WebdavBackend default_backend;
+
+static WSNamespace dav_namespace;
+
+static WebdavProperty dav_resourcetype_empty;
+static WebdavProperty dav_resourcetype_collection;
+static WSXmlData dav_resourcetype_collection_value;
+
+#define WEBDAV_RESOURCE_TYPE_COLLECTION "<D:collection/>"
+
+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;
+    default_backend.proppatch_do = default_proppatch_do;
+    default_backend.proppatch_finish = default_proppatch_finish;
+    default_backend.opt_mkcol = NULL;
+    default_backend.opt_delete = NULL;
+    default_backend.settings = WS_WEBDAV_PROPFIND_USE_VFS;
+    default_backend.instance = NULL;
+}
+
+int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate) {
+    WebdavType *webdavType = malloc(sizeof(WebdavType));
+    webdavType->init = webdavInit;
+    webdavType->create = webdavCreate;
+    return ucx_map_cstr_put(webdav_type_map, name, webdavType);
+}
+
+WebdavType* webdav_get_type(scstr_t dav_class) {
+    return ucx_map_sstr_get(webdav_type_map, dav_class);
+}
+
+void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error) {
+    *error = 0;
+    if(dav_class->init) {
+        void *initData = dav_class->init(cfg, pool, config);
+        if(!initData) {
+            *error = 1;
+        }
+        return initData;
+    } else {
+        return NULL;
+    }
+}
+
+WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData) {
+    WebdavType *webdavType = ucx_map_cstr_get(webdav_type_map, dav_class);
+    if(!webdavType) {
+        log_ereport(LOG_MISCONFIG, "webdav_create: unkown dav type %s", dav_class);
+        return NULL;
+    }
+    
+    return webdavType->create(sn, rq, pb, initData);
+}
+
+static WSBool webdav_is_initialized = FALSE;
+
+int webdav_init(pblock *pb, Session *sn, Request *rq) {
+    if(webdav_is_initialized) {
+        return REQ_NOACTION;
+    }
+    webdav_is_initialized = TRUE;
+    
+    webdav_type_map = ucx_map_new(8);
+    if(!webdav_type_map) {
+        return REQ_ABORTED;
+    }
+    
+    method_handler_map = ucx_map_new(64);
+    if(!method_handler_map) {
+        return REQ_ABORTED;
+    }
+    
+    init_default_backend();
+    ucx_map_cstr_put(webdav_type_map, "default", &default_backend);
+    
+    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_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION;
+    dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1;
+    
+    dav_resourcetype_collection.namespace = &dav_namespace;
+    dav_resourcetype_collection.name = "resourcetype";
+    dav_resourcetype_collection.value.data = dav_resourcetype_collection_value;
+    dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA;
+    
+    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) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    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);
+    
+    // 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;
+    }
+    
+    
+    int ret = webdav_propfind_do(dav, propfind, (WebdavResponse*)ms, NULL, path, uri);
+    
+    // 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: error response
+    }
+    
+    // cleanup
+    if(propfind->doc) {
+        xmlFreeDoc(propfind->doc);
+    }
+    
+    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,
+        const char *uri,
+        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;
+        pReq->dav = davList;
+        
+        // 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, uri, &newProp)) {
+            return REQ_ABORTED;
+        }
+        
+        newPropCount = webdav_plist_size(newProp);
+        
+        davList = davList->next;
+    }
+    
+    *out_req = requestObjects;
+    return REQ_PROCEED;
+}
+
+int webdav_propfind_do(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path,
+        char *uri)
+{
+    Session *sn = propfind->sn;
+    Request *rq = propfind->rq;
+    
+    // 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, uri, &requestObjects)) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavOperation *op = webdav_create_propfind_operation(
+            sn,
+            rq,
+            dav,
+            propfind->properties,
+            requestObjects,
+            response);
+    
+    // some Backends can list all children by themselves, but some
+    // require the VFS for this
+    WSBool usevfs = (settings & WS_WEBDAV_PROPFIND_USE_VFS)
+                        == WS_WEBDAV_PROPFIND_USE_VFS;
+    struct stat s;
+    struct stat *statptr = NULL;
+    
+    if(usevfs && !vfs) {
+        vfs = vfs_request_context(sn, rq);
+        if(!vfs) {
+            return REQ_ABORTED;
+        }
+        
+        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, uri, 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)) {
+        // TODO: log error
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    int error = 0;
+    WebdavProppatchRequest *proppatch = proppatch_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!proppatch) {
+        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);
+    
+    // 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;
+    }
+    ms->proppatch = TRUE;
+    
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    WebdavOperation *op = webdav_create_proppatch_operation(
+            sn,
+            rq,
+            dav,
+            proppatch,
+            response);
+    
+    int ret = REQ_PROCEED;
+    
+    // Execute proppatch
+    if(webdav_op_proppatch(op, uri, path)) {
+        ret = REQ_ABORTED;
+    }
+    
+    // send response
+    if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) {
+        ret = REQ_ABORTED;
+        // TODO: log error
+    } else {
+        // TODO: error response
+    }
+    
+    // cleanup
+    xmlFreeDoc(proppatch->doc);
+    
+    return ret;
+}
+
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = REQ_ABORTED;
+    if(!webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR)) {
+        pblock_nvinsert("content-length", "0", rq->srvhdrs);
+        protocol_status(sn, rq, 201, NULL);
+        protocol_start_response(sn, rq);
+        ret = REQ_PROCEED;
+    } else {
+        int status_code = 500;
+        if(op->vfs->vfs_errno == EEXIST) {
+            // 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL.
+            status_code = 405;
+        } else if(op->vfs->vfs_errno == ENOENT) {
+            // 409 (Conflict) - A collection cannot be made at the Request-URI until
+            // one or more intermediate collections have been created.  The server
+            // MUST NOT create those intermediate collections automatically.
+            status_code = 409;
+        }
+        protocol_status(sn, rq, status_code, NULL);
+    }
+    
+    return ret;
+}
+
+int webdav_post(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+typedef struct DeleteFile {
+    char *path;
+    struct stat s;
+} DeleteFile;
+
+typedef struct DeleteLists {
+    UcxAllocator *a;
+    UcxList *dirs_begin;
+    UcxList *dirs_end;
+    UcxList *files_begin;
+    UcxList *files_end;
+} DeleteOp;
+
+static int deletelist_add(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *userdata)
+{
+    DeleteOp *op = userdata;
+    
+    // create object for this file
+    DeleteFile *file = almalloc(op->a, sizeof(DeleteFile));
+    if(!file) {
+        return 1;
+    }
+    file->path = sstrdup_a(op->a, sstr((char*)path)).ptr;
+    if(!file->path) {
+        return 1;
+    }
+    file->s = *s;
+    
+    // determine which list to use
+    UcxList **begin;
+    UcxList **end;
+    if(S_ISDIR(s->st_mode)) {
+        begin = &op->dirs_begin;
+        end = &op->dirs_end;
+    } else {
+        begin = &op->files_begin;
+        end = &op->files_end;
+    }
+    
+    // add file to list
+    UcxList *elm = ucx_list_append_a(op->a, NULL, file);
+    if(!elm) {
+        alfree(op->a, file->path); // at least do some cleanup, although it
+        alfree(op->a, file);       // isn't really necessary
+        return 1;
+    }
+    if(*begin == NULL) {
+        *begin = elm;
+        *end = elm;
+    } else {
+        ucx_list_concat(*end, elm);
+        *end = elm;
+    }
+    
+    return 0;
+}
+
+static int webdav_delete_collection(WebdavVFSOperation *op)
+{
+    DeleteOp del;
+    ZERO(&del, sizeof(DeleteOp));
+    del.a = session_get_allocator(op->sn);
+    
+    // get a list of all files
+    if(webdav_op_iterate_children(op->vfs, -1, NULL, op->path,
+            deletelist_add, &del))
+    {
+        return 1;
+    }
+    
+    // add root to list of dir list
+    DeleteFile root;
+    root.path = op->path;
+    root.s = *op->stat;
+    UcxList root_elm;
+    root_elm.data = &root;
+    root_elm.prev = NULL;
+    root_elm.next = del.dirs_begin;
+    
+    if(del.dirs_begin) {
+        del.dirs_begin->prev = &root_elm;
+        del.dirs_begin = &root_elm;
+    } else {
+        del.dirs_begin = &root_elm;
+        del.dirs_end = &root_elm;
+    }
+    
+    // delete files first
+    UCX_FOREACH(elm, del.files_begin) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    // delete directories, reverse order
+    for(UcxList *elm=del.dirs_end;elm;elm=elm->prev) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+int webdav_delete(pblock *pb, Session *sn, Request *rq) { 
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    // stat to find out if the resource is a collection
+    struct stat s;
+    if(vfs_stat(op->vfs, op->path, &s)) {
+        sys_set_error_status(op->vfs);
+        return REQ_ABORTED;
+    }
+    op->stat = &s;
+    
+    int ret;
+    if(S_ISDIR(s.st_mode)) {
+        ret = webdav_delete_collection(op);
+    } else {
+        ret = webdav_vfs_op_do(op, WEBDAV_VFS_DELETE);
+    }
+    
+    // send response
+    if(ret == REQ_PROCEED) {
+        pblock_nvinsert("content-length", "0", rq->srvhdrs);
+        protocol_status(sn, rq, 204, NULL);
+        protocol_start_response(sn, rq);
+    } else {
+        protocol_status(sn, rq, 500, NULL);
+    }
+    
+    return ret;
+}
+
+int webdav_put(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat s;
+    int create_file = 0;
+    if(vfs_stat(vfs, path, &s)) {
+        if(vfs->vfs_errno == ENOENT) {
+            create_file = O_CREAT;
+        } else {
+            protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+            return REQ_ABORTED;
+        }
+    } else if(S_ISDIR(s.st_mode)) {
+        // PUT on collections is not allowed
+        protocol_status(sn, rq, PROTOCOL_METHOD_NOT_ALLOWED, NULL);
+        return REQ_ABORTED;
+    }
+    
+    SYS_FILE fd = vfs_open(vfs, path, O_WRONLY | O_TRUNC | create_file);
+    if(!fd) {
+        // if it fails, vfs_open sets http status code
+        return REQ_ABORTED;
+    }
+    
+    // TODO: check permissions, lock, ...
+    
+    // all checks done
+    
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    char in[4096];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        int w = 0;
+        while(w < r) {
+            w += system_fwrite(fd, in, r);
+        }
+    }
+    
+    system_fclose(fd);
+    
+    int status = create_file ? PROTOCOL_CREATED : PROTOCOL_NO_CONTENT;
+    pblock_nvinsert("content-length", "0", rq->srvhdrs);
+    protocol_status(sn, rq, status, NULL);
+    protocol_start_response(sn, rq);
+    
+    return REQ_PROCEED;
+}
+
+int webdav_copy(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    char *destination = pblock_findval("destination", rq->headers);
+    if(!destination) {
+        protocol_status(sn, rq, PROTOCOL_BAD_REQUEST, NULL);
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat src_s;
+    if(vfs_stat(vfs, path, &src_s)) {
+        protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+        return REQ_ABORTED;
+    }
+    
+    // TODO: if src is a directory, make sure the uri has a trailing path separator
+    
+    
+    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,
+        const char *href,
+        WebdavPList **outplist)
+{
+    DefaultWebdavData *data = pool_malloc(
+            rq->sn->pool,
+            sizeof(DefaultWebdavData));
+    if(!data) {
+        return 1;
+    }
+    rq->userdata = data;
+    
+    data->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0);
+    
+    return 0;
+}
+
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        WebdavResource *resource,
+        struct stat *s)
+{
+    DefaultWebdavData *data = request->userdata;
+    
+    // add all requested vfs properties like getcontentlength ...
+    if(webdav_add_vfs_properties(
+            resource,
+            request->sn->pool,
+            data->vfsproperties,
+            s))
+    {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int default_propfind_finish(WebdavPropfindRequest *rq) {
+    return 0;
+}
+
+int default_proppatch_do(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WebdavPList **setInOut,
+            WebdavPList **removeInOut)
+{
+    return 0;
+}
+
+int default_proppatch_finish(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WSBool commit)
+{
+    return 0;
+}
+
+
+
+/* ------------------------------ Utils ------------------------------ */
+
+UcxKey webdav_property_key(const char *ns, const char *name) {
+    UcxKey key;
+    sstr_t data = ucx_sprintf("%s\n%s", name, ns);
+    key.data = data.ptr;
+    key.len = data.length;
+    key.hash = ucx_hash(data.ptr, data.length);
+    return key;
+}
+
+
+
+
+/* ------------------------------ 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) {
+    return webdav_plist_clone_s(pool, list, NULL);
+}
+
+WebdavPList* webdav_plist_clone_s(
+        pool_handle_t *pool,
+        WebdavPList *list,
+        size_t *newlen)
+{
+    WebdavPList *new_list = NULL;     // start of the new list
+    WebdavPList *new_list_end = NULL; // end of the new list
+    
+    size_t len = 0;
+    
+    WebdavPList *elm = list;
+    while(elm) {
+        // copy list item
+        WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList));
+        if(!new_elm) {
+            if(newlen) *newlen = 0;
+            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;
+        
+        len++;
+        elm = elm->next;
+    }
+    
+    if(newlen) *newlen = len;
+    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_resourcetype_collection(void) {
+    return &dav_resourcetype_collection;
+}
+
+WebdavProperty* webdav_resourcetype_empty(void) {
+    return &dav_resourcetype_empty;
+}
+
+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_resource_add_dav_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *name,
+        const char *str,
+        size_t len)
+{
+    WebdavProperty *property = webdav_dav_property(pool, name);
+    if(!property) {
+        return 1;
+    }
+    
+    property->name = pool_strdup(pool, name);
+    if(!property->name) {
+        return 1;
+    }
+    
+    char *value = pool_malloc(pool, len+1);
+    if(!value) {
+        return 1;
+    }
+    memcpy(value, str, len);
+    value[len] = '\0';
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    return res->addproperty(res, property, 200);
+}
+
+int webdav_resource_add_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *xmlns_prefix,
+        const char *xmlns_href,
+        const char *name,
+        const char *str,
+        size_t len)
+{
+    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+    if(!property) {
+        return 1;
+    }
+    memset(property, 0, sizeof(WebdavProperty));
+    
+    property->name = pool_strdup(pool, name);
+    if(!property->name) {
+        return 1;
+    }
+    
+    xmlNs *ns = pool_malloc(pool, sizeof(xmlNs));
+    if(!ns) {
+        return 1;
+    }
+    memset(ns, 0, sizeof(xmlNs));
+    ns->prefix = (const xmlChar*)pool_strdup(pool, xmlns_prefix);
+    ns->href = (const xmlChar*)pool_strdup(pool, xmlns_href);
+    if(!ns->prefix || !ns->href) {
+        return 1;
+    }
+    
+    char *value = pool_malloc(pool, len+1);
+    if(!value) {
+        return 1;
+    }
+    memcpy(value, str, len);
+    value[len] = '\0';
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    return res->addproperty(res, property, 200);
+}
+
+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(
+        WebdavPList **plistInOut,
+        WSBool removefromlist,
+        WSBool allprop,
+        uint32_t flags)
+{
+    WebdavVFSProperties ret;
+    ZERO(&ret, sizeof(WebdavVFSProperties));
+    
+    WSBool etag = 1;
+    WSBool creationdate = 1;
+    
+    WebdavPListIterator i = webdav_plist_iterator(plistInOut);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WSNamespace *ns = cur->property->namespace;
+        if(ns && !strcmp((const char*)ns->href, "DAV:")) {
+            const char *name = cur->property->name;
+            WSBool remove_prop = removefromlist;
+            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 {
+                remove_prop = FALSE;
+            }
+            
+            if(remove_prop) {
+                webdav_plist_iterator_remove_current(&i);
+            }
+        }
+    }
+    
+    if(allprop) {
+        ret.creationdate = 1;
+        ret.getcontentlength = 1;
+        ret.getetag = 1;
+        ret.getlastmodified = 1;
+        ret.getresourcetype = 1;
+    }
+    
+    return ret;
+}
+
+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, contentlength);
+        if(webdav_resource_add_dav_stringproperty(res, pool, "getcontentlength", buf, strlen(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(webdav_resource_add_dav_stringproperty(res, pool, "getlastmodified", buf, strlen(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\"",
+            (int)s->st_size,
+            (int)s->st_mtim.tv_sec);
+        if(webdav_resource_add_dav_stringproperty(res, pool, "getetag", buf, strlen(buf))) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}

mercurial