src/server/test/webdav.c

Fri, 13 Sep 2024 23:19:04 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 13 Sep 2024 23:19:04 +0200
changeset 557
e35829a3a6d8
parent 415
d938228c382e
permissions
-rw-r--r--

add sessionhandler debug logging

/*
 * 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;
    
    WebdavPropfindRequestList *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);
    
    CxBuffer b1;
    rqbody2buffer(sn, rq, &b1);
    UCX_TEST_ASSERT(b1.size == 12, "b1: wrong size");
    UCX_TEST_ASSERT(!memcmp(b1.space,"Hello World!",12), "b1: wrong content");
    
    cxBufferDestroy(&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);
    
    CxBuffer b2;
    rqbody2buffer(sn, rq, &b2);
    UCX_TEST_ASSERT(b2.size == len1, "b2: wrong size");
    UCX_TEST_ASSERT(!memcmp(b2.space, body1, len1), "b2: wrong content");
    
    cxBufferDestroy(&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");
    
    WebdavPropfindRequestList *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(cx_linked_list_size(requests, offsetof(WebdavPropfindRequestList, next)), "request list has wrong size");
    
    WebdavPropfindRequest *p1 = requests->propfind;
    WebdavPropfindRequest *p2 = requests->next->propfind;
    
    // 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