merge branch config into webdav webdav

Tue, 25 Aug 2020 12:07:56 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 25 Aug 2020 12:07:56 +0200
branch
webdav
changeset 259
0b8692959d37
parent 252
5653a9626cc0 (diff)
parent 258
134279e804b6 (current diff)
child 260
4779a6fb4fbe

merge branch config into webdav

src/server/config/serverconf.c file | annotate | diff | comparison | revisions
src/server/config/serverconf.h file | annotate | diff | comparison | revisions
src/server/test/main.c file | annotate | diff | comparison | revisions
src/ucx/allocator.h file | annotate | diff | comparison | revisions
src/ucx/avl.h file | annotate | diff | comparison | revisions
src/ucx/buffer.h file | annotate | diff | comparison | revisions
src/ucx/list.h file | annotate | diff | comparison | revisions
src/ucx/logging.h file | annotate | diff | comparison | revisions
src/ucx/map.h file | annotate | diff | comparison | revisions
src/ucx/mempool.h file | annotate | diff | comparison | revisions
src/ucx/properties.h file | annotate | diff | comparison | revisions
src/ucx/stack.h file | annotate | diff | comparison | revisions
src/ucx/string.h file | annotate | diff | comparison | revisions
src/ucx/test.h file | annotate | diff | comparison | revisions
src/ucx/ucx.h file | annotate | diff | comparison | revisions
src/ucx/utils.h file | annotate | diff | comparison | revisions
--- a/configure	Mon Aug 24 19:19:56 2020 +0200
+++ b/configure	Tue Aug 25 12:07:56 2020 +0200
@@ -57,7 +57,7 @@
   --mandir=DIR            man documentation [DATAROOTDIR/man]
 
 Optional Features:
-  --enable-pg
+  --enable-postgresql
 
 __EOF__
 }
@@ -96,10 +96,10 @@
     elif [ $ARG = "--help" ]; then
 		printhelp
         exit 0	
-    elif [[ $ARG == --enable-pg ]]; then
-    	FEATURE_PG=on
-    elif [[ $ARG == --disable-pg ]]; then
-    	unset FEATURE_PG
+    elif [[ $ARG == --enable-postgresql ]]; then
+    	FEATURE_POSTGRESQL=on
+    elif [[ $ARG == --disable-postgresql ]]; then
+    	unset FEATURE_POSTGRESQL
     fi
 done
 
@@ -265,6 +265,7 @@
         fi
         CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libpq`"
         LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libpq`"
+        CFLAGS="$CFLAGS -DENABLE_POSTGRESQL"    
 		echo yes
         return 0
     done
@@ -469,6 +470,19 @@
 	ERROR=1
 fi
 
+# Features
+if [ ! -z "$FEATURE_POSTGRESQL" ]; then
+	# check dependency
+	dependency_libpq
+	if [ $? -ne 0 ]; then
+		# "auto" features can fail and are just disabled in this case
+		if [ $FEATURE_POSTGRESQL != "auto" ]; then
+			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libpq "
+			ERROR=1
+		fi
+	fi
+fi
+
 
 echo >> $TEMP_DIR/config.mk
 if [ ! -z "${CFLAGS}" ]; then
--- a/make/configure.vm	Mon Aug 24 19:19:56 2020 +0200
+++ b/make/configure.vm	Tue Aug 25 12:07:56 2020 +0200
@@ -534,6 +534,23 @@
 fi
 #end
 
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+	# check dependency
+	dependency_$dependency
+	if [ $? -ne 0 ]; then
+		# "auto" features can fail and are just disabled in this case
+		if [ $${feature.getVarName()} != "auto" ]; then
+			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+			ERROR=1
+		fi
+	fi
+#end
+fi
+#end
+
 #foreach( $opt in $target.options )
 # Option: --${opt.argument}
 if [ -z ${D}${opt.getVarName()} ]; then
--- a/make/project.xml	Mon Aug 24 19:19:56 2020 +0200
+++ b/make/project.xml	Tue Aug 25 12:07:56 2020 +0200
@@ -76,10 +76,11 @@
 	<!-- optional dependencies -->
 	<dependency name="libpq">
 		<pkgconfig>libpq</pkgconfig>
+		<cflags>-DENABLE_POSTGRESQL</cflags>
 	</dependency>
 	
 	<target>
-		<feature name="pg" default="false">
+		<feature name="postgresql" default="false">
 			<dependencies>libpq</dependencies>
 		</feature>
 		<dependencies>libxml2,openssl</dependencies>
--- a/make/suncc.mk	Mon Aug 24 19:19:56 2020 +0200
+++ b/make/suncc.mk	Tue Aug 25 12:07:56 2020 +0200
@@ -2,9 +2,9 @@
 # suncc toolchain
 #
 
-CFLAGS += -xc99 -g
-LDFLAGS += -Wl,-R,'$$ORIGIN/../lib'
+CFLAGS += -xc99 -g -m64
+LDFLAGS += -m64 -Wl,-R,'$$ORIGIN/../lib'
 
 SHLIB_CFLAGS = -Kpic
-SHLIB_LDFLAGS = -G
+SHLIB_LDFLAGS = -G -m64
 
--- a/src/server/daemon/acl.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/acl.c	Tue Aug 25 12:07:56 2020 +0200
@@ -100,6 +100,7 @@
 }
 
 User* acllist_getuser(Session *sn, Request *rq, ACLListHandle *list) {
+    // TODO: cache result
     if(!sn || !rq || !list) {
         return NULL;
     }
@@ -316,7 +317,7 @@
         uid_t owner,
         gid_t owninggroup);
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) {
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
     sstr_t p;
     if(path[0] != '/') {
         size_t n = 128;
@@ -331,11 +332,11 @@
             }
         }
         sstr_t wd = sstr(cwd);
-        sstr_t pp = sstr(path);
+        sstr_t pp = sstr((char*)path);
 
         p = sstrcat(3, wd, sstrn("/", 1), pp);
     } else {
-        p = sstrdup(sstr(path));
+        p = sstrdup(sstr((char*)path));
     }
     if(p.ptr[p.length-1] == '/') {
         p.ptr[p.length-1] = 0;
@@ -461,6 +462,11 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    // TODO:
+    return 1;
+}
+
 int solaris_acl_check(
         char *path,
         struct stat *s,
@@ -571,6 +577,10 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    return 1;
+}
+
 void fs_acl_finish() {
 
 }
@@ -583,6 +593,10 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    return 1;
+}
+
 void fs_acl_finish() {
 
 }
@@ -638,6 +652,11 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    // TODO
+    return 1;
+}
+
 void fs_acl_finish() {
     struct passwd *pw = conf_getglobals()->Vuserpw;
     if(!pw) {
--- a/src/server/daemon/acl.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/acl.h	Tue Aug 25 12:07:56 2020 +0200
@@ -48,7 +48,8 @@
 
 // file system acl functions
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask);
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask);
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask);
 void fs_acl_finish();
 
 #ifdef	__cplusplus
--- a/src/server/daemon/httprequest.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/httprequest.c	Tue Aug 25 12:07:56 2020 +0200
@@ -87,6 +87,46 @@
     return S("/");
 }
 
+NSAPISession* nsapisession_create(pool_handle_t *pool) {
+    NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession));
+    if(!sn) {
+        return NULL;
+    }
+    
+    ZERO(sn, sizeof(NSAPISession));
+    
+    sn->sn.pool = pool;
+    sn->allocator = util_pool_allocator(pool);
+    
+    sn->sn.client = pblock_create_pool(sn->sn.pool, 8);
+    if(!sn->sn.client) {
+        pool_free(pool, sn);
+        return NULL;
+    }
+    sn->sn.fill = 1;
+    
+    return sn;
+}
+
+int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io) {
+    SessionHandler *sh = conn->session_handler;
+    WSBool ssl;
+    IOStream *sio = sh->create_iostream(sh, conn, sn->sn.pool, &ssl);
+    if(!sio) {
+        return 1;
+    }
+    *io = sio;
+    IOStream *http = httpstream_new(sn->sn.pool, sio);
+    if(!http) {
+        return 1;
+    }
+    sn->connection = conn;
+    sn->netbuf = inbuf;
+    sn->sn.csd = http;
+    sn->sn.ssl = ssl;
+    return 0;
+}
+
 int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) {
     // handle nsapi request
      
@@ -94,11 +134,11 @@
     pool_handle_t *pool = pool_create();
 
     // create nsapi data structures
-    NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession));
+    NSAPISession *sn = nsapisession_create(pool);
     if(sn == NULL) {
         /* TODO: error */
     }
-    ZERO(sn, sizeof(NSAPISession));
+    
     NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest));
     if(rq == NULL) {
         /* TODO: error */
@@ -108,25 +148,16 @@
     rq->phase = NSAPIAuthTrans;
 
     // fill session structure
-    sn->connection = request->connection;
-    sn->netbuf = request->netbuf;
-    sn->sn.pool = pool;
-    SessionHandler *sh = request->connection->session_handler;
-    WSBool ssl;
-    IOStream *io = sh->create_iostream(sh, request->connection, pool, &ssl);
-    sn->sn.csd = httpstream_new(pool, io);
-    sn->sn.ssl = ssl;
-    
-    sn->sn.client = pblock_create_pool(sn->sn.pool, 8);
-    sn->sn.next = NULL;
-    sn->sn.fill = 1;
-    sn->sn.subject = NULL;
+    IOStream *io = NULL;
+    if(nsapisession_setconnection(sn, request->connection, request->netbuf, &io)) {
+        // TODO: error
+    }
     
     if(!ev) {
         ev = ev_instance(get_default_event_handler());
     }
     sn->sn.ev = ev;
-    
+      
     // the session needs the current server configuration
     sn->config = request->connection->listener->cfg;
 
@@ -342,7 +373,7 @@
     // check for request body and prepare input buffer
     char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers);
     if(ctlen_str) {
-        int ctlen = atoi(ctlen_str);
+        int ctlen = atoi(ctlen_str); // TODO: use other func
               
         //printf("request body length: %d\n", ctlen);
 
@@ -914,7 +945,9 @@
     
     if(ret != REQ_PROCEED) {
         // default error handler
-        nsapi_error_request((Session*)sn, (Request*)rq);
+        if(!rq->rq.senthdrs) {
+            nsapi_error_request((Session*)sn, (Request*)rq);
+        }
     }
 
     return ret;
--- a/src/server/daemon/httprequest.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/httprequest.h	Tue Aug 25 12:07:56 2020 +0200
@@ -73,6 +73,10 @@
 
 sstr_t http_request_get_abspath(HTTPRequest *req);
 
+
+NSAPISession* nsapisession_create(pool_handle_t *pool);
+int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io);
+
 /*
  * starts request processing after reading the request header
  * 
--- a/src/server/daemon/protocol.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/protocol.c	Tue Aug 25 12:07:56 2020 +0200
@@ -347,7 +347,7 @@
         }
         
         // set stream property
-        HttpStream *stream = (HttpStream*)sn->csd;
+        HttpStream *stream = (HttpStream*)sn->csd; // TODO: make this typesafe
         stream->chunked_enc = 1;
         rq->rq_attr.chunked = 1;
     }
@@ -377,6 +377,16 @@
     return 0;
 }
 
+int http_send_continue(Session *sn) {
+    NSAPISession *s = (NSAPISession*)sn;
+    sstr_t msg = S("HTTP/1.1 100 Continue\r\n\r\n");
+    int w = s->connection->write(s->connection, msg.ptr, msg.length);
+    if(w != msg.length) {
+        return 1;
+    }
+    return 0;
+}
+
 int request_header(char *name, char **value, Session *sn, Request *rq) {
     const pb_key *key = pblock_key(name);
     pb_param *pp = pblock_findkey(key, rq->headers);
--- a/src/server/daemon/protocol.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/protocol.h	Tue Aug 25 12:07:56 2020 +0200
@@ -46,6 +46,8 @@
 
 int http_start_response(Session *sn, Request *rq);
 
+int http_send_continue(Session *sn);
+
 int request_header(char *name, char **value, Session *sn, Request *rq);
 
 void http_get_scheme_host_port(
--- a/src/server/daemon/session.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/session.c	Tue Aug 25 12:07:56 2020 +0200
@@ -40,3 +40,8 @@
     NSAPISession *sn = (NSAPISession*)s;
     return sn->config;
 }
+
+NSAPI_PUBLIC void* session_get_allocator(Session *sn) {
+    return &((NSAPISession*)sn)->allocator;
+}
+
--- a/src/server/daemon/session.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/session.h	Tue Aug 25 12:07:56 2020 +0200
@@ -47,6 +47,8 @@
     threadpool_t *currentpool;
     threadpool_t *defaultpool;
     
+    UcxAllocator allocator;
+    
     ServerConfiguration *config;
 };
 
@@ -57,6 +59,8 @@
 // get the server configuration of this session
 NSAPI_PUBLIC void* session_get_config(Session *s);
 
+NSAPI_PUBLIC void* session_get_allocator(Session *sn);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/src/server/daemon/vfs.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/vfs.c	Tue Aug 25 12:07:56 2020 +0200
@@ -52,9 +52,12 @@
     sys_vfs_stat,
     sys_vfs_fstat,
     sys_vfs_opendir,
+    sys_vfs_fdopendir,
     sys_vfs_mkdir,
     sys_vfs_unlink,
-    VFS_CHECKS_ACL
+    sys_vfs_rmdir,
+    VFS_CHECKS_ACL,
+    NULL
 };
 
 static VFS_IO sys_file_io = {
@@ -97,6 +100,9 @@
     WS_ASSERT(rq);
     
     VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext));
+    if(!ctx) {
+        return NULL;
+    }
     ctx->sn = sn;
     ctx->rq = rq;
     ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs;
@@ -108,7 +114,7 @@
     return ctx;
 }
 
-SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) {
+SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -129,19 +135,19 @@
     return file;
 }
 
-SYS_FILE vfs_openRO(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_RDONLY);
 }
 
-SYS_FILE vfs_openWO(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_WRONLY | O_CREAT);
 }
 
-SYS_FILE vfs_openRW(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT);
 }
 
-int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) {
+int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -181,7 +187,7 @@
     }
 }
 
-VFS_DIR vfs_opendir(VFSContext *ctx, char *path) {
+VFS_DIR vfs_opendir(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -202,6 +208,27 @@
     return dir;
 }
 
+VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    WS_ASSERT(ctx);
+    WS_ASSERT(path);
+    
+    uint32_t access_mask = ctx->aclreqaccess | ACL_LIST;
+    
+    // ctx->aclreqaccess should be the complete access mask
+    uint32_t m = ctx->aclreqaccess; // save original access mask
+    ctx->aclreqaccess = access_mask; // set mask for vfs->open call
+    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
+        // VFS does not evaluates the ACL itself, so we have to do it here
+        SysACL sysacl;
+        if(sys_acl_check(ctx, access_mask, &sysacl)) {
+            return NULL;
+        }
+    }
+    VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd);
+    ctx->aclreqaccess = m; // restore original access mask
+    return dir;
+}
+
 int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) {
     WS_ASSERT(dir);
     WS_ASSERT(entry);
@@ -227,23 +254,29 @@
     }
 }
 
-int vfs_mkdir(VFSContext *ctx, char *path) {
+int vfs_mkdir(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
     return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE);
 }
 
-int vfs_unlink(VFSContext *ctx, char *path) {
+int vfs_unlink(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
     return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE);
 }
 
+int vfs_rmdir(VFSContext *ctx, const char *path) {
+    WS_ASSERT(ctx);
+    WS_ASSERT(path);
+    
+    return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE);
+}
 
 // private
-int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) {  
+int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) {  
     uint32_t access_mask = ctx->aclreqaccess;
     access_mask |= access;
     
@@ -264,7 +297,7 @@
 
 /* system vfs implementation */
 
-SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags) {
+SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) {
     uint32_t access_mask = ctx->aclreqaccess;
     pool_handle_t *pool = ctx->pool;
     
@@ -313,7 +346,7 @@
     return file;
 }
 
-int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf) {
+int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
     uint32_t access_mask = ctx->aclreqaccess;
     
     // check ACLs
@@ -353,7 +386,7 @@
     return 0;
 }
 
-VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path) {
+VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path) {
     uint32_t access_mask = ctx->aclreqaccess;
     pool_handle_t *pool = ctx->pool;
     
@@ -422,16 +455,76 @@
     return dir;
 }
 
-int sys_vfs_mkdir(VFSContext *ctx, char *path) {
+VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    uint32_t access_mask = ctx->aclreqaccess;
+    pool_handle_t *pool = ctx->pool;
+    
+    // check ACLs
+    SysACL sysacl;
+    if(sys_acl_check(ctx, access_mask, &sysacl)) {
+        return NULL;
+    }
+    
+    if(sysacl.acl) {
+        if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) {
+            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
+            return NULL;
+        }
+    }
+    
+    // open directory
+    DIR *sys_dir = fdopendir(fd->fd);
+    if(!sys_dir) {
+        if(ctx) {
+            ctx->vfs_errno = errno;
+            sys_set_error_status(ctx);
+        }
+        return NULL;
+    }
+    
+    SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir));
+    if(!dir_data) {
+        closedir(sys_dir);
+        return NULL;
+    }
+    long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX);
+    size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1;
+    dir_data->cur = VFS_MALLOC(pool, entry_len);
+    if(!dir_data->cur) {
+        closedir(sys_dir);
+        VFS_FREE(pool, dir_data);
+        return NULL;
+    }
+    dir_data->dir = sys_dir;
+    
+    VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir));
+    if(!dir) {
+        closedir(sys_dir);
+        VFS_FREE(pool, dir_data->cur);
+        VFS_FREE(pool, dir_data);
+        return NULL;
+    }
+    dir->ctx = ctx;
+    dir->data = dir_data;
+    dir->fd = fd->fd;
+    dir->io = &sys_dir_io;
+    return dir;
+}
+
+int sys_vfs_mkdir(VFSContext *ctx, const char *path) {
     return sys_path_op(ctx, path, sys_mkdir);
 }
 
-int sys_vfs_unlink(VFSContext *ctx, char *path) {
+int sys_vfs_unlink(VFSContext *ctx, const char *path) {
     return sys_path_op(ctx, path, sys_unlink);
 }
 
+int sys_vfs_rmdir(VFSContext *ctx, const char *path) {
+    return sys_path_op(ctx, path, sys_rmdir);
+}
 
-int sys_path_op(VFSContext *ctx, char *path, sys_op_f op) {
+
+int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) {
     uint32_t access_mask = ctx->aclreqaccess;
     
     // check ACLs
@@ -539,6 +632,7 @@
             entry->name = name;
             if(getstat) {
                 // TODO: check ACLs again for new path
+                entry->stat_errno = 0;
                 if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) {
                     entry->stat_errno = errno;
                 }
@@ -567,7 +661,7 @@
     }
 }
 
-int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) {
+int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
     mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
     int ret = mkdir(path, mode);
     if(ret == 0) {
@@ -580,10 +674,14 @@
     return ret;
 }
 
-int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) {
+int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) {
     return unlink(path);
 }
 
+int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
+    return rmdir(path);
+}
+
 /* public file api */
 
 NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) {
--- a/src/server/daemon/vfs.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/vfs.h	Tue Aug 25 12:07:56 2020 +0200
@@ -49,18 +49,20 @@
     
 int vfs_init();
 
-typedef int(*vfs_op_f)(VFSContext *, char *);
-typedef int(*sys_op_f)(VFSContext *, char *, SysACL *);
-int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access);
+typedef int(*vfs_op_f)(VFSContext *, const char *);
+typedef int(*sys_op_f)(VFSContext *, const char *, SysACL *);
+int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access);
 
-SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags);
-int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf);
+SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags);
+int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf);
 int sys_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
-VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path);
-int sys_vfs_mkdir(VFSContext *ctx, char *path);
-int sys_vfs_unlink(VFSContext *ctx, char *path);
+VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path);
+VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
+int sys_vfs_mkdir(VFSContext *ctx, const char *path);
+int sys_vfs_unlink(VFSContext *ctx, const char *path);
+int sys_vfs_rmdir(VFSContext *ctx, const char *path);
 
-int sys_path_op(VFSContext *ctx, char *path, sys_op_f op);
+int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op);
 int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *externacl);
 void sys_set_error_status(VFSContext *ctx);
 
@@ -76,8 +78,9 @@
 int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat);
 void sys_dir_close(VFS_DIR dir);
 
-int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl);
-int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl);
+int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl);
+int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl);
+int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl);
 
 void vfs_queue_aio(aiocb_s *aiocb, VFSAioOp op);
 
--- a/src/server/daemon/ws-fn.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/daemon/ws-fn.c	Tue Aug 25 12:07:56 2020 +0200
@@ -70,5 +70,7 @@
     { "set-variable", set_variable, NULL, NULL, 0},
     { "common-log", common_log, NULL, NULL, 0},
     { "send-cgi", send_cgi, NULL, NULL, 0},
+    { "webdav-init", webdav_init, NULL, NULL, 0},
+    { "webdav-service", webdav_service, NULL, NULL, 0},
     {NULL, NULL, NULL, NULL, 0}
 };
--- a/src/server/public/acl.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/public/acl.h	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
--- a/src/server/public/auth.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/public/auth.h	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
--- a/src/server/public/nsapi.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/public/nsapi.h	Tue Aug 25 12:07:56 2020 +0200
@@ -110,6 +110,9 @@
 
 /* --- Begin miscellaneous definitions --- */
 
+#define WS_TRUE 1
+#define WS_FALSE 0
+
 /* Used in some places as a length limit on error messages */
 #define MAGNUS_ERROR_LEN 1024
 
@@ -404,6 +407,7 @@
 //          they are VFSFile*
 // TODO: fix NOTE
 
+typedef int WSBool;
 
 #ifndef SYS_FILE_T
 typedef struct VFSFile *SYS_FILE;
