add propfind/proppatch parser and first iteration of the new webdav api webdav

Thu, 31 Oct 2019 10:26:35 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 31 Oct 2019 10:26:35 +0100
branch
webdav
changeset 211
2160585200ac
parent 210
21274e5950af
child 212
d7e7ea9c6bc6

add propfind/proppatch parser and first iteration of the new webdav api

src/server/daemon/acl.c file | annotate | diff | comparison | revisions
src/server/daemon/acl.h file | annotate | diff | comparison | revisions
src/server/daemon/httprequest.c file | annotate | diff | comparison | revisions
src/server/daemon/httprequest.h file | annotate | diff | comparison | revisions
src/server/daemon/session.c file | annotate | diff | comparison | revisions
src/server/daemon/session.h file | annotate | diff | comparison | revisions
src/server/daemon/vfs.c file | annotate | diff | comparison | revisions
src/server/daemon/vfs.h file | annotate | diff | comparison | revisions
src/server/daemon/ws-fn.c file | annotate | diff | comparison | revisions
src/server/public/acl.h file | annotate | diff | comparison | revisions
src/server/public/auth.h file | annotate | diff | comparison | revisions
src/server/public/nsapi.h file | annotate | diff | comparison | revisions
src/server/public/vfs.h file | annotate | diff | comparison | revisions
src/server/public/webdav.h file | annotate | diff | comparison | revisions
src/server/test/main.c file | annotate | diff | comparison | revisions
src/server/test/objs.mk file | annotate | diff | comparison | revisions
src/server/test/testutils.c file | annotate | diff | comparison | revisions
src/server/test/testutils.h file | annotate | diff | comparison | revisions
src/server/test/webdav.c file | annotate | diff | comparison | revisions
src/server/test/webdav.h file | annotate | diff | comparison | revisions
src/server/util/netbuf.c file | annotate | diff | comparison | revisions
src/server/util/objs.mk file | annotate | diff | comparison | revisions
src/server/util/pblock.cpp file | annotate | diff | comparison | revisions
src/server/util/pblock.h file | annotate | diff | comparison | revisions
src/server/util/systems.h file | annotate | diff | comparison | revisions
src/server/util/writer.c file | annotate | diff | comparison | revisions
src/server/util/writer.h file | annotate | diff | comparison | revisions
src/server/webdav/multistatus.c file | annotate | diff | comparison | revisions
src/server/webdav/multistatus.h file | annotate | diff | comparison | revisions
src/server/webdav/objs.mk file | annotate | diff | comparison | revisions
src/server/webdav/requestparser.c file | annotate | diff | comparison | revisions
src/server/webdav/requestparser.h file | annotate | diff | comparison | revisions
src/server/webdav/search.c file | annotate | diff | comparison | revisions
src/server/webdav/search.h file | annotate | diff | comparison | revisions
src/server/webdav/versioning.c file | annotate | diff | comparison | revisions
src/server/webdav/versioning.h file | annotate | diff | comparison | revisions
src/server/webdav/webdav.c file | annotate | diff | comparison | revisions
src/server/webdav/webdav.h file | annotate | diff | comparison | revisions
src/server/webdav/xml.c file | annotate | diff | comparison | revisions
src/server/webdav/xml.h file | annotate | diff | comparison | revisions
templates/config/init.conf file | annotate | diff | comparison | revisions
--- a/src/server/daemon/acl.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/acl.c	Thu Oct 31 10:26:35 2019 +0100
@@ -100,6 +100,7 @@
 }
 
 User* acllist_getuser(Session *sn, Request *rq, ACLListHandle *list) {
+    // TODO: cache result
     if(!sn || !rq || !list) {
         return NULL;
     }
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/acl.h	Thu Oct 31 10:26:35 2019 +0100
@@ -49,6 +49,7 @@
 // file system acl functions
 
 int fs_acl_check(SysACL *acl, User *user, 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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httprequest.c	Thu Oct 31 10:26:35 2019 +0100
@@ -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);
 
--- a/src/server/daemon/httprequest.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httprequest.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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/session.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/session.c	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/session.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vfs.c	Thu Oct 31 10:26:35 2019 +0100
@@ -52,9 +52,11 @@
     sys_vfs_stat,
     sys_vfs_fstat,
     sys_vfs_opendir,
+    sys_vfs_fdopendir,
     sys_vfs_mkdir,
     sys_vfs_unlink,
-    VFS_CHECKS_ACL
+    VFS_CHECKS_ACL,
+    NULL
 };
 
 static VFS_IO sys_file_io = {
@@ -202,6 +204,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);
@@ -422,6 +445,62 @@
     return dir;
 }
 
+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, char *path) {
     return sys_path_op(ctx, path, sys_mkdir);
 }
@@ -539,6 +618,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;
                 }
