src/server/test/webdav.c

changeset 385
a1f4cb076d2f
parent 376
61d481d3c2e4
child 415
d938228c382e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,1767 @@
+/*
+ * 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 "testutils.h"
+
+#include "../webdav/requestparser.h"
+#include "../webdav/webdav.h"
+#include "../webdav/multistatus.h"
+#include "../webdav/operation.h"
+
+#include "vfs.h"
+#include "webdav.h"
+
+static int webdav_is_initialized = 0;
+
+/* ----------------------------- Test Backends --------------------------*/
+
+static int backend2_init_called = 0;
+static int backend2_propfind_do_count = 0;
+static int backend2_propfind_finish_called = 0;
+static int backend2_proppatch_commit = 0;
+static int backend2_proppatch_do_count = 0;
+static int backend2_proppatch_finish_count = 0;
+
+// backend2
+static int backend2_propfind_init(
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *href,
+        WebdavPList **outPList)
+{
+    backend2_init_called = 1;
+    return 0;
+}
+
+static int backend2_propfind_do(
+            WebdavPropfindRequest *propfind,
+            WebdavResponse *response,
+            VFS_DIR parent,
+            WebdavResource *resource,
+            struct stat *s)
+{
+    backend2_propfind_do_count++;
+    return 0;
+}
+
+static int backend2_propfind_finish(WebdavPropfindRequest *propfind) {
+    backend2_propfind_finish_called = 1;
+    return 0;
+}
+
+static int backend2_proppatch_do(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WebdavPList **out_set,
+        WebdavPList **out_remove)
+{
+    backend2_proppatch_do_count++;
+    
+    if(*out_remove) {
+        return 1; // backend1 should remove all remove-props
+    }
+    
+    WebdavPListIterator i = webdav_plist_iterator(out_set);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(!strcmp(cur->property->name, "a")) {
+            // property 'a' should already be removed by backend1
+            return 1;
+        } else if(!strcmp(cur->property->name, "abort")) {
+            return 1; // test abort
+        }
+        response->addproperty(response, cur->property, 200);
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    return 0;
+}
+
+static int backend2_proppatch_finish(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WSBool commit)
+{
+    backend2_proppatch_finish_count++;
+    backend2_proppatch_commit = commit;
+    return 0;
+}
+
+static WebdavBackend backend2 = {
+    backend2_propfind_init,
+    backend2_propfind_do,
+    backend2_propfind_finish,
+    backend2_proppatch_do,
+    backend2_proppatch_finish,
+    NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
+    NULL, // opt_delete
+    NULL, // opt_delete_finish
+    0,
+    NULL, // instance
+    NULL
+};
+
+// backend1
+
+static int backend1_init_called = 0;
+static int backend1_propfind_do_count = 0;
+static int backend1_propfind_finish_called = 0;
+static int backend1_proppatch_commit = 0;
+static int backend1_proppatch_do_count = 0;
+static int backend1_proppatch_finish_count = 0;
+
+
+static int backend1_propfind_init(
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *href,
+        WebdavPList **outPList)
+{
+    backend1_init_called = 1;
+    
+    WebdavPList *plist = *outPList;
+    WebdavProperty *p = plist->property;
+    if(!strcmp(p->name, "displayname")) {
+        plist->next->prev = NULL;
+        *outPList = plist->next; // remove first item from plist
+    } else {
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int backend1_propfind_do(
+            WebdavPropfindRequest *propfind,
+            WebdavResponse *response,
+            VFS_DIR parent,
+            WebdavResource *resource,
+            struct stat *s)
+{
+    backend1_propfind_do_count++;
+    return 0;
+}
+
+static int backend1_propfind_finish(WebdavPropfindRequest *propfind) {
+    backend1_propfind_finish_called = 1;
+    return 0;
+}
+
+static int backend1_proppatch_do(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WebdavPList **out_set,
+        WebdavPList **out_remove)
+{
+    backend1_proppatch_do_count++;
+    
+    // remove everything from out_remove
+    WebdavPListIterator i = webdav_plist_iterator(out_remove);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        response->addproperty(response, cur->property, 200);
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    // remove property 'a' and fail at property 'fail'
+    i = webdav_plist_iterator(out_set);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(!strcmp(cur->property->name, "fail")) {
+            response->addproperty(response, cur->property, 403);
+            webdav_plist_iterator_remove_current(&i);
+        } else if(!strcmp(cur->property->name, "a")) {
+            response->addproperty(response, cur->property, 200);
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+        
+    return 0;
+}
+
+static int backend1_proppatch_finish(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WSBool commit)
+{
+    backend1_proppatch_finish_count++;
+    backend1_proppatch_commit = commit;
+    return 0;
+}
+
+WebdavBackend backend1 = {
+    backend1_propfind_init,
+    backend1_propfind_do,
+    backend1_propfind_finish,
+    backend1_proppatch_do,
+    backend1_proppatch_finish,
+    NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
+    NULL, // opt_delete
+    NULL, // opt_delete_finish
+    0,
+    NULL, // instance
+    &backend2
+};
+
+static void reset_backends(void) {
+    backend1_init_called = 0;
+    backend1_propfind_do_count = 0;
+    backend1_propfind_finish_called = 0;
+    backend1_proppatch_commit = 0;
+    backend1_proppatch_do_count = 0;
+    backend1_proppatch_finish_count = 0;
+    backend2_init_called = 0;
+    backend2_propfind_do_count = 0;
+    backend2_propfind_finish_called = 0;
+    backend2_proppatch_commit = 0;
+    backend2_proppatch_do_count = 0;
+    backend2_proppatch_finish_count = 0;
+}
+
+/* ----------------------------------------------------------------------*/
+
+
+static int test_init(
+        Session **out_sn,
+        Request **out_rq,
+        WebdavPropfindRequest **out_propfind,
+        const char *xml)
+{
+    if(!webdav_is_initialized) {
+        if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) {
+            return 1;
+        }
+        webdav_is_initialized = 1;
+    }
+    
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    int error = 0;
+    
+    WebdavPropfindRequest *propfind = propfind_parse(
+            sn,
+            rq,
+            xml,
+            strlen(xml),
+            &error);
+    
+    if(error) {
+        return 1;
+    }
+    
+    if(!propfind || !propfind->properties) {
+        return 1;
+    }
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_propfind = propfind;
+    return 0;
+}
+
+static WebdavOperation* test_propfind_op(
+        Session **out_sn,
+        Request **out_rq,
+        const char *xml)
+{
+    WebdavPropfindRequest *propfind;
+    if(test_init(out_sn, out_rq, &propfind, xml)) {
+        return NULL;
+    }
+    
+    Multistatus *ms = multistatus_response(*out_sn, *out_rq);
+    if(!ms) {
+        return NULL;
+    }
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    UcxList *requests = NULL;
+    
+    // Initialize all Webdav Backends
+    if(webdav_propfind_init(&backend1, propfind, "/", "/", &requests)) {
+        return NULL;
+    }
+    
+    return webdav_create_propfind_operation(
+            (*out_sn),
+            (*out_rq),
+            &backend1,
+            propfind->properties,
+            requests,
+            response);
+}
+
+
+UCX_TEST(test_webdav_plist_add) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavPList *begin = NULL;
+    WebdavPList *end = NULL;
+    
+    WebdavProperty p1, p2, p3;
+    ZERO(&p1, sizeof(WebdavProperty));
+    ZERO(&p2, sizeof(WebdavProperty));
+    ZERO(&p3, sizeof(WebdavProperty));
+    int r;
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p1);
+    
+    UCX_TEST_ASSERT(r == 0, "add 1 failed");
+    UCX_TEST_ASSERT(begin && end, "ptrs are NULL");
+    UCX_TEST_ASSERT(begin == end, "begin != end");
+    UCX_TEST_ASSERT(begin->prev == NULL, "begin->prev not NULL");
+    UCX_TEST_ASSERT(begin->next == NULL, "begin->next not NULL");
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p2);
+    
+    UCX_TEST_ASSERT(r == 0, "add 2 failed");
+    UCX_TEST_ASSERT(begin && end, "add2: ptrs are NULL");
+    UCX_TEST_ASSERT(begin->next, "begin->next is NULL");
+    UCX_TEST_ASSERT(begin->next == end, "begin->next != end");
+    UCX_TEST_ASSERT(end->prev = begin, "end->prev != begin");
+    UCX_TEST_ASSERT(begin->prev == NULL, "add2: begin->prev not NULL");
+    UCX_TEST_ASSERT(end->next == NULL, "add2: end->next not NULL");
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p3);
+    
+    UCX_TEST_ASSERT(r == 0, "add 3 failed");
+    UCX_TEST_ASSERT(begin && end, "add3: ptrs are NULL");
+    UCX_TEST_ASSERT(begin->next == end->prev, "begin->next != end->prev");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_plist_size) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavPList *begin = NULL;
+    WebdavPList *end = NULL;
+    
+    WebdavProperty p1, p2, p3;
+    ZERO(&p1, sizeof(WebdavProperty));
+    ZERO(&p2, sizeof(WebdavProperty));
+    ZERO(&p3, sizeof(WebdavProperty));
+    int r;
+    
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 0, "size != 0");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p1);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 1, "size != 1");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p2);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 2, "size != 2");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p3);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 3, "size != 3");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_propfind_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    UCX_TEST_BEGIN
+    
+    int error = 0;
+    
+    //
+    // ----------------- TEST_PROPFIND1 -----------------
+    // test basic propfind request
+    WebdavPropfindRequest *p1 = propfind_parse(
+            sn,
+            rq,
+            TEST_PROPFIND1,
+            strlen(TEST_PROPFIND1),
+            &error);
+    
+    UCX_TEST_ASSERT(p1, "p1 is NULL");
+    UCX_TEST_ASSERT(p1->properties, "p1: no props");
+    UCX_TEST_ASSERT(!p1->allprop, "p1: allprop is TRUE");
+    UCX_TEST_ASSERT(!p1->propname, "p1: propname is TRUE");
+    UCX_TEST_ASSERT(p1->propcount == 6, "p1: wrong propcount");
+    
+    // property 1: DAV:displayname
+    WebdavPList *elm = p1->properties;
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "displayname"),
+            "p1: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 1 has wrong namespace");
+    
+    // property 2: DAV:getcontentlength
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 2 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "getcontentlength"),
+            "p1: property 2 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 2 has wrong namespace");
+    
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 3 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 4 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 5 missing");
+    
+    // property 6: DAV:getetag
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 6 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "getetag"),
+            "p1: property 6 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 6 has wrong namespace");
+    UCX_TEST_ASSERT(!elm->next, "p1: should not have property 7");
+    
+    //
+    // ----------------- TEST_PROPFIND2 -----------------
+    // test with multiple namespaces
+    WebdavPropfindRequest *p2 = propfind_parse(
+            sn,
+            rq,
+            TEST_PROPFIND2,
+            strlen(TEST_PROPFIND2),
+            &error);
+    
+    UCX_TEST_ASSERT(p2, "p2 is NULL");
+    UCX_TEST_ASSERT(p2->properties, "p2: no props");
+    UCX_TEST_ASSERT(!p2->allprop, "p2: allprop is TRUE");
+    UCX_TEST_ASSERT(!p2->propname, "p2: propname is TRUE");
+    
+    // property 1: DAV:resourcetype
+    elm = p2->properties;
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "resourcetype"),
+            "p2: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p2: property 1 has wrong namespace");
+    
+    // property 2: X:testprop
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 2 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "testprop"),
+            "p2: property 2 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "http://example.com/"),
+            "p2: property 2 has wrong namespace");
+    
+    // property 3: X:name
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 3 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "name"),
+            "p2: property 3 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "http://example.com/"),
+            "p2: property 3 has wrong namespace");
+    
+    // property 4: Z:testprop
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 4 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "testprop"),
+            "p2: property 4 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "testns"),
+            "p2: property 4 has wrong namespace");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND3 -----------------
+    // test allprop
+    WebdavPropfindRequest *p3 = propfind_parse(sn, rq, TEST_PROPFIND3, strlen(TEST_PROPFIND3), &error);
+    
+    UCX_TEST_ASSERT(p3, "p3 is NULL");
+    UCX_TEST_ASSERT(!p3->properties, "p2: has props");
+    UCX_TEST_ASSERT(p3->allprop, "p2: allprop is FALSE");
+    UCX_TEST_ASSERT(!p3->propname, "p2: propname is TRUE");
+    UCX_TEST_ASSERT(p3->propcount == 0, "p2: wrong propcount");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND4 -----------------
+    // test propname
+    WebdavPropfindRequest *p4 = propfind_parse(sn, rq, TEST_PROPFIND4, strlen(TEST_PROPFIND4), &error);
+    
+    UCX_TEST_ASSERT(p4, "p4 is NULL");
+    UCX_TEST_ASSERT(!p4->properties, "p2: has props");
+    UCX_TEST_ASSERT(!p4->allprop, "p2: allprop is TRUE");
+    UCX_TEST_ASSERT(p4->propname, "p2: propname is FALSE");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND5 -----------------
+    // test duplicate check
+    WebdavPropfindRequest *p5 = propfind_parse(sn, rq, TEST_PROPFIND5, strlen(TEST_PROPFIND5), &error);
+    
+    UCX_TEST_ASSERT(p5, "p5 is NULL");
+    UCX_TEST_ASSERT(p5->properties, "p5: no props");
+    UCX_TEST_ASSERT(!p5->allprop, "p5: allprop is TRUE");
+    UCX_TEST_ASSERT(!p5->propname, "p5: propname is TRUE");
+    UCX_TEST_ASSERT(p5->propcount == 4, "p5: wrong propcount");
+    
+    // property 1: DAV:displayname
+    elm = p5->properties;
+    UCX_TEST_ASSERT(elm, "p5: property 1 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "displayname"),
+            "p5: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p5: property 1 has wrong namespace");
+    
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 2 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 3 missing");
+    
+    // property 4: DAV:resourcetype
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 4 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "resourcetype"),
+            "p5: property 4 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p5: property 4 has wrong namespace");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND6 -----------------
+    // test prop/allprop mix
+    WebdavPropfindRequest *p6 = propfind_parse(sn, rq, TEST_PROPFIND6, strlen(TEST_PROPFIND6), &error);
+    
+    UCX_TEST_ASSERT(p6, "p5 is NULL");
+    UCX_TEST_ASSERT(!p6->properties, "p5: has props");
+    UCX_TEST_ASSERT(p6->allprop, "p5: allprop is FALSE");
+    UCX_TEST_ASSERT(!p6->propname, "p5: propname is TRUE");
+    UCX_TEST_ASSERT(p6->propcount == 0, "p5: wrong propcount");
+    
+    UCX_TEST_END
+            
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_proppatch_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPPATCH", "/");
+    
+    UCX_TEST_BEGIN
+    int error = 0;
+    
+    WebdavProppatchRequest *p1 = proppatch_parse(sn, rq, TEST_PROPPATCH1, strlen(TEST_PROPPATCH1), &error);
+    
+    UCX_TEST_ASSERT(p1->set, "p1: missing set props");
+    UCX_TEST_ASSERT(!p1->remove, "p1: has remove props");
+    UCX_TEST_ASSERT(p1->setcount == 2, "p1: wrong setcount");
+    UCX_TEST_ASSERT(p1->set->next, "p1: set plist broken");
+    UCX_TEST_ASSERT(!p1->set->next->next, "p1: set plist has no end");
+    UCX_TEST_ASSERT(p1->set->property, "p1: missing property ptr in plist");
+    UCX_TEST_ASSERT(
+            !strcmp(p1->set->property->name, "test"),
+            "p1: wrong property 1 name");
+    
+    WebdavProppatchRequest *p2 = proppatch_parse(sn, rq, TEST_PROPPATCH2, strlen(TEST_PROPPATCH2), &error);
+    
+    UCX_TEST_ASSERT(p2->set, "p2: missing set props");
+    UCX_TEST_ASSERT(p2->remove, "p2: missing remove props");
+    UCX_TEST_ASSERT(p2->setcount == 4, "p2: wrong setcount");
+    UCX_TEST_ASSERT(p2->removecount == 1, "p2: wrong removecount");
+    
+    UCX_TEST_ASSERT(
+            !strcmp((char*)p2->set->property->namespace->href, "http://example.com/"),
+            "p2: set property 1: wrong namespace");
+    UCX_TEST_ASSERT(
+            !strcmp(p2->set->property->name, "a"),
+            "p2: set property 1: wrong name");
+    WSXmlNode *p2set1 = p2->set->property->value.node;
+    UCX_TEST_ASSERT(
+            p2set1->type == WS_NODE_TEXT,
+            "p2: set property 1: wrong type");
+    UCX_TEST_ASSERT(
+            p2set1->content,
+            "p2: set property 1: no text");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)p2set1->content, "test"),
+            "p2: set property 1: wrong value");
+    
+    WSXmlNode *p2set3 = p2->set->next->next->property->value.node;
+    UCX_TEST_ASSERT(p2set3, "p2: set property 3 missing");
+    UCX_TEST_ASSERT(
+            p2set3->type == WS_NODE_TEXT,
+            "p2: set property 3: wrong type");
+    UCX_TEST_ASSERT(
+            p2set3->next,
+            "p2: set property 3: missing element X:name");
+    
+    UCX_TEST_ASSERT(
+            xmlHasProp(p2set3->next, BAD_CAST"test"),
+            "p2: set property 3: missing attribute 'test'");
+    
+    UCX_TEST_ASSERT(
+            xmlHasProp(p2set3->next, BAD_CAST"abc"),
+            "p2: set property 3: missing attribute 'abc");
+    
+    xmlChar *value1 = xmlGetProp(p2set3->next, BAD_CAST"test");
+    UCX_TEST_ASSERT(
+            !strcmp((char*) value1, "test1"),
+            "p2: set property 3: wrong attribute value 1");
+    xmlFree(value1);
+    
+    xmlChar *value2 = xmlGetProp(p2set3->next, BAD_CAST"abc");
+    UCX_TEST_ASSERT(
+            !strcmp((char*) value2, "def"),
+            "p2: set property 3: wrong attribute value 2");
+    xmlFree(value2);
+    
+    UCX_TEST_ASSERT(
+            !strcmp(p2->remove->property->name, "e"),
+            "p2: wrong remove property");
+    
+    UCX_TEST_END
+            
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_lock_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "LOCK", "/");
+    
+    UCX_TEST_BEGIN
+    int error = 0;
+    
+    WebdavLockRequest *l1 = lock_parse(sn, rq, TEST_LOCK1, strlen(TEST_LOCK1), &error);
+    
+    UCX_TEST_ASSERT(l1, "l1 is NULL");
+    UCX_TEST_ASSERT(l1->type == WEBDAV_LOCK_WRITE, "l1: wrong type");
+    UCX_TEST_ASSERT(l1->scope == WEBDAV_LOCK_SHARED, "l1: wrong scope");
+    UCX_TEST_ASSERT(l1->owner, "l1: owner is NULL");
+    UCX_TEST_ASSERT(!strcmp((char*)l1->owner->content, "User"), "l1: wrong owner");
+    
+    UCX_TEST_END
+    
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_rqbody2buffer) {
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    //
+    // TEST 1
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, "PUT", "/");
+    testutil_request_body(sn, rq, "Hello World!", 12);
+    
+    UcxBuffer *b1 = rqbody2buffer(sn, rq);
+    UCX_TEST_ASSERT(b1->size == 12, "b1: wrong size");
+    UCX_TEST_ASSERT(!memcmp(b1->space,"Hello World!",12), "b1: wrong content");
+    
+    ucx_buffer_free(b1);
+    testutil_destroy_session(sn);
+    
+    //
+    // TEST 2
+    size_t len1 = 25000;
+    unsigned char *body1 = malloc(len1);
+    for(int i=0;i<len1;i++) {
+        body1[i] = i;
+    }
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, "PUT", "/");
+    testutil_request_body(sn, rq, (char*)body1, len1);
+    
+    UcxBuffer *b2 = rqbody2buffer(sn, rq);
+    UCX_TEST_ASSERT(b2->size == len1, "b2: wrong size");
+    UCX_TEST_ASSERT(!memcmp(b2->space, body1, len1), "b2: wrong content");
+    
+    ucx_buffer_free(b2);
+    testutil_destroy_session(sn);
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_plist_iterator) {
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    WebdavPList *properties = propfind->properties;
+    size_t count = 0;
+    
+    WebdavPListIterator i = webdav_plist_iterator(&properties);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        switch(i.index) {
+            case 0: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "displayname"), "wrong property 1");
+                break;
+            }
+            case 1: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontentlength"), "wrong property 2");
+                break;
+            }
+            case 2: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontenttype"), "wrong property 3");
+                break;
+            }
+            case 3: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getlastmodified"), "wrong property 4");
+                break;
+            }
+            case 4: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "resourcetype"), "wrong property 5");
+                break;
+            }
+            case 5: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getetag"), "wrong property 6");
+                break;
+            }
+        }
+        count++;
+    }
+    
+    UCX_TEST_ASSERT(count == propfind->propcount, "wrong count");
+    
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_plist_iterator_remove_current) {
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    WebdavPList *properties1 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties2 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties3 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties4 = webdav_plist_clone(sn->pool, propfind->properties);
+    
+    WebdavPListIterator i;
+    WebdavPList *cur;
+    
+    // test removal of first element
+    i = webdav_plist_iterator(&properties1);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 0) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(!properties1->prev, "test1: prev not cleared");
+    UCX_TEST_ASSERT(!strcmp(properties1->property->name, "getcontentlength"), "test1: wrong property");
+    UCX_TEST_ASSERT(!strcmp(properties1->next->property->name, "getcontenttype"), "test1: wrong property 2");
+    UCX_TEST_ASSERT(properties1->next->prev == properties1, "test1: wrong link");
+    
+    // test removal of second element
+    i = webdav_plist_iterator(&properties2);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 1) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(!strcmp(properties2->next->property->name, "getcontenttype"), "test2: wrong property");
+    UCX_TEST_ASSERT(properties2->next->prev == properties2, "test2: wrong link");
+    UCX_TEST_ASSERT(webdav_plist_size(properties2) == 5, "test2: wrong size");
+    
+    // remove last element
+    i = webdav_plist_iterator(&properties3);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 5) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(webdav_plist_size(properties3) == 5, "test3: wrong size");
+    UCX_TEST_ASSERT(!strcmp(properties3->next->next->next->next->property->name, "resourcetype"), "test2: wrong property");
+    
+    // remove all elements
+    i = webdav_plist_iterator(&properties4);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        webdav_plist_iterator_remove_current(&i);
+        switch(i.index) {
+            case 0: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontentlength"), "test4: wrong property 2");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (0)");
+                break;
+            }
+            case 1: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontenttype"), "test4: wrong property 3");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (1)");
+                break;
+            }
+            case 2: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getlastmodified"), "test4: wrong property 4");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (2)");
+                break;
+            }
+            case 3: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "resourcetype"), "test4: wrong property 5");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (3)");
+                break;
+            }
+            case 4: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getetag"), "test4: wrong property 6");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (4)");
+                break;
+            }
+            default: {
+                UCX_TEST_ASSERT(i.index <= 5, "fail");
+            }
+        }
+    }
+    
+    UCX_TEST_ASSERT(properties4 == NULL, "test4: list not NULL");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_msresponse_addproperty) {
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "init failed");
+    UCX_TEST_ASSERT(op->response, "no response");
+    
+    Multistatus *ms = (Multistatus*)op->response;
+    MSResponse *r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/");
+    
+    WebdavProperty p1;
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    
+    WSNamespace ns1;
+    ZERO(&ns1, sizeof(WSNamespace));
+    WSNamespace ns2;
+    ZERO(&ns2, sizeof(WSNamespace));
+    ns1.prefix = (xmlChar*)"x1";
+    ns1.href = (xmlChar*)"http://example.com/test/";
+    ns2.prefix = (xmlChar*)"x2";
+    ns2.href = (xmlChar*)"http://example.com/test/";
+    
+    WebdavProperty dp1;
+    ZERO(&dp1, sizeof(WebdavProperty));
+    dp1.name = "dup";
+    dp1.namespace = &ns1;
+    dp1.value.text.str = "Hello";
+    dp1.value.text.length = 5;
+    dp1.vtype = WS_VALUE_TEXT;
+    
+    WebdavProperty dp2;
+    ZERO(&dp2, sizeof(WebdavProperty));
+    dp2.name = "dup";
+    dp2.namespace = &ns1;
+    dp2.value.text.str = "Hello";
+    dp2.value.text.length = 5;
+    dp2.vtype = WS_VALUE_TEXT;
+    
+    WebdavProperty dp3;
+    ZERO(&dp3, sizeof(WebdavProperty));
+    dp3.name = "dup";
+    dp3.namespace = &ns2;
+    dp3.value.text.str = "Hello";
+    dp3.value.text.length = 5;
+    dp3.vtype = WS_VALUE_TEXT;
+    
+    // init test data
+    p1.namespace = webdav_dav_namespace();
+    p1.lang = NULL;
+    p1.name = "test1";
+    p1.value.data = (WSXmlData){ NULL, NULL, 0};
+    p1.vtype = 0;
+    
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[1].vtype = 0;
+    }
+    
+    UCX_TEST_ASSERT(!r->plist_begin && !r->plist_end, "plist not empty");
+    
+    r->resource.addproperty((WebdavResource*)r, &p1, 200);
+    UCX_TEST_ASSERT(r->plist_begin, "!plist_begin");
+    UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end");
+    
+    r->resource.addproperty((WebdavResource*)r, &p[0], 404);
+    r->resource.addproperty((WebdavResource*)r, &p[1], 404);
+    r->resource.addproperty((WebdavResource*)r, &p[2], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[3], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[4], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[5], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[6], 500);
+    
+    UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end");
+    
+    UCX_TEST_ASSERT(r->errors, "no prop errors");
+    UCX_TEST_ASSERT(r->errors->next, "no second error code");
+    UCX_TEST_ASSERT(r->errors->next->next, "no third error code");
+    UCX_TEST_ASSERT(!r->errors->next->next->next, "too many error codes");
+    
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 2, "404 list size != 2");
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->begin) == 4, "403 list size != 4");
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->next->begin) == 1, "500 list size != 1");
+    
+    // new resource for prop duplication tests
+    r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/test");
+    UCX_TEST_ASSERT(r, "cannot create second response");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp1, 200);
+    UCX_TEST_ASSERT(r->plist_begin, "adding dp1 failed");
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: list size not 1");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp2, 200);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 should not work");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp2, 404);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 with different status should not work (1)");
+    if(r->errors) {
+        UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 0, "dp1: error list not empty");
+    }
+    
+    r->resource.addproperty((WebdavResource*)r, &dp3, 200);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp3 should not work");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_propfind_init) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    UcxList *requests = NULL;
+    int err = webdav_propfind_init(&backend1, propfind, "/", "/", &requests);
+    
+    UCX_TEST_ASSERT(!err, "webdav_propfind_init failed");
+    UCX_TEST_ASSERT(requests, "request list is empty");
+    UCX_TEST_ASSERT(ucx_list_size(requests), "request list has wrong size");
+    
+    WebdavPropfindRequest *p1 = requests->data;
+    WebdavPropfindRequest *p2 = requests->next->data;
+    
+    // backend1 removes the first property from the plist
+    // backend2 should have one property less 
+    
+    UCX_TEST_ASSERT(p1 && p2, "missing requests objects");
+    UCX_TEST_ASSERT(p1 != p2, "request objects equal");
+    UCX_TEST_ASSERT(p1->properties != p2->properties, "plists equal");
+    UCX_TEST_ASSERT(p1->propcount == p2->propcount + 1, "first property not removed");
+    
+    UCX_TEST_ASSERT(backend1_init_called == 1, "backend1 init not called");
+    UCX_TEST_ASSERT(backend2_init_called == 1, "backend2 init not called");
+    
+    UCX_TEST_END;
+    
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_webdav_op_propfind_begin) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "WebdavOperation not created");
+    
+    int err = webdav_op_propfind_begin(op, "/", NULL, NULL);
+    UCX_TEST_ASSERT(err == 0, "err not 0");
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend2 propfind_do not called");
+    
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_op_propfind_children) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "WebdavOperation not created");
+    
+    int err = webdav_op_propfind_begin(op, "/", NULL, NULL);
+    UCX_TEST_ASSERT(err == 0, "propfind_begin error");
+    
+    // create test vfs with some files (code from test_vfs_readdir)
+    rq->vfs = testvfs_create(sn);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    UCX_TEST_ASSERT(vfs, "no vfs");
+    
+    err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    // add some test file to /dir
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed");
+    
+    VFSDir *dir = vfs_opendir(vfs, "/dir");
+    UCX_TEST_ASSERT(dir, "dir not opened");
+    
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend1 propfind_do not called")
+   
+    // propfind for all children
+    err = webdav_op_propfind_children(op, vfs, "/", "/dir");
+    UCX_TEST_ASSERT(err == 0, "webdav_op_propfind_children failed");
+    
+    // 1 dir + 4 children
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 5, "backend1 propfind_do wrong count");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 5, "backend2 propfind_do wrong count");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+void init_test_webdav_method(
+        Session **out_sn,
+        Request **out_rq,
+        TestIOStream **out_st,
+        pblock **out_pb,
+        const char *method,
+        const char *path,
+        const char *request_body)
+{
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, method, "/");
+    
+    pblock_nvinsert("path", path, rq->vars);
+    pblock_nvinsert("uri", path, rq->reqpb);
+    
+    st = testutil_iostream(2048, TRUE);
+    sn->csd = (IOStream*)st;
+    
+    if(request_body) {
+        testutil_request_body(sn, rq, request_body, strlen(request_body));
+    }
+    
+    pb = pblock_create_pool(sn->pool, 4);
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_st = st;
+    *out_pb = pb;
+}
+
+UCX_TEST(test_webdav_propfind) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND1);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    xmlDoc *doc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "propfind1: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc);
+    testutil_iostream_destroy(st);
+    
+    // Test2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND2);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    xmlDoc *doc2 = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "propfind2: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc2);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+    
+}
+
+/* -------------------------------------------------------------------------
+ * 
+ *                           PROPPATCH TESTS
+ * 
+ * ------------------------------------------------------------------------ */
+
+static int test_proppatch_init(
+        Session **out_sn,
+        Request **out_rq,
+        WebdavProppatchRequest **out_proppatch,
+        const char *xml)
+{
+    if(!webdav_is_initialized) {
+        if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) {
+            return 1;
+        }
+        webdav_is_initialized = 1;
+    }
+    
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPPATCH", "/");
+    
+    int error = 0;
+    
+    WebdavProppatchRequest *proppatch = proppatch_parse(
+            sn,
+            rq,
+            xml,
+            strlen(xml),
+            &error);
+    
+    if(error) {
+        return 1;
+    }
+    
+    if(!proppatch || !(proppatch->set || proppatch->remove)) {
+        return 1;
+    }
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_proppatch = proppatch;
+    return 0;
+}
+
+static WebdavOperation* test_proppatch_op1(
+        Session **out_sn,
+        Request **out_rq,
+        const char *xml)
+{
+    WebdavProppatchRequest *proppatch;
+    if(test_proppatch_init(out_sn, out_rq, &proppatch, xml)) {
+        return NULL;
+    }
+    
+    Multistatus *ms = multistatus_response(*out_sn, *out_rq);
+    if(!ms) {
+        return NULL;
+    }
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    return webdav_create_proppatch_operation(
+            (*out_sn),
+            (*out_rq),
+            &backend1,
+            proppatch,
+            response);
+}
+
+
+UCX_TEST(test_proppatch_msresponse) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[i].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    ms = (Multistatus*)op->response;
+    ms->proppatch = TRUE;
+    res = ms->response.addresource(&ms->response, "/");
+    UCX_TEST_ASSERT(res, "cannot create resource 1");
+    
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 200), "addproperty 3 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed");
+    
+    UCX_TEST_ASSERT(!res->close(res), "close failed");
+
+    MSResponse *msres = (MSResponse*)res;
+    UCX_TEST_ASSERT(!msres->errors, "error list not NULL");
+    UCX_TEST_ASSERT(msres->plist_begin, "elm1 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next, "elm2 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next->next, "elm3 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next->next->next, "elm4 missing");
+    UCX_TEST_ASSERT(!msres->plist_begin->next->next->next->next, "count != 4");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_msresponse_addproperty_with_errors) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[i].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    ms = (Multistatus*)op->response;
+    ms->proppatch = TRUE;
+    res = ms->response.addresource(&ms->response, "/");
+    UCX_TEST_ASSERT(res, "cannot create resource 1");
+    
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 409), "addproperty 3 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed");
+    
+    UCX_TEST_ASSERT(!res->close(res), "close failed");
+    
+    // all properties should have an error status code now
+    // 1 x 409, 3 x 424
+
+    MSResponse *msres = (MSResponse*)res;
+    
+    UCX_TEST_ASSERT(!msres->plist_begin, "plist not NULL");
+    UCX_TEST_ASSERT(msres->errors, "error list is NULL");
+    UCX_TEST_ASSERT(msres->errors->next, "second error list is missing");
+    UCX_TEST_ASSERT(!msres->errors->next->next, "wrong error list size");
+    
+    // We know that we have 2 error lists, one with status code 409 and
+    // the other must have 409. However we don't enforce the order of the
+    // error lists, therefore check both variants
+    if(msres->errors->status == 409) {
+        UCX_TEST_ASSERT(msres->errors->next->status == 424, "wrong status code in second err elm");
+        UCX_TEST_ASSERT(msres->errors->begin, "missing 409 property");
+        UCX_TEST_ASSERT(msres->errors->next->begin, "missing 424 properties");
+    } else {
+        UCX_TEST_ASSERT(msres->errors->next->status == 409, "wrong status code in second err elm");
+        UCX_TEST_ASSERT(msres->errors->begin, "missing 424 properties");
+        UCX_TEST_ASSERT(msres->errors->next->begin, "missing 409 property");
+    } 
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_op_proppatch) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[1].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    // TEST_PROPPATCH2 should succeed
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    int ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed");
+    UCX_TEST_ASSERT(backend1_proppatch_commit, "backend1 no commit");
+    UCX_TEST_ASSERT(backend2_proppatch_commit, "backend2 no commit");
+    UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (1)");
+    UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (1)");
+    UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (1)");
+    UCX_TEST_ASSERT(backend2_proppatch_finish_count == 1, "backend1 wrong finish count (1)");
+    
+    // TEST_PROPPATCH3 should fail (commit == FALSE)
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH3);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation 2");
+    
+    ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed (2)");
+    UCX_TEST_ASSERT(!backend1_proppatch_commit, "backend1 commit");
+    UCX_TEST_ASSERT(!backend2_proppatch_commit, "backend2 commit");
+    
+    // TEST_PROPPATCH4 should abort
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH4);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation 3");
+    
+    ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret != 0, "webdav_op_proppatch should fail");
+    UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (2)");
+    UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (2)");
+    UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (2)");
+    UCX_TEST_ASSERT(backend2_proppatch_finish_count == 0, "backend1 wrong finish count (2)");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+#define xstreq(a, b) (!strcmp((const char*)a, (const char*)b))
+
+UCX_TEST(test_webdav_proppatch) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/", TEST_PROPPATCH2);
+    rq->davCollection = &backend1;
+    ret = webdav_proppatch(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_proppatch (1) failed");
+    
+    xmlDoc *doc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "proppatch1: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    UCX_TEST_ASSERT(root, "proppatch1: no root");
+    
+    xmlNode *nodeC = NULL;
+    xmlNode *node = root->children;
+    int depth = 1;
+    while(node) {
+        const xmlChar *name = node->name;
+        int nextNode = 1;
+        if(node->type != XML_ELEMENT_NODE) {
+            // nothing
+        } else if(depth == 1) {
+            if(xstreq(name, "response")) {
+                nextNode = 0;
+            }
+        } else if(depth == 2) {
+            if(xstreq(name, "propstat")) {
+                nextNode = 0;
+            }
+        } else if(depth == 3) {
+            if(xstreq(name, "prop")) {
+                nextNode = 0;
+            }
+        } else if(depth == 4) {
+            if(xstreq(name, "c")) {
+                nodeC = node;
+                break;
+            }
+        }
+        
+        if(nextNode) {
+            node = node->next;
+        } else {
+            node = node->children;
+            depth++;
+        }
+    }
+    
+    UCX_TEST_ASSERT(nodeC, "prop c not in response");
+    UCX_TEST_ASSERT(!nodeC->children, "properties must not have a value");
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}
+
+
+/* -------------------------------------------------------------------------
+ * 
+ *                          WEBDAV VFS TESTS
+ * 
+ * ------------------------------------------------------------------------ */
+
+static int mkcol_data1 = 10;
+static int mkcol_data2 = 20;
+static int mkcol_data3 = 30;
+static int mkcol_data4 = 40;
+
+static int mkcol_count = 0;
+static int mkcol_finish_count = 0;
+
+static int mkcol_err = 0;
+
+static int set_created = 0;
+
+static int test_webdav_mkcol(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    
+    switch(mkcol_count) {
+        case 1: {
+            req->userdata = &mkcol_data1;
+            break;
+        }
+        case 2: {
+            req->userdata = &mkcol_data2;
+            break;
+        }
+        case 3: {
+            req->userdata = &mkcol_data3;
+            break;
+        }
+        case 4: {
+            req->userdata = &mkcol_data4;
+            break;
+        }
+        default: break;
+    }
+    
+    if(set_created) {
+        *created = TRUE;
+        set_created = 0;
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_finish(WebdavVFSRequest *req, WSBool success) {
+    mkcol_finish_count++;
+    
+    if(mkcol_finish_count == 1) {
+        int *data = req->userdata;
+        if(data != &mkcol_data1) {
+            mkcol_err = 1;
+        }
+    } else if(mkcol_finish_count == 3) {
+        int *data = req->userdata;
+        if(data != &mkcol_data3) {
+            mkcol_err = 1;
+        }
+    } else {
+        int *data = req->userdata;
+        // data4 should never be used
+        if(data == &mkcol_data4) {
+            mkcol_err = 1;
+        }
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_fail(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    return 1;
+}
+
+static int delete_count = 0;
+static int delete_finish_count = 0;
+
+static int test_backend_webdav_delete(WebdavVFSRequest *req, WSBool *created) {
+    delete_count++;    
+    return 0;
+}
+
+static int test_backend_webdav_delete_finish(WebdavVFSRequest *req, WSBool success) {
+    delete_finish_count++;
+    return 0;
+}
+
+
+UCX_TEST(test_webdav_vfs_op_do) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    // Tests performed primarily with MKCOL, because webdav_vfs_op_do
+    // behaves the same for both operations
+    // the only difference are the callbacks
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "MKCOL", "/", NULL);
+    VFS *testvfs = testvfs_create(sn);
+    rq->vfs = testvfs;
+    
+    WebdavBackend dav1;
+    ZERO(&dav1, sizeof(WebdavBackend));
+    dav1.opt_mkcol = test_webdav_mkcol;
+    dav1.opt_mkcol_finish = test_webdav_mkcol_finish;
+    dav1.opt_delete = test_backend_webdav_delete;
+    dav1.opt_delete_finish = test_backend_webdav_delete_finish;
+    
+    WebdavBackend dav2;
+    ZERO(&dav2, sizeof(WebdavBackend));
+    dav2.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    WebdavBackend dav3;
+    ZERO(&dav3, sizeof(WebdavBackend));
+    dav3.opt_mkcol = test_webdav_mkcol;
+    
+    WebdavBackend dav4;
+    ZERO(&dav4, sizeof(WebdavBackend));
+    dav4.opt_mkcol = test_webdav_mkcol;
+    dav4.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    dav1.next = &dav2;
+    dav2.next = &dav3;
+    dav3.next = &dav4;
+    
+    rq->davCollection = &dav1;
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavVFSOperation *op1 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    
+    int ret = webdav_vfs_op_do(op1, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "webdav_vfs_op_do failed");
+    UCX_TEST_ASSERT(mkcol_count == 3, "wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 3, "wrong mkcol_finish_count");
+    UCX_TEST_ASSERT(mkcol_err == 0, "mkcol_err");
+    
+    // test without VFS, but set *created to TRUE to skip VFS usage
+    rq->vfs = NULL;
+    set_created = 1;
+    
+    WebdavVFSOperation *op2 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op2, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "op2 failed");
+    
+    // test 3: abort after first backend
+    mkcol_count = 0;
+    mkcol_finish_count = 0;
+    dav1.opt_mkcol = test_webdav_mkcol_fail;
+    
+    WebdavVFSOperation *op3 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op3, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(ret, "op3 should fail");
+    UCX_TEST_ASSERT(mkcol_count == 1, "op3: wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 1, "op3: wrong mkcol_finish_count");
+    
+    // test DELETE to make sure, delete callbacks will be used
+    pblock_replace("path", "/deltest", rq->vars);
+    rq->vfs = testvfs;
+    WebdavVFSOperation *op_del = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    vfs_open(op_del->vfs, "/deltest", O_CREAT);
+    ret = webdav_vfs_op_do(op_del, WEBDAV_VFS_DELETE);
+    
+    UCX_TEST_ASSERT(!ret, "op_del failed");
+    UCX_TEST_ASSERT(delete_count == 1, "op_del: wrong delete_count");
+    UCX_TEST_ASSERT(delete_finish_count == 1, "op_del: wrong delete_finish_count");
+    
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_delete){
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "DELETE", "/", NULL);
+    rq->vfs = testvfs_create(sn);
+    
+    WebdavBackend dav1;
+    ZERO(&dav1, sizeof(WebdavBackend));
+    dav1.opt_delete = test_backend_webdav_delete;
+    dav1.opt_delete_finish = test_backend_webdav_delete_finish;
+    delete_count = 0;
+    delete_finish_count = 0;
+    rq->davCollection = &dav1;
+    
+    UCX_TEST_BEGIN;
+    
+    // prepare
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir1 failed");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir2 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir3");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir3 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir4");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir4 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir4/dir5");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir5 failed");
+    
+    SYS_FILE f0 = vfs_open(vfs, "/file0", O_CREAT);
+    UCX_TEST_ASSERT(f0, "f0 create failed");
+    // no f1
+    SYS_FILE f2 = vfs_open(vfs, "/dir2/file2", O_CREAT);
+    UCX_TEST_ASSERT(f2, "f2 create failed");
+    SYS_FILE f3 = vfs_open(vfs, "/dir2/dir3/file3", O_CREAT);
+    UCX_TEST_ASSERT(f3, "f3 create failed");
+    SYS_FILE f4 = vfs_open(vfs, "/dir2/dir4/file4", O_CREAT);
+    UCX_TEST_ASSERT(f4, "f4 create failed");
+    SYS_FILE f5 = vfs_open(vfs, "/dir2/dir4/dir5/file5", O_CREAT);
+    UCX_TEST_ASSERT(f5, "f5 create failed");
+    
+    // delete single file
+    pblock_replace("path", "/file0", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /file0 failed");
+    UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count");
+    
+    delete_count = 0;
+    pblock_replace("path", "/dir1", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /dir1 failed");
+    UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count");
+    
+    delete_count = 0;
+    pblock_replace("path", "/dir2", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /dir2 failed");
+    UCX_TEST_ASSERT(delete_count == 8, "del2: wrong delete count");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_put) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    const char *content_const = "Hello World";
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PUT", "/", content_const);
+    rq->vfs = testvfs_create(sn);
+    
+    UCX_TEST_BEGIN;
+    
+    int err;
+    
+    pblock_replace("path", "/file0", rq->vars);
+    err = webdav_put(NULL, sn, rq);
+    
+    UCX_TEST_ASSERT(err == REQ_PROCEED, "put failed");
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE f0 = vfs_open(vfs, "/file0", 0);
+    UCX_TEST_ASSERT(f0, "cannot open file0");
+    
+    char buf[1024];
+    int r = system_fread(f0, buf, 1024);
+    
+    UCX_TEST_ASSERT(r == strlen(content_const), "wrong file size");
+    UCX_TEST_ASSERT(!memcmp(content_const, buf, r), "wrong file content");
+    
+    testutil_destroy_session(sn);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+}

mercurial