--- a/src/server/public/vfs.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/public/vfs.h	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
@@ -48,13 +48,16 @@
 #define VFS_ENTRY         VFSEntry
 
 struct VFS {
-    SYS_FILE (*open)(VFSContext *ctx, char *path, int oflags);
-    int (*stat)(VFSContext *ctx, char *path, struct stat *buf);
+    SYS_FILE (*open)(VFSContext *ctx, const char *path, int oflags);
+    int (*stat)(VFSContext *ctx, const char *path, struct stat *buf);
     int (*fstat)(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
-    VFS_DIR (*opendir)(VFSContext *ctx, char *path);
-    int (*mkdir)(VFSContext *ctx, char *path);
-    int (*unlink)(VFSContext *ctx, char *path);
+    VFS_DIR (*opendir)(VFSContext *ctx, const char *path);
+    VFS_DIR (*fdopendir)(VFSContext *ctx, SYS_FILE fd);
+    int (*mkdir)(VFSContext *ctx, const char *path);
+    int (*unlink)(VFSContext *ctx, const char *path);
+    int (*rmdir)(VFSContext *Ctx, const char *path);
     uint32_t flags;
+    void *instance;
 };
 
 struct VFSContext {
@@ -70,16 +73,16 @@
 
 struct VFSFile {
     VFSContext *ctx;
-    VFS_IO *io; // IO functions
-    void *data; // private data used by the VFSFile implementation
-    int fd; // native file descriptor if available, or -1
+    VFS_IO *io; /* IO functions */
+    void *data; /* private data used by the VFSFile implementation */
+    int fd; /* native file descriptor if available, or -1 */
 };
 
 struct VFSDir {
     VFSContext *ctx;
     VFS_DIRIO *io;
-    void *data; // private data used by the VFSDir implementation
-    int fd; // native file descriptor if available, or -1
+    void *data; /* private data used by the VFSDir implementation */
+    int fd; /* native file descriptor if available, or -1 */
 };
 
 struct VFSEntry {
@@ -116,19 +119,21 @@
  */
 VFSContext* vfs_request_context(Session *sn, Request *rq);
 
-SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags);
-SYS_FILE vfs_openRO(VFSContext *ctx, char *path);
-SYS_FILE vfs_openWO(VFSContext *ctx, char *path);
-SYS_FILE vfs_openRW(VFSContext *ctx, char *path);
-int vfs_stat(VFSContext *ctx, char *path, struct stat *buf);
+SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags);
+SYS_FILE vfs_openRO(VFSContext *ctx, const char *path);
+SYS_FILE vfs_openWO(VFSContext *ctx, const char *path);
+SYS_FILE vfs_openRW(VFSContext *ctx, const char *path);
+int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf);
 int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
 void vfs_close(SYS_FILE fd);
-VFS_DIR vfs_opendir(VFSContext *ctx, char *path);
+VFS_DIR vfs_opendir(VFSContext *ctx, const char *path);
+VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
 int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry);
 int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry);
 void vfs_closedir(VFS_DIR dir);
-int vfs_mkdir(VFSContext *ctx, char *path);
-int vfs_unlink(VFSContext *ctx, char *path);
+int vfs_mkdir(VFSContext *ctx, const char *path);
+int vfs_unlink(VFSContext *ctx, const char *path);
+int vfs_rmdir(VFSContext *ctx, const char *path);
 
 #ifdef	__cplusplus
 }
--- a/src/server/public/webdav.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/public/webdav.h	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,7 @@
 #define	WS_WEBDAV_H
 
 #include "nsapi.h"
+#include "vfs.h"
 
 #include <sys/file.h>
 #include <sys/stat.h>
@@ -39,7 +40,408 @@
 extern "C" {
 #endif
 
+typedef struct WebdavBackend          WebdavBackend;
+    
+typedef struct WebdavProperty         WebdavProperty;
+typedef struct WebdavPList            WebdavPList;
+typedef struct WebdavNSList           WebdavNSList;
 
+typedef struct WebdavPListIterator    WebdavPListIterator;
+
+typedef enum   WebdavLockScope        WebdavLockScope;
+typedef enum   WebdavLockType         WebdavLockType;
+
+typedef enum   WebdavValueType        WebdavValueType;
+
+typedef struct WebdavPropfindRequest  WebdavPropfindRequest;
+typedef struct WebdavProppatchRequest WebdavProppatchRequest;
+typedef struct WebdavLockRequest      WebdavLockRequest;
+
+typedef struct WebdavVFSRequest       WebdavVFSRequest;
+
+typedef struct WebdavResponse         WebdavResponse;
+typedef struct WebdavResource         WebdavResource;
+
+typedef struct WebdavVFSProperties    WebdavVFSProperties;
+
+typedef struct WebdavOperation        WebdavOperation;
+
+typedef struct WSXmlData              WSXmlData;
+typedef struct WSText                 WSText;
+
+typedef struct _xmlNs   WSNamespace;
+typedef struct _xmlNode WSXmlNode;
+
+#define WS_NODE_ELEMENT        1
+#define WS_NODE_TEXT           3
+#define WS_NODE_CDATA          4
+#define WS_NODE_ENTITY_REF     5
+
+typedef int(*wsxml_func)(WSXmlNode *, void *);
+
+/* propfind settings */
+
+/*
+ * Use the vfs to stat files or read the directory children
+ */
+#define WS_WEBDAV_PROPFIND_USE_VFS     0x01
+
+/*
+ * Use the vfs to open a file for proppatch
+ */
+#define WS_WEBDAV_PROPPATCH_USE_VFS   0x02
+
+
+enum WebdavValueType {
+    WS_VALUE_NO_TYPE = 0,
+    WS_VALUE_XML_NODE,
+    WS_VALUE_XML_DATA,
+    WS_VALUE_TEXT
+};
+
+struct WSText {
+    char   *str;
+    size_t length;
+};
+
+struct WebdavProperty {
+    WSNamespace *namespace;
+    
+    const char *name;
+    
+    char *lang;
+    
+    union {
+        WSXmlNode *node;
+        WSXmlData *data;
+        WSText    text;
+    } value;
+    WebdavValueType vtype;
+};
+
+struct WSXmlData {
+    WebdavNSList *namespaces;
+    char         *data;
+    size_t       length;
+};
+
+struct WebdavPList {
+    WebdavProperty *property;
+    WebdavPList *prev;
+    WebdavPList *next;
+};
+
+struct WebdavNSList {
+    WSNamespace  *namespace;
+    WebdavNSList *prev;
+    WebdavNSList *next;
+};
+
+struct WebdavPListIterator {
+    WebdavPList **list;
+    WebdavPList *cur;
+    WebdavPList *next;
+    size_t      index;
+};
+
+enum WebdavLockScope {
+    WEBDAV_LOCK_EXCLUSIVE = 0,
+    WEBDAV_LOCK_SHARED,
+    WEBDAV_LOCK_SCOPE_UNKNOWN
+};
+
+enum WebdavLockType {
+    WEBDAV_LOCK_WRITE = 0,
+    WEBDAV_LOCK_TYPE_UNKNOWN
+};
+
+struct WebdavPropfindRequest {
+    Session *sn;
+    Request *rq;
+
+    void *doc;
+      
+    /*
+     * list of requested properties
+     */
+    WebdavPList *properties;
+    
+    /*
+     * number of properties
+     */
+    size_t propcount;
+    
+    WSBool allprop;
+    WSBool propname;
+    WSBool deadproperties;
+    
+    int depth;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavProppatchRequest {
+    Session *sn;
+    Request *rq;
+    
+    void *doc;
+    
+    WebdavPList *set;
+    size_t setcount;
+    
+    WebdavPList *remove;
+    size_t removecount;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavVFSRequest {
+    Session *sn;
+    Request *rq;
+    
+    char *path;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavLockRequest {
+    Session *sn;
+    Request *rq;
+    
+    void *doc;
+    
+    WebdavLockScope scope;
+    WebdavLockType type;
+    
+    WSXmlNode *owner;
+};
+
+struct WebdavVFSProperties {
+    uint32_t getcontentlength:1;
+    uint32_t getlastmodified:1;
+    uint32_t getresourcetype:1;
+    uint32_t getetag:1;
+    uint32_t creationdate:1;
+};
+
+struct WebdavResponse {
+    WebdavOperation *op;
+    
+    WebdavResource* (*addresource)(WebdavResponse*, const char*);
+};
+
+struct WebdavResource {
+    char *href;
+    
+    WSBool isclosed;
+    
+    int err;
+    
+    /*
+     * int addprop(WebdavResource *res, WebdavProperty *property, int status);
+     * 
+     * Adds a property to the resource
+     */
+    int (*addproperty)(WebdavResource*, WebdavProperty*, int);
+    
+    /*
+     * int close(WebdavResource *res);
+     * 
+     * Closes a resource object
+     */
+    int (*close)(WebdavResource*);
+};
+
+struct WebdavBackend {
+    /*
+     * int propfind_init(
+     *     WebdavPropfindRequest *rq,
+     *     const char *path,
+     *     WebdavPList **outplist);
+     * 
+     * Initializes a propfind request. This is called once for each propfind
+     * request and should initialize everything needed for generating the
+     * multistatus response.
+     * 
+     */
+    int (*propfind_init)(WebdavPropfindRequest *, const char *, WebdavPList **);
+    
+    /*
+     * int propfind_do(
+     *     WebdavPropfindRequest *rq,
+     *     WebdavResponse *response,
+     *     VFS_DIR parent,
+     *     WebdavResource *resource,
+     *     struct stat *s);
+     * 
+     * This function is called for the requsted resource and for all children
+     * if WS_PROPFIND_NO_VFS_CHILDREN is not set.
+     */
+    int (*propfind_do)(
+            WebdavPropfindRequest *,
+            WebdavResponse *,
+            VFS_DIR,
+            WebdavResource *,
+            struct stat *);
+    
+    /*
+     * int propfind_finish(WebdavPropfindRequest *rq);
+     * 
+     * Finishes a propfind request.
+     */
+    int (*propfind_finish)(WebdavPropfindRequest *);
+    
+    /*
+     * int proppatch_do(
+     *     WebdavProppatchRequest *request,
+     *     WebdavResource *response,
+     *     VFSFile *file,
+     *     WebdavPList **out_set,
+     *     WebdavPList **out_remove);
+     * 
+     * Modifies properties of the requsted resource.
+     */
+    int (*proppatch_do)(
+            WebdavProppatchRequest *,
+            WebdavResource *,
+            VFSFile *,
+            WebdavPList **,
+            WebdavPList **);
+    
+    /*
+     * int proppatch_finish(
+     *     WebdavProppatchRequest *request,
+     *     WebdavResource *response,
+     *     VFSFile *file,
+     *     WSBool commit);
+     * 
+     * Called after all proppatch_do functions of all backends are executed
+     * and should either permanently store the properties (commit == true) or
+     * revert all changed (commit == false).
+     */
+    int (*proppatch_finish)(
+            WebdavProppatchRequest *,
+            WebdavResource *,
+            VFSFile *,
+            WSBool);
+    
+    /*
+     * int opt_mkcol(WebdavVFSRequest *request, WSBool *out_created);
+     * 
+     * Optional mkcol callback that is called before vfs_mkdir. If the function
+     * sets out_created to TRUE, vfs_mkdir will not be executed.
+     */
+    int (*opt_mkcol)(WebdavVFSRequest *, WSBool *);
+    
+    /*
+     * int opt_mkcol_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a MKCOL request.
+     */
+    int(*opt_mkcol_finish)(WebdavVFSRequest *, WSBool);
+    
+    /*
+     * int opt_delete(WebdavVFSRequest *request, WSBool *out_deleted);
+     * 
+     * Optional delete callback that is called once before any VFS deletions.
+     * When the callback sets out_deleted to TRUE, no VFS unlink operations
+     * will be done.
+     * 
+     */
+    int (*opt_delete)(WebdavVFSRequest *, WSBool *);
+    
+    /*
+     * int opt_delete_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a DELETE request.
+     */
+    int (*opt_delete_finish)(WebdavVFSRequest *, WSBool);
+    
+    /*
+     * See the WS_WEBDAV_* macros for informations about the settings
+     */
+    uint32_t settings;
+    
+    
+    /*
+     * next Backend
+     */
+    WebdavBackend *next;
+};
+
+/*
+ * gets the requested depth
+ * 
+ * in case of infinity, -1 is returned
+ * if no depth is specified, 0 is returned
+ */
+int webdav_getdepth(Request *rq);
+
+int webdav_plist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop);
+
+WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list);
+WebdavPList* webdav_plist_clone_s(
+        pool_handle_t *pool,
+        WebdavPList *list,
+        size_t *newlen);
+
+size_t webdav_plist_size(WebdavPList *list);
+
+int webdav_nslist_add(
+        pool_handle_t *pool,
+        WebdavNSList **begin,
+        WebdavNSList **end,
+        WSNamespace *ns);
+
+WebdavPListIterator webdav_plist_iterator(WebdavPList **list);
+int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur);
+void webdav_plist_iterator_remove_current(WebdavPListIterator *i);
+
+WSNamespace* webdav_dav_namespace(void);
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name);
+
+int webdav_property_set_value(
+        WebdavProperty *property,
+        pool_handle_t *pool,
+        char *value);
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPList **plistInOut,
+        WSBool removefromlist,
+        uint32_t flags);
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s);
+
+int wsxml_iterator(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        wsxml_func begincb,
+        wsxml_func endcb,
+        void *udata);
+
+WebdavNSList* wsxml_get_required_namespaces(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        int *error);
 
 #ifdef	__cplusplus
 }
--- a/src/server/test/main.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/test/main.c	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -39,10 +39,12 @@
 #include "../util/plist.h"
 #include "../util/date.h"
 
-#include <ucx/string.h>
+#include <ucx/test.h>
 