--- a/src/server/daemon/vfs.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vfs.h	Thu Oct 31 10:26:35 2019 +0100
@@ -57,6 +57,7 @@
 int sys_vfs_stat(VFSContext *ctx, 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);
+VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
 int sys_vfs_mkdir(VFSContext *ctx, char *path);
 int sys_vfs_unlink(VFSContext *ctx, char *path);
 
--- a/src/server/daemon/ws-fn.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/ws-fn.c	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/acl.h	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/auth.h	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/nsapi.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/vfs.h	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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:
@@ -52,9 +52,11 @@
     int (*stat)(VFSContext *ctx, char *path, struct stat *buf);
     int (*fstat)(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
     VFS_DIR (*opendir)(VFSContext *ctx, char *path);
+    VFS_DIR (*fdopendir)(VFSContext *ctx, SYS_FILE fd);
     int (*mkdir)(VFSContext *ctx, char *path);
     int (*unlink)(VFSContext *ctx, char *path);
     uint32_t flags;
+    void *instance;
 };
 
 struct VFSContext {
@@ -70,16 +72,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 {
@@ -124,6 +126,7 @@
 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_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);
--- a/src/server/public/webdav.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/webdav.h	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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:
@@ -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,209 @@
 extern "C" {
 #endif
 
+typedef struct WebdavBackend          WebdavBackend;
+    
+typedef struct WebdavProperty         WebdavProperty;
+typedef struct WebdavPList            WebdavPList;
 
+typedef enum   WebdavLockScope        WebdavLockScope;
+typedef enum   WebdavLockType         WebdavLockType;
+
+typedef struct WebdavPropfindRequest  WebdavPropfindRequest;
+typedef struct WebdavProppatchRequest WebdavProppatchRequest;
+typedef struct WebdavLockRequest      WebdavLockRequest;
+
+typedef struct WebdavResponse         WebdavResponse;
+typedef struct WebdavResource         WebdavResource;
+
+typedef struct WebdavVFSProperties    WebdavVFSProperties;
+
+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
+
+/* propfind settings */
+
+/*
+ * Don't use the vfs to stat files or read the directory children
+ */
+#define WS_PROPFIND_NO_VFS 0x01
+
+struct WebdavProperty {
+    WSNamespace *namespace;
+    
+    const char *name;
+    
+    char *lang;
+    
+    WSXmlNode *value;
+};
+
+struct WebdavPList {
+    WebdavProperty *property;
+    WebdavPList *prev;
+    WebdavPList *next;
+};
+
+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;
+    
+    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;
+};
+
+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 {
+    WebdavResource* (*addresource)(WebdavResponse*, const char*);
+};
+
+struct WebdavResource {
+    char *href;
+    
+    /*
+     * int addprop(WebdavResource *res, WebdavProperty *property, int status);
+     * 
+     * Adds a property to the resource
+     */
+    int (*addproperty)(WebdavResource*, WebdavProperty*, int);
+};
+
+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.
+     * 
+     * Optionally, the function can store a pointer to a list of all properties,
+     * which will be processed by this backend, in the outplist argument.
+     */
+    int (*propfind_init)(WebdavPropfindRequest *, const char *, WebdavPList **);
+    
+    /*
+     * int propfind_do(
+     *     WebdavPropfindRequest *rq,
+     *     WebdavResponse *response,
+     *     VFS_DIR parent,
+     *     const char *path,
+     *     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,
+            const char *,
+            struct stat *);
+    
+    /*
+     * int propfind_finish(WebdavPropfindRequest *rq);
+     * 
+     * Finishes a propfind request.
+     */
+    int (*propfind_finish)(WebdavPropfindRequest *);
+    
+    /*
+     * See the WS_PROPFIND_ macros for informations about the settings
+     */
+    uint32_t settings;
+};
+
+int webdav_getdepth(Request *rq);
+
+WSNamespace* webdav_dav_namespace(void);
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name);
+
+int webdav_property_set_value(
+        WebdavProperty *p,
+        pool_handle_t *pool,
+        char *value);
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPropfindRequest *rq,
+        WSBool removefromlist,
+        uint32_t flags);
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s);
 
 #ifdef	__cplusplus
 }
--- a/src/server/test/main.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/test/main.c	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -39,10 +39,9 @@
 #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 "webdav.h"
 
 void test() {
     
@@ -50,7 +49,7 @@
 
 // needed for linking
 WSBool main_is_daemon(void) {
-    return is_daemon;
+    return 0;
 }
 
 int main(int argc, char **argv) {
@@ -59,7 +58,20 @@
     //test();
     
     printf("%s", "Webserver Test Suite\n====================\n\n");
-
+    
+    UcxTestSuite* suite = ucx_test_suite_new();
+    
+    // webdav tests
+    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_msresponse_addproperty);
+    
+    // run tests
+    ucx_test_run(suite, stdout);
+    fflush(stdout);
+    
     return EXIT_SUCCESS;
 }
 