-static int std_pipe_fds[2];
-static WSBool is_daemon;
+#include "vfs.h"
+#include "writer.h"
+#include "xml.h"
+#include "webdav.h"
 
 void test() {
     
@@ -50,7 +52,7 @@
 
 // needed for linking
 WSBool main_is_daemon(void) {
-    return is_daemon;
+    return 0;
 }
 
 int main(int argc, char **argv) {
@@ -59,7 +61,55 @@
     //test();
     
     printf("%s", "Webserver Test Suite\n====================\n\n");
-
+    
+    UcxTestSuite* suite = ucx_test_suite_new();
+    
+    // vfs tests
+    ucx_test_register(suite, test_vfs_open);
+    ucx_test_register(suite, test_vfs_mkdir);
+    ucx_test_register(suite, test_vfs_opendir);
+    ucx_test_register(suite, test_vfs_readdir);
+    ucx_test_register(suite, test_vfs_unlink);
+    ucx_test_register(suite, test_vfs_rmdir);
+    
+    // writer tests
+    ucx_test_register(suite, test_writer_putc);
+    ucx_test_register(suite, test_writer_flush);
+    ucx_test_register(suite, test_writer_put);
+    
+    // xml tests
+    ucx_test_register(suite, test_wsxml_iterator);
+    ucx_test_register(suite, test_wsxml_get_required_namespaces);
+    ucx_test_register(suite, test_wsxml_write_nodes);
+    
+    // webdav tests
+    ucx_test_register(suite, test_webdav_plist_add);
+    ucx_test_register(suite, test_webdav_plist_size);
+    ucx_test_register(suite, test_propfind_parse);
+    ucx_test_register(suite, test_proppatch_parse);
+    ucx_test_register(suite, test_lock_parse);
+    ucx_test_register(suite, test_rqbody2buffer);
+    ucx_test_register(suite, test_webdav_plist_iterator);
+    ucx_test_register(suite, test_webdav_plist_iterator_remove_current);
+    ucx_test_register(suite, test_msresponse_addproperty);
+    ucx_test_register(suite, test_webdav_propfind_init);
+    ucx_test_register(suite, test_webdav_op_propfind_begin);
+    ucx_test_register(suite, test_webdav_op_propfind_children);
+    ucx_test_register(suite, test_proppatch_msresponse);
+    ucx_test_register(suite, test_msresponse_addproperty_with_errors);
+    ucx_test_register(suite, test_webdav_op_proppatch);
+    ucx_test_register(suite, test_webdav_vfs_op_do);
+    ucx_test_register(suite, test_webdav_delete);
+       
+    // webdav methods
+    ucx_test_register(suite, test_webdav_propfind);
+    ucx_test_register(suite, test_webdav_proppatch);
+    ucx_test_register(suite, test_webdav_put);
+       
+    // run tests
+    ucx_test_run(suite, stdout);
+    fflush(stdout);
+    
     return EXIT_SUCCESS;
 }
 
--- a/src/server/test/objs.mk	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/test/objs.mk	Tue Aug 25 12:07:56 2020 +0200
@@ -31,6 +31,11 @@
 TEST_OBJPRE = $(OBJ_DIR)$(TEST_SRC_DIR)
 
 TESTOBJ = main.o
+TESTOBJ += testutils.o
+TESTOBJ += webdav.o
+TESTOBJ += vfs.o
+TESTOBJ += xml.o
+TESTOBJ += writer.o
 
 TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%)
 TESTSOURCE = $(TESTOBJ:%.o=test/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/testutils.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,170 @@
+/*
+ * 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 <ucx/string.h>
+#include <ucx/utils.h>
+
+#include "../util/pblock.h"
+
+#include "../util/io.h"
+
+#include "testutils.h"
+
+Session* testutil_session(void) {
+    pool_handle_t *pool = pool_create();
+    NSAPISession *sn = nsapisession_create(pool);
+    sn->connection = testutil_dummy_connection(pool);
+    
+    return &sn->sn;
+}
+
+Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri) {
+    NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest));
+    
+    HTTPRequest httprequest;
+    ZERO(&httprequest, sizeof(HTTPRequest));
+    request_initialize(pool, &httprequest, rq);
+    
+    sstr_t clf = ucx_sprintf("%s %s HTTP/1.1", method, uri);
+    pblock_kvinsert(
+            pb_key_clf_request,
+            clf.ptr,
+            clf.length,
+            rq->rq.reqpb);
+    free(clf.ptr);
+    
+    pblock_nvinsert(
+            "method",
+            method,
+            rq->rq.reqpb);
+    
+    pblock_nvinsert(
+            "protocol",
+            "HTTP/1.1",
+            rq->rq.reqpb);
+    
+    pblock_nvinsert("uri", uri, rq->rq.reqpb);
+    
+    return &rq->rq;
+}
+
+static int dummyconn_read(Connection *conn, void *buf, int len) {
+    return len;
+}
+
+static int dummyconn_write(Connection *conn, const void *buf, int len) {
+    return len;
+}
+
+static void dummyconn_close(Connection *conn) {
+    
+}
+
+
+Connection* testutil_dummy_connection(pool_handle_t *pool) {
+    Connection *conn = pool_malloc(pool, sizeof(Connection));
+    ZERO(conn, sizeof(Connection));
+    conn->read = dummyconn_read;
+    conn->write = dummyconn_write;
+    conn->close = dummyconn_close; 
+    return conn;
+}
+
+void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len) {
+    sstr_t cl = ucx_sprintf("%d", (int)len);
+    pblock_nvreplace("content-length", cl.ptr, rq->headers);
+    free(cl.ptr);
+    
+    netbuf *inbuf = pool_malloc(sn->pool, sizeof(netbuf));
+    inbuf->sd = NULL;
+    inbuf->inbuf = pool_malloc(sn->pool, len);
+    inbuf->pos = 0;
+    inbuf->maxsize = len;
+    inbuf->cursize = len;
+    sn->inbuf = inbuf;
+    
+    memcpy(inbuf->inbuf, body, len);
+}
+
+void testutil_destroy_session(Session *sn) {
+    pool_destroy(sn->pool);
+}
+
+
+static ssize_t test_io_write(IOStream *io, void *buf, size_t size) {
+    TestIOStream *st = (TestIOStream*)io;
+    return ucx_buffer_write(buf, 1, size, st->buf);
+}
+
+static ssize_t test_io_writev(IOStream *io, struct iovec *iovec, int iovctn) {
+    return -1;
+}
+
+static ssize_t test_io_read(IOStream *io, void *buf, size_t size) {
+    return -1;
+}
+
+static void test_io_close(IOStream *io) {
+    
+}
+
+static void test_io_finish(IOStream *io) {
+    
+}
+
+static void test_io_setmode(IOStream *io, int mode) {
+    
+}
+
+static int test_io_poll(IOStream *io, EventHandler *ev, int events , Event *event) {
+    return 1;
+}
+
+TestIOStream* testutil_iostream(size_t size, int autoextend) {
+    TestIOStream *stream = calloc(1, sizeof(TestIOStream));
+    int flags = 0;
+    if(autoextend) {
+        flags = UCX_BUFFER_AUTOEXTEND;
+    }
+    stream->buf = ucx_buffer_new(NULL, size, flags);
+    
+    stream->io.st.write = test_io_write;
+    stream->io.st.writev = test_io_writev;
+    stream->io.st.close = test_io_close;
+    stream->io.st.finish = test_io_finish;
+    
+    return stream;
+}
+
+void testutil_iostream_destroy(TestIOStream *stream) {
+    ucx_buffer_free(stream->buf);
+    free(stream);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/testutils.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#ifndef TESTUTILS_H
+#define TESTUTILS_H
+
+#include "../public/nsapi.h"
+#include "../daemon/httprequest.h"
+
+#include "../util/io.h"
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct TestIOStream {
+    HttpStream io;
+    UcxBuffer *buf;
+} TestIOStream;
+    
+Session* testutil_session(void);
+
+Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri);
+
+Connection* testutil_dummy_connection(pool_handle_t *pool);
+
+void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len);
+
+void testutil_destroy_session(Session *sn);
+
+TestIOStream* testutil_iostream(size_t size, int autoextend);
+void testutil_iostream_destroy(TestIOStream *stream);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTUTILS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/vfs.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,557 @@
+/*
+ * 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 <errno.h>
+
+#include <ucx/string.h>
+#include <ucx/list.h>
+#include <ucx/map.h>
+
+#include "../daemon/session.h"
+
+#include "testutils.h"
+
+#include "vfs.h"
+
+typedef struct TestVFS {
+    UcxMap *files;
+    int count_unlink;
+    int count_rmdir;
+} TestVFS;
+
+typedef struct TestVFSFile {
+    VFSFile file;
+    sstr_t path;
+    int isdir;
+    UcxBuffer *content;
+} TestVFSFile;
+
+typedef struct TestVFSDir {
+    VFSDir dir;
+    TestVFSFile *file;
+    UcxMapIterator i;
+    sstr_t name;
+} TestVFSDir;
+
+/* dir io */
+
+static char* test_resource_name(char *url) {
+    sstr_t urlstr = sstr(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    sstr_t resname = sstrrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) {
+    TestVFS *vfs = dir->ctx->vfs->instance;
+    TestVFSDir *vfsdir = (TestVFSDir*)dir;
+    
+    sstr_t prefix = sstrcat(2, vfsdir->file->path, S("/"));
+    
+    TestVFSFile *file = NULL;
+    UCX_MAP_FOREACH(key, file, vfsdir->i) {
+        sstr_t file_path = sstrcat(
+                2,
+                prefix,
+                sstr(test_resource_name(file->path.ptr)));
+        void *m = ucx_map_get(vfs->files, ucx_key(file_path.ptr, file_path.length));
+        // don't ask why alfree and not free()
+        alfree(ucx_default_allocator(), file_path.ptr);
+        if(m) {
+            break;
+        } else {
+            file = NULL;
+        }
+    }
+    free(prefix.ptr);
+    
+    if(file) {
+        vfsdir->name = sstrdup_a(
+                session_get_allocator(dir->ctx->sn),
+                sstr(test_resource_name(file->path.ptr)));
+        ZERO(entry, sizeof(VFS_ENTRY));
+        entry->name = vfsdir->name.ptr;
+        
+        if(getstat) {
+            ZERO(&entry->stat, sizeof(struct stat));
+            if(file->isdir) {
+                entry->stat.st_mode = S_IFDIR;
+            }
+        }
+        
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+void testvfs_dir_close(VFS_DIR dir) {
+    TestVFSDir *testdir = (TestVFSDir*)dir;
+    pool_free(testdir->dir.ctx->sn->pool, dir);
+    
+}
+
+ ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     return (ssize_t)ucx_buffer_read(buf, 1, nbyte, file->content);
+ }
+ 
+ ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     return (ssize_t)ucx_buffer_write(buf, 1, nbyte, file->content);
+ }
+ 
+ ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = (size_t)offset;
+     return testvfs_read(fd, buf, nbyte);
+ }
+ 
+ ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = (size_t)offset;
+     return testvfs_write(fd, buf, nbyte);
+ }
+ 
+ off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     ucx_buffer_seek(file->content, offset, whence);
+     return (off_t)file->content->pos;
+ }
+ 
+ void testvfs_close(SYS_FILE fd) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = 0;
+ }
+
+VFS_IO test_file_io = {
+    testvfs_read,
+    testvfs_write,
+    testvfs_pread,
+    testvfs_pwrite,
+    testvfs_seek,
+    testvfs_close,
+    NULL, /* aio_read */
+    NULL  /* aio_write */
+};
+
+VFS_DIRIO test_dir_io = {
+    testvfs_readdir,
+    testvfs_dir_close
+};
+
+
+/* vfs funcs */
+
+SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        if((oflags & O_CREAT) == O_CREAT) {
+            file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile));
+            ZERO(file, sizeof(TestVFSFile));
+            file->path = sstrdup_a(session_get_allocator(ctx->sn), s_path);
+            file->file.io = &test_file_io;
+            
+            file->content = pool_calloc(ctx->sn->pool, 1, sizeof(UcxBuffer));
+            file->content->capacity = 2048;
+            file->content->space = pool_malloc(ctx->sn->pool, file->content->capacity);
+            file->content->flags = 0;
+            file->content->pos = 0;
+            file->content->size = 0;
+            
+            ucx_map_sstr_put(vfs->files, s_path, file);
+        } else {
+            ctx->vfs_errno = ENOENT;
+        }
+    }
+    
+    return (SYS_FILE)file;
+}
+
+int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        ctx->vfs_errno = ENOENT;
+        return 1;
+    }
+    
+    ZERO(buf, sizeof(struct stat));
+    if(file->isdir) {
+        buf->st_mode = S_IFDIR;
+    }
+    
+    return 0;
+}
+
+int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) {
+    return 0;
+}
+
+VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        ctx->vfs_errno = ENOENT;
+        return NULL;
+    }
+    
+    if(!file->isdir) {
+        return NULL;
+    }
+    
+    TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir));
+    ZERO(dir, sizeof(TestVFSDir));
+    dir->file = file;
+    dir->i = ucx_map_iterator(vfs->files);
+    
+    dir->dir.ctx = ctx;
+    dir->dir.io = &test_dir_io;
+    
+    return (VFS_DIR)dir;
+}
+
+VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = (TestVFSFile*)fd;
+    if(!file->isdir) {
+        return NULL;
+    }
+    
+    TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir));
+    ZERO(dir, sizeof(TestVFSDir));
+    dir->file = file;
+    dir->i = ucx_map_iterator(vfs->files);
+    
+    dir->dir.ctx = ctx;
+    dir->dir.io = &test_dir_io;
+    
+    return (VFS_DIR)dir;
+}
+
+int testvfs_mkdir(VFSContext *ctx, const char *path) {
+    SYS_FILE fd = testvfs_open(ctx, path, O_CREAT);
+    if(!fd) {
+        return 1;
+    }
+    
+    TestVFSFile *file = (TestVFSFile*)fd;
+    file->isdir = 1;
+    
+    return 0;
+}
+
+int testvfs_unlink(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;
+    TestVFSFile *file = ucx_map_cstr_get(vfs->files, path);
+    if(!file) {
+        return 1;
+    }
+    
+    if(file->isdir) {
+        return 1;
+    }
+    
+    ucx_map_cstr_remove(vfs->files, path);
+    vfs->count_unlink++;
+    return 0;
+}
+
+int testvfs_rmdir(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;
+    TestVFSFile *dir = ucx_map_cstr_get(vfs->files, path);
+    if(!dir) {
+        ctx->vfs_errno = ENOENT;
+        return 1;
+    }
+    
+    if(!dir->isdir) {
+        return 1;
+    }
+    
+    UcxMapIterator i = ucx_map_iterator(vfs->files);
+    TestVFSFile *f;
+    UCX_MAP_FOREACH(key, f, i) {
+        if(f->path.length > dir->path.length && sstrprefix(f->path, dir->path)){
+            return 1; // dir not empty
+        }
+    }
+    
+    ucx_map_cstr_remove(vfs->files, path);
+    vfs->count_rmdir++;
+    return 0;
+}
+
+static VFS testVFSClass = {
+    testvfs_open,
+    testvfs_stat,
+    testvfs_fstat,
+    testvfs_opendir,
+    testvfs_fdopendir,
+    testvfs_mkdir,
+    testvfs_unlink,
+    testvfs_rmdir,
+    0,
+    NULL
+};
+
+
+VFS* testvfs_create(Session *sn) {
+    TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS));
+    vfs->count_unlink = 0;
+    vfs->count_rmdir = 0;
+    vfs->files = ucx_map_new_a(session_get_allocator(sn), 64);
+    
+    testVFSClass.instance = vfs;
+    return &testVFSClass;
+}
+
+
+/* ------------------------------------------------------------------------- */
+//
+// VFS Tests
+//
+/* ------------------------------------------------------------------------- */
+
+UCX_TEST(test_vfs_open) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    UCX_TEST_ASSERT(vfs, "vfs is NULL");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    SYS_FILE f2 = vfs_open(vfs, "/file1", 0);
+    UCX_TEST_ASSERT(f2, "f2 not opened");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_mkdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    SYS_FILE fd = vfs_open(vfs, "/dir", 0);
+    UCX_TEST_ASSERT(fd, "no fd");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_opendir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    VFSDir *dir = vfs_opendir(vfs, "/dir");
+    UCX_TEST_ASSERT(dir, "no dir");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_readdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int 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");
+    
+    UcxMap *files = ucx_map_new(8);
+    
+    VFSEntry entry;
+    while(vfs_readdir(dir, &entry)) {
+        ucx_map_cstr_put(files, entry.name, dir);
+    }
+    
+    UCX_TEST_ASSERT(files->count == 4, "wrong files count");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file1"), "file1 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file2"), "file2 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file3"), "file3 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file4"), "file4 missing");
+    
+    ucx_map_free(files);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_unlink) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    // prepare test
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f2 not opened");
+    
+    SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f3 not opened");
+    
+    // test unlink
+    err = vfs_unlink(vfs, "/file1");
+    UCX_TEST_ASSERT(err == 0, "unlink /file1 failed");
+    err = vfs_unlink(vfs, "/dir1/file3");
+    UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed");
+    
+    err = vfs_unlink(vfs, "/filex");
+    UCX_TEST_ASSERT(err != 0, "unlink /filex should fail");
+    
+    // check if files were removed
+    SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY);
+    UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted");
+    SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY);
+    UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted");
+    
+    // file2 should still be there
+    SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY);
+    UCX_TEST_ASSERT(o2, "/file2 deleted");
+    
+    // check if dir unlink fails
+    err = vfs_unlink(vfs, "/dir1");
+    UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_rmdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    // prepare test
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    err = vfs_rmdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail");
+    err = vfs_rmdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed");
+    
+    err = vfs_unlink(vfs, "/dir1/file1");
+    UCX_TEST_ASSERT(err == 0, "unlink failed");
+    err = vfs_rmdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/vfs.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef TEST_VFS_H
+#define TEST_VFS_H
+
+#include "../public/nsapi.h"
+#include "../public/vfs.h"
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VFS* testvfs_create(Session *sn);
+
+UCX_TEST(test_vfs_open);
+UCX_TEST(test_vfs_mkdir);
+UCX_TEST(test_vfs_opendir);
+UCX_TEST(test_vfs_readdir);
+UCX_TEST(test_vfs_unlink);
+UCX_TEST(test_vfs_rmdir);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_VFS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,1762 @@
+/*
+ * 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,
+        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
+};
+
+// 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,
+        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,
+    &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 = NULL;
+    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);
+}
+
+static void init_test_webdav_method(
+        Session **out_sn,
+        Request **out_rq,
+        TestIOStream **out_st,
+        pblock **out_pb,
+        const char *method,
+        const char *request_body)
+{
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, method, "/");
+    
+    pblock_nvinsert("path", "/", rq->vars);
+    pblock_nvinsert("uri", "/", 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[1].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[1].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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+#ifndef TEST_WEBDAV_H
+#define TEST_WEBDAV_H
+
+#include "../public/nsapi.h"
+#include "../public/webdav.h"
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+UCX_TEST(test_webdav_plist_add);
+UCX_TEST(test_webdav_plist_size);
+    
+UCX_TEST(test_propfind_parse);
+UCX_TEST(test_proppatch_parse);
+UCX_TEST(test_lock_parse);
+
+UCX_TEST(test_rqbody2buffer);
+
+UCX_TEST(test_webdav_plist_iterator);
+UCX_TEST(test_webdav_plist_iterator_remove_current);
+
+UCX_TEST(test_msresponse_addproperty);
+UCX_TEST(test_msresponse_addproperty_with_errors);
+
+UCX_TEST(test_webdav_propfind_init);
+UCX_TEST(test_webdav_op_propfind_begin);
+UCX_TEST(test_webdav_op_propfind_children);
+
+UCX_TEST(test_webdav_propfind);
+
+UCX_TEST(test_proppatch_msresponse);
+UCX_TEST(test_webdav_op_proppatch);
+
+UCX_TEST(test_webdav_proppatch);
+
+UCX_TEST(test_webdav_vfs_op_do);
+
+UCX_TEST(test_webdav_delete);
+UCX_TEST(test_webdav_put);
+
+/* --------------------------- PROPFIND --------------------------- */
+
+#define TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getcontenttype/> \
+                <D:getlastmodified/> \
+                <D:resourcetype/> \
+                <D:getetag/> \
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop xmlns:X=\"http://example.com/\"> \
+                <D:resourcetype/> \
+                <X:testprop/> \
+                <X:name/> \
+                <Z:testprop xmlns:Z=\"testns\"/>\
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:allprop/> \
+        </D:propfind>"
+
+#define TEST_PROPFIND4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:propname/> \
+        </D:propfind>"
+
+#define TEST_PROPFIND5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getetag/> \
+                <D:getcontentlength/> \
+                <D:resourcetype/> \
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getetag/> \
+                <D:resourcetype/> \
+            </D:prop> \
+            <D:allprop/> \
+        </D:propfind>"
+
+/* --------------------------- PROPPATCH --------------------------- */
+
+#define TEST_PROPPATCH1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop><X:test>test</X:test><D:creationdate>123</D:creationdate></D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop> \
+                    <X:a>test</X:a> \
+                    <X:b>15</X:b> \
+                    <X:c> \
+                        <X:name test='test1' abc='def'>User</X:name><X:mail>user@host</X:mail> \
+                    </X:c> \
+                    <X:d><X:name>Test</X:name></X:d> \
+                </D:prop> \
+            </D:set> \
+            <D:remove> \
+                <D:prop> \
+                    <X:e/> \
+                </D:prop> \
+            </D:remove> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop> \
+                    <X:a>test</X:a> \
+                    <X:fail>15</X:fail> \
+                </D:prop> \
+            </D:set> \
+            <D:remove> \
+                <D:prop> \
+                    <X:e/> \
+                </D:prop> \
+            </D:remove> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop><X:abort>error</X:abort></D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+/* --------------------------- LOCK --------------------------- */
+
+#define TEST_LOCK1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:lockinfo xmlns:D=\"DAV:\"> \
+            <D:lockscope><D:shared/></D:lockscope> \
+            <D:locktype>D:write/></D:locktype> \
+            <D:owner>User</D:owner> \
+        </D:lockinfo>"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_WEBDAV_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,151 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 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 "../util/writer.h"
+
+#include <ucx/buffer.h>
+
+#include "writer.h"
+#include "testutils.h"
+
+UCX_TEST(test_writer_putc) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[1] == 'b', "2: wrong char at pos 1");
+    UCX_TEST_ASSERT(writer.pos == 2, "2: wrong pos");
+    
+    writer_putc(out, 'c');
+    writer_putc(out, 'd');
+    UCX_TEST_ASSERT(wbuf[2] == 'c', "3: wrong char at pos 2");
+    UCX_TEST_ASSERT(wbuf[3] == 'd', "4: wrong char at pos 3");
+    
+    writer_putc(out, 'f'); // should flush the buffer
+    UCX_TEST_ASSERT(wbuf[0] == 'f', "5: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "5: wrong pos");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "5: wrong char at UcxBuffer pos 0");
+    UCX_TEST_ASSERT(buf->space[1] == 'b', "5: wrong char at UcxBuffer pos 1");
+    UCX_TEST_ASSERT(buf->pos == 4, "5: wrong UcxBuffer pos");    
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_flush) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_flush(out);
+    UCX_TEST_ASSERT(writer.pos == 0, "wrong pos after flush");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 1, "wrong UcxBuffer pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[0] == 'b', "2: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "2: wrong pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_put) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 8);
+    Writer *out = &writer;
+    
+    writer_put(out, "abcd", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcd", 4), "1: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "1: wrong pos");
+    
+    writer_put(out, "efgh", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcdefgh", 8), "2: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 8, "2: wrong pos");
+    
+    writer_put(out, "1234", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "1234", 4), "3: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "3: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh", 8), "3: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 8, "3: wrong UcxBuffer pos");
+    
+    writer_put(out, "5678xx", 6);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "xx", 2), "4: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 2, "4: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh12345678", 16), "4: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 16, "4: wrong UcxBuffer pos");
+    
+    writer_puts(out, S("345678abcdefgh12345678end."));
+    UCX_TEST_ASSERT(!memcmp(wbuf, "end.", 4), "5: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "5: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(
+            buf->space,
+            "abcdefgh12345678xx345678abcdefgh12345678",
+            40),
+            "5: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 40, "5: wrong UcxBuffer pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 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.
+ */
+
+#ifndef TEST_WRITER_H
+#define TEST_WRITER_H
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_writer_putc);
+UCX_TEST(test_writer_flush);
+UCX_TEST(test_writer_put);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_WRITER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/xml.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,337 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 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 "xml.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libxml/tree.h>
+
+#include "testutils.h"
+
+#include "../public/webdav.h"
+#include "../util/writer.h"
+
+#include "../webdav/webdav.h"
+#include "../webdav/xml.h"
+
+typedef struct Test1Data {
+    int beginCounter;
+    int endCounter;
+    int elmCounter;
+    int endElmCounter;
+    int textErr;
+    int err;
+    int endErr;
+    int nodesWithAttributesCounter;
+    xmlNode *prev;
+} Test1Data;
+
+static int test1_begin(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->beginCounter++;
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->elmCounter++;
+        const char *name = (const char*)node->name;
+        
+        if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) {
+            data->err = 1;
+            return 1;
+        }
+        
+        switch(data->elmCounter) {
+            case 1: if(strcmp(name, "test")){ data->err = 1; return 1; } break;
+            case 2: if(strcmp(name, "elm1")) { data->err = 1; return 1; } break;
+            case 3: if(strcmp(name, "elm2")) { data->err = 1; return 1; } break;
+            case 4: if(strcmp(name, "c")) { data->err = 1; return 1; } break;
+            case 5: if(strcmp(name, "a")) { data->err = 1; return 1; } break;
+            case 6: if(strcmp(name, "d")) { data->err = 1; return 1; } break;
+            case 7: if(strcmp(name, "e")) { data->err = 1; return 1; } break;
+            case 8: if(strcmp(name, "b")) { data->err = 1; return 1; } break;
+            case 9: if(strcmp(name, "x")) { data->err = 1; return 1; } break;
+            case 10: if(strcmp(name, "z")) { data->err = 1; return 1; } break;
+            case 11: if(strcmp(name, "nextelm")) { data->err = 1; return 1; } break;
+        }
+    } else if(node->type == XML_TEXT_NODE) {
+        const char *text = (const char*)node->content;
+        if(!strcmp(text, "teststr")) {
+            if(strcmp((const char*)data->prev->name, "elm1")) {
+                data->textErr = 1;
+                return 1;
+            }
+        } else if(!strcmp(text, "hello") || !strcmp(text, "world")) {
+            if(strcmp((const char*)data->prev->name, "a")) {
+                data->textErr = 1;
+                return 1;
+            }
+        }
+    }
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->prev = node;
+    }
+    return 0;
+}
+
+static int test1_end(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->endCounter++;
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->endElmCounter++;
+        const char *name = (const char*)node->name;
+        
+        if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) {
+            data->err = 1;
+            return 1;
+        }
+        
+        switch(data->endElmCounter) {
+            case 1: if(strcmp(name, "elm1")){ data->endErr = 1; return 1; } break;
+            case 2: if(strcmp(name, "elm2")){ data->endErr = 1; return 1; } break;
+            case 3: if(strcmp(name, "a")){ data->endErr = 1; return 1; } break;
+            case 4: if(strcmp(name, "b")){ data->endErr = 1; return 1; } break;
+            case 5: if(strcmp(name, "e")){ data->endErr = 1; return 1; } break;
+            case 6: if(strcmp(name, "d")){ data->endErr = 1; return 1; } break;
+            case 7: if(strcmp(name, "c")){ data->endErr = 1; return 1; } break;
+            case 8: if(strcmp(name, "z")){ data->endErr = 1; return 1; } break;
+            case 9: if(strcmp(name, "x")){ data->endErr = 1; return 1; } break;
+            case 10: if(strcmp(name, "test")){ data->endErr = 1; return 1; } break;
+            case 11: if(strcmp(name, "nextelm")) { data->endErr = 1; return 1; } break;
+        }
+    }
+    
+    return 0;
+}
+
+static int test2_begin(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->beginCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->elmCounter++;
+        if(node->properties) {
+            data->nodesWithAttributesCounter++;
+        }
+    }
+    return 0;
+}
+
+static int test2_end(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->endCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->endElmCounter++;
+    }
+    return 0;
+}
+
+UCX_TEST(test_wsxml_iterator) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    xmlDoc *doc = xmlReadMemory(
+            XML_TESTDATA1, strlen(XML_TESTDATA1), NULL, NULL, 0);
+    xmlDoc *doc2 = xmlReadMemory(
+            XML_TESTDATA2, strlen(XML_TESTDATA2), NULL, NULL, 0);
+    xmlDoc *doc6 = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "doc is NULL");
+    UCX_TEST_ASSERT(doc2, "doc2 is NULL");
+    UCX_TEST_ASSERT(doc6, "doc6 is NULL");
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    
+    // Test 1: iterate over complete document
+    Test1Data testdata;
+    ZERO(&testdata, sizeof(Test1Data));
+    int ret = wsxml_iterator(sn->pool, root, test1_begin, test1_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "wsxml_iterator failed");
+    UCX_TEST_ASSERT(!testdata.err, "wrong element order (begin)");
+    UCX_TEST_ASSERT(!testdata.endErr, "wrong element order (end)");
+    UCX_TEST_ASSERT(!testdata.textErr, "text order error");
+    UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "begin/end counter not equal");
+    
+    // Test 2: iterate over sub-document
+    ZERO(&testdata, sizeof(Test1Data));
+    xmlNode *root2 = xmlDocGetRootElement(doc2);
+    xmlNode *sub = root2->children->children;
+    ret = wsxml_iterator(sn->pool, sub, test1_begin, test1_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "test2: wsxml_iterator failed");
+    UCX_TEST_ASSERT(!testdata.err, "test2: wrong element order (begin)");
+    UCX_TEST_ASSERT(!testdata.endErr, "test2: wrong element order (end)");
+    UCX_TEST_ASSERT(!testdata.textErr, "test2: text order error");
+    UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "test2: begin/end counter not equal");
+    
+    // Test 3: iterate over document with all kinds of node types
+    xmlNode *root6 = xmlDocGetRootElement(doc6);
+    ZERO(&testdata, sizeof(Test1Data));
+    ret = wsxml_iterator(sn->pool, root6, test2_begin, test2_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "test3: wsxml_iterator failed");
+    UCX_TEST_ASSERT(testdata.elmCounter == testdata.endElmCounter, "test3: begin/end counter not equal");
+    UCX_TEST_ASSERT(testdata.elmCounter == 12, "test3: wrong elm counter");
+    UCX_TEST_ASSERT(testdata.nodesWithAttributesCounter == 5, "test3: wrong entity ref counter");
+    
+    xmlFreeDoc(doc);
+    xmlFreeDoc(doc2);
+    xmlFreeDoc(doc6);
+    UCX_TEST_END;
+}
+
+// checks if the namespace list contains the test namespaces x1, x2, x3 and x4
+static void check_ns_list(WebdavNSList *list, int *x1, int *x2, int *x3, int *x4) {
+    *x1 = 0;
+    *x2 = 0;
+    *x3 = 0;
+    *x4 = 0;
+    
+    WebdavNSList *elm = list;
+    while(elm) {
+        if(!strcmp((const char*)elm->namespace->prefix, "x1") &&
+           !strcmp((const char*)elm->namespace->href, "http://example.com/ns1/"))
+        {
+            *x1 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x2") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns2/"))
+        {
+            *x2 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x3") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/"))
+        {
+            *x3 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x4") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/"))
+        {
+            *x4 = 1;
+        }
+            
+        elm = elm->next;
+    }
+}
+
+UCX_TEST(test_wsxml_get_required_namespaces) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    xmlDoc *doc3 = xmlReadMemory(
+            XML_TESTDATA3, strlen(XML_TESTDATA3), NULL, NULL, 0);
+    xmlDoc *doc4 = xmlReadMemory(
+            XML_TESTDATA4, strlen(XML_TESTDATA4), NULL, NULL, 0);
+    xmlDoc *doc5 = xmlReadMemory(
+            XML_TESTDATA5, strlen(XML_TESTDATA5), NULL, NULL, 0);
+    
+    xmlNode *node0 = xmlDocGetRootElement(doc3);
+    xmlNode *node1 = xmlDocGetRootElement(doc3)->children;
+    xmlNode *node2 = xmlDocGetRootElement(doc4)->children;
+    xmlNode *node3 = xmlDocGetRootElement(doc5)->children;
+    
+    UCX_TEST_ASSERT(doc3, "doc3 is NULL");
+    UCX_TEST_ASSERT(doc4, "doc4 is NULL");
+    UCX_TEST_ASSERT(doc5, "doc5 is NULL");
+    
+    int err0, err1, err2, err3;
+    int x1 = 0;
+    int x2 = 0;
+    int x3 = 0;
+    int x4 = 0;
+    WebdavNSList *elm = NULL;
+    
+    // Test 0: 
+    WebdavNSList *ns0 = wsxml_get_required_namespaces(sn->pool, node0, &err0);
+    UCX_TEST_ASSERT(!err0, "ns0 failed");
+    UCX_TEST_ASSERT(!ns0, "ns0: nsdefs should be ignored");
+    
+    WebdavNSList *ns1 = wsxml_get_required_namespaces(sn->pool, node1, &err1);
+    check_ns_list(ns1, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err1, "ns1 failed");
+    UCX_TEST_ASSERT(ns1, "ns1: no list");
+    UCX_TEST_ASSERT(x1, "ns1: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns1: x2 missing");
+    UCX_TEST_ASSERT(x3, "ns1: x3 missing");
+    UCX_TEST_ASSERT(x4, "ns1: x4 missing");
+    
+    WebdavNSList *ns2 = wsxml_get_required_namespaces(sn->pool, node2, &err2);
+    check_ns_list(ns2, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err2, "ns2 failed");
+    UCX_TEST_ASSERT(ns2, "ns2: no list");
+    UCX_TEST_ASSERT(x1, "ns2: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns2: x2 missing");
+    UCX_TEST_ASSERT(!x3, "ns2: x3");
+    UCX_TEST_ASSERT(!x4, "ns2: x4");
+    
+    WebdavNSList *ns3 = wsxml_get_required_namespaces(sn->pool, node3, &err3);
+    check_ns_list(ns3, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err3, "ns3 failed");
+    UCX_TEST_ASSERT(ns3, "ns3: no list");
+    UCX_TEST_ASSERT(x1, "ns3: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns3: x2 missing");
+    UCX_TEST_ASSERT(!x3, "ns3: x3");
+    UCX_TEST_ASSERT(!x4, "ns3: x4");
+    
+    xmlFreeDoc(doc3);
+    xmlFreeDoc(doc4);
+    xmlFreeDoc(doc5);
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_wsxml_write_nodes) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    
+    UCX_TEST_BEGIN;
+    xmlDoc *doc = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "xml parser error");
+    xmlNode *root = xmlDocGetRootElement(doc);
+    
+    Writer writer;
+    char buffer[1024];
+    writer_init(&writer, st, buffer, 1024);
+    
+    int err = wsxml_write_nodes(sn->pool, &writer, NULL, root);
+    writer_flush(&writer);
+    UCX_TEST_ASSERT(err == 0, "wsxml_write_nodes error");
+    UCX_TEST_ASSERT(st->buf->pos > 0, "buffer is empty");
+    
+    //printf("\n\n");
+    //printf("%.*s\n", (int)st->buf->size, st->buf->space);
+    //printf("\n\n");
+    
+    xmlDoc *genDoc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(genDoc, "generated doc is not valid xml");
+    
+    xmlFreeDoc(doc);
+    xmlFreeDoc(genDoc);
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/xml.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,131 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 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.
+ */
+
+#ifndef TEST_XML_H
+#define TEST_XML_H
+
+#include "../public/nsapi.h"
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_wsxml_iterator);
+UCX_TEST(test_wsxml_get_required_namespaces);
+UCX_TEST(test_wsxml_write_nodes);
+
+
+#define XML_TESTDATA1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <test> \
+            <elm1>teststr</elm1> \
+            <!-- comment -->\
+            <elm2 /> \
+            <c> \
+                <a>hello</a> \
+                world\
+                <d><e><b/></e></d>\
+            </c> \
+            <x><z/></x> \
+        </test>"
+
+#define XML_TESTDATA2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <root><wrapper><test> \
+            <elm1>teststr</elm1> \
+            <!-- comment -->\
+            <elm2 /> \
+            <c> \
+                <a>hello</a> \
+                world\
+                <d><e><b/></e></d>\
+            </c> \
+            <x><z/></x> \
+        </test><nextelm/></wrapper><ignore/></root>"
+
+#define XML_TESTDATA3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" \
+            xmlns:x3=\"http://example.com/ns_0/\" \
+            xmlns:x4=\"http://example.com/ns_0/\" > \
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+            <x3:elm3>str1</x3:elm3>\
+            <x4:elm4>str1</x4:elm4>\
+        </x1:prop>"
+
+#define XML_TESTDATA4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" \
+            xmlns:x3=\"http://example.com/ns_0/\" \
+            xmlns:x4=\"http://example.com/ns_0/\" >\
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+        </x1:prop>"
+
+#define XML_TESTDATA5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" > \
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+            <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\
+            <x4:elm4 xmlns:x4=\"http://example.com/ns_0/\" >str1</x4:elm4>\
+        </x1:prop>"
+
+#define XML_TESTDATA6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \n\
+        <x1:test \n\
+            xmlns:x1=\"http://example.com/ns1/\" \n\
+            xmlns:x2=\"http://example.com/ns2/\" > \n\
+            <x1:elm1>str1</x1:elm1>\n\
+            <x2:elm2>str1</x2:elm2>\n\
+            <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\n\
+            <x1:sub> \n\
+                <x1:a attr1=\"val1\"/> \n\
+                <x1:a attr2=\"val2\">text</x1:a>\n\
+                <x1:b x2:nsattr=\"nsval\"><x1:c/></x1:b>\n\
+            </x1:sub> \n\
+            <x1:newns xmlns:x4=\"http://example.com/0/\" x4:attr3=\"val3\">\n\
+            </x1:newns>\n\
+            <x1:text>Hello\n\
+            World\n\
+            end.\n\
+            </x1:text>\n\
+            <x1:entityref ea=\"test &amp; value\">\n\
+            entity reference test &amp;quote&amp; \n\
+            &#x3C;xml&#x3E;\n\
+            </x1:entityref>\n\
+        </x1:test>\n"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_XML_H */
+
--- a/src/server/util/io.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/io.c	Tue Aug 25 12:07:56 2020 +0200
@@ -269,8 +269,10 @@
         io[1].iov_len = nbytes;
         io[2].iov_base = "\r\n";
         io[2].iov_len = 2;
+        // TODO: FIXME: if r < sum of iov_len, everything would explode
+        // we need to store the chunk state and remaining bytes
         ssize_t r = fd->writev(fd, io, 3);
-        return r - io[0].iov_len;
+        return r - io[0].iov_len - io[2].iov_len;
     } else {
         return fd->write(fd, buf, nbytes);
     }
@@ -280,6 +282,9 @@
     IOStream *fd = st->fd;
     if(st->chunked_enc) {
         struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec));
+        if(!io) {
+            return 0;
+        }
         char chunk_len[16];
         io[0].iov_base = chunk_len;
         size_t len = 0;
@@ -289,7 +294,10 @@
         io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len);
         memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec));
         ssize_t r = fd->writev(fd, io, iovcnt + 1);
-        return r - io[0].iov_len;
+        
+        ssize_t ret = r - io[0].iov_len;
+        free(io);
+        return ret;
     } else {
         return fd->writev(fd, iovec, iovcnt);
     }
--- a/src/server/util/netbuf.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/netbuf.c	Tue Aug 25 12:07:56 2020 +0200
@@ -143,7 +143,11 @@
             return bytes_in_buffer;
         }
     }
-
+    
+    if(!buf->sd) {
+        return NETBUF_EOF;
+    }
+    
     /* The netbuf is empty.  Read data directly into the caller's buffer */
     bytes = net_read(buf->sd, buffer, size);
     if (bytes == 0)
--- a/src/server/util/objs.mk	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/objs.mk	Tue Aug 25 12:07:56 2020 +0200
@@ -42,6 +42,7 @@
 UTILOBJ += thrpool.o
 UTILOBJ += util.o
 UTILOBJ += date.o
+UTILOBJ += writer.o
 
 UTILOBJS = $(UTILOBJ:%=$(UTIL_OBJPRE)%)
 UTILSOURCE = $(UTILOBJ:%.o=util/%.c)
--- a/src/server/util/pblock.cpp	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/pblock.cpp	Tue Aug 25 12:07:56 2020 +0200
@@ -324,7 +324,8 @@
 const pb_key *const pb_key_vary = _create_key("vary");
 const pb_key *const pb_key_via = _create_key("via");
 const pb_key *const pb_key_warning = _create_key("warning");
-
+const pb_key *const pb_key_depth = _create_key("depth");
+const pb_key *const pb_key_if = _create_key("if");
 
 /* ------------------------------ _find_key ------------------------------- */
 
--- a/src/server/util/pblock.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/pblock.h	Tue Aug 25 12:07:56 2020 +0200
@@ -301,6 +301,8 @@
 extern const pb_key *const pb_key_vary;
 extern const pb_key *const pb_key_via;
 extern const pb_key *const pb_key_warning;
+extern const pb_key *const pb_key_depth;
+extern const pb_key *const pb_key_if;
 
 NSAPI_PUBLIC pool_handle_t *pblock_pool(pblock *pb);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/platform.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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.
+ */
+
+#ifndef BASE_PLATFORM_H
+#define BASE_PLATFORM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__clang__)
+#define WS_FALLTHROUGH [[clang::fallthrough]]
+#elif defined(__GNUC__)
+#define WS_FALLTHROUGH __attribute__((fallthrough))
+#else
+#define WS_FALLTHROUGH
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BASE_PLATFORM_H */
+
--- a/src/server/util/systems.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/util/systems.h	Tue Aug 25 12:07:56 2020 +0200
@@ -58,10 +58,6 @@
 typedef int PRBool;
 #define PR_TRUE  1
 #define PR_FALSE 0
-
-typedef int WSBool;
-#define WS_TRUE  1
-#define WS_FALSE 0
 /* end new types */
 
 /* --- End common definitions for all supported platforms --- */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/writer.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,107 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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 "writer.h"