--- a/src/server/test/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/test/objs.mk	Thu Oct 31 10:26:35 2019 +0100
@@ -31,6 +31,8 @@
 TEST_OBJPRE = $(OBJ_DIR)$(TEST_SRC_DIR)
 
 TESTOBJ = main.o
+TESTOBJ += testutils.o
+TESTOBJ += webdav.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	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,94 @@
+/*
+ * 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 "testutils.h"
+
+Session* testutil_session(void) {
+    pool_handle_t *pool = pool_create();
+    NSAPISession *sn = nsapisession_create(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;
+}
+
+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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/testutils.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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 TESTUTILS_H
+#define TESTUTILS_H
+
+#include "../public/nsapi.h"
+#include "../daemon/httprequest.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Session* testutil_session(void);
+
+Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri);
+
+void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len);
+
+void testutil_destroy_session(Session *sn);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTUTILS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.c	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,412 @@
+/*
+ * 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.h"
+
+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;
+    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;
+    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_msresponse_addproperty) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    Multistatus *ms = multistatus_response(sn, rq);
+    MSResponse *r;
+    
+    UCX_TEST_BEGIN;
+    
+    r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/");
+    
+    WebdavProperty p1;
+    WebdavProperty p[16];
+    
+    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(ucx_list_size(r->errors->begin) == 2, "404 list size != 2");
+    UCX_TEST_ASSERT(ucx_list_size(r->errors->next->begin) == 4, "403 list size != 4");
+    UCX_TEST_ASSERT(ucx_list_size(r->errors->next->next->begin) == 1, "500 list size != 1");
+    
+    UCX_TEST_END;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.h	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,147 @@
+/*
+ * 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_propfind_parse);
+UCX_TEST(test_proppatch_parse);
+UCX_TEST(test_lock_parse);
+
+UCX_TEST(test_rqbody2buffer);
+
+UCX_TEST(test_msresponse_addproperty);
+
+/* --------------------------- 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>"
+
+/* --------------------------- 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 */
+
--- a/src/server/util/netbuf.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/netbuf.c	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/objs.mk	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/pblock.cpp	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/pblock.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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);
 