+
+void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len) {
+    w->fd = fd;
+    w->buffer = buf;
+    w->size = len;
+    w->pos = 0;
+    w->error = 0;
+}
+
+int writer_flush(Writer *w) {
+    if(w->error) {
+        return w->error;
+    }
+    
+    size_t pos = 0;
+    size_t len = w->pos;
+    
+    while(len > 0) {
+        ssize_t r = net_write(w->fd, w->buffer + pos, len);
+        if(r <= 0) {
+            break;
+        }
+        len -= r;
+        pos += r;
+    }
+    
+    if(pos != w->pos) {
+        w->error = 1;
+        return 1;
+    }
+    
+    w->pos = 0;
+    return 0;
+}
+
+int writer_put(Writer *w, const char *s, size_t len) {
+    if(w->error) {
+        return w->error;
+    }
+    
+    // available bytes
+    size_t a = w->size - w->pos;
+    if(a == 0) {
+        if(writer_flush(w)) {
+            return 1;
+        }
+    }
+    
+    size_t cplen = len > a ? a : len; // number of bytes we can copy
+    memcpy(w->buffer+w->pos, s, cplen);
+    w->pos += cplen;
+    
+    if(cplen < len) {
+        // not all bytes copied -> call writer_put again
+        // the number of available bytes is 0 then, therefore flush is called
+        return writer_put(w, s + cplen, len - cplen);
+    } else {
+        return 0;
+    }
+}
+
+int writer_puts(Writer *w, sstr_t s) {
+    return writer_put(w, s.ptr, s.length);
+}
+
+int writer_putc(Writer *w, char c) {
+    if(w->pos == w->size) {
+        if(writer_flush(w)) {
+            return 1;
+        }
+    }
+    w->buffer[w->pos++] = c;
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/writer.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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.
+ */
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "../public/nsapi.h"
+#include <ucx/string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Writer {
+    SYS_NETFD fd;
+    char *buffer;
+    size_t size;
+    size_t pos;
+    int error;
+} Writer;
+
+void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len);
+
+int writer_flush(Writer *w);
+
+int writer_put(Writer *w, const char *s, size_t len);
+
+int writer_puts(Writer *w, sstr_t s);
+
+int writer_putc(Writer *w, char c);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WRITER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/multistatus.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,626 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 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 "../daemon/session.h"
+#include "../daemon/protocol.h"
+#include "../util/platform.h"
+
+#include <ucx/string.h>
+
+#include "multistatus.h"
+
+#include "operation.h"
+#include "xml.h"
+
+#define MULTISTATUS_BUFFER_LENGTH 2048
+
+Multistatus* multistatus_response(Session *sn, Request *rq) {
+    Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus));
+    if(!ms) {
+        return NULL;
+    }
+    ZERO(ms, sizeof(Multistatus)); 
+    ms->response.addresource = multistatus_addresource;
+    ms->sn = sn;
+    ms->rq = rq;
+    ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8);
+    ms->proppatch = FALSE;
+    if(!ms->namespaces) {
+        return NULL;
+    }
+    if(ucx_map_cstr_put(ms->namespaces, "D", webdav_dav_namespace())) {
+        return NULL;
+    }
+    return ms;
+}
+
+static int send_xml_root(Multistatus *ms, Writer *out) {
+    writer_puts(out, S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+                       "<D:multistatus"));
+    
+    // write the namespaces definitions
+    // key is the namespace prefix
+    // the map always contains the "DAV:" namespace with the prefix "D"
+    UcxMapIterator i = ucx_map_iterator(ms->namespaces);
+    WSNamespace *ns;
+    UCX_MAP_FOREACH(key, ns, i) {
+        writer_puts(out, S(" xmlns:"));
+        writer_put(out, key.data, key.len);
+        writer_puts(out, S("=\""));
+        writer_puts(out, sstr((char*)ns->href));
+        writer_puts(out, S("\""));
+    }
+    
+    writer_puts(out, S(">\n"));
+    
+    return out->error;
+}
+
+static void send_nsdef(WSNamespace *ns, Writer *out) {
+    writer_puts(out, S(" xmlns:"));
+    writer_puts(out, sstr((char*)ns->prefix));
+    writer_puts(out, S("=\""));
+    writer_puts(out, sstr((char*)ns->href));
+    writer_putc(out, '\"');
+}
+
+static int send_property(
+        Multistatus *ms,
+        WebdavProperty *property,
+        WebdavNSList *nsdef,
+        WSBool writeContent,
+        Writer *out)
+{
+    // write: "<prefix:name"
+    writer_putc(out, '<');
+    writer_puts(out, sstr((char*)property->namespace->prefix));
+    writer_putc(out, ':');
+    writer_puts(out, sstr((char*)property->name));
+    
+    // check if the namespace is already defined
+    WSBool need_nsdef = TRUE;
+    WSNamespace *ns = ucx_map_cstr_get(
+            ms->namespaces,
+            (char*)property->namespace->prefix);
+    if(ns && !strcmp(
+            (const char*)ns->href,
+            (const char*)property->namespace->href))
+    {
+        need_nsdef = FALSE; // prefix and href are the same, no need for nsdef
+    }
+    
+    // send definition for the element's namespace
+    if(need_nsdef) {
+        send_nsdef(property->namespace, out);
+    }
+    
+    // send additional namespace definitions required for the value
+    WebdavNSList *def = nsdef;
+    while(def) {
+        send_nsdef(def->namespace, out);
+        def = def->next;
+    }
+    
+    // send xml lang attribute
+    if(property->lang) {
+        writer_puts(out, S(" xml:lang=\""));
+        writer_puts(out, sstr((char*)property->lang));
+        writer_putc(out, '\"');
+    }
+    
+    // end property tag and write content
+    if(writeContent) {
+        writer_putc(out, '>');
+        
+        // content
+        switch(property->vtype) {
+            case WS_VALUE_NO_TYPE: break;
+            case WS_VALUE_XML_NODE: {
+                wsxml_write_nodes_without_nsdef(
+                        ms->sn->pool,
+                        out,
+                        property->value.node);
+                break;
+            }
+            case WS_VALUE_XML_DATA: {
+                // only write data, data->namespaces is already handled
+                writer_put(
+                        out,
+                        property->value.data->data,
+                        property->value.data->length);
+                break;
+            }
+            case WS_VALUE_TEXT: {
+                // asume the text is already escaped
+                writer_put(
+                        out,
+                        property->value.text.str,
+                        property->value.text.length);
+                break;
+            }
+        }
+        
+        // end tag
+        writer_puts(out, S("</"));
+        writer_puts(out, sstr((char*)property->namespace->prefix));
+        writer_putc(out, ':');
+        writer_puts(out, sstr((char*)property->name));
+        writer_putc(out, '>');
+    } else {
+        writer_puts(out, S("/>"));
+    }
+    
+    return out->error;
+}
+
+static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) {
+    writer_puts(out, S(" <D:response>\n"
+                       "  <D:href>"));
+    writer_puts(out, sstr(rp->resource.href));
+    writer_puts(out, S("</D:href>\n"));
+    
+    WSBool writeContent = ms->proppatch ? FALSE : TRUE;
+    
+    if(rp->plist_begin) {
+        writer_puts(out, S("  <D:propstat>\n"
+                           "   <D:prop>\n"));
+        // send properties
+        PropertyOkList *p = rp->plist_begin;
+        while(p) {
+            writer_puts(out, S("    "));
+            if(send_property(ms, p->property, p->nsdef, writeContent, out)) {
+                return out->error;
+            }
+            writer_puts(out, S("\n"));
+            p = p->next;
+        }
+        
+        writer_puts(out, S("   </D:prop>\n"
+                           "   <D:status>HTTP/1.1 200 OK</D:status>\n"
+                           "  </D:propstat>\n"));
+    }
+    
+    // send error properties
+    PropertyErrorList *error = rp->errors;
+    while(error) {
+        writer_puts(out, S("  <D:propstat>\n"
+                           "   <D:prop>\n"));
+        
+        WebdavPList *errprop = error->begin;
+        while(errprop) {
+            writer_puts(out, S("    "));
+            if(send_property(ms, errprop->property, NULL, FALSE, out)) {
+                return out->error;
+            }
+            writer_putc(out, '\n');
+            errprop = errprop->next;
+        }
+        
+        char statuscode[8];
+        int sclen = snprintf(statuscode, 8, "%d ", error->status);
+        if(sclen > 4) {
+            statuscode[0] = '5';
+            statuscode[1] = '0';
+            statuscode[2] = '0';
+            statuscode[3] = ' ';
+            sclen = 4;
+        }
+        writer_puts(out, S("   </D:prop>\n"
+                           "   <D:status>HTTP/1.1 "));
+        writer_put(out, statuscode, sclen);
+        const char *status_msg = protocol_status_message(error->status);
+        if(status_msg) {
+            writer_put(out, status_msg, strlen(status_msg));
+        } else {
+            writer_puts(out, S("Server Error"));
+        }
+        writer_puts(out, S("</D:status>\n"
+                           "  </D:propstat>\n"));
+        
+        
+        error = error->next;
+    }
+    
+    // end response tag
+    writer_puts(out, S(" </D:response>\n"));
+    
+    return out->error;
+}
+
+int multistatus_send(Multistatus *ms, SYS_NETFD net) {  
+    // start http response
+    protocol_status(ms->sn, ms->rq, 207, NULL);
+    protocol_start_response(ms->sn, ms->rq);
+    
+    char buffer[MULTISTATUS_BUFFER_LENGTH];
+    // create a writer, that flushes the buffer when it is filled
+    Writer writer;
+    Writer *out = &writer;
+    writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH);
+    
+    // send the xml root element with namespace defs
+    if(send_xml_root(ms, out)) {
+        return 1;
+    }
+    
+    // send response tags
+    MSResponse *response = ms->first;
+    while(response) {
+        if(send_response_tag(ms, response, out)) {
+            return 1;
+        }
+        response = response->next;
+    }
+    
+    // end multistatus
+    writer_puts(out, S("</D:multistatus>\n"));
+    
+    writer_flush(out);
+    
+    return 0;
+}
+
+WebdavResource * multistatus_addresource(
+        WebdavResponse *response,
+        const char *path)
+{
+    Multistatus *ms = (Multistatus*)response;
+    MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse));
+    if(!res) {
+        return NULL;
+    }
+    ZERO(res, sizeof(MSResponse));
+    
+    // set href
+    res->resource.href = pool_strdup(ms->sn->pool, path);
+    if(!res->resource.href) {
+        return NULL;
+    }
+    
+    res->resource.err = 0;
+    
+    // add resource funcs
+    res->resource.addproperty = msresponse_addproperty;
+    res->resource.close = msresponse_close;
+    
+    res->properties = ucx_map_new_a(session_get_allocator(ms->sn), 32);
+    if(!res->properties) {
+        return NULL;
+    }
+    
+    res->multistatus = ms;
+    res->errors = NULL;
+    res->resource.isclosed = 0;
+    res->closing = 0;
+    
+    // add new resource to the resource list
+    if(ms->current) {
+        // before adding a new resource, the current resource must be closed
+        if(!ms->current->resource.isclosed) {
+            msresponse_close((WebdavResource*)ms->current);
+        }
+        ms->current->next = res;
+    } else {
+        ms->first = res;
+    }
+    ms->current = res;
+    
+    return (WebdavResource*)res;
+}
+
+static int oklist_add(
+        pool_handle_t *pool,
+        PropertyOkList **begin,
+        PropertyOkList **end,
+        WebdavProperty *property,
+        WebdavNSList *nsdef)
+{
+    PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList));
+    if(!newelm) {
+        return 1;
+    }
+    newelm->property = property;
+    newelm->nsdef = nsdef;
+    newelm->next = NULL;
+    if(*end) {
+        (*end)->next = newelm;
+    } else {
+        *begin = newelm;
+    }
+    *end = newelm;
+    return 0;
+}
+
+int msresponse_addproperty(
+        WebdavResource *res,
+        WebdavProperty *property,
+        int status)
+{
+    MSResponse *response = (MSResponse*)res;
+    Session *sn = response->multistatus->sn;
+    if(response->resource.isclosed) {
+        log_ereport(
+                LOG_WARN,
+                "%s",
+                "webdav: cannot add property to closed response tag");
+        return 0;
+    }
+    
+    // some WebdavProperty checks to make sure nothing explodes  
+    if(!property->namespace || !property->namespace->href) {
+        // error: namespace is required
+        log_ereport(
+                LOG_FAILURE,
+                "%s",
+                "webdav: property '%s' has no namespace",
+                property->name);
+        return 1;
+    }
+    
+    // check if the property was already added to the resource
+    UcxAllocator *a = session_get_allocator(sn);
+    sstr_t key = sstrcat_a(
+            a,
+            3,
+            sstr((char*)property->namespace->href),
+            S("\0"),
+            sstr((char*)property->name));
+    if(ucx_map_sstr_get(response->properties, key)) {
+        a->free(a->pool, key.ptr);
+        return 0;
+    }
+    if(ucx_map_sstr_put(response->properties, key, property)) {
+        return 1; // OOM
+    }
+    a->free(a->pool, key.ptr);
+    
+    // list of namespace definitions for this property
+    WebdavNSList *nsdef_begin = NULL;
+    WebdavNSList *nsdef_end = NULL;
+    
+    // add namespace of this property to the namespace map
+    // the namespace map will be used for global namespace definitions
+    if(property->namespace->prefix) {
+        WSNamespace *ns = ucx_map_cstr_get(
+                response->multistatus->namespaces,
+                (const char*)property->namespace->prefix);
+        if(!ns) {
+            // prefix is not in use -> we can add the namespace to the ns map
+            int err = ucx_map_cstr_put(
+                    response->multistatus->namespaces,
+                    (const char*)property->namespace->prefix,
+                    property->namespace);
+            if(err) {
+                return 1; // OOM
+            }
+        } else if(
+                strcmp((const char*)property->namespace->href,
+                (const char*)ns->href))
+        {
+            // global namespace != local namespace
+            // therefore we need a namespace definition in this element
+            
+            // ns-prefix != property-prefix -> add ns to nsdef
+            if(webdav_nslist_add(
+                    sn->pool,
+                    &nsdef_begin,
+                    &nsdef_end,
+                    property->namespace))
+            {
+                return 1; // OOM
+            }
+        }
+    }
+    
+    if(response->multistatus->proppatch && response->errors) {
+        // in a proppatch request all operations must succeed
+        // if we have an error, the property update status code must be
+        // 424 Failed Dependency
+        status = 424;
+    }
+    
+    // error properties will be added to a separate list
+    if(status != 200) { 
+        return msresponse_addproperror(response, property, status);
+    }
+    
+    // add all namespaces used by this property to the nsdef list
+    WebdavNSList *nslist = NULL;
+    if(property->vtype == WS_VALUE_XML_NODE) {
+        // iterate over xml tree and collect all namespaces
+        int err = 0;
+        nslist = wsxml_get_required_namespaces(
+                response->multistatus->sn->pool,
+                property->value.node,
+                &err);
+        if(err) {
+            return 1; // OOM
+        }
+    } else if(property->vtype == WS_VALUE_XML_DATA) {
+        // xml data contains a list of all used namespaces
+        nslist = property->value.data->namespaces;
+    } // other value types don't contain xml namespaces
+    
+    while(nslist) {
+        // only add the namespace to the definitions list, if it isn't a
+        // property namespace, because the prop ns is already added
+        // to the element's def list or global definitions list
+        if(strcmp(
+                (const char*)nslist->namespace->prefix,
+                (const char*)property->namespace->prefix))
+        {
+            // ns-prefix != property-prefix -> add ns to nsdef
+            if(webdav_nslist_add(
+                    sn->pool,
+                    &nsdef_begin,
+                    &nsdef_end,
+                    nslist->namespace))
+            {
+                return 1; // OOM
+            }
+        }
+        nslist = nslist->next;
+    }
+    
+    // add property to the list
+    if(oklist_add(
+            sn->pool,
+            &response->plist_begin,
+            &response->plist_end,
+            property,
+            nsdef_begin))
+    {
+        return 1;
+    }
+    return 0;
+}
+
+int msresponse_addproperror(
+        MSResponse *response,
+        WebdavProperty *property,
+        int statuscode)
+{
+    pool_handle_t *pool = response->multistatus->sn->pool;
+    UcxAllocator *a = session_get_allocator(response->multistatus->sn);
+    
+    response->resource.err++;
+      
+    // MSResponse contains a list of properties for each status code
+    // at first find the list for this status code
+    PropertyErrorList *errlist = NULL;
+    PropertyErrorList *list = response->errors;
+    PropertyErrorList *last = NULL;
+    while(list) {
+        if(list->status == statuscode) {
+            errlist = list;
+            break;
+        }
+        last = list;
+        list = list->next;
+    }
+    
+    if(!errlist) {
+        // no list available for this statuscode
+        PropertyErrorList *newelm = pool_malloc(pool,
+                sizeof(PropertyErrorList));
+        if(!newelm) {
+            return 1;
+        }
+        newelm->begin = NULL;
+        newelm->end = NULL;
+        newelm->next = NULL;
+        newelm->status = statuscode;
+        
+        if(last) {
+            last->next = newelm;
+        } else {
+            response->errors = newelm;
+        }
+        errlist = newelm;
+    }
+    
+    // we have the list -> add the new element
+    if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) {
+        return 1;
+    }
+    return 0;
+}
+
+int msresponse_close(WebdavResource *res) {
+    MSResponse *response = (MSResponse*)res;
+    if(response->closing) {
+        return 0; // close already in progress
+    }
+    response->closing = TRUE;
+    Multistatus *ms = response->multistatus;
+    
+    int ret = REQ_PROCEED;
+    
+    // PROPFIND:
+    // response_close will execute propfind_do of all remaining backends
+    // after that we will have all available properties
+    WebdavOperation *op = ms->response.op;
+    if(op->response_close(op, res)) {
+        ret = REQ_ABORTED;
+    }
+    
+    // add missing properties with status code 404
+    UcxAllocator *a = session_get_allocator(ms->sn);
+    WebdavPList *pl = ms->response.op->reqprops;
+    while(pl) {
+        sstr_t key = sstrcat_a(
+            a,
+            3,
+            sstr((char*)pl->property->namespace->href),
+            S("\0"),
+            sstr((char*)pl->property->name));
+        if(!ucx_map_sstr_get(response->properties, key)) {
+            // property was not added to this response
+            if(ms->proppatch) {
+                if(msresponse_addproperror(response, pl->property, 424)) {
+                    ret = REQ_ABORTED;
+                    break;
+                }
+            } else {
+                if(msresponse_addproperror(response, pl->property, 404)) {
+                    ret = REQ_ABORTED;
+                    break;
+                }
+            }
+        }
+        
+        pl = pl->next;
+    }
+    
+    if(ms->proppatch && response->errors) {
+        // a proppatch response must succeed entirely
+        // if we have a single error prop, move all props with status 200
+        // to the error list
+        PropertyOkList *elm = response->plist_begin;
+        PropertyOkList *nextelm;
+        while(elm) {
+            if(msresponse_addproperror(response, elm->property, 424)) {
+                return 1;
+            }
+            nextelm = elm->next;
+            pool_free(response->multistatus->sn->pool, elm);
+            elm = nextelm;
+        }
+        response->plist_begin = NULL;
+        response->plist_end = NULL;
+    }
+    
+    // we don't need the properties anymore
+    ucx_map_free(response->properties);
+    
+    response->resource.isclosed = TRUE;
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/multistatus.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+#ifndef MULTISTATUS_H
+#define MULTISTATUS_H
+
+#include "../public/webdav.h"
+
+#include <ucx/map.h>
+#include <ucx/buffer.h>
+#include <libxml/tree.h>
+#include "../util/writer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Multistatus Multistatus;
+typedef struct MSResponse MSResponse;
+
+typedef struct PropertyOkList PropertyOkList;
+typedef struct PropertyErrorList PropertyErrorList;
+
+/*
+ * implements the WebdavResponse interface
+ */
+struct Multistatus {
+    WebdavResponse response;
+    Session *sn;
+    Request *rq;
+    MSResponse *first;
+    MSResponse *current;
+    
+    /*
+     * Map of document namespace definitions
+     * 
+     * key: (char*) namespace prefix
+     * value: WSNamespace*
+     */
+    UcxMap *namespaces;
+    
+    /*
+     * Is this a proppatch request?
+     * 
+     * In a proppatch response, when the first property with an error occurs,
+     * all already added properties will be set to 424 Failed Dependency.
+     */
+    WSBool proppatch;
+};
+
+/*
+ * implements the WebdavResource interface
+ */
+struct MSResponse {
+    WebdavResource resource;
+    Multistatus *multistatus;
+    
+    /*
+     * Contains all properties that were added to the response
+     * key: <href> null-byte <name>
+     * value: WebdavProperty*
+     */
+    UcxMap *properties;
+    
+    /*
+     * All properties with status != 200
+     */
+    PropertyErrorList *errors;
+    
+    /*
+     * All properties with status == 200
+     */
+    PropertyOkList *plist_begin;
+    PropertyOkList *plist_end;
+    
+    MSResponse *next;
+    WSBool closing;
+};
+
+struct PropertyOkList {
+    WebdavProperty *property;
+    WebdavNSList   *nsdef;
+    PropertyOkList *next;
+};
+
+struct PropertyErrorList {
+    /*
+     * next list for different status code
+     */
+    PropertyErrorList *next;
+    
+    /*
+     * property list for all properties with this status code
+     */
+    WebdavPList *begin;
+    
+    /*
+     * tail of the property list
+     */
+    WebdavPList *end;
+    
+    /*
+     * property response status code
+     */
+    int status;
+};
+
+Multistatus* multistatus_response(Session *sn, Request *rq);
+
+int multistatus_send(Multistatus *ms, SYS_NETFD out);
+
+WebdavResource * multistatus_addresource(
+        WebdavResponse *response,
+        const char *path);
+
+int msresponse_addproperty(
+        WebdavResource *res,
+        WebdavProperty *property,
+        int status);
+
+int msresponse_addproperror(
+        MSResponse *response,
+        WebdavProperty *property,
+        int statuscode);
+
+int msresponse_close(WebdavResource *res);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MULTISTATUS_H */
+
--- a/src/server/webdav/objs.mk	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/webdav/objs.mk	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 #
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 #
-# Copyright 2013 Olaf Wintermann. All rights reserved.
+# Copyright 2019 Olaf Wintermann. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
@@ -31,6 +31,12 @@
 DAV_OBJPRE = $(OBJ_DIR)$(DAV_SRC_DIR)
 
 DAVOBJ = webdav.o
+DAVOBJ += xml.o
+DAVOBJ += requestparser.o
+DAVOBJ += operation.o
+DAVOBJ += multistatus.o
+DAVOBJ += search.o
+DAVOBJ += versioning.o
 
 DAVOBJS = $(DAVOBJ:%=$(DAV_OBJPRE)%)
 DAVSOURCE = $(DAVOBJ:%.o=webdav/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/operation.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,752 @@
+/*
+ * 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 <errno.h>
+
+#include <ucx/list.h>
+
+#include "../daemon/session.h"
+#include "../util/pblock.h"
+
+#include "operation.h"
+
+#define WEBDAV_PATH_MAX 8192
+
+
+size_t webdav_num_backends(WebdavBackend *dav) {
+    size_t count = 0;
+    while(dav) {
+        count++;
+        dav = dav->next;
+    }
+    return count;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPFIND OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_propfind_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavPList *reqprops,
+        UcxList *requests,
+        WebdavResponse *response)
+{
+    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
+    ZERO(op, sizeof(WebdavOperation));
+    op->dav = dav;
+    op->sn = sn;
+    op->rq = rq;
+    op->reqprops = reqprops;
+    op->requests = requests;
+    op->response = response;
+    op->response_close = webdav_op_propfiond_close_resource;
+    response->op = op;
+    
+    return op;
+}
+
+int webdav_op_propfind_begin(
+        WebdavOperation *op,
+        const char *href,
+        VFS_DIR parent,
+        struct stat *s)
+{
+    // create WebdavResource object for requested resource
+    WebdavResource *resource = op->response->addresource(op->response, href);
+    if(!resource) {
+        return REQ_ABORTED;
+    }
+    
+    // store data that we need when the resource will be closed
+    op->stat = s;
+    op->parent = parent;
+    
+    // get first propfind object
+    WebdavPropfindRequest *propfind = op->requests->data;
+    
+    // execute propfind_do of the first backend for the first resource
+    int ret = REQ_PROCEED;
+    if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) {
+        ret = REQ_ABORTED;
+    } else {
+        // propfind_do successful, close resource if needed
+        // closing the resource will execute propfind_do of all remaining
+        // backends
+        if(!resource->isclosed) {
+            ret = resource->close(resource);
+        }
+    }
+    
+    return ret;
+}
+
+typedef struct PathSearchElm {
+    char   *href;
+    char   *path;
+    size_t hreflen;
+    size_t pathlen;
+} PathSearchElm;
+
+/*
+ * concats base + / + elm
+ * if baseinit is true, only elm is copied
+ */
+static int path_buf_concat(
+        pool_handle_t *pool,
+        char **buf,
+        size_t * restrict len,
+        WSBool * restrict baseinit,
+        const char *base,
+        size_t baselen,
+        const char *elm,
+        size_t elmlen)
+{
+    if(base[baselen-1] == '/') {
+        baselen--;
+    }
+    
+    size_t newlen = baselen + elmlen + 1;
+    if(newlen > WEBDAV_PATH_MAX) {
+        log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
+        return 1;
+    }
+    
+    // check if new path + terminator fits in the buffer
+    if(newlen + 1 > *len) {
+        *len = newlen + 128;
+        char *newbuf = pool_realloc(pool, *buf, newlen);
+        if(newbuf) {
+            log_ereport(LOG_FAILURE, "webdav: path memory allocation failed");
+            return 1;
+        }
+        *baseinit = FALSE;
+        
+        *buf = newbuf;
+    }
+    
+    // if baseinit is true, the parent is already in the buffer
+    // and we don't need to memcpy it again
+    if(!(*baseinit)) {
+        memcpy(*buf, base, baselen);
+        (*buf)[baselen] = '/';
+        *baseinit = TRUE;
+    }
+    // copy child and terminate string
+    memcpy((*buf) + baselen + 1, elm, elmlen);
+    (*buf)[newlen] = '\0';
+    
+    return 0;
+}
+
+static int propfind_child_cb(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *op)
+{
+    return webdav_op_propfind_begin(op, href, parent, s);
+}
+
+int webdav_op_propfind_children(
+        WebdavOperation *op,
+        VFSContext *vfs,
+        const char *href,
+        const char *path)
+{
+    WebdavPropfindRequest *request = op->requests->data;
+    return webdav_op_iterate_children(
+            vfs, request->depth, href, path, propfind_child_cb, op);
+}
+
+int webdav_op_propfiond_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource)
+{
+    // start with second backend and request, because
+    // the first one was already called by webdav_op_propfind_begin
+    WebdavBackend *dav = op->dav->next;
+    UcxList *request = op->requests->next;
+    
+    // call propfind_do of all remaining backends
+    int ret = REQ_PROCEED;
+    while(dav && request) {
+        if(dav->propfind_do(
+                request->data,
+                op->response,
+                op->parent,
+                resource,
+                op->stat))
+        {
+            ret = REQ_ABORTED;
+        }
+        
+        dav = dav->next;
+        request = request->next;
+    }
+    return ret;
+}
+
+/*
+ * Executes propfind_finish for each Backend
+ */
+int webdav_op_propfind_finish(WebdavOperation *op) {
+    WebdavBackend *dav = op->dav;
+    UcxList *requests = op->requests;
+    
+    int ret = REQ_PROCEED;
+    while(dav && requests) {
+        if(dav->propfind_finish(requests->data)) {
+            ret = REQ_ABORTED;
+        }
+        
+        dav = dav->next;
+        requests = requests->next;
+    }
+    return ret;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPPATCH OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_proppatch_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavProppatchRequest *proppatch,
+        WebdavResponse *response)
+{
+    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
+    ZERO(op, sizeof(WebdavOperation));
+    op->dav = dav;
+    op->sn = sn;
+    op->rq = rq;
+    op->reqprops = NULL;
+    op->response = response;
+    op->proppatch = proppatch;
+    op->response_close = webdav_op_proppatch_close_resource;
+    response->op = op;
+    
+    return op;
+}
+
+
+
+int webdav_op_proppatch(
+        WebdavOperation *op,
+        const char *href,
+        const char *path)
+{
+    WebdavProppatchRequest *orig_request = op->proppatch;
+    UcxAllocator *a = session_get_allocator(op->sn);
+    
+    // create WebdavResource object for the requested resource
+    WebdavResource *resource = op->response->addresource(op->response, href);
+    if(!resource) {
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *ctx = NULL;
+    VFSFile *file = NULL;
+    
+    // requests for each backends
+    WebdavProppatchRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavProppatchRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavPList *prev_set = orig_request->set;
+    WebdavPList *prev_remove = orig_request->remove;
+    size_t set_count = orig_request->setcount;
+    size_t remove_count = orig_request->removecount;
+    
+    int ret = REQ_PROCEED;
+    
+    // iterate backends and execute proppatch_do
+    WebdavBackend *dav = op->dav;
+    size_t numrequests = 0;
+    while(dav) {
+        WebdavPList *set = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_set,
+                &set_count);
+        WebdavPList *remove = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_remove,
+                &remove_count);
+        if((prev_set && !set) || (prev_remove && !remove)) {
+            // clone failed, OOM
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // create new WebdavProppatchRequest object for this backend
+        WebdavProppatchRequest *req = pool_malloc(
+                op->sn->pool,
+                sizeof(WebdavProppatchRequest));
+        memcpy(req, orig_request, sizeof(WebdavProppatchRequest));
+        req->set = set;
+        req->setcount = set_count;
+        req->remove = remove;
+        req->removecount = remove_count;
+        req->userdata = NULL;
+        
+        // check if we need to open the file because the backend want's it
+        if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS)
+                         == WS_WEBDAV_PROPPATCH_USE_VFS)
+        {
+            ctx = vfs_request_context(op->sn, op->rq);
+            if(!ctx) {
+                ret = REQ_ABORTED;
+                break;
+            }
+            
+            file = vfs_open(ctx, path, O_RDONLY);
+            if(!file) {
+                protocol_status(
+                        op->sn,
+                        op->rq,
+                        util_errno2status(ctx->vfs_errno),
+                        NULL);
+                ret = REQ_ABORTED;
+            }
+        }
+        
+        // execute proppatch_do
+        if(dav->proppatch_do(req, resource, file, &set, &remove)) {
+            // return later, because we need do execute proppatch_finish
+            // for all successfully called backends
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // proppatch_do should remove all handled props from set and remove
+        // in the next iteration, the backend must use these reduced lists
+        prev_set = set;
+        prev_remove = remove;
+        
+        requests[numrequests++] = req;
+        
+        // continue with next backend
+        dav = dav->next;
+    }
+    
+    WSBool commit = FALSE;
+    if(ret == REQ_PROCEED && resource->err == 0) {
+        // no errors, no properties with errors -> save the changes
+        commit = TRUE; 
+    }
+    
+    // call proppatch_finish for each successfully called proppatch_do
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < numrequests) {
+        if(dav->proppatch_finish(requests[i], resource, file, commit)) {
+            ret = REQ_ABORTED;
+        }
+        i++;
+        dav = dav->next;
+    }
+    
+    if(file) {
+        vfs_close(file);
+    }
+    
+    if(resource->close(resource)) {
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+int webdav_op_proppatch_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource)
+{
+    return 0; // NOP
+}
+
+
+/****************************************************************************
+ * 
+ *                             VFS OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition)
+{
+    WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation));
+    if(!op) {
+        return NULL;
+    }
+    ZERO(op, sizeof(WebdavVFSOperation));
+    
+    op->sn = sn;
+    op->rq = rq;
+    op->dav = dav;
+    op->stat = NULL;
+    op->stat_errno = 0;
+    
+    // create VFS context
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        pool_free(sn->pool, op);
+        return NULL;
+    }
+    op->vfs = vfs;
+    
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    op->path = path;  
+    
+    return op;
+}
+
+WebdavVFSOperation webdav_vfs_sub_op(
+        WebdavVFSOperation *op,
+        char *path,
+        struct stat *s)
+{
+    WebdavVFSOperation sub;
+    sub.dav = op->dav;
+    sub.path = path;
+    sub.sn = op->sn;
+    sub.vfs = op->vfs;
+    sub.path = path;
+    sub.stat = s;
+    sub.stat_errno = 0;
+    return sub;
+}
+
+int webdav_op_iterate_children(
+        VFSContext *vfs,
+        int depth,
+        const char *href,
+        const char *path,
+        vfs_op_child_func func,
+        void *userdata)
+{
+    UcxAllocator *a = session_get_allocator(vfs->sn);
+    pool_handle_t *pool = vfs->sn->pool;
+    
+    PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm));
+    start_elm->href = pool_strdup(pool, href ? href : "");
+    start_elm->path = pool_strdup(pool, path ? path : "");
+    start_elm->hreflen = href ? strlen(href) : 0;
+    start_elm->pathlen = path ? strlen(path) : 0;
+    
+    UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm);
+    UcxList *stack_end = stack;
+    if(!stack) {
+        return 1;
+    }
+    
+    // reusable buffer for full child path and href
+    char *newpath = pool_malloc(pool, 256);
+    size_t newpathlen = 256;
+    
+    char *newhref = pool_malloc(pool, 256);
+    size_t newhreflen = 256;
+    
+    int err = 0;
+    while(stack && !err) {
+        PathSearchElm *cur_elm = stack->data;
+        
+        // when newpath is initialized with the parent path
+        // set path_buf_init to TRUE
+        WSBool href_buf_init = FALSE;
+        WSBool path_buf_init = FALSE;
+        
+        VFS_DIR dir = vfs_opendir(vfs, cur_elm->path);
+        if(!dir) {
+            log_ereport(
+                    LOG_FAILURE,
+                    "webdav: propfind: cannot open directory %d",
+                    vfs->vfs_errno);
+            err = 1;
+            break;
+        }
+        
+        VFS_ENTRY f;
+        while(vfs_readdir_stat(dir, &f)) {
+            if(f.stat_errno != 0) {
+                continue;
+            }
+            
+            size_t child_len = strlen(f.name);
+            
+            // create new path and href for the child
+            if(path_buf_concat(
+                    pool,
+                    &newhref,
+                    &newhreflen,
+                    &href_buf_init,
+                    cur_elm->href,
+                    cur_elm->hreflen,
+                    f.name,
+                    child_len))
+            {
+                err = 1;
+                break;
+            }
+            if(path_buf_concat(
+                    pool,
+                    &newpath,
+                    &newpathlen,
+                    &path_buf_init,
+                    cur_elm->path,
+                    cur_elm->pathlen,
+                    f.name,
+                    child_len))
+            {
+                err = 1;
+                break;
+            }
+            size_t childhreflen = cur_elm->hreflen + 1 + child_len;
+            size_t childpathlen = cur_elm->pathlen + 1 + child_len;
+            
+            // execute callback func for this file
+            if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) {
+                err = 1;
+                break;
+            }
+            
+            // depth of -1 means infinity
+            if(depth == -1 && S_ISDIR(f.stat.st_mode)) {
+                char *hrefcp = pool_malloc(pool, childhreflen + 1);
+                memcpy(hrefcp, newhref, childhreflen + 1);
+                hrefcp[childhreflen] = '\0';
+                
+                char *pathcp = pool_malloc(pool, childpathlen + 1);
+                memcpy(pathcp, newpath, childpathlen + 1);
+                pathcp[childpathlen] = '\0';
+                
+                PathSearchElm *new_elm = pool_malloc(pool,
+                                            sizeof(PathSearchElm));
+                new_elm->href = hrefcp;
+                new_elm->path = pathcp;
+                new_elm->hreflen = childhreflen;
+                new_elm->pathlen = childpathlen;
+                
+                // add the new_elm to the stack
+                // stack_end is always not NULL here, because we remove
+                // the first stack element at the end of the loop
+                UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm);
+                if(!newlistelm) {
+                    err = 1;
+                    break;
+                }
+                stack_end = newlistelm;
+            }
+        }
+        
+        vfs_closedir(dir);
+        
+        pool_free(pool, cur_elm->path);
+        pool_free(pool, cur_elm->href);
+        pool_free(pool, cur_elm);
+        
+        stack = ucx_list_remove_a(a, stack, stack);
+    }
+    
+    // in case of an error, we have to free all remaining stack elements
+    UCX_FOREACH(elm, stack) {
+        char *data = elm->data;
+        if(data != path) {
+            pool_free(pool, data);
+        }
+    }
+    
+    return err;
+}
+
+
+int webdav_vfs_stat(WebdavVFSOperation *op) {
+    if(op->stat) {
+        return 0;
+    } else if(op->stat_errno != 0) {
+        // previous stat failed
+        return 1;
+    }
+    
+    // stat file
+    struct stat sbuf;
+    int ret = vfs_stat(op->vfs, op->path, &sbuf);
+    if(!ret) {
+        // save result in op->stat and in s
+        op->stat = pool_malloc(op->sn->pool, sizeof(struct stat));
+        if(op->stat) {
+            memcpy(op->stat, &sbuf, sizeof(struct stat));
+        } else {
+            ret = 1;
+            op->stat_errno = ENOMEM;
+        }
+    } else {
+        op->stat_errno = errno;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) {
+    WSBool exec_vfs = TRUE;
+    
+    // requests for each backends
+    WebdavVFSRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavVFSRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = REQ_PROCEED;
+    
+    // call opt_* func for each backend
+    WebdavBackend *dav = op->dav;
+    int called_backends = 0;
+    while(dav) {    
+        WebdavVFSRequest *request = NULL;
+        
+        // get vfs operation functions
+        vfs_op_func        op_func        = NULL;
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_func        = dav->opt_mkcol;
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_func        = dav->opt_delete;
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_func || op_finish_func) {
+            // we need a request object
+            request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest));
+            if(!request) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+            request->sn = op->sn;
+            request->rq = op->rq;
+            request->path = op->path;
+            request->userdata = NULL;
+            
+            requests[called_backends] = request;
+        }
+        
+        // exec backend func for this operation
+        // this will set 'done' to TRUE, if no further vfs call is required
+        WSBool done = FALSE;
+        called_backends++;
+        if(op_func) {
+            if(op_func(request, &done)) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+        }
+        if(done) {
+            exec_vfs = FALSE;
+        }
+        
+        dav = dav->next;
+    }
+    
+    // if needed, call vfs func for this operation
+    if(exec_vfs) {
+        int r = 0;
+        if(type == WEBDAV_VFS_MKDIR) {
+            r = vfs_mkdir(op->vfs, op->path);
+        } else if(type == WEBDAV_VFS_DELETE) {
+            r = webdav_vfs_unlink(op);
+        }
+        
+        if(r) {
+            ret = REQ_ABORTED;
+        }
+    }
+    
+    WSBool success = ret == REQ_PROCEED ? TRUE : FALSE;
+    
+    // finish mkcol (cleanup) by calling opt_*_finish for each backend
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < called_backends) {
+        // get vfs operation functions
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_finish_func) {
+            if(op_finish_func(requests[i], success)) {
+                ret = REQ_ABORTED; // don't exit loop
+            }
+        }
+        
+        dav = dav->next;
+        i++;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_unlink(WebdavVFSOperation *op) {
+    // stat the file first, to check if the file is a directory
+    if(webdav_vfs_stat(op)) {
+        return 1; // error
+    } else {
+        if(!S_ISDIR(op->stat->st_mode)) {
+            return vfs_unlink(op->vfs, op->path);
+        } else {
+            return vfs_rmdir(op->vfs, op->path);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/operation.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#ifndef OPERATION_H
+#define OPERATION_H
+
+#include "../public/webdav.h"
+#include <ucx/list.h>
+#include <ucx/map.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int(*response_close_func)(WebdavOperation *, WebdavResource *);
+
+typedef struct WebdavVFSOperation WebdavVFSOperation;
+
+typedef struct WebdavCopy         WebdavCopy;
+typedef struct CopyResource       CopyResource;
+    
+struct WebdavOperation {
+    WebdavBackend          *dav;
+    Request                *rq;
+    Session                *sn;
+    
+    WebdavProppatchRequest *proppatch;    /* proppatch request or NULL */
+    WebdavPList            *reqprops;     /* requested properties */
+    UcxList                *requests;     /* backend specific request objects */
+    
+    WebdavResponse         *response;
+    
+    response_close_func    response_close;
+    
+    VFS_DIR                parent;        /* current directory */
+    struct stat            *stat;         /* current stat object */
+};
+
+struct WebdavVFSOperation {
+    WebdavBackend          *dav;
+    Request                *rq;
+    Session                *sn;
+    
+    VFSContext             *vfs;
+    
+    char                   *path;
+    struct stat            *stat;
+    int                    stat_errno;
+};
+
+struct WebdavCopy {
+    WebdavResponse response;
+    Session *sn;
+    Request *rq;
+    CopyResource *current;
+    
+    char *src_href;
+    char *src_path;
+    char *dst_href;
+    char *dst_path;
+};
+
+struct CopyResource {
+    WebdavResource resource;
+    UcxMap *properties;
+};
+
+enum WebdavVFSOpType {
+    WEBDAV_VFS_MKDIR = 0,
+    WEBDAV_VFS_DELETE
+};
+
+typedef enum WebdavVFSOpType WebdavVFSOpType;
+
+typedef int(*vfs_op_func)(WebdavVFSRequest *, WSBool *);
+typedef int(*vfs_op_finish_func)(WebdavVFSRequest *, WSBool);
+
+typedef int(*vfs_op_child_func)(
+        VFSContext *,
+        const char *,                     /* href */
+        const char *,                     /* path */
+        VFSDir *,                         /* parent dir */
+        struct stat *,                    /* child stat */
+        void *);                          /* user data */
+
+/*
+ * counts the number of backends
+ */
+size_t webdav_num_backends(WebdavBackend *dav);
+
+WebdavOperation* webdav_create_propfind_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavPList *reqprops,
+        UcxList *requests,
+        WebdavResponse *response);
+
+int webdav_op_propfind_begin(
+        WebdavOperation *op,
+        const char *href,
+        VFS_DIR parent,
+        struct stat *s);
+
+int webdav_op_propfind_children(
+        WebdavOperation *op,
+        VFSContext *vfs,
+        const char *href,
+        const char *path);
+
+int webdav_op_propfiond_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource);
+
+int webdav_op_propfind_finish(WebdavOperation *op);
+
+WebdavOperation* webdav_create_proppatch_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavProppatchRequest *proppatch,
+        WebdavResponse *response);
+
+int webdav_op_proppatch(
+        WebdavOperation *op,
+        const char *href,
+        const char *path);
+
+int webdav_op_proppatch_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource);
+
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition);
+
+WebdavVFSOperation webdav_vfs_sub_op(
+        WebdavVFSOperation *op,
+        char *path,
+        struct stat *s);
+
+int webdav_op_iterate_children(
+        VFSContext *vfs,
+        int depth,
+        const char *href,
+        const char *path,
+        vfs_op_child_func func,
+        void *userdata);
+
+int webdav_vfs_stat(WebdavVFSOperation *op);
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type);
+
+int webdav_vfs_unlink(WebdavVFSOperation *op);
+
+
+WebdavCopy* webdav_copy_create(
+        Session *sn,
+        Request *rq,
+        VFSContext *vfs,
+        char *from_href,
+        char *from_path,
+        char *to_href,
+        char *to_path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OPERATION_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/requestparser.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,504 @@
+/*
+ * 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 <ucx/string.h>
+#include <ucx/utils.h>
+#include <ucx/map.h>
+
+#include "requestparser.h"
+
+#define xstreq(a, b) !strcmp((const char*)a, (const char*)b)
+
+void proplist_free(pool_handle_t *pool, WebdavPList *list) {
+    while(list) {
+        WebdavPList *next = list->next;
+        pool_free(pool, list);
+        list = next;
+    }
+}
+
+WebdavProperty* prop_create(
+        pool_handle_t *pool,
+        WSNamespace *ns,
+        const char *name)
+{
+    WebdavProperty *prop = pool_malloc(pool, sizeof(WebdavProperty));
+    memset(prop, 0, sizeof(WebdavProperty));
+    prop->lang = NULL;
+    prop->name = (char*)name;
+    prop->namespace = ns;
+    return prop;
+}
+
+static UcxKey propkey(const char *ns, const char *name) {
+    UcxKey key;
+    sstr_t data = ucx_sprintf("%s\n%s", name, ns);
+    key.data = data.ptr;
+    key.len = data.length;
+    key.hash = ucx_hash(data.ptr, data.length);
+    return key;
+}
+
+static int parse_prop(
+        Session *sn,
+        xmlNode *node,
+        UcxMap *propmap,
+        WebdavPList **plist_begin,
+        WebdavPList **plist_end,
+        size_t *propcount,
+        int proppatch,
+        int *error)
+{
+    xmlNode *pnode = node->children;
+    for(;pnode;pnode=pnode->next) {
+        if(pnode->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+        
+        const char* ns = (const char*)pnode->ns->href;
+        const char* name = (const char*)pnode->name;
+        
+        // check for prop duplicates
+        UcxKey k = propkey((const char*)ns, (const char*)name);
+        if(!k.data) {
+            *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            return 1;
+        }
+        void *c = ucx_map_get(propmap, k);
+        if(!c) {
+            if(ucx_map_put(propmap, k, (void*)1)) {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+            
+            // no duplicate
+            // create property elment and add it to the list
+            WebdavProperty *prop = prop_create(sn->pool, pnode->ns, name);
+            if(proppatch) {
+                prop->value.node = pnode->children;
+                prop->vtype = WS_VALUE_XML_NODE;
+            }
+            if(prop) {
+                if(webdav_plist_add(sn->pool, plist_begin, plist_end, prop)) {
+                    *error = proppatch ?
+                        PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+                }
+                (*propcount)++;
+            } else {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+        } else if(proppatch) {
+            *error = PROPPATCH_PARSER_DUPLICATE;
+        }
+        
+        free(k.data);
+        if(*error) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+WebdavPropfindRequest* propfind_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPFIND_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    WSBool allprop = FALSE;
+    WSBool propname = FALSE;
+    WebdavPList *plist_begin = NULL;
+    WebdavPList *plist_end = NULL;
+    size_t propcount = 0;
+    int depth = webdav_getdepth(rq);
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is DAV:propfind
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "propfind")))
+    {
+        *error = PROPFIND_PARSER_NO_PROPFIND;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    UcxMap *propmap = ucx_map_new(32);
+    if(!propmap) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->ns->href, "DAV:") && !allprop && !propname) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "prop")) {
+                    ret = parse_prop(
+                            sn,
+                            node,
+                            propmap,
+                            &plist_begin,
+                            &plist_end,
+                            &propcount,
+                            0, // proppatch = false
+                            error);
+                } else if(xstreq(node->name, "allprop")) {
+                    allprop = TRUE;
+                } else if(xstreq(node->name, "propname")) {
+                    propname = TRUE;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // no allocated content must be freed
+    
+    if(ret) {
+        // parse_prop failed
+        // in this case, error is already set
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    if(!allprop && !propname && propcount == 0) {
+        *error = PROPFIND_PARSER_NO_PROPERTIES;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPropfindRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavPropfindRequest));
+    if(!request) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->properties = NULL;
+    request->propcount = 0;
+    request->depth = depth;
+    request->doc = doc;
+    if(allprop) {
+        request->allprop = TRUE;
+        request->propname = FALSE; // we cannot have allprop and propname
+    } else if(propname) {
+        request->allprop = FALSE;
+        request->propname = TRUE;
+    } else {
+        request->allprop = FALSE;
+        request->propname = FALSE;
+        request->properties = plist_begin;
+        request->propcount = propcount;
+    }
+    
+    if(!request->properties && plist_begin) {
+        proplist_free(sn->pool, plist_begin);
+    }
+    return request;
+}
+
+WebdavProppatchRequest* proppatch_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    return webdav_parse_set(
+            sn, rq, buf, buflen, "DAV:", "propertyupdate", TRUE, error);
+}
+
+static xmlNode* find_child(xmlNode *node, const char *name) {
+    xmlNode *c = node->children;
+    while(c) {
+        if(c->ns) {
+            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, name)) {
+                return c;
+            }
+        }
+        c = c->next;
+    }
+    return NULL;
+}
+
+WebdavProppatchRequest* webdav_parse_set(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        const char *rootns,
+        const char *rootname,
+        WSBool allowremove,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, rootns)
+            && xstreq(root->name, rootname)))
+    {
+        *error = PROPPATCH_PARSER_NO_PROPERTYUPDATE;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    UcxMap *propmap = ucx_map_new(32); // map for duplicate checking
+    if(!propmap) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPList *set_begin = NULL;
+    WebdavPList *set_end = NULL;
+    WebdavPList *remove_begin = NULL;
+    WebdavPList *remove_end = NULL;
+    size_t set_count = 0;
+    size_t remove_count = 0;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(node->ns && xstreq(node->ns->href, "DAV:")) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "set")) {
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &set_begin,
+                            &set_end,
+                            &set_count,
+                            TRUE, // proppatch = true
+                            error);
+                } else if(xstreq(node->name, "remove")) {
+                    if(!allowremove) {
+                        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+                        ret = 1;
+                        break;
+                    }
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &remove_begin,
+                            &remove_end,
+                            &remove_count,
+                            TRUE, // proppatch = true
+                            error);
+                }
+                    
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // allocated content must not be freed
+    
+    if(set_count + remove_count == 0) {
+        *error = PROPPATCH_PARSER_NO_PROPERTIES;
+        ret = 1;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavProppatchRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavProppatchRequest));
+    if(!request) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    request->set = set_begin;
+    request->setcount = set_count;
+    request->remove = remove_begin;
+    request->removecount = remove_count;
+    return request;
+}
+
+WebdavLockRequest* lock_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = LOCK_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "lockinfo")))
+    {
+        *error = LOCK_PARSER_NO_LOCKINFO;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockScope lockscope = WEBDAV_LOCK_SCOPE_UNKNOWN;
+    WebdavLockType locktype = WEBDAV_LOCK_TYPE_UNKNOWN;
+    WSXmlNode *owner = NULL;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(
+                node->type == XML_ELEMENT_NODE
+                && node->ns
+                && xstreq(node->ns->href, "DAV:"))
+        {
+            char *name = (char*)node->name;
+            if(xstreq(name, "lockscope")) {
+                xmlNode *s = node->children;
+                while(s) {
+                    if(
+                            s->type == XML_ELEMENT_NODE
+                            && s->ns
+                            && xstreq(s->ns->href, "DAV:"))
+                    {
+                        if(xstreq(s->name, "exclusive")) {
+                            lockscope = WEBDAV_LOCK_EXCLUSIVE;
+                        } else if(xstreq(s->name, "shared")) {
+                            lockscope = WEBDAV_LOCK_SHARED;
+                        } else {
+                            // don't ignore unknown lockscope
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    s = s->next;
+                }
+            } else if(xstreq(name, "locktype")) {
+                xmlNode *t = node->children;
+                while(t) {
+                    if(
+                            t->type == XML_ELEMENT_NODE
+                            && t->ns
+                            && xstreq(t->ns->href, "DAV:"))
+                    {
+                        if(xstreq(t->name, "write")) {
+                            locktype = WEBDAV_LOCK_WRITE;
+                        } else {
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    t = t->next;
+                }
+            } else if(xstreq(name, "owner")) {
+                owner = node->children;
+            }
+        }
+        node = node->next;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavLockRequest));
+    if(!request) {
+        *error = LOCK_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    
+    if(locktype == WEBDAV_LOCK_TYPE_UNKNOWN) {
+        locktype = WEBDAV_LOCK_WRITE;
+    }
+    if(lockscope == WEBDAV_LOCK_SCOPE_UNKNOWN) {
+        lockscope = WEBDAV_LOCK_EXCLUSIVE;
+    }
+    request->scope = lockscope;
+    request->type = locktype;
+    request->owner = owner;
+    return request;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/requestparser.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#ifndef REQUESTPARSER_H
+#define REQUESTPARSER_H
+
+#include "../public/webdav.h"
+
+#include <libxml/tree.h>
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define PROPFIND_PARSER_OK                   0
+#define PROPFIND_PARSER_NO_PROPFIND          1
+#define PROPFIND_PARSER_NO_PROPERTIES        2
+#define PROPFIND_PARSER_INVALID_REQUEST      3
+#define PROPFIND_PARSER_OOM                  4
+#define PROPFIND_PARSER_ERROR                5
+    
+#define PROPPATCH_PARSER_OK                  0
+#define PROPPATCH_PARSER_NO_PROPERTYUPDATE   1
+#define PROPPATCH_PARSER_NO_PROPERTIES       2
+#define PROPPATCH_PARSER_INVALID_REQUEST     3
+#define PROPPATCH_PARSER_DUPLICATE           4
+#define PROPPATCH_PARSER_OOM                 5
+#define PROPPATCH_PARSER_ERROR               6
+
+#define LOCK_PARSER_OK                       0
+#define LOCK_PARSER_NO_LOCKINFO              1
+#define LOCK_PARSER_INVALID_REQUEST          2
+#define LOCK_PARSER_UNKNOWN_ELEMENT          3
+#define LOCK_PARSER_OOM                      4
+#define LOCK_PARSER_ERROR                    5
+
+
+WebdavProperty* prop_create(
+        pool_handle_t *pool,
+        WSNamespace *ns,
+        const char *name);
+    
+WebdavPropfindRequest* propfind_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+WebdavProppatchRequest* proppatch_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+WebdavProppatchRequest* webdav_parse_set(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        const char *rootns,
+        const char *rootname,
+        WSBool allowremove,
+        int *error);
+
+WebdavLockRequest* lock_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* REQUESTPARSER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/search.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,33 @@
+/*
+ * 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 "search.h"
+
+int webdav_search (pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/search.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef SEARCH_H
+#define SEARCH_H
+
+#include "../public/webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int webdav_search (pblock *pb, Session *sn, Request *rq);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SEARCH_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/versioning.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,61 @@
+/*
+ * 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 "versioning.h"
+
+int webdav_version_control(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_checkout(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_checkin(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_uncheckout(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_update(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_label(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_merge(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/versioning.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef VERSIONING_H
+#define VERSIONING_H
+
+#include "../public/webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int webdav_version_control(pblock *pb, Session *sn, Request *rq);
+int webdav_checkout(pblock *pb, Session *sn, Request *rq);
+int webdav_checkin(pblock *pb, Session *sn, Request *rq);
+int webdav_uncheckout(pblock *pb, Session *sn, Request *rq);
+int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq);
+int webdav_update(pblock *pb, Session *sn, Request *rq);
+int webdav_label(pblock *pb, Session *sn, Request *rq);
+int webdav_merge(pblock *pb, Session *sn, Request *rq);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VERSIONING_H */
+
--- a/src/server/webdav/webdav.c	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/webdav/webdav.c	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,1080 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <ucx/buffer.h>
+#include <ucx/list.h>
+
 #include "webdav.h"
 
+#include "search.h"
+#include "versioning.h"
+#include "multistatus.h"
+#include "requestparser.h"
+#include "operation.h"
 
+#include "../util/pblock.h"
+#include "../util/util.h"
+#include "../daemon/session.h"
+#include "../daemon/http.h"
+#include "../daemon/protocol.h"
+#include "../daemon/vfs.h"
+
+static UcxMap *method_handler_map;
+
+static WebdavBackend default_backend;
+
+static WSNamespace dav_namespace;
+
+static WebdavProperty dav_resourcetype_empty;
+static WebdavProperty dav_resourcetype_collection;
+static WSXmlData dav_resourcetype_collection_value;
+
+#define WEBDAV_RESOURCE_TYPE_COLLECTION "<D:collection/>"
+
+static void init_default_backend(void) {
+    memset(&default_backend, 0, sizeof(WebdavBackend));
+    default_backend.propfind_init = default_propfind_init;
+    default_backend.propfind_do = default_propfind_do;
+    default_backend.propfind_finish = default_propfind_finish;
+    default_backend.proppatch_do = default_proppatch_do;
+    default_backend.proppatch_finish = default_proppatch_finish;
+    default_backend.opt_mkcol = NULL;
+    default_backend.opt_delete = NULL;
+    default_backend.settings = WS_WEBDAV_PROPFIND_USE_VFS;
+}
+
+int webdav_init(pblock *pb, Session *sn, Request *rq) {
+    init_default_backend();
+    
+    method_handler_map = ucx_map_new(64);
+    
+    ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options);
+    ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind);
+    ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch);
+    ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol);
+    ucx_map_cstr_put(method_handler_map, "POST", webdav_post);
+    ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete);
+    ucx_map_cstr_put(method_handler_map, "PUT", webdav_put);
+    ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy);
+    ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move);
+    ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock);
+    ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock);
+    ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report);
+    ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl);
+    
+    ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search);
+    
+    ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control);
+    ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout);
+    ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin);
+    ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout);
+    ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace);
+    ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update);
+    ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label);
+    ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge);
+    
+    dav_namespace.href = (xmlChar*)"DAV:";
+    dav_namespace.prefix = (xmlChar*)"D";
+    
+    dav_resourcetype_empty.namespace = &dav_namespace;
+    dav_resourcetype_empty.name = "resourcetype";
+    
+    dav_resourcetype_collection.namespace = &dav_namespace;
+    dav_resourcetype_collection.name = "resourcetype";
+    dav_resourcetype_collection.value.data = &dav_resourcetype_collection_value;
+    dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA;
+    dav_resourcetype_collection_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION;
+    dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1;
+    
+    return REQ_PROCEED;
+}
+
+
+int webdav_service(pblock *pb, Session *sn, Request *rq) {
+    if(!method_handler_map) {
+        log_ereport(LOG_FAILURE, "WebDAV module not initialized");
+        protocol_status(sn, rq, 500, NULL);
+        return REQ_ABORTED;
+    }
+    char *method = pblock_findkeyval(pb_key_method, rq->reqpb);
+    
+    FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method);
+    if(!saf) {
+        return REQ_NOACTION;
+    }
+    
+    return saf(pb, sn, rq);
+}
+
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq) {
+    if(!sn->inbuf) {
+        //request body required, set http response code
+        protocol_status(sn, rq, 400, NULL);
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(
+            NULL,
+            sn->inbuf->maxsize,
+            UCX_BUFFER_AUTOEXTEND);
+    if(!buf) {
+        protocol_status(sn, rq, 500, NULL);
+        return NULL;
+    }
+    
+    char in[2048];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        if(ucx_buffer_write(in, 1, r, buf) != r) {
+            protocol_status(sn, rq, 500, NULL);
+            ucx_buffer_free(buf);
+            return NULL;
+        }
+    }
+    
+    return buf;
+}
+
+int webdav_options(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    UcxAllocator *a = session_get_allocator(sn);
+    
+    int error = 0;
+    WebdavPropfindRequest *propfind = propfind_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!propfind) {
+        switch(error) {
+            // TODO: handle all errors
+            default: return REQ_ABORTED;
+        }
+    }
+    
+    WebdavBackend *dav =  rq->davCollection ?
+                              rq->davCollection : &default_backend;
+    
+    
+    // requested uri and path
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    // The multistatus response object contains responses for all
+    // requested resources. At the end the Multistatus object will be
+    // serialized to xml
+    Multistatus *ms = multistatus_response(sn, rq);
+    if(!ms) {
+        return REQ_ABORTED;
+    }
+    
+    
+    int ret = webdav_propfind_do(dav, propfind, (WebdavResponse*)ms, NULL, path, uri);
+    
+    // if propfind was successful, send the result to the client
+    if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) {
+        ret = REQ_ABORTED;
+        // TODO: log error
+    } else {
+        // TODO: error response
+    }
+    
+    // cleanup
+    if(propfind->doc) {
+        xmlFreeDoc(propfind->doc);
+    }
+    
+    return ret;
+}
+
+/*
+ * Initializes Backend Chain
+ * 
+ * Calls propfind_init of each Backend and generates a list of custom
+ * WebdavPropfindRequest objects for each backend
+ */
+int webdav_propfind_init(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        UcxList **out_req)
+{   
+    pool_handle_t *pool = propfind->sn->pool;
+    UcxAllocator *a = session_get_allocator(propfind->sn);
+    
+    // list of individual WebdavPropfindRequest objects for each Backend
+    UcxList *requestObjects = NULL;
+    
+    // new properties after init, start with clone of original plist
+    WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties);
+    size_t newPropCount = propfind->propcount;
+    
+    // Call propfind_init for each Backend
+    // propfind_init can return a new property list, which
+    // will be passed to the next backend
+
+    WebdavBackend *davList = dav;
+    while(davList) {
+        // create WebdavPropfindRequest copy
+        WebdavPropfindRequest *pReq = pool_malloc(
+                pool,
+                sizeof(WebdavPropfindRequest));
+        memcpy(pReq, propfind, sizeof(WebdavPropfindRequest));
+        // use new plist after previous init (or orig. plist in the first run)
+        pReq->properties = newProp;
+        pReq->propcount = newPropCount;
+        
+        // add new WebdavPropfindRequest object to list for later use
+        requestObjects = ucx_list_append_a(a, requestObjects, pReq);
+        if(!requestObjects) {
+            return REQ_ABORTED; // OOM
+        }
+        
+        // create plist copy as out-plist for init
+        newProp = webdav_plist_clone(pool, newProp);
+        
+        // run init: this can generate a new properties list (newProp)
+        //           which will be passed to the next backend
+        if(davList->propfind_init(pReq, path, &newProp)) {
+            return REQ_ABORTED;
+        }
+        
+        newPropCount = webdav_plist_size(newProp);
+        
+        davList = davList->next;
+    }
+    
+    *out_req = requestObjects;
+    return REQ_PROCEED;
+}
+
+int webdav_propfind_do(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path,
+        char *uri)
+{
+    Session *sn = propfind->sn;
+    Request *rq = propfind->rq;
+    
+    // VFS settings are only taken from the first backend
+    uint32_t settings = dav->settings;
+    
+    // list of individual WebdavPropfindRequest objects for each Backend
+    UcxList *requestObjects = NULL;
+    
+    // Initialize all Webdav Backends
+    if(webdav_propfind_init(dav, propfind, path, &requestObjects)) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavOperation *op = webdav_create_propfind_operation(
+            sn,
+            rq,
+            dav,
+            propfind->properties,
+            requestObjects,
+            response);
+    
+    // some Backends can list all children by themselves, but some
+    // require the VFS for this
+    WSBool usevfs = (settings & WS_WEBDAV_PROPFIND_USE_VFS)
+                        == WS_WEBDAV_PROPFIND_USE_VFS;
+    struct stat s;
+    struct stat *statptr = NULL;
+    
+    if(usevfs && !vfs) {
+        vfs = vfs_request_context(sn, rq);
+        if(!vfs) {
+            return REQ_ABORTED;
+        }
+        
+        if(vfs_stat(vfs, path, &s)) {
+            protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+            return REQ_ABORTED;
+        }
+        statptr = &s;
+        if(!S_ISDIR(s.st_mode)) {
+            // the file is not a directory, therefore we don't need the VFS
+            usevfs = FALSE;
+        }
+    }
+    if(propfind->depth == 0) {
+        usevfs = FALSE;
+    }
+    
+    int ret = REQ_PROCEED;
+    
+    // create WebdavResource object for requested resource
+    if(!webdav_op_propfind_begin(op, uri, NULL, statptr)) {
+        // propfind for the requested resource was successful
+        
+        // usevfsdir is TRUE if
+        //   the webdav backend has not disabled vfs usage
+        //   the file is a directory
+        //   depth is not 0
+        // in this case we need to execute propfind_do for all children
+        if(usevfs) {
+            if(webdav_op_propfind_children(op, vfs, uri, path)) {
+                ret = REQ_ABORTED;
+            }
+        }
+    }
+    
+    // finish the propfind request
+    // this function should cleanup all resources, therefore we execute it
+    // even if a previous function failed
+    if(webdav_op_propfind_finish(op)) {
+        // TODO: log error
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    int error = 0;
+    WebdavProppatchRequest *proppatch = proppatch_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!proppatch) {
+        switch(error) {
+            // TODO: handle all errors
+            default: return REQ_ABORTED;
+        }
+    }
+     
+    WebdavBackend *dav =  rq->davCollection ?
+                              rq->davCollection : &default_backend;
+    
+    // requested uri and path
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    // The multistatus response object contains responses for all
+    // requested resources. At the end the Multistatus object will be
+    // serialized to xml
+    Multistatus *ms = multistatus_response(sn, rq);
+    if(!ms) {
+        return REQ_ABORTED;
+    }
+    ms->proppatch = TRUE;
+    
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    WebdavOperation *op = webdav_create_proppatch_operation(
+            sn,
+            rq,
+            dav,
+            proppatch,
+            response);
+    
+    int ret = REQ_PROCEED;
+    
+    // Execute proppatch
+    if(webdav_op_proppatch(op, uri, path)) {
+        ret = REQ_ABORTED;
+    }
+    
+    // send response
+    if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) {
+        ret = REQ_ABORTED;
+        // TODO: log error
+    } else {
+        // TODO: error response
+    }
+    
+    // cleanup
+    xmlFreeDoc(proppatch->doc);
+    
+    return ret;
+}
+
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR);
+    
+    return ret;
+}
+
+int webdav_post(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+typedef struct DeleteFile {
+    char *path;
+    struct stat s;
+} DeleteFile;
+
+typedef struct DeleteLists {
+    UcxAllocator *a;
+    UcxList *dirs_begin;
+    UcxList *dirs_end;
+    UcxList *files_begin;
+    UcxList *files_end;
+} DeleteOp;
+
+static int deletelist_add(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *userdata)
+{
+    DeleteOp *op = userdata;
+    
+    // create object for this file
+    DeleteFile *file = almalloc(op->a, sizeof(DeleteFile));
+    if(!file) {
+        return 1;
+    }
+    file->path = sstrdup_a(op->a, sstr((char*)path)).ptr;
+    if(!file->path) {
+        return 1;
+    }
+    file->s = *s;
+    
+    // determine which list to use
+    UcxList **begin;
+    UcxList **end;
+    if(S_ISDIR(s->st_mode)) {
+        begin = &op->dirs_begin;
+        end = &op->dirs_end;
+    } else {
+        begin = &op->files_begin;
+        end = &op->files_end;
+    }
+    
+    // add file to list
+    UcxList *elm = ucx_list_append_a(op->a, NULL, file);
+    if(!elm) {
+        alfree(op->a, file->path); // at least do some cleanup, although it
+        alfree(op->a, file);       // isn't really necessary
+        return 1;
+    }
+    if(*begin == NULL) {
+        *begin = elm;
+        *end = elm;
+    } else {
+        ucx_list_concat(*end, elm);
+        *end = elm;
+    }
+    
+    return 0;
+}
+
+static int webdav_delete_collection(WebdavVFSOperation *op)
+{
+    DeleteOp del;
+    ZERO(&del, sizeof(DeleteOp));
+    del.a = session_get_allocator(op->sn);
+    
+    // get a list of all files
+    if(webdav_op_iterate_children(op->vfs, -1, NULL, op->path,
+            deletelist_add, &del))
+    {
+        return 1;
+    }
+    
+    // add root to list of dir list
+    DeleteFile root;
+    root.path = op->path;
+    root.s = *op->stat;
+    UcxList root_elm;
+    root_elm.data = &root;
+    root_elm.prev = NULL;
+    root_elm.next = del.dirs_begin;
+    
+    if(del.dirs_begin) {
+        del.dirs_begin->prev = &root_elm;
+        del.dirs_begin = &root_elm;
+    } else {
+        del.dirs_begin = &root_elm;
+        del.dirs_end = &root_elm;
+    }
+    
+    // delete files first
+    UCX_FOREACH(elm, del.files_begin) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    // delete directories, reverse order
+    for(UcxList *elm=del.dirs_end;elm;elm=elm->prev) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+int webdav_delete(pblock *pb, Session *sn, Request *rq) { 
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    // stat to find out if the resource is a collection
+    struct stat s;
+    if(vfs_stat(op->vfs, op->path, &s)) {
+        sys_set_error_status(op->vfs);
+        return REQ_ABORTED;
+    }
+    op->stat = &s;
+    
+    int ret;
+    if(S_ISDIR(s.st_mode)) {
+        ret = webdav_delete_collection(op);
+    } else {
+        ret = webdav_vfs_op_do(op, WEBDAV_VFS_DELETE);
+    }
+    
+    return ret;
+}
+
+int webdav_put(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat s;
+    int create_file = 0;
+    if(vfs_stat(vfs, path, &s)) {
+        if(vfs->vfs_errno == ENOENT) {
+            create_file = O_CREAT;
+        } else {
+            protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+            return REQ_ABORTED;
+        }
+    }
+    
+    if(S_ISDIR(s.st_mode)) {
+        // PUT on collections is not allowed
+        protocol_status(sn, rq, PROTOCOL_METHOD_NOT_ALLOWED, NULL);
+        return REQ_ABORTED;
+    }
+    
+    SYS_FILE fd = vfs_open(vfs, path, O_RDONLY | create_file);
+    if(!fd) {
+        // if it fails, vfs_open sets http status code
+        return REQ_ABORTED;
+    }
+    
+    // TODO: check permissions, lock, ...
+    
+    // all checks done
+    
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    char in[4096];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        int w = 0;
+        while(w < r) {
+            w += system_fwrite(fd, in, r);
+        }
+    }
+    
+    system_fclose(fd);
+    
+    int status = create_file ? PROTOCOL_CREATED : PROTOCOL_NO_CONTENT;
+    protocol_status(sn, rq, status, NULL);
+    
+    return REQ_PROCEED;
+}
+
+int webdav_copy(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    char *destination = pblock_findval("destination", rq->headers);
+    if(!destination) {
+        protocol_status(sn, rq, PROTOCOL_BAD_REQUEST, NULL);
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat src_s;
+    if(vfs_stat(vfs, path, &src_s)) {
+        protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+        return REQ_ABORTED;
+    }
+    
+    // TODO: if src is a directory, make sure the uri has a trailing path separator
+    
+    
+    return REQ_ABORTED;
+}
+
+int webdav_move(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_lock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_unlock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_report(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_acl(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+
+
+/* ------------------------ default webdav backend  ------------------------ */
+
+int default_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char* path,
+        WebdavPList **outplist)
+{
+    DefaultWebdavData *data = pool_malloc(
+            rq->sn->pool,
+            sizeof(DefaultWebdavData));
+    if(!data) {
+        return 1;
+    }
+    rq->userdata = data;
+    
+    data->vfsproperties = webdav_vfs_properties(outplist, TRUE, 0);
+    
+    return 0;
+}
+
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        WebdavResource *resource,
+        struct stat *s)
+{
+    DefaultWebdavData *data = request->userdata;
+    
+    // add all requested vfs properties like getcontentlength ...
+    if(webdav_add_vfs_properties(
+            resource,
+            request->sn->pool,
+            data->vfsproperties,
+            s))
+    {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int default_propfind_finish(WebdavPropfindRequest *rq) {
+    return 0;
+}
+
+int default_proppatch_do(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WebdavPList **setInOut,
+            WebdavPList **removeInOut)
+{
+    return 0;
+}
+
+int default_proppatch_finish(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WSBool commit)
+{
+    return 0;
+}
+
+
+/* ------------------------------ public API ------------------------------ */
+
+int webdav_getdepth(Request *rq) {
+    char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers);
+    int depth = 0;
+    if(depth_str) {
+        size_t dlen = strlen(depth_str);
+        if(!memcmp(depth_str, "infinity", dlen)) {
+            depth = -1;
+        } else if(dlen == 1 && depth_str[0] == '1') {
+            depth = 1;
+        }
+    }
+    return depth;
+}
+
+int webdav_plist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop)
+{
+    WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList));
+    if(!elm) {
+        return 1;
+    }
+    elm->prev = *end;
+    elm->next = NULL;
+    elm->property = prop;
+    
+    if(!*begin) {
+        *begin = elm;
+        *end = elm;
+        return 0;
+    }
+    
+    (*end)->next = elm;
+    *end = elm;
+    
+    return 0;
+}
+
+WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) {
+    return webdav_plist_clone_s(pool, list, NULL);
+}
+
+WebdavPList* webdav_plist_clone_s(
+        pool_handle_t *pool,
+        WebdavPList *list,
+        size_t *newlen)
+{
+    WebdavPList *new_list = NULL;     // start of the new list
+    WebdavPList *new_list_end = NULL; // end of the new list
+    
+    size_t len = 0;
+    
+    WebdavPList *elm = list;
+    while(elm) {
+        // copy list item
+        WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList));
+        if(!new_elm) {
+            if(newlen) *newlen = 0;
+            return NULL;
+        }
+        new_elm->property = elm->property; // new list contains original ptr
+        new_elm->prev = new_list_end;
+        new_elm->next = NULL;
+        
+        if(new_list_end) {
+            new_list_end->next = new_elm;
+        } else {
+            new_list = new_elm;
+        }
+        new_list_end = new_elm;
+        
+        len++;
+        elm = elm->next;
+    }
+    
+    if(newlen) *newlen = len;
+    return new_list;
+}
+
+size_t webdav_plist_size(WebdavPList *list) {
+    size_t count = 0;
+    WebdavPList *elm = list;
+    while(elm) {
+        count++;
+        elm = elm->next;
+    }
+    return count;
+}
+
+WebdavPListIterator webdav_plist_iterator(WebdavPList **list) {
+    WebdavPListIterator i;
+    i.list = list;
+    i.cur = NULL;
+    i.next = *list;
+    i.index = 0;
+    return i;
+}
+
+int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur) {
+    if(i->cur) {
+        i->index++;
+    }
+    
+    i->cur = i->next;
+    i->next = i->cur ? i->cur->next : NULL;
+    *cur = i->cur;
+    
+    return i->cur != NULL;
+}
+
+void webdav_plist_iterator_remove_current(WebdavPListIterator *i) {
+    WebdavPList *cur = i->cur;
+    if(cur->prev) {
+        cur->prev->next = cur->next;
+        if(cur->next) {
+            cur->next->prev = cur->prev;
+        }
+    } else {
+        *i->list = cur->next;
+        if(cur->next) {
+            cur->next->prev = NULL;
+        }
+    }
+}
+
+int webdav_nslist_add(
+        pool_handle_t *pool,
+        WebdavNSList **begin,
+        WebdavNSList **end,
+        WSNamespace *ns)
+{
+    // same as webdav_plist_add but with different type
+    WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList));
+    if(!elm) {
+        return 1;
+    }
+    elm->prev = *end;
+    elm->next = NULL;
+    elm->namespace = ns;
+    
+    if(!*begin) {
+        *begin = elm;
+        *end = elm;
+        return 0;
+    }
+    
+    (*end)->next = elm;
+    *end = elm;
+    
+    return 0;
+}
+
+
+WSNamespace* webdav_dav_namespace(void) {
+    return &dav_namespace;
+}
+
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name)
+{
+    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+    if(!property) {
+        return NULL;
+    }
+    memset(property, 0, sizeof(WebdavProperty));
+    
+    property->namespace = &dav_namespace;
+    property->name = name;
+    return property;
+}
+
+int webdav_property_set_value(
+        WebdavProperty *p,
+        pool_handle_t *pool,
+        char *value)
+{
+    WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode));
+    if(!node) {
+        return 1;
+    }
+    ZERO(node, sizeof(WSXmlNode));
+    
+    node->content = (xmlChar*)value;
+    node->type = XML_TEXT_NODE;
+    
+    p->value.node = node;
+    p->vtype = WS_VALUE_XML_NODE;
+    return 0;
+}
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPList **plistInOut,
+        WSBool removefromlist,
+        uint32_t flags)
+{
+    WebdavVFSProperties ret;
+    ZERO(&ret, sizeof(WebdavVFSProperties));
+    
+    WSBool etag = 1;
+    WSBool creationdate = 1;
+    
+    WebdavPListIterator i = webdav_plist_iterator(plistInOut);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WSNamespace *ns = cur->property->namespace;
+        if(ns && !strcmp((const char*)ns->href, "DAV:")) {
+            const char *name = cur->property->name;
+            WSBool remove_prop = TRUE;
+            if(!strcmp(name, "getlastmodified")) {
+                ret.getlastmodified = 1;
+            } else if(!strcmp(name, "getcontentlength")) {
+                ret.getcontentlength = 1;
+            } else if(!strcmp(name, "resourcetype")) {
+                ret.getresourcetype = 1;
+            } else if(etag && !strcmp(name, "getetag")) {
+                ret.getetag = 1;
+            } else if(creationdate && !strcmp(name, "creationdate")) {
+                ret.creationdate = 1;
+            } else {
+                remove_prop = FALSE;
+            }
+            
+            if(remove_prop) {
+                webdav_plist_iterator_remove_current(&i);
+            }
+        }
+    }
+    
+    return ret;
+}
+
+static inline int w_addprop(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        const char *name,
+        char *value)
+{
+    WebdavProperty *p = webdav_dav_property(pool, name);
+    if(!p) {
+        return 1;
+    }
+    if(webdav_property_set_value(p, pool, value)) {
+        return 1;
+    }
+    return res->addproperty(res, p, 200);
+}
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s)
+{
+    if(properties.getresourcetype) {
+        if(S_ISDIR(s->st_mode)) {
+            res->addproperty(res, &dav_resourcetype_collection, 200);
+        } else {
+            res->addproperty(res, &dav_resourcetype_empty, 200);
+        }
+    }
+    if(properties.getcontentlength) {
+        char *buf = pool_malloc(pool, 64);
+        if(!buf) {
+            return 1;
+        }
+        uint64_t contentlength = s->st_size;
+        snprintf(buf, 64, "%" PRIu64 "\0", contentlength);
+        if(w_addprop(res, pool, "getcontentlength", buf)) {
+            return 1;
+        }
+    }
+    if(properties.getlastmodified) {
+        char *buf = pool_malloc(pool, HTTP_DATE_LEN+1);
+        if(!buf) {
+            return 1;
+        }
+        buf[HTTP_DATE_LEN] = 0;
+        
+        struct tm mtms;
+        struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms);
+        
+        if(mtm) {
+            strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
+            if(w_addprop(res, pool, "getlastmodified", buf)) {
+                return 1;
+            }
+        } else {
+            return 1;
+        }
+    }
+    if(properties.creationdate) {
+        // TODO
+    }
+    if(properties.getetag) {
+        char *buf = pool_malloc(pool, 96);
+        if(!buf) {
+            return 1;
+        }
+        snprintf(buf,
+            96,
+            "\"%x-%x\"\0",
+            (int)s->st_size,
+            (int)s->st_mtim.tv_sec);
+        if(w_addprop(res, pool, "getetag", buf)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
--- a/src/server/webdav/webdav.h	Mon Aug 24 19:19:56 2020 +0200
+++ b/src/server/webdav/webdav.h	Tue Aug 25 12:07:56 2020 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,7 +30,6 @@
 #define	WEBDAV_H
 
 #include "../public/webdav.h"
-#include "../util/strbuf.h"
 
 #include <ucx/map.h>
 #include <ucx/list.h>
@@ -38,8 +37,82 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+   
+
+    
+typedef struct DefaultWebdavData {
+    WebdavVFSProperties vfsproperties;
+} DefaultWebdavData;
+    
+    
+int webdav_init(pblock *pb, Session *sn, Request *rq);
+    
+int webdav_service(pblock *pb, Session *sn, Request *rq);
+
+/*
+ * returns a buffer containing the request body
+ * 
+ * this function sets an http response code in case of an error
+ * or missing request body
+ */
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq);
 
 
+int webdav_options(pblock *pb, Session *sn, Request *rq);
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq);
+
+int webdav_propfind_init(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        UcxList **out_req);
+
+int webdav_propfind_do(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path,
+        char *uri);
+
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq);
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq);
+int webdav_post(pblock *pb, Session *sn, Request *rq);
+int webdav_delete(pblock *pb, Session *sn, Request *rq);
+int webdav_put(pblock *pb, Session *sn, Request *rq);
+int webdav_copy(pblock *pb, Session *sn, Request *rq);
+int webdav_move(pblock *pb, Session *sn, Request *rq);
+int webdav_lock(pblock *pb, Session *sn, Request *rq);
+int webdav_unlock(pblock *pb, Session *sn, Request *rq);
+int webdav_report(pblock *pb, Session *sn, Request *rq);
+int webdav_acl(pblock *pb, Session *sn, Request *rq);
+int webdav_search (pblock *pb, Session *sn, Request *rq);
+
+
+int default_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char* path,
+        WebdavPList **outplist);
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        WebdavResource *resource,
+        struct stat *s);
+int default_propfind_finish(WebdavPropfindRequest *rq);
+int default_proppatch_do(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WebdavPList **setInOut,
+            WebdavPList **removeInOut);
+int default_proppatch_finish(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WSBool commit);
 
 #ifdef	__cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.c	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,564 @@
+/*
+ * 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 <ucx/string.h>
+#include <ucx/map.h>
+
+#include "../util/util.h"
+
+#include "xml.h"
+
+/*****************************************************************************
+ *    Utility functions
+ *****************************************************************************/
+
+/*
+ * generates a string key for an xml namespace
+ * format: prefix '\0' href
+ */
+static sstr_t xml_namespace_key(UcxAllocator *a, WSNamespace *ns) {
+    sstr_t key = sstrcat_a(a, 3,
+            ns->prefix ? sstr((char*)ns->prefix) : S("\0"),
+            S("\0"),
+            sstr((char*)ns->href));
+    return key;
+}
+
+
+/*****************************************************************************
+ *    Public functions
+ *****************************************************************************/
+
+/* ------------------------ wsxml_iterator ------------------------ */
+
+typedef struct StackElm {
+    WSXmlNode *node; // list of nodes
+    //WSXmlNode *parent; // if not NULL, call endcb after node->next is NULL
+    int endonly;
+    struct StackElm *next;
+} StackElm;
+
+#define STACK_PUSH(stack, elm) if(stack) { elm->next = stack; } stack = elm;
+
+int wsxml_iterator(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        wsxml_func begincb,
+        wsxml_func endcb,
+        void *udata)
+{  
+    if(!node) {
+        return 0;
+    }
+    
+    StackElm *stack = pool_malloc(pool, sizeof(StackElm));
+    if(!stack) {
+        return 1; // OOM
+    }
+    stack->next = NULL;
+    stack->node = node;
+    stack->endonly = 0;
+    //stack->parent = NULL;
+    
+    int ret = 0;
+    int br = 0;
+    while(stack) {
+        StackElm *cur = stack;
+        WSXmlNode *xmlnode = cur->node; // get top stack element
+        stack = cur->next;              // and remove it
+        cur->next = NULL;
+        
+        while(xmlnode && !cur->endonly) {
+            // element begin callback
+            if(begincb(xmlnode, udata)) {
+                br = 1;
+                break; // I don't like break with labels - is this wrong?
+            }
+            
+            if(xmlnode->children) {
+                // put the children on the stack
+                // the next stack iteration will process the children
+                StackElm *newelm = pool_malloc(pool, sizeof(StackElm));
+                if(!newelm) {
+                    ret = 1;
+                    br = 1;
+                    break;
+                }
+                newelm->next = NULL;
+                newelm->node = xmlnode->children;
+                // setting the parent will make sure endcb will be called
+                // for the current xmlnode after all children are processed
+                //newelm->parent = xmlnode;
+                newelm->endonly = 0;
+                
+                // if xmlnode->next is not NULL, there are still nodes at
+                // this level, therefore we have to put these also on the
+                // stack
+                // this way, the remaining nodes are processed after all
+                // children and the end tag are processed
+                if(xmlnode->next) {
+                    StackElm *nextelm = pool_malloc(pool, sizeof(StackElm));
+                    if(!nextelm) {
+                        ret = 1;
+                        br = 1;
+                        break;
+                    }
+                    nextelm->node = xmlnode->next;
+                    nextelm->next = NULL;
+                    nextelm->endonly = 0;
+                    STACK_PUSH(stack, nextelm);
+                }
+                
+                // we have to put the end tag of the current element
+                // on the stack to ensure endcb is called for the current
+                // element, after all children are processed
+                // reuse cur
+                cur->node = xmlnode;
+                cur->endonly = 1;
+                STACK_PUSH(stack, cur);
+                
+                cur = NULL;
+                
+                // now we can put the children on the stack
+                STACK_PUSH(stack, newelm);
+                // break, because we don't want to process xmlnode->next now
+                break;
+            } else {
+                // no children means, the end callback can be called directly
+                // after the begin callback (no intermediate nodes)
+                cur->node = NULL;
+                if(endcb(xmlnode, udata)) {
+                    br = 1;
+                    break;
+                }
+            }
+            
+            // continue with next node at this level
+            xmlnode = xmlnode->next;
+        }
+        if(br) {
+            break; // break because of an error
+        }
+        
+        if(cur && cur->node) {
+            //xmlNode *endNode = cur->parent ? cur->parent : cur->node;
+            xmlNode *endNode = cur->node;
+            if(endcb(endNode, udata)) {
+                break;
+            }
+            pool_free(pool, cur);
+        }
+    }
+    
+    // free all remaining elements
+    StackElm *elm = stack;
+    while(elm) {
+        StackElm *next = elm->next;
+        pool_free(pool, elm);
+        elm = next;
+    }
+
+    return ret;
+}
+
+/* ------------------- wsxml_get_required_namespaces ------------------- */
+
+typedef struct WSNsCollector {
+    UcxAllocator *a;
+    UcxMap *nsmap;
+    WebdavNSList *def;
+    int error;
+} WSNsCollector;
+
+static int nslist_node_begin(xmlNode *node, void *userdata) {
+    WSNsCollector *col = userdata;
+    // namespace required for all elements
+    if(node->type == XML_ELEMENT_NODE && node->ns) {
+        // we create a list of unique prefix-href namespaces by putting
+        // all namespaces in a map
+        sstr_t nskey = xml_namespace_key(col->a, node->ns);
+        if(!nskey.ptr) {
+            col->error = 1;
+            return 1;
+        }
+        if(ucx_map_sstr_put(col->nsmap, nskey, node->ns)) {
+            col->error = 1;
+            return 1;
+        }
+        
+        // collect all namespace definitions for removing these namespaces
+        // from col->nsmap later
+        WSNamespace *def = node->nsDef;
+        while(def) {
+            WebdavNSList *newdef = col->a->malloc(
+                    col->a->pool, sizeof(WebdavNSList));
+            if(!newdef) {
+                col->error = 1;
+                return 1;
+            }
+            newdef->namespace = def;
+            newdef->prev = NULL;
+            newdef->next = NULL;
+            // prepend newdef to the list
+            if(col->def) {
+                newdef->next = col->def;
+                col->def->prev = newdef;
+            }
+            col->def = newdef;
+            
+            // continue with next namespace definition
+            def = def->next;
+        }
+    }
+    return 0;
+}
+
+static int nslist_node_end(xmlNode *node, void *userdata) {
+    return 0;
+}
+
+WebdavNSList* wsxml_get_required_namespaces(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        int *error)
+{
+    if(error) *error = 0;
+    
+    UcxAllocator a = util_pool_allocator(pool);
+    UcxMap *nsmap = ucx_map_new_a(&a, 16);
+    if(!nsmap) {
+        if(error) *error = 1;
+        return NULL;
+    }
+    
+    WSNsCollector col;
+    col.a = &a;
+    col.nsmap = nsmap;
+    col.def = NULL;
+    
+    // iterate over all xml elements
+    // this will fill the hashmap with all namespaces
+    // all namespace definitions are added to col.def
+    WebdavNSList *list = NULL;
+    WebdavNSList *end = NULL;
+    if(wsxml_iterator(pool, node, nslist_node_begin, nslist_node_end, &col)) {
+        if(error) *error = 1;
+    } else {
+        // remove all namespace definitions from the map
+        // what we get is a map that contains all missing namespace definitions
+        WebdavNSList *def = col.def;
+        while(def) {
+            sstr_t nskey = xml_namespace_key(&a, def->namespace);
+            if(!nskey.ptr) {
+                if(error) *error = 1;
+                break;
+            }
+            ucx_map_sstr_remove(nsmap, nskey);
+            def = def->next;
+        }
+        
+        // convert nsmap to a list
+        UcxMapIterator i = ucx_map_iterator(nsmap);
+        WSNamespace *ns;
+        UCX_MAP_FOREACH(key, ns, i) {
+            WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList));
+            if(!newelm) {
+                if(error) *error = 1;
+                list = NULL;
+                break;
+            }
+            newelm->namespace = ns;
+            newelm->next = NULL;
+            newelm->prev = end; // NULL or the end of list
+            if(end) {
+                end->next = newelm; // append new element
+            } else {
+                list = newelm; // start new list
+            }
+            end = newelm;
+        }
+    }
+    
+    ucx_map_free(nsmap);
+    return list;
+}
+
+
+/*****************************************************************************
+ *    Non public functions
+ *****************************************************************************/
+
+typedef struct XmlWriter {
+    /*
+     * Memory pool for temp memory allocations
+     */
+    pool_handle_t *pool;
+    
+    /*
+     * Buffered output stream
+     */
+    Writer *out;
+    
+    /*
+     * Map for all previously defined namespaces
+     * key: (char*) namespace prefix
+     * value: WSNamespace*
+     */
+    UcxMap *namespaces;
+    
+    /*
+     * Should namespace definitions be created
+     */
+    WSBool define_namespaces;
+} XmlWriter;
+
+/*
+ * Serialize an XML text node
+ * This replaces some special characters with entity refs
+ * type: 0 = element text, 1 = attribute text
+ */
+static void xml_ser_text(Writer *out, int type, const char *text) {
+    size_t start = 0;
+    size_t i;
+    sstr_t entityref = { NULL, 0 };
+    for(i=0;text[i]!='\0';i++) {
+        char c = text[i];
+        if(c == '&') {
+            entityref = S("&amp;");
+        } else if(type == 0) {
+            if(c == '<') {
+                entityref = S("&lt;");
+            } else if(c == '>') {
+                entityref = S("&gt;");
+            }
+        } else {
+            if(c == '\"') {
+                entityref = S("&quot;");
+            } else if(c == '\'') {
+                entityref = S("&apos;");
+            }
+        }
+        
+        if(entityref.ptr) {
+            size_t len = i-start;
+            if(len > 0) {
+                writer_put(out, text+start, len);
+            }
+            writer_puts(out, entityref);
+            entityref.ptr = NULL;
+            entityref.length = 0;
+            start = i+1;
+        }
+    }
+    size_t len = i-start;
+    if(len > 0) {
+        writer_put(out, text+start, len);
+    }
+}
+
+/*
+ * Serialize an XML element node
+ */
+static void xml_ser_element(XmlWriter *xw, xmlNode *node) {
+    Writer *out = xw->out;
+    writer_putc(out, '<');
+    
+    // write prefix and ':'
+    if(node->ns && node->ns->prefix) {
+        writer_puts(out, sstr((char*)node->ns->prefix));
+        writer_putc(out, ':');
+    }
+    
+    // node name
+    writer_puts(out, sstr((char*)node->name));
+    
+    // namespace definitions
+    if(xw->define_namespaces) {
+        xmlNs *nsdef = node->nsDef;
+        while(nsdef) {
+            // we define only namespaces without prefix or namespaces
+            // with prefix, that are not already defined
+            // xw->namespaces contains all namespace, that were defined
+            // before xml serialization
+            if(!nsdef->prefix) {
+                writer_puts(out, S(" xmlns=\""));
+                writer_puts(out, sstr((char*)nsdef->href));
+                writer_putc(out, '"');
+            } else {
+                WSNamespace *n = xw->namespaces ?
+                    ucx_map_cstr_get(xw->namespaces, (char*)nsdef->prefix) :
+                    NULL;
+                if(!n) {
+                    writer_puts(out, S(" xmlns:"));
+                    writer_puts(out, sstr((char*)nsdef->prefix));
+                    writer_puts(out, S("=\""));
+                    writer_puts(out, sstr((char*)nsdef->href));
+                    writer_putc(out, '"');
+                }
+            }
+            
+            nsdef = nsdef->next;
+        }
+    }
+    
+    // attributes
+    xmlAttr *attr = node->properties;
+    while(attr) {
+        // format: ' [<prefix>:]<name>="<value>"'
+        writer_putc(out, ' ');
+        // optional namespace
+        if(attr->ns && attr->ns->prefix) {
+            writer_puts(out, sstr((char*)attr->ns->prefix));
+            writer_putc(out, ':');
+        }
+        // <name>="
+        writer_puts(out, sstr((char*)attr->name));
+        writer_puts(out, S("=\""));
+        // value
+        xmlNode *value = attr->children;
+        while(value) {
+            if(value->content) {
+                xml_ser_text(out, 1, (const char*)value->content);
+            }
+            value = value->next;
+        }
+        // trailing quote
+        writer_putc(out, '"');
+        
+        attr = attr->next;
+    }
+    
+    if(node->children) {
+        writer_putc(out, '>');
+    } else {
+        writer_puts(out, S("/>"));
+    }
+}
+
+static int xml_ser_node_begin(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    switch(node->type) {
+        case XML_ELEMENT_NODE: xml_ser_element(xw, node); break;
+        case XML_ATTRIBUTE_NODE: break;
+        case XML_TEXT_NODE: {
+            xml_ser_text(xw->out, 0, (const char*)node->content);
+            break;
+        }
+        case XML_CDATA_SECTION_NODE: {
+            break;
+        }
+        case XML_ENTITY_REF_NODE: break;
+        case XML_ENTITY_NODE: break;
+        case XML_PI_NODE: break;
+        case XML_COMMENT_NODE: break;
+        case XML_DOCUMENT_NODE: break;
+        case XML_DOCUMENT_TYPE_NODE: break;
+        case XML_DOCUMENT_FRAG_NODE: break;
+        case XML_NOTATION_NODE: break;
+        case XML_HTML_DOCUMENT_NODE: break;
+        case XML_DTD_NODE: break;
+        case XML_ELEMENT_DECL: break;
+        case XML_ATTRIBUTE_DECL: break;
+        case XML_ENTITY_DECL: break;
+        case XML_NAMESPACE_DECL: break;
+        case XML_XINCLUDE_START: break;
+        case XML_XINCLUDE_END: break;
+        default: break;
+    }
+    return 0;
+}
+
+static int xml_ser_node_end(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    Writer *out = xw->out;
+    if(node->type == XML_ELEMENT_NODE) {
+        if(node->children) {
+            writer_puts(xw->out, S("</"));
+            // write prefix and ':'
+            if(node->ns && node->ns->prefix) {
+                writer_puts(out, sstr((char*)node->ns->prefix));
+                writer_putc(out, ':');
+            }
+            // name and close tag
+            writer_puts(out, sstr((char*)node->name));
+            writer_putc(out, '>');
+            
+        } // element was already closed in xml_ser_node_begin
+    }
+    return 0;
+}
+
+
+static int xml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        WSBool createdefs,
+        xmlNode *node)
+{
+    XmlWriter xmlwriter;
+    xmlwriter.pool = pool;
+    xmlwriter.out = out;
+    xmlwriter.namespaces = nsdefs;
+    xmlwriter.define_namespaces = createdefs;
+    
+    // iterate over xml nodes
+    // this includes node->children and node->next
+    int err = wsxml_iterator(
+            pool,
+            node,
+            xml_ser_node_begin,
+            xml_ser_node_end,
+            &xmlwriter);
+    if(err) {
+        return -1;
+    }
+    
+    return out->error;
+}
+
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, nsdefs, TRUE, node);
+}
+
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, NULL, FALSE, node);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.h	Tue Aug 25 12:07:56 2020 +0200
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBDAV_XML_H
+#define WEBDAV_XML_H
+
+#include "../public/webdav.h"
+#include <libxml/tree.h>
+
+#include <ucx/map.h>
+
+#include "../util/writer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'.
+ */
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node);
+
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'
+ * without creating any namespace definitions. Therefore all namespaces must
+ * be already defined and previously written to 'out'.
+ */
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WEBDAV_XML_H */
+
--- a/templates/config/init.conf	Mon Aug 24 19:19:56 2020 +0200
+++ b/templates/config/init.conf	Tue Aug 25 12:07:56 2020 +0200
@@ -3,4 +3,6 @@
 #
 
 Init fn="admin-init"
+Init fn="webdav-init"
 
+

mercurial