--- a/src/server/util/systems.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/systems.h	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,104 @@
+/*
+ * 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;
+    }
+    
+    size_t a = w->size - w->pos;
+    if(a == 0) {
+        if(writer_flush(w)) {
+            return 1;
+        }
+    }
+    
+    size_t cplen = len > a ? a : len;
+    memcpy(w->buffer, s, cplen);
+    w->pos += cplen;
+    
+    if(cplen < len) {
+        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	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,391 @@
+/*
+ * 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 "../daemon/session.h"
+
+#include "multistatus.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);
+    if(!ms->namespaces) {
+        return NULL;
+    }
+    if(ucx_map_cstr_put(ms->namespaces, "D", "DAV:")) {
+        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);
+    char *href;
+    UCX_MAP_FOREACH(key, href, i) {
+        writer_puts(out, S(" xmlns:"));
+        writer_put(out, key.data, key.len);
+        writer_puts(out, S("=\""));
+        writer_puts(out, sstr(href));
+        writer_puts(out, S("\""));
+    }
+    
+    writer_puts(out, S(">\n"));
+    
+    return out->error;
+}
+
+static int send_prop_name(
+        Multistatus *ms,
+        WebdavProperty *p,
+        Writer *out,
+        char *prefixbuf,
+        int *prefixbuflen)
+{
+    int in_prefix_len = *prefixbuflen;
+    *prefixbuflen = 0;
+    
+    writer_putc(out, '<');
+    
+    const char *prefix = NULL;
+    const char *href = NULL;
+    
+    if(p->namespace && p->namespace->href) {
+        href = (const char*)p->namespace->href;
+
+        if(p->namespace->prefix) {
+            // check if there is a namespace with this prefix already defined
+            // and has the same namespace uri
+            prefix = (const char*)p->namespace->prefix;
+            char *nshref = ucx_map_cstr_get(
+                    ms->namespaces,
+                    (const char*)p->namespace->prefix);
+            if(!strcmp(nshref, href)) {
+                href = NULL; // we don't need a new xmlns def
+            }
+        } else {
+            // generate new prefix
+            for(int i=0;i<1024;i++) {
+                int len = snprintf(prefixbuf, in_prefix_len, "x%d\0", i);
+                char *test = ucx_map_cstr_get(ms->namespaces, prefixbuf);
+                if(!test) {
+                    prefix = prefixbuf;
+                    *prefixbuflen = len;
+                    break; // found an unused prefix
+                }
+                if(!prefix) {
+                    // What? Can't find a free prefix?
+                    return 1;
+                }
+            }
+        }
+    }
+    
+    if(prefix) {
+        writer_put(out, prefix, strlen(prefix));
+        writer_put(out, ":", 1);
+    }
+    
+    // write xml element name
+    writer_put(out, (const char*)p->name, strlen((const char*)p->name));
+    
+    if(href) {
+        writer_puts(out, S(" xmlns:"));
+        writer_put(out, prefix, strlen(prefix));
+        writer_puts(out, S("=\""));
+        writer_put(out, href, strlen(href));
+        writer_putc(out, '\"');
+    }
+    
+    if(p->lang) {
+        writer_puts(out, S(" lang=\""));
+        writer_puts(out, sstr(p->lang));
+        writer_putc(out, '\"');
+    }
+    
+    writer_putc(out, '>');
+    
+    return out->error;
+}
+
+
+
+#define MAX_XML_TREE_DEPTH 128
+static int send_xml(Multistatus *ms, Writer *out, WSXmlNode *node, WSNamespace *rootns, int depth) {
+    int ret = 0;
+    const char *s;
+    while(node) {
+        switch(node->type) {
+            case XML_ELEMENT_NODE: {
+                writer_putc(out, '<');
+                if(node->ns && node->ns->prefix) {
+                    s = (const char*)node->ns->prefix;
+                    writer_put(out, s, strlen(s));
+                    writer_putc(out, ':');
+                }
+                s = (const char*)node->name;
+                writer_put(out, s, strlen(s));
+                
+                
+            }
+            
+        }
+        node = node->next;
+    }
+    
+    return ret;
+}
+
+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("</href>\n"));
+    
+    if(rp->plist_begin) {
+        writer_puts(out, S("    <D:propstat>"
+                           "      <D:prop>\n"));
+        WebdavPList *p = rp->plist_begin;
+        char prefix[16];
+        while(p) {
+            WebdavProperty *prop = p->property;
+            int prefixlen = 16;
+            if(send_prop_name(ms, prop, out, prefix, &prefixlen)) {
+                return 1;
+            }
+            
+            // send content
+            
+            
+            // send end tag
+            writer_put(out, "<", 1);
+            if(prop->namespace && prop->namespace->href) {
+                const char *pre = NULL;
+                if(prop->namespace->prefix) {
+                    pre = (const char*)prop->namespace->prefix;
+                } else if(prefixlen > 0) {
+                    pre = prefix;
+                }
+                
+                if(pre) {
+                    writer_put(out, pre, strlen(pre));
+                    writer_put(out, ":", 1);
+                }
+            }
+            writer_put(out, prop->name, strlen(prop->name));
+            writer_put(out, ">", 1);
+            
+            if(out->error) {
+                return 1;
+            }
+            
+            p = p->next;
+        }
+        writer_puts(out, S("      </D:prop>\n"
+                           "      <D:status>HTTP/1.1 200 OK</D:status>"
+                           "    </D:propstat>\n"));
+    }
+    
+    return out->error;
+}
+
+int multistatus_send(Multistatus *ms, SYS_NETFD net) {
+    char buffer[MULTISTATUS_BUFFER_LENGTH];
+    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;
+    }
+    
+    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));
+    
+    res->resource.addproperty = msresponse_addproperty;
+    
+    res->multistatus = ms;
+    res->errors = NULL;
+    res->end = 0;
+    
+    if(ms->current) {
+        ms->current->end = 1;
+        ms->current->next = res;
+    } else {
+        ms->first = res;
+    }
+    ms->current = res;
+    
+    return (WebdavResource*)res;
+}
+
+int msresponse_addproperty(
+        WebdavResource *res,
+        WebdavProperty *property,
+        int status)
+{
+    MSResponse *response = (MSResponse*)res;
+    if(response->end) {
+        log_ereport(
+                LOG_WARN,
+                "%s",
+                "webdav: cannot add property to closed response tag");
+        return 0;
+    }
+    
+    // add namespace of this property to the namespace map
+    if(property->namespace && property->namespace->prefix) {
+        char *ns = ucx_map_cstr_get(
+                response->multistatus->namespaces,
+                (const char*)property->namespace->prefix);
+        if(!ns) {
+            int err = ucx_map_cstr_put(
+                    response->multistatus->namespaces,
+                    (const char*)property->namespace->prefix,
+                    property->namespace->href);
+            if(err) {
+                return 1;
+            }
+        }
+    }
+    
+    if(status != 200) {
+        return msresponse_addproperror(response, property, status);
+    }
+    
+    // add property to the list
+    WebdavPList *listelm = pool_malloc(
+            response->multistatus->sn->pool,
+            sizeof(WebdavPList));
+    if(!listelm) {
+        return 1;
+    }
+    
+    listelm->property = property;
+    listelm->next = NULL;
+    
+    if(response->plist_end) {
+        response->plist_end->next = listelm;
+    } else {
+        response->plist_begin = listelm;
+    }
+    response->plist_end = listelm;
+    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);
+    
+    // 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
+    UcxList *newlistelm = ucx_list_append_a(a, errlist->end, property);
+    if(!newlistelm) {
+        return 1;
+    }
+    errlist->end = newlistelm;
+    if(!errlist->begin) {
+        errlist->begin = newlistelm;
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/multistatus.h	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,113 @@
+/*
+ * 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 PropertyErrorList PropertyErrorList;
+
+/*
+ * implements the WebdavResponse interface
+ */
+struct Multistatus {
+    WebdavResponse response;
+    Session *sn;
+    Request *rq;
+    MSResponse *first;
+    MSResponse *current;
+    
+    /*
+     * this map contains some namespaces of property elements, but not all
+     * only the namespace of the property name element is added
+     * 
+     * key: (char*) namespace prefix
+     * value: (char*) namespace href
+     */
+    UcxMap *namespaces;
+};
+
+/*
+ * implements the WebdavResource interface
+ */
+struct MSResponse {
+    WebdavResource resource;
+    Multistatus *multistatus;
+    
+    PropertyErrorList *errors;
+    
+    WebdavPList *plist_begin;
+    WebdavPList *plist_end;
+    
+    MSResponse *next;
+    WSBool end;
+};
+
+struct PropertyErrorList {
+    PropertyErrorList *next;
+    UcxList *begin;
+    UcxList *end;
+    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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MULTISTATUS_H */
+
--- a/src/server/webdav/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/objs.mk	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 #
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 #
-# Copyright 2013 Olaf Wintermann. All rights reserved.
+# Copyright 2019 Olaf Wintermann. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
@@ -31,6 +31,10 @@
 DAV_OBJPRE = $(OBJ_DIR)$(DAV_SRC_DIR)
 
 DAVOBJ = webdav.o
+DAVOBJ += requestparser.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/requestparser.c	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,528 @@
+/*
+ * 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)
+
+int proplist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop)
+{
+    WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList));
+    if(!elm) {
+        return 1;
+    }
+    elm->next = NULL;
+    elm->property = prop;
+    
+    if(!*begin) {
+        *begin = elm;
+        *end = elm;
+        return 0;
+    }
+    
+    (*end)->next = elm;
+    *end = elm;
+    
+    return 0;
+}
+
+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));
+    prop->lang = NULL;
+    prop->name = (char*)name;
+    prop->namespace = ns;
+    prop->value = NULL;
+    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 = pnode->children;
+            }
+            if(prop) {
+                if(proplist_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); // no allocated content must 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	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,111 @@
+/*
+ * 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
+    
+int proplist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop);
+
+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	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Thu Oct 31 10:26:35 2019 +0100
@@ -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	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/webdav.c	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,622 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <ucx/buffer.h>
+#include <ucx/list.h>
+
 #include "webdav.h"
 
+#include "search.h"
+#include "versioning.h"
+#include "multistatus.h"
+#include "requestparser.h"
 
+#include "../util/pblock.h"
+#include "../util/util.h"
+#include "../daemon/session.h"
+#include "../daemon/http.h"
+
+static UcxMap *method_handler_map;
+
+static WebdavBackend default_backend;
+
+static WSNamespace dav_namespace;
+
+static WebdavProperty dav_resourcetype_empty;
+static WebdavProperty dav_resourcetype_collection;
+static WSXmlNode dav_resourcetype_collection_value;
+
+static void init_default_backend(void) {
+    memset(&default_backend, 0, sizeof(WebdavBackend));
+    default_backend.propfind_init = default_propfind_init;
+    default_backend.propfind_do = default_propfind_do;
+    default_backend.propfind_finish = default_propfind_finish;
+}
+
+int webdav_init(pblock *pb, Session *sn, Request *rq) {
+    init_default_backend();
+    
+    method_handler_map = ucx_map_new(64);
+    
+    ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options);
+    ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind);
+    ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch);
+    ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol);
+    ucx_map_cstr_put(method_handler_map, "POST", webdav_post);
+    ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete);
+    ucx_map_cstr_put(method_handler_map, "PUT", webdav_put);
+    ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy);
+    ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move);
+    ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock);
+    ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock);
+    ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report);
+    ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl);
+    
+    ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search);
+    
+    ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control);
+    ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout);
+    ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin);
+    ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout);
+    ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace);
+    ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update);
+    ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label);
+    ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge);
+    
+    dav_namespace.href = (xmlChar*)"DAV:";
+    dav_namespace.prefix = (xmlChar*)"D";
+    
+    dav_resourcetype_empty.namespace = &dav_namespace;
+    dav_resourcetype_empty.name = "resourcetype";
+    
+    dav_resourcetype_collection.namespace = &dav_namespace;
+    dav_resourcetype_collection.name = "resourcetype";
+    dav_resourcetype_collection.value = &dav_resourcetype_collection_value;
+    dav_resourcetype_collection_value.content = (xmlChar*)"<D:collection/>";
+    dav_resourcetype_collection_value.type = XML_TEXT_NODE;
+    
+    
+    return REQ_PROCEED;
+}
+
+
+int webdav_service(pblock *pb, Session *sn, Request *rq) {
+    if(!method_handler_map) {
+        log_ereport(LOG_FAILURE, "WebDAV module not initialized");
+        protocol_status(sn, rq, 500, NULL);
+        return REQ_ABORTED;
+    }
+    char *method = pblock_findkeyval(pb_key_method, rq->reqpb);
+    
+    FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method);
+    if(!saf) {
+        return REQ_NOACTION;
+    }
+    
+    return saf(pb, sn, rq);
+}
+
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq) {
+    if(!sn->inbuf) {
+        protocol_status(sn, rq, 400, NULL);
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(
+            NULL,
+            sn->inbuf->maxsize,
+            UCX_BUFFER_AUTOEXTEND);
+    if(!buf) {
+        protocol_status(sn, rq, 500, NULL);
+        return NULL;
+    }
+    
+    char in[2048];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        if(ucx_buffer_write(in, 1, r, buf) != r) {
+            protocol_status(sn, rq, 500, NULL);
+            ucx_buffer_free(buf);
+            return NULL;
+        }
+    }
+    
+    return buf;
+}
+
+int webdav_options(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq) {
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    int error = 0;
+    WebdavPropfindRequest *propfind = propfind_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!propfind) {
+        switch(error) {
+            // TODO: handle all errors
+            default: return REQ_ABORTED;
+        }
+    }
+    
+    
+    Multistatus *ms = multistatus_response(sn, rq);
+    if(!ms) {
+        return REQ_ABORTED;
+    }
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    WebdavBackend *dav = 
+            rq->davCollection ? rq->davCollection : &default_backend;
+    
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    
+    uint32_t settings = dav->settings;
+    if(dav->propfind_init(propfind, path)) {
+        return REQ_ABORTED;
+    }
+    
+    WSBool usevfs = (settings & WS_PROPFIND_NO_VFS) != WS_PROPFIND_NO_VFS;
+    struct stat s;
+    struct stat *statptr = NULL;
+    
+    VFSContext *vfs = NULL;
+    if(usevfs) {
+        vfs = vfs_request_context(sn, rq);
+        
+        if(vfs_stat(vfs, path, &s)) {
+            return REQ_ABORTED;
+        }
+        statptr = &s;
+        if(!S_ISDIR(s.st_mode)) {
+            usevfs = FALSE;
+        }
+    }
+    if(propfind->depth == 0) {
+        usevfs = FALSE;
+    }
+    
+    int ret = REQ_ABORTED;
+    if(!dav->propfind_do(propfind, response, NULL, path, statptr)) {
+        // propfind for the requested resource was successful
+        
+        // usevfsdir is TRUE if
+        //   the webdav backend has not disabled vfs usage
+        //   the file is a directory
+        //   depth is not 0
+        // in this case we need to execute propfind_do for all children
+        if(usevfs && !propfind_children(dav, propfind, response, vfs, path)) {
+            ret = REQ_PROCEED;
+        }
+    }
+    
+    // finish the propfind request
+    // this function should cleanup all resources, therefore we execute it
+    // even if a previous function failed
+    if(dav->propfind_finish(propfind)) {
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+int propfind_children(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path)
+{
+    UcxAllocator *a = session_get_allocator(request->sn);
+    pool_handle_t *pool = request->sn->pool;
+    UcxList *stack = ucx_list_prepend_a(a, NULL, path);
+    UcxList *stack_end = stack;
+    if(!stack) {
+        return 1;
+    }
+    
+    // reusable buffer for full child path
+    char *newpath = NULL;
+    size_t newpathlen = 0;
+    
+    int err = 0;
+    while(stack && !err) {
+        char *cur_path = stack->data;
+        size_t parent_len = strlen(cur_path);
+        if(parent_len > WEBDAV_PATH_MAX) {
+            log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
+            err = 1;
+            break;
+        }
+        if(cur_path[parent_len-1] == '/') {
+            parent_len--;
+        }
+        size_t max_child_len = WEBDAV_PATH_MAX - parent_len;
+        
+        // when newpath is initialized with the parent path
+        // set path_buf_init to TRUE
+        WSBool path_buf_init = FALSE;
+        
+        VFS_DIR dir = vfs_opendir(vfs, path);
+        if(!dir) {
+            log_ereport(
+                    LOG_FAILURE,
+                    "webdav: propfind: cannot open directory %d",
+                    vfs->vfs_errno);
+            err = 1;
+            break;
+        }
+        
+        VFS_ENTRY f;
+        while(vfs_readdir_stat(dir, &f)) {
+            if(f.stat_errno != 0) {
+                continue;
+            }
+            
+            size_t child_len = strlen(f.name);
+            if(child_len > max_child_len) {
+                log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
+                err = 1;
+                break;
+            }
+            size_t childpathlen = parent_len + child_len + 1; // +1 '/'
+            if(childpathlen > newpathlen) {
+                // we're gonna need a bigger boa^H^H^Hbuffer
+                if(newpath) {
+                    pool_free(pool, newpath);
+                }
+                newpath = pool_malloc(pool, childpathlen + 1);
+                if(!newpath) {
+                    err = 1;
+                    break;
+                }
+                newpathlen = childpathlen;
+                path_buf_init = FALSE;
+            }
+            // create full path string for this child
+            if(!path_buf_init) {
+                memcpy(newpath, cur_path, parent_len);
+                newpath[parent_len] = '/';
+            }
+            memcpy(newpath+parent_len+1, f.name, child_len);
+            newpath[childpathlen] = 0;
+            
+            // propfind for this child
+            if(dav->propfind_do(request, response, dir, newpath, &f.stat)) {
+                err = 1;
+                break;
+            }
+            
+            // depth of -1 means infinity
+            if(request->depth == -1 && S_ISDIR(f.stat.st_mode)) {
+                char *pathcp = pool_malloc(pool, childpathlen + 1);
+                memcpy(pathcp, newpath, childpathlen + 1);
+                
+                // add the newpath copy to the stack
+                // stack_end is always not NULL here, because we remove
+                // the first stack element at the end of the loop
+                UcxList *newlistelm = ucx_list_append_a(a, stack_end, pathcp);
+                if(!newlistelm) {
+                    err = 1;
+                    break;
+                }
+                stack_end = newlistelm;
+            }
+        }
+        
+        vfs_closedir(dir);
+        
+        if(cur_path != path) {
+            pool_free(pool, cur_path);
+        }
+        stack = ucx_list_remove_a(a, stack, stack);
+    }
+    
+    // in case of an error, we have to free all remaining stack elements
+    UCX_FOREACH(elm, stack) {
+        char *data = elm->data;
+        if(data != path) {
+            pool_free(pool, data);
+        }
+    }
+    
+    return err;
+}
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_post(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_delete(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_put(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_copy(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_move(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_lock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_unlock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_report(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_acl(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+
+/* ------------------------ default webdav backend  ------------------------ */
+
+int default_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char* path)
+{
+    DefaultWebdavData *data = pool_malloc(
+            rq->sn->pool,
+            sizeof(DefaultWebdavData));
+    if(!data) {
+        return 1;
+    }
+    rq->userdata = data;
+    
+    data->vfsproperties = webdav_vfs_properties(rq, TRUE, 0);
+    
+    return 0;
+}
+
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        const char *path,
+        struct stat *s)
+{
+    DefaultWebdavData *data = request->userdata;
+    
+    // add a resource to the response
+    // usually this will lead to a <response> ... </response> tag in the
+    // multistatus response
+    WebdavResource *resource = response->addresource(response, path);
+    if(!resource) {
+        return 1;
+    }
+    
+    // add all requested vfs properties like getcontentlength ...
+    if(webdav_add_vfs_properties(
+            resource,
+            request->sn->pool,
+            data->vfsproperties,
+            s))
+    {
+        return 1;
+    }
+    
+    // all remaining properties are not available
+    WebdavPList *p = request->properties;
+    while(p) {
+        resource->addproperty(resource, p->property, 404);
+        p = p->next;
+    }
+    
+    return 0;
+}
+
+int default_propfind_finish(WebdavPropfindRequest *rq) {
+    return 0;
+}
+
+
+/* ------------------------------ public API ------------------------------ */
+
+int webdav_getdepth(Request *rq) {
+    char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers);
+    int depth = 0;
+    if(depth_str) {
+        size_t dlen = strlen(depth_str);
+        if(!memcmp(depth_str, "infinity", dlen)) {
+            depth = -1;
+        } else if(dlen == 1 && depth_str[0] == '1') {
+            depth = 1;
+        }
+    }
+    return depth;
+}
+
+WSNamespace* webdav_dav_namespace(void) {
+    return &dav_namespace;
+}
+
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name)
+{
+    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+    if(!property) {
+        return NULL;
+    }
+    
+    property->namespace = &dav_namespace;
+    property->lang = NULL;
+    property->name = name;
+    property->value = NULL;
+    return property;
+}
+
+int webdav_property_set_value(
+        WebdavProperty *p,
+        pool_handle_t *pool,
+        char *value)
+{
+    WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode));
+    if(!node) {
+        return 1;
+    }
+    ZERO(node, sizeof(WSXmlNode));
+    
+    node->content = (xmlChar*)value;
+    node->type = XML_TEXT_NODE;
+    
+    p->value = node;
+    return 0;
+}
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPropfindRequest *rq,
+        WSBool removefromlist,
+        uint32_t flags)
+{
+    WebdavVFSProperties ret;
+    ZERO(&ret, sizeof(WebdavVFSProperties));
+    
+    WSBool etag = 1;
+    WSBool creationdate = 1;
+    
+    WebdavPList *property = rq->properties;
+    WebdavPList *prev = NULL;
+    while(property) {
+        WebdavPList *next = property->next;
+        WSNamespace *ns = property->property->namespace;
+        if(ns && !strcmp((char*)ns->href, "DAV:")) {
+            const char *name = property->property->name;
+            WebdavPList *removethis = property;
+            if(!strcmp(name, "getlastmodified")) {
+                ret.getlastmodified = 1;
+            } else if(!strcmp(name, "getcontentlength")) {
+                ret.getcontentlength = 1;
+            } else if(!strcmp(name, "resourcetype")) {
+                ret.getresourcetype = 1;
+            } else if(etag && !strcmp(name, "getetag")) {
+                ret.getetag = 1;
+            } else if(creationdate && !strcmp(name, "creationdate")) {
+                ret.creationdate = 1;
+            } else {
+                removethis = NULL;
+            }
+            
+            if(removefromlist && removethis) {
+                if(prev) {
+                    prev->next = next;
+                } else {
+                    rq->properties = next;
+                }
+            }
+        }
+        prev = property;
+        property = next;
+    }
+    
+    return ret;
+}
+
+static inline int w_addprop(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        const char *name,
+        char *value)
+{
+    WebdavProperty *p = webdav_dav_property(pool, name);
+    if(!p) {
+        return 1;
+    }
+    if(webdav_property_set_value(p, pool, value)) {
+        return 1;
+    }
+    return res->addproperty(res, p, 200);
+}
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s)
+{
+    if(properties.getresourcetype) {
+        if(S_ISDIR(s->st_mode)) {
+            res->addproperty(res, &dav_resourcetype_collection, 200);
+        } else {
+            res->addproperty(res, &dav_resourcetype_empty, 200);
+        }
+    }
+    if(properties.getcontentlength) {
+        char *buf = pool_malloc(pool, 64);
+        if(!buf) {
+            return 1;
+        }
+        uint64_t contentlength = s->st_size;
+        snprintf(buf, 64, "%" PRIu64 "\0", contentlength);
+        if(w_addprop(res, pool, "getcontentlength", buf)) {
+            return 1;
+        }
+    }
+    if(properties.getlastmodified) {
+        char *buf = pool_malloc(pool, HTTP_DATE_LEN+1);
+        if(!buf) {
+            return 1;
+        }
+        buf[HTTP_DATE_LEN] = 0;
+        
+        struct tm mtms;
+        struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms);
+        
+        if(mtm) {
+            strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
+            if(w_addprop(res, pool, "getlastmodified", buf)) {
+                return 1;
+            }
+        } else {
+            return 1;
+        }
+    }
+    if(properties.creationdate) {
+        // TODO
+    }
+    if(properties.getetag) {
+        char *buf = pool_malloc(pool, 96);
+        if(!buf) {
+            return 1;
+        }
+        snprintf(buf,
+            96,
+            "\"%x-%x\"\0",
+            (int)s->st_size,
+            (int)s->st_mtim.tv_sec);
+        if(w_addprop(res, pool, "getetag", buf)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
--- a/src/server/webdav/webdav.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/webdav.h	Thu Oct 31 10:26:35 2019 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,7 +30,6 @@
 #define	WEBDAV_H
 
 #include "../public/webdav.h"
-#include "../util/strbuf.h"
 
 #include <ucx/map.h>
 #include <ucx/list.h>
@@ -39,7 +38,54 @@
 extern "C" {
 #endif
 
+#define WEBDAV_PATH_MAX 8192
+    
+typedef struct DefaultWebdavData {
+    WebdavVFSProperties vfsproperties;
+} DefaultWebdavData;
+    
+    
+int webdav_init(pblock *pb, Session *sn, Request *rq);
+    
+int webdav_service(pblock *pb, Session *sn, Request *rq);
 
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq);
+
+int webdav_getdepth(Request *rq);
+
+int webdav_options(pblock *pb, Session *sn, Request *rq);
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq);
+int propfind_children(
+        WebdavBackend *webdav,
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path);
+
+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);
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        const char *path,
+        struct stat *s);
+int default_propfind_finish(WebdavPropfindRequest *rq);
 
 #ifdef	__cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.c	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,36 @@
+/*
+ * 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 "xml.h"
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.h	Thu Oct 31 10:26:35 2019 +0100
@@ -0,0 +1,47 @@
+/*
+ * 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 XML_H
+#define XML_H
+
+#include "../public/webdav.h"
+#include <libxml/tree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* XML_H */
+
--- a/templates/config/init.conf	Tue Aug 13 22:14:32 2019 +0200
+++ b/templates/config/init.conf	Thu Oct 31 10:26:35 2019 +0100
@@ -3,4 +3,6 @@
 #
 
 Init fn="admin-init"
+Init fn="webdav-init"
 
+

mercurial