merge branch webdav into default

Sat, 24 Sep 2022 16:26:10 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 24 Sep 2022 16:26:10 +0200
changeset 385
a1f4cb076d2f
parent 210
21274e5950af (current diff)
parent 384
f9e9f2b3e299 (diff)
child 386
b91f8efadb63

merge branch webdav into default

--- a/.hgignore	Tue Aug 13 22:14:32 2019 +0200
+++ b/.hgignore	Sat Sep 24 16:26:10 2022 +0200
@@ -8,3 +8,5 @@
 \.rej$
 \.conflict\~$
 ^nbproject/.*$
+# Default ignored files
+.idea/workspace.xml
--- a/Makefile	Tue Aug 13 22:14:32 2019 +0200
+++ b/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -53,3 +53,5 @@
 install: all
 	cd make; $(MAKE) -f install.mk install
 
+install-bin: all
+	cd make; $(MAKE) -f install.mk install-bin
--- a/configure	Tue Aug 13 22:14:32 2019 +0200
+++ b/configure	Sat Sep 24 16:26:10 2022 +0200
@@ -57,7 +57,7 @@
   --mandir=DIR            man documentation [DATAROOTDIR/man]
 
 Optional Features:
-  --enable-pg
+  --enable-postgresql
 
 __EOF__
 }
@@ -67,40 +67,25 @@
 #
 for ARG in $@
 do
-    if [[ $ARG == --prefix=* ]]; then
-        PREFIX=${ARG:9}
-    elif [[ $ARG = --exec-prefix=* ]]; then
-    	EPREFIX=${ARG:14}
-    elif [[ $ARG = --bindir=* ]]; then
-    	BINDIR=${ARG:9}
-    elif [[ $ARG = --sbindir=* ]]; then
-    	SBINDIR=${ARG:10}
-    elif [[ $ARG = --libdir=* ]]; then
-    	LIBDIR=${ARG:9}
-    elif [[ $ARG = --libexecdir=* ]]; then
-    	LIBEXECDIR=${ARG:13}
-    elif [[ $ARG = --datadir=* ]]; then
-    	DATADIR=${ARG:10}
-    elif [[ $ARG = --sysconfdir=* ]]; then
-    	SYSCONFDIR=${ARG:13}
-    elif [[ $ARG = --sharedstatedir=* ]]; then
-    	SHAREDSTATEDIR=${ARG:17}
-    elif [[ $ARG = --localstatedir=* ]]; then
-    	LOCALSTATEDIR=${ARG:16}
-    elif [[ $ARG = --includedir=* ]]; then
-    	INCLUDEDIR=${ARG:12}
-    elif [[ $ARG = --infodir=* ]]; then
-    	INFODIR=${ARG:10}
-    elif [[ $ARG = --mandir=* ]]; then
-    	MANDIR=${ARG:9}
-    elif [ $ARG = "--help" ]; then
-		printhelp
-        exit 0	
-    elif [[ $ARG == --enable-pg ]]; then
-    	FEATURE_PG=on
-    elif [[ $ARG == --disable-pg ]]; then
-    	unset FEATURE_PG
-    fi
+    case "$ARG" in
+		"--prefix="*)         PREFIX=${ARG#--prefix=} ;;
+		"--exec-prefix="*)    EPREFIX=${ARG#--exec-prefix=} ;;
+		"--bindir="*)         BINDIR=${ARG#----bindir=} ;;
+		"--sbindir="*)        SBINDIR=${ARG#--sbindir=} ;;
+		"--libdir="*)         LIBDIR=${ARG#--libdir=} ;;
+		"--libexecdir="*)     LIBEXECDIR=${ARG#--libexecdir=} ;;
+		"--datadir="*)        DATADIR=${ARG#--datadir=} ;;
+		"--sysconfdir="*)     SYSCONFDIR=${ARG#--sysconfdir=} ;;
+		"--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;;
+		"--localstatedir="*)  LOCALSTATEDIR=${ARG#--localstatedir=} ;;
+		"--includedir="*)     INCLUDEDIR=${ARG#--includedir=} ;;
+		"--infodir="*)        INFODIR=${ARG#--infodir=} ;;
+		"--mandir"*)          MANDIR=${ARG#--mandir} ;;
+		"--help"*) printhelp; exit 1 ;;
+		"--enable-postgresql") FEATURE_POSTGRESQL=on ;;
+		"--disable-postgresql") unset FEATURE_POSTGRESQL ;;
+		"-"*) echo "unknown option: $ARG"; exit 1 ;;
+	esac
 done
 
 # set dir variables
@@ -265,6 +250,13 @@
         fi
         CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libpq`"
         LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libpq`"
+		cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: libpq		
+CFLAGS += -DENABLE_POSTGRESQL
+PLUGINS += postgresql
+TEST_PLUGIN_LDFLAGS += -lwspgtest
+
+__EOF__
 		echo yes
         return 0
     done
@@ -291,6 +283,14 @@
         return 0
     done
 	
+    # dependency openssl 
+    while true
+    do
+        LDFLAGS="$LDFLAGS -lssl -lcrypto"    
+		echo yes
+        return 0
+    done
+	
 	echo no
 	return 1
 }
@@ -359,7 +359,7 @@
     while true
     do
         
-        CFLAGS="$CFLAGS -DBSD"    
+        CFLAGS="$CFLAGS -DBSD -I/usr/local/include"    
         LDFLAGS="$LDFLAGS -lpthread -lm -lldap"    
 		cat >> $TEMP_DIR/make.mk << __EOF__
 # platform dependend source files
@@ -469,6 +469,8 @@
 	ERROR=1
 fi
 
+# Features
+
 
 echo >> $TEMP_DIR/config.mk
 if [ ! -z "${CFLAGS}" ]; then
@@ -481,10 +483,41 @@
     echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
 fi
 
+# Target: postgresql
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+
+# Features
+if [ ! -z "$FEATURE_POSTGRESQL" ]; then
+	# check dependency
+	dependency_libpq
+	if [ $? -ne 0 ]; then
+		# "auto" features can fail and are just disabled in this case
+		if [ $FEATURE_POSTGRESQL != "auto" ]; then
+			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libpq "
+			ERROR=1
+		fi
+	fi
+fi
+
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "POSTGRESQL_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "POSTGRESQL_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "POSTGRESQL_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
 if [ $ERROR -ne 0 ]; then
 	echo
 	echo "Error: Unresolved dependencies"
-	echo $DEPENCIES_FAILED
+	echo $DEPENDENCIES_FAILED
 	rm -Rf $TEMP_DIR
 	exit 1
 fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/development/postgresql_vfs.sql	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,35 @@
+create table Resource (
+    resource_id       serial    primary key,
+    parent_id         int       references Resource(resource_id),
+    nodename          text      not null,
+    iscollection      boolean   not null default false,
+	
+    lastmodified      timestamp not null default current_date,
+    creationdate      timestamp not null default current_date,
+    contentlength     bigint    not null default 0,
+	
+	etag              uuid,
+
+    resoid            oid,
+    
+    unique(parent_id, nodename)
+);
+
+create table Property (
+	property_id       serial   primary key,
+	resource_id       int      references Resource(resource_id) on delete cascade,
+	prefix            text     not null,
+	xmlns             text     not null,
+	pname             text     not null,
+	lang              text,
+	nsdeflist         text,
+	pvalue            text,
+	
+	unique(resource_id, xmlns, pname)       
+);
+
+create type property_name as (
+ xmlns     text,
+ name      text
+);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/development/postgresql_vfs_testdata.sql	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,29 @@
+
+do $$
+declare
+    res_id int;
+begin
+    insert into Resource (nodename, iscollection) values ('', true);
+    res_id := lastval();
+   
+    insert into Resource(parent_id,  nodename, resoid) values
+    (res_id, 'file1.txt', (select lo_create(0))),
+    (res_id, 'file2.txt', (select lo_create(0))),
+    (res_id, 'file3.txt', (select lo_create(0))),
+    (res_id, 'file4.txt', (select lo_create(0)));
+    
+    insert into Resource(parent_id,  nodename, iscollection) values
+    (res_id, 'dir1', true);
+    res_id := lastval();
+    
+    insert into Resource(parent_id,  nodename, iscollection) values
+    (res_id, 'dir2', true);
+    res_id := lastval();
+    
+    insert into Resource(parent_id,  nodename, resoid) values
+    (res_id, 'd1file1.txt', (select lo_create(0))),
+    (res_id, 'd2file1.txt', (select lo_create(0))),
+    (res_id, 'd2file2.txt', (select lo_create(0)));
+
+end $$;
+
--- a/make/configure.vm	Tue Aug 13 22:14:32 2019 +0200
+++ b/make/configure.vm	Sat Sep 24 16:26:10 2022 +0200
@@ -119,46 +119,30 @@
 #set( $D = '$' )
 for ARG in $@
 do
-    if [[ $ARG == --prefix=* ]]; then
-        PREFIX=${D}{ARG:9}
-    elif [[ $ARG = --exec-prefix=* ]]; then
-    	EPREFIX=${D}{ARG:14}
-    elif [[ $ARG = --bindir=* ]]; then
-    	BINDIR=${D}{ARG:9}
-    elif [[ $ARG = --sbindir=* ]]; then
-    	SBINDIR=${D}{ARG:10}
-    elif [[ $ARG = --libdir=* ]]; then
-    	LIBDIR=${D}{ARG:9}
-    elif [[ $ARG = --libexecdir=* ]]; then
-    	LIBEXECDIR=${D}{ARG:13}
-    elif [[ $ARG = --datadir=* ]]; then
-    	DATADIR=${D}{ARG:10}
-    elif [[ $ARG = --sysconfdir=* ]]; then
-    	SYSCONFDIR=${D}{ARG:13}
-    elif [[ $ARG = --sharedstatedir=* ]]; then
-    	SHAREDSTATEDIR=${D}{ARG:17}
-    elif [[ $ARG = --localstatedir=* ]]; then
-    	LOCALSTATEDIR=${D}{ARG:16}
-    elif [[ $ARG = --includedir=* ]]; then
-    	INCLUDEDIR=${D}{ARG:12}
-    elif [[ $ARG = --infodir=* ]]; then
-    	INFODIR=${D}{ARG:10}
-    elif [[ $ARG = --mandir=* ]]; then
-    	MANDIR=${D}{ARG:9}
-    elif [ $ARG = "--help" ]; then
-		printhelp
-        exit 0	
-    #foreach( $opt in $options )
-    elif [[ $ARG == --${opt.getArgument()}=* ]]; then
-    	${opt.getVarName()}=${opt.getArgValue()}
+    case "$ARG" in
+		"--prefix="*)         PREFIX=${D}{ARG#--prefix=} ;;
+		"--exec-prefix="*)    EPREFIX=${D}{ARG#--exec-prefix=} ;;
+		"--bindir="*)         BINDIR=${D}{ARG#----bindir=} ;;
+		"--sbindir="*)        SBINDIR=${D}{ARG#--sbindir=} ;;
+		"--libdir="*)         LIBDIR=${D}{ARG#--libdir=} ;;
+		"--libexecdir="*)     LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
+		"--datadir="*)        DATADIR=${D}{ARG#--datadir=} ;;
+		"--sysconfdir="*)     SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
+		"--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
+		"--localstatedir="*)  LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
+		"--includedir="*)     INCLUDEDIR=${D}{ARG#--includedir=} ;;
+		"--infodir="*)        INFODIR=${D}{ARG#--infodir=} ;;
+		"--mandir"*)          MANDIR=${D}{ARG#--mandir} ;;
+		"--help"*) printhelp; exit 1 ;;
+	#foreach( $opt in $options )
+    	"--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
     #end
-    #foreach( $feature in $features )
-    elif [[ $ARG == --enable-${feature.arg} ]]; then
-    	${feature.getVarName()}=on
-    elif [[ $ARG == --disable-${feature.arg} ]]; then
-    	unset ${feature.getVarName()}
-    #end
-    fi
+	#foreach( $feature in $features )
+		"--enable-${feature.arg}") ${feature.getVarName()}=on ;;
+		"--disable-${feature.arg}") unset ${feature.getVarName()} ;;
+	#end
+		"-"*) echo "unknown option: $ARG"; exit 1 ;;
+	esac
 done
 
 # set dir variables
@@ -534,6 +518,23 @@
 fi
 #end
 
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+	# check dependency
+	dependency_$dependency
+	if [ $? -ne 0 ]; then
+		# "auto" features can fail and are just disabled in this case
+		if [ $${feature.getVarName()} != "auto" ]; then
+			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+			ERROR=1
+		fi
+	fi
+#end
+fi
+#end
+
 #foreach( $opt in $target.options )
 # Option: --${opt.argument}
 if [ -z ${D}${opt.getVarName()} ]; then
@@ -593,7 +594,7 @@
 if [ $ERROR -ne 0 ]; then
 	echo
 	echo "Error: Unresolved dependencies"
-	echo $DEPENCIES_FAILED
+	echo $DEPENDENCIES_FAILED
 	rm -Rf $TEMP_DIR
 	exit 1
 fi
--- a/make/install.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/make/install.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -30,25 +30,13 @@
 
 include ../config.mk
 
-install:
-	@echo "install Webserver to $(WS_INSTALL_DIR)"
-	mkdir -p $(INSTALL_DIR)/bin
-	mkdir -p $(INSTALL_DIR)/lib
-	mkdir -p $(INSTALL_DIR)/config
-	mkdir -p $(INSTALL_DIR)/docs
-	mkdir -p $(INSTALL_DIR)/logs
-	mkdir -p $(INSTALL_DIR)/include
+install: install-dir install-bin
 	@echo "copy config"
 	cp ../templates/config/init.conf $(INSTALL_DIR)/config/init.conf
 	cp ../templates/config/obj.conf $(INSTALL_DIR)/config/obj.conf
 	cp ../templates/config/mime.types $(INSTALL_DIR)/config/mime.types
 	cp ../templates/config/acl.conf $(INSTALL_DIR)/config/acl.conf
 	sed s:%%WS_HOST%%:$(HOST):g ../templates/config/server.template > $(INSTALL_DIR)/config/server.conf
-	@echo "copy binaries"
-	cp ../build/bin/webservd$(APP_EXT) $(INSTALL_DIR)/bin/
-	cp ../build/bin/wstool$(APP_EXT) $(INSTALL_DIR)/bin/
-	cp ../build/lib/libucx$(LIB_EXT) $(INSTALL_DIR)/lib/
-	cp ../build/lib/libwscfg$(LIB_EXT) $(INSTALL_DIR)/lib/
 	@echo "copy includes"
 	cp ../src/server/public/nsapi.h $(INSTALL_DIR)/include/nsapi.h
 	cp ../src/server/public/auth.h $(INSTALL_DIR)/include/auth.h
@@ -64,3 +52,18 @@
 	chmod +x $(INSTALL_DIR)/bin/reconfig
 	@echo "copy docs"
 	cp -R ../templates/docs $(INSTALL_DIR)/
+	
+install-dir:
+	@echo "install Webserver to $(WS_INSTALL_DIR)"
+	mkdir -p $(INSTALL_DIR)/bin
+	mkdir -p $(INSTALL_DIR)/lib
+	mkdir -p $(INSTALL_DIR)/config
+	mkdir -p $(INSTALL_DIR)/docs
+	mkdir -p $(INSTALL_DIR)/logs
+	mkdir -p $(INSTALL_DIR)/include
+
+install-bin: install-dir
+	@echo "copy binaries"
+	cp ../build/bin/webservd$(APP_EXT) $(INSTALL_DIR)/bin/
+	cp ../build/bin/wstool$(APP_EXT) $(INSTALL_DIR)/bin/
+	cp ../build/lib/* $(INSTALL_DIR)/lib/
--- a/make/project.xml	Tue Aug 13 22:14:32 2019 +0200
+++ b/make/project.xml	Sat Sep 24 16:26:10 2022 +0200
@@ -23,7 +23,7 @@
 	</dependency>
 	
 	<dependency platform="bsd" not="macos">
-		<cflags>-DBSD</cflags>
+		<cflags>-DBSD -I/usr/local/include</cflags>
 		<ldflags>-lpthread -lm -lldap</ldflags>
 		<make>
 # platform dependend source files
@@ -73,16 +73,28 @@
 		<pkgconfig>openssl</pkgconfig>
 	</dependency>
 	
+	<dependency name="openssl">
+		<ldflags>-lssl -lcrypto</ldflags>
+	</dependency>
+	
 	<!-- optional dependencies -->
 	<dependency name="libpq">
 		<pkgconfig>libpq</pkgconfig>
+		<make>
+CFLAGS += -DENABLE_POSTGRESQL
+PLUGINS += postgresql
+TEST_PLUGIN_LDFLAGS += -lwspgtest
+		</make>
 	</dependency>
 	
 	<target>
-		<feature name="pg" default="false">
+		<dependencies>libxml2,openssl</dependencies>
+	</target>
+	
+	<target name="postgresql">
+		<feature name="postgresql" default="false">
 			<dependencies>libpq</dependencies>
 		</feature>
-		<dependencies>libxml2,openssl</dependencies>
 	</target>
 </project>
 
--- a/make/suncc.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/make/suncc.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -2,9 +2,9 @@
 # suncc toolchain
 #
 
-CFLAGS += -xc99 -g
-LDFLAGS += -Wl,-R,'$$ORIGIN/../lib'
+CFLAGS += -xc99 -g -m64
+LDFLAGS += -m64 -Wl,-R,'$$ORIGIN/../lib'
 
 SHLIB_CFLAGS = -Kpic
-SHLIB_LDFLAGS = -G
+SHLIB_LDFLAGS = -G -m64
 
--- a/make/toolchain.sh	Tue Aug 13 22:14:32 2019 +0200
+++ b/make/toolchain.sh	Sat Sep 24 16:26:10 2022 +0200
@@ -11,161 +11,171 @@
 
 check_c_compiler()
 {
-    cat > $TEMP_DIR/test.c << __EOF__
+	cat > $TEMP_DIR/test.c << __EOF__
 /* test file */
 #include <stdio.h>
 int main(int argc, char **argv) {
-#if defined(__GNUC__)
-    printf("gcc\n");
-#elif defined(__clang__)
-    printf("clang\n");
+#if defined(__clang__)
+	printf("clang\n");
+#elif defined(__GNUC__)
+	printf("gcc\n");
 #elif defined(__sun)
-    printf("suncc\n");
+	printf("suncc\n");
 #else
-    printf("unknown\n");
+	printf("unknown\n");
 #endif
-    return 0;
+	return 0;
 }
 __EOF__
-    rm -f $TEMP_DIR/checkcc
-    $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
-    
-    if [ $? -ne 0 ]; then
-        return 1
-    fi
-    return 0
+	rm -f $TEMP_DIR/checkcc
+	$1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
+	
+	if [ $? -ne 0 ]; then
+		return 1
+	fi
+	return 0
 }
 
 check_cpp_compiler()
 {
-    cat > $TEMP_DIR/test.cpp << __EOF__
+	cat > $TEMP_DIR/test.cpp << __EOF__
 /* test file */
 #include <iostream>
 int main(int argc, char **argv) {
-#if defined(__GNUC__)
-    std::cout << "gcc" << std::endl;
-#elif defined(__clang__)
-    std::cout << "clang" << std::endl;
+#if defined(__clang__)
+	std::cout << "clang" << std::endl;
+#elif defined(__GNUC__)
+	std::cout << "gcc" << std::endl;
 #elif defined(__sun)
 	std::cout << "suncc" << std::endl;
 #else
-    std::cout << "unknown" << std::endl;
+	std::cout << "unknown" << std::endl;
 #endif
-    return 0;
+	return 0;
 }
 __EOF__
-    rm -f $TEMP_DIR/checkcc
-    $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
-    
-    if [ $? -ne 0 ]; then
-        return 1
-    fi
-    return 0
+	rm -f $TEMP_DIR/checkcc
+	$1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
+	
+	if [ $? -ne 0 ]; then
+		return 1
+	fi
+	return 0
 }
 
 printf "detect C compiler... "
 
 for COMP in $C_COMPILERS
 do
-    check_c_compiler $COMP
-    if [ $? -ne 0 ]; then
-	    if [ ! -z "$CC" ]; then
-        	if [ $COMP = $CC ]; then
-            	echo "$CC is not a working C Compiler"
-            	TOOLCHAIN_DETECTION_ERROR="error"
-            	break
-        	fi
+	check_c_compiler $COMP
+	if [ $? -ne 0 ]; then
+		if [ ! -z "$CC" ]; then
+			if [ $COMP = $CC ]; then
+				echo "$CC is not a working C Compiler"
+				TOOLCHAIN_DETECTION_ERROR="error"
+				break
+			fi
 		fi
-    else
-        TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+	else
+		TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+		USE_TOOLCHAIN=$TOOLCHAIN_NAME
 		if [ $COMP = "cc" ]; then
-		   # we have found a working compiler, but in case
-		   # the compiler is gcc or clang, we try to use
-		   # these commands and not 'cc'
-		   TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-		   if [ $TOOLCHAIN_NAME = "gcc" ]; then
-		       check_c_compiler "gcc"
-			   if [ $? -eq 0 ]; then
-			       COMP=gcc
-			   fi
-		   fi
-		   if [ $TOOLCHAIN_NAME = "clang" ]; then
-		       check_c_compiler "clang"
-			   if [ $? -eq 0 ]; then
-			       COMP=clang
-			   fi
-		   fi
+			# we have found a working compiler, but in case
+			# the compiler is gcc or clang, we try to use
+			# these commands and not 'cc'
+			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+			if [ $TOOLCHAIN_NAME = "gcc" ]; then
+				check_c_compiler "gcc"
+				if [ $? -eq 0 ]; then
+					COMP=gcc
+					USE_TOOLCHAIN="gcc"
+				fi
+			fi
+			if [ $TOOLCHAIN_NAME = "clang" ]; then
+				check_c_compiler "clang"
+				if [ $? -eq 0 ]; then
+					COMP=clang
+					USE_TOOLCHAIN="clang"
+				fi
+			fi
 		fi
 		
-		TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-        TOOLCHAIN_CC=$COMP
-        echo $COMP
-        break
-    fi
+		TOOLCHAIN_NAME=$USE_TOOLCHAIN
+		TOOLCHAIN_CC=$COMP
+		echo $COMP
+		break
+	fi
 done
 if [ -z $TOOLCHAIN_CC ]; then
-    echo "not found"
+	echo "not found"
 fi
 
 printf "detect C++ compiler... "
 
 for COMP in $CPP_COMPILERS
 do
-    check_cpp_compiler $COMP
-    if [ $? -ne 0 ]; then
+	check_cpp_compiler $COMP
+	if [ $? -ne 0 ]; then
 		if [ ! -z "$CXX" ]; then
-        	if [ $COMP = $CXX ]; then
-                echo "$CC is not a working C++ Compiler"
-                TOOLCHAIN_DETECTION_ERROR="error"
-                break
-            fi
+			if [ $COMP = $CXX ]; then
+				echo "$CC is not a working C++ Compiler"
+				TOOLCHAIN_DETECTION_ERROR="error"
+				break
+			fi
 		fi
-    else
-        if [ $COMP = "CC" ]; then
-		   # we have found a working compiler, but in case
-		   # the compiler is gcc or clang, we try to use
-		   # these commands and not 'cc'
-		   TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-		   if [ $TOOLCHAIN_NAME = "gcc" ]; then
-		       check_cpp_compiler "g++"
-			   if [ $? -eq 0 ]; then
-			       COMP=g++
-			   fi
-		   fi
-		   if [ $TOOLCHAIN_NAME = "clang" ]; then
-		       check_cpp_compiler "clang++"
-			   if [ $? -eq 0 ]; then
-			       COMP=clang++
-			   fi
-		   fi
+	else
+		if [ $COMP = "CC" ]; then
+			# we have found a working compiler, but in case
+			# the compiler is gcc or clang, we try to use
+			# these commands and not 'cc'
+			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+			USE_TOOLCHAIN=$TOOLCHAIN_NAME
+			if [ $TOOLCHAIN_NAME = "gcc" ]; then
+				check_cpp_compiler "g++"
+				if [ $? -eq 0 ]; then
+				   COMP=g++
+				   USE_TOOLCHAIN="gcc"
+				fi
+			fi
+			if [ $TOOLCHAIN_NAME = "clang" ]; then
+				check_cpp_compiler "clang++"
+				if [ $? -eq 0 ]; then
+				   COMP=clang++
+				   USE_TOOLCHAIN="clang"
+				fi
+			fi
 		fi
 		
-		TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-        TOOLCHAIN_CXX=$COMP
-        echo $COMP
-        break
-    fi
+		TOOLCHAIN_NAME=$USE_TOOLCHAIN
+		TOOLCHAIN_CXX=$COMP
+		echo $COMP
+		break
+	fi
 done
 if [ -z $TOOLCHAIN_CXX ]; then
-    echo "not found"
+	echo "not found"
 fi
 
 TOOLCHAIN_LD=$TOOLCHAIN_CC
 
 if [ -z "$TOOLCHAIN_NAME" ]; then
-    TOOLCHAIN_DETECTION_ERROR="error"
+	TOOLCHAIN_DETECTION_ERROR="error"
 else
-    cat >> $TEMP_DIR/config.mk << __EOF__
+	cat >> $TEMP_DIR/config.mk << __EOF__
 # toolchain
 __EOF__
 	echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
 	if [ ! -z "$TOOLCHAIN_CXX" ]; then
 		echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
 	fi
-    echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
-    
+	echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
+	echo >> $TEMP_DIR/config.mk
+	
 	cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
-    if [ $? -eq 0 ]; then 
-        echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
-    fi
+	if [ $? -eq 0 ]; then 
+		echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
+	else
+		echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
+		echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
+	fi
 fi
--- a/src/server/Makefile	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -62,7 +62,7 @@
 OBJ_DIRS = daemon safs ucx util webdav config admin plugins test
 MK_OBJ_DIRS = $(OBJ_DIRS:%=$(OBJ_DIR)server/%)
 
-CFLAGS += -I../
+CFLAGS += -I../ucx/
 LDFLAGS += -lucx
 
 preparation: $(MK_OBJ_DIRS)
@@ -76,8 +76,8 @@
 $(LIB_WSCFG): $(CONFOBJS)
 	$(CC) $(SHLIB_LDFLAGS) -o $@ $(CONFOBJS)
 
-$(TEST_TARGET): $(TESTOBJS)
-	$(CXX) -o $(TEST_TARGET) $(TESTOBJS) -L$(BUILD_ROOT)/build/lib $(LDFLAGS)
+$(TEST_TARGET): $(TESTOBJS) $(PLUGINS)
+	$(CXX) -o $(TEST_TARGET) $(TESTOBJS) -L$(BUILD_ROOT)/build/lib $(LDFLAGS) $(TEST_PLUGIN_LDFLAGS)
 
 
 $(PLUGINS): $(MAIN_TARGET) FORCE
--- a/src/server/admin/admin.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/admin/admin.c	Sat Sep 24 16:26:10 2022 +0200
@@ -34,7 +34,6 @@
 #include "../util/pblock.h"
 
 #include "../config/conf.h"
-#include "../config/serverconf.h"
 #include "../config/objconf.h"
 
 static Page *root_page;
--- a/src/server/config/keyfile.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/config/keyfile.c	Sat Sep 24 16:26:10 2022 +0200
@@ -31,7 +31,7 @@
 
 #include "keyfile.h"
 
-KeyfileConfig *load_keyfile_config(char *file) {
+KeyfileConfig *load_keyfile_config(const char *file) {
     FILE *in = fopen(file, "r");
     if(in == NULL) {
         return NULL;
@@ -39,7 +39,7 @@
     
     KeyfileConfig *conf = malloc(sizeof(KeyfileConfig));
     conf->parser.parse = keyfile_parse;
-    conf->file = file;
+    conf->file = strdup(file);
     conf->users = NULL;
 
     int r = cfg_parse_basic_file((ConfigParser*)conf, in);
--- a/src/server/config/keyfile.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/config/keyfile.h	Sat Sep 24 16:26:10 2022 +0200
@@ -50,7 +50,7 @@
     size_t               numgroups;
 } KeyfileEntry;
 
-KeyfileConfig *load_keyfile_config(char *file);
+KeyfileConfig *load_keyfile_config(const char *file);
 void free_keyfile_config(KeyfileConfig *conf);
 int keyfile_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line);
 
--- a/src/server/config/mimeconf.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/config/mimeconf.c	Sat Sep 24 16:26:10 2022 +0200
@@ -38,7 +38,7 @@
     void           *ptr;
 } ucx_regdestr;
 
-MimeConfig *load_mime_config(char *file) {
+MimeConfig *load_mime_config(const char *file) {
     FILE *in = fopen(file, "r");
     if(in == NULL) {
         return NULL;
@@ -46,7 +46,6 @@
     
     MimeConfig *conf = malloc(sizeof(MimeConfig));
     conf->parser.parse = mimeconf_parse;
-    conf->file = file;
     conf->directives = NULL;
     conf->ntypes = 0;
     int r = cfg_parse_basic_file((ConfigParser*)conf, in);
--- a/src/server/config/mimeconf.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/config/mimeconf.h	Sat Sep 24 16:26:10 2022 +0200
@@ -37,7 +37,6 @@
 
 typedef struct _mime_conf {
     ConfigParser parser;
-    char         *file;
     UcxList      *directives; // MimeDirective list
     int          ntypes;
 } MimeConfig;
@@ -47,7 +46,7 @@
     UcxList *exts; // char*
 } MimeDirective;
 
-MimeConfig *load_mime_config(char *file);
+MimeConfig *load_mime_config(const char *file);
 
 void free_mime_config(MimeConfig *conf);
 
--- a/src/server/config/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/config/objs.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -33,10 +33,10 @@
 CONFOBJ = objconf.o
 CONFOBJ += conf.o
 CONFOBJ += initconf.o
-CONFOBJ += serverconf.o
 CONFOBJ += mimeconf.o
 CONFOBJ += acl.o
 CONFOBJ += keyfile.o
+CONFOBJ += serverconfig.o
 
 CONFOBJS = $(CONFOBJ:%=$(CONF_OBJPRE)%)
 CONFSOURCE = $(CONFOBJ:%.o=config/%.c)
--- a/src/server/config/serverconf.c	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2013 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 "serverconf.h"
-
-ServerConfig *load_server_config(char *file) {
-    FILE *in = fopen(file, "r");
-    if(in == NULL) {
-        return NULL;
-    }
-
-    ServerConfig *conf = malloc(sizeof(ServerConfig));
-    conf->parser.parse = serverconf_parse;
-    conf->file = file;
-    conf->obj = NULL;
-    
-    conf->objects = ucx_map_new(16);
-    int r = cfg_parse_basic_file((ConfigParser*)conf, in);
-    cfg_map_destr(conf->parser.mp->pool, conf->objects);
-    if(r != 0) {
-        // TODO: free
-        return NULL;
-    }
-    
-    fclose(in);
-
-    return conf;
-}
-
-void free_server_config(ServerConfig *conf) {
-    ucx_mempool_destroy(conf->parser.mp->pool);
-    free(conf);
-}
-
-int serverconf_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line){
-    ServerConfig *conf = p;
-    UcxAllocator *mp = conf->parser.mp;
-    
-    begin->type = cfg_get_line_type(line);
-    switch(begin->type) {
-        case LINE_BEGIN_TAG: {
-            ConfigTag *tag = cfg_parse_begin_tag(line, conf->parser.mp);
-
-            // create server config object
-            ServerConfigObject *obj = OBJ_NEW(
-                    conf->parser.mp,
-                    ServerConfigObject);
-            obj->type = tag->name;
-            obj->begin = begin;
-            obj->end = end;
-            obj->directives = NULL;
-
-            // add object to server config
-            UcxList *list = ucx_map_sstr_get(conf->objects, obj->type);
-            list = ucx_list_append_a(mp, list, obj);
-            ucx_map_sstr_put(conf->objects, obj->type, list);
-            conf->obj = obj;
-
-            break;
-        }
-        case LINE_END_TAG: {
-            sstr_t tag = cfg_get_end_tag_name(line);
-            if(sstrcmp(tag, conf->obj->type) != 0) {
-                log_ereport(LOG_FAILURE, "server.conf: syntax error: wrong close tag");
-                log_ereport(LOG_FAILURE, "open tag: %s   close tag: %s", sstrdup(tag).ptr, sstrdup(conf->obj->type).ptr);
-                exit(-1);
-            }
-            conf->obj = NULL;
-
-            break;
-        }
-        case LINE_DIRECTIVE: {
-            if(conf->obj == NULL) {
-                log_ereport(LOG_FAILURE, "server.conf: directive outside of object");
-                exit(-1);
-            }
-
-            ConfigDirective *d = cfg_parse_directive(line, conf->parser.mp);
-            d->begin = begin;
-            d->end = end;
-
-            //printf("%s.%s\n", conf->obj->type.ptr, d->directive_type.ptr);
-            conf->obj->directives = ucx_list_append_a(mp, conf->obj->directives, d);
-        }
-    }
-    return 0;
-}
-
-
-UcxList* srvcfg_get_listeners(ServerConfig *cfg, UcxAllocator *mp, int *error) {
-    mp = mp ? mp : cfg->parser.mp;
-    
-    UcxList *list = ucx_map_sstr_get(cfg->objects, sstrn("Listener", 8));
-    UcxList *lslist = NULL;
-    UCX_FOREACH(elm, list) {
-        ServerConfigObject *ls = elm->data;
-        sstr_t name = cfg_directivelist_get_str(ls->directives, sstr("Name"));
-        sstr_t port = cfg_directivelist_get_str(ls->directives, sstr("Port"));
-        sstr_t vs = cfg_directivelist_get_str(
-                ls->directives,
-                sstr("DefaultVS"));
-        sstr_t threadpool = cfg_directivelist_get_str(
-                ls->directives,
-                sstr("Threadpool"));
-        
-        CfgListener *listener = OBJ_NEW_N(mp, CfgListener);
-        // threadpool is optional, all other configs must be set
-        if(!name.ptr || !port.ptr || !vs.ptr) {
-            // TODO: log error
-            *error = 1;
-            listener->cfg_correct = 0;
-        } else {
-            listener->cfg_correct = 1;
-        }
-        
-        if(name.ptr) {
-            listener->name = sstrdup_a(mp, name);
-        }
-        if(port.ptr) {
-            // don't expect that port is null terminated, sstrdup it to be sure
-            sstr_t portdp = sstrdup(port);
-            listener->port = atoi(portdp.ptr);
-            free(portdp.ptr);
-        }
-        if(vs.ptr) {
-            listener->vs = sstrdup_a(mp, vs);
-        }
-        if(threadpool.ptr) {
-            listener->threadpool = sstrdup_a(mp, threadpool);
-        }
-        
-        lslist = ucx_list_append_a(mp, lslist, listener);
-    }
-    
-    return lslist;
-}
--- a/src/server/config/serverconf.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2013 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 SERVERCONF_H
-#define	SERVERCONF_H
-
-#include "conf.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-typedef struct _server_conf_obj {
-    ConfigLine   *begin;
-    ConfigLine   *end;
-
-    sstr_t       type;
-    UcxList     *directives;
-} ServerConfigObject;
-
-typedef struct _server_conf {
-    ConfigParser parser;
-    char         *file;
-    UcxMap       *objects;  // contains UcxList of ServerConfigObject
-    // parser temp vars
-    ServerConfigObject *obj;
-} ServerConfig;
-
-// server.conf objects
-
-typedef struct _cfg_listener {
-    ServerConfigObject   *cfgobj;
-    sstr_t               name;
-    sstr_t               vs;
-    sstr_t               threadpool;
-    sstr_t               address;
-    int                  port;
-    int                  nacceptors;
-    int                  cfg_correct;
-} CfgListener;
-
-typedef struct _cfg_keyfile_authdb {
-    sstr_t file;
-} CfgKeyfileAuthDB;
-
-typedef struct _cfg_ldap_authdb {
-    sstr_t host;
-    int    port;
-    sstr_t basedn;
-    sstr_t binddn;
-    sstr_t bindpw;
-} CfgLDAPAuthDB;
-
-union authdb {
-    CfgKeyfileAuthDB keyfile;
-    CfgLDAPAuthDB    ldap;
-};
-
-enum authdb_type {
-    AUTHDB_TYPE_KEYFILE,
-    AUTHDB_TYPE_LDAP
-};
-
-typedef struct _cfg_authdb {
-    sstr_t           name;
-    enum authdb_type type;
-    union authdb     cfg;
-    int              cfg_correct;
-} CfgAuthDB;
-
-
-/*
- * Loads and parses a server configuration file. The function only structures
- * the file content to configuration objects with directives. The semantics
- * of the objects and directives can be extracted with the srvcfg_* funcsions.
- */
-ServerConfig *load_server_config(char *file);
-
-/*
- * frees the ServerConfig object
- */
-void free_server_config(ServerConfig *conf);
-
-// private - parses a server.conf line
-int serverconf_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line);
-
-
-UcxList* srvcfg_get_listeners(ServerConfig *cfg, UcxAllocator *mp, int *error);
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* SERVERCONF_H */
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/config/serverconfig.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,361 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "serverconfig.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+
+ServerConfig* serverconfig_load(const char *file) {
+    FILE *in = fopen(file, "r");
+    if(in == NULL) {
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    if(!buf) {
+        fclose(in);
+        return NULL;
+    }
+    
+    ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write);
+    fclose(in);
+    
+    ServerConfig *scfg = serverconfig_parse(scstrn(buf->space, buf->size));
+    
+    ucx_buffer_free(buf);
+    return scfg;
+}
+
+
+static CFGToken get_next_token(scstr_t content, int *pos) {
+    CFGToken token = { {NULL, 0}, CFG_NO_TOKEN };
+    CFGTokenType type = CFG_TOKEN;
+    
+    int start = *pos;
+    
+    int token_begin = -1;
+    int token_end = content.length-1;
+    
+    int quote = 0;
+    int comment = 0;
+    
+    int i;
+    char prev = 0;
+    for(i=start;i<content.length;i++) {
+        char c = content.ptr[i];
+        if(c == '\n') {
+            if(quote) {
+                *pos = i;
+                return token; // error
+            } else if(start == i) {
+                // single newline char token
+                type = CFG_TOKEN_NEWLINE;
+                token_begin = i;
+                token_end = i+1;
+                break;
+            }
+            
+            token_end = i;
+            if(token_begin < 0) {
+                // only space/comment token
+                token_begin = start;
+                type = comment ? CFG_TOKEN_COMMENT : CFG_TOKEN_SPACE;
+            }
+            // make sure next run will return current newline char as token
+            i--;
+            break;
+        } else if(quote) {
+            if(c == '"' && prev != '\\') {
+                quote = 0;
+            }
+        } else if(comment) {
+            // ignore
+            if(c == '\n') {
+                comment = 0;
+            }
+        } else if(c == '#') {
+            comment = 1;
+        } else if(isspace(c)) {
+            if(token_begin >= 0) {
+                token_end = i;
+                break;
+            }
+        } else if(c == '"') {
+            quote = 1;
+            if(token_begin < 0) {
+                token_begin = i;
+            }
+        } else if(token_begin < 0) {
+            token_begin = i;
+        }
+        prev = c;
+    }
+    
+    *pos = i + 1;
+    
+    if(token_begin < 0) {
+        return token; // error
+    }
+    
+    token.type = type;
+    token.content = scstrsubsl(content, token_begin, token_end - token_begin);
+    return token;
+}
+
+/*
+static void test_print_config(ConfigNode *parent) {
+    UCX_FOREACH(elm, parent->children) {
+        ConfigNode *node = elm->data;
+        
+        if(node->type == CONFIG_NODE_SPACE) {
+            printf("sp: %s", node->text_begin.ptr);
+        } else if(node->type == CONFIG_NODE_COMMENT) {
+            printf("cm: %s", node->text_begin.ptr);
+        } else if(node->type == CONFIG_NODE_OBJECT) {
+            printf("o{: %s : %s", node->name.ptr, node->text_begin.ptr);
+            test_print_config(node);
+            printf("o}: %s", node->text_end.ptr);
+        } else if(node->type == CONFIG_NODE_DIRECTIVE) {
+            printf("di: %s", node->text_begin.ptr);
+        } else {
+            printf("fk: %s", node->text_begin.ptr);
+        }
+    }
+}
+*/
+
+static void config_arg_set_value(UcxAllocator *a, ConfigArg *arg, CFGToken token) {
+    scstr_t nv = scstrchr(token.content, '=');
+    if(!nv.ptr) {
+        arg->value = sstrdup_a(a, token.content);
+    } else {
+        intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr);
+        scstr_t name = token.content;
+        name.length = (size_t)eq;
+        
+        scstr_t value = nv;
+        value.ptr++;
+        value.length--;
+        if(value.length > 1 && value.ptr[0] == '"' && value.ptr[value.length-1] == '"') {
+            value.ptr++;
+            value.length -= 2; // remove quote
+        }
+        
+        arg->name = sstrdup_a(a, name);
+        arg->value = sstrdup_a(a, value);
+    }
+}
+
+ServerConfig* serverconfig_parse(scstr_t content) {
+    UcxMempool *mp = ucx_mempool_new(512);
+    if(!mp) return NULL;
+    UcxAllocator *a = mp->allocator;
+    
+    ServerConfig *config = ucx_mempool_malloc(mp, sizeof(ServerConfig));
+    if(!config) {
+        ucx_mempool_destroy(mp);
+        return NULL;
+    }
+    config->mp = mp;
+    
+    // PARSE:
+    // first non space/comment token is directive/object name
+    // following tokens are arguments
+    // newline starts new directive
+    // '{' converts directive to object and following directives will
+    // be placed into the object
+    int pos = 0; // needed for tokenizer
+    CFGToken token;
+    
+    ConfigNode *root_obj = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
+    root_obj->type = CONFIG_NODE_OBJECT;
+    
+    UcxList *node_stack = ucx_list_prepend(NULL, root_obj);
+    
+    ConfigNode *current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
+    current->type = CONFIG_NODE_SPACE;
+    ConfigNode *obj = NULL;
+    int obj_closed = 0;
+    
+    int text_start = 0;
+    int err = 0;
+    while((token = get_next_token(content, &pos)).type != CFG_NO_TOKEN) {
+        //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr);
+        
+        switch(token.type) {
+            case CFG_NO_TOKEN: break;
+            case CFG_TOKEN_COMMENT: {
+                if(current->type == CONFIG_NODE_SPACE) {
+                    current->type = CONFIG_NODE_COMMENT;
+                }
+                break;
+            }
+            case CFG_TOKEN_SPACE: break;
+            case CFG_TOKEN_NEWLINE: {
+                scstr_t line = scstrsubsl(content, text_start, pos - text_start);
+                text_start = pos;
+                
+                sstr_t line_cp = sstrdup_a(a, line);
+
+                ConfigNode *parent = node_stack->data;
+                if(current->type == CONFIG_NODE_CLOSE_OBJECT) {
+                    parent->text_end = line_cp;
+                    node_stack = ucx_list_remove_a(a, node_stack, node_stack);
+                } else if(current->type == CONFIG_NODE_OPEN_OBJECT) {
+                    sstr_t new_textbegin = sstrcat_a(a, 2, obj->text_begin, line_cp);
+                    alfree(a, obj->text_begin.ptr);
+                    alfree(a, line_cp.ptr);
+                    obj->text_begin = new_textbegin;
+                } else {
+                    current->text_begin = line_cp;
+                    ConfigNode *parent = node_stack->data;
+                    parent->children = ucx_list_append_a(a, parent->children, current);
+                }
+                
+                if(obj && obj->type == CONFIG_NODE_OBJECT) {
+                    node_stack = ucx_list_prepend_a(a, node_stack, obj);
+                    obj = NULL;
+                }
+
+                current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
+                current->type = CONFIG_NODE_SPACE;
+                
+                obj_closed = 0;
+                break;
+            }
+            case CFG_TOKEN: {
+                if(!sstrcmp(token.content, S("{"))) {
+                    if(!obj) {
+                        err = 1;
+                        break;
+                    }
+                    obj->type = CONFIG_NODE_OBJECT;
+                    if(current != obj) {
+                        current->type = CONFIG_NODE_OPEN_OBJECT;
+                    }
+                } else if(!sstrcmp(token.content, S("}"))) {
+                    obj_closed = 1; // force newline before next directive
+                    obj = NULL;
+                    current->type = CONFIG_NODE_CLOSE_OBJECT;
+                } else {
+                    if(obj_closed) {
+                        err = 1;
+                        break;
+                    }
+                    
+                    if(!current->name.ptr) {
+                        current->name = sstrdup_a(a, token.content);
+                        current->type = CONFIG_NODE_DIRECTIVE;
+                        obj = current;
+                    } else {
+                        ConfigArg *arg = ucx_mempool_calloc(mp, 1, sizeof(ConfigArg));
+                        config_arg_set_value(a, arg, token);
+                        current->args = ucx_list_append_a(a, current->args, arg);
+                    }
+                }
+                break;
+            }
+        }
+        
+        if(err) {
+            break;
+        }
+    }
+    
+    if(pos < content.length || err) {
+        // content not fully parsed because of an error
+        ucx_mempool_destroy(mp);
+        return NULL;
+    }
+    
+    //test_print_config(&root_obj);
+    config->root = root_obj;
+    config->tab = sstrdup_a(a, SC("\t"));
+    
+    return config;
+}
+
+void serverconfig_free(ServerConfig *cfg) {
+    ucx_mempool_destroy(cfg->mp);
+}
+
+ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
+    UCX_FOREACH(elm, parent->children) {
+        ConfigNode *node = elm->data;
+        if(node->type == type && !sstrcasecmp(node->name, name)) {
+            return node;
+        }
+    }
+    return NULL;
+}
+
+UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
+    UcxList *nodes = NULL;
+    
+    UCX_FOREACH(elm, parent->children) {
+        ConfigNode *node = elm->data;
+        if(node->type == type && !sstrcasecmp(node->name, name)) {
+            nodes = ucx_list_append(nodes, node);
+        }
+    }
+    
+    return nodes;
+}
+
+scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name) {
+    ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name);
+    if(node && ucx_list_size(node->args) == 1) {
+        ConfigArg *arg = node->args->data;
+        return SCSTR(arg->value);
+    }
+    return scstrn(NULL, 0);
+}
+
+sstr_t serverconfig_arg_name_value(UcxAllocator *a, scstr_t str, scstr_t *name) {
+    int valstart = 0;
+    for(int i=0;i<str.length;i++) {
+        if(str.ptr[i] == '=') {
+            if(name) {
+                name->ptr = str.ptr;
+                name->length = i;
+            }
+            valstart = i + 1;
+            break;
+        }
+    }
+    
+    sstr_t ret;
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/config/serverconfig.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WS_CONFIG_SERVERCONFIG_H
+#define WS_CONFIG_SERVERCONFIG_H
+
+#include <ucx/list.h>
+#include <ucx/map.h>
+#include <ucx/mempool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct ServerConfig    ServerConfig;
+typedef struct ConfigNode      ConfigNode;
+typedef struct ConfigArg       ConfigArg;
+typedef struct CFGToken        CFGToken;
+
+typedef enum ConfigNodeType    ConfigNodeType;
+typedef enum CFGTokenType      CFGTokenType;
+
+struct ServerConfig {
+    UcxMempool *mp;
+    ConfigNode *root;
+    sstr_t tab;
+};
+
+enum ConfigNodeType {
+    CONFIG_NODE_SPACE = 0,
+    CONFIG_NODE_COMMENT,
+    CONFIG_NODE_OBJECT,
+    CONFIG_NODE_DIRECTIVE,
+    CONFIG_NODE_OPEN_OBJECT,
+    CONFIG_NODE_CLOSE_OBJECT
+};
+
+struct ConfigNode {
+    sstr_t text_begin;
+    sstr_t text_end; 
+    ConfigNodeType type;
+    
+    sstr_t name;
+    UcxList *args;
+    UcxList *children;
+};
+
+struct ConfigArg {
+    sstr_t name;
+    sstr_t value;
+};
+
+
+enum CFGTokenType {
+    CFG_NO_TOKEN = 0,
+    CFG_TOKEN_COMMENT,
+    CFG_TOKEN_SPACE,
+    CFG_TOKEN_NEWLINE,
+    CFG_TOKEN
+};
+
+struct CFGToken {
+    scstr_t content;
+    CFGTokenType type;
+};
+
+ServerConfig* serverconfig_load(const char *file);
+
+ServerConfig* serverconfig_parse(scstr_t content);
+
+void serverconfig_free(ServerConfig *cfg);
+
+ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name);
+
+UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name);
+
+scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name);
+
+sstr_t serverconfig_arg_name_value(UcxAllocator *a, scstr_t str, scstr_t *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_CONFIG_SERVERCONFIG_H */
+
--- a/src/server/daemon/acl.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/acl.c	Sat Sep 24 16:26:10 2022 +0200
@@ -100,6 +100,7 @@
 }
 
 User* acllist_getuser(Session *sn, Request *rq, ACLListHandle *list) {
+    // TODO: cache result #50
     if(!sn || !rq || !list) {
         return NULL;
     }
@@ -178,7 +179,7 @@
     ACLList *acl = acl_evallist(list, user, access_mask, NULL);
     if(acl) {
         acl_set_error_status(sn, rq, acl, user);
-        // TODO: don't free the user here
+        // TODO: don't free the user here #51
         if(user) {
             user->free(user);
         }
@@ -316,7 +317,7 @@
         uid_t owner,
         gid_t owninggroup);
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) {
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
     sstr_t p;
     if(path[0] != '/') {
         size_t n = 128;
@@ -331,11 +332,11 @@
             }
         }
         sstr_t wd = sstr(cwd);
-        sstr_t pp = sstr(path);
+        sstr_t pp = sstr((char*)path);
 
         p = sstrcat(3, wd, sstrn("/", 1), pp);
     } else {
-        p = sstrdup(sstr(path));
+        p = sstrdup(sstr((char*)path));
     }
     if(p.ptr[p.length-1] == '/') {
         p.ptr[p.length-1] = 0;
@@ -461,6 +462,11 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    // TODO:
+    return 1;
+}
+
 int solaris_acl_check(
         char *path,
         struct stat *s,
@@ -571,6 +577,10 @@
     return 1;
 }
 
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
+    return 1;
+}
+
 void fs_acl_finish() {
 
 }
@@ -579,7 +589,11 @@
 
 #ifdef BSD
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) {
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
+    return 1;
+}
+
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
     return 1;
 }
 
@@ -594,7 +608,7 @@
 
 #include <sys/fsuid.h>
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) {
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
     struct passwd *ws_pw = conf_getglobals()->Vuserpw;
     if(!ws_pw) {
         log_ereport(LOG_FAILURE, "fs_acl_check: unknown webserver uid/gid");
@@ -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	Sat Sep 24 16:26:10 2022 +0200
@@ -48,7 +48,8 @@
 
 // file system acl functions
 
-int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask);
+int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask);
+int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask);
 void fs_acl_finish();
 
 #ifdef	__cplusplus
--- a/src/server/daemon/acldata.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/acldata.c	Sat Sep 24 16:26:10 2022 +0200
@@ -32,39 +32,20 @@
 
 #include "acldata.h"
 
-ACLData* acl_data_new() {
-    ACLData *dat = malloc(sizeof(ACLData));
-    dat->ref = 1;
+ACLData* acl_data_new(UcxAllocator *a) {
+    ACLData *dat = almalloc(a, sizeof(ACLData));
+    if(!dat) {
+        return NULL;
+    }
     
-    dat->namedACLs = ucx_map_new(16);
+    dat->namedACLs = ucx_map_new_a(a, 16);
+    if(!dat->namedACLs) {
+        return NULL;
+    }
     
     return dat;
 }
 
-void acl_data_ref(ACLData *acldata) {
-    if(acldata) {
-        ws_atomic_inc32(&acldata->ref);
-    }
-}
-
-void acl_data_unref(ACLData *acldata) {
-    uint32_t ref = ws_atomic_dec32(&acldata->ref);
-    if(ref == 0) {
-        UcxMapIterator i = ucx_map_iterator(acldata->namedACLs);
-        WSAcl *acl;
-        UCX_MAP_FOREACH(key, acl, i) {
-            free(acl->ace);
-            free(acl->ece);
-            if(acl->acl.authprompt) {
-                free(acl->acl.authprompt);
-            }
-            free(acl);
-        }
-        ucx_map_free(acldata->namedACLs);
-        free(acldata);
-    }
-}
-
 ACLList* acl_get(ACLData *acldata, char *name) {
     ACLList *acl = ucx_map_cstr_get(acldata->namedACLs, name);
     return acl;
--- a/src/server/daemon/acldata.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/acldata.h	Sat Sep 24 16:26:10 2022 +0200
@@ -41,12 +41,9 @@
     
 typedef struct acl_data {
     UcxMap  *namedACLs;
-    uint32_t ref;
 } ACLData;
 
 ACLData* acl_data_new();
-void acl_data_ref(ACLData *acldata);
-void acl_data_unref(ACLData *acldata);
 
 ACLList* acl_get(ACLData *acldata, char *name);
 
--- a/src/server/daemon/auth.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/auth.h	Sat Sep 24 16:26:10 2022 +0200
@@ -33,6 +33,8 @@
 #include <inttypes.h>
 #include "../public/auth.h"
 
+#include <ucx/map.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -48,12 +50,12 @@
 
 typedef struct user_cache_elm UserCacheElm;
 struct user_cache_elm {
-    CachedUser   *user;
-    UserCacheElm *next_user; // next elm in the cached user list
-    UcxKey       key;        // key to access this element
-    size_t       slot;       // slot in the map
-    UserCacheElm *next_elm;  // next element in this map slot
-    time_t       created;
+    CachedUser       *user;
+    UserCacheElm     *next_user; // next elm in the cached user list
+    UcxKey           key;        // key to access this element
+    size_t           slot;       // slot in the map
+    UserCacheElm     *next_elm;  // next element in this map slot
+    time_t           created;
 };
 
 typedef struct {
--- a/src/server/daemon/config.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/config.c	Sat Sep 24 16:26:10 2022 +0200
@@ -48,6 +48,7 @@
 #include "threadpools.h"
 #include "ldap_auth.h"
 #include "configmanager.h"
+#include "resourcepool.h"
 
 #include "vserver.h"
 #include "../util/pblock.h"
@@ -55,10 +56,13 @@
 #include "../util/atomic.h"
 #include "ucx/buffer.h"
 
-pool_handle_t *cfg_pool;
+pool_handle_t *init_pool;
 
-// TODO: Funktion für ConfigDirective -> directive
-// TODO: Funktion für UcxList parameter list -> pblock
+char* cfg_config_file_path(const char *file) {
+    sstr_t base = ST("config/");
+    sstr_t path = sstrcat(2, base, scstr(file));
+    return path.ptr;
+}
 
 int load_init_conf(char *file) {
     log_ereport(LOG_VERBOSE, "load_init_conf");
@@ -70,14 +74,14 @@
     }
     UcxAllocator *mp = cfg->parser.mp;
 
-    cfg_pool = pool_create(); // one pool for one Configuration
+    init_pool = pool_create(); // one pool for one Configuration
     UcxList *dirs = cfg->directives;
     while(dirs != NULL) {
         ConfigDirective *dir = dirs->data;
 
         /* create NSAPI directive */
         directive *d = malloc(sizeof(directive));
-        d->param = pblock_create_pool(cfg_pool, 8);
+        d->param = pblock_create_pool(init_pool, 8);
         UcxList *param = cfg_param_list(dir->value, mp);
         while(param != NULL) {
             ConfigParam *p = param->data;
@@ -127,19 +131,34 @@
     return 0;
 }
 
-ServerConfiguration* load_server_conf(ServerConfiguration *old, char *file) {
+ServerConfiguration* load_server_conf(char *file) {
     log_ereport(LOG_VERBOSE, "load_server_conf");
 
-    ServerConfig *serverconf = load_server_config(file);
-    if(serverconf == NULL) {
+    ServerConfig *serverconf = serverconfig_load(file);
+    if(!serverconf) {
         log_ereport(LOG_FAILURE, "Cannot load server.conf");
+        return NULL;
     }
-    ServerConfiguration *serverconfig = calloc(1, sizeof(ServerConfiguration));
+    
+    pool_handle_t *pool = pool_create();
+    
+    
+    ServerConfiguration *serverconfig = pool_calloc(pool, 1, sizeof(ServerConfiguration));
     serverconfig->ref = 1;
-    serverconfig->pool = pool_create();
+    serverconfig->pool = pool;
+    
+    UcxAllocator allocator = util_pool_allocator(serverconfig->pool);
+    serverconfig->a = pool_malloc(pool, sizeof(UcxAllocator));
+    *serverconfig->a = allocator;
+    
     serverconfig->listeners = NULL;
-    serverconfig->host_vs = ucx_map_new(16);
-    serverconfig->authdbs = ucx_map_new(16);
+    serverconfig->host_vs = ucx_map_new_a(&allocator, 16);
+    serverconfig->authdbs = ucx_map_new_a(&allocator, 16);
+    serverconfig->resources = ucx_map_new_a(&allocator, 16);
+    serverconfig->dav = ucx_map_new_a(&allocator, 16);
+    
+    
+    
     // TODO: init serverconfig stuff
     
     
@@ -161,49 +180,54 @@
      */
     
     // init logfile first
-    UcxList *lfl = ucx_map_sstr_get(serverconf->objects, sstrn("LogFile", 7));
-    if(lfl != NULL) {
-        ServerConfigObject *logobj = lfl->data;
-        if(logobj == NULL) {
+    UcxList *list = NULL;
+    
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("LogFile"));
+    if(list) {
+        ConfigNode *logobj = list->data;
+        if(!logobj) {
             // error
-            return NULL;
+            return NULL; // TODO: fix memory leak
         }
         
         int ret = cfg_handle_logfile(serverconfig, logobj);
         if(ret != 0) {
             // cannot initialize log file
-            return NULL;
+            return NULL; // TODO: fix memory leak
         }
     } else {
         // horrible error
         return NULL;
     }
+    ucx_list_free(list);
      
-    UcxList *list = ucx_map_sstr_get(serverconf->objects, sstrn("Runtime", 7));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Runtime"));
     UCX_FOREACH(elm, list) {
-        ServerConfigObject *scfgobj = elm->data;
-        if(cfg_handle_runtime(serverconfig, scfgobj)) {
+        ConfigNode *runtimeobj = elm->data;
+        if(cfg_handle_runtime(serverconfig, runtimeobj)) {
             // error
             return NULL;
         }
     }
+    ucx_list_free(list);
     
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("Threadpool", 10));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Threadpool"));
     UCX_FOREACH(elm, list) {
         if(cfg_handle_threadpool(serverconfig, elm->data)) {
             return NULL;
         }
     }
+    ucx_list_free(list);
     // check thread pool config
     if(check_thread_pool_cfg() != 0) {
         /* critical error */
         return NULL;
     }
     
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("EventHandler", 12));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("EventHandler"));
     UCX_FOREACH(elm, list) {
         if(cfg_handle_eventhandler(
-                serverconfig, (ServerConfigObject*)elm->data)) {
+                serverconfig, elm->data)) {
             // error            
             return NULL;
         }
@@ -213,39 +237,59 @@
         /* critical error */
         return NULL;
     }
+    ucx_list_free(list);
     
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("AccessLog", 9));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("AccessLog"));
     UCX_FOREACH(elm, list) {
-        ServerConfigObject *scfgobj = elm->data;
+        ConfigNode *scfgobj = elm->data;
         if(cfg_handle_accesslog(serverconfig, scfgobj)) {
             return NULL;
         }
     }
+    ucx_list_free(list);
     
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("AuthDB", 6));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("AuthDB"));
+    UCX_FOREACH(elm, list) {
+        ConfigNode *scfgobj = elm->data;
+        if(cfg_handle_authdb(serverconfig, scfgobj)) {
+            return NULL;
+        }
+    }
+    ucx_list_free(list);
+    
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Listener"));
     UCX_FOREACH(elm, list) {
-        ServerConfigObject *scfgobj = elm->data;
-        if(cfg_handle_authdb(serverconfig, scfgobj)) {
+        ConfigNode *scfgobj = elm->data;
+        if(cfg_handle_listener(serverconfig, scfgobj)) {
+            return NULL;
+        }
+    }
+    ucx_list_free(list);
+    
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("VirtualServer"));
+    UCX_FOREACH(elm, list) {
+        ConfigNode *scfgobj = elm->data;
+        if(cfg_handle_vs(serverconfig, scfgobj)) {
+            return NULL;
+        }
+    }
+    ucx_list_free(list);
+    
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("ResourcePool"));
+    UCX_FOREACH(elm, list) {
+        ConfigNode *scfgobj = elm->data;
+        if(cfg_handle_resourcepool(serverconfig, scfgobj)) {
             return NULL;
         }
     }
     
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("Listener", 8));
+    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Dav"));
     UCX_FOREACH(elm, list) {
-        ServerConfigObject *scfgobj = elm->data;
-        if(cfg_handle_listener(serverconfig, scfgobj)) {
+        ConfigNode *scfgobj = elm->data;
+        if(cfg_handle_dav(serverconfig, scfgobj)) {
             return NULL;
         }
     }
-    
-    list = ucx_map_sstr_get(serverconf->objects, sstrn("VirtualServer", 13));
-    UCX_FOREACH(elm, list) {
-        ServerConfigObject *scfgobj = elm->data;
-        if(cfg_handle_vs(serverconfig, scfgobj)) {
-            return NULL;
-        }
-    }
-    
 
     // set VirtualServer for all listeners
     UcxList *ls = serverconfig->listeners;
@@ -268,7 +312,8 @@
         ls = ls->next;
     }
     
-    free_server_config(serverconf);
+    serverconfig_free(serverconf);
+    
     return serverconfig;
 }
 
@@ -279,8 +324,7 @@
 void cfg_unref(ServerConfiguration *cfg) {
     uint32_t ref = ws_atomic_dec32(&cfg->ref);
     if(ref == 0) {
-        // TODO: free configuration
-        printf("free ServerConfiguration %"PRIxPTR"\n", (intptr_t)cfg);
+        pool_destroy(cfg->pool);
     }
 }
 
@@ -289,53 +333,36 @@
     
 }
 
-int cfg_handle_runtime(ServerConfiguration *cfg, ServerConfigObject *obj) {
-    sstr_t user = cfg_directivelist_get_str(obj->directives, sstr("User"));
+int cfg_handle_runtime(ServerConfiguration *cfg, ConfigNode *obj) { 
+    scstr_t user = serverconfig_directive_value(obj, SC("User"));
     if(user.ptr) {
-        cfg->user = sstrdup_pool(cfg->pool, user);
+        cfg->user = sstrdup_a(cfg->a, user);
     }
-    sstr_t tmp = cfg_directivelist_get_str(obj->directives, sstr("Temp"));
+    scstr_t tmp = serverconfig_directive_value(obj, SC("Temp"));
     if(tmp.ptr) {
-        cfg->tmp = sstrdup_pool(cfg->pool, tmp);
+        cfg->tmp = sstrdup_a(cfg->a, tmp);
     } else {
+        // TODO: do this check after all config loading is done
         log_ereport(LOG_MISCONFIG, "no temporary directory specified");
         return -1;
     }
     
     // mime file
-    sstr_t mf = cfg_directivelist_get_str(obj->directives, sstr("MimeFile"));  
-    sstr_t base = sstr("config/"); 
+    scstr_t mf = serverconfig_directive_value(obj, SC("MimeFile"));  
+    scstr_t base = SC("config/"); 
     sstr_t file = sstrcat(2, base, mf);
     
-    ConfigFile *f = cfgmgr_get_file(file);
-    if(f == NULL) {
-        f = malloc(sizeof(ConfigFile));
-        f->data = NULL;
-        f->file = sstrdup(file);
-        f->reload = mime_conf_reload;
-        f->last_modified = 0;
-        
-        // load the file content
-        //f->reload(f, cfg);
-        if(cfgmgr_reload_file(f, cfg, NULL)) {
-            free(f->file.ptr);
-            free(f);
-            
-            free(file.ptr);
-            return -1;
-        }
-        cfgmgr_attach_file(f);
+    if(mime_conf_load(cfg, file)) {
+        return -1;
     }
     
-    cfg->mimetypes = f->data;
-    
     free(file.ptr);
     return 0;
 }
 
-int cfg_handle_logfile(ServerConfiguration *cfg, ServerConfigObject *obj) {
-    sstr_t file = cfg_directivelist_get_str(obj->directives, sstr("File"));
-    sstr_t lvl = cfg_directivelist_get_str(obj->directives, sstr("Level"));
+int cfg_handle_logfile(ServerConfiguration *cfg, ConfigNode *obj) {
+    scstr_t file = serverconfig_directive_value(obj, SC("File"));
+    scstr_t lvl = serverconfig_directive_value(obj, SC("Level"));
     
     if(file.ptr == NULL || lvl.ptr == NULL) {
         /* missing log file parameters */
@@ -343,21 +370,18 @@
     }
     
     LogConfig logcfg;
-    logcfg.file = sstrdup(file).ptr;
-    logcfg.level = sstrdup(lvl).ptr;
+    logcfg.file = file.ptr;
+    logcfg.level = lvl.ptr;
     logcfg.log_stdout = 0;
     logcfg.log_stderr = 0;
     /* TODO: stdout, stderr config */
     
     int ret = init_log_file(&logcfg);
-    
-    free(logcfg.file);
-    free(logcfg.level);
 
     return ret;
 }
 
-int cfg_handle_threadpool(ServerConfiguration *cfg, ServerConfigObject *obj) {
+int cfg_handle_threadpool(ServerConfiguration *cfg, ConfigNode *obj) {
     ThreadPoolConfig poolcfg;
     poolcfg.min_threads = 4;
     poolcfg.min_threads = 4;
@@ -365,21 +389,11 @@
     poolcfg.queue_size = 64;
     poolcfg.stack_size = 262144;
     
-    sstr_t name = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Name"));
-    sstr_t min = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("MinThreads"));
-    sstr_t max = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("MaxThreads"));
-    sstr_t stack = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("StackSize"));
-    sstr_t queue = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("QueueSize"));
+    scstr_t name  = serverconfig_directive_value(obj, SC("Name"));
+    scstr_t min   = serverconfig_directive_value(obj, SC("MinThreads"));
+    scstr_t max   = serverconfig_directive_value(obj, SC("MaxThreads"));
+    scstr_t stack = serverconfig_directive_value(obj, SC("StackSize"));
+    scstr_t queue = serverconfig_directive_value(obj, SC("QueueSize"));
     // TODO: Type
     
     if(name.length == 0) {
@@ -388,27 +402,41 @@
     }
     
     if(min.length != 0) {
-        min = sstrdup(min);
-        poolcfg.min_threads = atoi(min.ptr);
-        free(min.ptr);
+        int64_t value;
+        if(util_strtoint(min.ptr, &value)) {
+            poolcfg.min_threads = value;
+        } else {
+            log_ereport(LOG_MISCONFIG, "Threadpool: MinThreads not an integer");
+            return 1;
+        }
     }
     
     if(max.length != 0) {
-        max = sstrdup(max);
-        poolcfg.max_threads = atoi(max.ptr);
-        free(max.ptr);
+        int64_t value;
+        if(util_strtoint(max.ptr, &value)) {
+            poolcfg.max_threads = value;
+        } else {
+            log_ereport(LOG_MISCONFIG, "Threadpool: MaxThreads not an integer");
+            return 1;
+        }
     }
     
     if(stack.length != 0) {
-        stack = sstrdup(stack);
-        poolcfg.stack_size = atoi(stack.ptr);
-        free(stack.ptr);
+        int64_t value;
+        if(util_strtoint(stack.ptr, &value)) {
+            poolcfg.stack_size = value;
+        } else {
+            log_ereport(LOG_MISCONFIG, "Threadpool: StackSize not an integer");
+        }
     }
     
     if(queue.length != 0) {
-        queue = sstrdup(queue);
-        poolcfg.queue_size = atoi(queue.ptr);
-        free(queue.ptr);
+        int64_t value;
+        if(util_strtoint(queue.ptr, &value)) {
+            poolcfg.queue_size = value;
+        } else {
+            log_ereport(LOG_MISCONFIG, "Threadpool: QueueSize not an integer");
+        }
     }
     
     create_threadpool(name, &poolcfg);
@@ -416,32 +444,49 @@
     return 0;
 }
 
-int cfg_handle_eventhandler(ServerConfiguration *c, ServerConfigObject *obj) {
+#define EV_MAX_THREADS 2048
+int cfg_handle_eventhandler(ServerConfiguration *c, ConfigNode *obj) {
     EventHandlerConfig evcfg;
     
-    sstr_t name      = cfg_directivelist_get_str(obj->directives, sstr("Name"));
-    sstr_t threads   = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Threads"));
-    sstr_t isdefault = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Default"));
+    scstr_t name      = serverconfig_directive_value(obj, SC("Name"));
+    scstr_t threads   = serverconfig_directive_value(obj, SC("Threads"));
+    scstr_t isdefault = serverconfig_directive_value(obj, SC("Default"));
     
     evcfg.name = name;
     
-    sstr_t s = sstrdup(threads);
-    evcfg.nthreads = atoi(s.ptr);
-    free(s.ptr);
+    int64_t value;
+    if(!util_strtoint(threads.ptr, &value)) {
+        log_ereport(LOG_MISCONFIG, "EventHandler: Threads: '%s' is not an integer", threads.ptr);
+        return 1;
+    }
+    if(value < 1 || value > EV_MAX_THREADS) {
+        log_ereport(LOG_MISCONFIG, "EventHandler: Invalid number of threads (1 .. %d)", EV_MAX_THREADS);
+        return 1;
+    }
+    
+    evcfg.nthreads = value;
     
     evcfg.isdefault = util_getboolean(isdefault.ptr, 0);
     
     return create_event_handler(&evcfg);
 }
 
-int cfg_handle_accesslog(ServerConfiguration *cfg, ServerConfigObject *obj) {
+int cfg_handle_resourcepool(ServerConfiguration *cfg, ConfigNode *obj) {
+    scstr_t name = serverconfig_directive_value(obj, SC("Name"));
+    scstr_t type = serverconfig_directive_value(obj, SC("Type"));
+    
+    int ret = 0;
+    if(resourcepool_new(cfg, type, name, obj)) {
+        ret = 1;
+    }
+    
+    return ret;
+}
+
+int cfg_handle_accesslog(ServerConfiguration *cfg, ConfigNode *obj) {
     // TODO: use a name to identify the log file
     
-    sstr_t file = cfg_directivelist_get_str(obj->directives, sstr("File"));
+    scstr_t file = serverconfig_directive_value(obj, SC("File"));
     if(file.ptr == NULL) {
         return 0;
     }
@@ -456,10 +501,10 @@
         return 0;
     }
     AccessLog *log = pool_malloc(cfg->pool, sizeof(AccessLog));
-    log->file = sstrdup_pool(cfg->pool, file);
+    log->file = sstrdup_a(cfg->a, file);
     log->format = format;
     log->log = log_file;
-    cfg->logfiles = ucx_list_append(cfg->logfiles, log);
+    cfg->logfiles = ucx_list_append_a(cfg->a, cfg->logfiles, log);
     
     if(!cfg->default_log) {
         cfg->default_log = log;
@@ -468,61 +513,31 @@
     return 0;
 }
 
-int cfg_handle_authdb(ServerConfiguration *cfg, ServerConfigObject *obj) {
-    sstr_t name = cfg_directivelist_get_str(obj->directives, sstr("Name"));
-    sstr_t type = cfg_directivelist_get_str(obj->directives, sstr("Type"));
+int cfg_handle_authdb(ServerConfiguration *cfg, ConfigNode *obj) {
+    scstr_t name = serverconfig_directive_value(obj, SC("Name"));
+    scstr_t type = serverconfig_directive_value(obj, SC("Type"));
+    
+    AuthDB *authdb = NULL;
     
     if(!sstrcmp(type, sstr("ldap"))) {
         LDAPConfig conf;
         
-        sstr_t host = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("Host"));
-        sstr_t port = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("Port"));
-        sstr_t basedn = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("BaseDN"));
-        sstr_t binddn = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("BindDN"));
-        sstr_t basepw = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("BindPW"));
-        
-        host = sstrdup(host);
-        port = sstrdup(port);
-        basedn = sstrdup(basedn);
-        binddn = sstrdup(binddn);
-        basepw = sstrdup(basepw);
+        scstr_t host = serverconfig_directive_value(obj, SC("Host"));
+        scstr_t port = serverconfig_directive_value( obj, SC("Port"));
+        scstr_t basedn = serverconfig_directive_value(obj, SC("BaseDN"));
+        scstr_t binddn = serverconfig_directive_value(obj, SC("BindDN"));
+        scstr_t basepw = serverconfig_directive_value(obj, SC("BindPW"));
         
-        conf.hostname = host.ptr;
+        conf.hostname = sstrdup_a(cfg->a, host).ptr;
         conf.port = atoi(port.ptr);
-        conf.basedn = basedn.ptr;
-        conf.binddn = binddn.ptr;
-        conf.bindpw = basepw.ptr;
-        
-        name = sstrdup(name);
-        
-        AuthDB *authdb = create_ldap_authdb(name.ptr, &conf);
-        ucx_map_sstr_put(cfg->authdbs, name, authdb);
+        conf.basedn = sstrdup_a(cfg->a, basedn).ptr;
+        conf.binddn = sstrdup_a(cfg->a, binddn).ptr;
+        conf.bindpw = sstrdup_a(cfg->a, basepw).ptr;
         
-        // TODO: create_ldap_authdb should copy the strings
-        /*
-        free(host.ptr);
-        free(port.ptr);
-        free(basedn.ptr);
-        free(binddn.ptr);
-        free(basepw.ptr);
-        free(name.ptr);
-        */
-        
+        authdb = create_ldap_authdb(cfg, name.ptr, &conf);      
     } else if(!sstrcmp(type, sstr("keyfile"))) {
         // we only need the file parameter
-        sstr_t file = cfg_directivelist_get_str(
-                obj->directives,
-                sstr("File"));
+        scstr_t file = serverconfig_directive_value(obj, SC("File"));
         if(file.length == 0) {
             log_ereport(
                     LOG_MISCONFIG,
@@ -531,67 +546,55 @@
         }
         
         // load keyfile
-        ConfigFile *f = cfgmgr_get_file(file);
-        if(f == NULL) {
-            f = malloc(sizeof(ConfigFile));
-            f->data = NULL;
-            f->file = sstrdup(file);
-            f->reload = keyfile_reload;
-            f->last_modified = 0;
-            //f->reload(f, cfg);
-            if(cfgmgr_reload_file(f, cfg, NULL)) {
-                free(f->file.ptr);
-                free(f);
-                return -1;
-            }
-            cfgmgr_attach_file(f);
-        }
-        
-        // add keyfile authdb
-        Keyfile *keyfile = f->data;
-        keyfile->authdb.name = sstrdup(name).ptr;
-        ucx_map_sstr_put(cfg->authdbs, name, keyfile);
+        authdb = keyfile_load(cfg, file);
     }
 
+    if(authdb) {
+        if(ucx_map_sstr_put(cfg->authdbs, name, authdb)) {
+            return -1;
+        }
+    }
+    
     return 0;
 }
 
-int cfg_handle_listener(ServerConfiguration *cfg, ServerConfigObject *obj) {
+int cfg_handle_listener(ServerConfiguration *cfg, ConfigNode *obj) {
     ListenerConfig lc;
     ZERO(&lc, sizeof(ListenerConfig));
     lc.cfg = cfg;
     lc.port = 8080;
     lc.nacceptors = 1;
     
+    scstr_t name = serverconfig_directive_value(obj, SC("Name"));
+    scstr_t port = serverconfig_directive_value(obj, SC("Port"));
+    scstr_t vs   = serverconfig_directive_value(obj, SC("DefaultVS"));
+    scstr_t thrp = serverconfig_directive_value(obj, SC("Threadpool"));
+    scstr_t blck = serverconfig_directive_value(obj, SC("BlockingIO"));
+    
     // TODO: use sstrdup_pool?
-    lc.name = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Name")));
-    lc.port = atoi(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Port")).ptr);
-    lc.vs = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("DefaultVS")));
-    lc.threadpool = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Threadpool")));
-    
-    sstr_t blockingio = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("BlockingIO"));
-    if(blockingio.ptr) {
-        lc.blockingio = util_getboolean_s(blockingio, WS_FALSE);
+    int64_t port_value;
+    if(!util_strtoint(port.ptr, &port_value)) {
+        log_ereport(LOG_MISCONFIG, "Listener: Invalid argument for parameter 'Port': '%s'", port.ptr);
+        return 1;
+    }
+    if(port_value < 1 || port_value > 65535) {
+        log_ereport(LOG_MISCONFIG, "Listener: Port number out of range (1 .. 65535)");
+        return 1;
     }
     
-    sstr_t ssl = cfg_directivelist_get_str(obj->directives, S("SSL"));
+    lc.name = sstrdup(name);
+    lc.port = port_value;
+    lc.vs = sstrdup(vs);
+    lc.threadpool = sstrdup(thrp);
+    
+    lc.blockingio = util_getboolean_s(blck, WS_FALSE);
+    
+    scstr_t ssl = serverconfig_directive_value(obj, SC("SSL"));
     if(util_getboolean_s(ssl, WS_FALSE)) {
-        sstr_t cert = cfg_directivelist_get_str(obj->directives, S("Cert"));
-        sstr_t privkey = cfg_directivelist_get_str(obj->directives, S("Key"));
-        sstr_t chain = cfg_directivelist_get_str(obj->directives, S("CertChain"));
-        sstr_t disableprot = cfg_directivelist_get_str(
-                obj->directives,
-                S("SSLDisableProtocol"));
+        scstr_t cert        = serverconfig_directive_value(obj, SC("Cert"));
+        scstr_t privkey     = serverconfig_directive_value(obj, SC("Key"));
+        scstr_t chain       = serverconfig_directive_value(obj, SC("CertChain"));
+        scstr_t disableprot = serverconfig_directive_value(obj, SC("SSLDisableProtocol"));
         
         WSBool config_ok = WS_TRUE;
         // TODO: log error
@@ -628,84 +631,50 @@
         return 1;
     }
     
-    listener->default_vs.vs_name = lc.vs.ptr;
-    cfg->listeners = ucx_list_append(cfg->listeners, listener); 
+    listener->default_vs.vs_name = sstrdup_a(cfg->a, lc.vs).ptr;
+    cfg->listeners = ucx_list_append_a(cfg->a, cfg->listeners, listener); 
     
     return 0;
 }
 
-int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj) {
+int cfg_handle_vs(ServerConfiguration *cfg, ConfigNode *obj) {
     VirtualServer *vs = vs_new();
 
-    vs->name = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Name")));
-    vs->host = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("Host")));
-    vs->document_root = sstrdup(cfg_directivelist_get_str(
-            obj->directives,
-            sstr("DocRoot")));
-    sstr_t objfile = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("ObjectFile"));
-    sstr_t aclfile = cfg_directivelist_get_str(
-            obj->directives,
-            sstr("ACLFile"));
+    vs->name = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("Name")));
+    vs->host = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("Host")));
+    vs->document_root = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("DocRoot")));
+    
+    scstr_t objfile = serverconfig_directive_value(obj, SC("ObjectFile"));
+    scstr_t aclfile = serverconfig_directive_value(obj, SC("ACLFile"));
     
     // load the object config file
     sstr_t base = sstr("config/");
     sstr_t file = sstrcat(2, base, objfile);
-    file = sstrcat(2, base, objfile);
+    // sstrcat with allocator because we want to keep the string
+    file = sstrcat_a(cfg->a, 2, base, objfile);
 
-    // the file is managed by the configuration manager
-    ConfigFile *f = cfgmgr_get_file(file);
-    if(f == NULL) {
-        f = malloc(sizeof(ConfigFile));
-        f->data = NULL;
-        f->file = sstrdup(file);
-        f->reload = object_conf_reload;
-        f->last_modified = 0;
-        //f->reload(f, cfg);
-        if(cfgmgr_reload_file(f, cfg, NULL)) {
-            free(f->file.ptr);
-            free(f);
-            
-            free(file.ptr);
-            return -1;
-        }
-        cfgmgr_attach_file(f);
+    HTTPObjectConfig *httpobj = objconf_load(cfg, file);
+    if(!httpobj) {
+        return -1;
     }
-    vs->objectfile = sstrdup(file);
-    vs->objects = (HTTPObjectConfig*)f->data;
-    free(file.ptr);
+    vs->objectfile = file;
+    vs->objects = httpobj;
     
     
     // load acl config file
     file = sstrcat(2, base, aclfile);
     
-    ConfigFile *aclf = cfgmgr_get_file(file);
-    if(aclf == NULL) {
-        aclf = malloc(sizeof(ConfigFile));
-        aclf->data = NULL;
-        aclf->file = sstrdup(file);
-        aclf->reload = acl_conf_reload;
-        aclf->last_modified = 0;
-        //aclf->reload(aclf, cfg);
-        if(cfgmgr_reload_file(aclf, cfg, NULL)) {
-            free(aclf->file.ptr);
-            free(aclf);
-            
-            free(file.ptr);
-            return -1;
-        }
-        cfgmgr_attach_file(aclf);
+    ACLData *acldata = acl_conf_load(cfg, file);
+    if(!acldata) {
+        return -1;
     }
-    vs->acls = aclf->data;
+    vs->acls = acldata;
+    
     free(file.ptr);
     
+    
     // set the access log for the virtual server
-    // TODO: don't use always the default
+    // TODO: don't always use the default
     vs->log = cfg->default_log;
 
     ucx_map_sstr_put(cfg->host_vs, vs->host, vs);
@@ -713,54 +682,130 @@
     return 0;
 }
 
-
-int object_conf_reload(ConfigFile *file, ServerConfiguration *cfg) {
-    HTTPObjectConfig *old_conf = file->data;
-    file->data = load_obj_conf(file->file.ptr);
-    if(old_conf) {
-        object_conf_unref(old_conf);
+int cfg_handle_dav(ServerConfiguration *cfg, ConfigNode *obj) {
+    UcxList *backends = NULL; // list of ConfigArg*
+    UcxAllocator a = util_pool_allocator(cfg->pool);
+    int init_error;
+    
+    // parse args
+    char *uri = NULL;
+    char *ppath = NULL;
+    char *name = NULL;
+    UCX_FOREACH(elm, obj->args) {
+        ConfigArg *arg = elm->data;
+        if(arg->name.ptr == NULL) {
+            // default: uri
+            uri = arg->value.ptr;
+        } else if(!sstrcasecmp(arg->name, SC("uri"))) {
+            uri = arg->value.ptr;
+        } else if(!sstrcasecmp(arg->name, SC("ppath"))) {
+            ppath = arg->value.ptr;
+        } else if(!sstrcasecmp(arg->name, SC("name"))) {
+            name = arg->value.ptr;
+        }
     }
-    if(file->data) {
-        return 0;
-    } else {
+    if(!uri && !ppath && !name) {
         return 1;
     }
-}
-
-void object_conf_ref(HTTPObjectConfig *conf) {
-    if(conf) {
-        ws_atomic_inc32(&conf->ref);
+    
+    // get a list of all DavBackends
+    UCX_FOREACH(elm, obj->children) {
+        ConfigNode *node = elm->data;
+        if(!sstrcasecmp(node->name, SC("DavBackend"))) {
+            if(node->type == CONFIG_NODE_DIRECTIVE) {
+                if(ucx_list_size(node->args) == 1) {
+                    ConfigArg *arg = node->args->data;
+                    backends = ucx_list_append(backends, arg);
+                } else {
+                    log_ereport(LOG_MISCONFIG, "DavBackend must have only one value");
+                    ucx_list_free(backends);
+                    return 1;
+                }
+            } else {
+                log_ereport(LOG_MISCONFIG, "DavBackend must be a directive");
+                ucx_list_free(backends);
+                return 1;
+            }
+        }
     }
+    
+    int ret = 0;
+    WebdavRepository *repository = pool_malloc(cfg->pool, sizeof(WebdavRepository));
+    repository->vfs = NULL;
+    repository->vfsInitData = NULL;
+    repository->davBackends = NULL;
+    
+    // initialize backends
+    UCX_FOREACH(elm, backends) {
+        // the DavBackend value should contain the dav class name
+        ConfigArg *backendArg = elm->data;
+        
+        WebdavType *dav = webdav_get_type((scstr_t){backendArg->value.ptr, backendArg->value.length});
+        if(!dav) {
+            log_ereport(LOG_MISCONFIG, "Unknown webdav backend type '%s'", backendArg->value.ptr);
+            ret = 1;
+            break;
+        }
+        
+        // call backend init
+        // init_data can be NULL, errors will be indicated by init_error
+        void *init_data = webdav_init_backend(cfg, cfg->pool, dav, obj, &init_error);
+        if(init_error) {
+            log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s", backendArg->value.ptr);
+            ret = 1;
+            break;
+        }
+        
+        WebdavBackendInitData *davInit = pool_malloc(cfg->pool, sizeof(WebdavBackendInitData));
+        if(!davInit) {
+            log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s: OOM", backendArg->value.ptr);
+            ret = 1;
+            break;
+        }
+        davInit->davType = dav;
+        davInit->davInitData = init_data;
+        
+        repository->davBackends = ucx_list_append_a(&a, repository->davBackends, davInit);
+    }
+    
+    // initialize vfs
+    scstr_t vfs_class = serverconfig_directive_value(obj, SC("VFS"));
+    if(vfs_class.length > 0) {
+        VfsType *vfs = vfs_get_type((scstr_t){vfs_class.ptr, vfs_class.length});
+        if(vfs) {
+            repository->vfs = vfs;
+            repository->vfsInitData = vfs_init_backend(cfg, cfg->pool, vfs, obj, &init_error);
+            ret = init_error;
+        } else {
+            log_ereport(LOG_FAILURE, "Unknown vfs type '%s'", vfs_class.ptr);
+            ret = 1;
+        }
+    }
+    
+    scstr_t object = serverconfig_directive_value(obj, SC("Object"));
+    if(object.length > 0) {
+        repository->object = sstrdup_a(&a, object);
+        if(repository->object.length != object.length) {
+            // OOM
+            log_ereport(LOG_FAILURE, "Cannot create webdav repository: OOM");
+            ret = 1;
+        }
+    }
+    
+    if(!ret) {
+        if(name) {
+            ucx_map_cstr_put(cfg->dav, name, repository);
+        } else {
+            log_ereport(LOG_FAILURE, "TODO: location based dav repositories not implemented");
+            ret = 1;
+        }
+    }
+    
+    return ret;    
 }
 
-void object_conf_unref(HTTPObjectConfig *conf) {
-    uint32_t ref = ws_atomic_dec32(&conf->ref);
-    if(ref == 0) {
-        printf("free HTTPObjectConfig %"PRIxPTR"\n", (intptr_t)conf);
-        pool_destroy(conf->pool);
-    }
-}
-
-HTTPObjectConfig* load_obj_conf(char *file) {
-    log_ereport(LOG_VERBOSE, "load_obj_conf");
-
-    // new conf function test
-    ObjectConfig *cfg = load_object_config(file);
-    UcxAllocator *mp = cfg->parser.mp;
-    if(cfg == NULL) {
-        return NULL;
-    }
-
-    // create object config
-    pool_handle_t *pool = pool_create();
-    HTTPObjectConfig *conf = pool_calloc(pool, sizeof(HTTPObjectConfig), 1);
-    conf->pool = pool;
-
-    // convert ObjectConfig to HTTPObjectConfig
-
-    // add objects
-    conf->nobj = ucx_list_size(cfg->objects);
-    conf->objects = pool_calloc(pool, conf->nobj, sizeof(httpd_object*));
+static int convert_objconf(ServerConfiguration *scfg, ObjectConfig *cfg, HTTPObjectConfig *conf, sstr_t file) {
+    pool_handle_t *pool = conf->pool;
     
     UcxList *objlist = cfg->objects;
     int i = 0;
@@ -772,13 +817,16 @@
         char *ppath = NULL;
         if(cob->name.length > 0) {
             name = sstrdup_pool(pool, cob->name).ptr;
+            if(!name) return -1;
         }
         if(cob->ppath.length > 0) {
             ppath = sstrdup_pool(pool, cob->ppath).ptr;
+            if(!ppath) return -1;
         }
 
         // create and add object
         httpd_object *obj = object_new(pool, name);
+        if(!obj) return -1;
         obj->path = NULL;
 
         conf->objects[i] = obj;
@@ -788,8 +836,9 @@
             UcxList *dirs = cob->directives[j];
             while(dirs != NULL) {
                 ConfigDirective *cfgdir = dirs->data;
-                
+
                 directive *d = pool_malloc(pool, sizeof(directive));
+                if(!d) return -1;
                 if(cfgdir->condition) {
                     sstr_t expr = cfgdir->condition->param_str;
                     d->cond = condition_from_str(pool, expr.ptr, expr.length);
@@ -799,7 +848,7 @@
                 d->param = pblock_create_pool(pool, 8);
 
                 // add params
-                UcxList *param = cfg_param_list(cfgdir->value, mp);
+                UcxList *param = cfg_param_list(cfgdir->value, scfg->a);
                 while(param != NULL) {
                     ConfigParam *p = param->data;
                     pblock_nvlinsert(
@@ -814,13 +863,13 @@
                 // get function
                 char *func_name = pblock_findval("fn", d->param);
                 if(!func_name) {
-                    log_ereport(LOG_MISCONFIG, "%s: Missing fn parameter", file);
-                    return NULL;
+                    log_ereport(LOG_MISCONFIG, "%s: Missing fn parameter", file.ptr);
+                    return -1;
                 }
                 d->func = get_function(func_name);
                 if(!d->func) {
                     log_ereport(LOG_MISCONFIG, "func %s not found", func_name);
-                    return NULL;
+                    return -1;
                 }
 
                 dirs = dirs->next;
@@ -834,66 +883,94 @@
         i++;
         objlist = objlist->next;
     }
+    
+    return 0;
+}
+
+HTTPObjectConfig* objconf_load(ServerConfiguration *scfg, sstr_t file) {
+    log_ereport(LOG_VERBOSE, "load_obj_conf");
+    
+    int ret = 0;
+
+    // create object config
+    pool_handle_t *pool = scfg->pool;
+    HTTPObjectConfig *conf = pool_calloc(pool, sizeof(HTTPObjectConfig), 1);
+    if(!conf) {
+        return NULL;
+    }
+    conf->pool = pool;
+    
+    // load obj config file
+    ObjectConfig *cfg = load_object_config(file.ptr);
+    if(!cfg) {
+        return NULL;
+    }
+
+    // convert ObjectConfig to HTTPObjectConfig
+
+    // add objects
+    conf->nobj = ucx_list_size(cfg->objects);
+    conf->objects = pool_calloc(pool, conf->nobj, sizeof(httpd_object*));
+    if(conf->objects) {
+        ret = convert_objconf(scfg, cfg, conf, file);
+    } else {
+        ret = -1;
+    }
 
     free_object_config(cfg);
 
-    return conf;
+    return !ret ? conf : NULL;
 }
 
-int mime_conf_reload(ConfigFile *file, ServerConfiguration *cfg) {
-    MimeConfig *mimecfg = load_mime_config(file->file.ptr);
-    MimeMap *old_conf = file->data;
-    
-    MimeMap *mimemap = malloc(sizeof(MimeMap));
-    mimemap->ref = 1;
-    UcxMap *map = ucx_map_new((mimecfg->ntypes * 3) / 2);
-    mimemap->map = map;
-    
-    // add ext type pairs
-    UCX_FOREACH(md, mimecfg->directives) {
-        MimeDirective *d = md->data;
-        // add the type for each extension to the map
-        UCX_FOREACH(xl, d->exts) {
-            sstr_t ext = sstr(xl->data);
-            sstr_t value = sstrdup(d->type);
-            ucx_map_sstr_put(map, ext, value.ptr);
-        }
+int mime_conf_load(ServerConfiguration *cfg, sstr_t file) {
+    MimeConfig *mimecfg = load_mime_config(file.ptr);
+    if(!mimecfg) {
+        return -1;
     }
     
-    file->data = mimemap;
+    int ret = 0;
+    
+    // cleanup in case of errors is done by the allocator
+    MimeMap *mimemap = almalloc(cfg->a, sizeof(MimeMap));
+    UcxMap *map = ucx_map_new_a(cfg->a, (mimecfg->ntypes * 3) / 2);
     
-    if(old_conf) {
-        mime_conf_unref(old_conf);
+    if(mimemap && map) {
+        mimemap->map = map;
+        
+        // add ext type pairs
+        UCX_FOREACH(md, mimecfg->directives) {
+            MimeDirective *d = md->data;
+            // add the type for each extension to the map
+            UCX_FOREACH(xl, d->exts) {
+                sstr_t ext = sstr(xl->data);
+                sstr_t value = sstrdup(d->type);
+                if(ucx_map_sstr_put(map, ext, value.ptr)) {
+                    ret = -1;
+                    break;
+                }
+            }
+            if(ret) {
+                break;
+            }
+        }
+        
+        cfg->mimetypes = mimemap;
+    } else {
+        ret = -1;
     }
     
     free_mime_config(mimecfg);
-    return 0;
-}
-
-void mime_conf_ref(MimeMap *conf) {
-    if(conf) {
-        ws_atomic_inc32(&conf->ref);
-    }
+    return ret;
 }
 
-void mime_conf_unref(MimeMap *conf) {
-    uint32_t ref = ws_atomic_dec32(&conf->ref);
-    if(ref == 0) {
-        printf("free MimeConfig %"PRIxPTR"\n", (intptr_t)conf);
-        UcxMapIterator i = ucx_map_iterator(conf->map);
-        char *str;
-        UCX_MAP_FOREACH(key, str, i) {
-            free(str);
-        }
-        ucx_map_free(conf->map);
-        free(conf);
-    }
-}
+
 
-int acl_conf_reload(ConfigFile *file, ServerConfiguration *cfg) {
-    ACLFile *aclfile = load_acl_file(file->file.ptr);
+ACLData* acl_conf_load(ServerConfiguration *cfg, sstr_t file) {
+    ACLFile *aclfile = load_acl_file(file.ptr);
     
-    ACLData *acldata = acl_data_new();
+    // TODO: malloc return checks
+    
+    ACLData *acldata = acl_data_new(cfg->a);
     UCX_FOREACH(elm, aclfile->namedACLs) {
         ACLConfig *ac = elm->data;
         ACLList *acl = acl_config_convert(cfg, ac);
@@ -902,17 +979,13 @@
     }
     free_acl_file(aclfile);
     
-    ACLData *old_data = file->data;
-    file->data = acldata;
-    if(old_data) {
-        acl_data_unref(old_data);
-    }
-    
-    return 0;
+    return acldata;
 }
 
 ACLList* acl_config_convert(ServerConfiguration *cfg, ACLConfig *acl) {
-    WSAcl *acllist = malloc(sizeof(WSAcl));
+    UcxAllocator *a = cfg->a;
+    
+    WSAcl *acllist = almalloc(cfg->a, sizeof(WSAcl));
     acllist->acl.check = (acl_check_f)wsacl_check;
     acllist->acl.authdb = NULL;
     acllist->acl.authprompt = NULL;
@@ -925,8 +998,8 @@
     }
     
     size_t s = ucx_list_size(acl->entries);
-    WSAce **aces = calloc(s, sizeof(WSAce*));
-    WSAce **eces = calloc(s, sizeof(WSAce*));
+    WSAce **tmp_aces = calloc(s, sizeof(WSAce*));
+    WSAce **tmp_eces = calloc(s, sizeof(WSAce*));
     int ai = 0;
     int ei = 0;
     
@@ -935,36 +1008,36 @@
         ACEConfig *acecfg = elm->data;
         
         // copy data
-        WSAce *ace = malloc(sizeof(WSAce));
+        WSAce *ace = almalloc(a, sizeof(WSAce));
         ace->access_mask = acecfg->access_mask;
         ace->flags = acecfg->flags;
         ace->type = acecfg->type;
-        ace->who = sstrdup(acecfg->who).ptr;
+        ace->who = sstrdup_a(a, acecfg->who).ptr;
         
         // add the entry to the correct array
         if(ace->type >= ACL_TYPE_AUDIT) {
-            eces[ei] = ace;
+            tmp_eces[ei] = ace;
             ei++;
         } else {
-            aces[ai] = ace;
+            tmp_aces[ai] = ace;
             ai++;
         }
     }
     
     // create new entrie arrays with perfect fitting size
     if(ai > 0) {
-        acllist->ace = calloc(ai, sizeof(WSAce*));
+        acllist->ace = alcalloc(a, ai, sizeof(WSAce*));
     }
     if(ei > 0) {
-        acllist->ece = calloc(ei, sizeof(WSAce*));
+        acllist->ece = alcalloc(a, ei, sizeof(WSAce*));
     }
-    memcpy(acllist->ace, aces, ai*sizeof(WSAce*));
-    memcpy(acllist->ece, eces, ei*sizeof(WSAce*));
+    memcpy(acllist->ace, tmp_aces, ai*sizeof(WSAce*));
+    memcpy(acllist->ece, tmp_eces, ei*sizeof(WSAce*));
     acllist->acenum = ai;
     acllist->ecenum = ei;
     
-    free(aces);
-    free(eces);
+    free(tmp_aces);
+    free(tmp_eces);
     
     // get authentication information
     if(acl->authparam) {
@@ -975,7 +1048,7 @@
             AuthDB *authdb = ucx_map_sstr_get(cfg->authdbs, authdb_str);
             acllist->acl.authdb = authdb;
             if(authdb && prompt_str.ptr) {
-                acllist->acl.authprompt = sstrdup(prompt_str).ptr;
+                acllist->acl.authprompt = sstrdup_a(a, prompt_str).ptr;
             }
         }
     }
@@ -983,69 +1056,47 @@
     return &acllist->acl;
 }
 
-int keyfile_reload(ConfigFile *file, ServerConfiguration *cfg) {
-    KeyfileConfig *conf = load_keyfile_config(file->file.ptr);
-    if(!conf) {
-        return 1;
+AuthDB* keyfile_load(ServerConfiguration *cfg, scstr_t file) {
+    Keyfile *keyfile = keyfile_new(cfg->a);
+    if(!keyfile) {
+        return NULL;
     }
     
-    Keyfile *keyfile = keyfile_new();
+    KeyfileConfig *conf = load_keyfile_config(file.ptr);
+    if(!conf) {
+        return NULL;
+    }
+    
+    AuthDB *ret = &keyfile->authdb;
     
     UCX_FOREACH(elm, conf->users) {
         KeyfileEntry *user = elm->data;
-        keyfile_add_user(
+        if(keyfile_add_user(
                 keyfile,
                 user->name,
                 user->hashtype,
                 user->hashdata,
                 user->groups,
-                user->numgroups);
+                user->numgroups))
+        {
+            ret = NULL;
+            break;
+        }
     }
     
     free_keyfile_config(conf);
     
-    Keyfile *old_data = file->data;
-    file->data = keyfile;
-    if(old_data) {
-        keyfile_unref(old_data);
-    }
-    
-    return 0;
+    return ret;
 }
 
-
-sstr_t cfg_load_file(sstr_t file) {
-    sstr_t r;
-    r.ptr = NULL;
-    r.length = 0;
-    
-    if(!file.ptr) {
-        return r;
-    }
-    
-    sstr_t f = sstrdup(file);
-    FILE *in = fopen(f.ptr, "r");
-    if(!in) {
-        return r;
+pblock* config_obj2pblock(pool_handle_t *pool, ConfigNode *obj) {
+    pblock *pb = pblock_create_pool(pool, 8);
+    UCX_FOREACH(elm, obj->children) {
+        ConfigNode *d = elm->data;
+        if(d->type == CONFIG_NODE_DIRECTIVE && d->name.length > 0 && ucx_list_size(d->args) == 1) {
+            ConfigArg *arg = d->args->data;
+            pblock_nvlinsert(d->name.ptr, d->name.length, arg->value.ptr, arg->value.length, pb);
+        }
     }
-    
-    UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
-    if(!buf) {
-        fclose(in);
-        return r;
-    }
-    
-    if(ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write) == 0) {
-        fclose(in);
-        ucx_buffer_free(buf);
-        return r;
-    }
-    
-    r.ptr = buf->space;
-    r.length = buf->pos;
-    
-    free(buf);
-    fclose(in);
-    
-    return r;
+    return pb;
 }
--- a/src/server/daemon/config.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/config.h	Sat Sep 24 16:26:10 2022 +0200
@@ -35,14 +35,17 @@
 
 #include "../config/objconf.h"
 #include "../config/initconf.h"
-#include "../config/serverconf.h"
 #include "../config/mimeconf.h"
 #include "../config/acl.h"
 #include "../config/keyfile.h"
+#include "../config/serverconfig.h"
 
 #include "acldata.h"
 #include "keyfile_auth.h"
 #include "log.h"
+#include "vfs.h"
+
+#include "../webdav/webdav.h"
 
 #include <ucx/list.h>
 #include <ucx/map.h>
@@ -54,74 +57,81 @@
 #endif
 
 typedef struct mime_map MimeMap;
+
+typedef struct WebdavRepository      WebdavRepository;
+typedef struct WebdavBackendInitData WebdavBackendInitData;
     
-typedef struct _server_configuration {
+struct ServerConfiguration {
     pool_handle_t   *pool;
+    UcxAllocator    *a;
+    
     UcxMap          *host_vs;   // map of all vservers. key is the host name
     UcxList         *listeners; // list of all listeners
     UcxList         *logfiles;
     AccessLog       *default_log;
     UcxMap          *authdbs;
     MimeMap         *mimetypes;
+    UcxMap          *resources;
+    UcxMap          *dav;
     sstr_t          tmp;
     sstr_t          user;
     uint32_t        ref;        // reference counter
-} ServerConfiguration;
-
-
-typedef struct ConfigFile ConfigFile;
+};
 
-typedef int(*cfg_reload_f)(ConfigFile*,ServerConfiguration*);
+struct WebdavRepository {
+    VfsType *vfs;
+    void *vfsInitData;
+    UcxList *davBackends; // list of WebdavBackendInitData*
+    sstr_t object;
+};
 
-struct ConfigFile {
-    sstr_t         file;
-    time_t         last_modified;
-    cfg_reload_f   reload;
-    void           *data;
+struct WebdavBackendInitData {
+    WebdavType *davType;
+    void *davInitData;
 };
 
 struct mime_map {
     UcxMap   *map;
-    uint32_t ref;
 };
 
+char* cfg_config_file_path(const char *file);
+
 int load_init_conf(char *file);
 
 void init_server_config_parser();
 
-int cfg_handle_runtime(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_runtime(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_logfile(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_logfile(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_threadpool(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_threadpool(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_eventhandler(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_eventhandler(ServerConfiguration *cfg, ConfigNode *obj);
+
+int cfg_handle_resourcepool(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_accesslog(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_accesslog(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_authdb(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_authdb(ServerConfiguration *cfg, ConfigNode *obj);
+
+int cfg_handle_listener(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_listener(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_vs(ServerConfiguration *cfg, ConfigNode *obj);
 
-int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj);
+int cfg_handle_dav(ServerConfiguration *cfg, ConfigNode *obj);
 
-ServerConfiguration* load_server_conf(ServerConfiguration *old, char *file);
+ServerConfiguration* load_server_conf(char *file);
 void cfg_ref(ServerConfiguration *cfg);
 void cfg_unref(ServerConfiguration *cfg);
 
+HTTPObjectConfig* objconf_load(ServerConfiguration *scfg, sstr_t file);
+int mime_conf_load(ServerConfiguration *cfg, sstr_t file);
 
-int object_conf_reload(ConfigFile *file, ServerConfiguration *cfg);
-void object_conf_ref(HTTPObjectConfig *conf);
-void object_conf_unref(HTTPObjectConfig *conf);
-HTTPObjectConfig* load_obj_conf(char *file);
-int mime_conf_reload(ConfigFile *file, ServerConfiguration *cfg);
-void mime_conf_ref(MimeMap *conf);
-void mime_conf_unref(MimeMap *conf);
-int acl_conf_reload(ConfigFile *file, ServerConfiguration *cfg);
+ACLData* acl_conf_load(ServerConfiguration *cfg, sstr_t file);
 ACLList* acl_config_convert(ServerConfiguration *cfg, ACLConfig *acl);
-int keyfile_reload(ConfigFile *file, ServerConfiguration *cfg);
+AuthDB* keyfile_load(ServerConfiguration *cfg, scstr_t file);
 
-sstr_t cfg_load_file(sstr_t file);
+pblock* config_obj2pblock(pool_handle_t *pool, ConfigNode *obj);
 
 #ifdef	__cplusplus
 }
--- a/src/server/daemon/configmanager.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/configmanager.c	Sat Sep 24 16:26:10 2022 +0200
@@ -38,111 +38,39 @@
 #include "configmanager.h"
 
 static ServerConfiguration *current_config = NULL;
-static time_t sc_last_modified = 0;
 
-static UcxMap *config_files;
 
 static conf_global_vars_s global_vars;
 
 void init_configuration_manager() {
     /* init parser */
     init_server_config_parser();
-
-    config_files = ucx_map_new(16);
 }
 
 NSAPI_PUBLIC conf_global_vars_s* conf_getglobals() {
     return &global_vars;
 }
 
-void cfgmgr_attach_file(ConfigFile *cf) {
-    ucx_map_sstr_put(config_files, cf->file, cf);
-}
-
-ConfigFile* cfgmgr_get_file(sstr_t name) {
-    return ucx_map_sstr_get(config_files, name);
-}
-
-int cfgmgr_reload_file(ConfigFile *f, ServerConfiguration *conf, int *reload) {
-    struct stat s;
-    if(stat(f->file.ptr, &s) != 0) {
-        fprintf(
-                stderr,
-                "Error: Cannot get stat of file %s\n", f->file.ptr);
-        perror("cfgmgr_load_config: stat");
-        return -1;
-    }
+int cfgmgr_load_config(ServerConfiguration **set_cfg) {
+    ServerConfiguration *config = load_server_conf("config/server.conf");
     
-    //printf("1 time: %d - %d\n", f->last_modified, s.st_mtim.tv_sec);
-    if(f->last_modified != s.st_mtime) {
-        /* reload the file */
-        if(f->last_modified != 0) {
-            log_ereport(
-                    LOG_INFORM,
-                    "reload configuration file: %s",
-                    f->file.ptr);
-        }
-        if(f->reload(f, conf)) {
-            return -1;
-        }
-        f->last_modified = s.st_mtime;
-        if(reload) {
-            *reload = 1;
-        }
-    }
-    return 0;
-}
-
-int cfgmgr_load_config(ServerConfiguration **set_cfg) {
-    int cfgreload = 0;
-    
-    /* check config files */  
-    UcxMapIterator iter = ucx_map_iterator(config_files);
-    ConfigFile *f;
-    UCX_MAP_FOREACH(key, f, iter) {
-        if(cfgmgr_reload_file(f, current_config, &cfgreload) == -1) {
-            return -1;
-        }
-    }
-
-    struct stat s;
-    if(stat("config/server.conf", &s) != 0) {
-        perror("stat(\"config/server.conf\")");
+    if(!config) {
         return -1;
     }
-
-    ServerConfiguration *config;
-    if(cfgreload || !current_config || sc_last_modified != s.st_mtime) {
-        config = load_server_conf(
-            current_config,
-            "config/server.conf");
-
-        if(config == NULL) {
-            return -1;
-        }
-        
-        sc_last_modified = s.st_mtime;
-    } else {
-        log_ereport(LOG_VERBOSE, "no reconfig required");
-        config = current_config;
-    }
     
     if(set_cfg) {
          *set_cfg = config;
     }
-    ServerConfiguration *old_conf = NULL;
-    if(current_config != config) {
-        old_conf = current_config;
+    
+    if(current_config) {
+        cfg_unref(current_config);
     }
     current_config = config;
-    if(old_conf) {
-        cfg_unref(old_conf);
-    }
+    
     return 0;
 }
 
 ServerConfiguration *cfgmgr_get_server_config() {
-    //cfg_ref(current_config);
     return current_config;
 }
 
--- a/src/server/daemon/configmanager.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/configmanager.h	Sat Sep 24 16:26:10 2022 +0200
@@ -46,9 +46,6 @@
 
 void init_configuration_manager();
 
-void cfgmgr_attach_file(ConfigFile *cf);
-ConfigFile* cfgmgr_get_file(sstr_t name);
-int cfgmgr_reload_file(ConfigFile *f, ServerConfiguration *conf, int *reload);
 int cfgmgr_load_config(ServerConfiguration **cfg);
 
 ServerConfiguration* cfgmgr_get_server_config();
--- a/src/server/daemon/event.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/event.c	Sat Sep 24 16:26:10 2022 +0200
@@ -26,7 +26,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "../../ucx/map.h"
+#include <ucx/map.h>
 #include "../util/atomic.h"
 
 #include "event.h"
@@ -88,7 +88,7 @@
     }
     
     EventHandlerConfig cfg;
-    cfg.name = sstr("default");
+    cfg.name = SC("default");
     cfg.nthreads = 1;
     cfg.isdefault = 1;
     
--- a/src/server/daemon/event.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/event.h	Sat Sep 24 16:26:10 2022 +0200
@@ -43,7 +43,7 @@
 } EVHandler;
 
 typedef struct event_handler_conf {
-    sstr_t   name;
+    scstr_t  name;
     int      nthreads;
     int      isdefault;
 } EventHandlerConfig;
--- a/src/server/daemon/event_bsd.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/event_bsd.c	Sat Sep 24 16:26:10 2022 +0200
@@ -67,47 +67,75 @@
     timeout.tv_sec = 600;
     
     struct kevent events[64];
-    struct kevent changes[64];
+    struct kevent changes[128];
     int numchanges = 0;
     
     for(;;) {
         // wait for events
-        int nev = kevent(ev->kqueue, changes, numchanges, events, 64, &timeout);
+        int nev = kevent(ev->kqueue, changes, numchanges, events, 64, &timeout);  
         if(nev == -1) {
-            // TODO: check for error
-            perror("kevent");
+            log_ereport(LOG_FAILURE, "kevent: %s", strerror(errno));
             continue;
         }
         
         numchanges = 0;
         for(int i=0;i<nev;i++) {
             Event *event = (Event*)events[i].udata;
+            if(!event) {
+                if(events[i].flags == 0) {
+                    log_ereport(LOG_WARN, "Unknown kevent (ident=%d)", (int)events[i].ident);
+                }
+                // don't warn in case flags is not 0, because socket EOF events
+                // are triggered even if we apply EV_DELETE in the changelist
+                // the only way to stop this is to apply the changelist without
+                // getting new events, but that comes with a performance penalty
+                
+                continue;
+            }
+            int event_events = event->events;
+            
             if(event->fn) {
-                int ep = event->events;
-                if(event->fn(ev, event)) {
-                    if(event->events != ep) {
-                        changes[numchanges++].filter = ev_convert2sys_events(ep);
+                int saved_ev = event->events;
+                if(!event->fn(ev, event)) {
+                    // ret 0 => remove event
+                    
+                    if(event->finish) {
+                        event->finish(ev, event);
                     }
-                } else if(event->finish) {
-                    changes[numchanges++].filter = ev_convert2sys_events(ep);
-                    event->finish(ev, event);
+                    
+                    event_events = 0;
+                } else {
+                    event_events = event->events;
+                }
+                
+                // if events have changed, we need to add/remove filters
+                if(saved_ev != event_events) {
+                    int e = event_events;
+                    int e_fd = events[i].ident;
+                    if((e & EVENT_POLLIN) != (saved_ev & EVENT_POLLIN)) {
+                        if((e & EVENT_POLLIN) == EVENT_POLLIN) {
+                            // add
+                            EV_SET(&changes[numchanges++], e_fd, EVFILT_READ, EV_ADD, 0, 0, event);
+                        } else {
+                            // delete
+                            EV_SET(&changes[numchanges++], e_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+                        }
+                    }
+                    if((e & EVENT_POLLOUT) != (saved_ev & EVENT_POLLOUT)) {
+                        if((e & EVENT_POLLOUT) == EVENT_POLLOUT) {
+                            // add
+                            EV_SET(&changes[numchanges++], e_fd, EVFILT_WRITE, EV_ADD, 0, 0, event);
+                        } else {
+                            // delete
+                            EV_SET(&changes[numchanges++], e_fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+                        }
+                    }
                 }
             }
         }
     }
 }
 
-int ev_convert2sys_events(int events) {
-    int e = 0;
-    if((events & EVENT_POLLIN) == EVENT_POLLIN) {
-        e |= EVFILT_READ;
-    }
-    if((events & EVENT_POLLOUT) == EVENT_POLLOUT) {
-        e |= EVFILT_WRITE;
-    }
-    return e;
-}
-
 int ev_pollin(EventHandler *h, int fd, Event *event) {
     event->events = EVENT_POLLIN;
     struct kevent kev;
@@ -122,6 +150,35 @@
     return kevent(h->kqueue, &kev, 1, NULL, 0, NULL);
 }
 
+int ev_remove_poll(EventHandler *h, int fd) {
+    struct kevent kev;
+    EV_SET(&kev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+    int r1 = kevent(h->kqueue, &kev, 1, NULL, 0, NULL);
+    EV_SET(&kev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+    int r2 = kevent(h->kqueue, &kev, 1, NULL, 0, NULL);
+    // in caase r1 or r2 was successful, we return 0 (no error)
+    return r1 != -1 || r2 != -1 ? 0 : 1;
+}
+
 int event_send(EventHandler *h, Event *event) {
     return 0;
 }
+
+// TODO: remove this fake aio
+int ev_aioread(int fd, aiocb_s *cb) {
+    ssize_t result = pread(fd, cb->buf, cb->nbytes, cb->offset);
+    cb->result = result;
+    if(result < 0) {
+        cb->result_errno = errno;
+    }
+    return event_send(cb->evhandler, cb->event);
+}
+
+int ev_aiowrite(int fd, aiocb_s *cb) {
+    ssize_t result = pwrite(fd, cb->buf, cb->nbytes, cb->offset);
+    cb->result = result;
+    if(result < 0) {
+        cb->result_errno = errno;
+    }
+    return event_send(cb->evhandler, cb->event);
+}
--- a/src/server/daemon/http.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/http.c	Sat Sep 24 16:26:10 2022 +0200
@@ -198,7 +198,7 @@
 
 /* ---------------------------- http_set_finfo ---------------------------- */
 
-static inline int set_finfo(Session *sn, Request *rq, off_t size, time_t mtime)
+static inline int set_finfo(Session *sn, Request *rq, off_t size, time_t mtime, const char *etag)
 {
     struct tm mtms;
     struct tm *mtm = system_gmtime(&mtime, &mtms);
@@ -223,15 +223,19 @@
     snprintf(pp->value, content_length_size, "%lld", (long long)size);
     pblock_kpinsert(pb_key_content_length, pp, rq->srvhdrs);
 
-    char *etag;
     if (http_etag) {
         /* Insert Etag */
-        pp = pblock_key_param_create(rq->srvhdrs, pb_key_etag, NULL, MAX_ETAG);
-        if (!pp || !pp->value)
-            return REQ_ABORTED;
-        http_format_etag(sn, rq, pp->value, MAX_ETAG, size, mtime);
-        pblock_kpinsert(pb_key_etag, pp, rq->srvhdrs);
-        etag = pp->value;
+        if(etag) {
+            pblock_kvinsert(pb_key_etag, etag, strlen(etag), rq->srvhdrs);
+        } else {
+            pp = pblock_key_param_create(rq->srvhdrs, pb_key_etag, NULL, MAX_ETAG);
+            if (!pp || !pp->value)
+                return REQ_ABORTED;
+            http_format_etag(sn, rq, pp->value, MAX_ETAG, size, mtime);
+            pblock_kpinsert(pb_key_etag, pp, rq->srvhdrs);
+            etag = pp->value;
+        }
+        
     } else {
         etag = NULL;
     }
@@ -242,7 +246,11 @@
 
 NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo)
 {
-    return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime);
+    return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime, NULL);
+}
+
+NSAPI_PUBLIC int http_set_finfo_etag(Session *sn, Request *rq, struct stat *finfo, const char *etag) {
+    return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime, etag);
 }
 
 
--- a/src/server/daemon/httplistener.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httplistener.c	Sat Sep 24 16:26:10 2022 +0200
@@ -237,23 +237,18 @@
         }
         // TODO: cleanup on error
         
-        sstr_t file;
         int ret;
         char errbuf[512];
         
         if(!conf->chainfile.ptr) {
-            file = sstrdup(conf->certfile);
-            ret = SSL_CTX_use_certificate_file(ctx, file.ptr, SSL_FILETYPE_PEM);
-            free(file.ptr);
+            ret = SSL_CTX_use_certificate_file(ctx, conf->certfile.ptr, SSL_FILETYPE_PEM);
             if(!ret) {
                 ERR_error_string(ERR_get_error(), errbuf);
                 log_ereport(LOG_MISCONFIG, "Cannot load ssl chain file: %s", errbuf);
                 return NULL;
             }
         } else {
-            file = sstrdup(conf->chainfile);
-            ret = SSL_CTX_use_certificate_chain_file(ctx, file.ptr);
-            free(file.ptr);
+            ret = SSL_CTX_use_certificate_chain_file(ctx, conf->chainfile.ptr);
             if(!ret) { 
                 ERR_error_string(ERR_get_error(), errbuf);
                 log_ereport(LOG_MISCONFIG, "Cannot load ssl cert file: %s", errbuf);
@@ -261,9 +256,7 @@
             }
         }
         
-        file = sstrdup(conf->privkeyfile);
-        ret = SSL_CTX_use_PrivateKey_file(ctx, file.ptr, SSL_FILETYPE_PEM);
-        free(file.ptr);
+        ret = SSL_CTX_use_PrivateKey_file(ctx, conf->privkeyfile.ptr, SSL_FILETYPE_PEM);
         if(!ret) { 
             ERR_error_string(ERR_get_error(), errbuf);
             log_ereport(LOG_MISCONFIG, "Cannot load ssl key file: %s", errbuf);
--- a/src/server/daemon/httplistener.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httplistener.h	Sat Sep 24 16:26:10 2022 +0200
@@ -64,10 +64,10 @@
     int                  nacceptors;
     WSBool               blockingio;
     WSBool               ssl;
-    sstr_t               certfile;
-    sstr_t               privkeyfile;
-    sstr_t               chainfile;
-    sstr_t               disable_proto;
+    scstr_t              certfile;
+    scstr_t              privkeyfile;
+    scstr_t              chainfile;
+    scstr_t              disable_proto;
 };
 
 struct _acceptor {
--- a/src/server/daemon/httpparser.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httpparser.c	Sat Sep 24 16:26:10 2022 +0200
@@ -187,7 +187,6 @@
     for(;i<line.length;i++) {
         if(!ns && line.ptr[i] == ' ') {
             ns = 1;
-            //line.ptr[i] = 0; // TODO: remove
             parser->request->method.length = i;
         } else if(ns) {
             if(line.ptr[i] != ' ') {
@@ -200,9 +199,8 @@
     ns = 0;
     int s = i;
     for(;i<line.length;i++) {
-        if(!ns && line.ptr[i] < 33) {
+        if(!ns && isspace(line.ptr[i])) {
             ns = 1;
-            //line.ptr[i] = 0; // TODO: remove
             parser->request->uri.length = i - s;
         } else if(ns) {
             if(line.ptr[i] > 32) {
@@ -215,9 +213,8 @@
     ns = 0;
     s = i;
     for(;i<line.length;i++) {
-        if(!ns && line.ptr[i] < 33) {
+        if(!ns && isspace(line.ptr[i])) {
             ns = 1;
-            //line.ptr[i] = 0; // TODO: remove
             parser->request->httpv.length = i - s;
         } else if(ns) {
             if(line.ptr[i] > 32) {
--- a/src/server/daemon/httprequest.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httprequest.c	Sat Sep 24 16:26:10 2022 +0200
@@ -87,6 +87,48 @@
     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;
+    sn->sn.inbuf = inbuf;
+    sn->sn.inbuf->sd = http;
+    return 0;
+}
+
 int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) {
     // handle nsapi request
      
@@ -94,11 +136,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 +150,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;
 
@@ -198,13 +231,13 @@
     /*
      * get absolute path and query of the request uri
      */
-    // TODO: check for '#'
+    // TODO: check for '#' #72
     sstr_t absPath = http_request_get_abspath(request);
     if(!absPath.ptr) {
         // TODO: error msg
         return 1;
     } else if(absPath.ptr[0] == '*') {
-        // TODO: implement global OPTIONS
+        // TODO: implement global OPTIONS #71
         return 1;
     }
     
@@ -342,44 +375,42 @@
     // 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);
-              
-        //printf("request body length: %d\n", ctlen);
+        int64_t ctlen;
+        if(util_strtoint(ctlen_str, &ctlen)) {
+            netbuf *nb = sn->netbuf;
+            HttpStream *net_io = (HttpStream*)sn->sn.csd;
+            net_io->read_eof = WS_FALSE;
 
-        netbuf *nb = request->netbuf;
+            // how many bytes are already read and in the buffer
+            int cur_input_available = nb->cursize - nb->pos;
 
-        // create new netbuf
-        HttpStream *net_io = (HttpStream*)httpstream_new(pool, io);
-        net_io->max_read = ctlen;
-        
-        sn->sn.inbuf = pool_malloc(pool, sizeof(netbuf));
-        sn->sn.inbuf->sd = net_io;
-        sn->sn.inbuf->pos = 0;
-        
-        // prepare buffer
-        int cur_input_len = nb->cursize - nb->pos;
-        
-        if(cur_input_len >= ctlen) {
-            /*
-             * all data is already in the primary input buffer
-             * just link the new netbuf to the primary buffer
-             */
-            sn->sn.inbuf->maxsize = ctlen;
-            sn->sn.inbuf->cursize = ctlen;
-            sn->sn.inbuf->inbuf = nb->inbuf + nb->pos;
-        } else {
-            sn->sn.inbuf->maxsize = (ctlen > 2048) ? (2048) : (ctlen);
-            sn->sn.inbuf->inbuf = pool_malloc(pool, sn->sn.inbuf->maxsize);
-
-            if(cur_input_len > 0) {
-                // we have read a part of the request body -> copy to netbuf
-                memcpy(sn->sn.inbuf->inbuf, nb->inbuf+nb->pos, cur_input_len);
+            if(cur_input_available >= ctlen) {
+                // we have the whole request body in the buffer and
+                // maybe even more
+                // no more read from the socket is necessary to get the body,
+                // therefore disable it
+                net_io->max_read = 0;
+            } else {
+                // read still required to get the complete request body
+                net_io->max_read = ctlen - cur_input_available;
             }
-
-            sn->sn.inbuf->cursize = cur_input_len;
+            //printf("request body length: %d\n", ctlen);
+        } // else: should we abort?
+    }
+    char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers);
+    if(transfer_encoding) {
+        if(!strcmp(transfer_encoding, "chunked")) {
+            netbuf *nb = sn->netbuf;
+            sn->buffer = (char*)nb->inbuf;
+            sn->pos = nb->pos;
+            sn->cursize = nb->cursize;
+            
+            if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) {
+                pool_destroy(pool);
+                // TODO: error 500 
+                return 1;
+            }
         }
-    } else {
-        sn->sn.inbuf = NULL;
     }
     
     //
@@ -535,6 +566,33 @@
 }
 
 int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq) {
+    request_free_resources(sn, rq);
+    
+    WSBool read_stream_eof = httpstream_eof(sn->sn.csd);
+    if(!read_stream_eof) {
+        log_ereport(LOG_WARN, "request input stream not closed");
+        // TODO: clean stream
+        rq->rq.rq_attr.keep_alive = 0; // workaround
+    }
+    if(sn->pos < sn->cursize) {
+        log_ereport(LOG_WARN, "nsapi_finish_request: TODO: remaining bytes in buffer");
+        // TODO: reuse buffer in next request
+        rq->rq.rq_attr.keep_alive = 0; // workaround
+    }
+    
+    if(rq->rq.senthdrs) {
+        // flush buffer and add termination if chunked encoding
+        // is enabled
+         net_finish(sn->sn.csd);
+    } else {
+        // why was no response sent?
+        // something must have gone wrong
+        // terminate the session
+        char *clf_req = pblock_findkeyval(pb_key_clf_request, rq->rq.reqpb);
+        log_ereport(LOG_WARN, "nsapi_finish_request: no response header: request: %s", clf_req);
+        rq->rq.rq_attr.keep_alive = 0;
+    }
+    
     if(rq->rq.rq_attr.keep_alive) {
         SessionHandler *sh = sn->connection->session_handler;
         sh->keep_alive(sh, sn->connection);
@@ -557,6 +615,16 @@
     return 0;
 }
 
+void request_free_resources(NSAPISession *sn, NSAPIRequest *rq) {
+    if(!rq->resources) return;
+    
+    UcxMapIterator i = ucx_map_iterator(rq->resources);
+    ResourceData *resource;
+    UCX_MAP_FOREACH(key, resource, i) {
+        resourcepool_free(&sn->sn, &rq->rq, resource);
+    }
+}
+
 int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq) {
     HTTPObjectConfig *objconf = rq->vs->objects;
     httpd_object *obj = objconf->objects[0];
@@ -835,12 +903,12 @@
             }
             
             if(ret != REQ_NOACTION) {
-                if(ret == REQ_PROCEED) {
-                    /*
-                     * flush buffer and add termination if chunked encoding
-                     * is enabled
-                     */
-                    net_finish(sn->sn.csd);
+                if(ret == REQ_PROCEED && !rq->rq.senthdrs) {
+                    // a service SAF must send a response
+                    // senthdrs == 0 indicators something has gone
+                    // wrong
+                    protocol_status(&sn->sn, &rq->rq, 500, NULL);
+                    ret = REQ_ABORTED;
                 } else if(ret == REQ_PROCESSING) {
                     // save nsapi context
                     rq->context.objset_index = i;
@@ -875,11 +943,18 @@
             if(ret == REQ_NOACTION) {
                 directive *d = dt->dirs[j];
 
-                // check status code parameter          
+                // check status code parameter
+                // Error SAFs can specify, for which status code they should
+                // be executed
                 char *status = pblock_findkeyval(pb_key_type, d->param);
                 if(status) {
-                    int statuscode = atoi(status);
-                    if(statuscode != rq->rq.status_num) {
+                    int64_t statuscode = -1;
+                    if(!util_strtoint(status, &statuscode)) {
+                        log_ereport(
+                                LOG_WARN,
+                                "nsapi_error: directive '%s' ignored: invalid type parameter: integer status code expected",
+                                d->func->name);
+                    } else if(statuscode != rq->rq.status_num) {
                         continue;
                     }
                 }
@@ -895,10 +970,8 @@
             }
             if(ret != REQ_NOACTION) {
                 if(ret == REQ_PROCEED) {
-                    /*
-                     * flush buffer and add termination if chunked encoding
-                     * is enabled
-                     */
+                    // flush buffer and add termination if chunked encoding
+                    // is enabled
                     net_finish(sn->sn.csd);
                 } else if(ret == REQ_PROCESSING) {
                     // save nsapi context
@@ -914,7 +987,9 @@
     
     if(ret != REQ_PROCEED) {
         // default error handler
-        nsapi_error_request((Session*)sn, (Request*)rq);
+        if(!rq->rq.senthdrs) {
+            nsapi_error_request((Session*)sn, (Request*)rq);
+        }
     }
 
     return ret;
--- a/src/server/daemon/httprequest.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/httprequest.h	Sat Sep 24 16:26:10 2022 +0200
@@ -73,6 +73,10 @@
 
 sstr_t http_request_get_abspath(HTTPRequest *req);
 
+
+NSAPISession* nsapisession_create(pool_handle_t *pool);
+int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io);
+
 /*
  * starts request processing after reading the request header
  * 
@@ -89,6 +93,8 @@
 int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq);
 int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq);
 
+void request_free_resources(NSAPISession *sn, NSAPIRequest *rq);
+
 int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq);
 int nsapi_nametrans(NSAPISession *sn, NSAPIRequest *rq);
 int nsapi_pathcheck(NSAPISession *sn, NSAPIRequest *rq);
--- a/src/server/daemon/keyfile_auth.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/keyfile_auth.c	Sat Sep 24 16:26:10 2022 +0200
@@ -43,40 +43,18 @@
 
 #include "keyfile_auth.h"
 
-Keyfile* keyfile_new() {
-    Keyfile *keyfile = malloc(sizeof(Keyfile));
+Keyfile* keyfile_new(UcxAllocator *a) {
+    Keyfile *keyfile = alcalloc(a, 1, sizeof(Keyfile));
+    if(!keyfile) {
+        return NULL;
+    }
     keyfile->authdb.get_user = keyfile_get_user;
     keyfile->authdb.use_cache = 0;
-    keyfile->users = ucx_map_new(16);
-    keyfile->ref = 1;
+    keyfile->users = ucx_map_new_a(a, 16);
     return keyfile;
 }
 
-void keyfile_ref(Keyfile *keyfile) {
-    ws_atomic_inc32(&keyfile->ref);
-}
-
-void keyfile_unref(Keyfile *keyfile) {
-    uint32_t ref = ws_atomic_dec32(&keyfile->ref);
-    if(ref == 0) { 
-        UcxMapIterator i = ucx_map_iterator(keyfile->users);
-        KeyfileUser *user;
-        UCX_MAP_FOREACH(key, user, i) {
-            free(user->user.name);
-            free(user->hash);
-            for(int n=0;n<user->numgroups;n++) {
-                free(user->groups[n].ptr);
-            }
-            free(user->groups);
-        }
-        ucx_map_free(keyfile->users);
-        
-        free(keyfile->authdb.name);
-        free(keyfile);
-    }
-}
-
-void keyfile_add_user(
+int keyfile_add_user(
         Keyfile *keyfile,
         sstr_t name,
         enum KeyfileHashType hash_type,
@@ -84,14 +62,16 @@
         sstr_t *groups,
         size_t ngroups)
 {
+    UcxAllocator *a = keyfile->users->allocator;
+    
     if(hash.length < 12) {
         // hash too short
         // TODO: log
-        return;
+        return -1;
     }
     
-    KeyfileUser *user = malloc(sizeof(KeyfileUser));
-    user->user.name = sstrdup(name).ptr;
+    KeyfileUser *user = almalloc(a, sizeof(KeyfileUser));
+    user->user.name = sstrdup_a(a, name).ptr;
     user->user.uid = -1;
     user->user.gid = -1;
     user->user.verify_password = keyfile_user_verify_password;
@@ -99,16 +79,29 @@
     user->user.free = keyfile_user_free;
     
     user->hash_type = hash_type;
-    user->hash = malloc(hash.length + 1);
+    user->hash = almalloc(a, hash.length + 1);
+    
+    if(!user->user.name || !user->hash) {
+        return -1;
+    }
+    
     user->hashlen = util_base64decode(hash.ptr, hash.length, user->hash);
     
-    user->groups = calloc(ngroups, sizeof(sstr_t));
-    for(int i=0;i<ngroups;i++) {
-        user->groups[i] = sstrdup(groups[i]);
+    if(ngroups > 0) {
+        user->groups = alcalloc(a, ngroups, sizeof(sstr_t));
+        if(!user->groups) {
+            return -1;
+        }
+        for(int i=0;i<ngroups;i++) {
+            user->groups[i] = sstrdup_a(a, groups[i]);
+        }
+        
+    } else {
+        user->groups = NULL;
     }
     
     // add to keyfile
-    ucx_map_sstr_put(keyfile->users, name, user);
+    return ucx_map_sstr_put(keyfile->users, name, user);
 }
 
 // authdb functions
--- a/src/server/daemon/keyfile_auth.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/keyfile_auth.h	Sat Sep 24 16:26:10 2022 +0200
@@ -48,9 +48,8 @@
 };
 
 struct keyfile {
-    AuthDB   authdb;
-    UcxMap   *users;
-    uint32_t ref;
+    AuthDB       authdb;
+    UcxMap       *users;
 };
 
 struct keyfile_user {
@@ -62,11 +61,9 @@
     size_t               hashlen;
 };
 
-Keyfile* keyfile_new();
-void keyfile_ref(Keyfile *keyfile);
-void keyfile_unref(Keyfile *keyfile);
+Keyfile* keyfile_new(UcxAllocator *a);
 
-void keyfile_add_user(
+int keyfile_add_user(
         Keyfile *keyfile,
         sstr_t user,
         enum KeyfileHashType hash_type,
--- a/src/server/daemon/ldap_auth.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/ldap_auth.c	Sat Sep 24 16:26:10 2022 +0200
@@ -47,9 +47,9 @@
 #endif
 }
 
-AuthDB* create_ldap_authdb(char *name, LDAPConfig *conf) {
-    LDAPAuthDB *authdb = malloc(sizeof(LDAPAuthDB));
-    authdb->authdb.name = strdup(name);
+AuthDB* create_ldap_authdb(ServerConfiguration *cfg, const char *name, LDAPConfig *conf) {
+    LDAPAuthDB *authdb = almalloc(cfg->a, sizeof(LDAPAuthDB));
+    authdb->authdb.name = pool_strdup(cfg->pool, name);
     authdb->authdb.get_user = ldap_get_user;
     authdb->authdb.use_cache = 1;
     authdb->config = *conf;
@@ -64,7 +64,7 @@
     // initialize group cache
     authdb->groups.first = NULL;
     authdb->groups.last = NULL;
-    authdb->groups.map = ucx_map_new(32);
+    authdb->groups.map = ucx_map_new_a(cfg->a, 32);
 
     return (AuthDB*) authdb;
 }
--- a/src/server/daemon/ldap_auth.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/ldap_auth.h	Sat Sep 24 16:26:10 2022 +0200
@@ -34,6 +34,8 @@
 #include <ldap.h>
 #include <ucx/map.h>
 
+#include "config.h"
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -90,7 +92,7 @@
     LDAPGroup   *next;
 };
 
-AuthDB* create_ldap_authdb(char *name, LDAPConfig *conf);
+AuthDB* create_ldap_authdb(ServerConfiguration *cfg, const char *name, LDAPConfig *conf);
 
 LDAP* get_ldap_session(LDAPAuthDB *authdb);
 
--- a/src/server/daemon/log.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/log.c	Sat Sep 24 16:26:10 2022 +0200
@@ -335,7 +335,9 @@
  * This source file only manages access log files. IO is performed directly
  * by AddLog safs. 
  */
-LogFile* get_access_log_file(sstr_t file) {
+LogFile* get_access_log_file(scstr_t file) {
+    // TODO: this looks dubious
+    
     if(!access_log_files) {
         access_log_files = ucx_map_new(4);
     }
--- a/src/server/daemon/log.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/log.h	Sat Sep 24 16:26:10 2022 +0200
@@ -40,10 +40,10 @@
 #endif
 
 typedef struct {
-    char   *file;
-    char   *level;
-    int    log_stdout;
-    int    log_stderr;
+    const char   *file;
+    const char   *level;
+    int          log_stdout;
+    int          log_stderr;
 } LogConfig;
 
 typedef struct {
@@ -75,7 +75,7 @@
 void log_remove_logdup(LogDup *dup);
 
 // access logging
-LogFile* get_access_log_file(sstr_t file);
+LogFile* get_access_log_file(scstr_t file);
 
 #ifdef	__cplusplus
 }
--- a/src/server/daemon/main.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/main.c	Sat Sep 24 16:26:10 2022 +0200
@@ -39,7 +39,7 @@
 #include "../util/plist.h"
 #include "../util/date.h"
 
-#include "../../ucx/string.h"
+#include <ucx/string.h>
 
 #include "webserver.h"
 #include "log.h"
@@ -167,7 +167,7 @@
     int status;
     status = webserver_init();
     if(status != 0) {
-        log_ereport(LOG_FAILURE, "cannot initialize server.");
+        log_ereport(LOG_FAILURE, "Cannot initialize server.");
         return EXIT_FAILURE;
     }
 
--- a/src/server/daemon/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/objs.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -56,6 +56,7 @@
 DAEMONOBJ += acl.o
 DAEMONOBJ += acldata.o
 DAEMONOBJ += vfs.o
+DAEMONOBJ += resourcepool.o
 
 # add additional platform dependend objects
 # defined in generated config.mk
--- a/src/server/daemon/protocol.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/protocol.c	Sat Sep 24 16:26:10 2022 +0200
@@ -347,7 +347,7 @@
         }
         
         // set stream property
-        HttpStream *stream = (HttpStream*)sn->csd;
+        HttpStream *stream = (HttpStream*)sn->csd; // TODO: make this typesafe
         stream->chunked_enc = 1;
         rq->rq_attr.chunked = 1;
     }
@@ -377,6 +377,16 @@
     return 0;
 }
 
+int http_send_continue(Session *sn) {
+    NSAPISession *s = (NSAPISession*)sn;
+    sstr_t msg = S("HTTP/1.1 100 Continue\r\n\r\n");
+    int w = s->connection->write(s->connection, msg.ptr, msg.length);
+    if(w != msg.length) {
+        return 1;
+    }
+    return 0;
+}
+
 int request_header(char *name, char **value, Session *sn, Request *rq) {
     const pb_key *key = pblock_key(name);
     pb_param *pp = pblock_findkey(key, rq->headers);
--- a/src/server/daemon/protocol.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/protocol.h	Sat Sep 24 16:26:10 2022 +0200
@@ -46,6 +46,8 @@
 
 int http_start_response(Session *sn, Request *rq);
 
+int http_send_continue(Session *sn);
+
 int request_header(char *name, char **value, Session *sn, Request *rq);
 
 void http_get_scheme_host_port(
--- a/src/server/daemon/request.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/request.h	Sat Sep 24 16:26:10 2022 +0200
@@ -32,6 +32,8 @@
 #include "../public/nsapi.h"
 #include "../util/object.h"
 
+#include <ucx/map.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -46,6 +48,7 @@
     uint16_t       port;
     NSAPIContext   context;
     void           *jvm_context;
+    UcxMap         *resources;
 };
 
 /* macros for context access */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/daemon/resourcepool.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,256 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "resourcepool.h"
+#include "request.h"
+#include "session.h"
+#include "../public/nsapi.h"
+#include "../util/atomic.h"
+
+#define RESOURCE_POOL_MAX_DEFAULT 32
+
+#define RESOURCE_POOL_MAX_ALLOC 268435455
+
+static UcxMap *resource_pool_types;
+
+int init_resource_pools(void) {
+    resource_pool_types = ucx_map_new(4);
+    return resource_pool_types ? 0 : 1;
+}
+
+int resourcepool_register_type(const char *type_name, ResourceType *type_info) {
+    if(ucx_map_cstr_put(resource_pool_types, type_name, type_info)) {
+        log_ereport(LOG_CATASTROPHE, "resourcepool_register_type: OOM");
+        return 1;
+    }
+    return 0;
+}
+
+
+
+int resourcepool_new(ServerConfiguration *cfg, scstr_t type, scstr_t name, ConfigNode *node) {
+    ResourceType *restype = ucx_map_sstr_get(resource_pool_types, type);
+    if(!restype) {
+        log_ereport(LOG_MISCONFIG, "Unknown resource pool type: %s", type.ptr);
+        return 1;
+    }
+    
+    // convert ConfigNode to pblock
+    // no sub-objects allowed for this specific ConfigNode, therefore
+    // it can be represented as key-value-pairs
+    pblock *param = config_obj2pblock(cfg->pool, node);
+    if(!param) {
+        log_ereport(LOG_FAILURE, "resourcepool_new: OOM");
+        return 1;
+    }
+    
+    ResourcePool *respool = pool_malloc(cfg->pool, sizeof(ResourcePool));
+    if(!respool) {
+        log_ereport(LOG_FAILURE, "resourcepool_new: OOM");
+        return 1;
+    }
+    respool->pool = cfg->pool;
+    
+    void *respool_data = restype->init(cfg->pool, name.ptr, param);
+    if(!respool_data) {
+        log_ereport(LOG_FAILURE, "Cannot create resource pool data: pool: %s type: %s", name.ptr, type.ptr);
+        return 1;
+    }
+    
+    respool->type = restype;
+    respool->data = respool_data;
+    respool->min = 4; // TODO: get from node
+    respool->max = RESOURCE_POOL_MAX_DEFAULT; // TODO: get from node
+    
+    respool->numcreated = 0;
+    respool->numresources = 0;
+    
+    
+    // don't allow too large resource pools
+    // this prevents the need to check malloc integer overflows
+    if(respool->max > RESOURCE_POOL_MAX_ALLOC) {
+        respool->max = RESOURCE_POOL_MAX_ALLOC;
+        log_ereport(LOG_WARN, "Resource pool %s: limit max to %d", name.ptr, respool->max);
+    }
+    
+    respool->resalloc = respool->max;
+    respool->resources = pool_malloc(cfg->pool, respool->resalloc * sizeof(ResourceDataPrivate*));
+    
+    if(!respool->resources || ucx_map_sstr_put(cfg->resources, name, respool)) {
+        log_ereport(LOG_FAILURE, "Cannot add resource pool: OOM");
+        // the only cleanup we have to do
+        restype->destroy(respool_data);
+        return 1;
+    }
+    
+    pthread_mutex_init(&respool->lock, NULL);
+    pthread_cond_init(&respool->available, NULL);
+    
+    return 0;
+}
+
+static ResourceData* s_resourcepool_lookup(ServerConfiguration *cfg, Request *opt_rq, Session *opt_sn, const char *name, int flags) {
+    NSAPIRequest *request = (NSAPIRequest*)opt_rq;
+    NSAPISession *session = (NSAPISession*)opt_sn;
+    ResourceDataPrivate *resource = NULL;
+    
+    // was this resource already used by this request?
+    if(request && request->resources) {
+        resource = ucx_map_cstr_get(request->resources, name);
+        if(resource) {
+            return &resource->data;
+        }
+    }
+    
+    ResourcePool *respool = ucx_map_cstr_get(cfg->resources, name);
+    if(!respool) return NULL;
+
+    
+    pthread_mutex_lock(&respool->lock);
+    WSBool createResource = FALSE;
+    if(respool->numcreated < respool->min) {
+        createResource = TRUE;
+    }
+    
+    if(createResource) {
+        // create a new resource and store it in the resourcepool
+        void *resourceData = respool->type->createresource(respool->data);
+        if(resourceData) {
+            respool->numcreated++;
+            
+            resource = pool_malloc(respool->pool, sizeof(ResourceDataPrivate));
+            if(resource) {
+                resource->data.data = respool->type->getresourcedata(resourceData);
+                resource->data.resourcepool = respool;
+                resource->resdata = resourceData;
+            } else {
+                respool->type->freeresource(respool->data, resourceData);
+                log_ereport(LOG_CATASTROPHE, "resourcepool_lookup: OOM");
+            }
+        }
+        // else: respool->type->createresource does logging in case of errors
+    } else if(respool->numresources > 0) {
+        resource = respool->resources[--respool->numresources];
+    } else {
+        // wait for free resource
+        pthread_cond_wait(&respool->available, &respool->lock);
+        if(respool->numresources > 0) {
+            resource = respool->resources[--respool->numresources];
+        }        
+    }
+    
+    // save the resource in the request object, for caching and also
+    // for cleanup later
+    int err = 0;
+    if(resource) {
+        if(request && session) {
+            if(!request->resources) {
+                request->resources = ucx_map_new_a(&session->allocator, 8);
+            }
+
+            if(request->resources) {
+                if(ucx_map_cstr_put(request->resources, name, resource)) {
+                    err = 1;
+                }
+            } else {
+                err = 1;
+            }
+        } // else: lookup is outside of any request context
+        
+        if(respool->type->prepare(respool->data, resource->resdata)) {
+            err = -1;
+        }
+    }
+    
+    if(err) {
+        // err == 1 caused by OOM
+        log_ereport(LOG_FAILURE, "resourcepool_lookup: OOM");
+        // cleanup
+        resourcepool_destroy_resource(resource);
+        resource = NULL;
+    }
+    
+    pthread_mutex_unlock(&respool->lock);
+    
+    return (ResourceData*)resource;
+}
+
+ResourceData* resourcepool_cfg_lookup(ServerConfiguration *cfg, const char *name, int flags) {
+    return s_resourcepool_lookup(cfg, NULL, NULL, name, flags);
+}
+
+ResourceData* resourcepool_lookup(Session *sn, Request *rq, const char *name, int flags) {
+    NSAPISession *session = (NSAPISession*)sn;
+    ServerConfiguration *cfg = session->config;
+    return s_resourcepool_lookup(cfg, rq, sn, name, flags);
+}
+
+void resourcepool_free(Session *sn, Request *rq, ResourceData *resource) {
+    ResourceDataPrivate *res = (ResourceDataPrivate*)resource;
+    ResourcePool *respool = resource->resourcepool;
+    
+    if(respool->type->finish(respool->data, res->resdata)) {
+        log_ereport(LOG_FAILURE, "resourcepool_free: finish failed");
+    }
+    
+    pthread_mutex_lock(&respool->lock);
+    
+    if(respool->numresources >= respool->resalloc) {
+        // actually respool->resalloc == respool->max
+        // and numresources should always be smaller
+        // however just be extra safe here
+        respool->resalloc += 8;
+        ResourceDataPrivate **new_res_array = pool_realloc(
+                respool->pool,
+                respool->resources,
+                respool->resalloc * sizeof(ResourceDataPrivate*));
+        if(new_res_array) {
+            respool->resources = new_res_array;
+        } else {
+            log_ereport(LOG_FAILURE, "resourcepool_free: OOM");
+            resourcepool_destroy_resource(res);
+            pthread_mutex_unlock(&respool->lock);
+            return;
+        }
+    }
+    
+    respool->resources[respool->numresources++] = res;
+    
+    pthread_cond_signal(&respool->available);
+    pthread_mutex_unlock(&respool->lock);
+}
+
+void resourcepool_destroy_resource(ResourceDataPrivate *res) {
+    res->data.resourcepool->numcreated--;
+    res->data.resourcepool->type->freeresource(res->data.resourcepool->data, res->resdata);
+    pool_free(res->data.resourcepool->pool, res);
+}
+
+void resourcepool_destroy(ResourcePool *respool) {
+    // TODO
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/daemon/resourcepool.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,125 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 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 WS_RESOURCEPOOL_H
+#define WS_RESOURCEPOOL_H
+
+#include "../public/nsapi.h"
+
+#include "config.h"
+
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct ResourceDataPrivate ResourceDataPrivate;
+
+struct ResourceDataPrivate {
+    ResourceData data;
+    
+    /*
+     * the void* pointer returned by respool->type->createresource
+     * 
+     * ResourceData.data contains the pointer returned by
+     * respool->type->getresourcedata(resdata))
+     */
+    void *resdata;
+};
+
+struct ResourcePool {
+    /*
+     * Memory pool
+     */
+    pool_handle_t *pool;
+    
+    /*
+     * Type information of this resource pool
+     * The type object is stored in the registered types map and should not
+     * be freed, when the resource pool is destroyed
+     */
+    ResourceType *type;
+    
+    /*
+     * Data returned by the ResourceType init function
+     * When the pool is destroyed, the data should be passed to
+     * ResourceType.destroy
+     */
+    void *data;
+    
+    pthread_mutex_t lock;
+    pthread_cond_t available;
+    
+    /*
+     * Array of available resources
+     * each entry is created with ResourceType.createresource
+     */
+    ResourceDataPrivate **resources;
+    
+    /*
+     * Allocated size of the resources array
+     */
+    size_t resalloc;
+    
+    /*
+     * Number of currently available resources in the array
+     */
+    size_t numresources;
+    
+    
+    /*
+     * Number of created resources (in use + available)
+     */
+    size_t numcreated;
+    
+    /*
+     * resource pool min parameter
+     */
+    int min;
+    
+    /*
+     * resource pool max parameter
+     */
+    int max;
+};
+
+int init_resource_pools(void);
+
+int resourcepool_new(ServerConfiguration *cfg, scstr_t type, scstr_t name, ConfigNode *node);
+
+void resourcepool_destroy_resource(ResourceDataPrivate *res);
+
+void resourcepool_destroy(ResourcePool *respool);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_RESOURCEPOOL_H */
+
--- a/src/server/daemon/session.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/session.c	Sat Sep 24 16:26:10 2022 +0200
@@ -40,3 +40,8 @@
     NSAPISession *sn = (NSAPISession*)s;
     return sn->config;
 }
+
+NSAPI_PUBLIC void* session_get_allocator(Session *sn) {
+    return &((NSAPISession*)sn)->allocator;
+}
+
--- a/src/server/daemon/session.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/session.h	Sat Sep 24 16:26:10 2022 +0200
@@ -47,6 +47,12 @@
     threadpool_t *currentpool;
     threadpool_t *defaultpool;
     
+    char *buffer;
+    int pos;
+    int cursize;
+    
+    UcxAllocator allocator;
+    
     ServerConfiguration *config;
 };
 
@@ -57,6 +63,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/sessionhandler.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/sessionhandler.c	Sat Sep 24 16:26:10 2022 +0200
@@ -126,6 +126,7 @@
 SessionHandler* create_basic_session_handler() {
     BasicSessionHandler *handler = malloc(sizeof(BasicSessionHandler));
     handler->threadpool = threadpool_new(4, 8);
+    threadpool_start(handler->threadpool); // TODO: handle error
     handler->sh.enqueue_connection = basic_enq_conn;
     handler->sh.keep_alive = basic_keep_alive;
     handler->sh.create_iostream = create_connection_iostream;
@@ -351,6 +352,9 @@
     if(state == 2) {
         // parse error
         fatal_error(request, 400);
+        log_ereport(LOG_VERBOSE, "http parser: bad request");
+        //printf("\n\n%.*s\n\n", parser->request->netbuf->cursize, parser->request->netbuf->inbuf);
+        //fflush(stdout);
         event->finish = evt_request_error;
         io->error = 2;
         return 0;
--- a/src/server/daemon/srvctrl.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/srvctrl.c	Sat Sep 24 16:26:10 2022 +0200
@@ -37,8 +37,8 @@
 
 #include "../util/systhr.h"
 
-#include "../../ucx/utils.h"
-#include "../../ucx/buffer.h"
+#include <ucx/utils.h>
+#include <ucx/buffer.h>
 
 #include "srvctrl.h"
 
--- a/src/server/daemon/srvctrl.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/srvctrl.h	Sat Sep 24 16:26:10 2022 +0200
@@ -31,7 +31,7 @@
 
 #include "../public/nsapi.h"
 
-#include "../../ucx/string.h"
+#include <ucx/string.h>
 
 #include "config.h"
 
--- a/src/server/daemon/threadpools.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/threadpools.c	Sat Sep 24 16:26:10 2022 +0200
@@ -45,7 +45,7 @@
 static threadpool_t *default_io_pool;
 static threadpool_t *last_io_pool;
 
-int create_threadpool(sstr_t name, ThreadPoolConfig *cfg) { 
+int create_threadpool(scstr_t name, ThreadPoolConfig *cfg) { 
     if(thread_pool_map == NULL) {
         thread_pool_map = ucx_map_new(16);
     }
@@ -63,7 +63,12 @@
     } else {
         threadpool_t *tp = threadpool_new(cfg->min_threads, cfg->max_threads);
         
-        int ret = ucx_map_sstr_put(thread_pool_map, name, tp);
+        int ret = 0;
+        if(!threadpool_start(tp)) {
+            ret = ucx_map_sstr_put(thread_pool_map, name, tp);
+        } else {
+            ret = 1;
+        }
         
         if(ret == 0) {
             num_thrpools++;
@@ -77,7 +82,7 @@
     }
 }
 
-int create_io_pool(sstr_t name, int numthreads) {
+int create_io_pool(scstr_t name, int numthreads) {
     if(io_pool_map == NULL) {
         io_pool_map = ucx_map_new(4);
     }
@@ -110,13 +115,13 @@
         cfg.max_threads = 8;
         cfg.queue_size = 64;
         cfg.stack_size = 262144;
-        if(create_threadpool(sstr("default"), &cfg)) {
+        if(create_threadpool(SC("default"), &cfg)) {
             return 1;
         }
     }
     
     if(num_iopools == 0) {
-        if(create_io_pool(sstr("default"), 8)) {
+        if(create_io_pool(SC("default"), 8)) {
             return 1;
         }
     }
--- a/src/server/daemon/threadpools.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/threadpools.h	Sat Sep 24 16:26:10 2022 +0200
@@ -45,7 +45,7 @@
     int queue_size;
 } ThreadPoolConfig;
 
-int create_threadpool(sstr_t name, ThreadPoolConfig *cfg);
+int create_threadpool(scstr_t name, ThreadPoolConfig *cfg);
 
 int check_thread_pool_cfg();
 
--- a/src/server/daemon/vfs.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vfs.c	Sat Sep 24 16:26:10 2022 +0200
@@ -45,16 +45,19 @@
 #define VFS_MALLOC(pool, size) pool ? pool_malloc(pool, size) : malloc(size)
 #define VFS_FREE(pool, ptr) pool ? pool_free(pool, ptr) : free(ptr)
 
-static UcxMap *vfs_map;
+static UcxMap *vfs_type_map;
 
 static VFS sys_vfs = {
     sys_vfs_open,
     sys_vfs_stat,
     sys_vfs_fstat,
     sys_vfs_opendir,
+    sys_vfs_fdopendir,
     sys_vfs_mkdir,
     sys_vfs_unlink,
-    VFS_CHECKS_ACL
+    sys_vfs_rmdir,
+    VFS_CHECKS_ACL,
+    NULL
 };
 
 static VFS_IO sys_file_io = {
@@ -66,8 +69,9 @@
     sys_file_close,
     //sys_file_aioread,
     //sys_file_aiowrite,
-    NULL,
-    NULL
+    NULL,  // aioread
+    NULL,  // aiowrite
+    NULL   // getetag
 };
 
 static VFS_DIRIO sys_dir_io = {
@@ -75,21 +79,58 @@
     sys_dir_close
 };
 
-int vfs_init() {
-    vfs_map = ucx_map_new(16);
-    if(!vfs_map) {
+int vfs_init(void) {
+    vfs_type_map = ucx_map_new(16);
+    if(!vfs_type_map) {
         return -1;
     }
     return 0;
 }
 
-void vfs_add(char *name, VFS *vfs) {
+int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate) {
     WS_ASSERT(name);
     
-    if(!vfs_map) {
-        vfs_init();
+    if(!vfs_type_map) {
+        if(vfs_init()) {
+            return 1;
+        }
+    }
+    
+    VfsType *vfsType = malloc(sizeof(VfsType));
+    if(!vfsType) {
+        return 1;
     }
-    ucx_map_cstr_put(vfs_map, name, vfs);
+    vfsType->init = vfsInit;
+    vfsType->create = vfsCreate;
+    
+    return ucx_map_cstr_put(vfs_type_map, name, vfsType);
+}
+
+VfsType* vfs_get_type(scstr_t vfs_class) {
+    return ucx_map_sstr_get(vfs_type_map, vfs_class);
+}
+
+void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error) {
+    *error = 0;
+    if(vfs_class->init) {
+        void *initData = vfs_class->init(cfg, pool, config);
+        if(!initData) {
+            *error = 1;
+        }
+        return initData;
+    } else {
+        return NULL;
+    }
+}
+
+VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData) {
+    VfsType *vfsType = ucx_map_cstr_get(vfs_type_map, vfs_class);
+    if(!vfsType) {
+        log_ereport(LOG_MISCONFIG, "vfs_create: unkown VFS type %s", vfs_class);
+        return NULL;
+    }
+    
+    return vfsType->create(sn, rq, pb, initData);
 }
 
 VFSContext* vfs_request_context(Session *sn, Request *rq) {
@@ -97,6 +138,9 @@
     WS_ASSERT(rq);
     
     VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext));
+    if(!ctx) {
+        return NULL;
+    }
     ctx->sn = sn;
     ctx->rq = rq;
     ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs;
@@ -105,10 +149,11 @@
     ctx->aclreqaccess = rq->aclreqaccess;
     ctx->pool = sn->pool;
     ctx->vfs_errno = 0;
+    ctx->error_response_set = 0;
     return ctx;
 }
 
-SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) {
+SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -125,23 +170,26 @@
         }
     }
     SYS_FILE file = ctx->vfs->open(ctx, path, oflags);
-    ctx->aclreqaccess = m; // restore original access mask
+    ctx->aclreqaccess = m; // restore original access mask 
+    if(!file && ctx) {
+        sys_set_error_status(ctx);
+    }
     return file;
 }
 
-SYS_FILE vfs_openRO(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_RDONLY);
 }
 
-SYS_FILE vfs_openWO(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_WRONLY | O_CREAT);
 }
 
-SYS_FILE vfs_openRW(VFSContext *ctx, char *path) {
+SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) {
     return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT);
 }
 
-int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) {
+int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -159,6 +207,9 @@
     }
     int ret = ctx->vfs->stat(ctx, path, buf);
     ctx->aclreqaccess = m; // restore original access mask
+    if(ret && ctx) {
+        sys_set_error_status(ctx);
+    }
     return ret;
 }
 
@@ -167,7 +218,20 @@
     WS_ASSERT(fd);
     WS_ASSERT(buf);
     
-    return ctx->vfs->fstat(ctx, fd, buf);
+    int ret = ctx->vfs->fstat(ctx, fd, buf);
+    if(ret && ctx) {
+        sys_set_error_status(ctx);
+    }
+    return ret;
+}
+
+const char * vfs_getetag(SYS_FILE fd) {
+    WS_ASSERT(fd);
+    
+    if(fd->io->opt_getetag) {
+        return fd->io->opt_getetag(fd);
+    }
+    return NULL;
 }
 
 void vfs_close(SYS_FILE fd) {
@@ -181,7 +245,7 @@
     }
 }
 
-VFS_DIR vfs_opendir(VFSContext *ctx, char *path) {
+VFS_DIR vfs_opendir(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
@@ -199,6 +263,33 @@
     }
     VFS_DIR dir = ctx->vfs->opendir(ctx, path);
     ctx->aclreqaccess = m; // restore original access mask
+    if(!dir && ctx) {
+        sys_set_error_status(ctx);
+    }
+    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
+    if(!dir && ctx) {
+        sys_set_error_status(ctx);
+    }
     return dir;
 }
 
@@ -227,23 +318,29 @@
     }
 }
 
-int vfs_mkdir(VFSContext *ctx, char *path) {
+int vfs_mkdir(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
     return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE);
 }
 
-int vfs_unlink(VFSContext *ctx, char *path) {
+int vfs_unlink(VFSContext *ctx, const char *path) {
     WS_ASSERT(ctx);
     WS_ASSERT(path);
     
     return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE);
 }
 
+int vfs_rmdir(VFSContext *ctx, const char *path) {
+    WS_ASSERT(ctx);
+    WS_ASSERT(path);
+    
+    return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE);
+}
 
 // private
-int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) {  
+int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) {  
     uint32_t access_mask = ctx->aclreqaccess;
     access_mask |= access;
     
@@ -259,12 +356,15 @@
     }
     int ret = op(ctx, path);
     ctx->aclreqaccess = m; // restore original access mask
+    if(ret && ctx) {
+        sys_set_error_status(ctx);
+    }
     return ret;
 }
 
 /* system vfs implementation */
 
-SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags) {
+SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) {
     uint32_t access_mask = ctx->aclreqaccess;
     pool_handle_t *pool = ctx->pool;
     
@@ -313,7 +413,7 @@
     return file;
 }
 
-int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf) {
+int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
     uint32_t access_mask = ctx->aclreqaccess;
     
     // check ACLs
@@ -353,7 +453,7 @@
     return 0;
 }
 
-VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path) {
+VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path) {
     uint32_t access_mask = ctx->aclreqaccess;
     pool_handle_t *pool = ctx->pool;
     
@@ -386,6 +486,9 @@
     DIR *sys_dir = fdopendir(dir_fd);
 #endif
     if(!sys_dir) {
+        if(dir_fd > 0) {
+            close(dir_fd);
+        }
         if(ctx) {
             ctx->vfs_errno = errno;
             sys_set_error_status(ctx);
@@ -422,16 +525,76 @@
     return dir;
 }
 
-int sys_vfs_mkdir(VFSContext *ctx, char *path) {
+VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    uint32_t access_mask = ctx->aclreqaccess;
+    pool_handle_t *pool = ctx->pool;
+    
+    // check ACLs
+    SysACL sysacl;
+    if(sys_acl_check(ctx, access_mask, &sysacl)) {
+        return NULL;
+    }
+    
+    if(sysacl.acl) {
+        if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) {
+            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
+            return NULL;
+        }
+    }
+    
+    // open directory
+    DIR *sys_dir = fdopendir(fd->fd);
+    if(!sys_dir) {
+        if(ctx) {
+            ctx->vfs_errno = errno;
+            sys_set_error_status(ctx);
+        }
+        return NULL;
+    }
+    
+    SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir));
+    if(!dir_data) {
+        closedir(sys_dir);
+        return NULL;
+    }
+    long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX);
+    size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1;
+    dir_data->cur = VFS_MALLOC(pool, entry_len);
+    if(!dir_data->cur) {
+        closedir(sys_dir);
+        VFS_FREE(pool, dir_data);
+        return NULL;
+    }
+    dir_data->dir = sys_dir;
+    
+    VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir));
+    if(!dir) {
+        closedir(sys_dir);
+        VFS_FREE(pool, dir_data->cur);
+        VFS_FREE(pool, dir_data);
+        return NULL;
+    }
+    dir->ctx = ctx;
+    dir->data = dir_data;
+    dir->fd = fd->fd;
+    dir->io = &sys_dir_io;
+    return dir;
+}
+
+int sys_vfs_mkdir(VFSContext *ctx, const char *path) {
     return sys_path_op(ctx, path, sys_mkdir);
 }
 
-int sys_vfs_unlink(VFSContext *ctx, char *path) {
+int sys_vfs_unlink(VFSContext *ctx, const char *path) {
     return sys_path_op(ctx, path, sys_unlink);
 }
 
+int sys_vfs_rmdir(VFSContext *ctx, const char *path) {
+    return sys_path_op(ctx, path, sys_rmdir);
+}
 
-int sys_path_op(VFSContext *ctx, char *path, sys_op_f op) {
+
+int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) {
     uint32_t access_mask = ctx->aclreqaccess;
     
     // check ACLs
@@ -484,9 +647,10 @@
 }
 
 void sys_set_error_status(VFSContext *ctx) {
-    if(ctx->sn && ctx->rq) {
+    if(ctx->sn && ctx->rq && !ctx->error_response_set) {
         int status = util_errno2status(ctx->vfs_errno);
         protocol_status(ctx->sn, ctx->rq, status, NULL);
+        ctx->error_response_set = TRUE;
     }
 }
 
@@ -539,6 +703,7 @@
             entry->name = name;
             if(getstat) {
                 // TODO: check ACLs again for new path
+                entry->stat_errno = 0;
                 if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) {
                     entry->stat_errno = errno;
                 }
@@ -567,7 +732,7 @@
     }
 }
 
-int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) {
+int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
     mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
     int ret = mkdir(path, mode);
     if(ret == 0) {
@@ -580,10 +745,14 @@
     return ret;
 }
 
-int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) {
+int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) {
     return unlink(path);
 }
 
+int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
+    return rmdir(path);
+}
+
 /* public file api */
 
 NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) {
--- a/src/server/daemon/vfs.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vfs.h	Sat Sep 24 16:26:10 2022 +0200
@@ -32,10 +32,17 @@
 #include "../public/vfs.h"
 #include "acl.h"
 
+#include <ucx/string.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
+typedef struct VfsType {
+    vfs_init_func      init;
+    vfs_create_func    create;
+} VfsType;
+    
 typedef struct SysVFSDir {
     DIR           *dir;
     struct dirent *cur;
@@ -47,20 +54,25 @@
 };
 typedef enum VFSAioOp VFSAioOp;
     
-int vfs_init();
+int vfs_init(void);
+VfsType* vfs_get_type(scstr_t vfs_class);
 
-typedef int(*vfs_op_f)(VFSContext *, char *);
-typedef int(*sys_op_f)(VFSContext *, char *, SysACL *);
-int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access);
+void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error);
+
+typedef int(*vfs_op_f)(VFSContext *, const char *);
+typedef int(*sys_op_f)(VFSContext *, const char *, SysACL *);
+int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access);
 
-SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags);
-int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf);
+SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags);
+int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf);
 int sys_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
-VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path);
-int sys_vfs_mkdir(VFSContext *ctx, char *path);
-int sys_vfs_unlink(VFSContext *ctx, char *path);
+VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path);
+VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
+int sys_vfs_mkdir(VFSContext *ctx, const char *path);
+int sys_vfs_unlink(VFSContext *ctx, const char *path);
+int sys_vfs_rmdir(VFSContext *ctx, const char *path);
 
-int sys_path_op(VFSContext *ctx, char *path, sys_op_f op);
+int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op);
 int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *externacl);
 void sys_set_error_status(VFSContext *ctx);
 
@@ -76,8 +88,9 @@
 int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat);
 void sys_dir_close(VFS_DIR dir);
 
-int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl);
-int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl);
+int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl);
+int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl);
+int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl);
 
 void vfs_queue_aio(aiocb_s *aiocb, VFSAioOp op);
 
--- a/src/server/daemon/vserver.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vserver.c	Sat Sep 24 16:26:10 2022 +0200
@@ -38,23 +38,6 @@
     return vs;
 }
 
-VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool) {
-    VirtualServer *newvs = malloc(sizeof(VirtualServer));
-    newvs->ref = 1;
-    newvs->document_root = sstrdup_pool(pool, vs->document_root);
-    newvs->host = sstrdup_pool(pool, vs->host);
-    newvs->name = sstrdup_pool(pool, vs->name);
-    newvs->objectfile = sstrdup_pool(pool, vs->objectfile);
-    newvs->acls = vs->acls;
-    acl_data_ref(newvs->acls);
-    newvs->log = vs->log; // TODO: ref
-    
-    newvs->objects = vs->objects;
-    
-    return newvs;
-}
-
-
 
 
 
--- a/src/server/daemon/vserver.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/vserver.h	Sat Sep 24 16:26:10 2022 +0200
@@ -58,7 +58,6 @@
 };
 
 VirtualServer* vs_new();
-VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool);
 
 
 #ifdef	__cplusplus
--- a/src/server/daemon/webserver.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/webserver.c	Sat Sep 24 16:26:10 2022 +0200
@@ -48,7 +48,7 @@
 #include "../util/io.h"
 #include "../util/util.h"
 
-#include "../../ucx/utils.h"
+#include <ucx/utils.h>
 
 #include "../safs/common.h"
 
@@ -60,6 +60,7 @@
 #include "log.h"
 #include "auth.h"
 #include "srvctrl.h"
+#include "resourcepool.h"
 
 extern struct FuncStruct webserver_funcs[];
 
@@ -78,6 +79,11 @@
     func_init();
     add_functions(webserver_funcs);
     
+    // init resource pools
+    if(init_resource_pools()) {
+        return -1;
+    }
+    
     // load init.conf
     if(load_init_conf("config/init.conf")) {
         return -1;
@@ -87,7 +93,6 @@
     init_configuration_manager();
     ServerConfiguration *cfg;
     if(cfgmgr_load_config(&cfg) != 0) {
-        fprintf(stderr, "Cannot load configuration\n");
         return -1;
     }
     
--- a/src/server/daemon/ws-fn.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/daemon/ws-fn.c	Sat Sep 24 16:26:10 2022 +0200
@@ -70,5 +70,7 @@
     { "set-variable", set_variable, NULL, NULL, 0},
     { "common-log", common_log, NULL, NULL, 0},
     { "send-cgi", send_cgi, NULL, NULL, 0},
+    { "webdav-init", webdav_init, NULL, NULL, 0},
+    { "webdav-service", webdav_service, NULL, NULL, 0},
     {NULL, NULL, NULL, NULL, 0}
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,69 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2022 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.
+#
+
+BUILD_ROOT = ../../../..
+
+include $(BUILD_ROOT)/config.mk
+
+# list of source files
+SRC  = init.c
+SRC += resource.c
+SRC += service.c
+SRC += vfs.c
+SRC += webdav.c
+SRC += config.c
+
+TEST_SRC  = pgtest.c
+
+OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/server/plugins/postgresql/%$(OBJ_EXT))
+
+TEST_OBJ = $(TEST_SRC:%.c=$(BUILD_ROOT)/build/server/plugins/postgresql/%$(OBJ_EXT))
+	
+BUILD_DIR = $(BUILD_ROOT)/build/server/plugins/postgresql
+
+PLUGIN_TARGET = $(BUILD_ROOT)/build/lib/libwspostgresql$(LIB_EXT)
+TEST_TARGET = $(BUILD_ROOT)/build/lib/libwspgtest$(LIB_EXT)
+
+PGTEST = $(BUILD_ROOT)/build/server/plugins/postgresql/test
+
+all: $(PLUGIN_TARGET) $(TEST_TARGET) $(PGTEST)
+
+$(BUILD_DIR):
+	mkdir -p $(BUILD_DIR)
+
+$(PLUGIN_TARGET): $(BUILD_DIR) $(OBJ)
+	$(CC) $(POSTGRESQL_LDFLAGS) $(SHLIB_LDFLAGS) -o $@ $(OBJ)
+	
+$(TEST_TARGET): $(BUILD_DIR) $(OBJ) $(TEST_OBJ)
+	$(CC) $(POSTGRESQL_LDFLAGS) $(SHLIB_LDFLAGS) -o $@ $(OBJ) $(TEST_OBJ)
+
+$(BUILD_DIR)/%.o: %.c
+	$(CC) $(CFLAGS) $(POSTGRESQL_CFLAGS) $(SHLIB_CFLAGS) -c -o $@ $<
+
+$(PGTEST): $(OBJ)
+	cd test; $(MAKE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/config.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,394 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "config.h"
+
+#include "../../util/util.h"
+
+#include <libxml/tree.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+static int pg_load_ext_dav_config(
+        ServerConfiguration *cfg,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file);
+
+
+static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;";
+
+
+// Uses a PGconn to lookup the resource id of the specified root node
+// if the lookup succeeds, the resource id is written to rootid
+// in case of an error, an error message will be logged
+// returns: 0 success, 1 error
+int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) {
+    PGconn *connection = res->data;
+    
+    PGresult *result = PQexecParams(
+            connection,
+            sql_get_repository_root,
+            1,     // number of parameters
+            NULL,
+            &rootnode, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    if(!result) {
+        log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection));
+        return 1;
+    }
+    
+    int ret = 0;
+    
+    int nrows = PQntuples(result);
+    if(nrows == 1) {
+        char *resource_id_str = PQgetvalue(result, 0, 0);
+        if(resource_id_str) {
+            if(!util_strtoint(resource_id_str, rootid)) {
+                log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode);
+                ret = 1;
+            }
+        }
+    } else {
+        log_ereport(LOG_FAILURE, "pg: cannot find root resource '%s'", rootnode);
+        ret = 1;
+    }
+    PQclear(result);
+    
+    return ret;
+}
+
+PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) {
+    UcxAllocator a = util_pool_allocator(pool);
+    
+    ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql"));
+    if(!pg) {
+        log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object");
+        return NULL;
+    }
+    
+    scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool"));
+    scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId"));
+    scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode"));
+    scstr_t cfg_dav = serverconfig_directive_value(pg, SC("PGDavConfig"));
+    
+    // minimum requirement is a resource pool
+    if(cfg_respool.length == 0) {
+        return NULL;
+    }
+    
+    // check rootid
+    int64_t root_id = 1;
+    if(cfg_rootid.length > 0) {
+        if(!util_strtoint(cfg_rootid.ptr, &root_id)) {
+            log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr);
+            return NULL;
+        }
+    }
+    
+    // warn if RootId and RootNode are specified
+    if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) {
+        log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored");
+    } else if(cfg_rootnode.length > 0) {
+        // check root node
+        
+        // resolve rootnode
+        // therefore we first need to get a connection from the resourcepool
+        ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0);
+        if(!res) {
+            log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed");
+            return NULL;
+        }
+        // do lookup, if successful, root_id will be set
+        int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id);
+        // we don't need the connection anymore
+        resourcepool_free(NULL, NULL, res);
+        if(lookup_err) {
+            // no logging required, pg_lookup_root will log the error
+            return NULL;
+        }
+    }
+    
+    PgRepository *repo = pool_malloc(pool, sizeof(PgRepository));
+    ZERO(repo, sizeof(PgRepository));
+    
+    repo->resourcepool = sstrdup_a(&a, cfg_respool);
+    repo->root_resource_id = root_id;
+    
+    // check for extended pg dav config
+    if(cfg_dav.length > 0) {
+        // load extended config from config file
+        char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr);
+        if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) {
+            // error
+            repo = NULL; // no need to cleanup because everything is from the pool
+        }
+        free(cfg_file_path);
+    }
+    
+    return repo;
+}
+
+static int pg_ext_get_config(
+        ServerConfiguration *cfg,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file_path,
+        xmlNode *root);
+static int pg_ext_get_extension(
+        PgExtParser *ext,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file_path,
+        xmlNode *ext_node);
+
+static const char* pg_util_xml_get_text(const xmlNode *elm) {
+    xmlNode *node = elm->children;
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (const char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+// load additional postgresql webdav config from the specified xml file
+static int pg_load_ext_dav_config(
+        ServerConfiguration *cfg,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file_path)
+{
+    UcxAllocator a = util_pool_allocator(pool);
+    
+    // check if the file exists and can be accessed
+    struct stat s;
+    if(stat(file_path, &s)) {
+        if(errno == ENOENT) {
+            log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path);
+        } else {
+            log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path);
+        }
+        
+        return 1;
+    }
+    
+    // prepare PgRepository
+    repo->prop_ext = ucx_map_new_a(&a, 8);
+    if(!repo->prop_ext) {
+        log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM");
+        return 1;
+    }
+    
+    // load xml document
+    xmlDoc *doc = xmlReadFile(file_path, NULL, 0);
+    if(!doc) {
+        log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path);
+        return 1;
+    }
+    
+    // the root element must be <repository>
+    int ret = 0;
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    if(xstreq(xml_root->name, "repository")) {
+        // parse config
+        ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root);
+    } else {
+        log_ereport(LOG_MISCONFIG, "pg: config %s: root element <repository> expected", file_path);
+        ret = 1;
+    }
+    xmlFreeDoc(doc);
+    
+    return ret;
+}
+
+
+
+
+static int pg_ext_get_config(
+        ServerConfiguration *cfg,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file_path,
+        xmlNode *root)
+{
+    xmlNode *node = root->children;
+    int ret = 0;
+    
+    PgExtParser parserData;
+    parserData.table_lookup = ucx_map_new(8);
+    parserData.tables = NULL;
+    
+    while(node && !ret) {
+        // currently, the only possible config element is <extension>
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "extension")) {
+                ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node);
+            }
+        }
+        node = node->next;
+    }
+    
+    // convert parserData
+    if(!ret) {
+        size_t ntables = ucx_list_size(parserData.tables);
+        repo->ntables = ntables;
+        repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable));
+        if(repo->tables) {
+            int i = 0;
+            UCX_FOREACH(elm, parserData.tables) {
+                PgExtTable *tab = elm->data;
+                repo->tables[i++] = *tab;
+            }
+        } else {
+            ret = 1;
+        }
+        
+    }
+    
+    // cleanup parser
+    ucx_list_free_content(parserData.tables, free);
+    ucx_list_free(parserData.tables);
+    ucx_map_free(parserData.table_lookup);
+    
+    return ret;
+}
+
+static int pg_ext_get_extension(
+        PgExtParser *ext,
+        pool_handle_t *pool,
+        PgRepository *repo,
+        const char *file_path,
+        xmlNode *ext_node)
+{
+    UcxAllocator a = util_pool_allocator(pool);
+    
+    xmlNode *node = ext_node->children;
+    
+    const char *table = NULL;
+    UcxList *properties = NULL;
+    
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "table")) {
+                const char *value = pg_util_xml_get_text(node);
+                if(!value) {
+                    log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table);
+                    return 1;
+                } else if(table) {
+                    log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table);
+                    return 1;
+                }
+                table = value;
+            } else if(xstreq(node->name, "properties")) {
+                // add all child elements to the properties list
+                xmlNode *ps = node->children;
+                while(ps) {
+                    if(ps->type == XML_ELEMENT_NODE) {
+                        // validate
+                        // required: namespace, value
+                        if(!ps->ns || !ps->ns->href) {
+                            log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name);
+                            return 1;
+                        }
+                        const char *value = pg_util_xml_get_text(ps);
+                        if(!value) {
+                            log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name);
+                            return 1;
+                        }
+                        properties = ucx_list_append_a(&a, properties, ps);
+                    }
+                    ps = ps->next;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    // check if anything is missing
+    if(!table) {
+        log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path);
+        return 1;
+    }
+    if(!properties) {
+        log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path);
+        return 1;
+    }  
+    
+    // check if the table was already specified
+    if(ucx_map_cstr_get(ext->table_lookup, table)) {
+        log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table);
+        return 1;
+    }
+    // mark table as used
+    // tabname will be used later, so ist must be allocated in the pool
+    char *tabname = pool_strdup(pool, table);
+    if(!tabname) return 1;
+    
+    // exttable is only used temporarily
+    PgExtTable *exttable = malloc(sizeof(PgExtTable));
+    if(!exttable) return 1;
+    exttable->table = tabname;
+    exttable->isused = 0; // not relevant in config
+    int tableindex = (int)ucx_list_size(ext->tables);
+    ext->tables = ucx_list_append(ext->tables, exttable);
+    
+    if(ucx_map_cstr_put(ext->table_lookup, table, table)) {
+        return 1;
+    }
+     
+    UCX_FOREACH(elm, properties) {
+        xmlNode *ps = elm->data;
+        const char *value = pg_util_xml_get_text(ps);
+        
+        PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt));
+        if(!ext_col) {
+            return 1;
+        }
+        ext_col->tableindex = tableindex;
+        ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href);
+        ext_col->name = pool_strdup(pool, (const char*)ps->name);
+        ext_col->column = pool_strdup(pool, (const char*)value);
+        if(!ext_col->ns || !ext_col->name || !ext_col->column) {
+            return 1;
+        }
+        
+        UcxKey key = webdav_property_key(ext_col->ns, ext_col->name);
+        int err = ucx_map_put(repo->prop_ext, key, ext_col);
+        free((void*)key.data);
+        if(err) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/config.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,83 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 WS_PG_CONFIG_H
+#define WS_PG_CONFIG_H
+
+#include "../../public/nsapi.h"
+#include "../../public/webdav.h"
+
+#include "../../daemon/config.h"
+#include "../../config/serverconfig.h"
+
+#include <inttypes.h>
+
+#include <libpq-fe.h>
+
+#include <ucx/string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    char   *table;
+    WSBool isused;
+} PgExtTable;
+    
+typedef struct PgRepository {
+    int64_t root_resource_id;
+    sstr_t resourcepool;
+    PgExtTable *tables;
+    size_t ntables;
+    UcxMap *prop_ext;
+} PgRepository;
+
+typedef struct {
+    char *column;
+    char *ns;
+    char *name;
+    int  tableindex;
+} PgPropertyStoreExt;
+
+typedef struct {
+    UcxMap *table_lookup;
+    UcxList *tables;
+} PgExtParser;
+
+int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid);
+
+PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_PG_CONFIG_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/init.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "init.h"
+
+#include "resource.h"
+#include "vfs.h"
+#include "webdav.h"
+
+int pg_init(pblock *pb, Session *sn, Request *rq) {
+    if(resourcepool_register_type("postgresql", pg_get_resource_type())) {
+        log_ereport(LOG_FAILURE, "pg-init: Cannot register resourcepool type");
+        return REQ_ABORTED;
+    }
+    
+    if(pg_register_vfs(pb)) {
+        log_ereport(LOG_FAILURE, "pg-init: Cannot register vfs type");
+        return REQ_ABORTED;
+    }
+    
+    if(pg_register_webdav_backend(pb)) {
+        log_ereport(LOG_FAILURE, "pg-init: Cannot register dav type");
+        return REQ_ABORTED;
+    }
+    
+    return REQ_PROCEED;
+}
+
+int pg_register_vfs(pblock *pb) {
+    return vfs_register_type("postgresql", pg_vfs_init, pg_vfs_create);
+}
+
+int pg_register_webdav_backend(pblock *pb) {
+    if(webdav_register_backend("postgresql", pg_webdav_init, pg_webdav_create)) {
+        return 1;
+    }
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/init.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 WS_PG_INIT_H
+#define WS_PG_INIT_H
+
+#include "../../public/nsapi.h"
+
+#include <libpq-fe.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int pg_init(pblock *pb, Session *sn, Request *Rq);
+
+int pg_register_vfs(pblock *pb);
+
+int pg_register_webdav_backend(pblock *pb);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_PG_INIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/pgtest.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,911 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 <inttypes.h>
+
+#include "../../util/util.h"
+#include "../../test/testutils.h"
+#include "../../test/webdav.h"
+#include "../../public/nsapi.h"
+#include "../../public/webdav.h"
+#include "../../webdav/webdav.h"
+
+#include <ucx/string.h>
+#include <ucx/utils.h>
+#include <ucx/buffer.h>
+
+#include "pgtest.h"
+#include "vfs.h"
+#include "webdav.h"
+
+#include <libpq-fe.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+static char *pg_connstr = "postgresql://localhost/test1";
+static int abort_pg_tests = 0;
+static PGconn *test_connection;
+static ResourceData resdata;
+static PgRepository test_repo;
+
+void debug_print_resources(void) {
+    PGresult *result = PQexec(test_connection, "select * from Resource;");
+    int n = PQntuples(result);
+    printf("\nntuples: %d\n-----------------------------------------------\n", n);
+    printf("%10s  %10s  %s\n", "resource_id", "parent_id", "nodename");
+    for(int i=0;i<n;i++) {
+        char *res_id = PQgetvalue(result, i, 0);
+        char *parent_id = PQgetvalue(result, i, 1);
+        char *nodename = PQgetvalue(result, i, 2);
+        printf("%10s  %10s  %s\n", res_id, parent_id, nodename);
+    }
+    printf("\n");
+}
+
+static void test_root_lookup(void) {
+    memset(&test_repo,  0, sizeof(PgRepository));
+    
+    int64_t root_id = -1;
+    int err = pg_lookup_root(&resdata, "root", &root_id);
+    test_repo.root_resource_id = root_id;
+    
+    if(err || root_id < 0) {
+        abort_pg_tests = 1;
+    }
+}
+
+void register_pg_tests(int argc, char **argv, UcxTestSuite *suite) {
+    
+    test_connection = PQconnectdb(pg_connstr);
+    if(!test_connection) {
+        abort_pg_tests = 1;
+    }
+    
+    if(PQstatus(test_connection) != CONNECTION_OK) {
+        abort_pg_tests = 1;
+    }
+    
+    resdata.data = test_connection;
+    test_root_lookup();
+    
+    ucx_test_register(suite, test_pg_conn);
+    if(!abort_pg_tests) {
+        ucx_test_register(suite, test_pg_lookup_root);
+        
+        ucx_test_register(suite, test_pg_vfs_open);
+        ucx_test_register(suite, test_pg_vfs_io);
+        ucx_test_register(suite, test_pg_vfs_stat);
+        ucx_test_register(suite, test_pg_vfs_mkdir);
+        ucx_test_register(suite, test_pg_vfs_unlink);
+        ucx_test_register(suite, test_pg_vfs_rmdir);
+        
+        ucx_test_register(suite, test_pg_webdav_create_from_resdata);
+        ucx_test_register(suite, test_pg_prepare_tests);
+        ucx_test_register(suite, test_pg_webdav_propfind);
+        ucx_test_register(suite, test_pg_webdav_propfind_allprop);
+        ucx_test_register(suite, test_pg_webdav_proppatch_set);
+        
+        PGresult *result = PQexec(test_connection, "BEGIN");
+        PQclear(result);
+    }
+}
+
+static void parse_response_tag(TestMultistatus *ms, xmlNode *node) {
+    // thanks to dav for some of this code
+    UcxAllocator *a = ms->mp->allocator;
+    node = node->children;
+    
+    sstr_t href = {NULL, 0};
+    UcxMap *properties = ucx_map_new_a(ms->mp->allocator, 16);
+    
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "href")) {
+                xmlNode *href_node = node->children;
+                if(href_node->type != XML_TEXT_NODE) {
+                    return;
+                }
+                href = sstrdup_a(ms->mp->allocator, scstr((const char*)href_node->content));
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int status_code = 0;
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        if(xstreq(n->name, "prop")) {
+                            prop_node = n;
+                        } else if(xstreq(n->name, "status")) {
+                            xmlNode *status_node = n->children;
+                            if(status_node->type != XML_TEXT_NODE) {
+                                return;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                return;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            sstr_t status_s = sstrdup(status_str);
+                            status_code = atoi(status_s.ptr);
+                            free(status_s.ptr);
+                        }
+                    }    
+                    n = n->next;
+                }
+                
+                n = prop_node->children;
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        TestProperty *property = ucx_mempool_calloc(ms->mp, 1, sizeof(TestProperty));
+                        if(n->ns) {
+                            property->prefix = n->ns->prefix ? sstrdup_a(a, scstr((const char*)n->ns->prefix)).ptr : NULL;
+                            property->namespace = n->ns->href ? sstrdup_a(a, scstr((const char*)n->ns->href)).ptr : NULL;
+                        }
+                        property->name = sstrdup_a(a, scstr((const char*)n->name)).ptr;
+                        property->node = n;
+                        property->status = status_code;
+                        xmlNode *value = n->children;
+                        if(value && value->type == XML_TEXT_NODE) {
+                            property->value = sstrdup_a(a, scstr((const char*)value->content)).ptr;
+                        }
+                        sstr_t pname = sstrcat(2, sstr(property->namespace), sstr(property->name));
+                        ucx_map_sstr_put(properties, pname, property);
+                        free(pname.ptr);
+                    }
+                    n = n->next;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    TestResponse *resp =almalloc(a, sizeof(TestResponse));
+    resp->href = href.ptr;
+    resp->properties = properties;
+    
+    ucx_map_sstr_put(ms->responses, href, resp);
+}
+
+TestMultistatus* test_parse_multistatus(const char *space, size_t size) {
+    xmlDoc *doc = xmlReadMemory(space, size, NULL, NULL, 0);
+    if(!doc) {
+        return NULL;
+    }
+    
+    UcxMempool *mp = ucx_mempool_new(64);
+    TestMultistatus *ms = ucx_mempool_malloc(mp, sizeof(TestMultistatus));
+    ms->doc = doc;
+    ms->mp = mp;
+    ms->responses = ucx_map_new_a(mp->allocator, 8);
+    
+    // parse response
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    xmlNode *node = xml_root->children;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "response")) {
+                parse_response_tag(ms, node);
+            }
+        }
+        node = node->next;
+    }
+    
+    return ms;
+}
+
+
+void test_multistatus_destroy(TestMultistatus *ms) {
+    if(!ms) return;
+    xmlFreeDoc(ms->doc);
+    ucx_mempool_destroy(ms->mp);
+}
+
+
+UCX_TEST(test_pg_conn) {
+    char *msg = test_connection ? PQerrorMessage(test_connection) : "no connection";
+    
+    UCX_TEST_BEGIN;
+    
+    if(abort_pg_tests) {
+        int msglen = strlen(msg);
+        if(msglen > 0 && msg[msglen-1] == '\n') {
+            msglen--;
+        }
+        fprintf(stdout, "%.*s: ", msglen, msg);
+        UCX_TEST_ASSERT(1 == 0, "skip pg tests");
+    } else {
+        UCX_TEST_ASSERT(1 == 1, "ok");
+    }
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_lookup_root) {
+    UCX_TEST_BEGIN;
+    
+    // test already done in test_root_lookup()
+    UCX_TEST_ASSERT(!abort_pg_tests, "Lookup failed");
+    
+    UCX_TEST_END;
+}
+
+
+static VFS* create_test_pgvfs(Session *sn, Request *rq) { 
+    return pg_vfs_create_from_resourcedata(sn, rq, &test_repo, &resdata);
+}
+
+
+UCX_TEST(test_pg_vfs_open) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE file;
+    
+    UCX_TEST_BEGIN;
+    
+    file = vfs_open(vfs, "/test_notfound1", O_RDONLY);
+    UCX_TEST_ASSERT(!file, "/test_notfound should not exist");
+    
+    file = vfs_open(vfs, "/test_file1", O_RDWR | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot create file 1");
+    
+    vfs_close(file);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_io) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE file;
+    SYS_FILE file2;
+    
+    UCX_TEST_BEGIN;
+    
+    file = vfs_open(vfs, "/test_f1", O_WRONLY | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot open file1");
+    
+    int w = system_fwrite(file, "test1\n", 6);
+    UCX_TEST_ASSERT(w == 6, "fwrite ret (1)");
+    w = system_fwrite(file, "2", 1);
+    UCX_TEST_ASSERT(w == 1, "fwrite ret (2)");
+    
+    vfs_close(file);
+    
+    file = vfs_open(vfs, "/test_f1", O_RDONLY);
+    file2 = vfs_open(vfs, "/test_f2", O_WRONLY | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot open file1");
+    UCX_TEST_ASSERT(file2, "cannot open file2");
+    
+    char buf[128];
+    int r = system_fread(file, buf, 128);
+    UCX_TEST_ASSERT(r == 7, "cannot read from file1");
+    
+    w = system_fwrite(file2, buf, r);
+    UCX_TEST_ASSERT(w == 7, "cannot write to file2");
+    
+    vfs_close(file);
+    vfs_close(file2);
+    
+    file2 = vfs_open(vfs, "/test_f2", O_RDONLY);
+    
+    r = system_fread(file, buf, 128);
+    UCX_TEST_ASSERT(r == 7, "fread ret");
+    UCX_TEST_ASSERT(!memcmp(buf, "test1\n2", 7), "wrong buffer content after read");
+    
+    vfs_close(file2);
+    
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_stat) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    // testdata, content doesn't matter
+    char test1[512];
+    memset(test1, 'x', 512);
+    const int test_len1 = 200;
+    const int test_len2 = 432;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_s1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "cannot open test_s1");
+    system_fwrite(f1, test1, test_len1);
+    vfs_close(f1);
+    
+    SYS_FILE f2 = vfs_open(vfs, "/test_s2", O_RDWR|O_CREAT);
+    UCX_TEST_ASSERT(f2, "cannot open test_s2");
+    system_fwrite(f2, test1, test_len2);
+    vfs_close(f2);
+    
+    struct stat st1, st2;
+    int r1 = vfs_stat(vfs, "/test_s1", &st1);
+    int r2 = vfs_stat(vfs, "/test_s2", &st2);
+    
+    UCX_TEST_ASSERT(r1 == 0, "stat1 failed");
+    UCX_TEST_ASSERT(r2 == 0, "stat2 failed");
+    
+    UCX_TEST_ASSERT(st1.st_size == test_len1, "s1 wrong length");
+    UCX_TEST_ASSERT(st2.st_size == test_len2, "s2 wrong length");
+    
+    int testfail = vfs_stat(vfs, "/test_stat_fail", &st1);
+    UCX_TEST_ASSERT(testfail != 0, "stat 3 should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_mkdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    struct stat s;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1 == NULL, "open should fail");
+    
+    int r = vfs_mkdir(vfs, "/test_mkdir");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed");
+    
+    r = vfs_stat(vfs, "/test_mkdir", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (1) failed");
+    
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir is not a directory");
+    
+    f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT);
+    vfs_close(f1);
+    UCX_TEST_ASSERT(f1, "open failed");
+    
+    r = vfs_stat(vfs, "/test_mkdir/file", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (2) failed");
+
+    r = vfs_mkdir(vfs, "/test_mkdir/test_sub");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (2)");
+    
+    r = vfs_stat(vfs, "/test_mkdir/test_sub", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (3) failed");
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub is not a directory");
+    
+    r = vfs_mkdir(vfs, "/test_mkdir/test_sub/test_sub2/");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (4)");
+    
+    r = vfs_stat(vfs, "/test_mkdir/test_sub/test_sub2/", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (4) failed");
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub/test_sub2/ is not a directory");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_unlink) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_unlink1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "cannot create test file");
+    system_fwrite(f1, "test", 4);
+    
+    PgFile *pgfile = f1->data;
+    Oid oid = pgfile->oid;
+    
+    vfs_close(f1);
+    
+    int r = vfs_unlink(vfs, "/test_unlink1");
+    UCX_TEST_ASSERT(r == 0, "unlink failed");
+    
+    f1 = vfs_open(vfs, "/test_unlink1", O_RDONLY);
+    UCX_TEST_ASSERT(f1 == NULL, "test file not deleted");
+    
+    PGresult *result = PQexec(test_connection, "savepoint sp;");
+    PQclear(result);
+    int pgfd = lo_open(test_connection, oid, INV_READ);
+    UCX_TEST_ASSERT(pgfd < 0, "large object not deleted");
+    result = PQexec(test_connection, "rollback to savepoint sp;");
+    PQclear(result);
+    
+    r = vfs_unlink(vfs, "/test_unlink1");
+    UCX_TEST_ASSERT(r, "unlink should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_rmdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    PQexec(test_connection, "delete from Resource where parent_id is not null;");
+    
+    UCX_TEST_BEGIN;
+    
+    int r;
+    SYS_FILE f1;
+    
+    // prepare some dirs/files
+    r = vfs_mkdir(vfs, "/rmdir_test");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (1)");
+    r = vfs_mkdir(vfs, "/rmdir_test/subdir1");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (2)");
+    r = vfs_mkdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (3)");
+    
+    f1 = vfs_open(vfs, "/rmdir_test/subdir2/file", O_CREAT|O_WRONLY);
+    UCX_TEST_ASSERT(f1, "open failed");
+    vfs_close(f1);
+    
+    // test rmdir
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir1");
+    UCX_TEST_ASSERT(r == 0, "rmdir failed");;
+    
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r != 0, "rmdir should fail if the dir is not empty");
+    
+    r = vfs_unlink(vfs, "/rmdir_test/subdir2/file");
+    UCX_TEST_ASSERT(r == 0, "unlink failed");
+    
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r == 0, "rmdir failed 2");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+/* ----------------------------- WebDAV tests ----------------------------- */
+
+
+static WebdavBackend* create_test_pgdav(Session *sn, Request *rq) {
+    return pg_webdav_create_from_resdata(sn, rq, &test_repo, &resdata);
+}
+
+UCX_TEST(test_pg_webdav_create_from_resdata) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavBackend *dav = create_test_pgdav(sn, rq);
+    UCX_TEST_ASSERT(dav, "cannot create pg dav backend");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_prepare_tests) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    vfs_mkdir(vfs, "/propfind");
+    vfs_mkdir(vfs, "/proppatch");
+    SYS_FILE f1;
+    
+    int64_t res1_id, res2_id;
+    
+    f1 = vfs_open(vfs, "/propfind/res1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res1 create failed");
+    res1_id = ((PgFile*)f1->data)->resource_id;
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/propfind/res2", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res2 create failed");
+    res2_id = ((PgFile*)f1->data)->resource_id;
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/propfind/res3", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res3 create failed");
+    vfs_close(f1);
+    
+    int r = vfs_mkdir(vfs, "/propfind/sub");
+    UCX_TEST_ASSERT(r == 0, "sub create failed");
+    
+    f1 = vfs_open(vfs, "/propfind/sub/res4", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res4 create failed");
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/proppatch/pp1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "pp1 create failed");
+    vfs_close(f1);
+    
+    // 2 properties for res1
+    char idstr[32];
+    snprintf(idstr, 32, "%" PRId64, res1_id);
+    const char* params[1] = { idstr };
+    PGresult *result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'testvalue');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'prop2', 'value2');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    // 1 property for res2
+    snprintf(idstr, 32, "%" PRId64, res2_id);
+    result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'res2test');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_webdav_propfind) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND1);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "0", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml");
+    
+    TestResponse *r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count");
+    
+    TestProperty *p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(p, "propfind1: missing property 'resourcetype'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'resourcetype'");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:getlastmodified");
+    UCX_TEST_ASSERT(p, "propfind1: missing property 'getlastmodified'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'getlastmodified'");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    // Test 2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "1", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res2");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res2 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(p, "propfind2: missing property 'test'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind2: wrong status code for property 'test'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "res2test"), "propfind2: wrong property value");
+    
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    
+    // Test 3
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "infinity", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (3) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count");
+    
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(p, "propfind3: missing property 'test'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'test'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "testvalue"), "propfind3: wrong property value");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(p, "propfind3: missing property 'prop2'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'prop2'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "value2"), "propfind3: wrong property value");
+    
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/sub/res4");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res4 response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+}
+
+
+UCX_TEST(test_pg_webdav_propfind_allprop) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    TestResponse *r1;
+    TestProperty *p;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "0", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind1: missing resourcetype property");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "1", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind2: missing resourcetype property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(r1, "propfind2: missing test property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(r1, "propfind2: missing prop2 property");
+    
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind2: missing /propfind/res2 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind2: missing /propfind/res3 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind2: missing /propfind/sub response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 3
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "infinity", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind3: missing resourcetype property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(r1, "propfind3: missing test property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(r1, "propfind3: missing prop2 property");
+    
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind3: missing /propfind/res2 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind3: missing /propfind/res3 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind3: missing /propfind/sub response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/res4"), "propfind3: missing /propfind/sub/res4 response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_webdav_proppatch_set) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    TestResponse *r1;
+    TestProperty *p;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH1);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    
+    ret = webdav_proppatch(pb, sn, rq);
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch1 failed");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "proppatch1 response is not valid xml");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 2: xml property value
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    
+    ret = webdav_proppatch(pb, sn, rq);
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch2 failed");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "proppatch2 response is not valid xml");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/pgtest.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,129 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/* 
+ * File:   pgtest.h
+ * Author: olaf
+ *
+ * Created on 16. April 2022, 14:37
+ */
+
+#ifndef PGTEST_H
+#define PGTEST_H
+
+#include <ucx/test.h>
+#include <ucx/map.h>
+#include <libxml/tree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct TestMultistatus {
+    UcxMempool *mp;
+    xmlDoc *doc;
+    UcxMap *responses;
+} TestMultistatus;
+    
+typedef struct TestResponse {
+    char *href;
+    UcxMap *properties;
+} TestResponse;
+
+typedef struct TestProperty {
+    char *prefix;
+    char *namespace;
+    char *name;
+    char *lang;
+    char *value;
+    xmlNode *node;
+    int status;
+} TestProperty;
+
+TestMultistatus* test_parse_multistatus(const char *space, size_t size);
+void test_multistatus_destroy(TestMultistatus *ms);
+
+UCX_TEST(test_pg_conn);
+
+UCX_TEST(test_pg_lookup_root);
+
+UCX_TEST(test_pg_vfs_open);
+UCX_TEST(test_pg_vfs_io);
+UCX_TEST(test_pg_vfs_stat);
+UCX_TEST(test_pg_vfs_mkdir);
+UCX_TEST(test_pg_vfs_unlink);
+UCX_TEST(test_pg_vfs_rmdir);
+
+UCX_TEST(test_pg_webdav_create_from_resdata);
+UCX_TEST(test_pg_prepare_tests);
+UCX_TEST(test_pg_webdav_propfind);
+UCX_TEST(test_pg_webdav_propfind_allprop);
+UCX_TEST(test_pg_webdav_proppatch_set);
+
+
+/* --------------------------- PROPFIND --------------------------- */
+
+#define PG_TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" > \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getcontenttype/> \
+                <D:getlastmodified/> \
+                <D:creationdate/> \
+                <D:resourcetype/> \
+                <D:getetag/> \
+            </D:prop> \
+        </D:propfind>"
+
+#define PG_TEST_PROPFIND2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" > \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getcontenttype/> \
+                <D:getlastmodified/> \
+                <D:creationdate/> \
+                <D:resourcetype/> \
+                <D:getetag/> \
+                <X:test /> \
+                <X:prop2 /> \
+            </D:prop> \
+        </D:propfind>"
+
+#define PG_TEST_ALLPROP "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" > \
+            <D:allprop/> \
+        </D:propfind>"
+
+#define PG_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><X:author>name</X:author></D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+#define PG_TEST_PROPPATCH2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" xmlns:XA=\"http://example.com/a/\"> \
+            <D:set> \
+                <D:prop>\
+                    <XA:xtest>\
+                        <XA:elm>value</XA:elm>\
+                        <X:ex>example</X:ex>\
+                        <Z:nstest xmlns:Z=\"http://example.com/z\">\
+                            <Y:nstest2 xmlns:Y=\"http://example.com/y\"></Y:nstest2>\
+                        </Z:nstest>\
+                    </XA:xtest>\
+                </D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PGTEST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/resource.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "resource.h"
+
+static ResourceType pg_resource_type = {
+    (resource_pool_init_func)pg_resourcepool_init,
+    (resource_pool_destroy_func)pg_resourcepool_destroy,
+    (resource_pool_createresource_func)pg_resourcepool_createresource,
+    (resource_pool_freeresource_func)pg_resourcepool_freeresource,
+    (resource_pool_prepare_func)pg_resourcepool_prepare,
+    (resource_pool_finish_func)pg_resourcepool_finish,
+    (resource_pool_getresourcedata_func)pg_resourcepool_getresourcedata
+};
+
+ResourceType* pg_get_resource_type(void) {
+    return &pg_resource_type;
+}
+
+void * pg_resourcepool_init(pool_handle_t *pool, const char *rpname, pblock *pb) {
+    char *connection = pblock_findval("Connection", pb);
+    if(!connection) {
+        log_ereport(LOG_MISCONFIG, "Resource pool %s: Missing Connection parameter", rpname);
+        return NULL;
+    }
+    
+    // test connection
+    PGconn *test_connection = PQconnectdb(connection);
+    if(pg_check_connection(LOG_WARN, rpname, test_connection)) {
+        log_ereport(LOG_WARN, "Resource pool %s: Connection check failed", rpname);
+    }
+    if(test_connection) PQfinish(test_connection);
+    
+    PgResourcePool *pg = pool_malloc(pool, sizeof(PgResourcePool));
+    if(!pg) {
+        return NULL;
+    }
+    pg->pool = pool;
+    pg->name = rpname;
+    pg->connection = connection;
+    
+    return pg;
+    
+}
+
+int pg_check_connection(int loglevel, const char *rpname, PGconn *connection) {
+    if(!connection) {
+        log_ereport(loglevel, "Resource pool %s: Cannot create PQ connection", rpname);
+        return 1;
+    }
+    if(PQstatus(connection) != CONNECTION_OK) {
+        char *err = PQerrorMessage(connection);
+        int errlen = 0;
+        if(err) {
+            errlen = strlen(err);
+            if(errlen > 0 && err[errlen-1] == '\n') {
+                errlen--;
+            }
+        }
+        log_ereport(loglevel, "Resource pool %s: Failed to connect to database: %.*s", rpname, errlen, err);
+        PQfinish(connection);
+        return 1;
+    }
+    return 0;
+}
+
+
+void pg_resourcepool_destroy(PgResourcePool *pg) {
+    // unused
+}
+    
+void * pg_resourcepool_createresource(PgResourcePool *pg) {
+    PGconn *connection = PQconnectdb(pg->connection);
+    if(pg_check_connection(LOG_FAILURE, pg->name, connection)) {
+        return NULL;
+    }
+    
+    PgResource *res = pool_malloc(pg->pool, sizeof(PgResource));
+    if(!res) {
+        PQfinish(connection);
+        log_ereport(LOG_CATASTROPHE, "pg_resourcepool_createresource: OOM");
+        return NULL;
+    }
+    res->connection = connection;
+    
+    return res;
+}
+
+void pg_resourcepool_freeresource(PgResourcePool *pg, PgResource *res) {
+    if(res->connection) {
+        PQfinish(res->connection);
+    }
+    pool_free(pg->pool, res);
+}
+
+int pg_resourcepool_prepare(PgResourcePool *pg, PgResource *res) {
+    PGresult *result = PQexec(res->connection, "BEGIN");
+    PQclear(result); // TODO: handle error
+    return 0;
+}
+
+int pg_resourcepool_finish(PgResourcePool *pg, PgResource *res) {
+    PGresult *result = PQexec(res->connection, "COMMIT");
+    if(PQresultStatus(result) != PGRES_COMMAND_OK) {
+        log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: COMMIT failed failed: %s", PQerrorMessage(res->connection));
+    }
+    PQclear(result);
+    return 0;
+}
+
+void * pg_resourcepool_getresourcedata(PgResource *res) {
+    return res->connection;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/resource.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,91 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 WS_PG_RESOURCE_H
+#define WS_PG_RESOURCE_H
+
+#include "../../public/nsapi.h"
+
+#include <libpq-fe.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct PgResourcePool {
+    /*
+     * ResourcePool parameters
+     */
+    pblock *param;
+    
+    /*
+     * Cfg memorypool
+     */
+    pool_handle_t *pool;
+    
+    /*
+     * ResourcePool name
+     */
+    const char *name;
+    
+    /*
+     * connection string used for PQconnectdb
+     */
+    char *connection;
+    
+    
+} PgResourcePool;
+
+typedef struct PgResource {
+    PGconn *connection;
+} PgResource;
+    
+ResourceType* pg_get_resource_type(void);
+
+void * pg_resourcepool_init(pool_handle_t *pool, const char *rpname, pblock *pb);
+void   pg_resourcepool_destroy(PgResourcePool *pg);
+void * pg_resourcepool_createresource(PgResourcePool *pg);
+void   pg_resourcepool_freeresource(PgResourcePool *pg, PgResource *res);
+int    pg_resourcepool_prepare(PgResourcePool *pg, PgResource *res);
+int    pg_resourcepool_finish(PgResourcePool *pg, PgResource *res);
+void * pg_resourcepool_getresourcedata(PgResource *res);
+
+/*
+ * checks if conn is a working connection and logs an error message if not
+ * conn can be null (resulting in an error message)
+ * if conn is not null but the status is not CONNECTION_OK,
+ * conn will be destroyed
+ */
+int pg_check_connection(int loglevel, const char *rpname, PGconn *connection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_PG_RESOURCE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/service.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,113 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "service.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "resource.h"
+
+int pg_query(pblock *pb, Session *sn, Request *rq) {
+    char *respool = pblock_findval("resource", pb);
+    char *query = pblock_findval("query", pb);
+    
+    if(!respool) {
+        log_ereport(LOG_MISCONFIG, "pg-query: missing resource parameter");
+        return REQ_ABORTED;
+    }
+    if(!query) {
+        log_ereport(LOG_MISCONFIG, "pg-query: missing query parameter");
+        return REQ_ABORTED;
+    }
+    
+    // Get the named resource and check if the PG connection works
+    ResourceData *res = resourcepool_lookup(sn, rq, respool, 0);
+    if(!res) {
+        log_ereport(LOG_FAILURE, "pg-query: cannot get resource '%s'", respool);
+        return REQ_ABORTED;
+    }
+    
+    PGconn *connection = res->data;
+    if(pg_check_connection(LOG_FAILURE, respool, connection)) {
+        return REQ_ABORTED;
+    }
+    
+    // execute query
+    PGresult *result = PQexec(connection, query);
+    if(!result) return REQ_ABORTED;
+    
+    // start response
+    pblock_remove("content-type", rq->srvhdrs);
+    pblock_nvinsert("content-type", "text/html", rq->srvhdrs);
+    
+    protocol_status(sn, rq, 200, NULL);
+    http_start_response(sn, rq);
+    
+    // (html) header
+    net_printf(sn->csd, "%s", "<!DOCTYPE html>\n<html><head></head><body>\n");
+    
+    int nfields = PQnfields(result);
+    if(nfields > 0) {
+        net_printf(sn->csd, "<table>\n<tr>\n");
+        for(int i=0;i<nfields;i++) {
+            char *fieldName = PQfname(result, i);
+            char *fieldNameEscaped = util_html_escape(fieldName);
+            if(fieldNameEscaped) {
+                net_printf(sn->csd, "<th>%s</th>\n", fieldNameEscaped);
+                FREE(fieldNameEscaped);
+            }
+        }
+        net_printf(sn->csd, "</tr>\n");
+        
+        int nrows = PQntuples(result);
+        for(int r=0;r<nrows;r++) {
+            net_printf(sn->csd, "<tr>\n");
+            for(int c=0;c<nfields;c++) {
+                char *fieldValue = PQgetvalue(result, r, c);
+                char *fieldValueEscaped = util_html_escape(fieldValue);
+                if(fieldValueEscaped) {
+                    net_printf(sn->csd, "<td>%s</td>\n", fieldValueEscaped);
+                    FREE(fieldValueEscaped);
+                }
+            }
+            net_printf(sn->csd, "</tr>\n");
+        }
+        
+        
+        net_printf(sn->csd, "</table>\n");
+    } else {
+        net_printf(sn->csd, "<p>%s</p>\n", PQresStatus(PQresultStatus(result)));
+    }
+    
+    net_printf(sn->csd, "%s", "</body></html>\n");
+    
+    PQclear(result);
+    
+    return REQ_PROCEED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/service.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 PG_SERVICE_H
+#define PG_SERVICE_H
+
+#include "../../public/nsapi.h"
+
+#include <libpq-fe.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * SAF parameter:
+ * resourcepool: name of the postgresql resource pool
+ * query: SQL query to execute
+ */
+int pg_query(pblock *pb, Session *sn, Request *rq);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PG_SERVICE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/test/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,42 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2022 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.
+#
+
+BUILD_ROOT = ../../../../..
+
+include $(BUILD_ROOT)/config.mk
+
+BUILD_DIR = $(BUILD_ROOT)/build/server/plugins/postgresql
+
+PGTEST = $(BUILD_ROOT)/build/server/plugins/postgresql/test
+
+
+
+all: $(TESTDB)
+	
+$(PGTEST):
+	mkdir -p $(PGTEST)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/test/createtestdb.sh	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+BUILD_ROOT=../../../../..
+
+PGTEST=$BUILD_ROOT/build/server/plugins/postgresql/test
+TESTDB=$PGTEST/data
+
+echo "## prepare testdb directory"
+rm -Rf $TESTDB
+mkdir $TESTDB
+if [ $? -ne 0 ]; then
+	exit 1
+fi
+
+TESTDB_ABS=`realpath $TESTDB`
+echo "testdb path: " $TESTDB_ABS
+
+echo "## init testdb"
+initdb -D $TESTDB_ABS
+if [ $? -ne 0 ]; then
+	exit 1
+fi
+
+cp pg/postgresql.conf $TESTDB
+mkdir $TESTDB/run
+
+echo "## start database"
+pg_ctl -D $TESTDB_ABS start
+if [ $? -ne 0 ]; then
+	echo "## start failed"
+	exit 1
+fi
+
+echo "## create testdb"
+createdb -h $TESTDB_ABS/run testdb
+if [ $? -ne 0 ]; then
+	echo "## createdb failed"
+	echo "## stop database"
+	pg_ctl -D $TESTDB_ABS stop
+	exit 1
+fi
+echo "success"
+
+echo "## create test data"
+psql -h $TESTDB_ABS/run -d testdb -f postgresql_vfs.sql
+if [ $? -ne 0 ]; then
+	echo "## create test data (1) failed"
+	echo "## stop database"
+	pg_ctl -D $TESTDB_ABS stop
+	exit 1
+fi
+psql -h $TESTDB_ABS/run -d testdb -f postgresql_vfs_testdata.sql
+if [ $? -ne 0 ]; then
+	echo "## create test data (2) failed"
+	echo "## stop database"
+	pg_ctl -D $TESTDB_ABS stop
+	exit 1
+fi
+echo "success"
+
+echo "## stop database"
+pg_ctl -D $TESTDB_ABS stop
+exit $?
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/test/pg/postgresql.conf	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,805 @@
+# -----------------------------
+# PostgreSQL configuration file
+# -----------------------------
+#
+# This file consists of lines of the form:
+#
+#   name = value
+#
+# (The "=" is optional.)  Whitespace may be used.  Comments are introduced with
+# "#" anywhere on a line.  The complete list of parameter names and allowed
+# values can be found in the PostgreSQL documentation.
+#
+# The commented-out settings shown in this file represent the default values.
+# Re-commenting a setting is NOT sufficient to revert it to the default value;
+# you need to reload the server.
+#
+# This file is read on server startup and when the server receives a SIGHUP
+# signal.  If you edit the file on a running system, you have to SIGHUP the
+# server for the changes to take effect, run "pg_ctl reload", or execute
+# "SELECT pg_reload_conf()".  Some parameters, which are marked below,
+# require a server shutdown and restart to take effect.
+#
+# Any parameter can also be given as a command-line option to the server, e.g.,
+# "postgres -c log_connections=on".  Some parameters can be changed at run time
+# with the "SET" SQL command.
+#
+# Memory units:  B  = bytes            Time units:  us  = microseconds
+#                kB = kilobytes                     ms  = milliseconds
+#                MB = megabytes                     s   = seconds
+#                GB = gigabytes                     min = minutes
+#                TB = terabytes                     h   = hours
+#                                                   d   = days
+
+
+#------------------------------------------------------------------------------
+# FILE LOCATIONS
+#------------------------------------------------------------------------------
+
+# The default values of these variables are driven from the -D command-line
+# option or PGDATA environment variable, represented here as ConfigDir.
+
+#data_directory = 'ConfigDir'		# use data in another directory
+					# (change requires restart)
+#hba_file = 'ConfigDir/pg_hba.conf'	# host-based authentication file
+					# (change requires restart)
+#ident_file = 'ConfigDir/pg_ident.conf'	# ident configuration file
+					# (change requires restart)
+
+# If external_pid_file is not explicitly set, no extra PID file is written.
+#external_pid_file = ''			# write an extra PID file
+					# (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# CONNECTIONS AND AUTHENTICATION
+#------------------------------------------------------------------------------
+
+# - Connection Settings -
+
+unix_socket_directories = 'run'
+
+# Disable TCP for testing
+listen_addresses = ''
+
+
+#listen_addresses = 'localhost'		# what IP address(es) to listen on;
+					# comma-separated list of addresses;
+					# defaults to 'localhost'; use '*' for all
+					# (change requires restart)
+#port = 5432				# (change requires restart)
+max_connections = 100			# (change requires restart)
+#superuser_reserved_connections = 3	# (change requires restart)
+#unix_socket_directories = '/tmp'	# comma-separated list of directories
+					# (change requires restart)
+#unix_socket_group = ''			# (change requires restart)
+#unix_socket_permissions = 0777		# begin with 0 to use octal notation
+					# (change requires restart)
+#bonjour = off				# advertise server via Bonjour
+					# (change requires restart)
+#bonjour_name = ''			# defaults to the computer name
+					# (change requires restart)
+
+# - TCP settings -
+# see "man tcp" for details
+
+#tcp_keepalives_idle = 0		# TCP_KEEPIDLE, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_interval = 0		# TCP_KEEPINTVL, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_count = 0		# TCP_KEEPCNT;
+					# 0 selects the system default
+#tcp_user_timeout = 0			# TCP_USER_TIMEOUT, in milliseconds;
+					# 0 selects the system default
+
+#client_connection_check_interval = 0	# time between checks for client
+					# disconnection while running queries;
+					# 0 for never
+
+# - Authentication -
+
+#authentication_timeout = 1min		# 1s-600s
+#password_encryption = scram-sha-256	# scram-sha-256 or md5
+#db_user_namespace = off
+
+# GSSAPI using Kerberos
+#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab'
+#krb_caseins_users = off
+
+# - SSL -
+
+#ssl = off
+#ssl_ca_file = ''
+#ssl_cert_file = 'server.crt'
+#ssl_crl_file = ''
+#ssl_crl_dir = ''
+#ssl_key_file = 'server.key'
+#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
+#ssl_prefer_server_ciphers = on
+#ssl_ecdh_curve = 'prime256v1'
+#ssl_min_protocol_version = 'TLSv1.2'
+#ssl_max_protocol_version = ''
+#ssl_dh_params_file = ''
+#ssl_passphrase_command = ''
+#ssl_passphrase_command_supports_reload = off
+
+
+#------------------------------------------------------------------------------
+# RESOURCE USAGE (except WAL)
+#------------------------------------------------------------------------------
+
+# - Memory -
+
+shared_buffers = 128MB			# min 128kB
+					# (change requires restart)
+#huge_pages = try			# on, off, or try
+					# (change requires restart)
+#huge_page_size = 0			# zero for system default
+					# (change requires restart)
+#temp_buffers = 8MB			# min 800kB
+#max_prepared_transactions = 0		# zero disables the feature
+					# (change requires restart)
+# Caution: it is not advisable to set max_prepared_transactions nonzero unless
+# you actively intend to use prepared transactions.
+#work_mem = 4MB				# min 64kB
+#hash_mem_multiplier = 1.0		# 1-1000.0 multiplier on hash table work_mem
+#maintenance_work_mem = 64MB		# min 1MB
+#autovacuum_work_mem = -1		# min 1MB, or -1 to use maintenance_work_mem
+#logical_decoding_work_mem = 64MB	# min 64kB
+#max_stack_depth = 2MB			# min 100kB
+#shared_memory_type = mmap		# the default is the first option
+					# supported by the operating system:
+					#   mmap
+					#   sysv
+					#   windows
+					# (change requires restart)
+dynamic_shared_memory_type = posix	# the default is the first option
+					# supported by the operating system:
+					#   posix
+					#   sysv
+					#   windows
+					#   mmap
+					# (change requires restart)
+#min_dynamic_shared_memory = 0MB	# (change requires restart)
+
+# - Disk -
+
+#temp_file_limit = -1			# limits per-process temp file space
+					# in kilobytes, or -1 for no limit
+
+# - Kernel Resources -
+
+#max_files_per_process = 1000		# min 64
+					# (change requires restart)
+
+# - Cost-Based Vacuum Delay -
+
+#vacuum_cost_delay = 0			# 0-100 milliseconds (0 disables)
+#vacuum_cost_page_hit = 1		# 0-10000 credits
+#vacuum_cost_page_miss = 2		# 0-10000 credits
+#vacuum_cost_page_dirty = 20		# 0-10000 credits
+#vacuum_cost_limit = 200		# 1-10000 credits
+
+# - Background Writer -
+
+#bgwriter_delay = 200ms			# 10-10000ms between rounds
+#bgwriter_lru_maxpages = 100		# max buffers written/round, 0 disables
+#bgwriter_lru_multiplier = 2.0		# 0-10.0 multiplier on buffers scanned/round
+#bgwriter_flush_after = 0		# measured in pages, 0 disables
+
+# - Asynchronous Behavior -
+
+#backend_flush_after = 0		# measured in pages, 0 disables
+#effective_io_concurrency = 1		# 1-1000; 0 disables prefetching
+#maintenance_io_concurrency = 10	# 1-1000; 0 disables prefetching
+#max_worker_processes = 8		# (change requires restart)
+#max_parallel_workers_per_gather = 2	# taken from max_parallel_workers
+#max_parallel_maintenance_workers = 2	# taken from max_parallel_workers
+#max_parallel_workers = 8		# maximum number of max_worker_processes that
+					# can be used in parallel operations
+#parallel_leader_participation = on
+#old_snapshot_threshold = -1		# 1min-60d; -1 disables; 0 is immediate
+					# (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# WRITE-AHEAD LOG
+#------------------------------------------------------------------------------
+
+# - Settings -
+
+#wal_level = replica			# minimal, replica, or logical
+					# (change requires restart)
+#fsync = on				# flush data to disk for crash safety
+					# (turning this off can cause
+					# unrecoverable data corruption)
+#synchronous_commit = on		# synchronization level;
+					# off, local, remote_write, remote_apply, or on
+#wal_sync_method = fsync		# the default is the first option
+					# supported by the operating system:
+					#   open_datasync
+					#   fdatasync (default on Linux and FreeBSD)
+					#   fsync
+					#   fsync_writethrough
+					#   open_sync
+#full_page_writes = on			# recover from partial page writes
+#wal_log_hints = off			# also do full page writes of non-critical updates
+					# (change requires restart)
+#wal_compression = off			# enable compression of full-page writes
+#wal_init_zero = on			# zero-fill new WAL files
+#wal_recycle = on			# recycle WAL files
+#wal_buffers = -1			# min 32kB, -1 sets based on shared_buffers
+					# (change requires restart)
+#wal_writer_delay = 200ms		# 1-10000 milliseconds
+#wal_writer_flush_after = 1MB		# measured in pages, 0 disables
+#wal_skip_threshold = 2MB
+
+#commit_delay = 0			# range 0-100000, in microseconds
+#commit_siblings = 5			# range 1-1000
+
+# - Checkpoints -
+
+#checkpoint_timeout = 5min		# range 30s-1d
+#checkpoint_completion_target = 0.9	# checkpoint target duration, 0.0 - 1.0
+#checkpoint_flush_after = 0		# measured in pages, 0 disables
+#checkpoint_warning = 30s		# 0 disables
+max_wal_size = 1GB
+min_wal_size = 80MB
+
+# - Archiving -
+
+#archive_mode = off		# enables archiving; off, on, or always
+				# (change requires restart)
+#archive_command = ''		# command to use to archive a logfile segment
+				# placeholders: %p = path of file to archive
+				#               %f = file name only
+				# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
+#archive_timeout = 0		# force a logfile segment switch after this
+				# number of seconds; 0 disables
+
+# - Archive Recovery -
+
+# These are only used in recovery mode.
+
+#restore_command = ''		# command to use to restore an archived logfile segment
+				# placeholders: %p = path of file to restore
+				#               %f = file name only
+				# e.g. 'cp /mnt/server/archivedir/%f %p'
+#archive_cleanup_command = ''	# command to execute at every restartpoint
+#recovery_end_command = ''	# command to execute at completion of recovery
+
+# - Recovery Target -
+
+# Set these only when performing a targeted recovery.
+
+#recovery_target = ''		# 'immediate' to end recovery as soon as a
+                                # consistent state is reached
+				# (change requires restart)
+#recovery_target_name = ''	# the named restore point to which recovery will proceed
+				# (change requires restart)
+#recovery_target_time = ''	# the time stamp up to which recovery will proceed
+				# (change requires restart)
+#recovery_target_xid = ''	# the transaction ID up to which recovery will proceed
+				# (change requires restart)
+#recovery_target_lsn = ''	# the WAL LSN up to which recovery will proceed
+				# (change requires restart)
+#recovery_target_inclusive = on # Specifies whether to stop:
+				# just after the specified recovery target (on)
+				# just before the recovery target (off)
+				# (change requires restart)
+#recovery_target_timeline = 'latest'	# 'current', 'latest', or timeline ID
+				# (change requires restart)
+#recovery_target_action = 'pause'	# 'pause', 'promote', 'shutdown'
+				# (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# REPLICATION
+#------------------------------------------------------------------------------
+
+# - Sending Servers -
+
+# Set these on the primary and on any standby that will send replication data.
+
+#max_wal_senders = 10		# max number of walsender processes
+				# (change requires restart)
+#max_replication_slots = 10	# max number of replication slots
+				# (change requires restart)
+#wal_keep_size = 0		# in megabytes; 0 disables
+#max_slot_wal_keep_size = -1	# in megabytes; -1 disables
+#wal_sender_timeout = 60s	# in milliseconds; 0 disables
+#track_commit_timestamp = off	# collect timestamp of transaction commit
+				# (change requires restart)
+
+# - Primary Server -
+
+# These settings are ignored on a standby server.
+
+#synchronous_standby_names = ''	# standby servers that provide sync rep
+				# method to choose sync standbys, number of sync standbys,
+				# and comma-separated list of application_name
+				# from standby(s); '*' = all
+#vacuum_defer_cleanup_age = 0	# number of xacts by which cleanup is delayed
+
+# - Standby Servers -
+
+# These settings are ignored on a primary server.
+
+#primary_conninfo = ''			# connection string to sending server
+#primary_slot_name = ''			# replication slot on sending server
+#promote_trigger_file = ''		# file name whose presence ends recovery
+#hot_standby = on			# "off" disallows queries during recovery
+					# (change requires restart)
+#max_standby_archive_delay = 30s	# max delay before canceling queries
+					# when reading WAL from archive;
+					# -1 allows indefinite delay
+#max_standby_streaming_delay = 30s	# max delay before canceling queries
+					# when reading streaming WAL;
+					# -1 allows indefinite delay
+#wal_receiver_create_temp_slot = off	# create temp slot if primary_slot_name
+					# is not set
+#wal_receiver_status_interval = 10s	# send replies at least this often
+					# 0 disables
+#hot_standby_feedback = off		# send info from standby to prevent
+					# query conflicts
+#wal_receiver_timeout = 60s		# time that receiver waits for
+					# communication from primary
+					# in milliseconds; 0 disables
+#wal_retrieve_retry_interval = 5s	# time to wait before retrying to
+					# retrieve WAL after a failed attempt
+#recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+
+# - Subscribers -
+
+# These settings are ignored on a publisher.
+
+#max_logical_replication_workers = 4	# taken from max_worker_processes
+					# (change requires restart)
+#max_sync_workers_per_subscription = 2	# taken from max_logical_replication_workers
+
+
+#------------------------------------------------------------------------------
+# QUERY TUNING
+#------------------------------------------------------------------------------
+
+# - Planner Method Configuration -
+
+#enable_async_append = on
+#enable_bitmapscan = on
+#enable_gathermerge = on
+#enable_hashagg = on
+#enable_hashjoin = on
+#enable_incremental_sort = on
+#enable_indexscan = on
+#enable_indexonlyscan = on
+#enable_material = on
+#enable_memoize = on
+#enable_mergejoin = on
+#enable_nestloop = on
+#enable_parallel_append = on
+#enable_parallel_hash = on
+#enable_partition_pruning = on
+#enable_partitionwise_join = off
+#enable_partitionwise_aggregate = off
+#enable_seqscan = on
+#enable_sort = on
+#enable_tidscan = on
+
+# - Planner Cost Constants -
+
+#seq_page_cost = 1.0			# measured on an arbitrary scale
+#random_page_cost = 4.0			# same scale as above
+#cpu_tuple_cost = 0.01			# same scale as above
+#cpu_index_tuple_cost = 0.005		# same scale as above
+#cpu_operator_cost = 0.0025		# same scale as above
+#parallel_setup_cost = 1000.0	# same scale as above
+#parallel_tuple_cost = 0.1		# same scale as above
+#min_parallel_table_scan_size = 8MB
+#min_parallel_index_scan_size = 512kB
+#effective_cache_size = 4GB
+
+#jit_above_cost = 100000		# perform JIT compilation if available
+					# and query more expensive than this;
+					# -1 disables
+#jit_inline_above_cost = 500000		# inline small functions if query is
+					# more expensive than this; -1 disables
+#jit_optimize_above_cost = 500000	# use expensive JIT optimizations if
+					# query is more expensive than this;
+					# -1 disables
+
+# - Genetic Query Optimizer -
+
+#geqo = on
+#geqo_threshold = 12
+#geqo_effort = 5			# range 1-10
+#geqo_pool_size = 0			# selects default based on effort
+#geqo_generations = 0			# selects default based on effort
+#geqo_selection_bias = 2.0		# range 1.5-2.0
+#geqo_seed = 0.0			# range 0.0-1.0
+
+# - Other Planner Options -
+
+#default_statistics_target = 100	# range 1-10000
+#constraint_exclusion = partition	# on, off, or partition
+#cursor_tuple_fraction = 0.1		# range 0.0-1.0
+#from_collapse_limit = 8
+#jit = on				# allow JIT compilation
+#join_collapse_limit = 8		# 1 disables collapsing of explicit
+					# JOIN clauses
+#plan_cache_mode = auto			# auto, force_generic_plan or
+					# force_custom_plan
+
+
+#------------------------------------------------------------------------------
+# REPORTING AND LOGGING
+#------------------------------------------------------------------------------
+
+# - Where to Log -
+
+log_destination = 'syslog'
+#log_destination = 'stderr'		# Valid values are combinations of
+					# stderr, csvlog, syslog, and eventlog,
+					# depending on platform.  csvlog
+					# requires logging_collector to be on.
+
+# This is used when logging to stderr:
+#logging_collector = off		# Enable capturing of stderr and csvlog
+					# into log files. Required to be on for
+					# csvlogs.
+					# (change requires restart)
+
+# These are only used if logging_collector is on:
+#log_directory = 'log'			# directory where log files are written,
+					# can be absolute or relative to PGDATA
+#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'	# log file name pattern,
+					# can include strftime() escapes
+#log_file_mode = 0600			# creation mode for log files,
+					# begin with 0 to use octal notation
+#log_rotation_age = 1d			# Automatic rotation of logfiles will
+					# happen after that time.  0 disables.
+#log_rotation_size = 10MB		# Automatic rotation of logfiles will
+					# happen after that much log output.
+					# 0 disables.
+#log_truncate_on_rotation = off		# If on, an existing log file with the
+					# same name as the new log file will be
+					# truncated rather than appended to.
+					# But such truncation only occurs on
+					# time-driven rotation, not on restarts
+					# or size-driven rotation.  Default is
+					# off, meaning append to existing files
+					# in all cases.
+
+# These are relevant when logging to syslog:
+#syslog_facility = 'LOCAL0'
+#syslog_ident = 'postgres'
+#syslog_sequence_numbers = on
+#syslog_split_messages = on
+
+# This is only relevant when logging to eventlog (Windows):
+# (change requires restart)
+#event_source = 'PostgreSQL'
+
+# - When to Log -
+
+#log_min_messages = warning		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic
+
+#log_min_error_statement = error	# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic (effectively off)
+
+#log_min_duration_statement = -1	# -1 is disabled, 0 logs all statements
+					# and their durations, > 0 logs only
+					# statements running at least this number
+					# of milliseconds
+
+#log_min_duration_sample = -1		# -1 is disabled, 0 logs a sample of statements
+					# and their durations, > 0 logs only a sample of
+					# statements running at least this number
+					# of milliseconds;
+					# sample fraction is determined by log_statement_sample_rate
+
+#log_statement_sample_rate = 1.0	# fraction of logged statements exceeding
+					# log_min_duration_sample to be logged;
+					# 1.0 logs all such statements, 0.0 never logs
+
+
+#log_transaction_sample_rate = 0.0	# fraction of transactions whose statements
+					# are logged regardless of their duration; 1.0 logs all
+					# statements from all transactions, 0.0 never logs
+
+# - What to Log -
+
+#debug_print_parse = off
+#debug_print_rewritten = off
+#debug_print_plan = off
+#debug_pretty_print = on
+#log_autovacuum_min_duration = -1	# log autovacuum activity;
+					# -1 disables, 0 logs all actions and
+					# their durations, > 0 logs only
+					# actions running at least this number
+					# of milliseconds.
+#log_checkpoints = off
+#log_connections = off
+#log_disconnections = off
+#log_duration = off
+#log_error_verbosity = default		# terse, default, or verbose messages
+#log_hostname = off
+#log_line_prefix = '%m [%p] '		# special values:
+					#   %a = application name
+					#   %u = user name
+					#   %d = database name
+					#   %r = remote host and port
+					#   %h = remote host
+					#   %b = backend type
+					#   %p = process ID
+					#   %P = process ID of parallel group leader
+					#   %t = timestamp without milliseconds
+					#   %m = timestamp with milliseconds
+					#   %n = timestamp with milliseconds (as a Unix epoch)
+					#   %Q = query ID (0 if none or not computed)
+					#   %i = command tag
+					#   %e = SQL state
+					#   %c = session ID
+					#   %l = session line number
+					#   %s = session start timestamp
+					#   %v = virtual transaction ID
+					#   %x = transaction ID (0 if none)
+					#   %q = stop here in non-session
+					#        processes
+					#   %% = '%'
+					# e.g. '<%u%%%d> '
+#log_lock_waits = off			# log lock waits >= deadlock_timeout
+#log_recovery_conflict_waits = off	# log standby recovery conflict waits
+					# >= deadlock_timeout
+#log_parameter_max_length = -1		# when logging statements, limit logged
+					# bind-parameter values to N bytes;
+					# -1 means print in full, 0 disables
+#log_parameter_max_length_on_error = 0	# when logging an error, limit logged
+					# bind-parameter values to N bytes;
+					# -1 means print in full, 0 disables
+#log_statement = 'none'			# none, ddl, mod, all
+#log_replication_commands = off
+#log_temp_files = -1			# log temporary files equal or larger
+					# than the specified size in kilobytes;
+					# -1 disables, 0 logs all temp files
+log_timezone = 'Europe/Berlin'
+
+
+#------------------------------------------------------------------------------
+# PROCESS TITLE
+#------------------------------------------------------------------------------
+
+#cluster_name = ''			# added to process titles if nonempty
+					# (change requires restart)
+
+# On FreeBSD, this is a performance hog, so keep it off if you need speed
+update_process_title = off
+
+
+#------------------------------------------------------------------------------
+# STATISTICS
+#------------------------------------------------------------------------------
+
+# - Query and Index Statistics Collector -
+
+#track_activities = on
+#track_activity_query_size = 1024	# (change requires restart)
+#track_counts = on
+#track_io_timing = off
+#track_wal_io_timing = off
+#track_functions = none			# none, pl, all
+#stats_temp_directory = 'pg_stat_tmp'
+
+
+# - Monitoring -
+
+#compute_query_id = auto
+#log_statement_stats = off
+#log_parser_stats = off
+#log_planner_stats = off
+#log_executor_stats = off
+
+
+#------------------------------------------------------------------------------
+# AUTOVACUUM
+#------------------------------------------------------------------------------
+
+#autovacuum = on			# Enable autovacuum subprocess?  'on'
+					# requires track_counts to also be on.
+#autovacuum_max_workers = 3		# max number of autovacuum subprocesses
+					# (change requires restart)
+#autovacuum_naptime = 1min		# time between autovacuum runs
+#autovacuum_vacuum_threshold = 50	# min number of row updates before
+					# vacuum
+#autovacuum_vacuum_insert_threshold = 1000	# min number of row inserts
+					# before vacuum; -1 disables insert
+					# vacuums
+#autovacuum_analyze_threshold = 50	# min number of row updates before
+					# analyze
+#autovacuum_vacuum_scale_factor = 0.2	# fraction of table size before vacuum
+#autovacuum_vacuum_insert_scale_factor = 0.2	# fraction of inserts over table
+					# size before insert vacuum
+#autovacuum_analyze_scale_factor = 0.1	# fraction of table size before analyze
+#autovacuum_freeze_max_age = 200000000	# maximum XID age before forced vacuum
+					# (change requires restart)
+#autovacuum_multixact_freeze_max_age = 400000000	# maximum multixact age
+					# before forced vacuum
+					# (change requires restart)
+#autovacuum_vacuum_cost_delay = 2ms	# default vacuum cost delay for
+					# autovacuum, in milliseconds;
+					# -1 means use vacuum_cost_delay
+#autovacuum_vacuum_cost_limit = -1	# default vacuum cost limit for
+					# autovacuum, -1 means use
+					# vacuum_cost_limit
+
+
+#------------------------------------------------------------------------------
+# CLIENT CONNECTION DEFAULTS
+#------------------------------------------------------------------------------
+
+# - Statement Behavior -
+
+#client_min_messages = notice		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   log
+					#   notice
+					#   warning
+					#   error
+#search_path = '"$user", public'	# schema names
+#row_security = on
+#default_table_access_method = 'heap'
+#default_tablespace = ''		# a tablespace name, '' uses the default
+#default_toast_compression = 'pglz'	# 'pglz' or 'lz4'
+#temp_tablespaces = ''			# a list of tablespace names, '' uses
+					# only default tablespace
+#check_function_bodies = on
+#default_transaction_isolation = 'read committed'
+#default_transaction_read_only = off
+#default_transaction_deferrable = off
+#session_replication_role = 'origin'
+#statement_timeout = 0			# in milliseconds, 0 is disabled
+#lock_timeout = 0			# in milliseconds, 0 is disabled
+#idle_in_transaction_session_timeout = 0	# in milliseconds, 0 is disabled
+#idle_session_timeout = 0		# in milliseconds, 0 is disabled
+#vacuum_freeze_table_age = 150000000
+#vacuum_freeze_min_age = 50000000
+#vacuum_failsafe_age = 1600000000
+#vacuum_multixact_freeze_table_age = 150000000
+#vacuum_multixact_freeze_min_age = 5000000
+#vacuum_multixact_failsafe_age = 1600000000
+#bytea_output = 'hex'			# hex, escape
+#xmlbinary = 'base64'
+#xmloption = 'content'
+#gin_pending_list_limit = 4MB
+
+# - Locale and Formatting -
+
+datestyle = 'iso, dmy'
+#intervalstyle = 'postgres'
+timezone = 'Europe/Berlin'
+#timezone_abbreviations = 'Default'     # Select the set of available time zone
+					# abbreviations.  Currently, there are
+					#   Default
+					#   Australia (historical usage)
+					#   India
+					# You can create your own file in
+					# share/timezonesets/.
+#extra_float_digits = 1			# min -15, max 3; any value >0 actually
+					# selects precise output mode
+#client_encoding = sql_ascii		# actually, defaults to database
+					# encoding
+
+# These settings are initialized by initdb, but they can be changed.
+lc_messages = 'de_DE.UTF-8'			# locale for system error message
+					# strings
+lc_monetary = 'de_DE.UTF-8'			# locale for monetary formatting
+lc_numeric = 'de_DE.UTF-8'			# locale for number formatting
+lc_time = 'de_DE.UTF-8'				# locale for time formatting
+
+# default configuration for text search
+default_text_search_config = 'pg_catalog.german'
+
+# - Shared Library Preloading -
+
+#local_preload_libraries = ''
+#session_preload_libraries = ''
+#shared_preload_libraries = ''	# (change requires restart)
+#jit_provider = 'llvmjit'		# JIT library to use
+
+# - Other Defaults -
+
+#dynamic_library_path = '$libdir'
+#gin_fuzzy_search_limit = 0
+
+
+#------------------------------------------------------------------------------
+# LOCK MANAGEMENT
+#------------------------------------------------------------------------------
+
+#deadlock_timeout = 1s
+#max_locks_per_transaction = 64		# min 10
+					# (change requires restart)
+#max_pred_locks_per_transaction = 64	# min 10
+					# (change requires restart)
+#max_pred_locks_per_relation = -2	# negative values mean
+					# (max_pred_locks_per_transaction
+					#  / -max_pred_locks_per_relation) - 1
+#max_pred_locks_per_page = 2            # min 0
+
+
+#------------------------------------------------------------------------------
+# VERSION AND PLATFORM COMPATIBILITY
+#------------------------------------------------------------------------------
+
+# - Previous PostgreSQL Versions -
+
+#array_nulls = on
+#backslash_quote = safe_encoding	# on, off, or safe_encoding
+#escape_string_warning = on
+#lo_compat_privileges = off
+#quote_all_identifiers = off
+#standard_conforming_strings = on
+#synchronize_seqscans = on
+
+# - Other Platforms and Clients -
+
+#transform_null_equals = off
+
+
+#------------------------------------------------------------------------------
+# ERROR HANDLING
+#------------------------------------------------------------------------------
+
+#exit_on_error = off			# terminate session on any error?
+#restart_after_crash = on		# reinitialize after backend crash?
+#data_sync_retry = off			# retry or panic on failure to fsync
+					# data?
+					# (change requires restart)
+#recovery_init_sync_method = fsync	# fsync, syncfs (Linux 5.8+)
+
+
+#------------------------------------------------------------------------------
+# CONFIG FILE INCLUDES
+#------------------------------------------------------------------------------
+
+# These options allow settings to be loaded from files other than the
+# default postgresql.conf.  Note that these are directives, not variable
+# assignments, so they can usefully be given more than once.
+
+#include_dir = '...'			# include files ending in '.conf' from
+					# a directory, e.g., 'conf.d'
+#include_if_exists = '...'		# include file only if it exists
+#include = '...'			# include file
+
+
+#------------------------------------------------------------------------------
+# CUSTOMIZED OPTIONS
+#------------------------------------------------------------------------------
+
+# Add settings for extensions here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/test/postgresql_vfs.sql	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,16 @@
+
+create table Resource (
+    resource_id       serial    primary key,
+    parent_id         int       references Resource(resource_id),
+    nodename          text      not null,
+    iscollection      boolean   not null default false,
+	
+    lastmodified      timestamp not null default current_date,
+    creationdate      timestamp not null default current_date,
+    contentlength     bigint    not null default 0,
+
+    resoid            oid,
+    
+    unique(parent_id, nodename)
+);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/test/postgresql_vfs_testdata.sql	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,29 @@
+
+do $$
+declare
+    res_id int;
+begin
+    insert into Resource (nodename, iscollection) values ('', true);
+    res_id := lastval();
+   
+    insert into Resource(parent_id,  nodename, resoid) values
+    (res_id, 'file1.txt', (select lo_create(0))),
+    (res_id, 'file2.txt', (select lo_create(0))),
+    (res_id, 'file3.txt', (select lo_create(0))),
+    (res_id, 'file4.txt', (select lo_create(0)));
+    
+    insert into Resource(parent_id,  nodename, iscollection) values
+    (res_id, 'dir1', true);
+    res_id := lastval();
+    
+    insert into Resource(parent_id,  nodename, iscollection) values
+    (res_id, 'dir2', true);
+    res_id := lastval();
+    
+    insert into Resource(parent_id,  nodename, resoid) values
+    (res_id, 'd1file1.txt', (select lo_create(0))),
+    (res_id, 'd2file1.txt', (select lo_create(0))),
+    (res_id, 'd2file2.txt', (select lo_create(0)));
+
+end $$;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/vfs.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,1013 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "vfs.h"
+#include "config.h"
+
+#include <inttypes.h>
+
+#include "../../util/util.h"
+#include "../../util/pblock.h"
+
+static VFS pg_vfs_class = {
+    pg_vfs_open,
+    pg_vfs_stat,
+    pg_vfs_fstat,
+    pg_vfs_opendir,
+    pg_vfs_fdopendir,
+    pg_vfs_mkdir,
+    pg_vfs_unlink,
+    pg_vfs_rmdir
+};
+
+static VFS_IO pg_vfs_io_class = {
+    pg_vfs_io_read,
+    pg_vfs_io_write,
+    pg_vfs_io_pread,
+    pg_vfs_io_pwrite,
+    pg_vfs_io_seek,
+    pg_vfs_io_close,
+    NULL, // no pg aio implementation yet
+    NULL,
+    pg_vfs_io_getetag
+};
+
+static VFS_DIRIO pg_vfs_dirio_class = {
+    pg_vfs_dirio_readdir,
+    pg_vfs_dirio_close
+};
+
+
+/*
+ * SQL Queries
+ */
+
+// Resolves a path into resource_id and parent_id
+// params: $1: path string
+static const char *sql_resolve_path = 
+    "with recursive resolvepath as (\n\
+        select\n\
+            resource_id,\n\
+            parent_id,\n\
+            '' as fullpath,\n\
+            resoid,\n\
+            iscollection,\n\
+            lastmodified,\n\
+            creationdate,\n\
+            contentlength,\n\
+            etag,\n\
+            regexp_split_to_array($1, '/') as pathelm,\n\
+            1 as pathdepth\n\
+        from Resource\n\
+        where resource_id = $2\n\
+        union\n\
+        select\n\
+            r.resource_id,\n\
+            r.parent_id,\n\
+            p.fullpath || '/' || r.nodename,\n\
+            r.resoid,\n\
+            r.iscollection,\n\
+            r.lastmodified,\n\
+            r.creationdate,\n\
+            r.contentlength,\n\
+            r.etag,\n\
+            p.pathelm,\n\
+            p.pathdepth + 1\n\
+        from Resource r\n\
+        inner join resolvepath p on r.parent_id = p.resource_id\n\
+        where p.pathelm[p.pathdepth+1] = r.nodename\n\
+    )\n\
+    select resource_id, parent_id, fullpath, resoid, iscollection, lastmodified, creationdate, contentlength, etag from resolvepath\n\
+    where fullpath = $1 ;";
+
+// Same as sql_resolve_path, but it returns the root collection
+// params: $1: path string (should be '/')
+static const char *sql_get_root = "select resource_id, parent_id, $1 as fullpath, resoid, true as iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $2;";
+
+// Get all children of a specific collection
+// params: $1: parent resource_id
+static const char *sql_get_children = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where parent_id = $1;";
+
+// Get resource
+// params: $1: resource_id
+static const char *sql_get_resource = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $1;";
+
+// Create resource
+// params: $1: parent_id
+//         $2: node name
+static const char *sql_create_resource =
+        "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength, resoid) values\n\
+        ($1, $2, false, now(), now(), 0, lo_creat(-1))\n\
+        returning resource_id, resoid, lastmodified, creationdate;";
+
+// Create collection
+// params: $1: parent_id
+//         $2: node name
+static const char *sql_create_collection =
+        "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength) values\n\
+        ($1, $2, true, now(), now(), 0)\n\
+        returning resource_id, lastmodified, creationdate;";
+
+// Update resource metadata
+// params: $1: resource_id
+//         $2: contentlength
+static const char *sql_update_resource = "update Resource set contentlength = $2, lastmodified = now(), etag = gen_random_uuid() where resource_id = $1;";
+
+// Delete a resource
+// params: $1: resource_id
+static const char *sql_delete_res = "delete from Resource where parent_id is not null and resource_id = $1;";
+
+
+void* pg_vfs_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) {
+    return pg_init_repo(cfg, pool, config);
+}
+
+VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb, void *initData) {
+    PgRepository *repo = initData;
+    
+    char *resource_pool;
+    if(repo) {
+        resource_pool = repo->resourcepool.ptr;
+    } else {
+        // resourcepool is required
+        resource_pool = pblock_findval("resourcepool", pb);
+        if(!resource_pool) {
+            log_ereport(LOG_MISCONFIG, "pg_vfs_create: missing resourcepool parameter");
+            return NULL;
+        }
+    }
+    
+    // get the resource first (most likely to fail due to misconfig)
+    ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0);
+    if(!resdata) {
+        log_ereport(LOG_MISCONFIG, "postgresql vfs: resource pool %s not found", resource_pool);
+        return NULL;
+    }
+    // resdata will be freed automatically when the request is finished
+    
+    return pg_vfs_create_from_resourcedata(sn, rq, repo, resdata);
+}
+
+VFS* pg_vfs_create_from_resourcedata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) {
+    // Create a new VFS object and a separate instance object
+    // VFS contains fptrs that can be copied from pg_vfs_class
+    // instance contains request specific data (PGconn)
+    VFS *vfs = pool_malloc(sn->pool, sizeof(VFS));
+    if(!vfs) {
+        return NULL;
+    }
+    
+    PgVFS *vfs_priv = pool_malloc(sn->pool, sizeof(PgVFS));
+    if(!vfs_priv) {
+        pool_free(sn->pool, vfs);
+        return NULL;
+    }
+    vfs_priv->connection = resdata->data;
+    vfs_priv->pg_resource = resdata;
+    vfs_priv->root_resource_id = repo->root_resource_id;
+    snprintf(vfs_priv->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id);
+    
+    memcpy(vfs, &pg_vfs_class, sizeof(VFS));
+    vfs->flags = 0;
+    vfs->instance = vfs_priv;
+    
+    return vfs;
+}
+
+
+int pg_resolve_path(
+        PGconn *connection,
+        const char *path,
+        const char *root_id,
+        int64_t *parent_id,
+        int64_t *resource_id,
+        Oid *oid,
+        const char **resource_name,
+        WSBool *iscollection,
+        struct stat *s,
+        char *etag,
+        int *res_errno)
+{
+    // basic path validation
+    if(!path) return 1;
+    size_t pathlen = strlen(path);
+    if(pathlen == 0) return 1;
+    if(path[0] != '/') {
+        return 1;
+    }
+    
+    char *pathf = NULL;
+    if(pathlen > 1 && path[pathlen-1] == '/') {
+        pathf = malloc(pathlen);
+        memcpy(pathf, path, pathlen);
+        pathf[pathlen-1] = 0; // remove trailing '/'
+        path = pathf;
+    }
+    
+    // get last node of path
+    *resource_name = util_resource_name(path);
+    
+    const char *sql = pathlen == 1 ? sql_get_root : sql_resolve_path;
+    const char* params[2] = { path, root_id };
+    PGresult *result = PQexecParams(
+            connection,
+            sql,
+            2,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+      
+    if(pathf) {
+        free(pathf);
+    }
+    
+    if(!result) return 1;
+    
+    int ret = 1;
+    //int nfields = PQnfields(result);
+    int nrows = PQntuples(result);
+    if(nrows == 1) {
+        char *resource_id_str = PQgetvalue(result, 0, 0);
+        char *parent_id_str = PQgetvalue(result, 0, 1);
+        char *iscol = PQgetvalue(result, 0, 4);
+        char *lastmodified = PQgetvalue(result, 0, 5);
+        char *creationdate = PQgetvalue(result, 0, 6);
+        char *contentlength = PQgetvalue(result, 0, 7);
+        char *res_etag = PQgetvalue(result, 0, 8);
+        if(resource_id_str && parent_id_str) {
+            if(util_strtoint(resource_id_str, resource_id)) {
+                ret = 0; // success
+            }
+            // optionally get parent_id
+            util_strtoint(parent_id_str, parent_id);
+        }
+        
+        if(oid) {
+            char *resoid = PQgetvalue(result, 0, 3);
+            int64_t roid;
+            if(resoid && util_strtoint(resoid, &roid)) {
+                *oid = roid;
+            }
+        }
+        
+        if(iscollection && iscol) {
+            *iscollection = iscol[0] == 't' ? TRUE : FALSE;
+        }
+        
+        if(s) {
+            pg_set_stat(s, iscol, lastmodified, creationdate, contentlength);
+        }
+        
+        if(etag) {
+            size_t etag_len = strlen(res_etag);
+            if(etag_len < PG_ETAG_MAXLEN)
+            memcpy(etag, res_etag, etag_len+1);
+        }
+    } else if(res_errno) {
+        *res_errno = ENOENT;
+    }
+    
+    PQclear(result);
+    
+    return ret;
+}
+
+
+void pg_set_stat(
+        struct stat *s,
+        const char *iscollection,
+        const char *lastmodified,
+        const char *creationdate,
+        const char *contentlength)
+{
+    memset(s, 0, sizeof(struct stat));
+    if(iscollection) {
+        WSBool iscol = iscollection[0] == 't' ? TRUE : FALSE;
+        if(iscol) {
+            s->st_mode |= 0x4000;
+        }
+    }
+    s->st_mtime = pg_convert_timestamp(lastmodified);  
+    
+    if(contentlength) {
+        int64_t len;
+        if(util_strtoint(contentlength, &len)) {
+            s->st_size = len;
+        }
+    }
+}
+
+static int pg_create_res(
+        PgVFS *pg,
+        const char *resparentid_str,
+        const char *nodename,
+        int64_t *new_resource_id,
+        Oid *oid,
+        const char **resource_name,
+        struct stat *s)
+{
+    const char* params[2] = { resparentid_str, nodename };
+    PGresult *result = PQexecParams(
+            pg->connection,
+            sql_create_resource,
+            2,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    if(!result) return 1;
+    
+    int ret = 1;
+    if(PQntuples(result) == 1) {
+        // sql insert succesful
+        ret = 0;
+        
+        char *id_str = PQgetvalue(result, 0, 0);
+        char *oid_str = PQgetvalue(result, 0, 1);
+        char *lastmodified = PQgetvalue(result, 0, 2);
+        char *creationdate = PQgetvalue(result, 0, 3);
+        
+        if(new_resource_id) {
+            if(!id_str || !util_strtoint(id_str, new_resource_id)) {
+                ret = 1; // shouldn't happen
+                log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert resource_id to int");
+            }
+        }
+        if(oid) {
+            int64_t i;
+            if(!oid_str || !util_strtoint(oid_str, &i)) {
+                ret = 1; // shouldn't happen
+                log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert oid to int");
+            } else {
+                *oid = i;
+            }
+        }
+        if(resource_name) {
+            *resource_name = nodename;
+        }
+        if(s) {
+            pg_set_stat(s, 0, lastmodified, creationdate, NULL);
+        }
+    }
+    
+    PQclear(result);
+    
+    return ret;
+}
+
+
+static int pg_create_col(
+        PgVFS *pg,
+        const char *resparentid_str,
+        const char *nodename,
+        int64_t *new_resource_id,
+        const char **resource_name,
+        struct stat *s)
+{
+    const char* params[2] = { resparentid_str, nodename };
+    PGresult *result = PQexecParams(
+            pg->connection,
+            sql_create_collection,
+            2,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    if(!result) return 1;
+    
+    int ret = 1;
+    if(PQntuples(result) == 1) {
+        // sql insert succesful
+        ret = 0;
+        
+        char *id_str = PQgetvalue(result, 0, 0);
+        char *lastmodified = PQgetvalue(result, 0, 1);
+        char *creationdate = PQgetvalue(result, 0, 2);
+        
+        if(new_resource_id) {
+            if(!id_str || !util_strtoint(id_str, new_resource_id)) {
+                ret = 1; // shouldn't happen
+                log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_collection: Could not convert resource_id to int");
+            }
+        }
+        if(resource_name) {
+            *resource_name = nodename;
+        }
+        if(s) {
+            pg_set_stat(s, 0, lastmodified, creationdate, NULL);
+        }
+    }
+    
+    PQclear(result);
+    
+    return ret;
+}
+
+int pg_create_file(
+        VFSContext *ctx,
+        PgVFS *pg,
+        const char *path,
+        int64_t *new_resource_id,
+        int64_t *res_parent_id,
+        Oid *oid,
+        const char **resource_name,
+        struct stat *s,
+        WSBool collection)
+{
+    char *parent_path = util_parent_path(path);
+    if(!parent_path) return 1;
+    
+    size_t pathlen = strlen(path);
+    char *pathf = NULL;
+    if(pathlen > 1 && path[pathlen-1] == '/') {
+        pathf = malloc(pathlen);
+        memcpy(pathf, path, pathlen);
+        pathf[pathlen-1] = 0; // remove trailing '/'
+        path = pathf;
+    }
+    
+    const char *nodename = util_resource_name(path);
+    
+    // resolve the parent path
+    // if the parent path can't be resolved, we are done
+    const char *resname;
+    int64_t resource_id, parent_id;
+    resource_id = -1;
+    parent_id = -1;
+    WSBool iscollection;
+    Oid unused_oid = 0;
+    
+    int err = pg_resolve_path(
+            pg->connection,
+            parent_path,
+            pg->root_resource_id_str,
+            &parent_id,
+            &resource_id,
+            &unused_oid,
+            &resname,
+            &iscollection,
+            NULL,
+            NULL,
+            &ctx->vfs_errno);
+    FREE(parent_path);
+    if(err) {
+        ctx->vfs_errno = ENOENT;
+        if(pathf) free(pathf);
+        return 1;
+    }
+    
+    // parent path exists, check if it is a collection
+    if(!iscollection) {
+        if(pathf) free(pathf);
+        return 1;
+    }
+    
+    // create new Resource
+    char resid_str[32];
+    snprintf(resid_str, 32, "%" PRId64, resource_id); // convert parent resource_id to string
+    
+    int ret;
+    if(collection) {
+        ret = pg_create_col(pg, resid_str, nodename, new_resource_id, resource_name, s);
+    } else {
+        ret = pg_create_res(pg, resid_str, nodename, new_resource_id, oid, resource_name, s);
+    }
+    
+    if(pathf) free(pathf);
+    
+    if(res_parent_id) {
+        // resource_id is still the id of the parent from previous pg_resolve_path call
+        *res_parent_id = resource_id;
+    }
+    
+    return ret;
+}
+
+int pg_remove_res(
+        VFSContext *ctx,
+        PgVFS *pg,
+        int64_t resource_id,
+        Oid oid)
+{
+    // create transaction savepoint
+    PGresult *result = PQexec(pg->connection, "savepoint del_res;");
+    ExecStatusType execStatus = PQresultStatus(result);
+    PQclear(result);
+    if(execStatus != PGRES_COMMAND_OK) {
+        return 1;
+    }
+    
+    if(oid > 0) {
+        if(lo_unlink(pg->connection, oid) != 1) {
+            // restore savepoint
+            result = PQexec(pg->connection, "rollback to savepoint del_res;");
+            PQclear(result);
+            return 1; // error
+        }
+    }
+    
+    char resid_str[32];
+    snprintf(resid_str, 32, "%" PRId64, resource_id);
+    
+    const char* params[1] = { resid_str };
+    result = PQexecParams(
+            pg->connection,
+            sql_delete_res,
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+      
+    execStatus = PQresultStatus(result);
+    PQclear(result); 
+    int ret = 0;
+    
+    if(execStatus != PGRES_COMMAND_OK) {
+        ret = 1;
+        // restore savepoint
+        result = PQexec(pg->connection, "rollback to savepoint del_res;");
+        PQclear(result);
+    } else {
+        // we don't need the savepoint anymore
+        result = PQexec(pg->connection, "release savepoint del_res;");
+        PQclear(result);
+    }
+    
+    return ret;
+}
+
+int pg_update_resource(PgVFS *pg, int64_t resource_id, int64_t contentlength) {
+    char resid_str[32];
+    char ctlen_str[32];
+    snprintf(resid_str, 32, "%" PRId64, resource_id);
+    snprintf(ctlen_str, 32, "%" PRId64, contentlength);
+    
+    const char* params[2] = { resid_str, ctlen_str };
+    PGresult *result = PQexecParams(
+            pg->connection,
+            sql_update_resource,
+            2,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    int ret = PQresultStatus(result) == PGRES_COMMAND_OK ? 0 : 1;
+    PQclear(result);   
+    return ret;
+}
+
+/* -------------------------- VFS functions -------------------------- */
+
+SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags) {
+    VFS *vfs = ctx->vfs;
+    PgVFS *pg = vfs->instance;
+    
+    const char *resname;
+    int64_t resource_id, parent_id;
+    resource_id = -1;
+    parent_id = -1;
+    WSBool iscollection;
+    struct stat s;
+    char etag[PG_ETAG_MAXLEN];
+    Oid oid = 0;
+    if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, etag, &ctx->vfs_errno)) {
+        if((oflags & O_CREAT) == O_CREAT) {
+            if(pg_create_file(ctx, pg, path, &resource_id, &parent_id, &oid, &resname, &s, FALSE)) {
+                return NULL;
+            }
+            iscollection = 0;
+        } else {
+            return NULL;
+        }
+    }
+    
+    // store the resource_id in rq->vars
+    if(ctx->rq) {
+        char *rq_path = pblock_findkeyval(pb_key_path, ctx->rq->vars);
+        if(rq_path && !strcmp(rq_path, path)) {
+            char *res_id_str = pblock_findval("resource_id", ctx->rq->vars);
+            if(!res_id_str) {
+                char resource_id_str[32];
+                snprintf(resource_id_str, 32, "%" PRId64, resource_id);
+                pblock_nvinsert("resource_id",resource_id_str, ctx->rq->vars);
+            }
+        }
+    }
+    
+    VFSFile *file = pool_malloc(ctx->pool, sizeof(VFSFile));
+    if(!file) {
+        return NULL;
+    }
+    PgFile *pgfile = pool_malloc(ctx->pool, sizeof(PgFile));
+    if(!pgfile) {
+        pool_free(ctx->pool, file);
+        return NULL;
+    }
+    
+    int fd = -1;
+    if(!iscollection) {
+        if (PQstatus(pg->connection) != CONNECTION_OK) {
+            fd = -2;
+        }
+        
+        int lo_mode = INV_READ;
+        if((oflags & O_RDWR) == O_RDWR) {
+            lo_mode = INV_READ|INV_WRITE;
+        } else if((oflags & O_WRONLY) == O_WRONLY) {
+            lo_mode = INV_WRITE;
+        }
+        fd = lo_open(pg->connection, oid, lo_mode);
+        int err = 0;
+        if(fd < 0) {
+            err = 1;
+        } else if((oflags & O_TRUNC) == O_TRUNC) {
+            if(lo_truncate(pg->connection, fd, 0)) {
+                lo_close(pg->connection, fd);
+                err = 1;
+            }
+        }
+        
+        if(err) {
+            pool_free(ctx->pool, file);
+            pool_free(ctx->pool, pgfile);
+            return NULL;
+        }
+    }
+    
+    pgfile->iscollection = iscollection;
+    pgfile->resource_id = resource_id;
+    pgfile->parent_id = parent_id;
+    pgfile->oid = oid;
+    pgfile->fd = fd;
+    pgfile->oflags = oflags;
+    pgfile->s = s;
+    memcpy(pgfile->etag, etag, PG_ETAG_MAXLEN);
+    
+    file->ctx = ctx;
+    file->io = iscollection ? &pg_vfs_io_class : &pg_vfs_io_class;
+    file->fd = -1;
+    file->data = pgfile;
+    
+    return file;
+}
+
+int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
+    VFS *vfs = ctx->vfs;
+    PgVFS *pg = vfs->instance;
+    
+    int64_t parent_id, resource_id;
+    const char *resname;
+    WSBool iscollection;
+    return pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, buf, NULL, &ctx->vfs_errno);
+}
+
+int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) {
+    PgFile *pgfile = fd->data;
+    memcpy(buf, &pgfile->s, sizeof(struct stat));
+    return 0;
+}
+
+VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path) {
+    VFSFile *file = pg_vfs_open(ctx, path, O_RDONLY);
+    if(!file) return NULL;
+    return pg_vfs_fdopendir(ctx, file);
+}
+
+VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    PgFile *pg = fd->data;
+    if(!pg->iscollection) {
+        ctx->vfs_errno = ENOTDIR;
+        return NULL;
+    }
+    
+    VFSDir *dir = pool_malloc(ctx->pool, sizeof(VFSDir));
+    if(!dir) {
+        fd->io->close(fd);
+        ctx->vfs_errno = ENOMEM;
+        return NULL;
+    }
+    
+    PgDir *pgdir = pool_malloc(ctx->pool, sizeof(PgDir));
+    if(!pgdir) {
+        fd->io->close(fd);
+        pool_free(ctx->pool, dir);
+        ctx->vfs_errno = ENOMEM;
+        return NULL;
+    }
+    memset(pgdir, 0, sizeof(PgDir));
+    pgdir->file = fd;
+    
+    dir->ctx = ctx;
+    dir->io = &pg_vfs_dirio_class;
+    dir->data = pgdir;
+    dir->fd = -1;
+    
+    return dir;
+}
+
+int pg_vfs_mkdir(VFSContext *ctx, const char *path) {
+    VFS *vfs = ctx->vfs;
+    PgVFS *pg = vfs->instance;
+    
+    const char *resname;
+    int64_t resource_id, parent_id;
+    resource_id = -1;
+    parent_id = -1;
+    WSBool iscollection;
+    struct stat s;
+    Oid oid = 0;
+    if(!pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, NULL, &ctx->vfs_errno)) {
+        ctx->vfs_errno = EEXIST;
+        return 1;
+    }
+    
+    if(pg_create_file(ctx, pg, path, NULL, NULL, NULL, NULL, NULL, TRUE)) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int pg_vfs_unlink(VFSContext *ctx, const char *path) {
+    VFS *vfs = ctx->vfs;
+    PgVFS *pg = vfs->instance;
+    
+    const char *resname;
+    int64_t resource_id, parent_id;
+    resource_id = -1;
+    parent_id = -1;
+    WSBool iscollection;
+    Oid oid = 0;
+    if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) {
+        return 1;
+    }
+    
+    if(iscollection) {
+        ctx->vfs_errno = EISDIR;
+        return 1;
+    }
+    
+    return pg_remove_res(ctx, pg, resource_id, oid);
+}
+
+int pg_vfs_rmdir(VFSContext *ctx, const char *path) {
+    VFS *vfs = ctx->vfs;
+    PgVFS *pg = vfs->instance;
+    
+    const char *resname;
+    int64_t resource_id, parent_id;
+    resource_id = -1;
+    parent_id = -1;
+    WSBool iscollection;
+    if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) {
+        return 1;
+    }
+    
+    if(!iscollection) {
+        ctx->vfs_errno = ENOTDIR;
+        return 1;
+    }
+    
+    return pg_remove_res(ctx, pg, resource_id, 0);
+}
+
+
+/* -------------------------- VFS_IO functions -------------------------- */
+
+ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    return lo_read(pgvfs->connection, pg->fd, buf, nbyte);
+}
+
+ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    return lo_write(pgvfs->connection, pg->fd, buf, nbyte);
+}
+
+ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) {
+        return -1;
+    }
+    return lo_read(pgvfs->connection, pg->fd, buf, nbyte);
+}
+
+ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) {
+        return -1;
+    }
+    return lo_write(pgvfs->connection, pg->fd, buf, nbyte);
+}
+
+off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    return lo_lseek64(pgvfs->connection, pg->fd, offset, whence);
+}
+
+off_t pg_vfs_io_tell(SYS_FILE fd) {
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    if(pg->fd < 0) return-1;
+    return lo_tell64(pgvfs->connection, pg->fd);
+}
+
+void pg_vfs_io_close(SYS_FILE fd) {
+    pool_handle_t *pool = fd->ctx->pool;
+    PgVFS *pgvfs = fd->ctx->vfs->instance;
+    PgFile *pg = fd->data;
+    
+    if(pg->fd >= 0) {
+        if((pg->oflags & (O_WRONLY|O_RDWR)) > 0) {
+            // file modified, update length and lastmodified
+            off_t len = pg_vfs_io_seek(fd, 0, SEEK_END);
+            if(len < 0) len = 0;
+            
+            pg_update_resource(pgvfs, pg->resource_id, len);
+        }
+        
+        PgVFS *pgvfs = fd->ctx->vfs->instance;
+        lo_close(pgvfs->connection, pg->fd);
+    }
+    
+    pool_free(pool, pg);
+    pool_free(pool, fd);
+}
+
+const char *pg_vfs_io_getetag(SYS_FILE fd) {
+    PgFile *pg = fd->data;
+    return pg->etag;
+}
+
+/* -------------------------- VFS_DIRIO functions -------------------------- */
+
+static int load_dir(VFSDir *dir, PgDir *pg) {
+    VFS *vfs = dir->ctx->vfs;
+    PgVFS *pgvfs = vfs->instance;
+    PgFile *pgfd = pg->file->data;
+    PgDir *pgdir = dir->data;
+    
+    char resid_param[32];
+    snprintf(resid_param, 32, "%" PRId64, pgfd->resource_id);
+    
+    const char *param = resid_param;
+    
+    PGresult *result = PQexecParams(
+            pgvfs->connection,
+            sql_get_children,
+            1,                                  // number of parameters
+            NULL,
+            &param,                             // param: parent resource_id 
+            NULL,
+            NULL,
+            0);                                 // 0: result in text format
+    if(!result) return 1;
+    
+    pgdir->result = result;
+    pgdir->nrows = PQntuples(result);
+    return 0;
+}
+
+int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) {
+    PgDir *pg = dir->data;
+    if(!pg->result) {
+        if(load_dir(dir, pg)) {
+            return 0;
+        }
+    }
+    
+    if(pg->row >= pg->nrows) {
+        return 0; // EOF
+    }
+    
+    entry->name = PQgetvalue(pg->result, pg->row, 1);
+    entry->stat_errno = 0;
+    entry->stat_extra = NULL;
+        
+    if(getstat) {
+        memset(&entry->stat, 0, sizeof(struct stat));
+        
+        char *iscollection = PQgetvalue(pg->result, pg->row, 2);
+        char *lastmodified = PQgetvalue(pg->result, pg->row, 3);
+        char *creationdate = PQgetvalue(pg->result, pg->row, 4);
+        char *contentlength = PQgetvalue(pg->result, pg->row, 5);
+        pg_set_stat(&entry->stat, iscollection, lastmodified, creationdate, contentlength);
+    }
+
+    pg->row++;
+    return 1;
+}
+
+void pg_vfs_dirio_close(VFS_DIR dir) {
+    pool_handle_t *pool = dir->ctx->pool;
+    PgDir *pg = dir->data;
+    if(pg->result) {
+        PQclear(pg->result);
+    }
+    PgFile *pgfile = pg->file->data;
+    
+    pool_free(pool, pgfile);
+    pool_free(pool, pg->file);
+    pool_free(pool, pg);
+    pool_free(pool, dir);
+}
+
+time_t pg_convert_timestamp(const char *timestamp) {
+    struct tm tm;
+    if(pg_convert_timestamp_tm(timestamp, &tm)) {
+        return 0;
+    }
+#ifdef __FreeBSD__
+    return timelocal(&tm);
+#else
+    return mktime(&tparts) - timezone;
+#endif
+}
+
+int pg_convert_timestamp_tm(const char *timestamp, struct tm *tm) {
+    // TODO: this is a very basic implementation that needs some work
+    // format yyyy-mm-dd HH:MM:SS
+    
+    memset(tm, 0, sizeof(struct tm));
+    size_t len = timestamp ? strlen(timestamp) : 0;
+    if(len < 19) {
+        return 1;
+    } else if(len > 63) {
+        return 1;
+    }
+    
+    char buf[64];
+    memcpy(buf, timestamp, len);
+    
+    char *year_str = buf;
+    year_str[4] = '\0';
+    
+    char *month_str = buf + 5;
+    month_str[2] = '\0';
+    
+    char *day_str = buf + 8;
+    day_str[2] = '\0';
+    
+    char *hour_str = buf + 11;
+    hour_str[2] = '\0';
+    
+    char *minute_str = buf + 14;
+    minute_str[2] = '\0';
+    
+    char *second_str = buf + 17;
+    second_str[2] = '\0';
+    
+    tm->tm_year = atoi(year_str) - 1900;
+    tm->tm_mon = atoi(month_str) - 1;
+    tm->tm_mday = atoi(day_str);
+    tm->tm_hour = atoi(hour_str);
+    tm->tm_min = atoi(minute_str);
+    tm->tm_sec = atoi(second_str);
+    
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/vfs.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,155 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 WS_PG_VFS_H
+#define WS_PG_VFS_H
+
+#include "../../public/nsapi.h"
+#include "../../public/vfs.h"
+
+#include "config.h"
+
+#include <libpq-fe.h>
+#include <libpq/libpq-fs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PG_ETAG_MAXLEN 48
+    
+typedef struct PgVFS {
+    ResourceData *pg_resource;
+    PGconn *connection;
+    char root_resource_id_str[32];
+    int64_t root_resource_id;
+} PgVFS;
+
+typedef struct PgFile {
+    int64_t      resource_id;
+    int64_t      parent_id;
+    WSBool       iscollection;
+    Oid          oid;
+    int          fd;
+    int          oflags;
+    char         etag[PG_ETAG_MAXLEN];
+    struct stat  s;
+} PgFile;
+
+typedef struct PgDir {
+    VFSFile      *file;
+    PGresult     *result;
+    int          row;
+    int          nrows;
+} PgDir;
+
+void* pg_vfs_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config);
+
+VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb, void *initData);
+
+VFS* pg_vfs_create_from_resourcedata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata);
+
+
+/*
+ * Resolve a path into a parent_id and resource name
+ * 
+ * Only absolute paths are supported, therefore path[0] must be '/'
+ * 
+ * If the resource is not found, res_errno is set to ENOENT
+ */
+int pg_resolve_path(
+        PGconn *connection,
+        const char *path,
+        const char *root_id,
+        int64_t *parent_id,
+        int64_t *resource_id,
+        Oid *oid,
+        const char **resource_name,
+        WSBool *iscollection,
+        struct stat *s,
+        char *etag,
+        int *res_errno);
+
+void pg_set_stat(
+        struct stat *s,
+        const char *iscollection,
+        const char *lastmodified,
+        const char *creationdate,
+        const char *contentlength);
+
+int pg_create_file(
+        VFSContext *ctx,
+        PgVFS *pg,
+        const char *path,
+        int64_t *new_resource_id,
+        int64_t *res_parent_id,
+        Oid *oid,
+        const char **resource_name,
+        struct stat *s,
+        WSBool collection);
+
+int pg_remove_res(
+        VFSContext *ctx,
+        PgVFS *pg,
+        int64_t resource_id,
+        Oid oid);
+
+int pg_update_resource(PgVFS *pg, int64_t resource_id, int64_t contentlength);
+    
+SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags);
+int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf);
+int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
+VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path);
+VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
+int pg_vfs_mkdir(VFSContext *ctx, const char *path);
+int pg_vfs_unlink(VFSContext *ctx, const char *path);
+int pg_vfs_rmdir(VFSContext *ctx, const char *path);
+
+    
+ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte);
+ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte);
+ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset);
+ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset);
+off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence);
+off_t pg_vfs_io_tell(SYS_FILE fd);
+void pg_vfs_io_close(SYS_FILE fd);
+const char *pg_vfs_io_getetag(SYS_FILE fd);
+
+
+int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat);
+void pg_vfs_dirio_close(VFS_DIR dir);
+
+time_t pg_convert_timestamp(const char *timestamp);
+int pg_convert_timestamp_tm(const char *timestamp, struct tm *tm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_PG_VFS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/webdav.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,1426 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "webdav.h"
+#include "vfs.h"
+#include "config.h"
+
+#include "../../util/util.h"
+#include "../../util/pblock.h"
+
+#include "../../daemon/http.h" // etag
+
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+#include <libxml/tree.h>
+
+
+static WebdavBackend pg_webdav_backend = {
+    pg_dav_propfind_init,
+    pg_dav_propfind_do,
+    pg_dav_propfind_finish,
+    pg_dav_proppatch_do,
+    pg_dav_proppatch_finish,
+    NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
+    NULL, // opt_delete
+    NULL, // opt_delete_finish
+    0,
+    NULL,
+    NULL
+};
+
+
+
+/*
+ * SQL query components
+ */
+
+/*
+ * PROPFIND queries are build from components:
+ * 
+ * cte              cte_recursive or empty
+ * select
+ * ppath            ppath column
+ * cols             list of columns
+ *   ext_cols*      list of extension columns
+ * from             from [table] / from cte
+ * prop_join        join with property table, allprop or plist
+ *   ext_join*      extension table join
+ * where            different where clauses for depth0 and depth1
+ * order            depth0 doesn't need order
+ */
+
+static const char *sql_propfind_cte_recursive = "\
+with recursive resolvepath as (\n\
+    select\n\
+        '' as ppath,\n\
+        *\n\
+    from Resource\n\
+    where resource_id = $1 \n\
+    union\n\
+    select\n\
+        p.ppath || '/' || r.nodename,\n\
+        r.*\n\
+    from Resource r\n\
+    inner join resolvepath p on r.parent_id = p.resource_id\n\
+    )\n";
+
+static const char *sql_propfind_select = "\
+select\n";
+
+static const char *sql_propfind_ppath_depth0 = "\
+$2::text as ppath,\n";
+
+static const char *sql_propfind_ppath_depth1 = "\
+case when r.resource_id = $1 then $2\n\
+     else $2 || '/' || r.nodename\n\
+end as ppath,\n";
+
+static const char *sql_propfind_ppath_depth_infinity = "\
+case when r.resource_id = $1 then $2\n\
+     else $2 || r.ppath\n\
+end as ppath,\n";
+
+static const char *sql_propfind_cols = "\
+r.resource_id,\n\
+r.parent_id,\n\
+r.nodename,\n\
+r.iscollection,\n\
+r.lastmodified,\n\
+r.creationdate,\n\
+r.contentlength,\n\
+r.etag,\n\
+p.prefix,\n\
+p.xmlns,\n\
+p.pname,\n\
+p.lang,\n\
+p.nsdeflist,\n\
+p.pvalue\n";
+
+static const char *sql_propfind_from_table = "\
+from Resource r\n";
+
+static const char *sql_propfind_from_cte = "\
+from resolvepath r\n";
+
+static const char *sql_propfind_propjoin_allprop = "\
+left join Property p on r.resource_id = p.resource_id\n";
+
+static const char *sql_propfind_propjoin_plist = "\
+left join (\n\
+    select p.* from Property p\
+    inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\
+       on p.xmlns = n.xmlns and p.pname = n.pname\n\
+) p on r.resource_id = p.resource_id\n";
+
+static const char *sql_propfind_where_depth0 = "\
+where r.resource_id = $1\n";
+
+static const char *sql_propfind_where_depth1 = "\
+where r.resource_id = $1 or r.parent_id = $1\n";
+
+static const char *sql_propfind_order_depth1 = "\
+order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id";
+
+static const char *sql_propfind_order_depth_infinity = "\
+order by replace(ppath, '/', chr(1)), resource_id";
+
+/*
+ * SQL Queries
+ */
+
+
+// proppatch: set property
+// params: $1: resource_id
+//         $2: xmlns prefix
+//         $3: xmlns href
+//         $4: property name
+//         $5: lang attribute value
+//         $6: namespace list string
+//         $7: property value
+static const char *sql_proppatch_set = "\
+insert into Property(resource_id, prefix, xmlns, pname, lang, nsdeflist, pvalue)\n\
+values($1, $2, $3, $4, $5, $6, $7)\n\
+on conflict (resource_id, xmlns, pname) do\n\
+update set prefix=$2, lang=$5, nsdeflist=$6, pvalue=$7;";
+
+// proppatch: remove property
+// params: $1: resource_id
+//         $2: xmlns href
+//         $3: property name
+static const char *sql_proppatch_remove = "\
+delete from Property where resource_id = $1 and xmlns = $2 and pname = $3";
+
+
+void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) {
+    return pg_init_repo(cfg, pool, config);
+}
+
+WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData) {
+    PgRepository *repo = initData;
+    
+    char *resource_pool;
+    if(repo) {
+        resource_pool = repo->resourcepool.ptr;
+    } else {
+        // resourcepool is required
+        resource_pool = pblock_findval("resourcepool", pb);
+        if(!resource_pool) {
+            log_ereport(LOG_MISCONFIG, "pg_webdav_create: missing resourcepool parameter");
+            return NULL;
+        }
+    }
+    
+    // get the resource first (should only fail in case of misconfig)
+    ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0);
+    if(!resdata) {
+        log_ereport(LOG_MISCONFIG, "postgresql webdav: resource pool %s not found", resource_pool);
+        return NULL;
+    }
+    
+    return pg_webdav_create_from_resdata(sn, rq, repo, resdata);
+}
+
+WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) {
+    WebdavBackend *webdav = pool_malloc(sn->pool, sizeof(WebdavBackend));
+    if(!webdav) {
+        return NULL;
+    }
+    *webdav = pg_webdav_backend;
+    
+    PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend));
+    if(!instance) {
+        pool_free(sn->pool, webdav);
+        return NULL;
+    }
+    webdav->instance = instance;
+    
+    instance->pg_resource = resdata;
+    instance->connection = resdata->data;
+    
+    instance->repository = repo;
+    snprintf(instance->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id);
+    
+    return webdav;
+}
+
+WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) {
+    return NULL;
+}
+
+/*
+ * adds str to the buffer
+ * some characters will be escaped: \,{}
+ */
+static void buf_addstr_escaped(UcxBuffer *buf, const char *str) {
+    size_t len = strlen(str);
+    for(size_t i=0;i<len;i++) {
+        char c = str[i];
+        if(c == '{' || c == '}' || c == ',' || c == '\\') {
+            ucx_buffer_putc(buf, '\\');
+        }
+        ucx_buffer_putc(buf, c);
+    }
+}
+
+/*
+ * convert a property list to two pg array parameter strings
+ * array format: {elm1,elm2,elm3}
+ * xmlns: buffer for the xmlns array
+ * pname: buffer for the property name array
+ * 
+ * returns 0 on success, 1 otherwise
+ */
+int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname) {
+    ucx_buffer_putc(xmlns, '{');
+    ucx_buffer_putc(pname, '{');
+    while(plist) {
+        WebdavProperty *property = plist->property;
+        if(property && property->namespace && property->namespace->href && property->name) {
+            buf_addstr_escaped(xmlns, (const char*)property->namespace->href);
+            buf_addstr_escaped(pname, (const char*)property->name);
+            if(plist->next) {
+                ucx_buffer_putc(xmlns, ',');
+                ucx_buffer_putc(pname, ',');
+            }
+        }
+        plist = plist->next;
+    }
+    int r1 = ucx_buffer_write("}\0", 2, 1, xmlns) == 0;
+    int r2 = ucx_buffer_write("}\0", 2, 1, pname) == 0;
+    return r1+r2 != 0;
+}
+
+
+static int propfind_ext_cmp(const void *f1, const void *f2) {
+    const PgPropfindExtCol *e1 = f1;
+    const PgPropfindExtCol *e2 = f2;
+    
+    if(e1->ext->tableindex != e2->ext->tableindex) {
+        return e1->ext->tableindex < e2->ext->tableindex ? -1 : 1;
+    }
+    
+    return 0;
+}
+
+
+static int pg_create_propfind_query(
+        WebdavPropfindRequest *rq,
+        WSBool iscollection,
+        PgPropfindExtCol *ext,
+        size_t numext,
+        UcxBuffer *sql)
+{
+    PgWebdavBackend *pgdav = rq->dav->instance;
+    PgRepository *repo = pgdav->repository;
+    int depth = !iscollection ? 0 : rq->depth;
+    
+    /*
+     * PROPFIND queries are build from components:
+     * 
+     * cte              cte_recursive or empty
+     * select
+     * ppath            ppath column
+     * cols             list of columns
+     *   ext_cols*      list of extension columns
+     * from             from [table] / from cte
+     * prop_join        join with property table, allprop or plist
+     *   ext_join*      extension table join
+     * where            different where clauses for depth0 and depth1
+     * order            depth0 doesn't need order
+     */
+    
+    // CTE
+    if(depth == -1) {
+        ucx_buffer_puts(sql, sql_propfind_cte_recursive);
+    }
+    
+    // select
+    ucx_buffer_puts(sql, sql_propfind_select);
+    
+    // ppath
+    switch(depth) {
+        case 0: ucx_buffer_puts(sql, sql_propfind_ppath_depth0); break;
+        case 1: ucx_buffer_puts(sql, sql_propfind_ppath_depth1); break;
+        case -1: ucx_buffer_puts(sql, sql_propfind_ppath_depth_infinity); break;
+    }
+    
+    // cols
+    ucx_buffer_puts(sql, sql_propfind_cols);
+    
+    // ext_cols
+    if(ext) { 
+        if(rq->allprop) {
+            for(int i=0;i<repo->ntables;i++) {
+                ucx_bprintf(sql, ",x%d.*\n", i);
+            }
+        } else {
+            for(int i=0;i<numext;i++) {
+                PgPropfindExtCol e = ext[i];
+                ucx_bprintf(sql, ",x%d.%s\n", e.ext->tableindex, e.ext->column);
+            }
+        }
+    }
+    
+    // from
+    ucx_buffer_puts(sql, depth == -1 ? sql_propfind_from_cte : sql_propfind_from_table);
+    
+    // prop join
+    ucx_buffer_puts(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist);
+    
+    // ext_join
+    if(ext) {
+        if(rq->allprop) {
+            for(int i=0;i<repo->ntables;i++) {
+                ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[i].table, i, i);
+            }
+        } else {
+            int tab = -1;
+            for(int i=0;i<numext;i++) {
+                PgPropfindExtCol e = ext[i];
+                if(e.ext->tableindex != tab) {
+                    tab = e.ext->tableindex;
+                    ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[tab].table, tab, tab);
+                }
+            }
+        }
+        
+    }
+    
+    // where
+    if(depth == 0) {
+        ucx_buffer_puts(sql, sql_propfind_where_depth0);
+    } else if(depth == 1) {
+        ucx_buffer_puts(sql, sql_propfind_where_depth1);
+    }
+    
+    // order
+    if(depth == 1) {
+        ucx_buffer_puts(sql, sql_propfind_order_depth1);
+    } else if(depth == -1) {
+        ucx_buffer_puts(sql, sql_propfind_order_depth_infinity);
+    }
+    
+    // end
+    ucx_buffer_puts(sql, ";\0");
+    
+    return 0;
+}
+
+int pg_dav_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char *path,
+        const char *href,
+        WebdavPList **outplist)
+{
+    PgWebdavBackend *pgdav = rq->dav->instance;
+    
+    // first, check if the resource exists
+    // if it doesn't exist, we can return immediately
+    int64_t parent_id;
+    int64_t resource_id;
+    const char *resourcename;
+    WSBool iscollection;
+    int res_errno = 0;
+    int err = pg_resolve_path(
+            pgdav->connection,
+            path,
+            pgdav->root_resource_id_str,
+            &parent_id,
+            &resource_id,
+            NULL, // OID
+            &resourcename,
+            &iscollection,
+            NULL, // stat
+            NULL, // etag
+            &res_errno);
+    
+    if(err) {
+        if(res_errno == ENOENT) {
+            protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL);
+        }
+        return 1;
+    }
+    
+    // store resource_id in rq->vars, maybe some other modules
+    // like to use it
+    char resource_id_str[32];
+    snprintf(resource_id_str, 32, "%" PRId64, resource_id);
+    pblock_nvinsert("resource_id",resource_id_str, rq->rq->vars);
+    
+    // create a list of requsted extended properties
+    PgPropfindExtCol *ext;
+    size_t numext;
+    if(pgdav->repository->ntables == 0) {
+        // no property extensions configured
+        ext = NULL;
+        numext = 0;
+    } else {     
+        numext = pgdav->repository->prop_ext->count;
+        ext = pool_calloc(rq->sn->pool, numext, sizeof(PgPropfindExtCol));
+        
+        if(rq->allprop) {
+            // the map pgdav->repository->prop_ext contains all property extensions
+            // we can just convert the map to an array
+            UcxMapIterator i = ucx_map_iterator(pgdav->repository->prop_ext);
+            PgPropertyStoreExt *cfg_ext;
+            int j = 0;
+            UCX_MAP_FOREACH(key, cfg_ext, i) {
+                PgPropfindExtCol extcol;
+                extcol.ext = cfg_ext;
+                extcol.field_num = -1; // get the field_num after the PQexec
+                ext[j++] = extcol;
+            }
+        } else {
+            WebdavPListIterator i = webdav_plist_iterator(outplist);
+            WebdavPList *cur;
+            int j = 0;
+            while(webdav_plist_iterator_next(&i, &cur)) {
+                WSNamespace *ns = cur->property->namespace;
+                if(ns) {
+                    UcxKey pkey = webdav_property_key((const char*)ns->href, cur->property->name);
+                    PgPropertyStoreExt *cfg_ext = ucx_map_get(pgdav->repository->prop_ext, pkey);
+                    free((void*)pkey.data);
+                    if(cfg_ext) {
+                        PgPropfindExtCol extcol;
+                        extcol.ext = cfg_ext;
+                        extcol.field_num = -1; // get the field_num after the PQexec
+                        ext[j++] = extcol;
+                        
+                        webdav_plist_iterator_remove_current(&i);
+                    }
+                }
+            }
+            numext = j;
+        }
+        
+        qsort(ext, numext, sizeof(PgPropfindExtCol), propfind_ext_cmp);
+    }
+    
+    // create sql query
+    const char *query = NULL;
+    UcxBuffer *sql = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND);
+    if(pg_create_propfind_query(rq, iscollection, ext, numext, sql)) {
+        return 1;
+    }
+    query = sql->space;
+    
+    // get all resources and properties
+    size_t href_len = strlen(href);
+    char *href_param = pool_malloc(rq->sn->pool, href_len + 1);
+    memcpy(href_param, href, href_len);
+    if(href_param[href_len-1] == '/') {
+        href_len--;
+    }
+    href_param[href_len] = '\0';
+    
+    // if allprop is false, create array pair for xmlns/property names
+    UcxBuffer *xmlns_buf = NULL;
+    UcxBuffer *pname_buf = NULL;
+    char *xmlns_param = NULL;
+    char *pname_param = NULL;
+    int nparam = 2;
+    if(!rq->allprop) {
+        size_t bufsize = rq->propcount < 200 ? 8 + rq->propcount * 32 : 4096;
+        xmlns_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND);
+        if(!xmlns_buf) {
+            return 1;
+        }
+        pname_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND);
+        if(!pname_buf) {
+            ucx_buffer_free(xmlns_buf);
+            return 1;
+        }
+        if(pg_create_property_param_arrays(*outplist, xmlns_buf, pname_buf)) {
+            ucx_buffer_free(xmlns_buf);
+            ucx_buffer_free(pname_buf);
+            return 1;
+        }
+        xmlns_param = xmlns_buf->space;
+        pname_param = pname_buf->space;
+        nparam = 4;
+    }
+    
+    const char* params[4] = { resource_id_str, href_param, xmlns_param, pname_param };
+    PGresult *result = PQexecParams(
+            pgdav->connection,
+            query,
+            nparam,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    int nrows = PQntuples(result);
+    pool_free(rq->sn->pool, href_param);
+    if(xmlns_buf) {
+        ucx_buffer_free(xmlns_buf);
+        ucx_buffer_free(pname_buf);
+    }
+    if(nrows < 1) {
+        PQclear(result);
+        return 1;
+    }
+    
+    PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind));
+    rq->userdata = pg;
+    
+    pg->path = path;
+    pg->resource_id = resource_id;
+    pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0);
+    pg->result = result;
+    pg->nrows = nrows;
+    
+    pg->ext = ext;
+    pg->numext = numext;
+    if(ext) {
+        // get field_nums for all property extensions
+        for(int i=0;i<numext;i++) {
+            PgPropfindExtCol *c = &ext[i];
+            c->field_num = PQfnumber(result, c->ext->column);
+        }
+    }
+    
+    return 0;
+}
+
+int pg_dav_propfind_do(
+	WebdavPropfindRequest *rq,
+	WebdavResponse *response,
+	VFS_DIR parent,
+	WebdavResource *resource,
+	struct stat *s)
+{
+    PgPropfind *pg = rq->userdata;
+    pool_handle_t *pool = rq->sn->pool;
+    PGresult *result = pg->result;
+    WebdavVFSProperties vfsprops = pg->vfsproperties;
+      
+    WSBool vfsprops_set = 0; // are live properties added to the response?
+    WSBool extprops_set = 0; // are extended properties added to the response?
+    int64_t current_resource_id = pg->resource_id;
+    for(int r=0;r<pg->nrows;r++) {
+        // columns:
+        //  0: path
+        //  1: resource_id
+        //  2: parent_id
+        //  3: nodename
+        //  4: iscollection
+        //  5: lastmodified
+        //  6: creationdate
+        //  7: contentlength
+        //  8: etag
+        //  9: property prefix
+        // 10: property xmlns
+        // 11: property name
+        // 12: property lang
+        // 13: property nsdeflist
+        // 14: property value
+        
+        char *path = PQgetvalue(result, r, 0);
+        char *res_id = PQgetvalue(result, r, 1);
+        char *iscollection_str = PQgetvalue(result, r, 4);
+        WSBool iscollection = iscollection_str && iscollection_str[0] == 't';
+        int64_t resource_id;
+        if(!util_strtoint(res_id, &resource_id)) {
+            log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id);
+            return 1;
+        }
+        
+        if(resource_id != current_resource_id) {
+            // create a href string for the new resource
+            // if the resource is a collection, it should have a trailing '/'
+            size_t pathlen = strlen(path);
+            if(pathlen == 0) {
+                log_ereport(LOG_FAILURE, "pg_dav_propfind_do: query returned invalid path");
+                return 1;
+            }
+            if(pathlen > PG_MAX_PATH_LEN) {
+                log_ereport(LOG_FAILURE, "pg_dav_propfind_do: path too long: resource_id: %s", res_id);
+                return 1;
+            }
+            char *newres_href = pool_malloc(pool, (pathlen*3)+2);
+            util_uri_escape(newres_href, path);
+            if(iscollection && path[pathlen-1] != '/') {
+                size_t newres_href_len = strlen(newres_href);
+                newres_href[newres_href_len] = '/';
+                newres_href[newres_href_len+1] = '\0';
+            }
+            
+            // new resource
+            resource = response->addresource(response, newres_href);
+            vfsprops_set = FALSE;
+            extprops_set = FALSE;
+            current_resource_id = resource_id;
+        }
+        
+        // standard webdav live properties
+        if(!vfsprops_set) {
+            if(vfsprops.getresourcetype) {
+                if(iscollection) {
+                    resource->addproperty(resource, webdav_resourcetype_collection(), 200);
+                } else {
+                    resource->addproperty(resource, webdav_resourcetype_empty(), 200);
+                }
+            }
+            
+            char *lastmodified = PQgetvalue(result, r, 5);
+            char *contentlength = PQgetvalue(result, r, 7);
+            time_t t = pg_convert_timestamp(lastmodified);
+            
+            if(vfsprops.getlastmodified) {
+                struct tm tm;
+                gmtime_r(&t, &tm);
+                
+                char buf[HTTP_DATE_LEN+1];
+                strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, &tm);
+                webdav_resource_add_dav_stringproperty(resource, pool, "getlastmodified", buf, strlen(buf));
+            }
+            if(vfsprops.creationdate) {
+                char *creationdate = PQgetvalue(result, r, 6);
+                webdav_resource_add_dav_stringproperty(resource, pool, "creationdate", creationdate, strlen(creationdate));
+            }
+            if(vfsprops.getcontentlength && !iscollection) {
+                webdav_resource_add_dav_stringproperty(resource, pool, "getcontentlength", contentlength, strlen(contentlength));
+            }
+            if(vfsprops.getetag) {
+                char *etag = PQgetvalue(result, r, 8);
+                if(!PQgetisnull(result, r, 8)) {
+                    webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag));
+                } else {
+                    int64_t ctlen;
+                    if(util_strtoint(contentlength, &ctlen)) {
+                        char etag[MAX_ETAG];
+                        http_format_etag(rq->sn, rq->rq, etag, MAX_ETAG, ctlen, t);
+                        webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag));
+                    }
+                }
+            }
+            
+            vfsprops_set = TRUE;
+        }
+        
+        if(!extprops_set) {
+            // extended properties
+            if(pg->ext) {
+                for(int extc=0;extc<pg->numext;extc++) {
+                    PgPropfindExtCol ext = pg->ext[extc];
+                    int fieldnum = ext.field_num;
+                    
+                    if(!PQgetisnull(result, r, fieldnum)) {
+                        char *ext_value = PQgetvalue(result, r, fieldnum);
+                        int ext_value_len = PQgetlength(result, r, fieldnum);
+                        char ext_xmlns_prefix[32];
+                        snprintf(ext_xmlns_prefix, 32, "x%d", ext.ext->tableindex);
+
+                        WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+                        property->lang = NULL;
+                        property->name = pool_strdup(pool, ext.ext->name);
+
+                        xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
+                        memset(namespace, 0, sizeof(struct _xmlNs));
+                        namespace->href = (xmlChar*)pool_strdup(pool, ext.ext->ns);
+                        namespace->prefix = (xmlChar*)pool_strdup(pool, ext_xmlns_prefix);
+                        property->namespace = namespace;
+
+                        char *content = pool_malloc(pool, ext_value_len+1);
+                        memcpy(content, ext_value, ext_value_len);
+                        content[ext_value_len] = '\0';
+                        
+                        WebdavNSList *nslist = pool_malloc(pool, sizeof(WebdavNSList));
+                        nslist->namespace = namespace;
+                        nslist->prev = NULL;
+                        nslist->next = NULL;
+                        
+                        property->vtype = WS_VALUE_XML_DATA;               
+                        property->value.data.data = content;
+                        property->value.data.length = ext_value_len;
+                        property->value.data.namespaces = nslist;
+
+                        resource->addproperty(resource, property, 200);
+                    }
+                }
+            }
+            
+            extprops_set = TRUE;
+        }
+        
+        // dead properties
+        if(!PQgetisnull(result, r, 9)) {
+            char *prefix = PQgetvalue(result, r, 9);
+            char *xmlns = PQgetvalue(result, r, 10);
+            char *pname = PQgetvalue(result, r, 11);
+            char *lang = PQgetvalue(result, r, 12);
+            char *nsdef = PQgetvalue(result, r, 13);
+            char *pvalue = PQgetvalue(result, r, 14);
+            
+            int pvalue_len = PQgetlength(result, r, 14);
+            WSBool lang_isnull = PQgetisnull(result, r, 12);
+            WSBool nsdef_isnull = PQgetisnull(result, r, 13);
+            WSBool pvalue_isnull = PQgetisnull(result, r, 14);
+            
+            WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+            property->lang = NULL;
+            property->name = pool_strdup(pool, pname);
+            
+            xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
+            memset(namespace, 0, sizeof(struct _xmlNs));
+            namespace->href = (xmlChar*)pool_strdup(pool, xmlns);
+            namespace->prefix = (xmlChar*)pool_strdup(pool, prefix);
+            property->namespace = namespace;
+            
+            if(!lang_isnull) {
+                property->lang = pool_strdup(pool, lang);
+            }
+            
+            if(!pvalue_isnull) {
+                char *content = pool_malloc(pool, pvalue_len+1);
+                memcpy(content, pvalue, pvalue_len);
+                content[pvalue_len] = '\0';
+                
+                if(nsdef_isnull) {
+                    property->vtype = WS_VALUE_TEXT;
+                    property->value.text.str = content;
+                    property->value.text.length = pvalue_len;
+                } else {
+                    WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef);
+                    property->vtype = WS_VALUE_XML_DATA;
+                    property->value.data.data = content;
+                    property->value.data.length = pvalue_len;
+                    property->value.data.namespaces = nslist;
+                    
+                }
+            }
+            
+            resource->addproperty(resource, property, 200);
+        }
+    }
+        
+    return 0;
+}
+
+int pg_dav_propfind_finish(WebdavPropfindRequest *rq) {
+    PgPropfind *pg = rq->userdata;
+    pool_handle_t *pool = rq->sn->pool;
+    PGresult *result = pg->result;
+    
+    PQclear(result);
+    
+    return 0;
+}
+
+enum PgDavProp {
+    PG_DAV_PROPPATCH_NOT_ALLOWED = 0,
+    PG_DAV_CREATIONDATE,
+    PG_DAV_DISPLAYNAME,
+    PG_DAV_DEADPROP
+};
+/*
+ * checks if the property can be manipulated
+ */
+static enum PgDavProp proppatch_check_dav_prop(const char *name) {
+    if(!strcmp(name, "getlastmodified")) {
+        return PG_DAV_PROPPATCH_NOT_ALLOWED;
+    } else if(!strcmp(name, "getcontentlength")) {
+        return PG_DAV_PROPPATCH_NOT_ALLOWED;
+    } else if(!strcmp(name, "resourcetype")) {
+        return PG_DAV_PROPPATCH_NOT_ALLOWED;
+    } else if(!strcmp(name, "getetag")) {
+        return PG_DAV_PROPPATCH_NOT_ALLOWED;
+    } else if(!strcmp(name, "creationdate")) {
+        return PG_DAV_CREATIONDATE;
+    } else if(!strcmp(name, "displayname")) {
+        return PG_DAV_DISPLAYNAME;
+    }
+    return PG_DAV_DEADPROP;
+}
+
+typedef struct {
+    WebdavProperty *creationdate;
+    WebdavProperty *displayname;
+    int error;
+} PgProppatchOpResult;
+
+typedef int(*pg_proppatch_func)(PgWebdavBackend*, WebdavProppatchRequest*, WebdavResource*, WebdavProperty*, void*);
+
+/*
+ * This function iterates the property list 'plist',
+ * analyses if any DAV: property is in the list
+ * and calls opfunc for the each property
+ * 
+ * If the property list contains the properties creationdate or displayname,
+ * the pointers to these properties will be stored in the result structure
+ */
+static PgProppatchOpResult pg_proppatch_op(
+        PgWebdavBackend *pgdav,
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        WebdavPList **plist,
+        enum PgDavProp forbidden_extra,
+        pg_proppatch_func opfunc,
+        void *op_userdata)
+{
+    PgProppatchOpResult result;
+    result.creationdate = NULL;
+    result.displayname = NULL;
+    result.error = 0;
+    
+    WebdavPListIterator i = webdav_plist_iterator(plist);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WebdavProperty *property = cur->property;
+        WSNamespace *ns = property->namespace;
+        if(!ns) {
+            continue; // maybe we should abort
+        }
+        
+        // check if the property is a DAV: property that requires special
+        // handling
+        // get* properties can't be manipulated
+        // some properties can't be removed
+        if(!strcmp((const char*)ns->href, "DAV:")) {
+            const char *name = property->name;
+            enum PgDavProp davprop = proppatch_check_dav_prop(name);
+            if(davprop != PG_DAV_DEADPROP) {
+                if(davprop == PG_DAV_PROPPATCH_NOT_ALLOWED || davprop == forbidden_extra) {
+                    response->addproperty(response, property, 409);
+                } else if(davprop == PG_DAV_CREATIONDATE) {
+                    result.creationdate = property;
+                } else if(davprop == PG_DAV_DISPLAYNAME) {
+                    result.displayname = property;
+                }
+                webdav_plist_iterator_remove_current(&i);
+                continue;
+            }
+        }
+        
+        // call op func (set, remove specific code)
+        if(opfunc(pgdav, request, response, property, op_userdata)) {
+            result.error = 1;
+            break;
+        }
+        
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    return result;
+}
+
+#define PG_PROPPATCH_EXT_SET 0
+#define PG_PROPPATCH_EXT_REMOVE 1
+
+static int pg_proppatch_add_ext_prop(
+        pool_handle_t *pool,
+        PgWebdavBackend *pgdav,
+        PgProppatch *proppatch,
+        WebdavProperty *property,
+        int proppatch_op)
+{
+    UcxKey pkey = webdav_property_key((const char*)property->namespace->href, property->name);
+    PgPropertyStoreExt *ext = ucx_map_get(pgdav->repository->prop_ext, pkey);
+    free((void*)pkey.data);
+    if(ext) {
+        PgProppatchExtProp *ext_prop = pool_malloc(pool, sizeof(PgProppatchExtProp));
+        if(!ext_prop) {
+            return 1;
+        }
+        ext_prop->column = ext;
+        ext_prop->property = property;
+
+        UcxAllocator a = util_pool_allocator(pool);
+        proppatch->ext[ext->tableindex].isused = TRUE;
+        
+        UcxList **list = proppatch_op == PG_PROPPATCH_EXT_REMOVE
+                            ? &proppatch->ext[ext->tableindex].remove
+                            : &proppatch->ext[ext->tableindex].set;
+        *list = ucx_list_append_a(&a, *list, ext_prop);
+
+        proppatch->extensions_used = TRUE;
+    }
+    
+    return 0;
+}
+
+static int pg_dav_set_property(
+        PgWebdavBackend *pgdav,
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        WebdavProperty *property,
+        void *userdata)
+{
+    pool_handle_t *pool = request->sn->pool;
+    PgProppatch *proppatch = request->userdata;
+    WSNamespace *ns = property->namespace;
+    
+    // check if the property belongs to an extension
+    if(proppatch->ext && ns) {
+        return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_SET);
+    }
+    
+    char *resource_id_str = userdata;
+    int ret = 0;
+    
+    // convert the property value to WSXmlData
+    // property->vtype == WS_VALUE_XML_NODE should always be true
+    WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
+    char *value_str = NULL;
+    char *nsdef_str = NULL;
+    if(property_value) {
+        value_str = property_value->data;
+        if(property_value->namespaces) {
+            nsdef_str = wsxml_nslist2string(pool, property_value->namespaces);
+            if(!nsdef_str) {
+                return 1; // OOM
+            }
+        }
+    }
+
+    // exec sql
+    const char* params[7] = { resource_id_str, (const char*)ns->prefix, (const char*)ns->href, property->name, NULL, nsdef_str, value_str}; 
+    PGresult *result = PQexecParams(
+        pgdav->connection,
+        sql_proppatch_set,
+        7,      // number of parameters
+        NULL,
+        params, // parameter value
+        NULL,
+        NULL,
+        0);     // 0: result in text format
+
+    if(PQresultStatus(result) != PGRES_COMMAND_OK) {
+        response->addproperty(response, property, 500);
+        //printf(PQerrorMessage(pgdav->connection));
+        //fflush(stdout);
+        ret = 1;
+    } else {
+        response->addproperty(response, property, 200);
+    }
+    PQclear(result);
+    if(value_str) pool_free(pool, value_str);
+    
+    return ret;
+}
+
+
+static int pg_dav_remove_property(
+        PgWebdavBackend *pgdav,
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        WebdavProperty *property,
+        void *userdata)
+{
+    pool_handle_t *pool = request->sn->pool;
+    PgProppatch *proppatch = request->userdata;
+    WSNamespace *ns = property->namespace;
+    
+    // check if the property belongs to an extension
+    if(proppatch->ext && ns) {
+        return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_REMOVE);
+    }
+    
+    char *resource_id_str = userdata;
+    int ret = 0;
+
+    // exec sql
+    const char* params[3] = { resource_id_str, (const char*)ns->href, property->name }; 
+    PGresult *result = PQexecParams(
+        pgdav->connection,
+        sql_proppatch_remove,
+        3,      // number of parameters
+        NULL,
+        params, // parameter value
+        NULL,
+        NULL,
+        0);     // 0: result in text format
+
+    if(PQresultStatus(result) != PGRES_COMMAND_OK) {
+        response->addproperty(response, property, 500);
+        //printf(PQerrorMessage(pgdav->connection));
+        //fflush(stdout);
+        ret = 1;
+    }
+    PQclear(result);
+    
+    return ret;
+}
+
+
+/*
+ * Creates an SQL query for inserting a new row to an extension table
+ * A parameter list for PQexecParams will also be generated, however
+ * params[0] will be empty (resource_id str)
+ * 
+ * Query: insert into <table> (resource_id, col1, ...) values ($1, $2 ...);
+ */
+static UcxBuffer* ext_row_create_insert_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) {
+    pool_handle_t *pool = request->sn->pool;
+    
+    UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    if(!sql) {
+        return NULL;
+    }
+    
+    size_t pg_nparams = ucx_list_size(ext->set) + 1;
+    char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*));
+    if(!pg_params) {
+        ucx_buffer_free(sql);
+        return NULL;
+    }
+    
+    ucx_buffer_puts(sql, "insert into ");
+    ucx_buffer_puts(sql, table->table);
+    ucx_buffer_puts(sql, "(resource_id");
+    UCX_FOREACH(elm, ext->set) {
+        PgProppatchExtProp *prop = elm->data;
+        ucx_bprintf(sql, ",%s", prop->column->name);
+    }
+    
+    ucx_buffer_puts(sql, ") values ($1\n");
+    int i = 1;
+    UCX_FOREACH(elm, ext->set) {
+        PgProppatchExtProp *prop = elm->data;
+        WebdavProperty *property = prop->property;
+        // convert the property value to WSXmlData
+        // property->vtype == WS_VALUE_XML_NODE should always be true
+        WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
+        char *value_str = NULL;
+        //char *nsdef_str = NULL;
+        if(property_value) {
+            value_str = property_value->data;
+            if(property_value->namespaces) {
+                // currently only text data is supported
+                pool_free(pool, params);
+                ucx_buffer_free(sql);
+                return NULL;
+            }
+        }
+        
+        pg_params[i] = value_str;
+        ucx_bprintf(sql, ",$%d", ++i);
+    }
+    ucx_buffer_puts(sql, ");");
+    
+    
+    //printf("\n\n%.*s\n\n", (int)sql->size, sql->space);
+    //fflush(stdout);
+    
+    *params = pg_params;
+    *nparams = pg_nparams;
+    
+    return sql;
+}
+
+/*
+ * Creates an SQL query for updating an extension table row
+ * A parameter list for PQexecParams will also be generated, however
+ * params[0] will be empty (resource_id str)
+ * 
+ * Query: update <table> set
+ *        col1 = $2,
+ *        col2 = $3,
+ *        ...
+ *        where resource_id = $1 ;
+ */
+static UcxBuffer* ext_row_create_update_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) {
+    pool_handle_t *pool = request->sn->pool;
+    
+    UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    if(!sql) {
+        return NULL;
+    }
+    
+    ucx_buffer_puts(sql, "update ");
+    ucx_buffer_puts(sql, table->table);
+    ucx_buffer_puts(sql, " set\n");
+    
+    size_t pg_nparams = ucx_list_size(ext->set) + 1;
+    char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*));
+    if(!pg_params) {
+        ucx_buffer_free(sql);
+        return NULL;
+    }
+    
+    int i = 1;
+    UCX_FOREACH(elm, ext->set) {
+        PgProppatchExtProp *prop = elm->data;
+        WebdavProperty *property = prop->property;
+        // convert the property value to WSXmlData
+        // property->vtype == WS_VALUE_XML_NODE should always be true
+        WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
+        char *value_str = NULL;
+        //char *nsdef_str = NULL;
+        if(property_value) {
+            value_str = property_value->data;
+            if(property_value->namespaces) {
+                // currently only text data is supported
+                pool_free(pool, params);
+                ucx_buffer_free(sql);
+                return NULL;
+            }
+        }
+        
+        pg_params[i] = value_str;
+        ucx_bprintf(sql, " %s = $%d,\n", prop->column->name, ++i);
+    }
+    
+    UCX_FOREACH(elm, ext->remove) {
+        PgProppatchExtProp *prop = elm->data;
+        ucx_bprintf(sql, " %s = NULL,\n", prop->column->name);
+    }
+    
+    // check if any write worked
+    if(sql->pos == 0) {
+        ucx_buffer_free(sql);
+        pool_free(pool, pg_params);
+        return NULL;
+    }
+    
+    // last line should end with ',' '\n'
+    // replace ',' with space
+    if(sql->space[sql->pos-2] == ',') {
+        sql->space[sql->pos-2] = ' ';
+    }
+    
+    ucx_bprintf(sql, "where resource_id =  $1 ;\0");
+    
+    //printf("\n\n%.*s\n\n", (int)sql->size, sql->space);
+    //fflush(stdout);
+    
+    *params = pg_params;
+    *nparams = pg_nparams;
+    
+    return sql;
+}
+
+/*
+ * Executes an SQL insert for the extension table
+ */
+int ext_row_insert(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
+    PgWebdavBackend *pgdav = request->dav->instance;
+    PgProppatch *proppatch = request->userdata;
+    pool_handle_t *pool = request->sn->pool;
+    
+    char **params;
+    size_t nparam;
+    UcxBuffer *sql = ext_row_create_insert_query(request, ext, table, &params, &nparam);
+    if(!sql) {
+        return 1;
+    }
+    
+    char resource_id_str[32];
+    snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id);
+    params[0] = resource_id_str;
+    
+    PGresult *result = PQexecParams(
+            pgdav->connection,
+            sql->space,
+            nparam,     // number of parameters
+            NULL,
+            ( const char *const *)params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    ucx_buffer_free(sql);
+    
+    int ret = 1;
+    if(PQresultStatus(result) == PGRES_COMMAND_OK) {
+        // command ok, check if any row was updated
+        char *nrows_affected = PQcmdTuples(result);
+        if(nrows_affected[0] == '1') {
+            ret = 0;
+        } else {
+            log_ereport(LOG_FAILURE, "pg: extension row insert failed");
+        }
+    } else {
+        log_ereport(LOG_FAILURE, "pg: extension row insert failed: %s", PQresultErrorMessage(result));
+    }
+    
+    PQclear(result);
+    
+    return ret;
+}
+
+/*
+ * Executes an SQL update for the extension table
+ */
+int ext_row_update(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
+    PgWebdavBackend *pgdav = request->dav->instance;
+    PgProppatch *proppatch = request->userdata;
+    pool_handle_t *pool = request->sn->pool;
+    
+    char **params;
+    size_t nparam;
+    UcxBuffer *sql = ext_row_create_update_query(request, ext, table, &params, &nparam);
+    if(!sql) {
+        return 1;
+    }
+    
+    char resource_id_str[32];
+    snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id);
+    params[0] = resource_id_str;
+    
+    PGresult *result = PQexecParams(
+            pgdav->connection,
+            sql->space,
+            nparam,     // number of parameters
+            NULL,
+            ( const char *const *)params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    ucx_buffer_free(sql);
+    
+    int ret = 1;
+    if(PQresultStatus(result) == PGRES_COMMAND_OK) {
+        // command ok, check if any row was updated
+        char *nrows_affected = PQcmdTuples(result);
+        if(nrows_affected[0] == '1') {
+            ret = 0;
+        } else if(nrows_affected[0] == '0') {
+            // no rows affected, that means we have to insert a new row
+            // in the extension table for this resource
+            
+            // TODO: cleanup params
+            
+            ret = ext_row_insert(request, ext, table);
+        }
+    } else {
+        log_ereport(LOG_FAILURE, "pg: extension row update failed: %s", PQresultErrorMessage(result));
+    }
+    
+    
+    PQclear(result);
+    
+    return ret;
+}
+
+static int pg_dav_update_extension_tables(WebdavProppatchRequest *request) {
+    PgWebdavBackend *pgdav = request->dav->instance;
+    PgProppatch *proppatch = request->userdata;
+    
+    for(int i=0;i<proppatch->numext;i++) {
+        if(proppatch->ext[i].isused) {
+            if(ext_row_update(request, &proppatch->ext[i], &pgdav->repository->tables[i])) {
+                // extension proppatch failed
+                return 1;
+            }
+        }
+    }
+    
+    return 0;
+}
+
+int pg_dav_proppatch_do(
+	WebdavProppatchRequest *request,
+	WebdavResource *response,
+	VFSFile *file,
+	WebdavPList **out_set,
+	WebdavPList **out_remove)
+{
+    PgWebdavBackend *pgdav = request->dav->instance;
+    pool_handle_t *pool = request->sn->pool;
+    char *path = pblock_findkeyval(pb_key_path, request->rq->vars);
+    
+    PgProppatch proppatch;
+    proppatch.extensions_used = FALSE;
+    if(pgdav->repository->ntables == 0) {
+        proppatch.ext = NULL;
+        proppatch.numext = 0;
+    } else {
+        // some properties are stored in additional tables
+        // for each table we create a PgProppatchExt record
+        // which stores data about, which tables are used
+        // and which properties (columns) should be updated
+        //
+        // proppatch.ext[i] should contain the data for repository->tables[i]
+        proppatch.numext = pgdav->repository->ntables;
+        proppatch.ext = pool_calloc(request->sn->pool, proppatch.numext, sizeof(PgProppatchExt));
+        if(!proppatch.ext) {
+            return 1; // OOM
+        }
+    }
+    request->userdata = &proppatch;
+    
+    // check if the resource exists, we also need the resource_id
+    int64_t parent_id;
+    int64_t resource_id;
+    const char *resourcename;
+    WSBool iscollection;
+    int res_errno = 0;
+    int err = pg_resolve_path(
+            pgdav->connection,
+            path,
+            pgdav->root_resource_id_str,
+            &parent_id,
+            &resource_id,
+            NULL, // OID
+            &resourcename,
+            &iscollection,
+            NULL, // stat
+            NULL, // etag
+            &res_errno);
+    
+    if(err) {
+        return 1;
+    }
+    
+    proppatch.resource_id = resource_id;
+       
+    // because proppatch must be atomic and we have multiple sql
+    // queries and other backends that do stuff that could fail
+    // we need the possibility to reverse all changes
+    // we use a transaction savepoint for this
+    PGresult *result = PQexec(pgdav->connection, "savepoint proppatch;");
+    ExecStatusType execStatus = PQresultStatus(result);
+    PQclear(result);
+    if(execStatus != PGRES_COMMAND_OK) {
+        return 1;
+    }
+    
+    char resource_id_str[32];
+    snprintf(resource_id_str, 32, "%" PRId64, resource_id);
+    // store the resource_id in rq->vars, because it could be useful later
+    pblock_nvinsert("resource_id", resource_id_str, request->rq->vars);
+    
+    int ret = 0;
+    PgProppatchOpResult set_res = pg_proppatch_op(
+            pgdav,
+            request,
+            response,
+            out_set,
+            PG_DAV_PROPPATCH_NOT_ALLOWED,
+            pg_dav_set_property,
+            resource_id_str);
+    if(set_res.error) {
+        return 1;
+    }
+    PgProppatchOpResult rm_res = pg_proppatch_op(
+            pgdav,
+            request,
+            response,
+            out_remove,
+            PG_DAV_CREATIONDATE, // creationdate can't be removed
+            pg_dav_remove_property,
+            resource_id_str);
+    if(rm_res.error) {
+        return 1;
+    }
+    
+    // if extensions are in use and pg_proppatch_op found any
+    // properties, that should be stored in extension tables
+    // we do the update/insert now
+    if(proppatch.extensions_used) {
+        ret = pg_dav_update_extension_tables(request);
+    }
+    
+       
+    return ret;
+}
+
+int pg_dav_proppatch_finish(
+	WebdavProppatchRequest *request,
+	WebdavResource *response,
+	VFSFile *file,
+	WSBool commit)
+{
+    PgWebdavBackend *pgdav = request->dav->instance;
+    int ret = 0;
+    if(!commit) {
+        log_ereport(LOG_VERBOSE, "proppatch: rollback");
+        PGresult *result = PQexec(pgdav->connection, "rollback to savepoint proppatch;");
+        if(PQresultStatus(result) != PGRES_COMMAND_OK) {
+            log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: rollback failed: %s", PQerrorMessage(pgdav->connection));
+            ret = 1;
+        }
+        PQclear(result);
+    }
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/webdav.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,134 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 WS_PG_WEBDAV_H
+#define WS_PG_WEBDAV_H
+
+#include "../../public/nsapi.h"
+#include "../../public/webdav.h"
+
+#include "config.h"
+
+#include <libpq-fe.h>
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define PG_MAX_PATH_LEN 0x8000
+    
+typedef struct PgWebdavBackend {
+    ResourceData *pg_resource;
+    PGconn *connection;
+    PgRepository *repository;
+    char root_resource_id_str[32];
+} PgWebdavBackend;
+
+typedef struct PgPropfindExtCol {
+    /*
+     * property extension config
+     */
+    PgPropertyStoreExt *ext;
+    /*
+     * Result field number
+     */
+    int field_num;
+} PgPropfindExtCol;
+    
+typedef struct PgPropfind {
+    const char *path;
+    int64_t resource_id;
+    WebdavVFSProperties vfsproperties;
+    PGresult *result;
+    PgPropfindExtCol *ext;
+    size_t numext;
+    int nrows;
+} PgPropfind;
+
+typedef struct {
+    PgPropertyStoreExt *column;
+    WebdavProperty *property;
+} PgProppatchExtProp;
+
+typedef struct {
+    UcxList *set; /* list of PgProppatchExtProp* */
+    UcxList *remove; /* list of PgProppatchExtProp* */
+    WSBool isused;
+} PgProppatchExt;
+
+typedef struct {
+    int64_t resource_id;
+    PgProppatchExt *ext;
+    size_t numext;
+    WSBool extensions_used;
+} PgProppatch;
+
+void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config);
+WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData);
+WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata);
+
+WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb);
+
+int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname);
+
+/* ----------------- webdav backend functions ----------------- */
+int pg_dav_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char *path,
+        const char *href,
+        WebdavPList **outplist);
+
+int pg_dav_propfind_do(
+	WebdavPropfindRequest *rq,
+	WebdavResponse *response,
+	VFS_DIR parent,
+	WebdavResource *resource,
+	struct stat *s);
+
+int pg_dav_propfind_finish(WebdavPropfindRequest *rq);
+
+int pg_dav_proppatch_do(
+	WebdavProppatchRequest *request,
+	WebdavResource *response,
+	VFSFile *file,
+	WebdavPList **out_set,
+	WebdavPList **out_remove);
+
+int pg_dav_proppatch_finish(
+	WebdavProppatchRequest *request,
+	WebdavResource *response,
+	VFSFile *file,
+	WSBool commit);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PG_WEBDAV_H */
+
--- a/src/server/public/acl.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/acl.h	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 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	Sat Sep 24 16:26:10 2022 +0200
@@ -110,6 +110,9 @@
 
 /* --- Begin miscellaneous definitions --- */
 
+#define WS_TRUE 1
+#define WS_FALSE 0
+
 /* Used in some places as a length limit on error messages */
 #define MAGNUS_ERROR_LEN 1024
 
@@ -355,9 +358,6 @@
 #ifndef HPUX
 #include <sys/select.h>
 #endif
-#ifndef BSD
-#include <alloca.h> /* new */
-#endif
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -404,6 +404,7 @@
 //          they are VFSFile*
 // TODO: fix NOTE
 
+typedef int WSBool;
 
 #ifndef SYS_FILE_T
 typedef struct VFSFile *SYS_FILE;
@@ -690,6 +691,15 @@
 typedef struct VFS           VFS;
 typedef struct VFSContext    VFSContext;
 
+enum WSConfigNodeType {
+    WS_CONFIG_NODE_OBJECT = 0,
+    WS_CONFIG_NODE_DIRECTIVE
+};
+
+typedef struct ServerConfiguration ServerConfiguration;
+typedef struct ConfigNode          WSConfigNode;
+typedef enum WSConfigNodeType      WSConfigNodeType;
+
 #ifndef PR_AF_INET
 typedef union PRNetAddr PRNetAddr;
 #endif
@@ -763,6 +773,36 @@
 
 typedef struct _http_listener HttpListener;
 
+typedef struct ResourcePool ResourcePool;
+
+typedef struct ResourceType ResourceType;
+typedef struct ResourceData ResourceData;
+
+typedef void * (*resource_pool_init_func)(pool_handle_t *, const char *, pblock *);
+typedef void   (*resource_pool_destroy_func)(void *); 
+typedef void * (*resource_pool_createresource_func)(void *);
+typedef void   (*resource_pool_freeresource_func)(void *, void *);
+typedef int    (*resource_pool_prepare_func)(void *, void *);
+typedef int    (*resource_pool_finish_func)(void *, void *);
+typedef void * (*resource_pool_getresourcedata_func)(void *);
+
+struct ResourceType {
+    void * (*init)(pool_handle_t *, const char *, pblock *);
+    void   (*destroy)(void *);
+    void * (*createresource)(void *);
+    void   (*freeresource)(void *, void *);
+    int    (*prepare)(void *, void *);
+    int    (*finish)(void *, void *);
+    void * (*getresourcedata)(void *);
+};
+
+struct ResourceData {
+    ResourcePool *resourcepool;
+    void *data;
+};
+
+int resourcepool_register_type(const char *type_name, ResourceType *type_info);
+
 //////
 /*
  * VSInitFunc, VSDestroyFunc, VSDirectiveInitFunc and VSDirectiveDestroyFunc
@@ -1483,6 +1523,7 @@
 NSAPI_PUBLIC void http_format_etag(Session *sn, Request *rq, char *etagp, int etaglen, off_t size, time_t mtime);
 NSAPI_PUBLIC int http_check_preconditions(Session *sn, Request *rq, struct tm *mtm, const char *etag);
 NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo);
+NSAPI_PUBLIC int http_set_finfo_etag(Session *sn, Request *rq, struct stat *finfo, const char *etag);
 
 NSAPI_PUBLIC char **http_hdrs2env(pblock *pb);
 
@@ -1568,6 +1609,7 @@
 
 // threadpool
 threadpool_t* threadpool_new(int min, int max);
+int threadpool_start(threadpool_t *pool);
 void* threadpool_func(void *data);
 threadpool_job* threadpool_get_job(threadpool_t *pool);
 void threadpool_run(threadpool_t *pool, job_callback_f func, void *data);
@@ -1577,6 +1619,14 @@
 int event_removepoll(EventHandler *ev, SYS_NETFD fd);
 int event_send(EventHandler *ev, Event *event);
 
+// resource pool
+ResourceData* resourcepool_lookup(Session *sn, Request *rq, const char *name, int flags);
+ResourceData* resourcepool_cfg_lookup(ServerConfiguration *cfg, const char *name, int flags);
+void resourcepool_free(Session *sn, Request *rq, ResourceData *resource);
+
+// utils
+NSAPI_PUBLIC char *util_html_escape(const char *s);
+
 // assert
 void ws_log_assert(const char *file, const char *func, int line);
 #ifdef _DEBUG
--- a/src/server/public/vfs.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/vfs.h	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -48,13 +48,16 @@
 #define VFS_ENTRY         VFSEntry
 
 struct VFS {
-    SYS_FILE (*open)(VFSContext *ctx, char *path, int oflags);
-    int (*stat)(VFSContext *ctx, char *path, struct stat *buf);
+    SYS_FILE (*open)(VFSContext *ctx, const char *path, int oflags);
+    int (*stat)(VFSContext *ctx, const char *path, struct stat *buf);
     int (*fstat)(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
-    VFS_DIR (*opendir)(VFSContext *ctx, char *path);
-    int (*mkdir)(VFSContext *ctx, char *path);
-    int (*unlink)(VFSContext *ctx, char *path);
+    VFS_DIR (*opendir)(VFSContext *ctx, const char *path);
+    VFS_DIR (*fdopendir)(VFSContext *ctx, SYS_FILE fd);
+    int (*mkdir)(VFSContext *ctx, const char *path);
+    int (*unlink)(VFSContext *ctx, const char *path);
+    int (*rmdir)(VFSContext *Ctx, const char *path);
     uint32_t flags;
+    void *instance;
 };
 
 struct VFSContext {
@@ -66,20 +69,21 @@
     ACLListHandle *acllist;
     uint32_t aclreqaccess;
     int vfs_errno;
+    WSBool error_response_set;
 };
 
 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 {
@@ -98,6 +102,7 @@
     void (*close)(SYS_FILE fd);
     int (*opt_aioread)(aiocb_s *aiocb);
     int (*opt_aiowrite)(aiocb_s *aiocb);
+    const char* (*opt_getetag)(SYS_FILE fd);
 };
 
 struct VFS_DIRIO {
@@ -105,10 +110,18 @@
     void (*close)(VFS_DIR dir);
 };
 
+typedef void*(*vfs_init_func)(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config);
+typedef VFS*(*vfs_create_func)(Session *sn, Request *rq, pblock *pb, void *initData);
+
 /*
  * registers a new VFS
  */
-void vfs_add(char *name, VFS *vfs);
+int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate);
+
+/*
+ * Create a new VFS instance
+ */
+VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData);
 
 /*
  * creates a VFSContext for a Request
@@ -116,19 +129,22 @@
  */
 VFSContext* vfs_request_context(Session *sn, Request *rq);
 
-SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags);
-SYS_FILE vfs_openRO(VFSContext *ctx, char *path);
-SYS_FILE vfs_openWO(VFSContext *ctx, char *path);
-SYS_FILE vfs_openRW(VFSContext *ctx, char *path);
-int vfs_stat(VFSContext *ctx, char *path, struct stat *buf);
+SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags);
+SYS_FILE vfs_openRO(VFSContext *ctx, const char *path);
+SYS_FILE vfs_openWO(VFSContext *ctx, const char *path);
+SYS_FILE vfs_openRW(VFSContext *ctx, const char *path);
+int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf);
 int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf);
+const char * vfs_getetag(SYS_FILE fd);
 void vfs_close(SYS_FILE fd);
-VFS_DIR vfs_opendir(VFSContext *ctx, char *path);
+VFS_DIR vfs_opendir(VFSContext *ctx, const char *path);
+VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd);
 int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry);
 int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry);
 void vfs_closedir(VFS_DIR dir);
-int vfs_mkdir(VFSContext *ctx, char *path);
-int vfs_unlink(VFSContext *ctx, char *path);
+int vfs_mkdir(VFSContext *ctx, const char *path);
+int vfs_unlink(VFSContext *ctx, const char *path);
+int vfs_rmdir(VFSContext *ctx, const char *path);
 
 #ifdef	__cplusplus
 }
--- a/src/server/public/webdav.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/public/webdav.h	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,7 @@
 #define	WS_WEBDAV_H
 
 #include "nsapi.h"
+#include "vfs.h"
 
 #include <sys/file.h>
 #include <sys/stat.h>
@@ -39,7 +40,463 @@
 extern "C" {
 #endif
 
+typedef struct WebdavBackend          WebdavBackend;
+    
+typedef struct WebdavProperty         WebdavProperty;
+typedef struct WebdavPList            WebdavPList;
+typedef struct WebdavNSList           WebdavNSList;
 
+typedef struct WebdavPListIterator    WebdavPListIterator;
+
+typedef enum   WebdavLockScope        WebdavLockScope;
+typedef enum   WebdavLockType         WebdavLockType;
+
+typedef enum   WebdavValueType        WebdavValueType;
+
+typedef struct WebdavPropfindRequest  WebdavPropfindRequest;
+typedef struct WebdavProppatchRequest WebdavProppatchRequest;
+typedef struct WebdavLockRequest      WebdavLockRequest;
+
+typedef struct WebdavVFSRequest       WebdavVFSRequest;
+
+typedef struct WebdavResponse         WebdavResponse;
+typedef struct WebdavResource         WebdavResource;
+
+typedef struct WebdavVFSProperties    WebdavVFSProperties;
+
+typedef struct WebdavOperation        WebdavOperation;
+
+typedef struct WSXmlData              WSXmlData;
+typedef struct WSText                 WSText;
+
+typedef struct _xmlNs   WSNamespace;
+typedef struct _xmlNode WSXmlNode;
+
+#define WS_NODE_ELEMENT        1
+#define WS_NODE_TEXT           3
+#define WS_NODE_CDATA          4
+#define WS_NODE_ENTITY_REF     5
+
+typedef int(*wsxml_func)(WSXmlNode *, void *);
+
+/* propfind settings */
+
+/*
+ * Use the vfs to stat files or read the directory children
+ */
+#define WS_WEBDAV_PROPFIND_USE_VFS     0x01
+
+/*
+ * Use the vfs to open a file for proppatch
+ */
+#define WS_WEBDAV_PROPPATCH_USE_VFS   0x02
+
+
+typedef void*(*webdav_init_func)(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config);
+typedef WebdavBackend*(*webdav_create_func)(Session *sn, Request *rq, pblock *pb, void *initData);
+
+enum WebdavValueType {
+    WS_VALUE_NO_TYPE = 0,
+    WS_VALUE_XML_NODE,
+    WS_VALUE_XML_DATA,
+    WS_VALUE_TEXT
+};
+
+struct WSText {
+    char   *str;
+    size_t length;
+};
+
+struct WSXmlData {
+    WebdavNSList *namespaces;
+    char         *data;
+    size_t       length;
+};
+
+struct WebdavProperty {
+    WSNamespace *namespace;
+    
+    const char *name;
+    
+    char *lang;
+    
+    union {
+        WSXmlNode *node;
+        WSXmlData data;
+        WSText    text;
+    } value;
+    WebdavValueType vtype;
+};
+
+struct WebdavPList {
+    WebdavProperty *property;
+    WebdavPList *prev;
+    WebdavPList *next;
+};
+
+struct WebdavNSList {
+    WSNamespace  *namespace;
+    WebdavNSList *prev;
+    WebdavNSList *next;
+};
+
+struct WebdavPListIterator {
+    WebdavPList **list;
+    WebdavPList *cur;
+    WebdavPList *next;
+    size_t      index;
+};
+
+enum WebdavLockScope {
+    WEBDAV_LOCK_EXCLUSIVE = 0,
+    WEBDAV_LOCK_SHARED,
+    WEBDAV_LOCK_SCOPE_UNKNOWN
+};
+
+enum WebdavLockType {
+    WEBDAV_LOCK_WRITE = 0,
+    WEBDAV_LOCK_TYPE_UNKNOWN
+};
+
+struct WebdavPropfindRequest {
+    Session *sn;
+    Request *rq;
+    
+    WebdavBackend *dav;
+
+    void *doc;
+      
+    /*
+     * list of requested properties
+     */
+    WebdavPList *properties;
+    
+    /*
+     * number of properties
+     */
+    size_t propcount;
+    
+    WSBool allprop;
+    WSBool propname;
+    WSBool deadproperties;
+    
+    int depth;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavProppatchRequest {
+    Session *sn;
+    Request *rq;
+    
+    WebdavBackend *dav;
+    
+    void *doc;
+    
+    WebdavPList *set;
+    size_t setcount;
+    
+    WebdavPList *remove;
+    size_t removecount;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavVFSRequest {
+    Session *sn;
+    Request *rq;
+    
+    char *path;
+    
+    /*
+     * custom userdata for the backend
+     */
+    void *userdata;
+};
+
+struct WebdavLockRequest {
+    Session *sn;
+    Request *rq;
+    
+    void *doc;
+    
+    WebdavLockScope scope;
+    WebdavLockType type;
+    
+    WSXmlNode *owner;
+};
+
+struct WebdavVFSProperties {
+    uint32_t getcontentlength:1;
+    uint32_t getlastmodified:1;
+    uint32_t getresourcetype:1;
+    uint32_t getetag:1;
+    uint32_t creationdate:1;
+};
+
+struct WebdavResponse {
+    WebdavOperation *op;
+    
+    WebdavResource* (*addresource)(WebdavResponse*, const char*);
+};
+
+struct WebdavResource {
+    char *href;
+    
+    WSBool isclosed;
+    
+    int err;
+    
+    /*
+     * int addprop(WebdavResource *res, WebdavProperty *property, int status);
+     * 
+     * Adds a property to the resource
+     */
+    int (*addproperty)(WebdavResource*, WebdavProperty*, int);
+    
+    /*
+     * int close(WebdavResource *res);
+     * 
+     * Closes a resource object
+     */
+    int (*close)(WebdavResource*);
+};
+
+struct WebdavBackend {
+    /*
+     * int propfind_init(
+     *     WebdavPropfindRequest *rq,
+     *     const char *path,
+     *     const char *href,
+     *     WebdavPList **outplist);
+     * 
+     * Initializes a propfind request. This is called once for each propfind
+     * request and should initialize everything needed for generating the
+     * multistatus response.
+     * 
+     */
+    int (*propfind_init)(WebdavPropfindRequest *, const char *, const char *, WebdavPList **);
+    
+    /*
+     * int propfind_do(
+     *     WebdavPropfindRequest *rq,
+     *     WebdavResponse *response,
+     *     VFS_DIR parent,
+     *     WebdavResource *resource,
+     *     struct stat *s);
+     * 
+     * This function is called for the requsted resource and for all children
+     * if WS_PROPFIND_NO_VFS_CHILDREN is not set.
+     */
+    int (*propfind_do)(
+            WebdavPropfindRequest *,
+            WebdavResponse *,
+            VFS_DIR,
+            WebdavResource *,
+            struct stat *);
+    
+    /*
+     * int propfind_finish(WebdavPropfindRequest *rq);
+     * 
+     * Finishes a propfind request.
+     */
+    int (*propfind_finish)(WebdavPropfindRequest *);
+     
+    /*
+     * int proppatch_do(
+     *     WebdavProppatchRequest *request,
+     *     WebdavResource *response,
+     *     VFSFile *file,
+     *     WebdavPList **out_set,
+     *     WebdavPList **out_remove);
+     * 
+     * Modifies properties of the requsted resource.
+     */
+    int (*proppatch_do)(
+            WebdavProppatchRequest *,
+            WebdavResource *,
+            VFSFile *,
+            WebdavPList **,
+            WebdavPList **);
+    
+    /*
+     * int proppatch_finish(
+     *     WebdavProppatchRequest *request,
+     *     WebdavResource *response,
+     *     VFSFile *file,
+     *     WSBool commit);
+     * 
+     * Called after all proppatch_do functions of all backends are executed
+     * and should either permanently store the properties (commit == true) or
+     * revert all changed (commit == false).
+     */
+    int (*proppatch_finish)(
+            WebdavProppatchRequest *,
+            WebdavResource *,
+            VFSFile *,
+            WSBool);
+    
+    /*
+     * int opt_mkcol(WebdavVFSRequest *request, WSBool *out_created);
+     * 
+     * Optional mkcol callback that is called before vfs_mkdir. If the function
+     * sets out_created to TRUE, vfs_mkdir will not be executed.
+     */
+    int (*opt_mkcol)(WebdavVFSRequest *, WSBool *);
+    
+    /*
+     * int opt_mkcol_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a MKCOL request.
+     */
+    int(*opt_mkcol_finish)(WebdavVFSRequest *, WSBool);
+    
+    /*
+     * int opt_delete(WebdavVFSRequest *request, WSBool *out_deleted);
+     * 
+     * Optional delete callback that is called once before any VFS deletions.
+     * When the callback sets out_deleted to TRUE, no VFS unlink operations
+     * will be done.
+     * 
+     */
+    int (*opt_delete)(WebdavVFSRequest *, WSBool *);
+    
+    /*
+     * int opt_delete_finish(WebdavVFSRequest *request, WSBool success);
+     * 
+     * Optional callback for finishing a DELETE request.
+     */
+    int (*opt_delete_finish)(WebdavVFSRequest *, WSBool);
+    
+    /*
+     * See the WS_WEBDAV_* macros for informations about the settings
+     */
+    uint32_t settings;
+    
+    /*
+     * private instance data
+     */
+    void *instance;
+    
+    /*
+     * next Backend
+     */
+    WebdavBackend *next;
+};
+
+/*
+ * register a webdav backend
+ */
+int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate);
+
+WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData);
+
+/*
+ * gets the requested depth
+ * 
+ * in case of infinity, -1 is returned
+ * if no depth is specified, 0 is returned
+ */
+int webdav_getdepth(Request *rq);
+
+int webdav_plist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop);
+
+WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list);
+WebdavPList* webdav_plist_clone_s(
+        pool_handle_t *pool,
+        WebdavPList *list,
+        size_t *newlen);
+
+size_t webdav_plist_size(WebdavPList *list);
+
+int webdav_nslist_add(
+        pool_handle_t *pool,
+        WebdavNSList **begin,
+        WebdavNSList **end,
+        WSNamespace *ns);
+
+WebdavPListIterator webdav_plist_iterator(WebdavPList **list);
+int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur);
+void webdav_plist_iterator_remove_current(WebdavPListIterator *i);
+
+WSNamespace* webdav_dav_namespace(void);
+WebdavProperty* webdav_resourcetype_collection(void);
+WebdavProperty* webdav_resourcetype_empty(void);
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name);
+
+int webdav_resource_add_dav_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *name,
+        const char *str,
+        size_t len);
+int webdav_resource_add_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *xmlns_prefix,
+        const char *xmlns_href,
+        const char *name,
+        const char *str,
+        size_t len);
+
+int webdav_property_set_value(
+        WebdavProperty *property,
+        pool_handle_t *pool,
+        char *value);
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPList **plistInOut,
+        WSBool removefromlist,
+        WSBool appprop,
+        uint32_t flags);
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s);
+
+int wsxml_iterator(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        wsxml_func begincb,
+        wsxml_func endcb,
+        void *udata);
+
+WebdavNSList* wsxml_get_required_namespaces(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        int *error);
+
+WSXmlData* wsxml_node2data(
+        pool_handle_t *pool,
+        WSXmlNode *node);
+
+/*
+ * converts a property list to a string
+ * 
+ * namespaces are separated by a newline character and have the format:
+ * <prefix>:<href>
+ */
+char* wsxml_nslist2string(pool_handle_t *pool, WebdavNSList *nslist);
+
+/*
+ * converts a namespace list string (created by wsxml_nslist2string) to
+ * a WebdavNSList object
+ */
+WebdavNSList* wsxml_string2nslist(pool_handle_t *pool, char *nsliststr);
 
 #ifdef	__cplusplus
 }
--- a/src/server/safs/cgi.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/safs/cgi.c	Sat Sep 24 16:26:10 2022 +0200
@@ -36,9 +36,10 @@
 #include <signal.h>
 #include <sys/wait.h>
 
+#include <ucx/string.h>
+
 #include "../util/util.h"
 #include "../util/pblock.h"
-#include "../../ucx/string.h"
 #include "../daemon/netsite.h"
 #include "../util/io.h"
 
@@ -190,7 +191,12 @@
         log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path);
         kill(cgip.pid, SIGKILL);
     }
-    cgi_close(&cgip); // TODO: check return value
+    
+    int exit_code = cgi_close(&cgip);
+    if(exit_code != 0) {
+        log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", path, exit_code);
+        ret = REQ_ABORTED;
+    }
       
     cgi_parser_free(parser);
     return result;
@@ -268,7 +274,7 @@
         system_close(p->out[1]);
     }
     
-    return 0;
+    return status;
 }
 
 CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) {
--- a/src/server/safs/cgi.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/safs/cgi.h	Sat Sep 24 16:26:10 2022 +0200
@@ -30,7 +30,7 @@
 #define CGI_H
 
 #include "../public/nsapi.h"
-#include "../../ucx/buffer.h"
+#include <ucx/buffer.h>
 
 #ifdef __cplusplus
 extern "C" {
--- a/src/server/safs/common.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/safs/common.c	Sat Sep 24 16:26:10 2022 +0200
@@ -33,7 +33,7 @@
 
 #include "../util/pblock.h"
 #include "../util/util.h"
-#include "../../ucx/map.h"
+#include <ucx/map.h>
 
 static UcxMap *var_names;
 
--- a/src/server/safs/nametrans.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/safs/nametrans.c	Sat Sep 24 16:26:10 2022 +0200
@@ -32,6 +32,80 @@
 #include "../daemon/request.h"
 #include "../util/pblock.h"
 #include "../util/util.h"
+#include "../public/webdav.h"
+
+#include "../daemon/session.h"
+#include "../daemon/config.h"
+
+#include "../public/vfs.h"
+
+static int initialize_dav_repo(pblock *pb, Session *sn, Request *rq, WebdavRepository *repo) {
+    if(repo->vfs) {
+        VFS *vfs = repo->vfs->create(sn, rq, pb, repo->vfsInitData);
+        if(!vfs) {
+            return REQ_ABORTED;
+        }
+        rq->vfs = vfs;
+    }
+    
+    WebdavBackend *backend_first = NULL;
+    WebdavBackend *backend_last = NULL;
+    UCX_FOREACH(elm, repo->davBackends) {
+        WebdavBackendInitData *davInit = elm->data;
+        WebdavBackend *backend = davInit->davType->create(sn, rq, pb, davInit->davInitData);
+        if(!backend) {
+            return REQ_ABORTED;
+        }
+        
+        if(backend_last) {
+            backend_last->next = backend;
+            backend_last = backend;
+        } else {
+            backend_first = backend;
+            backend_last = backend;
+        }
+    }
+    rq->davCollection = backend_first;
+    
+    return 0;
+}
+
+static int nametrans_set_dav_repository(pblock *pb, Session *sn, Request *rq) {
+    char *dav = pblock_findkeyval(pb_key_dav, pb);
+    if(!dav) return 0;
+    
+    ServerConfiguration *config = session_get_config(sn);
+    WebdavRepository *repo = ucx_map_cstr_get(config->dav, dav);
+    
+    if(!repo) {
+        log_ereport(LOG_MISCONFIG, "nametrans: unknown dav repository '%s'", dav);
+        return REQ_ABORTED;
+    }
+    
+    return initialize_dav_repo(pb, sn, rq, repo);
+}
+
+static int nametrans_set_vfs(pblock *pb, Session *sn, Request *rq) {
+    char *vfsclass = pblock_findkeyval(pb_key_vfsclass, pb);
+    if(!vfsclass) return 0;
+    
+    VFS *vfs = vfs_create(sn, rq, vfsclass, pb, NULL);
+    if(!vfs) {
+        return 1;
+    }
+    rq->vfs = vfs;
+    return 0;
+}
+
+static int nametrans_set_dav(pblock *pb, Session *sn, Request *rq) {
+    char *davclass = pblock_findkeyval(pb_key_davclass, pb);
+    if(!davclass) return 0;
+    
+    WebdavBackend *dav = webdav_create(sn, rq, davclass, pb, NULL);
+    
+    rq->davCollection = dav;
+    return 0;
+}
 
 /*
  * assign_name
@@ -66,6 +140,19 @@
             i++;
         }
     }
+    
+    if(nametrans_set_dav_repository(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "assign-name: cannot create dav repository: name=%s from=%s", name, from);
+        return REQ_ABORTED;
+    }
+    if(nametrans_set_vfs(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "assign-name: cannot create VFS: name=%s from=%s", name, from);
+        return REQ_ABORTED;
+    }
+    if(nametrans_set_dav(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "assign-name: cannot create Webdav Backend: name=%s from=%s", name, from);
+        return REQ_ABORTED;
+    }
 
     // add object to rq->vars
     pblock_kvinsert(pb_key_name, name, strlen(name), rq->vars);
@@ -89,6 +176,11 @@
         return REQ_ABORTED;
     }
     
+    if(nametrans_set_vfs(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "document-root: cannot create VFS");
+        return REQ_ABORTED;
+    }
+    
     sstr_t root_str = sstr(root);
     sstr_t uri_str = sstr(pblock_findkeyval(pb_key_uri, rq->reqpb));  
     
@@ -106,6 +198,9 @@
  * from     prefix
  * dir      file system directory
  * name     (optional) object name
+ * dav      (optional) dav repository name
+ * vfsclass (optional) vfs name
+ * davclass (optional) dav backend
  * 
  */
 int pfx2dir(pblock *pb, Session *sn, Request *rq) {
@@ -147,6 +242,19 @@
         uri++;
     }
     
+    if(nametrans_set_dav_repository(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "pfx2dir: cannot create dav repository: from=%s dir=%s name=%s", from, dir, name);
+        return REQ_ABORTED;
+    }
+    if(nametrans_set_vfs(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "pfx2dir: cannot create VFS: from=%s dir=%s name=%s", from, dir, name);
+        return REQ_ABORTED;
+    }
+    if(nametrans_set_dav(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "pfx2dir: cannot create Webdav Backend: from=%s dir=%s name=%s", from, dir, name);
+        return REQ_ABORTED;
+    }
+    
     request_set_path(sstr(dir), sstr(uri), rq->vars);
     
     if(name) {
@@ -192,6 +300,11 @@
         return REQ_ABORTED;
     }
     
+    if(nametrans_set_vfs(pb, sn, rq)) {
+        log_ereport(LOG_FAILURE, "simple-rewrite: cannot create VFS: from=%s root=%s path=%s name=%s", from, root, path, name);
+        return REQ_ABORTED;
+    }
+    
     char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
     sstr_t u = sstr(uri);
     sstr_t f = sstr(from);
--- a/src/server/safs/service.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/safs/service.c	Sat Sep 24 16:26:10 2022 +0200
@@ -85,7 +85,8 @@
     }
     
     // sets last-modified, content-length and checks conditions
-    if(http_set_finfo(sn, rq, s) != REQ_PROCEED) {
+    const char *etag = vfs_getetag(fd); // optionally, get etag from file
+    if(http_set_finfo_etag(sn, rq, s, etag) != REQ_PROCEED) {
         vfs_close(fd);
         return NULL;
     }
@@ -637,7 +638,7 @@
         // send response header
         http_start_response(sn, rq);
         // send content
-        // TODO: fix: send_range_aio is unstable
+        // TODO: fix: send_range_aio is unstable #96
         //ret = send_range_aio(sn, rq, fd, offset, length, NULL, 0);
         //if(ret == REQ_PROCESSING) {
         //    return ret;
--- a/src/server/test/Makefile	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/test/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -29,8 +29,8 @@
 TEST_CFLAGS = 
 
 $(TEST_OBJPRE)%.o: test/%.c
-	$(CC) -o $@ -c $(ADMIN_CFLAGS) $(CFLAGS) $<
+	$(CC) -o $@ -c $(TEST_CFLAGS) $(CFLAGS) $<
 
 $(TEST_OBJPRE)%.o: test/%.cpp
-	$(CXX) -o $@ -c $(ADMIN_CFLAGS) $(CFLAGS) $<
+	$(CXX) -o $@ -c $(TEST_CFLAGS) $(CFLAGS) $<
 	
--- a/src/server/test/main.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/test/main.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -38,11 +38,18 @@
 #include "../public/nsapi.h"
 #include "../util/plist.h"
 #include "../util/date.h"
+#include "../daemon/vfs.h"
 
-#include "../../ucx/string.h"
+#include <ucx/test.h>
 
-static int std_pipe_fds[2];
-static WSBool is_daemon;
+#include "vfs.h"
+#include "writer.h"
+#include "xml.h"
+#include "webdav.h"
+#include "uri.h"
+
+void register_pg_tests(int argc, char **argv, UcxTestSuite *suite);
+
 
 void test() {
     
@@ -50,16 +57,78 @@
 
 // needed for linking
 WSBool main_is_daemon(void) {
-    return is_daemon;
+    return 0;
 }
 
 int main(int argc, char **argv) {
     pool_init(NULL, NULL, NULL);
+    vfs_init();
     
     //test();
     
     printf("%s", "Webserver Test Suite\n====================\n\n");
-
+    
+    UcxTestSuite* suite = ucx_test_suite_new();
+    
+    // util tests
+    ucx_test_register(suite, test_util_uri_escape_alphanum);
+    ucx_test_register(suite, test_util_uri_escape_space);
+    ucx_test_register(suite, test_util_uri_escape_latin);
+    ucx_test_register(suite, test_util_uri_escape_kanji);
+    
+    // vfs tests
+    ucx_test_register(suite, test_vfs_open);
+    ucx_test_register(suite, test_vfs_mkdir);
+    ucx_test_register(suite, test_vfs_opendir);
+    ucx_test_register(suite, test_vfs_readdir);
+    ucx_test_register(suite, test_vfs_unlink);
+    ucx_test_register(suite, test_vfs_rmdir);
+    
+    // writer tests
+    ucx_test_register(suite, test_writer_putc);
+    ucx_test_register(suite, test_writer_flush);
+    ucx_test_register(suite, test_writer_put);
+    
+    // xml tests
+    ucx_test_register(suite, test_wsxml_iterator);
+    ucx_test_register(suite, test_wsxml_get_required_namespaces);
+    ucx_test_register(suite, test_wsxml_write_nodes);
+    ucx_test_register(suite, test_wsxml_nslist2string);
+    ucx_test_register(suite, test_wsxml_string2nslist);
+    
+    // webdav tests
+    ucx_test_register(suite, test_webdav_plist_add);
+    ucx_test_register(suite, test_webdav_plist_size);
+    ucx_test_register(suite, test_propfind_parse);
+    ucx_test_register(suite, test_proppatch_parse);
+    ucx_test_register(suite, test_lock_parse);
+    ucx_test_register(suite, test_rqbody2buffer);
+    ucx_test_register(suite, test_webdav_plist_iterator);
+    ucx_test_register(suite, test_webdav_plist_iterator_remove_current);
+    ucx_test_register(suite, test_msresponse_addproperty);
+    ucx_test_register(suite, test_webdav_propfind_init);
+    ucx_test_register(suite, test_webdav_op_propfind_begin);
+    ucx_test_register(suite, test_webdav_op_propfind_children);
+    ucx_test_register(suite, test_proppatch_msresponse);
+    ucx_test_register(suite, test_msresponse_addproperty_with_errors);
+    ucx_test_register(suite, test_webdav_op_proppatch);
+    ucx_test_register(suite, test_webdav_vfs_op_do);
+    ucx_test_register(suite, test_webdav_delete);
+       
+    // webdav methods
+    ucx_test_register(suite, test_webdav_propfind);
+    ucx_test_register(suite, test_webdav_proppatch);
+    ucx_test_register(suite, test_webdav_put);
+    
+    // plugin tests
+#ifdef ENABLE_POSTGRESQL
+    register_pg_tests(argc, argv, suite);
+#endif
+       
+    // 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	Sat Sep 24 16:26:10 2022 +0200
@@ -31,6 +31,12 @@
 TEST_OBJPRE = $(OBJ_DIR)$(TEST_SRC_DIR)
 
 TESTOBJ = main.o
+TESTOBJ += testutils.o
+TESTOBJ += webdav.o
+TESTOBJ += vfs.o
+TESTOBJ += xml.o
+TESTOBJ += writer.o
+TESTOBJ += uri.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	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,171 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ucx/string.h>
+#include <ucx/utils.h>
+
+#include "../util/pblock.h"
+
+#include "../util/io.h"
+
+#include "testutils.h"
+
+Session* testutil_session(void) {
+    pool_handle_t *pool = pool_create();
+    NSAPISession *sn = nsapisession_create(pool);
+    sn->connection = testutil_dummy_connection(pool);
+    
+    return &sn->sn;
+}
+
+Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri) {
+    NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest));
+    ZERO(rq, sizeof(NSAPIRequest));
+    
+    HTTPRequest httprequest;
+    ZERO(&httprequest, sizeof(HTTPRequest));
+    request_initialize(pool, &httprequest, rq);
+    
+    sstr_t clf = ucx_sprintf("%s %s HTTP/1.1", method, uri);
+    pblock_kvinsert(
+            pb_key_clf_request,
+            clf.ptr,
+            clf.length,
+            rq->rq.reqpb);
+    free(clf.ptr);
+    
+    pblock_nvinsert(
+            "method",
+            method,
+            rq->rq.reqpb);
+    
+    pblock_nvinsert(
+            "protocol",
+            "HTTP/1.1",
+            rq->rq.reqpb);
+    
+    pblock_nvinsert("uri", uri, rq->rq.reqpb);
+    
+    return &rq->rq;
+}
+
+static int dummyconn_read(Connection *conn, void *buf, int len) {
+    return len;
+}
+
+static int dummyconn_write(Connection *conn, const void *buf, int len) {
+    return len;
+}
+
+static void dummyconn_close(Connection *conn) {
+    
+}
+
+
+Connection* testutil_dummy_connection(pool_handle_t *pool) {
+    Connection *conn = pool_malloc(pool, sizeof(Connection));
+    ZERO(conn, sizeof(Connection));
+    conn->read = dummyconn_read;
+    conn->write = dummyconn_write;
+    conn->close = dummyconn_close; 
+    return conn;
+}
+
+void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len) {
+    sstr_t cl = ucx_sprintf("%d", (int)len);
+    pblock_nvreplace("content-length", cl.ptr, rq->headers);
+    free(cl.ptr);
+    
+    netbuf *inbuf = pool_malloc(sn->pool, sizeof(netbuf));
+    inbuf->sd = NULL;
+    inbuf->inbuf = pool_malloc(sn->pool, len);
+    inbuf->pos = 0;
+    inbuf->maxsize = len;
+    inbuf->cursize = len;
+    sn->inbuf = inbuf;
+    
+    memcpy(inbuf->inbuf, body, len);
+}
+
+void testutil_destroy_session(Session *sn) {
+    pool_destroy(sn->pool);
+}
+
+
+static ssize_t test_io_write(IOStream *io, void *buf, size_t size) {
+    TestIOStream *st = (TestIOStream*)io;
+    return ucx_buffer_write(buf, 1, size, st->buf);
+}
+
+static ssize_t test_io_writev(IOStream *io, struct iovec *iovec, int iovctn) {
+    return -1;
+}
+
+static ssize_t test_io_read(IOStream *io, void *buf, size_t size) {
+    return -1;
+}
+
+static void test_io_close(IOStream *io) {
+    
+}
+
+static void test_io_finish(IOStream *io) {
+    
+}
+
+static void test_io_setmode(IOStream *io, int mode) {
+    
+}
+
+static int test_io_poll(IOStream *io, EventHandler *ev, int events , Event *event) {
+    return 1;
+}
+
+TestIOStream* testutil_iostream(size_t size, int autoextend) {
+    TestIOStream *stream = calloc(1, sizeof(TestIOStream));
+    int flags = 0;
+    if(autoextend) {
+        flags = UCX_BUFFER_AUTOEXTEND;
+    }
+    stream->buf = ucx_buffer_new(NULL, size, flags);
+    
+    stream->io.st.write = test_io_write;
+    stream->io.st.writev = test_io_writev;
+    stream->io.st.close = test_io_close;
+    stream->io.st.finish = test_io_finish;
+    
+    return stream;
+}
+
+void testutil_iostream_destroy(TestIOStream *stream) {
+    ucx_buffer_free(stream->buf);
+    free(stream);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/testutils.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,66 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTUTILS_H
+#define TESTUTILS_H
+
+#include "../public/nsapi.h"
+#include "../daemon/httprequest.h"
+
+#include "../util/io.h"
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct TestIOStream {
+    HttpStream io;
+    UcxBuffer *buf;
+} TestIOStream;
+    
+Session* testutil_session(void);
+
+Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri);
+
+Connection* testutil_dummy_connection(pool_handle_t *pool);
+
+void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len);
+
+void testutil_destroy_session(Session *sn);
+
+TestIOStream* testutil_iostream(size_t size, int autoextend);
+void testutil_iostream_destroy(TestIOStream *stream);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTUTILS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/uri.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,108 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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 "uri.h"
+
+#include "../util/util.h"
+
+
+UCX_TEST(test_util_uri_escape_alphanum) {
+    char *str1 = "/test/path/abc/";
+    char str_enc[512];
+    
+    UCX_TEST_BEGIN;
+    
+    char *test = util_uri_escape(str_enc, str1);
+    UCX_TEST_ASSERT(test, "util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str1), "test != str1");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_util_uri_escape_space) {
+    char *str1 = "/test/space in path/";
+    char *str_enc_expected = "/test/space%20in%20path/";
+    char str_enc[512];
+    
+    UCX_TEST_BEGIN;
+    
+    char *test = util_uri_escape(str_enc, str1);
+    UCX_TEST_ASSERT(test, "util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str_enc_expected), "unexpected result");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_util_uri_escape_latin) {
+    char *str1 = "/test/path/öäütestß/";
+    char *str_enc_expected = "/test/path/%C3%B6%C3%A4%C3%BCtest%C3%9F/";
+    
+    char *str2 = "€";
+    char *str2_enc_expected = "%E2%82%AC";
+    
+    char str_enc[512];
+    
+    UCX_TEST_BEGIN;
+    
+    // test 1
+    char *test = util_uri_escape(str_enc, str1);
+    UCX_TEST_ASSERT(test, "util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str_enc_expected), "unexpected result");
+    
+    // test 2
+    test = util_uri_escape(str_enc, str2);
+    UCX_TEST_ASSERT(test, "(2) util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str2_enc_expected), "(2) unexpected result");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_util_uri_escape_kanji) {
+    char *str1 = "漢字";
+    char *str1_enc_expected = "%E6%BC%A2%E5%AD%97";
+    
+    char *str2 = "/test/エンコーディング/漢字/";
+    char *str2_enc_expected = "/test/%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0/%E6%BC%A2%E5%AD%97/";
+    
+    char str_enc[512];
+    
+    UCX_TEST_BEGIN;
+    
+    // test 1
+    char *test = util_uri_escape(str_enc, str1);
+    UCX_TEST_ASSERT(test, "util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str1_enc_expected), "unexpected result");
+    
+    // test 2
+    test = util_uri_escape(str_enc, str2);
+    UCX_TEST_ASSERT(test, "(2) util_uri_escape returned NULL");
+    UCX_TEST_ASSERT(!strcasecmp(test, str2_enc_expected), "(2) unexpected result");
+    
+    UCX_TEST_END;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/uri.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 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_URI_H
+#define TEST_URI_H
+
+#include "../public/nsapi.h"
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_util_uri_escape_alphanum);
+UCX_TEST(test_util_uri_escape_space);
+UCX_TEST(test_util_uri_escape_latin);
+UCX_TEST(test_util_uri_escape_kanji);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_URI_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/vfs.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,558 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <ucx/string.h>
+#include <ucx/list.h>
+#include <ucx/map.h>
+
+#include "../daemon/session.h"
+
+#include "testutils.h"
+
+#include "vfs.h"
+
+typedef struct TestVFS {
+    UcxMap *files;
+    int count_unlink;
+    int count_rmdir;
+} TestVFS;
+
+typedef struct TestVFSFile {
+    VFSFile file;
+    sstr_t path;
+    int isdir;
+    UcxBuffer *content;
+} TestVFSFile;
+
+typedef struct TestVFSDir {
+    VFSDir dir;
+    TestVFSFile *file;
+    UcxMapIterator i;
+    sstr_t name;
+} TestVFSDir;
+
+/* dir io */
+
+static char* test_resource_name(char *url) {
+    sstr_t urlstr = sstr(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    sstr_t resname = sstrrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) {
+    TestVFS *vfs = dir->ctx->vfs->instance;
+    TestVFSDir *vfsdir = (TestVFSDir*)dir;
+    
+    sstr_t prefix = sstrcat(2, vfsdir->file->path, S("/"));
+    
+    TestVFSFile *file = NULL;
+    UCX_MAP_FOREACH(key, file, vfsdir->i) {
+        sstr_t file_path = sstrcat(
+                2,
+                prefix,
+                sstr(test_resource_name(file->path.ptr)));
+        void *m = ucx_map_get(vfs->files, ucx_key(file_path.ptr, file_path.length));
+        // don't ask why alfree and not free()
+        alfree(ucx_default_allocator(), file_path.ptr);
+        if(m) {
+            break;
+        } else {
+            file = NULL;
+        }
+    }
+    free(prefix.ptr);
+    
+    if(file) {
+        vfsdir->name = sstrdup_a(
+                session_get_allocator(dir->ctx->sn),
+                sstr(test_resource_name(file->path.ptr)));
+        ZERO(entry, sizeof(VFS_ENTRY));
+        entry->name = vfsdir->name.ptr;
+        
+        if(getstat) {
+            ZERO(&entry->stat, sizeof(struct stat));
+            if(file->isdir) {
+                entry->stat.st_mode = S_IFDIR;
+            }
+        }
+        
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+void testvfs_dir_close(VFS_DIR dir) {
+    TestVFSDir *testdir = (TestVFSDir*)dir;
+    pool_free(testdir->dir.ctx->sn->pool, dir);
+    
+}
+
+ ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     return (ssize_t)ucx_buffer_read(buf, 1, nbyte, file->content);
+ }
+ 
+ ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     return (ssize_t)ucx_buffer_write(buf, 1, nbyte, file->content);
+ }
+ 
+ ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = (size_t)offset;
+     return testvfs_read(fd, buf, nbyte);
+ }
+ 
+ ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = (size_t)offset;
+     return testvfs_write(fd, buf, nbyte);
+ }
+ 
+ off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     ucx_buffer_seek(file->content, offset, whence);
+     return (off_t)file->content->pos;
+ }
+ 
+ void testvfs_close(SYS_FILE fd) {
+     TestVFSFile *file = (TestVFSFile*)fd;
+     file->content->pos = 0;
+ }
+
+VFS_IO test_file_io = {
+    testvfs_read,
+    testvfs_write,
+    testvfs_pread,
+    testvfs_pwrite,
+    testvfs_seek,
+    testvfs_close,
+    NULL, /* aio_read */
+    NULL  /* aio_write */
+};
+
+VFS_DIRIO test_dir_io = {
+    testvfs_readdir,
+    testvfs_dir_close
+};
+
+
+/* vfs funcs */
+
+SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        if((oflags & O_CREAT) == O_CREAT) {
+            file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile));
+            ZERO(file, sizeof(TestVFSFile));
+            file->file.ctx = ctx;
+            file->path = sstrdup_a(session_get_allocator(ctx->sn), s_path);
+            file->file.io = &test_file_io;
+            
+            file->content = pool_calloc(ctx->sn->pool, 1, sizeof(UcxBuffer));
+            file->content->capacity = 2048;
+            file->content->space = pool_malloc(ctx->sn->pool, file->content->capacity);
+            file->content->flags = 0;
+            file->content->pos = 0;
+            file->content->size = 0;
+            
+            ucx_map_sstr_put(vfs->files, s_path, file);
+        } else {
+            ctx->vfs_errno = ENOENT;
+        }
+    }
+    
+    return (SYS_FILE)file;
+}
+
+int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        ctx->vfs_errno = ENOENT;
+        return 1;
+    }
+    
+    ZERO(buf, sizeof(struct stat));
+    if(file->isdir) {
+        buf->st_mode = S_IFDIR;
+    }
+    
+    return 0;
+}
+
+int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) {
+    return 0;
+}
+
+VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = NULL;
+    
+    sstr_t s_path = sstr((char*)path);
+    if(sstrsuffix(s_path, S("/"))) {
+        s_path.length--;
+    }
+    
+    file = ucx_map_sstr_get(vfs->files, s_path);
+    if(!file) {
+        ctx->vfs_errno = ENOENT;
+        return NULL;
+    }
+    
+    if(!file->isdir) {
+        return NULL;
+    }
+    
+    TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir));
+    ZERO(dir, sizeof(TestVFSDir));
+    dir->file = file;
+    dir->i = ucx_map_iterator(vfs->files);
+    
+    dir->dir.ctx = ctx;
+    dir->dir.io = &test_dir_io;
+    
+    return (VFS_DIR)dir;
+}
+
+VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
+    TestVFS *vfs = ctx->vfs->instance;  
+    TestVFSFile *file = (TestVFSFile*)fd;
+    if(!file->isdir) {
+        return NULL;
+    }
+    
+    TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir));
+    ZERO(dir, sizeof(TestVFSDir));
+    dir->file = file;
+    dir->i = ucx_map_iterator(vfs->files);
+    
+    dir->dir.ctx = ctx;
+    dir->dir.io = &test_dir_io;
+    
+    return (VFS_DIR)dir;
+}
+
+int testvfs_mkdir(VFSContext *ctx, const char *path) {
+    SYS_FILE fd = testvfs_open(ctx, path, O_CREAT);
+    if(!fd) {
+        return 1;
+    }
+    
+    TestVFSFile *file = (TestVFSFile*)fd;
+    file->isdir = 1;
+    
+    return 0;
+}
+
+int testvfs_unlink(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;
+    TestVFSFile *file = ucx_map_cstr_get(vfs->files, path);
+    if(!file) {
+        return 1;
+    }
+    
+    if(file->isdir) {
+        return 1;
+    }
+    
+    ucx_map_cstr_remove(vfs->files, path);
+    vfs->count_unlink++;
+    return 0;
+}
+
+int testvfs_rmdir(VFSContext *ctx, const char *path) {
+    TestVFS *vfs = ctx->vfs->instance;
+    TestVFSFile *dir = ucx_map_cstr_get(vfs->files, path);
+    if(!dir) {
+        ctx->vfs_errno = ENOENT;
+        return 1;
+    }
+    
+    if(!dir->isdir) {
+        return 1;
+    }
+    
+    UcxMapIterator i = ucx_map_iterator(vfs->files);
+    TestVFSFile *f;
+    UCX_MAP_FOREACH(key, f, i) {
+        if(f->path.length > dir->path.length && sstrprefix(f->path, dir->path)){
+            return 1; // dir not empty
+        }
+    }
+    
+    ucx_map_cstr_remove(vfs->files, path);
+    vfs->count_rmdir++;
+    return 0;
+}
+
+static VFS testVFSClass = {
+    testvfs_open,
+    testvfs_stat,
+    testvfs_fstat,
+    testvfs_opendir,
+    testvfs_fdopendir,
+    testvfs_mkdir,
+    testvfs_unlink,
+    testvfs_rmdir,
+    0,
+    NULL
+};
+
+
+VFS* testvfs_create(Session *sn) {
+    TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS));
+    vfs->count_unlink = 0;
+    vfs->count_rmdir = 0;
+    vfs->files = ucx_map_new_a(session_get_allocator(sn), 64);
+    
+    testVFSClass.instance = vfs;
+    return &testVFSClass;
+}
+
+
+/* ------------------------------------------------------------------------- */
+//
+// VFS Tests
+//
+/* ------------------------------------------------------------------------- */
+
+UCX_TEST(test_vfs_open) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    UCX_TEST_ASSERT(vfs, "vfs is NULL");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    SYS_FILE f2 = vfs_open(vfs, "/file1", 0);
+    UCX_TEST_ASSERT(f2, "f2 not opened");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_mkdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    SYS_FILE fd = vfs_open(vfs, "/dir", 0);
+    UCX_TEST_ASSERT(fd, "no fd");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_opendir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    VFSDir *dir = vfs_opendir(vfs, "/dir");
+    UCX_TEST_ASSERT(dir, "no dir");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_readdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    int err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    // add some test file to /dir
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed");
+    
+    VFSDir *dir = vfs_opendir(vfs, "/dir");
+    UCX_TEST_ASSERT(dir, "dir not opened");
+    
+    UcxMap *files = ucx_map_new(8);
+    
+    VFSEntry entry;
+    while(vfs_readdir(dir, &entry)) {
+        ucx_map_cstr_put(files, entry.name, dir);
+    }
+    
+    UCX_TEST_ASSERT(files->count == 4, "wrong files count");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file1"), "file1 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file2"), "file2 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file3"), "file3 missing");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file4"), "file4 missing");
+    
+    ucx_map_free(files);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_unlink) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    // prepare test
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f2 not opened");
+    
+    SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f3 not opened");
+    
+    // test unlink
+    err = vfs_unlink(vfs, "/file1");
+    UCX_TEST_ASSERT(err == 0, "unlink /file1 failed");
+    err = vfs_unlink(vfs, "/dir1/file3");
+    UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed");
+    
+    err = vfs_unlink(vfs, "/filex");
+    UCX_TEST_ASSERT(err != 0, "unlink /filex should fail");
+    
+    // check if files were removed
+    SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY);
+    UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted");
+    SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY);
+    UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted");
+    
+    // file2 should still be there
+    SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY);
+    UCX_TEST_ASSERT(o2, "/file2 deleted");
+    
+    // check if dir unlink fails
+    err = vfs_unlink(vfs, "/dir1");
+    UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_vfs_rmdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = testvfs_create(sn);
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    // prepare test
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0");
+    
+    SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT);
+    UCX_TEST_ASSERT(f1, "f1 not opened");
+    
+    err = vfs_rmdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail");
+    err = vfs_rmdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed");
+    
+    err = vfs_unlink(vfs, "/dir1/file1");
+    UCX_TEST_ASSERT(err == 0, "unlink failed");
+    err = vfs_rmdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/vfs.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,55 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEST_VFS_H
+#define TEST_VFS_H
+
+#include "../public/nsapi.h"
+#include "../public/vfs.h"
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VFS* testvfs_create(Session *sn);
+
+UCX_TEST(test_vfs_open);
+UCX_TEST(test_vfs_mkdir);
+UCX_TEST(test_vfs_opendir);
+UCX_TEST(test_vfs_readdir);
+UCX_TEST(test_vfs_unlink);
+UCX_TEST(test_vfs_rmdir);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_VFS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,1767 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "testutils.h"
+
+#include "../webdav/requestparser.h"
+#include "../webdav/webdav.h"
+#include "../webdav/multistatus.h"
+#include "../webdav/operation.h"
+
+#include "vfs.h"
+#include "webdav.h"
+
+static int webdav_is_initialized = 0;
+
+/* ----------------------------- Test Backends --------------------------*/
+
+static int backend2_init_called = 0;
+static int backend2_propfind_do_count = 0;
+static int backend2_propfind_finish_called = 0;
+static int backend2_proppatch_commit = 0;
+static int backend2_proppatch_do_count = 0;
+static int backend2_proppatch_finish_count = 0;
+
+// backend2
+static int backend2_propfind_init(
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *href,
+        WebdavPList **outPList)
+{
+    backend2_init_called = 1;
+    return 0;
+}
+
+static int backend2_propfind_do(
+            WebdavPropfindRequest *propfind,
+            WebdavResponse *response,
+            VFS_DIR parent,
+            WebdavResource *resource,
+            struct stat *s)
+{
+    backend2_propfind_do_count++;
+    return 0;
+}
+
+static int backend2_propfind_finish(WebdavPropfindRequest *propfind) {
+    backend2_propfind_finish_called = 1;
+    return 0;
+}
+
+static int backend2_proppatch_do(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WebdavPList **out_set,
+        WebdavPList **out_remove)
+{
+    backend2_proppatch_do_count++;
+    
+    if(*out_remove) {
+        return 1; // backend1 should remove all remove-props
+    }
+    
+    WebdavPListIterator i = webdav_plist_iterator(out_set);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(!strcmp(cur->property->name, "a")) {
+            // property 'a' should already be removed by backend1
+            return 1;
+        } else if(!strcmp(cur->property->name, "abort")) {
+            return 1; // test abort
+        }
+        response->addproperty(response, cur->property, 200);
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    return 0;
+}
+
+static int backend2_proppatch_finish(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WSBool commit)
+{
+    backend2_proppatch_finish_count++;
+    backend2_proppatch_commit = commit;
+    return 0;
+}
+
+static WebdavBackend backend2 = {
+    backend2_propfind_init,
+    backend2_propfind_do,
+    backend2_propfind_finish,
+    backend2_proppatch_do,
+    backend2_proppatch_finish,
+    NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
+    NULL, // opt_delete
+    NULL, // opt_delete_finish
+    0,
+    NULL, // instance
+    NULL
+};
+
+// backend1
+
+static int backend1_init_called = 0;
+static int backend1_propfind_do_count = 0;
+static int backend1_propfind_finish_called = 0;
+static int backend1_proppatch_commit = 0;
+static int backend1_proppatch_do_count = 0;
+static int backend1_proppatch_finish_count = 0;
+
+
+static int backend1_propfind_init(
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *href,
+        WebdavPList **outPList)
+{
+    backend1_init_called = 1;
+    
+    WebdavPList *plist = *outPList;
+    WebdavProperty *p = plist->property;
+    if(!strcmp(p->name, "displayname")) {
+        plist->next->prev = NULL;
+        *outPList = plist->next; // remove first item from plist
+    } else {
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int backend1_propfind_do(
+            WebdavPropfindRequest *propfind,
+            WebdavResponse *response,
+            VFS_DIR parent,
+            WebdavResource *resource,
+            struct stat *s)
+{
+    backend1_propfind_do_count++;
+    return 0;
+}
+
+static int backend1_propfind_finish(WebdavPropfindRequest *propfind) {
+    backend1_propfind_finish_called = 1;
+    return 0;
+}
+
+static int backend1_proppatch_do(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WebdavPList **out_set,
+        WebdavPList **out_remove)
+{
+    backend1_proppatch_do_count++;
+    
+    // remove everything from out_remove
+    WebdavPListIterator i = webdav_plist_iterator(out_remove);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        response->addproperty(response, cur->property, 200);
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    // remove property 'a' and fail at property 'fail'
+    i = webdav_plist_iterator(out_set);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(!strcmp(cur->property->name, "fail")) {
+            response->addproperty(response, cur->property, 403);
+            webdav_plist_iterator_remove_current(&i);
+        } else if(!strcmp(cur->property->name, "a")) {
+            response->addproperty(response, cur->property, 200);
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+        
+    return 0;
+}
+
+static int backend1_proppatch_finish(
+        WebdavProppatchRequest *request,
+        WebdavResource *response,
+        VFSFile *file,
+        WSBool commit)
+{
+    backend1_proppatch_finish_count++;
+    backend1_proppatch_commit = commit;
+    return 0;
+}
+
+WebdavBackend backend1 = {
+    backend1_propfind_init,
+    backend1_propfind_do,
+    backend1_propfind_finish,
+    backend1_proppatch_do,
+    backend1_proppatch_finish,
+    NULL, // opt_mkcol
+    NULL, // opt_mkcol_finish
+    NULL, // opt_delete
+    NULL, // opt_delete_finish
+    0,
+    NULL, // instance
+    &backend2
+};
+
+static void reset_backends(void) {
+    backend1_init_called = 0;
+    backend1_propfind_do_count = 0;
+    backend1_propfind_finish_called = 0;
+    backend1_proppatch_commit = 0;
+    backend1_proppatch_do_count = 0;
+    backend1_proppatch_finish_count = 0;
+    backend2_init_called = 0;
+    backend2_propfind_do_count = 0;
+    backend2_propfind_finish_called = 0;
+    backend2_proppatch_commit = 0;
+    backend2_proppatch_do_count = 0;
+    backend2_proppatch_finish_count = 0;
+}
+
+/* ----------------------------------------------------------------------*/
+
+
+static int test_init(
+        Session **out_sn,
+        Request **out_rq,
+        WebdavPropfindRequest **out_propfind,
+        const char *xml)
+{
+    if(!webdav_is_initialized) {
+        if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) {
+            return 1;
+        }
+        webdav_is_initialized = 1;
+    }
+    
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    int error = 0;
+    
+    WebdavPropfindRequest *propfind = propfind_parse(
+            sn,
+            rq,
+            xml,
+            strlen(xml),
+            &error);
+    
+    if(error) {
+        return 1;
+    }
+    
+    if(!propfind || !propfind->properties) {
+        return 1;
+    }
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_propfind = propfind;
+    return 0;
+}
+
+static WebdavOperation* test_propfind_op(
+        Session **out_sn,
+        Request **out_rq,
+        const char *xml)
+{
+    WebdavPropfindRequest *propfind;
+    if(test_init(out_sn, out_rq, &propfind, xml)) {
+        return NULL;
+    }
+    
+    Multistatus *ms = multistatus_response(*out_sn, *out_rq);
+    if(!ms) {
+        return NULL;
+    }
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    UcxList *requests = NULL;
+    
+    // Initialize all Webdav Backends
+    if(webdav_propfind_init(&backend1, propfind, "/", "/", &requests)) {
+        return NULL;
+    }
+    
+    return webdav_create_propfind_operation(
+            (*out_sn),
+            (*out_rq),
+            &backend1,
+            propfind->properties,
+            requests,
+            response);
+}
+
+
+UCX_TEST(test_webdav_plist_add) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavPList *begin = NULL;
+    WebdavPList *end = NULL;
+    
+    WebdavProperty p1, p2, p3;
+    ZERO(&p1, sizeof(WebdavProperty));
+    ZERO(&p2, sizeof(WebdavProperty));
+    ZERO(&p3, sizeof(WebdavProperty));
+    int r;
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p1);
+    
+    UCX_TEST_ASSERT(r == 0, "add 1 failed");
+    UCX_TEST_ASSERT(begin && end, "ptrs are NULL");
+    UCX_TEST_ASSERT(begin == end, "begin != end");
+    UCX_TEST_ASSERT(begin->prev == NULL, "begin->prev not NULL");
+    UCX_TEST_ASSERT(begin->next == NULL, "begin->next not NULL");
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p2);
+    
+    UCX_TEST_ASSERT(r == 0, "add 2 failed");
+    UCX_TEST_ASSERT(begin && end, "add2: ptrs are NULL");
+    UCX_TEST_ASSERT(begin->next, "begin->next is NULL");
+    UCX_TEST_ASSERT(begin->next == end, "begin->next != end");
+    UCX_TEST_ASSERT(end->prev = begin, "end->prev != begin");
+    UCX_TEST_ASSERT(begin->prev == NULL, "add2: begin->prev not NULL");
+    UCX_TEST_ASSERT(end->next == NULL, "add2: end->next not NULL");
+    
+    r = webdav_plist_add(sn->pool, &begin, &end, &p3);
+    
+    UCX_TEST_ASSERT(r == 0, "add 3 failed");
+    UCX_TEST_ASSERT(begin && end, "add3: ptrs are NULL");
+    UCX_TEST_ASSERT(begin->next == end->prev, "begin->next != end->prev");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_plist_size) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavPList *begin = NULL;
+    WebdavPList *end = NULL;
+    
+    WebdavProperty p1, p2, p3;
+    ZERO(&p1, sizeof(WebdavProperty));
+    ZERO(&p2, sizeof(WebdavProperty));
+    ZERO(&p3, sizeof(WebdavProperty));
+    int r;
+    
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 0, "size != 0");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p1);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 1, "size != 1");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p2);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 2, "size != 2");
+    r = webdav_plist_add(sn->pool, &begin, &end, &p3);
+    UCX_TEST_ASSERT(webdav_plist_size(begin) == 3, "size != 3");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_propfind_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    UCX_TEST_BEGIN
+    
+    int error = 0;
+    
+    //
+    // ----------------- TEST_PROPFIND1 -----------------
+    // test basic propfind request
+    WebdavPropfindRequest *p1 = propfind_parse(
+            sn,
+            rq,
+            TEST_PROPFIND1,
+            strlen(TEST_PROPFIND1),
+            &error);
+    
+    UCX_TEST_ASSERT(p1, "p1 is NULL");
+    UCX_TEST_ASSERT(p1->properties, "p1: no props");
+    UCX_TEST_ASSERT(!p1->allprop, "p1: allprop is TRUE");
+    UCX_TEST_ASSERT(!p1->propname, "p1: propname is TRUE");
+    UCX_TEST_ASSERT(p1->propcount == 6, "p1: wrong propcount");
+    
+    // property 1: DAV:displayname
+    WebdavPList *elm = p1->properties;
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "displayname"),
+            "p1: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 1 has wrong namespace");
+    
+    // property 2: DAV:getcontentlength
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 2 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "getcontentlength"),
+            "p1: property 2 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 2 has wrong namespace");
+    
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 3 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 4 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 5 missing");
+    
+    // property 6: DAV:getetag
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p1: property 6 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "getetag"),
+            "p1: property 6 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p1: property 6 has wrong namespace");
+    UCX_TEST_ASSERT(!elm->next, "p1: should not have property 7");
+    
+    //
+    // ----------------- TEST_PROPFIND2 -----------------
+    // test with multiple namespaces
+    WebdavPropfindRequest *p2 = propfind_parse(
+            sn,
+            rq,
+            TEST_PROPFIND2,
+            strlen(TEST_PROPFIND2),
+            &error);
+    
+    UCX_TEST_ASSERT(p2, "p2 is NULL");
+    UCX_TEST_ASSERT(p2->properties, "p2: no props");
+    UCX_TEST_ASSERT(!p2->allprop, "p2: allprop is TRUE");
+    UCX_TEST_ASSERT(!p2->propname, "p2: propname is TRUE");
+    
+    // property 1: DAV:resourcetype
+    elm = p2->properties;
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "resourcetype"),
+            "p2: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p2: property 1 has wrong namespace");
+    
+    // property 2: X:testprop
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 2 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "testprop"),
+            "p2: property 2 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "http://example.com/"),
+            "p2: property 2 has wrong namespace");
+    
+    // property 3: X:name
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 3 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "name"),
+            "p2: property 3 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "http://example.com/"),
+            "p2: property 3 has wrong namespace");
+    
+    // property 4: Z:testprop
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p2: property 4 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "testprop"),
+            "p2: property 4 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "testns"),
+            "p2: property 4 has wrong namespace");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND3 -----------------
+    // test allprop
+    WebdavPropfindRequest *p3 = propfind_parse(sn, rq, TEST_PROPFIND3, strlen(TEST_PROPFIND3), &error);
+    
+    UCX_TEST_ASSERT(p3, "p3 is NULL");
+    UCX_TEST_ASSERT(!p3->properties, "p2: has props");
+    UCX_TEST_ASSERT(p3->allprop, "p2: allprop is FALSE");
+    UCX_TEST_ASSERT(!p3->propname, "p2: propname is TRUE");
+    UCX_TEST_ASSERT(p3->propcount == 0, "p2: wrong propcount");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND4 -----------------
+    // test propname
+    WebdavPropfindRequest *p4 = propfind_parse(sn, rq, TEST_PROPFIND4, strlen(TEST_PROPFIND4), &error);
+    
+    UCX_TEST_ASSERT(p4, "p4 is NULL");
+    UCX_TEST_ASSERT(!p4->properties, "p2: has props");
+    UCX_TEST_ASSERT(!p4->allprop, "p2: allprop is TRUE");
+    UCX_TEST_ASSERT(p4->propname, "p2: propname is FALSE");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND5 -----------------
+    // test duplicate check
+    WebdavPropfindRequest *p5 = propfind_parse(sn, rq, TEST_PROPFIND5, strlen(TEST_PROPFIND5), &error);
+    
+    UCX_TEST_ASSERT(p5, "p5 is NULL");
+    UCX_TEST_ASSERT(p5->properties, "p5: no props");
+    UCX_TEST_ASSERT(!p5->allprop, "p5: allprop is TRUE");
+    UCX_TEST_ASSERT(!p5->propname, "p5: propname is TRUE");
+    UCX_TEST_ASSERT(p5->propcount == 4, "p5: wrong propcount");
+    
+    // property 1: DAV:displayname
+    elm = p5->properties;
+    UCX_TEST_ASSERT(elm, "p5: property 1 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "displayname"),
+            "p5: property 1 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p5: property 1 has wrong namespace");
+    
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 2 missing");
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 3 missing");
+    
+    // property 4: DAV:resourcetype
+    elm = elm->next;
+    UCX_TEST_ASSERT(elm, "p5: property 4 missing");
+    UCX_TEST_ASSERT(
+            !strcmp(elm->property->name, "resourcetype"),
+            "p5: property 4 has wrong name");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)elm->property->namespace->href, "DAV:"),
+            "p5: property 4 has wrong namespace");
+    
+    
+    //
+    // ----------------- TEST_PROPFIND6 -----------------
+    // test prop/allprop mix
+    WebdavPropfindRequest *p6 = propfind_parse(sn, rq, TEST_PROPFIND6, strlen(TEST_PROPFIND6), &error);
+    
+    UCX_TEST_ASSERT(p6, "p5 is NULL");
+    UCX_TEST_ASSERT(!p6->properties, "p5: has props");
+    UCX_TEST_ASSERT(p6->allprop, "p5: allprop is FALSE");
+    UCX_TEST_ASSERT(!p6->propname, "p5: propname is TRUE");
+    UCX_TEST_ASSERT(p6->propcount == 0, "p5: wrong propcount");
+    
+    UCX_TEST_END
+            
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_proppatch_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPPATCH", "/");
+    
+    UCX_TEST_BEGIN
+    int error = 0;
+    
+    WebdavProppatchRequest *p1 = proppatch_parse(sn, rq, TEST_PROPPATCH1, strlen(TEST_PROPPATCH1), &error);
+    
+    UCX_TEST_ASSERT(p1->set, "p1: missing set props");
+    UCX_TEST_ASSERT(!p1->remove, "p1: has remove props");
+    UCX_TEST_ASSERT(p1->setcount == 2, "p1: wrong setcount");
+    UCX_TEST_ASSERT(p1->set->next, "p1: set plist broken");
+    UCX_TEST_ASSERT(!p1->set->next->next, "p1: set plist has no end");
+    UCX_TEST_ASSERT(p1->set->property, "p1: missing property ptr in plist");
+    UCX_TEST_ASSERT(
+            !strcmp(p1->set->property->name, "test"),
+            "p1: wrong property 1 name");
+    
+    WebdavProppatchRequest *p2 = proppatch_parse(sn, rq, TEST_PROPPATCH2, strlen(TEST_PROPPATCH2), &error);
+    
+    UCX_TEST_ASSERT(p2->set, "p2: missing set props");
+    UCX_TEST_ASSERT(p2->remove, "p2: missing remove props");
+    UCX_TEST_ASSERT(p2->setcount == 4, "p2: wrong setcount");
+    UCX_TEST_ASSERT(p2->removecount == 1, "p2: wrong removecount");
+    
+    UCX_TEST_ASSERT(
+            !strcmp((char*)p2->set->property->namespace->href, "http://example.com/"),
+            "p2: set property 1: wrong namespace");
+    UCX_TEST_ASSERT(
+            !strcmp(p2->set->property->name, "a"),
+            "p2: set property 1: wrong name");
+    WSXmlNode *p2set1 = p2->set->property->value.node;
+    UCX_TEST_ASSERT(
+            p2set1->type == WS_NODE_TEXT,
+            "p2: set property 1: wrong type");
+    UCX_TEST_ASSERT(
+            p2set1->content,
+            "p2: set property 1: no text");
+    UCX_TEST_ASSERT(
+            !strcmp((char*)p2set1->content, "test"),
+            "p2: set property 1: wrong value");
+    
+    WSXmlNode *p2set3 = p2->set->next->next->property->value.node;
+    UCX_TEST_ASSERT(p2set3, "p2: set property 3 missing");
+    UCX_TEST_ASSERT(
+            p2set3->type == WS_NODE_TEXT,
+            "p2: set property 3: wrong type");
+    UCX_TEST_ASSERT(
+            p2set3->next,
+            "p2: set property 3: missing element X:name");
+    
+    UCX_TEST_ASSERT(
+            xmlHasProp(p2set3->next, BAD_CAST"test"),
+            "p2: set property 3: missing attribute 'test'");
+    
+    UCX_TEST_ASSERT(
+            xmlHasProp(p2set3->next, BAD_CAST"abc"),
+            "p2: set property 3: missing attribute 'abc");
+    
+    xmlChar *value1 = xmlGetProp(p2set3->next, BAD_CAST"test");
+    UCX_TEST_ASSERT(
+            !strcmp((char*) value1, "test1"),
+            "p2: set property 3: wrong attribute value 1");
+    xmlFree(value1);
+    
+    xmlChar *value2 = xmlGetProp(p2set3->next, BAD_CAST"abc");
+    UCX_TEST_ASSERT(
+            !strcmp((char*) value2, "def"),
+            "p2: set property 3: wrong attribute value 2");
+    xmlFree(value2);
+    
+    UCX_TEST_ASSERT(
+            !strcmp(p2->remove->property->name, "e"),
+            "p2: wrong remove property");
+    
+    UCX_TEST_END
+            
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_lock_parse) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "LOCK", "/");
+    
+    UCX_TEST_BEGIN
+    int error = 0;
+    
+    WebdavLockRequest *l1 = lock_parse(sn, rq, TEST_LOCK1, strlen(TEST_LOCK1), &error);
+    
+    UCX_TEST_ASSERT(l1, "l1 is NULL");
+    UCX_TEST_ASSERT(l1->type == WEBDAV_LOCK_WRITE, "l1: wrong type");
+    UCX_TEST_ASSERT(l1->scope == WEBDAV_LOCK_SHARED, "l1: wrong scope");
+    UCX_TEST_ASSERT(l1->owner, "l1: owner is NULL");
+    UCX_TEST_ASSERT(!strcmp((char*)l1->owner->content, "User"), "l1: wrong owner");
+    
+    UCX_TEST_END
+    
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_rqbody2buffer) {
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    //
+    // TEST 1
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, "PUT", "/");
+    testutil_request_body(sn, rq, "Hello World!", 12);
+    
+    UcxBuffer *b1 = rqbody2buffer(sn, rq);
+    UCX_TEST_ASSERT(b1->size == 12, "b1: wrong size");
+    UCX_TEST_ASSERT(!memcmp(b1->space,"Hello World!",12), "b1: wrong content");
+    
+    ucx_buffer_free(b1);
+    testutil_destroy_session(sn);
+    
+    //
+    // TEST 2
+    size_t len1 = 25000;
+    unsigned char *body1 = malloc(len1);
+    for(int i=0;i<len1;i++) {
+        body1[i] = i;
+    }
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, "PUT", "/");
+    testutil_request_body(sn, rq, (char*)body1, len1);
+    
+    UcxBuffer *b2 = rqbody2buffer(sn, rq);
+    UCX_TEST_ASSERT(b2->size == len1, "b2: wrong size");
+    UCX_TEST_ASSERT(!memcmp(b2->space, body1, len1), "b2: wrong content");
+    
+    ucx_buffer_free(b2);
+    testutil_destroy_session(sn);
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_plist_iterator) {
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    WebdavPList *properties = propfind->properties;
+    size_t count = 0;
+    
+    WebdavPListIterator i = webdav_plist_iterator(&properties);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        switch(i.index) {
+            case 0: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "displayname"), "wrong property 1");
+                break;
+            }
+            case 1: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontentlength"), "wrong property 2");
+                break;
+            }
+            case 2: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontenttype"), "wrong property 3");
+                break;
+            }
+            case 3: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getlastmodified"), "wrong property 4");
+                break;
+            }
+            case 4: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "resourcetype"), "wrong property 5");
+                break;
+            }
+            case 5: {
+                UCX_TEST_ASSERT(!strcmp(cur->property->name, "getetag"), "wrong property 6");
+                break;
+            }
+        }
+        count++;
+    }
+    
+    UCX_TEST_ASSERT(count == propfind->propcount, "wrong count");
+    
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_plist_iterator_remove_current) {
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    WebdavPList *properties1 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties2 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties3 = webdav_plist_clone(sn->pool, propfind->properties);
+    WebdavPList *properties4 = webdav_plist_clone(sn->pool, propfind->properties);
+    
+    WebdavPListIterator i;
+    WebdavPList *cur;
+    
+    // test removal of first element
+    i = webdav_plist_iterator(&properties1);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 0) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(!properties1->prev, "test1: prev not cleared");
+    UCX_TEST_ASSERT(!strcmp(properties1->property->name, "getcontentlength"), "test1: wrong property");
+    UCX_TEST_ASSERT(!strcmp(properties1->next->property->name, "getcontenttype"), "test1: wrong property 2");
+    UCX_TEST_ASSERT(properties1->next->prev == properties1, "test1: wrong link");
+    
+    // test removal of second element
+    i = webdav_plist_iterator(&properties2);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 1) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(!strcmp(properties2->next->property->name, "getcontenttype"), "test2: wrong property");
+    UCX_TEST_ASSERT(properties2->next->prev == properties2, "test2: wrong link");
+    UCX_TEST_ASSERT(webdav_plist_size(properties2) == 5, "test2: wrong size");
+    
+    // remove last element
+    i = webdav_plist_iterator(&properties3);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        if(i.index == 5) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    UCX_TEST_ASSERT(webdav_plist_size(properties3) == 5, "test3: wrong size");
+    UCX_TEST_ASSERT(!strcmp(properties3->next->next->next->next->property->name, "resourcetype"), "test2: wrong property");
+    
+    // remove all elements
+    i = webdav_plist_iterator(&properties4);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        webdav_plist_iterator_remove_current(&i);
+        switch(i.index) {
+            case 0: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontentlength"), "test4: wrong property 2");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (0)");
+                break;
+            }
+            case 1: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontenttype"), "test4: wrong property 3");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (1)");
+                break;
+            }
+            case 2: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getlastmodified"), "test4: wrong property 4");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (2)");
+                break;
+            }
+            case 3: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "resourcetype"), "test4: wrong property 5");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (3)");
+                break;
+            }
+            case 4: {
+                UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getetag"), "test4: wrong property 6");
+                UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (4)");
+                break;
+            }
+            default: {
+                UCX_TEST_ASSERT(i.index <= 5, "fail");
+            }
+        }
+    }
+    
+    UCX_TEST_ASSERT(properties4 == NULL, "test4: list not NULL");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_msresponse_addproperty) {
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "init failed");
+    UCX_TEST_ASSERT(op->response, "no response");
+    
+    Multistatus *ms = (Multistatus*)op->response;
+    MSResponse *r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/");
+    
+    WebdavProperty p1;
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    
+    WSNamespace ns1;
+    ZERO(&ns1, sizeof(WSNamespace));
+    WSNamespace ns2;
+    ZERO(&ns2, sizeof(WSNamespace));
+    ns1.prefix = (xmlChar*)"x1";
+    ns1.href = (xmlChar*)"http://example.com/test/";
+    ns2.prefix = (xmlChar*)"x2";
+    ns2.href = (xmlChar*)"http://example.com/test/";
+    
+    WebdavProperty dp1;
+    ZERO(&dp1, sizeof(WebdavProperty));
+    dp1.name = "dup";
+    dp1.namespace = &ns1;
+    dp1.value.text.str = "Hello";
+    dp1.value.text.length = 5;
+    dp1.vtype = WS_VALUE_TEXT;
+    
+    WebdavProperty dp2;
+    ZERO(&dp2, sizeof(WebdavProperty));
+    dp2.name = "dup";
+    dp2.namespace = &ns1;
+    dp2.value.text.str = "Hello";
+    dp2.value.text.length = 5;
+    dp2.vtype = WS_VALUE_TEXT;
+    
+    WebdavProperty dp3;
+    ZERO(&dp3, sizeof(WebdavProperty));
+    dp3.name = "dup";
+    dp3.namespace = &ns2;
+    dp3.value.text.str = "Hello";
+    dp3.value.text.length = 5;
+    dp3.vtype = WS_VALUE_TEXT;
+    
+    // init test data
+    p1.namespace = webdav_dav_namespace();
+    p1.lang = NULL;
+    p1.name = "test1";
+    p1.value.data = (WSXmlData){ NULL, NULL, 0};
+    p1.vtype = 0;
+    
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[1].vtype = 0;
+    }
+    
+    UCX_TEST_ASSERT(!r->plist_begin && !r->plist_end, "plist not empty");
+    
+    r->resource.addproperty((WebdavResource*)r, &p1, 200);
+    UCX_TEST_ASSERT(r->plist_begin, "!plist_begin");
+    UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end");
+    
+    r->resource.addproperty((WebdavResource*)r, &p[0], 404);
+    r->resource.addproperty((WebdavResource*)r, &p[1], 404);
+    r->resource.addproperty((WebdavResource*)r, &p[2], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[3], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[4], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[5], 403);
+    r->resource.addproperty((WebdavResource*)r, &p[6], 500);
+    
+    UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end");
+    
+    UCX_TEST_ASSERT(r->errors, "no prop errors");
+    UCX_TEST_ASSERT(r->errors->next, "no second error code");
+    UCX_TEST_ASSERT(r->errors->next->next, "no third error code");
+    UCX_TEST_ASSERT(!r->errors->next->next->next, "too many error codes");
+    
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 2, "404 list size != 2");
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->begin) == 4, "403 list size != 4");
+    UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->next->begin) == 1, "500 list size != 1");
+    
+    // new resource for prop duplication tests
+    r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/test");
+    UCX_TEST_ASSERT(r, "cannot create second response");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp1, 200);
+    UCX_TEST_ASSERT(r->plist_begin, "adding dp1 failed");
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: list size not 1");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp2, 200);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 should not work");
+    
+    r->resource.addproperty((WebdavResource*)r, &dp2, 404);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 with different status should not work (1)");
+    if(r->errors) {
+        UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 0, "dp1: error list not empty");
+    }
+    
+    r->resource.addproperty((WebdavResource*)r, &dp3, 200);
+    UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp3 should not work");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_propfind_init) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    WebdavPropfindRequest *propfind;
+    UCX_TEST_BEGIN;
+    UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed");
+    
+    UcxList *requests = NULL;
+    int err = webdav_propfind_init(&backend1, propfind, "/", "/", &requests);
+    
+    UCX_TEST_ASSERT(!err, "webdav_propfind_init failed");
+    UCX_TEST_ASSERT(requests, "request list is empty");
+    UCX_TEST_ASSERT(ucx_list_size(requests), "request list has wrong size");
+    
+    WebdavPropfindRequest *p1 = requests->data;
+    WebdavPropfindRequest *p2 = requests->next->data;
+    
+    // backend1 removes the first property from the plist
+    // backend2 should have one property less 
+    
+    UCX_TEST_ASSERT(p1 && p2, "missing requests objects");
+    UCX_TEST_ASSERT(p1 != p2, "request objects equal");
+    UCX_TEST_ASSERT(p1->properties != p2->properties, "plists equal");
+    UCX_TEST_ASSERT(p1->propcount == p2->propcount + 1, "first property not removed");
+    
+    UCX_TEST_ASSERT(backend1_init_called == 1, "backend1 init not called");
+    UCX_TEST_ASSERT(backend2_init_called == 1, "backend2 init not called");
+    
+    UCX_TEST_END;
+    
+    pool_destroy(sn->pool);
+}
+
+UCX_TEST(test_webdav_op_propfind_begin) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "WebdavOperation not created");
+    
+    int err = webdav_op_propfind_begin(op, "/", NULL, NULL);
+    UCX_TEST_ASSERT(err == 0, "err not 0");
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend2 propfind_do not called");
+    
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_op_propfind_children) {
+    reset_backends();
+    
+    Session *sn;
+    Request *rq;
+    
+    UCX_TEST_BEGIN;
+    WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1);
+    UCX_TEST_ASSERT(op, "WebdavOperation not created");
+    
+    int err = webdav_op_propfind_begin(op, "/", NULL, NULL);
+    UCX_TEST_ASSERT(err == 0, "propfind_begin error");
+    
+    // create test vfs with some files (code from test_vfs_readdir)
+    rq->vfs = testvfs_create(sn);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    UCX_TEST_ASSERT(vfs, "no vfs");
+    
+    err = vfs_mkdir(vfs, "/dir");
+    UCX_TEST_ASSERT(err == 0, "error not 0");
+    
+    // add some test file to /dir
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed");
+    UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed");
+    
+    VFSDir *dir = vfs_opendir(vfs, "/dir");
+    UCX_TEST_ASSERT(dir, "dir not opened");
+    
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend1 propfind_do not called")
+   
+    // propfind for all children
+    err = webdav_op_propfind_children(op, vfs, "/", "/dir");
+    UCX_TEST_ASSERT(err == 0, "webdav_op_propfind_children failed");
+    
+    // 1 dir + 4 children
+    UCX_TEST_ASSERT(backend1_propfind_do_count == 5, "backend1 propfind_do wrong count");
+    UCX_TEST_ASSERT(backend2_propfind_do_count == 5, "backend2 propfind_do wrong count");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+void init_test_webdav_method(
+        Session **out_sn,
+        Request **out_rq,
+        TestIOStream **out_st,
+        pblock **out_pb,
+        const char *method,
+        const char *path,
+        const char *request_body)
+{
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    sn = testutil_session();
+    rq = testutil_request(sn->pool, method, "/");
+    
+    pblock_nvinsert("path", path, rq->vars);
+    pblock_nvinsert("uri", path, rq->reqpb);
+    
+    st = testutil_iostream(2048, TRUE);
+    sn->csd = (IOStream*)st;
+    
+    if(request_body) {
+        testutil_request_body(sn, rq, request_body, strlen(request_body));
+    }
+    
+    pb = pblock_create_pool(sn->pool, 4);
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_st = st;
+    *out_pb = pb;
+}
+
+UCX_TEST(test_webdav_propfind) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND1);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    xmlDoc *doc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "propfind1: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc);
+    testutil_iostream_destroy(st);
+    
+    // Test2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND2);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    xmlDoc *doc2 = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "propfind2: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc2);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+    
+}
+
+/* -------------------------------------------------------------------------
+ * 
+ *                           PROPPATCH TESTS
+ * 
+ * ------------------------------------------------------------------------ */
+
+static int test_proppatch_init(
+        Session **out_sn,
+        Request **out_rq,
+        WebdavProppatchRequest **out_proppatch,
+        const char *xml)
+{
+    if(!webdav_is_initialized) {
+        if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) {
+            return 1;
+        }
+        webdav_is_initialized = 1;
+    }
+    
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPPATCH", "/");
+    
+    int error = 0;
+    
+    WebdavProppatchRequest *proppatch = proppatch_parse(
+            sn,
+            rq,
+            xml,
+            strlen(xml),
+            &error);
+    
+    if(error) {
+        return 1;
+    }
+    
+    if(!proppatch || !(proppatch->set || proppatch->remove)) {
+        return 1;
+    }
+    
+    *out_sn = sn;
+    *out_rq = rq;
+    *out_proppatch = proppatch;
+    return 0;
+}
+
+static WebdavOperation* test_proppatch_op1(
+        Session **out_sn,
+        Request **out_rq,
+        const char *xml)
+{
+    WebdavProppatchRequest *proppatch;
+    if(test_proppatch_init(out_sn, out_rq, &proppatch, xml)) {
+        return NULL;
+    }
+    
+    Multistatus *ms = multistatus_response(*out_sn, *out_rq);
+    if(!ms) {
+        return NULL;
+    }
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    return webdav_create_proppatch_operation(
+            (*out_sn),
+            (*out_rq),
+            &backend1,
+            proppatch,
+            response);
+}
+
+
+UCX_TEST(test_proppatch_msresponse) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[i].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    ms = (Multistatus*)op->response;
+    ms->proppatch = TRUE;
+    res = ms->response.addresource(&ms->response, "/");
+    UCX_TEST_ASSERT(res, "cannot create resource 1");
+    
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 200), "addproperty 3 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed");
+    
+    UCX_TEST_ASSERT(!res->close(res), "close failed");
+
+    MSResponse *msres = (MSResponse*)res;
+    UCX_TEST_ASSERT(!msres->errors, "error list not NULL");
+    UCX_TEST_ASSERT(msres->plist_begin, "elm1 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next, "elm2 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next->next, "elm3 missing");
+    UCX_TEST_ASSERT(msres->plist_begin->next->next->next, "elm4 missing");
+    UCX_TEST_ASSERT(!msres->plist_begin->next->next->next->next, "count != 4");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_msresponse_addproperty_with_errors) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[i].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    ms = (Multistatus*)op->response;
+    ms->proppatch = TRUE;
+    res = ms->response.addresource(&ms->response, "/");
+    UCX_TEST_ASSERT(res, "cannot create resource 1");
+    
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 409), "addproperty 3 failed");
+    UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed");
+    
+    UCX_TEST_ASSERT(!res->close(res), "close failed");
+    
+    // all properties should have an error status code now
+    // 1 x 409, 3 x 424
+
+    MSResponse *msres = (MSResponse*)res;
+    
+    UCX_TEST_ASSERT(!msres->plist_begin, "plist not NULL");
+    UCX_TEST_ASSERT(msres->errors, "error list is NULL");
+    UCX_TEST_ASSERT(msres->errors->next, "second error list is missing");
+    UCX_TEST_ASSERT(!msres->errors->next->next, "wrong error list size");
+    
+    // We know that we have 2 error lists, one with status code 409 and
+    // the other must have 409. However we don't enforce the order of the
+    // error lists, therefore check both variants
+    if(msres->errors->status == 409) {
+        UCX_TEST_ASSERT(msres->errors->next->status == 424, "wrong status code in second err elm");
+        UCX_TEST_ASSERT(msres->errors->begin, "missing 409 property");
+        UCX_TEST_ASSERT(msres->errors->next->begin, "missing 424 properties");
+    } else {
+        UCX_TEST_ASSERT(msres->errors->next->status == 409, "wrong status code in second err elm");
+        UCX_TEST_ASSERT(msres->errors->begin, "missing 424 properties");
+        UCX_TEST_ASSERT(msres->errors->next->begin, "missing 409 property");
+    } 
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_webdav_op_proppatch) {
+    Session *sn;
+    Request *rq;
+    WebdavOperation *op;
+    
+    Multistatus *ms;
+    WebdavResource *res;
+    
+    WebdavProperty p[16];
+    const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"};
+    for(int i=0;i<8;i++) {
+        p[i].namespace = webdav_dav_namespace();
+        p[i].name = names[i];
+        p[i].lang = NULL;
+        p[i].value.node = NULL;
+        p[1].vtype = 0;
+    }
+    
+    UCX_TEST_BEGIN;
+    
+    // TEST_PROPPATCH2 should succeed
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation");
+    
+    int ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed");
+    UCX_TEST_ASSERT(backend1_proppatch_commit, "backend1 no commit");
+    UCX_TEST_ASSERT(backend2_proppatch_commit, "backend2 no commit");
+    UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (1)");
+    UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (1)");
+    UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (1)");
+    UCX_TEST_ASSERT(backend2_proppatch_finish_count == 1, "backend1 wrong finish count (1)");
+    
+    // TEST_PROPPATCH3 should fail (commit == FALSE)
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH3);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation 2");
+    
+    ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed (2)");
+    UCX_TEST_ASSERT(!backend1_proppatch_commit, "backend1 commit");
+    UCX_TEST_ASSERT(!backend2_proppatch_commit, "backend2 commit");
+    
+    // TEST_PROPPATCH4 should abort
+    reset_backends();
+    op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH4);
+    UCX_TEST_ASSERT(op, "failed to create proppatch operation 3");
+    
+    ret = webdav_op_proppatch(op, "/", "/");
+    UCX_TEST_ASSERT(ret != 0, "webdav_op_proppatch should fail");
+    UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (2)");
+    UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (2)");
+    UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (2)");
+    UCX_TEST_ASSERT(backend2_proppatch_finish_count == 0, "backend1 wrong finish count (2)");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+#define xstreq(a, b) (!strcmp((const char*)a, (const char*)b))
+
+UCX_TEST(test_webdav_proppatch) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/", TEST_PROPPATCH2);
+    rq->davCollection = &backend1;
+    ret = webdav_proppatch(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_proppatch (1) failed");
+    
+    xmlDoc *doc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "proppatch1: response is not valid xml");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    UCX_TEST_ASSERT(root, "proppatch1: no root");
+    
+    xmlNode *nodeC = NULL;
+    xmlNode *node = root->children;
+    int depth = 1;
+    while(node) {
+        const xmlChar *name = node->name;
+        int nextNode = 1;
+        if(node->type != XML_ELEMENT_NODE) {
+            // nothing
+        } else if(depth == 1) {
+            if(xstreq(name, "response")) {
+                nextNode = 0;
+            }
+        } else if(depth == 2) {
+            if(xstreq(name, "propstat")) {
+                nextNode = 0;
+            }
+        } else if(depth == 3) {
+            if(xstreq(name, "prop")) {
+                nextNode = 0;
+            }
+        } else if(depth == 4) {
+            if(xstreq(name, "c")) {
+                nodeC = node;
+                break;
+            }
+        }
+        
+        if(nextNode) {
+            node = node->next;
+        } else {
+            node = node->children;
+            depth++;
+        }
+    }
+    
+    UCX_TEST_ASSERT(nodeC, "prop c not in response");
+    UCX_TEST_ASSERT(!nodeC->children, "properties must not have a value");
+    
+    testutil_destroy_session(sn);
+    xmlFreeDoc(doc);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}
+
+
+/* -------------------------------------------------------------------------
+ * 
+ *                          WEBDAV VFS TESTS
+ * 
+ * ------------------------------------------------------------------------ */
+
+static int mkcol_data1 = 10;
+static int mkcol_data2 = 20;
+static int mkcol_data3 = 30;
+static int mkcol_data4 = 40;
+
+static int mkcol_count = 0;
+static int mkcol_finish_count = 0;
+
+static int mkcol_err = 0;
+
+static int set_created = 0;
+
+static int test_webdav_mkcol(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    
+    switch(mkcol_count) {
+        case 1: {
+            req->userdata = &mkcol_data1;
+            break;
+        }
+        case 2: {
+            req->userdata = &mkcol_data2;
+            break;
+        }
+        case 3: {
+            req->userdata = &mkcol_data3;
+            break;
+        }
+        case 4: {
+            req->userdata = &mkcol_data4;
+            break;
+        }
+        default: break;
+    }
+    
+    if(set_created) {
+        *created = TRUE;
+        set_created = 0;
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_finish(WebdavVFSRequest *req, WSBool success) {
+    mkcol_finish_count++;
+    
+    if(mkcol_finish_count == 1) {
+        int *data = req->userdata;
+        if(data != &mkcol_data1) {
+            mkcol_err = 1;
+        }
+    } else if(mkcol_finish_count == 3) {
+        int *data = req->userdata;
+        if(data != &mkcol_data3) {
+            mkcol_err = 1;
+        }
+    } else {
+        int *data = req->userdata;
+        // data4 should never be used
+        if(data == &mkcol_data4) {
+            mkcol_err = 1;
+        }
+    }
+    
+    return 0;
+}
+
+static int test_webdav_mkcol_fail(WebdavVFSRequest *req, WSBool *created) {
+    mkcol_count++;
+    return 1;
+}
+
+static int delete_count = 0;
+static int delete_finish_count = 0;
+
+static int test_backend_webdav_delete(WebdavVFSRequest *req, WSBool *created) {
+    delete_count++;    
+    return 0;
+}
+
+static int test_backend_webdav_delete_finish(WebdavVFSRequest *req, WSBool success) {
+    delete_finish_count++;
+    return 0;
+}
+
+
+UCX_TEST(test_webdav_vfs_op_do) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    // Tests performed primarily with MKCOL, because webdav_vfs_op_do
+    // behaves the same for both operations
+    // the only difference are the callbacks
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "MKCOL", "/", NULL);
+    VFS *testvfs = testvfs_create(sn);
+    rq->vfs = testvfs;
+    
+    WebdavBackend dav1;
+    ZERO(&dav1, sizeof(WebdavBackend));
+    dav1.opt_mkcol = test_webdav_mkcol;
+    dav1.opt_mkcol_finish = test_webdav_mkcol_finish;
+    dav1.opt_delete = test_backend_webdav_delete;
+    dav1.opt_delete_finish = test_backend_webdav_delete_finish;
+    
+    WebdavBackend dav2;
+    ZERO(&dav2, sizeof(WebdavBackend));
+    dav2.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    WebdavBackend dav3;
+    ZERO(&dav3, sizeof(WebdavBackend));
+    dav3.opt_mkcol = test_webdav_mkcol;
+    
+    WebdavBackend dav4;
+    ZERO(&dav4, sizeof(WebdavBackend));
+    dav4.opt_mkcol = test_webdav_mkcol;
+    dav4.opt_mkcol_finish = test_webdav_mkcol_finish;
+    
+    dav1.next = &dav2;
+    dav2.next = &dav3;
+    dav3.next = &dav4;
+    
+    rq->davCollection = &dav1;
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavVFSOperation *op1 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    
+    int ret = webdav_vfs_op_do(op1, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "webdav_vfs_op_do failed");
+    UCX_TEST_ASSERT(mkcol_count == 3, "wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 3, "wrong mkcol_finish_count");
+    UCX_TEST_ASSERT(mkcol_err == 0, "mkcol_err");
+    
+    // test without VFS, but set *created to TRUE to skip VFS usage
+    rq->vfs = NULL;
+    set_created = 1;
+    
+    WebdavVFSOperation *op2 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op2, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(!ret, "op2 failed");
+    
+    // test 3: abort after first backend
+    mkcol_count = 0;
+    mkcol_finish_count = 0;
+    dav1.opt_mkcol = test_webdav_mkcol_fail;
+    
+    WebdavVFSOperation *op3 = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    ret = webdav_vfs_op_do(op3, WEBDAV_VFS_MKDIR);
+    
+    UCX_TEST_ASSERT(ret, "op3 should fail");
+    UCX_TEST_ASSERT(mkcol_count == 1, "op3: wrong mkcol_count");
+    UCX_TEST_ASSERT(mkcol_finish_count == 1, "op3: wrong mkcol_finish_count");
+    
+    // test DELETE to make sure, delete callbacks will be used
+    pblock_replace("path", "/deltest", rq->vars);
+    rq->vfs = testvfs;
+    WebdavVFSOperation *op_del = webdav_vfs_op(sn, rq, &dav1, FALSE);
+    vfs_open(op_del->vfs, "/deltest", O_CREAT);
+    ret = webdav_vfs_op_do(op_del, WEBDAV_VFS_DELETE);
+    
+    UCX_TEST_ASSERT(!ret, "op_del failed");
+    UCX_TEST_ASSERT(delete_count == 1, "op_del: wrong delete_count");
+    UCX_TEST_ASSERT(delete_finish_count == 1, "op_del: wrong delete_finish_count");
+    
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_delete){
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "DELETE", "/", NULL);
+    rq->vfs = testvfs_create(sn);
+    
+    WebdavBackend dav1;
+    ZERO(&dav1, sizeof(WebdavBackend));
+    dav1.opt_delete = test_backend_webdav_delete;
+    dav1.opt_delete_finish = test_backend_webdav_delete_finish;
+    delete_count = 0;
+    delete_finish_count = 0;
+    rq->davCollection = &dav1;
+    
+    UCX_TEST_BEGIN;
+    
+    // prepare
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    int err;
+    err = vfs_mkdir(vfs, "/dir1");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir1 failed");
+    err = vfs_mkdir(vfs, "/dir2");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir2 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir3");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir3 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir4");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir4 failed");
+    err = vfs_mkdir(vfs, "/dir2/dir4/dir5");
+    UCX_TEST_ASSERT(err == 0, "mkdir dir5 failed");
+    
+    SYS_FILE f0 = vfs_open(vfs, "/file0", O_CREAT);
+    UCX_TEST_ASSERT(f0, "f0 create failed");
+    // no f1
+    SYS_FILE f2 = vfs_open(vfs, "/dir2/file2", O_CREAT);
+    UCX_TEST_ASSERT(f2, "f2 create failed");
+    SYS_FILE f3 = vfs_open(vfs, "/dir2/dir3/file3", O_CREAT);
+    UCX_TEST_ASSERT(f3, "f3 create failed");
+    SYS_FILE f4 = vfs_open(vfs, "/dir2/dir4/file4", O_CREAT);
+    UCX_TEST_ASSERT(f4, "f4 create failed");
+    SYS_FILE f5 = vfs_open(vfs, "/dir2/dir4/dir5/file5", O_CREAT);
+    UCX_TEST_ASSERT(f5, "f5 create failed");
+    
+    // delete single file
+    pblock_replace("path", "/file0", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /file0 failed");
+    UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count");
+    
+    delete_count = 0;
+    pblock_replace("path", "/dir1", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /dir1 failed");
+    UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count");
+    
+    delete_count = 0;
+    pblock_replace("path", "/dir2", rq->vars);
+    err = webdav_delete(NULL, sn, rq);
+    UCX_TEST_ASSERT(err == 0, "DELETE /dir2 failed");
+    UCX_TEST_ASSERT(delete_count == 8, "del2: wrong delete count");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_webdav_put) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    const char *content_const = "Hello World";
+    
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PUT", "/", content_const);
+    rq->vfs = testvfs_create(sn);
+    
+    UCX_TEST_BEGIN;
+    
+    int err;
+    
+    pblock_replace("path", "/file0", rq->vars);
+    err = webdav_put(NULL, sn, rq);
+    
+    UCX_TEST_ASSERT(err == REQ_PROCEED, "put failed");
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE f0 = vfs_open(vfs, "/file0", 0);
+    UCX_TEST_ASSERT(f0, "cannot open file0");
+    
+    char buf[1024];
+    int r = system_fread(f0, buf, 1024);
+    
+    UCX_TEST_ASSERT(r == strlen(content_const), "wrong file size");
+    UCX_TEST_ASSERT(!memcmp(content_const, buf, r), "wrong file content");
+    
+    testutil_destroy_session(sn);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/webdav.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,203 @@
+/*
+ * 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 "testutils.h"
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+void init_test_webdav_method(
+        Session **out_sn,
+        Request **out_rq,
+        TestIOStream **out_st,
+        pblock **out_pb,
+        const char *method,
+        const char *path,
+        const char *request_body);
+    
+UCX_TEST(test_webdav_plist_add);
+UCX_TEST(test_webdav_plist_size);
+    
+UCX_TEST(test_propfind_parse);
+UCX_TEST(test_proppatch_parse);
+UCX_TEST(test_lock_parse);
+
+UCX_TEST(test_rqbody2buffer);
+
+UCX_TEST(test_webdav_plist_iterator);
+UCX_TEST(test_webdav_plist_iterator_remove_current);
+
+UCX_TEST(test_msresponse_addproperty);
+UCX_TEST(test_msresponse_addproperty_with_errors);
+
+UCX_TEST(test_webdav_propfind_init);
+UCX_TEST(test_webdav_op_propfind_begin);
+UCX_TEST(test_webdav_op_propfind_children);
+
+UCX_TEST(test_webdav_propfind);
+
+UCX_TEST(test_proppatch_msresponse);
+UCX_TEST(test_webdav_op_proppatch);
+
+UCX_TEST(test_webdav_proppatch);
+
+UCX_TEST(test_webdav_vfs_op_do);
+
+UCX_TEST(test_webdav_delete);
+UCX_TEST(test_webdav_put);
+
+/* --------------------------- PROPFIND --------------------------- */
+
+#define TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getcontenttype/> \
+                <D:getlastmodified/> \
+                <D:resourcetype/> \
+                <D:getetag/> \
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop xmlns:X=\"http://example.com/\"> \
+                <D:resourcetype/> \
+                <X:testprop/> \
+                <X:name/> \
+                <Z:testprop xmlns:Z=\"testns\"/>\
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:allprop/> \
+        </D:propfind>"
+
+#define TEST_PROPFIND4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:propname/> \
+        </D:propfind>"
+
+#define TEST_PROPFIND5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getetag/> \
+                <D:getcontentlength/> \
+                <D:resourcetype/> \
+            </D:prop> \
+        </D:propfind>"
+
+#define TEST_PROPFIND6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propfind xmlns:D=\"DAV:\"> \
+            <D:prop> \
+                <D:displayname/> \
+                <D:getcontentlength/> \
+                <D:getetag/> \
+                <D:resourcetype/> \
+            </D:prop> \
+            <D:allprop/> \
+        </D:propfind>"
+
+/* --------------------------- PROPPATCH --------------------------- */
+
+#define TEST_PROPPATCH1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop><X:test>test</X:test><D:creationdate>123</D:creationdate></D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop> \
+                    <X:a>test</X:a> \
+                    <X:b>15</X:b> \
+                    <X:c> \
+                        <X:name test='test1' abc='def'>User</X:name><X:mail>user@host</X:mail> \
+                    </X:c> \
+                    <X:d><X:name>Test</X:name></X:d> \
+                </D:prop> \
+            </D:set> \
+            <D:remove> \
+                <D:prop> \
+                    <X:e/> \
+                </D:prop> \
+            </D:remove> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop> \
+                    <X:a>test</X:a> \
+                    <X:fail>15</X:fail> \
+                </D:prop> \
+            </D:set> \
+            <D:remove> \
+                <D:prop> \
+                    <X:e/> \
+                </D:prop> \
+            </D:remove> \
+        </D:propertyupdate>"
+
+#define TEST_PROPPATCH4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \
+            <D:set> \
+                <D:prop><X:abort>error</X:abort></D:prop> \
+            </D:set> \
+        </D:propertyupdate>"
+
+/* --------------------------- LOCK --------------------------- */
+
+#define TEST_LOCK1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <D:lockinfo xmlns:D=\"DAV:\"> \
+            <D:lockscope><D:shared/></D:lockscope> \
+            <D:locktype>D:write/></D:locktype> \
+            <D:owner>User</D:owner> \
+        </D:lockinfo>"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_WEBDAV_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,151 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util/writer.h"
+
+#include <ucx/buffer.h>
+
+#include "writer.h"
+#include "testutils.h"
+
+UCX_TEST(test_writer_putc) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[1] == 'b', "2: wrong char at pos 1");
+    UCX_TEST_ASSERT(writer.pos == 2, "2: wrong pos");
+    
+    writer_putc(out, 'c');
+    writer_putc(out, 'd');
+    UCX_TEST_ASSERT(wbuf[2] == 'c', "3: wrong char at pos 2");
+    UCX_TEST_ASSERT(wbuf[3] == 'd', "4: wrong char at pos 3");
+    
+    writer_putc(out, 'f'); // should flush the buffer
+    UCX_TEST_ASSERT(wbuf[0] == 'f', "5: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "5: wrong pos");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "5: wrong char at UcxBuffer pos 0");
+    UCX_TEST_ASSERT(buf->space[1] == 'b', "5: wrong char at UcxBuffer pos 1");
+    UCX_TEST_ASSERT(buf->pos == 4, "5: wrong UcxBuffer pos");    
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_flush) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_flush(out);
+    UCX_TEST_ASSERT(writer.pos == 0, "wrong pos after flush");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 1, "wrong UcxBuffer pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[0] == 'b', "2: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "2: wrong pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_put) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 8);
+    Writer *out = &writer;
+    
+    writer_put(out, "abcd", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcd", 4), "1: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "1: wrong pos");
+    
+    writer_put(out, "efgh", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcdefgh", 8), "2: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 8, "2: wrong pos");
+    
+    writer_put(out, "1234", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "1234", 4), "3: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "3: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh", 8), "3: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 8, "3: wrong UcxBuffer pos");
+    
+    writer_put(out, "5678xx", 6);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "xx", 2), "4: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 2, "4: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh12345678", 16), "4: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 16, "4: wrong UcxBuffer pos");
+    
+    writer_puts(out, S("345678abcdefgh12345678end."));
+    UCX_TEST_ASSERT(!memcmp(wbuf, "end.", 4), "5: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "5: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(
+            buf->space,
+            "abcdefgh12345678xx345678abcdefgh12345678",
+            40),
+            "5: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 40, "5: wrong UcxBuffer pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEST_WRITER_H
+#define TEST_WRITER_H
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_writer_putc);
+UCX_TEST(test_writer_flush);
+UCX_TEST(test_writer_put);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_WRITER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/xml.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,430 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xml.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libxml/tree.h>
+
+#include "testutils.h"
+
+#include "../public/webdav.h"
+#include "../util/writer.h"
+
+#include "../webdav/webdav.h"
+#include "../webdav/xml.h"
+
+typedef struct Test1Data {
+    int beginCounter;
+    int endCounter;
+    int elmCounter;
+    int endElmCounter;
+    int textErr;
+    int err;
+    int endErr;
+    int nodesWithAttributesCounter;
+    xmlNode *prev;
+} Test1Data;
+
+static int test1_begin(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->beginCounter++;
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->elmCounter++;
+        const char *name = (const char*)node->name;
+        
+        if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) {
+            data->err = 1;
+            return 1;
+        }
+        
+        switch(data->elmCounter) {
+            case 1: if(strcmp(name, "test")){ data->err = 1; return 1; } break;
+            case 2: if(strcmp(name, "elm1")) { data->err = 1; return 1; } break;
+            case 3: if(strcmp(name, "elm2")) { data->err = 1; return 1; } break;
+            case 4: if(strcmp(name, "c")) { data->err = 1; return 1; } break;
+            case 5: if(strcmp(name, "a")) { data->err = 1; return 1; } break;
+            case 6: if(strcmp(name, "d")) { data->err = 1; return 1; } break;
+            case 7: if(strcmp(name, "e")) { data->err = 1; return 1; } break;
+            case 8: if(strcmp(name, "b")) { data->err = 1; return 1; } break;
+            case 9: if(strcmp(name, "x")) { data->err = 1; return 1; } break;
+            case 10: if(strcmp(name, "z")) { data->err = 1; return 1; } break;
+            case 11: if(strcmp(name, "nextelm")) { data->err = 1; return 1; } break;
+        }
+    } else if(node->type == XML_TEXT_NODE) {
+        const char *text = (const char*)node->content;
+        if(!strcmp(text, "teststr")) {
+            if(strcmp((const char*)data->prev->name, "elm1")) {
+                data->textErr = 1;
+                return 1;
+            }
+        } else if(!strcmp(text, "hello") || !strcmp(text, "world")) {
+            if(strcmp((const char*)data->prev->name, "a")) {
+                data->textErr = 1;
+                return 1;
+            }
+        }
+    }
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->prev = node;
+    }
+    return 0;
+}
+
+static int test1_end(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->endCounter++;
+    
+    if(node->type == XML_ELEMENT_NODE) {
+        data->endElmCounter++;
+        const char *name = (const char*)node->name;
+        
+        if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) {
+            data->err = 1;
+            return 1;
+        }
+        
+        switch(data->endElmCounter) {
+            case 1: if(strcmp(name, "elm1")){ data->endErr = 1; return 1; } break;
+            case 2: if(strcmp(name, "elm2")){ data->endErr = 1; return 1; } break;
+            case 3: if(strcmp(name, "a")){ data->endErr = 1; return 1; } break;
+            case 4: if(strcmp(name, "b")){ data->endErr = 1; return 1; } break;
+            case 5: if(strcmp(name, "e")){ data->endErr = 1; return 1; } break;
+            case 6: if(strcmp(name, "d")){ data->endErr = 1; return 1; } break;
+            case 7: if(strcmp(name, "c")){ data->endErr = 1; return 1; } break;
+            case 8: if(strcmp(name, "z")){ data->endErr = 1; return 1; } break;
+            case 9: if(strcmp(name, "x")){ data->endErr = 1; return 1; } break;
+            case 10: if(strcmp(name, "test")){ data->endErr = 1; return 1; } break;
+            case 11: if(strcmp(name, "nextelm")) { data->endErr = 1; return 1; } break;
+        }
+    }
+    
+    return 0;
+}
+
+static int test2_begin(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->beginCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->elmCounter++;
+        if(node->properties) {
+            data->nodesWithAttributesCounter++;
+        }
+    }
+    return 0;
+}
+
+static int test2_end(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->endCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->endElmCounter++;
+    }
+    return 0;
+}
+
+UCX_TEST(test_wsxml_iterator) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    xmlDoc *doc = xmlReadMemory(
+            XML_TESTDATA1, strlen(XML_TESTDATA1), NULL, NULL, 0);
+    xmlDoc *doc2 = xmlReadMemory(
+            XML_TESTDATA2, strlen(XML_TESTDATA2), NULL, NULL, 0);
+    xmlDoc *doc6 = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "doc is NULL");
+    UCX_TEST_ASSERT(doc2, "doc2 is NULL");
+    UCX_TEST_ASSERT(doc6, "doc6 is NULL");
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    
+    // Test 1: iterate over complete document
+    Test1Data testdata;
+    ZERO(&testdata, sizeof(Test1Data));
+    int ret = wsxml_iterator(sn->pool, root, test1_begin, test1_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "wsxml_iterator failed");
+    UCX_TEST_ASSERT(!testdata.err, "wrong element order (begin)");
+    UCX_TEST_ASSERT(!testdata.endErr, "wrong element order (end)");
+    UCX_TEST_ASSERT(!testdata.textErr, "text order error");
+    UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "begin/end counter not equal");
+    
+    // Test 2: iterate over sub-document
+    ZERO(&testdata, sizeof(Test1Data));
+    xmlNode *root2 = xmlDocGetRootElement(doc2);
+    xmlNode *sub = root2->children->children;
+    ret = wsxml_iterator(sn->pool, sub, test1_begin, test1_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "test2: wsxml_iterator failed");
+    UCX_TEST_ASSERT(!testdata.err, "test2: wrong element order (begin)");
+    UCX_TEST_ASSERT(!testdata.endErr, "test2: wrong element order (end)");
+    UCX_TEST_ASSERT(!testdata.textErr, "test2: text order error");
+    UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "test2: begin/end counter not equal");
+    
+    // Test 3: iterate over document with all kinds of node types
+    xmlNode *root6 = xmlDocGetRootElement(doc6);
+    ZERO(&testdata, sizeof(Test1Data));
+    ret = wsxml_iterator(sn->pool, root6, test2_begin, test2_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "test3: wsxml_iterator failed");
+    UCX_TEST_ASSERT(testdata.elmCounter == testdata.endElmCounter, "test3: begin/end counter not equal");
+    UCX_TEST_ASSERT(testdata.elmCounter == 12, "test3: wrong elm counter");
+    UCX_TEST_ASSERT(testdata.nodesWithAttributesCounter == 5, "test3: wrong entity ref counter");
+    
+    xmlFreeDoc(doc);
+    xmlFreeDoc(doc2);
+    xmlFreeDoc(doc6);
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+// checks if the namespace list contains the test namespaces x1, x2, x3 and x4
+static void check_ns_list(WebdavNSList *list, int *x1, int *x2, int *x3, int *x4) {
+    *x1 = 0;
+    *x2 = 0;
+    *x3 = 0;
+    *x4 = 0;
+    
+    WebdavNSList *elm = list;
+    while(elm) {
+        if(!strcmp((const char*)elm->namespace->prefix, "x1") &&
+           !strcmp((const char*)elm->namespace->href, "http://example.com/ns1/"))
+        {
+            *x1 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x2") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns2/"))
+        {
+            *x2 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x3") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/"))
+        {
+            *x3 = 1;
+        } else if(!strcmp((const char*)elm->namespace->prefix, "x4") &&
+                  !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/"))
+        {
+            *x4 = 1;
+        }
+            
+        elm = elm->next;
+    }
+}
+
+UCX_TEST(test_wsxml_get_required_namespaces) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    xmlDoc *doc3 = xmlReadMemory(
+            XML_TESTDATA3, strlen(XML_TESTDATA3), NULL, NULL, 0);
+    xmlDoc *doc4 = xmlReadMemory(
+            XML_TESTDATA4, strlen(XML_TESTDATA4), NULL, NULL, 0);
+    xmlDoc *doc5 = xmlReadMemory(
+            XML_TESTDATA5, strlen(XML_TESTDATA5), NULL, NULL, 0);
+    
+    xmlNode *node0 = xmlDocGetRootElement(doc3);
+    xmlNode *node1 = xmlDocGetRootElement(doc3)->children;
+    xmlNode *node2 = xmlDocGetRootElement(doc4)->children;
+    xmlNode *node3 = xmlDocGetRootElement(doc5)->children;
+    
+    UCX_TEST_ASSERT(doc3, "doc3 is NULL");
+    UCX_TEST_ASSERT(doc4, "doc4 is NULL");
+    UCX_TEST_ASSERT(doc5, "doc5 is NULL");
+    
+    int err0, err1, err2, err3;
+    int x1 = 0;
+    int x2 = 0;
+    int x3 = 0;
+    int x4 = 0;
+    WebdavNSList *elm = NULL;
+    
+    // Test 0: 
+    WebdavNSList *ns0 = wsxml_get_required_namespaces(sn->pool, node0, &err0);
+    UCX_TEST_ASSERT(!err0, "ns0 failed");
+    UCX_TEST_ASSERT(!ns0, "ns0: nsdefs should be ignored");
+    
+    WebdavNSList *ns1 = wsxml_get_required_namespaces(sn->pool, node1, &err1);
+    check_ns_list(ns1, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err1, "ns1 failed");
+    UCX_TEST_ASSERT(ns1, "ns1: no list");
+    UCX_TEST_ASSERT(x1, "ns1: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns1: x2 missing");
+    UCX_TEST_ASSERT(x3, "ns1: x3 missing");
+    UCX_TEST_ASSERT(x4, "ns1: x4 missing");
+    
+    WebdavNSList *ns2 = wsxml_get_required_namespaces(sn->pool, node2, &err2);
+    check_ns_list(ns2, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err2, "ns2 failed");
+    UCX_TEST_ASSERT(ns2, "ns2: no list");
+    UCX_TEST_ASSERT(x1, "ns2: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns2: x2 missing");
+    UCX_TEST_ASSERT(!x3, "ns2: x3");
+    UCX_TEST_ASSERT(!x4, "ns2: x4");
+    
+    WebdavNSList *ns3 = wsxml_get_required_namespaces(sn->pool, node3, &err3);
+    check_ns_list(ns3, &x1, &x2, &x3, &x4);
+    UCX_TEST_ASSERT(!err3, "ns3 failed");
+    UCX_TEST_ASSERT(ns3, "ns3: no list");
+    UCX_TEST_ASSERT(x1, "ns3: x1 missing");
+    UCX_TEST_ASSERT(x2, "ns3: x2 missing");
+    UCX_TEST_ASSERT(!x3, "ns3: x3");
+    UCX_TEST_ASSERT(!x4, "ns3: x4");
+    
+    xmlFreeDoc(doc3);
+    xmlFreeDoc(doc4);
+    xmlFreeDoc(doc5);
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_wsxml_write_nodes) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    
+    UCX_TEST_BEGIN;
+    xmlDoc *doc = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "xml parser error");
+    xmlNode *root = xmlDocGetRootElement(doc);
+    
+    Writer writer;
+    char buffer[1024];
+    writer_init(&writer, st, buffer, 1024);
+    
+    int err = wsxml_write_nodes(sn->pool, &writer, NULL, root);
+    writer_flush(&writer);
+    UCX_TEST_ASSERT(err == 0, "wsxml_write_nodes error");
+    UCX_TEST_ASSERT(st->buf->pos > 0, "buffer is empty");
+    
+    //printf("\n\n");
+    //printf("%.*s\n", (int)st->buf->size, st->buf->space);
+    //printf("\n\n");
+    
+    xmlDoc *genDoc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(genDoc, "generated doc is not valid xml");
+    
+    xmlFreeDoc(doc);
+    xmlFreeDoc(genDoc);
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_wsxml_nslist2string) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    WSNamespace ns1;
+    WSNamespace ns2;
+    WSNamespace ns3;  
+    memset(&ns1, 0, sizeof(WSNamespace));
+    memset(&ns2, 0, sizeof(WSNamespace));
+    memset(&ns3, 0, sizeof(WSNamespace));
+    ns1.prefix = (const xmlChar*)"x";
+    ns1.href = (const xmlChar*)"ns1";
+    ns2.prefix = (const xmlChar*)"y";
+    ns2.href = (const xmlChar*)"ns2";
+    ns3.prefix = (const xmlChar*)"z";
+    ns3.href = (const xmlChar*)"ns3";
+    
+    WebdavNSList elm1 = { &ns1, NULL, NULL };
+    WebdavNSList elm2 = { &ns2, NULL, NULL };
+    WebdavNSList elm3 = { &ns3, NULL, NULL };
+    
+    // single elm test
+    char *str1 = wsxml_nslist2string(sn->pool, &elm1);
+    UCX_TEST_ASSERT(str1, "str1 is null");
+    UCX_TEST_ASSERT(!strcmp(str1, "x:ns1"), "str1: wrong content");
+    
+    // 2 elm test
+    elm1.next = &elm2;
+    char *str2 = wsxml_nslist2string(sn->pool, &elm1);
+    UCX_TEST_ASSERT(str2, "str2 is null");
+    UCX_TEST_ASSERT(!strcmp(str2, "x:ns1\ny:ns2"), "str2: wrong content");
+    
+    // 3 elm test
+    elm2.next = &elm3;
+    char *str3 = wsxml_nslist2string(sn->pool, &elm1);
+    UCX_TEST_ASSERT(str3, "str3 is null");
+    UCX_TEST_ASSERT(!strcmp(str3, "x:ns1\ny:ns2\nz:ns3"), "str3: wrong content");
+    
+    // empty prefix test
+    ns1.prefix = NULL;
+    char *str4 = wsxml_nslist2string(sn->pool, &elm1);
+    UCX_TEST_ASSERT(str4, "str3 is null");
+    UCX_TEST_ASSERT(!strcmp(str4, ":ns1\ny:ns2\nz:ns3"), "str4: wrong content");
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_wsxml_string2nslist) {
+    Session *sn = testutil_session();
+    
+    UCX_TEST_BEGIN;
+    
+    // empty list
+    WebdavNSList *list1 = wsxml_string2nslist(sn->pool, "");
+    UCX_TEST_ASSERT(!list1, "list1 should be NULL");
+    
+    // 1 elm list
+    WebdavNSList *list2 = wsxml_string2nslist(sn->pool, "x:ns1");
+    UCX_TEST_ASSERT(list2, "list2 is NULL");
+    UCX_TEST_ASSERT(list2->namespace, "list2 namespace is NULL");
+    UCX_TEST_ASSERT(!strcmp((const char*)list2->namespace->prefix, "x"), "list2: wrong prefix");
+    UCX_TEST_ASSERT(!strcmp((const char*)list2->namespace->href, "ns1"), "list2: wrong href");
+    
+    // 2 elm list
+    WebdavNSList *list3 = wsxml_string2nslist(sn->pool, "x:ns1\ny:ns2");
+    UCX_TEST_ASSERT(list3, "list3 is NULL");
+    UCX_TEST_ASSERT(list3->namespace, "list3 namespace is NULL");
+    UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->prefix, "x"), "list3: wrong prefix");
+    UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: wrong href");
+    UCX_TEST_ASSERT(list3->next, "list3 elm2 is NULL");
+    UCX_TEST_ASSERT(list3->next->namespace, "list3 namespace 2 is NULL");
+    UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->prefix, "x"), "list3: elm2 wrong prefix");
+    UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: elm2 wrong href");
+    
+    // empty prefix
+    WebdavNSList *list4 = wsxml_string2nslist(sn->pool, ":x\ny:ns2");
+    UCX_TEST_ASSERT(list4, "list4 is NULL");
+    UCX_TEST_ASSERT(list4->namespace, "list4 namespace is NULL");
+    UCX_TEST_ASSERT(!list4->namespace->prefix, "list4 elm1 prefix should be NULL");
+    UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: wrong href");
+    
+    
+    UCX_TEST_END;
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/xml.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,133 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEST_XML_H
+#define TEST_XML_H
+
+#include "../public/nsapi.h"
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_wsxml_iterator);
+UCX_TEST(test_wsxml_get_required_namespaces);
+UCX_TEST(test_wsxml_write_nodes);
+UCX_TEST(test_wsxml_nslist2string);
+UCX_TEST(test_wsxml_string2nslist);
+
+
+#define XML_TESTDATA1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <test> \
+            <elm1>teststr</elm1> \
+            <!-- comment -->\
+            <elm2 /> \
+            <c> \
+                <a>hello</a> \
+                world\
+                <d><e><b/></e></d>\
+            </c> \
+            <x><z/></x> \
+        </test>"
+
+#define XML_TESTDATA2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <root><wrapper><test> \
+            <elm1>teststr</elm1> \
+            <!-- comment -->\
+            <elm2 /> \
+            <c> \
+                <a>hello</a> \
+                world\
+                <d><e><b/></e></d>\
+            </c> \
+            <x><z/></x> \
+        </test><nextelm/></wrapper><ignore/></root>"
+
+#define XML_TESTDATA3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" \
+            xmlns:x3=\"http://example.com/ns_0/\" \
+            xmlns:x4=\"http://example.com/ns_0/\" > \
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+            <x3:elm3>str1</x3:elm3>\
+            <x4:elm4>str1</x4:elm4>\
+        </x1:prop>"
+
+#define XML_TESTDATA4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" \
+            xmlns:x3=\"http://example.com/ns_0/\" \
+            xmlns:x4=\"http://example.com/ns_0/\" >\
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+        </x1:prop>"
+
+#define XML_TESTDATA5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
+        <x1:prop \
+            xmlns:x1=\"http://example.com/ns1/\" \
+            xmlns:x2=\"http://example.com/ns2/\" > \
+            <x1:elm1>str1</x1:elm1>\
+            <x2:elm2>str1</x2:elm2>\
+            <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\
+            <x4:elm4 xmlns:x4=\"http://example.com/ns_0/\" >str1</x4:elm4>\
+        </x1:prop>"
+
+#define XML_TESTDATA6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \n\
+        <x1:test \n\
+            xmlns:x1=\"http://example.com/ns1/\" \n\
+            xmlns:x2=\"http://example.com/ns2/\" > \n\
+            <x1:elm1>str1</x1:elm1>\n\
+            <x2:elm2>str1</x2:elm2>\n\
+            <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\n\
+            <x1:sub> \n\
+                <x1:a attr1=\"val1\"/> \n\
+                <x1:a attr2=\"val2\">text</x1:a>\n\
+                <x1:b x2:nsattr=\"nsval\"><x1:c/></x1:b>\n\
+            </x1:sub> \n\
+            <x1:newns xmlns:x4=\"http://example.com/0/\" x4:attr3=\"val3\">\n\
+            </x1:newns>\n\
+            <x1:text>Hello\n\
+            World\n\
+            end.\n\
+            </x1:text>\n\
+            <x1:entityref ea=\"test &amp; value\">\n\
+            entity reference test &amp;quote&amp; \n\
+            &#x3C;xml&#x3E;\n\
+            </x1:entityref>\n\
+        </x1:test>\n"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_XML_H */
+
--- a/src/server/util/io.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/io.c	Sat Sep 24 16:26:10 2022 +0200
@@ -252,12 +252,63 @@
     st->fd = fd;
     st->max_read = 0;
     st->read = 0;
+    st->read_total = 0;
+    st->readbuf = NULL;
+    st->bufsize = 0;
+    st->buflen = NULL;
+    st->bufpos = NULL;
+    st->chunk_buf_pos = 0;
     st->chunked_enc = WS_FALSE;
-    st->buffered = WS_FALSE;
+    st->read_eof = WS_TRUE;
+    st->write_eof = WS_FALSE;
     return (IOStream*)st;
 }
 
+int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos) {
+    if(st->read != (io_read_f)net_http_read) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_read: IOStream is not an HttpStream");
+        return 1;
+    }
+    st->read = (io_read_f)net_http_read_chunked;
+    HttpStream *http = (HttpStream*)st;
+    http->max_read = 0;
+    http->read = 0;
+    http->readbuf = buffer;
+    http->bufsize = bufsize;
+    http->buflen = cursize;
+    http->bufpos = pos;
+    http->chunk_buf_pos = 0;
+    http->read_eof = WS_FALSE;
+    return 0;
+}
+
+int httpstream_enable_chunked_write(IOStream *st) {
+    if(st->write != (io_write_f)net_http_write) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_write: IOStream is not an HttpStream");
+        return 1;
+    }
+    HttpStream *http = (HttpStream*)st;
+    http->chunked_enc = WS_TRUE;
+    return 0;
+}
+
+int httpstream_set_max_read(IOStream *st, int64_t maxread) {
+    if(st->write != (io_write_f)net_http_write) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_set_max_read: IOStream is not an HttpStream");
+        return 1;
+    }
+    HttpStream *http = (HttpStream*)st;
+    http->max_read = maxread;
+    return 0;
+}
+
+WSBool httpstream_eof(IOStream *st) {
+    HttpStream *http = (HttpStream*)st;
+    return http->read_eof;
+}
+
 ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) {
+    if(st->write_eof) return 0;
     IOStream *fd = st->fd;
     if(st->chunked_enc) {
         // TODO: on some plattforms iov_len is smaller than size_t
@@ -269,17 +320,23 @@
         io[1].iov_len = nbytes;
         io[2].iov_base = "\r\n";
         io[2].iov_len = 2;
+        // TODO: FIXME: if r < sum of iov_len, everything would explode
+        // we need to store the chunk state and remaining bytes
         ssize_t r = fd->writev(fd, io, 3);
-        return r - io[0].iov_len;
+        return r - io[0].iov_len - io[2].iov_len;
     } else {
         return fd->write(fd, buf, nbytes);
     }
 }
 
 ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt) {
+    if(st->write_eof) return 0;
     IOStream *fd = st->fd;
     if(st->chunked_enc) {
         struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec));
+        if(!io) {
+            return 0;
+        }
         char chunk_len[16];
         io[0].iov_base = chunk_len;
         size_t len = 0;
@@ -289,22 +346,247 @@
         io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len);
         memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec));
         ssize_t r = fd->writev(fd, io, iovcnt + 1);
-        return r - io[0].iov_len;
+        
+        ssize_t ret = r - io[0].iov_len;
+        free(io);
+        return ret;
     } else {
         return fd->writev(fd, iovec, iovcnt);
     }
 }
 
 ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes) {
-    if(st->max_read != 0 && st->read >= st->max_read) {
+    if(st->read >= st->max_read) {
+        st->read_eof = WS_TRUE;
         return 0;
     }
     ssize_t r = st->fd->read(st->fd, buf, nbytes);
+    if(r < 0) {
+        st->st.io_errno = st->fd->io_errno;
+    }
     st->read += r;
     return r;
 }
 
-ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) {  
+#define BUF_UNNEEDED_DIFF 64
+/*
+ * read from st->chunk_buf first, read from st->fd if perform_io is true
+ */
+static ssize_t net_http_read_buffered(HttpStream *st, char *buf, size_t nbytes, WSBool read_data, WSBool *perform_io) {
+    ssize_t r = 0;
+    
+    //memset(buf, 'x', nbytes);
+    //char *orig_buf = buf;
+    
+    // copy available data from st->readbuf to buf
+    int pos = *st->bufpos;
+    size_t buf_available = *st->buflen - pos;
+    if(buf_available) {
+        size_t cplen = buf_available > nbytes ? nbytes : buf_available;
+        if(read_data) {
+            // if we read data (and not a chunk header), we limit the
+            // amount of bytes we copy
+            size_t chunk_available = st->max_read - st->read;
+            cplen = cplen > chunk_available ? chunk_available : cplen;
+            st->read += cplen;
+        }
+        memcpy(buf, st->readbuf + pos, cplen);
+        *st->bufpos += cplen;
+        r += cplen;
+        buf += cplen;
+        nbytes -= cplen;
+    }
+    
+    // maybe perform IO and refill the read buffer
+    // if we read data (read_data == true), make sure not to perform IO,
+    // when a chunk is completed
+    //
+    // if we read a chunk header (read_data == false) it is very important
+    // to not perform IO, if we have previously copied data from readbuf
+    // this ensures we never override non-chunk-header data
+    if(*perform_io && ((read_data && nbytes > 0 && st->max_read - st->read) || (!read_data && r == 0))) {
+        if(*st->buflen - *st->bufpos > 0) {
+            printf("todo: fix, should not happen, remove later\n");
+        }
+        // fill buffer again
+        ssize_t rlen = st->fd->read(st->fd, st->readbuf, st->bufsize);
+        *st->buflen = rlen;
+        *st->bufpos = 0;
+        *perform_io = WS_FALSE;
+        if(rlen < 0) {
+            st->st.io_errno = st->fd->io_errno;
+        }
+        
+        if(rlen > 0) {
+            // call func again to get data from buffer (no IO will be performed)
+            r += net_http_read_buffered(st, buf, nbytes, read_data, perform_io);
+        }
+    }
+    
+    return r;
+}
+
+
+/*
+ * parses a chunk header
+ * the chunk length is stored in chunklen
+ * return:  0 if the data is incomplete
+ *         -1 if an error occured
+ *         >0 chunk header length
+ */
+static int parse_chunk_header(char *str, int len, WSBool first, int64_t *chunklen) {
+    char *hdr_start = NULL;
+    char *hdr_end = NULL;
+    int i = 0;
+    if(first) {
+        hdr_start = str;
+    } else {
+        if(len < 3) {
+            return 0;
+        }
+        if(str[0] == '\r' && str[1] == '\n') {
+            hdr_start = str+2;
+            i = 2;
+        } else if(str[0] == '\n') {
+            hdr_start = str+1;
+            i = 1;
+        } else {
+            return -1;
+        }
+    }
+    
+    for(;i<len;i++) {
+        char c = str[i];
+        if(c == '\r' || c == '\n') {
+            hdr_end = str+i;
+            break;
+        }
+    }
+    if(!hdr_end || i == len) {
+        return 0; // incomplete
+    }
+    
+    if(*hdr_end == '\r') {
+        // we also need '\n'
+        if(hdr_end[1] != '\n') {
+            return -1;
+        }
+        i++; // '\n' found
+    }
+    
+    // parse
+    char save_c = *hdr_end;
+    *hdr_end = '\0';
+    char *end;
+    int64_t clen;
+    errno = 0;
+    clen = strtoll(hdr_start, &end, 16);
+    *hdr_end = save_c;
+    if(errno) {
+        return -1;
+    }
+    i++;
+    
+    if(clen == 0) {
+        // chunk length of 0 indicates the end
+        // an additional \r\n is required (we also accept \n)
+        if(i >= len) {
+            return 0;
+        }
+        if(str[i] == '\n') {
+            i++;
+        } else if(str[i] == '\r') {
+            if(++i >= len) {
+                return 0;
+            }
+            if(str[i] == '\n') {
+                i++;
+            } else {
+                return -1;
+            }
+        } else {
+            return -1;
+        }
+    }
+    
+    *chunklen = clen;
+    return i;
+}
+
+ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes) {
+    if(st->read_eof) {
+        return 0;
+    }
+    
+    char *rbuf = buf; // buffer pos
+    size_t rd = 0; // number of bytes read
+    size_t rbuflen = nbytes; // number of bytes until end of buf
+    WSBool perform_io = WS_TRUE; // we do only 1 read before we abort
+    while(rd < nbytes && (perform_io || (st->max_read - st->read) > 0)) {
+        // how many bytes are available in the current chunk
+        size_t chunk_available = st->max_read - st->read;
+        if(chunk_available > 0) {
+            ssize_t r = net_http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io);
+            if(r == 0) {
+                break;
+            }
+            rd += r;
+            st->read_total += r;
+            rbuf += r;
+            rbuflen -= r;
+        } else {
+            int chunkbuf_avail = HTTP_STREAM_CBUF_SIZE - st->chunk_buf_pos;
+            if(chunkbuf_avail == 0) {
+                // for some reason HTTP_STREAM_CBUF_SIZE is not enough
+                // to store the chunk header
+                // this indicates that something has gone wrong (or this is an attack)
+                st->read_eof = WS_TRUE;
+                return -1;
+            }
+            // fill st->chunk_buf
+            ssize_t r = net_http_read_buffered(st, &st->chunk_buf[st->chunk_buf_pos], chunkbuf_avail, FALSE, &perform_io);
+            if(r == 0) {
+                break;
+            }
+            int chunkbuf_len = st->chunk_buf_pos + r;
+            int64_t chunklen;
+            int ret = parse_chunk_header(st->chunk_buf, chunkbuf_len, st->read_total > 0 ? FALSE : TRUE, &chunklen);
+            if(ret == 0) {
+                // incomplete chunk header
+                st->chunk_buf_pos = chunkbuf_len;
+            } else if(ret < 0) {
+                // error
+                st->read_eof = WS_TRUE;
+                return -1;
+            } else if(ret > 0) {
+                st->max_read = chunklen;
+                st->read = 0;
+                int remaining_len = chunkbuf_len - ret;
+                if(remaining_len > 0) {
+                    // we have read more into chunk_buf than the chunk_header
+                    // it is save to just move bufpos back
+                    *st->bufpos -= remaining_len;
+                }
+                //st->remaining_len = chunkbuf_len - ret;
+                st->chunk_buf_pos = 0;
+                
+                if(chunklen == 0) {
+                    st->read_eof = WS_TRUE;
+                    break;
+                }
+            }
+        }
+        
+        if(!perform_io && rd == 0) {
+            perform_io = WS_TRUE;
+        }
+    }
+    
+    return rd;
+}
+
+ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) {
+    if(st->write_eof) return 0;
     ssize_t ret = 0;
     // TODO: support chunked transfer encoding
     if(st->fd->sendfile) {
@@ -321,9 +603,10 @@
 }
 
 void net_http_finish(HttpStream *st) {
-    if(st->chunked_enc) {
+    if(st->chunked_enc && !st->write_eof) {
         st->fd->write(st->fd, "0\r\n\r\n", 5);
     }
+    st->write_eof = WS_TRUE;
 }
 
 void net_http_setmode(HttpStream *st, int mode) {
@@ -452,7 +735,7 @@
     va_list arg;
     va_start(arg, format);
     sstr_t buf = ucx_vasprintf(ucx_default_allocator(), format, arg);
-    ssize_t r = net_write(fd, buf.ptr, buf.length);
+    ssize_t r = buf.length > 0 ? net_write(fd, buf.ptr, buf.length) : 0;
     free(buf.ptr);
     va_end(arg);
     if(r < 0) {
--- a/src/server/util/io.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/io.h	Sat Sep 24 16:26:10 2022 +0200
@@ -89,13 +89,59 @@
 #endif
 };
 
+#define HTTP_STREAM_CBUF_SIZE 16
 struct HttpStream {
     IOStream st;
     IOStream *fd;
+    
+    /*
+     * content-length or current chunk size
+     */
     uint64_t max_read;
+    /*
+     * total bytes read (with content-length) or bytes read of current chunk
+     */
     uint64_t read;
+    /*
+     * total bytes read with chunked transfer encoding
+     */
+    uint64_t read_total;
+    /*
+     * read buffer (used only with chunked transfer encoding)
+     */
+    char     *readbuf;
+    /*
+     * readbuf allocated size
+     */
+    size_t   bufsize;
+    /*
+     * number of bytes currently stored in readbuf
+     */
+    int      *buflen;
+    /*
+     * current position in the read buffer
+     */
+    int      *bufpos;
+    /*
+     * current chunk_buf position
+     */
+    int      chunk_buf_pos;
+    /*
+     * buffer used only for parsing chunk headers
+     */
+    char     chunk_buf[HTTP_STREAM_CBUF_SIZE];
+    /*
+     * chunked transfer encoding for write enabled?
+     */
     WSBool   chunked_enc;
-    WSBool   buffered;
+    /*
+     * end of file indicator (read)
+     */
+    WSBool   read_eof;
+    /*
+     * end of file indicator (write)
+     */
+    WSBool write_eof;
 };
 
 typedef struct SSLStream {
@@ -120,9 +166,15 @@
 /* http stream */
 IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd);
 
+int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos);
+int httpstream_enable_chunked_write(IOStream *st);
+int httpstream_set_max_read(IOStream *st, int64_t maxread);
+WSBool httpstream_eof(IOStream *st);
+
 ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt);
 ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes);
+ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd);
 void    net_http_close(HttpStream *st);
 void    net_http_finish(HttpStream *st);
--- a/src/server/util/netbuf.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/netbuf.c	Sat Sep 24 16:26:10 2022 +0200
@@ -141,9 +141,15 @@
 
             buf->pos += bytes_in_buffer;
             return bytes_in_buffer;
+        } else if(buf->pos >= buf->maxsize) {
+            return NETBUF_EOF;
         }
     }
-
+    
+    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/object.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/object.c	Sat Sep 24 16:26:10 2022 +0200
@@ -38,7 +38,6 @@
 
 
 httpd_object* object_new(pool_handle_t *pool, char *name) {
-    // TODO: Speicherverwaltung
     httpd_object *obj = pool_malloc(pool, sizeof(httpd_object));
     obj->pool = pool;
     obj->name = name;
--- a/src/server/util/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/objs.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -42,6 +42,7 @@
 UTILOBJ += thrpool.o
 UTILOBJ += util.o
 UTILOBJ += date.o
+UTILOBJ += writer.o
 
 UTILOBJS = $(UTILOBJ:%=$(UTIL_OBJPRE)%)
 UTILSOURCE = $(UTILOBJ:%.o=util/%.c)
--- a/src/server/util/pblock.cpp	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/pblock.cpp	Sat Sep 24 16:26:10 2022 +0200
@@ -324,7 +324,12 @@
 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");
+const pb_key *const pb_key_vfs = _create_key("vfs");
+const pb_key *const pb_key_dav = _create_key("dav");
+const pb_key *const pb_key_vfsclass = _create_key("vfsclass");
+const pb_key *const pb_key_davclass = _create_key("davclass");
 
 /* ------------------------------ _find_key ------------------------------- */
 
--- a/src/server/util/pblock.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/pblock.h	Sat Sep 24 16:26:10 2022 +0200
@@ -301,6 +301,12 @@
 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;
+extern const pb_key *const pb_key_vfs;
+extern const pb_key *const pb_key_dav;
+extern const pb_key *const pb_key_vfsclass;
+extern const pb_key *const pb_key_davclass;
 
 NSAPI_PUBLIC pool_handle_t *pblock_pool(pblock *pb);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/platform.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BASE_PLATFORM_H
+#define BASE_PLATFORM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__clang__)
+#define WS_FALLTHROUGH [[clang::fallthrough]]
+#elif defined(__GNUC__)
+#define WS_FALLTHROUGH __attribute__((fallthrough))
+#else
+#define WS_FALLTHROUGH
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BASE_PLATFORM_H */
+
--- a/src/server/util/pool.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/pool.c	Sat Sep 24 16:26:10 2022 +0200
@@ -65,6 +65,7 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 //define PERM_MALLOC   malloc
 //define PERM_FREE     free
 //define PERM_REALLOC  realloc
@@ -91,6 +92,8 @@
     return 0;
 }
 
+#define POOL_MIN_BLOCKSIZE 128
+
 NSAPI_PUBLIC int
 pool_init(pblock *pb, Session *sn, Request *rq)
 {
@@ -101,11 +104,22 @@
     int n;
 
     //printf("standard block size: %d\n", pool_config.block_size);
-
+    
     if (str_block_size != NULL) {
-        n = atoi(str_block_size);
-        if (n > 0)
-            pool_config.block_size = n;
+        int64_t value;
+        if(!util_strtoint(str_block_size, &value)) {
+            log_ereport(LOG_MISCONFIG, "pool-init: param 'block-size' is not an integer");
+            return REQ_ABORTED;
+        }
+        if(value > INT_MAX) {
+            log_ereport(LOG_MISCONFIG, "pool-init: block-size is too big");
+            return REQ_ABORTED;
+        }
+        if(value < POOL_MIN_BLOCKSIZE) {
+            log_ereport(LOG_MISCONFIG, "pool-init: block-size is too small");
+            return REQ_ABORTED;
+        }
+        pool_config.block_size = value;
     }
 
     if (str_pool_disable && util_getboolean(str_pool_disable, PR_TRUE)) {
--- a/src/server/util/systems.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/systems.h	Sat Sep 24 16:26:10 2022 +0200
@@ -58,10 +58,6 @@
 typedef int PRBool;
 #define PR_TRUE  1
 #define PR_FALSE 0
-
-typedef int WSBool;
-#define WS_TRUE  1
-#define WS_FALSE 0
 /* end new types */
 
 /* --- End common definitions for all supported platforms --- */
--- a/src/server/util/thrpool.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/thrpool.c	Sat Sep 24 16:26:10 2022 +0200
@@ -47,18 +47,21 @@
 
     pthread_mutex_init(&pool->queue_lock, NULL);
     pthread_mutex_init(&pool->avlbl_lock, NULL);
-    pthread_cond_init(&pool->available, NULL);
+    pthread_cond_init(&pool->available, NULL);  
 
+    return pool;
+}
+
+int threadpool_start(threadpool_t *pool) {
     /* create pool threads */
-    for(int i=0;i<min;i++) {
+    for(int i=0;i<pool->min_threads;i++) {
         pthread_t t;
         if (pthread_create(&t, NULL, threadpool_func, pool) != 0) {
-            perror("Error: threadpool_new: pthread_create");
-            return NULL;
+            perror("Error: threadpool_start: pthread_create");
+            return 1;
         }
     }
-
-    return pool;
+    return 0;
 }
 
 void* threadpool_func(void *data) {
@@ -103,6 +106,12 @@
 }
 
 void threadpool_run(threadpool_t *pool, job_callback_f func, void *data) {
+    // TODO: handle errors
+    
+    if(pool->num_threads == 0) {
+        threadpool_start(pool);
+    }
+    
     threadpool_job *job = malloc(sizeof(threadpool_job));
     job->callback = func;
     job->data = data;
--- a/src/server/util/uri.cpp	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/uri.cpp	Sat Sep 24 16:26:10 2022 +0200
@@ -57,7 +57,7 @@
     }
     return flagDbcsUri;
     */
-    return PR_FALSE;
+    return PR_TRUE;
 }
 
 #ifdef XP_WIN32
@@ -228,7 +228,6 @@
 
 
 /* --------------------------- util_uri_escape ---------------------------- */
-/*
 NSAPI_PUBLIC char *util_uri_escape(char *od, const char *s)
 {
     int flagDbcsUri = allow_dbcs_uri();
@@ -267,11 +266,10 @@
     *d = '\0';
     return od;
 }
-*/
 
 
 /* --------------------------- util_url_escape ---------------------------- */
-/*
+
 NSAPI_PUBLIC char *util_url_escape(char *od, const char *s)
 {
     int flagDbcsUri = allow_dbcs_uri();
@@ -310,7 +308,7 @@
     *d = '\0';
     return od;
 }
-*/
+
 
 /* ------------------------- util_uri_strip_params ------------------------- */
 
--- a/src/server/util/util.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/util.c	Sat Sep 24 16:26:10 2022 +0200
@@ -55,6 +55,7 @@
 #include "../public/nsapi.h"
 #include <ucx/string.h>
 #include <ucx/mempool.h>
+#include <ucx/utils.h>
 
 #include "pblock.h"
 #include "util.h"
@@ -288,6 +289,24 @@
     return vsnprintf(s, n, fmt, args);
 }
 
+NSAPI_PUBLIC int util_vasprintf(pool_handle_t *pool, char **s, const char *fmt, 
+                                va_list args)
+{
+    UcxAllocator a = util_pool_allocator(pool);
+    va_list ap;
+    va_copy(ap, args);
+    sstr_t str = ucx_vasprintf(&a, fmt, ap);
+    *s = str.ptr;
+    return str.length;
+}
+
+NSAPI_PUBLIC int util_asprintf(pool_handle_t *pool, char **s, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    return util_vasprintf(pool, s, fmt, args);
+}
+
 NSAPI_PUBLIC int util_vsprintf(char *s, register const char *fmt, va_list args)
 {
     return vsprintf(s, fmt, args);
@@ -394,7 +413,7 @@
     return def;
 }
 
-int util_getboolean_s(sstr_t s, int def) {
+int util_getboolean_s(scstr_t s, int def) {
     if(s.length == 0) {
         return def;
     }
@@ -407,7 +426,7 @@
     return def;
 }
 
-NSAPI_PUBLIC int util_strtoint(char *str, int64_t *value) {
+NSAPI_PUBLIC int util_strtoint(const char *str, int64_t *value) {
     char *end;
     errno = 0;
     int64_t val = strtoll(str, &end, 0);
@@ -419,6 +438,41 @@
     }
 }
 
+NSAPI_PUBLIC const char* util_resource_name(const char *url) {
+    scstr_t urlstr = scstr(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    scstr_t resname = scstrrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+NSAPI_PUBLIC char* util_parent_path(const char *path) {
+    char *name = (char*)util_resource_name((char*)path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = MALLOC(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+NSAPI_PUBLIC char* util_parent_path_pool(pool_handle_t *pool, const char *path) {
+    // maybe we can unify this function with util_parent_path
+    char *name = (char*)util_resource_name((char*)path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = pool_malloc(pool, parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
 
 /* ------------------------------ util_itoa ------------------------------- */
 /*
@@ -1033,3 +1087,97 @@
 
     return pos;
 }
+
+
+/* ------------------------- util_html_escape -------------------------- */
+
+NSAPI_PUBLIC char *util_html_escape(const char *s)
+{
+    const char *in;
+
+    int len = 0;
+    for (in = s; *in; in++) {
+        switch (*in) {
+        case '<':
+            len += 4; // &lt;
+            break;
+        case '>':
+            len += 4; // &gt;
+            break;
+        case '&':
+            len += 5; // &amp;
+            break;
+        case '"':
+            len += 6; // &quot;
+            break;
+        case '\'':
+            len += 6; // &apos;
+            break;
+        case '+':
+            len += 5; // &#43;
+            break;
+        default:
+            len++;
+            break;
+        }
+    }
+
+    char *ns = (char *) MALLOC(len + 1);
+    if (!ns)
+        return ns;
+
+    char *out = ns;
+    for (in = s; *in; in++) {
+        switch (*in) {
+        case '<':
+            *out++ = '&';
+            *out++ = 'l';
+            *out++ = 't';
+            *out++ = ';';
+            break;
+        case '>':
+            *out++ = '&';
+            *out++ = 'g';
+            *out++ = 't';
+            *out++ = ';';
+            break;
+        case '&':
+            *out++ = '&';
+            *out++ = 'a';
+            *out++ = 'm';
+            *out++ = 'p';
+            *out++ = ';';
+            break;
+        case '"':
+            *out++ = '&';
+            *out++ = 'q';
+            *out++ = 'u';
+            *out++ = 'o';
+            *out++ = 't';
+            *out++ = ';';
+            break;
+        case '\'':
+            *out++ = '&';
+            *out++ = 'a';
+            *out++ = 'p';
+            *out++ = 'o';
+            *out++ = 's';
+            *out++ = ';';
+            break;
+        case '+':
+            *out++ = '&';
+            *out++ = '#';
+            *out++ = '4';
+            *out++ = '3';
+            *out++ = ';';
+            break;
+        default:
+            *out++ = *in;
+            break;
+        }
+    }
+    *out = '\0';
+
+    return ns;
+}
+
--- a/src/server/util/util.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/util/util.h	Sat Sep 24 16:26:10 2022 +0200
@@ -170,6 +170,11 @@
 
 NSAPI_PUBLIC int INTutil_snprintf(char *s, int n, const char *fmt, ...);
 
+NSAPI_PUBLIC int util_vasprintf(pool_handle_t *pool, char **s, const char *fmt, 
+                                va_list args);
+
+NSAPI_PUBLIC int util_asprintf(pool_handle_t *pool, char **s, const char *fmt, ...);
+
 NSAPI_PUBLIC int util_strlftime(char *dst, size_t dstsize, const char *format, const struct tm *t);
 
 NSAPI_PUBLIC int INTutil_strftime(char *s, const char *format, const struct tm *t);
@@ -197,10 +202,13 @@
 NSAPI_PUBLIC PRBool INTutil_format_http_version(const char *v, int *protv_num, char *buffer, int size);
 
 NSAPI_PUBLIC int INTutil_getboolean(const char *v, int def);
-int util_getboolean_s(sstr_t s, int def);
+int util_getboolean_s(scstr_t s, int def);
 
 // new
-NSAPI_PUBLIC int util_strtoint(char *str, int64_t *value);
+NSAPI_PUBLIC int util_strtoint(const char *str, int64_t *value);
+NSAPI_PUBLIC const char* util_resource_name(const char *url);
+NSAPI_PUBLIC char* util_parent_path(const char *path);
+NSAPI_PUBLIC char* util_parent_path_pool(pool_handle_t *pool, const char *path);
 
 // TODO
 //NSAPI_PUBLIC PRIntervalTime INTutil_getinterval(const char *v, PRIntervalTime def);
@@ -229,8 +237,6 @@
 
 NSAPI_PUBLIC int64_t util_atoi64(const char *a);
 
-NSAPI_PUBLIC char *util_html_escape(const char *s);
-
 NSAPI_PUBLIC int util_qtoi(const char *q, const char **p);
 
 /* path utils */
@@ -331,6 +337,8 @@
 #define util_sprintf INTutil_sprintf
 #define util_vsnprintf INTutil_vsnprintf
 #define util_snprintf INTutil_snprintf
+#define util_vasprintf INTutil_vasprintf
+#define util_asprintf INTutil_asprintf
 #define util_strftime INTutil_strftime
 #define util_strcasecmp INTutil_strcasecmp
 #define util_strncasecmp INTutil_strncasecmp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/writer.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,124 @@
+/*
+ * 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->write = (wr_writefunc)net_write;
+    w->buffer = buf;
+    w->size = len;
+    w->pos = 0;
+    w->error = 0;
+}
+
+void writer_init_with_stream(Writer *w, void *stream, wr_writefunc writefunc, char *buf, size_t len) {
+    w->fd = stream;
+    w->write = writefunc;
+    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) {
+        //fwrite(w->buffer+pos, 1, len, stdout);
+        //fflush(stdout);
+        ssize_t r = w->write(w->fd, w->buffer + pos, len);
+        if(r <= 0) {
+            break;
+        }
+        len -= r;
+        pos += r;
+    }
+    
+    if(pos != w->pos) {
+        w->error = 1;
+        return 1;
+    }
+    
+    w->pos = 0;
+    return 0;
+}
+
+int writer_put(Writer *w, const char *s, size_t len) {
+    if(w->error) {
+        return w->error;
+    }
+    
+    // available bytes
+    size_t a = w->size - w->pos;
+    if(a == 0) {
+        if(writer_flush(w)) {
+            return 1;
+        }
+    }
+    
+    size_t cplen = len > a ? a : len; // number of bytes we can copy
+    memcpy(w->buffer+w->pos, s, cplen);
+    w->pos += cplen;
+    
+    if(cplen < len) {
+        // not all bytes copied -> call writer_put again
+        // the number of available bytes is 0 then, therefore flush is called
+        return writer_put(w, s + cplen, len - cplen);
+    } else {
+        return 0;
+    }
+}
+
+int writer_puts(Writer *w, sstr_t s) {
+    return writer_put(w, s.ptr, s.length);
+}
+
+int writer_putc(Writer *w, char c) {
+    if(w->pos == w->size) {
+        if(writer_flush(w)) {
+            return 1;
+        }
+    }
+    w->buffer[w->pos++] = c;
+    return 0;
+}
+
+int writer_fwrite(const void *s, size_t size, size_t nelem, Writer *out) {
+    int w = writer_put(out, s, size*nelem);
+    return w/size;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/writer.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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 ssize_t (*wr_writefunc)(void *, const char *, size_t);
+    
+typedef struct Writer {
+    void *fd;
+    wr_writefunc write;
+    char *buffer;
+    size_t size;
+    size_t pos;
+    int error;
+} Writer;
+
+
+
+void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len);
+
+void writer_init_with_stream(Writer *w, void *stream, wr_writefunc writefunc, 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);
+
+int writer_fwrite(const void *s, size_t size, size_t nelem, Writer *w);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WRITER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/multistatus.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,687 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../daemon/session.h"
+#include "../daemon/protocol.h"
+#include "../util/platform.h"
+
+#include <ucx/string.h>
+
+#include "multistatus.h"
+
+#include "operation.h"
+#include "xml.h"
+
+#define MULTISTATUS_BUFFER_LENGTH 2048
+
+Multistatus* multistatus_response(Session *sn, Request *rq) {
+    Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus));
+    if(!ms) {
+        return NULL;
+    }
+    ZERO(ms, sizeof(Multistatus)); 
+    ms->response.addresource = multistatus_addresource;
+    ms->sn = sn;
+    ms->rq = rq;
+    ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8);
+    ms->proppatch = FALSE;
+    if(!ms->namespaces) {
+        return NULL;
+    }
+    if(ucx_map_cstr_put(ms->namespaces, "D", webdav_dav_namespace())) {
+        return NULL;
+    }
+    return ms;
+}
+
+static int send_xml_root(Multistatus *ms, Writer *out) {
+    writer_puts(out, S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+                       "<D:multistatus"));
+    
+    // write the namespaces definitions
+    // key is the namespace prefix
+    // the map always contains the "DAV:" namespace with the prefix "D"
+    UcxMapIterator i = ucx_map_iterator(ms->namespaces);
+    WSNamespace *ns;
+    UCX_MAP_FOREACH(key, ns, i) {
+        writer_puts(out, S(" xmlns:"));
+        writer_put(out, key.data, key.len);
+        writer_puts(out, S("=\""));
+        writer_puts(out, sstr((char*)ns->href));
+        writer_puts(out, S("\""));
+    }
+    
+    writer_puts(out, S(">\n"));
+    
+    return out->error;
+}
+
+static void send_nsdef(WSNamespace *ns, Writer *out) {
+    writer_puts(out, S(" xmlns:"));
+    writer_puts(out, sstr((char*)ns->prefix));
+    writer_puts(out, S("=\""));
+    writer_puts(out, sstr((char*)ns->href));
+    writer_putc(out, '\"');
+}
+
+static void send_string_escaped(Writer *out, sstr_t str) {
+    char *begin = str.ptr;
+    char *end = begin;
+    char *escape = NULL;
+    int esclen;
+    for(size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        end = str.ptr + i;
+        switch(c) {
+            case '"': {
+                escape = "&quot;";
+                esclen = 6;
+                break;
+            }
+            case '&': {
+                escape = "&amp;";
+                esclen = 5;
+                break;
+            }
+            case '\'': {
+                escape = "&apos;";
+                esclen = 6;
+                break;
+            }
+            case '<': {
+                escape = "&lt;";
+                esclen = 4;
+                break;
+            }
+            case '>': {
+                escape = "&gt;";
+                esclen = 4;
+                break;
+            }
+            default: continue;
+        }
+        ptrdiff_t len = end - begin;
+        if(len > 0) {
+            writer_put(out, begin, len);
+            begin = end + 1;
+        }
+        writer_put(out, escape, esclen);
+    }
+    ptrdiff_t len = end - begin;
+    if(len > 0) {
+        writer_put(out, begin, len + 1);
+        begin = end + 1;
+    }
+}
+
+static int send_property(
+        Multistatus *ms,
+        WebdavProperty *property,
+        WebdavNSList *nsdef,
+        WSBool writeContent,
+        Writer *out)
+{
+    // write: "<prefix:name"
+    writer_putc(out, '<');
+    writer_puts(out, sstr((char*)property->namespace->prefix));
+    writer_putc(out, ':');
+    writer_puts(out, sstr((char*)property->name));
+    
+    // check if the namespace is already defined
+    WSBool need_nsdef = TRUE;
+    WSNamespace *ns = ucx_map_cstr_get(
+            ms->namespaces,
+            (char*)property->namespace->prefix);
+    if(ns && !strcmp(
+            (const char*)ns->href,
+            (const char*)property->namespace->href))
+    {
+        need_nsdef = FALSE; // prefix and href are the same, no need for nsdef
+    }
+    
+    // send definition for the element's namespace
+    if(need_nsdef) {
+        send_nsdef(property->namespace, out);
+    }
+    
+    // send additional namespace definitions required for the value
+    WebdavNSList *def = nsdef;
+    while(def) {
+        send_nsdef(def->namespace, out);
+        def = def->next;
+    }
+    
+    // send xml lang attribute
+    if(property->lang) {
+        writer_puts(out, S(" xml:lang=\""));
+        writer_puts(out, sstr((char*)property->lang));
+        writer_putc(out, '\"');
+    }
+    
+    // end property tag and write content
+    if(writeContent) {
+        writer_putc(out, '>');
+        
+        // content
+        switch(property->vtype) {
+            case WS_VALUE_NO_TYPE: break;
+            case WS_VALUE_XML_NODE: {
+                wsxml_write_nodes_without_nsdef(
+                        ms->sn->pool,
+                        out,
+                        property->value.node);
+                break;
+            }
+            case WS_VALUE_XML_DATA: {
+                // only write data, data->namespaces is already handled
+                writer_put(
+                        out,
+                        property->value.data.data,
+                        property->value.data.length);
+                break;
+            }
+            case WS_VALUE_TEXT: {
+                // asume the text is already escaped
+                writer_put(
+                        out,
+                        property->value.text.str,
+                        property->value.text.length);
+                break;
+            }
+        }
+        
+        // end tag
+        writer_puts(out, S("</"));
+        writer_puts(out, sstr((char*)property->namespace->prefix));
+        writer_putc(out, ':');
+        writer_puts(out, sstr((char*)property->name));
+        writer_putc(out, '>');
+    } else {
+        writer_puts(out, S("/>"));
+    }
+    
+    return out->error;
+}
+
+static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) {
+    writer_puts(out, S(" <D:response>\n"
+                       "  <D:href>"));
+    //writer_puts(out, sstr(rp->resource.href));
+    send_string_escaped(out, sstr(rp->resource.href));
+    writer_puts(out, S("</D:href>\n"));
+    
+    WSBool writeContent = ms->proppatch ? FALSE : TRUE;
+    
+    if(rp->plist_begin) {
+        writer_puts(out, S("  <D:propstat>\n"
+                           "   <D:prop>\n"));
+        // send properties
+        PropertyOkList *p = rp->plist_begin;
+        while(p) {
+            writer_puts(out, S("    "));
+            if(send_property(ms, p->property, p->nsdef, writeContent, out)) {
+                return out->error;
+            }
+            writer_puts(out, S("\n"));
+            p = p->next;
+        }
+        
+        writer_puts(out, S("   </D:prop>\n"
+                           "   <D:status>HTTP/1.1 200 OK</D:status>\n"
+                           "  </D:propstat>\n"));
+    }
+    
+    // send error properties
+    PropertyErrorList *error = rp->errors;
+    while(error) {
+        writer_puts(out, S("  <D:propstat>\n"
+                           "   <D:prop>\n"));
+        
+        WebdavPList *errprop = error->begin;
+        while(errprop) {
+            writer_puts(out, S("    "));
+            if(send_property(ms, errprop->property, NULL, FALSE, out)) {
+                return out->error;
+            }
+            writer_putc(out, '\n');
+            errprop = errprop->next;
+        }
+        
+        char statuscode[8];
+        int sclen = snprintf(statuscode, 8, "%d ", error->status);
+        if(sclen > 4) {
+            statuscode[0] = '5';
+            statuscode[1] = '0';
+            statuscode[2] = '0';
+            statuscode[3] = ' ';
+            sclen = 4;
+        }
+        writer_puts(out, S("   </D:prop>\n"
+                           "   <D:status>HTTP/1.1 "));
+        writer_put(out, statuscode, sclen);
+        const char *status_msg = protocol_status_message(error->status);
+        if(status_msg) {
+            writer_put(out, status_msg, strlen(status_msg));
+        } else {
+            writer_puts(out, S("Server Error"));
+        }
+        writer_puts(out, S("</D:status>\n"
+                           "  </D:propstat>\n"));
+        
+        
+        error = error->next;
+    }
+    
+    // end response tag
+    writer_puts(out, S(" </D:response>\n"));
+    
+    return out->error;
+}
+
+int multistatus_send(Multistatus *ms, SYS_NETFD net) { 
+    // make sure every resource is closed
+    if(ms->current && !ms->current->resource.isclosed) {
+        if(msresponse_close((WebdavResource*)ms->current)) {
+            return 1;
+        }
+    }
+    
+    // start http response
+    protocol_status(ms->sn, ms->rq, 207, NULL);
+    protocol_start_response(ms->sn, ms->rq);
+    
+    char buffer[MULTISTATUS_BUFFER_LENGTH];
+    // create a writer, that flushes the buffer when it is filled
+    Writer writer;
+    Writer *out = &writer;
+    writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH);
+    
+    // send the xml root element with namespace defs
+    if(send_xml_root(ms, out)) {
+        return 1;
+    }
+    
+    // send response tags
+    MSResponse *response = ms->first;
+    while(response) {
+        if(send_response_tag(ms, response, out)) {
+            return 1;
+        }
+        response = response->next;
+    }
+    
+    // end multistatus
+    writer_puts(out, S("</D:multistatus>\n"));
+    
+    //printf("\n\n");
+    //fflush(stdout);
+    
+    writer_flush(out);
+    
+    return 0;
+}
+
+WebdavResource * multistatus_addresource(
+        WebdavResponse *response,
+        const char *path)
+{
+    Multistatus *ms = (Multistatus*)response;
+    MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse));
+    if(!res) {
+        return NULL;
+    }
+    ZERO(res, sizeof(MSResponse));
+    
+    // set href
+    res->resource.href = pool_strdup(ms->sn->pool, path);
+    if(!res->resource.href) {
+        return NULL;
+    }
+    
+    res->resource.err = 0;
+    
+    // add resource funcs
+    res->resource.addproperty = msresponse_addproperty;
+    res->resource.close = msresponse_close;
+    
+    res->properties = ucx_map_new_a(session_get_allocator(ms->sn), 32);
+    if(!res->properties) {
+        return NULL;
+    }
+    
+    res->multistatus = ms;
+    res->errors = NULL;
+    res->resource.isclosed = 0;
+    res->closing = 0;
+    
+    // add new resource to the resource list
+    if(ms->current) {
+        // before adding a new resource, the current resource must be closed
+        if(!ms->current->resource.isclosed) {
+            msresponse_close((WebdavResource*)ms->current);
+        }
+        ms->current->next = res;
+    } else {
+        ms->first = res;
+    }
+    ms->current = res;
+    
+    return (WebdavResource*)res;
+}
+
+static int oklist_add(
+        pool_handle_t *pool,
+        PropertyOkList **begin,
+        PropertyOkList **end,
+        WebdavProperty *property,
+        WebdavNSList *nsdef)
+{
+    PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList));
+    if(!newelm) {
+        return 1;
+    }
+    newelm->property = property;
+    newelm->nsdef = nsdef;
+    newelm->next = NULL;
+    if(*end) {
+        (*end)->next = newelm;
+    } else {
+        *begin = newelm;
+    }
+    *end = newelm;
+    return 0;
+}
+
+int msresponse_addproperty(
+        WebdavResource *res,
+        WebdavProperty *property,
+        int status)
+{
+    MSResponse *response = (MSResponse*)res;
+    Session *sn = response->multistatus->sn;
+    if(response->resource.isclosed) {
+        log_ereport(
+                LOG_WARN,
+                "%s",
+                "webdav: cannot add property to closed response tag");
+        return 0;
+    }
+    
+    // some WebdavProperty checks to make sure nothing explodes  
+    if(!property->namespace || !property->namespace->href) {
+        // error: namespace is required
+        log_ereport(
+                LOG_FAILURE,
+                "%s",
+                "webdav: property '%s' has no namespace",
+                property->name);
+        return 1;
+    }
+    
+    // check if the property was already added to the resource
+    UcxAllocator *a = session_get_allocator(sn);
+    sstr_t key = sstrcat_a(
+            a,
+            3,
+            sstr((char*)property->namespace->href),
+            S("\0"),
+            sstr((char*)property->name));
+    if(ucx_map_sstr_get(response->properties, key)) {
+        a->free(a->pool, key.ptr);
+        return 0;
+    }
+    if(ucx_map_sstr_put(response->properties, key, property)) {
+        return 1; // OOM
+    }
+    a->free(a->pool, key.ptr);
+    
+    // list of namespace definitions for this property
+    WebdavNSList *nsdef_begin = NULL;
+    WebdavNSList *nsdef_end = NULL;
+    
+    // add namespace of this property to the namespace map
+    // the namespace map will be used for global namespace definitions
+    if(property->namespace->prefix) {
+        WSNamespace *ns = ucx_map_cstr_get(
+                response->multistatus->namespaces,
+                (const char*)property->namespace->prefix);
+        if(!ns) {
+            // prefix is not in use -> we can add the namespace to the ns map
+            int err = ucx_map_cstr_put(
+                    response->multistatus->namespaces,
+                    (const char*)property->namespace->prefix,
+                    property->namespace);
+            if(err) {
+                return 1; // OOM
+            }
+        } else if(
+                strcmp((const char*)property->namespace->href,
+                (const char*)ns->href))
+        {
+            // global namespace != local namespace
+            // therefore we need a namespace definition in this element
+            
+            // ns-prefix != property-prefix -> add ns to nsdef
+            if(webdav_nslist_add(
+                    sn->pool,
+                    &nsdef_begin,
+                    &nsdef_end,
+                    property->namespace))
+            {
+                return 1; // OOM
+            }
+        }
+    }
+    
+    if(response->multistatus->proppatch && response->errors) {
+        // in a proppatch request all operations must succeed
+        // if we have an error, the property update status code must be
+        // 424 Failed Dependency
+        status = 424;
+    }
+    
+    // error properties will be added to a separate list
+    if(status != 200) { 
+        return msresponse_addproperror(response, property, status);
+    }
+    
+    // add all namespaces used by this property to the nsdef list
+    WebdavNSList *nslist = NULL;
+    if(property->vtype == WS_VALUE_XML_NODE) {
+        // iterate over xml tree and collect all namespaces
+        int err = 0;
+        nslist = wsxml_get_required_namespaces(
+                response->multistatus->sn->pool,
+                property->value.node,
+                &err);
+        if(err) {
+            return 1; // OOM
+        }
+    } else if(property->vtype == WS_VALUE_XML_DATA) {
+        // xml data contains a list of all used namespaces
+        nslist = property->value.data.namespaces;
+    } // other value types don't contain xml namespaces
+    
+    while(nslist) {
+        // only add the namespace to the definitions list, if it isn't a
+        // property namespace, because the prop ns is already added
+        // to the element's def list or global definitions list
+        if(strcmp(
+                (const char*)nslist->namespace->prefix,
+                (const char*)property->namespace->prefix))
+        {
+            // ns-prefix != property-prefix -> add ns to nsdef
+            if(webdav_nslist_add(
+                    sn->pool,
+                    &nsdef_begin,
+                    &nsdef_end,
+                    nslist->namespace))
+            {
+                return 1; // OOM
+            }
+        }
+        nslist = nslist->next;
+    }
+    
+    // add property to the list
+    if(oklist_add(
+            sn->pool,
+            &response->plist_begin,
+            &response->plist_end,
+            property,
+            nsdef_begin))
+    {
+        return 1;
+    }
+    return 0;
+}
+
+int msresponse_addproperror(
+        MSResponse *response,
+        WebdavProperty *property,
+        int statuscode)
+{
+    pool_handle_t *pool = response->multistatus->sn->pool;
+    UcxAllocator *a = session_get_allocator(response->multistatus->sn);
+    
+    response->resource.err++;
+      
+    // MSResponse contains a list of properties for each status code
+    // at first find the list for this status code
+    PropertyErrorList *errlist = NULL;
+    PropertyErrorList *list = response->errors;
+    PropertyErrorList *last = NULL;
+    while(list) {
+        if(list->status == statuscode) {
+            errlist = list;
+            break;
+        }
+        last = list;
+        list = list->next;
+    }
+    
+    if(!errlist) {
+        // no list available for this statuscode
+        PropertyErrorList *newelm = pool_malloc(pool,
+                sizeof(PropertyErrorList));
+        if(!newelm) {
+            return 1;
+        }
+        newelm->begin = NULL;
+        newelm->end = NULL;
+        newelm->next = NULL;
+        newelm->status = statuscode;
+        
+        if(last) {
+            last->next = newelm;
+        } else {
+            response->errors = newelm;
+        }
+        errlist = newelm;
+    }
+    
+    // we have the list -> add the new element
+    if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) {
+        return 1;
+    }
+    return 0;
+}
+
+int msresponse_close(WebdavResource *res) {
+    MSResponse *response = (MSResponse*)res;
+    if(response->closing) {
+        return 0; // close already in progress
+    }
+    response->closing = TRUE;
+    Multistatus *ms = response->multistatus;
+    
+    int ret = REQ_PROCEED;
+    
+    // PROPFIND:
+    // response_close will execute propfind_do of all remaining backends
+    // after that we will have all available properties
+    WebdavOperation *op = ms->response.op;
+    if(op->response_close(op, res)) {
+        ret = REQ_ABORTED;
+    }
+    
+    // add missing properties with status code 404
+    UcxAllocator *a = session_get_allocator(ms->sn);
+    WebdavPList *pl = ms->response.op->reqprops;
+    while(pl) {
+        sstr_t key = sstrcat_a(
+            a,
+            3,
+            sstr((char*)pl->property->namespace->href),
+            S("\0"),
+            sstr((char*)pl->property->name));
+        if(!ucx_map_sstr_get(response->properties, key)) {
+            // property was not added to this response
+            if(ms->proppatch) {
+                if(msresponse_addproperror(response, pl->property, 424)) {
+                    ret = REQ_ABORTED;
+                    break;
+                }
+            } else {
+                if(msresponse_addproperror(response, pl->property, 404)) {
+                    ret = REQ_ABORTED;
+                    break;
+                }
+            }
+        }
+        
+        pl = pl->next;
+    }
+    
+    if(ms->proppatch && response->errors) {
+        // a proppatch response must succeed entirely
+        // if we have a single error prop, move all props with status 200
+        // to the error list
+        PropertyOkList *elm = response->plist_begin;
+        PropertyOkList *nextelm;
+        while(elm) {
+            if(msresponse_addproperror(response, elm->property, 424)) {
+                return 1;
+            }
+            nextelm = elm->next;
+            pool_free(response->multistatus->sn->pool, elm);
+            elm = nextelm;
+        }
+        response->plist_begin = NULL;
+        response->plist_end = NULL;
+    }
+    
+    // we don't need the properties anymore
+    ucx_map_free(response->properties);
+    
+    response->resource.isclosed = TRUE;
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/multistatus.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,157 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef MULTISTATUS_H
+#define MULTISTATUS_H
+
+#include "../public/webdav.h"
+
+#include <ucx/map.h>
+#include <ucx/buffer.h>
+#include <libxml/tree.h>
+#include "../util/writer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Multistatus Multistatus;
+typedef struct MSResponse MSResponse;
+
+typedef struct PropertyOkList PropertyOkList;
+typedef struct PropertyErrorList PropertyErrorList;
+
+/*
+ * implements the WebdavResponse interface
+ */
+struct Multistatus {
+    WebdavResponse response;
+    Session *sn;
+    Request *rq;
+    MSResponse *first;
+    MSResponse *current;
+    
+    /*
+     * Map of document namespace definitions
+     * 
+     * key: (char*) namespace prefix
+     * value: WSNamespace*
+     */
+    UcxMap *namespaces;
+    
+    /*
+     * Is this a proppatch request?
+     * 
+     * In a proppatch response, when the first property with an error occurs,
+     * all already added properties will be set to 424 Failed Dependency.
+     */
+    WSBool proppatch;
+};
+
+/*
+ * implements the WebdavResource interface
+ */
+struct MSResponse {
+    WebdavResource resource;
+    Multistatus *multistatus;
+    
+    /*
+     * Contains all properties that were added to the response
+     * key: <href> null-byte <name>
+     * value: WebdavProperty*
+     */
+    UcxMap *properties;
+    
+    /*
+     * All properties with status != 200
+     */
+    PropertyErrorList *errors;
+    
+    /*
+     * All properties with status == 200
+     */
+    PropertyOkList *plist_begin;
+    PropertyOkList *plist_end;
+    
+    MSResponse *next;
+    WSBool closing;
+};
+
+struct PropertyOkList {
+    WebdavProperty *property;
+    WebdavNSList   *nsdef;
+    PropertyOkList *next;
+};
+
+struct PropertyErrorList {
+    /*
+     * next list for different status code
+     */
+    PropertyErrorList *next;
+    
+    /*
+     * property list for all properties with this status code
+     */
+    WebdavPList *begin;
+    
+    /*
+     * tail of the property list
+     */
+    WebdavPList *end;
+    
+    /*
+     * property response status code
+     */
+    int status;
+};
+
+Multistatus* multistatus_response(Session *sn, Request *rq);
+
+int multistatus_send(Multistatus *ms, SYS_NETFD out);
+
+WebdavResource * multistatus_addresource(
+        WebdavResponse *response,
+        const char *path);
+
+int msresponse_addproperty(
+        WebdavResource *res,
+        WebdavProperty *property,
+        int status);
+
+int msresponse_addproperror(
+        MSResponse *response,
+        WebdavProperty *property,
+        int statuscode);
+
+int msresponse_close(WebdavResource *res);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MULTISTATUS_H */
+
--- a/src/server/webdav/objs.mk	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/objs.mk	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 #
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 #
-# Copyright 2013 Olaf Wintermann. All rights reserved.
+# Copyright 2019 Olaf Wintermann. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
@@ -31,6 +31,12 @@
 DAV_OBJPRE = $(OBJ_DIR)$(DAV_SRC_DIR)
 
 DAVOBJ = webdav.o
+DAVOBJ += xml.o
+DAVOBJ += requestparser.o
+DAVOBJ += operation.o
+DAVOBJ += multistatus.o
+DAVOBJ += search.o
+DAVOBJ += versioning.o
 
 DAVOBJS = $(DAVOBJ:%=$(DAV_OBJPRE)%)
 DAVSOURCE = $(DAVOBJ:%.o=webdav/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/operation.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,753 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <ucx/list.h>
+
+#include "../daemon/session.h"
+#include "../util/pblock.h"
+
+#include "operation.h"
+
+#define WEBDAV_PATH_MAX 8192
+
+
+size_t webdav_num_backends(WebdavBackend *dav) {
+    size_t count = 0;
+    while(dav) {
+        count++;
+        dav = dav->next;
+    }
+    return count;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPFIND OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_propfind_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavPList *reqprops,
+        UcxList *requests,
+        WebdavResponse *response)
+{
+    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
+    ZERO(op, sizeof(WebdavOperation));
+    op->dav = dav;
+    op->sn = sn;
+    op->rq = rq;
+    op->reqprops = reqprops;
+    op->requests = requests;
+    op->response = response;
+    op->response_close = webdav_op_propfiond_close_resource;
+    response->op = op;
+    
+    return op;
+}
+
+int webdav_op_propfind_begin(
+        WebdavOperation *op,
+        const char *href,
+        VFS_DIR parent,
+        struct stat *s)
+{
+    // create WebdavResource object for requested resource
+    WebdavResource *resource = op->response->addresource(op->response, href);
+    if(!resource) {
+        return REQ_ABORTED;
+    }
+    
+    // store data that we need when the resource will be closed
+    op->stat = s;
+    op->parent = parent;
+    
+    // get first propfind object
+    WebdavPropfindRequest *propfind = op->requests->data;
+    
+    // execute propfind_do of the first backend for the first resource
+    int ret = REQ_PROCEED;
+    if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) {
+        ret = REQ_ABORTED;
+    } else {
+        // propfind_do successful, close resource if needed
+        // closing the resource will execute propfind_do of all remaining
+        // backends
+        if(!resource->isclosed) {
+            ret = resource->close(resource);
+        }
+    }
+    
+    return ret;
+}
+
+typedef struct PathSearchElm {
+    char   *href;
+    char   *path;
+    size_t hreflen;
+    size_t pathlen;
+} PathSearchElm;
+
+/*
+ * concats base + / + elm
+ * if baseinit is true, only elm is copied
+ */
+static int path_buf_concat(
+        pool_handle_t *pool,
+        char **buf,
+        size_t * restrict len,
+        WSBool * restrict baseinit,
+        const char *base,
+        size_t baselen,
+        const char *elm,
+        size_t elmlen)
+{
+    if(base[baselen-1] == '/') {
+        baselen--;
+    }
+    
+    size_t newlen = baselen + elmlen + 1;
+    if(newlen > WEBDAV_PATH_MAX) {
+        log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
+        return 1;
+    }
+    
+    // check if new path + terminator fits in the buffer
+    if(newlen + 1 > *len) {
+        *len = newlen + 128;
+        char *newbuf = pool_realloc(pool, *buf, newlen);
+        if(newbuf) {
+            log_ereport(LOG_FAILURE, "webdav: path memory allocation failed");
+            return 1;
+        }
+        *baseinit = FALSE;
+        
+        *buf = newbuf;
+    }
+    
+    // if baseinit is true, the parent is already in the buffer
+    // and we don't need to memcpy it again
+    if(!(*baseinit)) {
+        memcpy(*buf, base, baselen);
+        (*buf)[baselen] = '/';
+        *baseinit = TRUE;
+    }
+    // copy child and terminate string
+    memcpy((*buf) + baselen + 1, elm, elmlen);
+    (*buf)[newlen] = '\0';
+    
+    return 0;
+}
+
+static int propfind_child_cb(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *op)
+{
+    return webdav_op_propfind_begin(op, href, parent, s);
+}
+
+int webdav_op_propfind_children(
+        WebdavOperation *op,
+        VFSContext *vfs,
+        const char *href,
+        const char *path)
+{
+    WebdavPropfindRequest *request = op->requests->data;
+    return webdav_op_iterate_children(
+            vfs, request->depth, href, path, propfind_child_cb, op);
+}
+
+int webdav_op_propfiond_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource)
+{
+    // start with second backend and request, because
+    // the first one was already called by webdav_op_propfind_begin
+    WebdavBackend *dav = op->dav->next;
+    UcxList *request = op->requests->next;
+    
+    // call propfind_do of all remaining backends
+    int ret = REQ_PROCEED;
+    while(dav && request) {
+        if(dav->propfind_do(
+                request->data,
+                op->response,
+                op->parent,
+                resource,
+                op->stat))
+        {
+            ret = REQ_ABORTED;
+        }
+        
+        dav = dav->next;
+        request = request->next;
+    }
+    return ret;
+}
+
+/*
+ * Executes propfind_finish for each Backend
+ */
+int webdav_op_propfind_finish(WebdavOperation *op) {
+    WebdavBackend *dav = op->dav;
+    UcxList *requests = op->requests;
+    
+    int ret = REQ_PROCEED;
+    while(dav && requests) {
+        if(dav->propfind_finish(requests->data)) {
+            ret = REQ_ABORTED;
+        }
+        
+        dav = dav->next;
+        requests = requests->next;
+    }
+    return ret;
+}
+
+/****************************************************************************
+ * 
+ *                         PROPPATCH OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavOperation* webdav_create_proppatch_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavProppatchRequest *proppatch,
+        WebdavResponse *response)
+{
+    WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
+    ZERO(op, sizeof(WebdavOperation));
+    op->dav = dav;
+    op->sn = sn;
+    op->rq = rq;
+    op->reqprops = NULL;
+    op->response = response;
+    op->proppatch = proppatch;
+    op->response_close = webdav_op_proppatch_close_resource;
+    response->op = op;
+    
+    return op;
+}
+
+
+
+int webdav_op_proppatch(
+        WebdavOperation *op,
+        const char *href,
+        const char *path)
+{
+    WebdavProppatchRequest *orig_request = op->proppatch;
+    UcxAllocator *a = session_get_allocator(op->sn);
+    
+    // create WebdavResource object for the requested resource
+    WebdavResource *resource = op->response->addresource(op->response, href);
+    if(!resource) {
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *ctx = NULL;
+    VFSFile *file = NULL;
+    
+    // requests for each backends
+    WebdavProppatchRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavProppatchRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavPList *prev_set = orig_request->set;
+    WebdavPList *prev_remove = orig_request->remove;
+    size_t set_count = orig_request->setcount;
+    size_t remove_count = orig_request->removecount;
+    
+    int ret = REQ_PROCEED;
+    
+    // iterate backends and execute proppatch_do
+    WebdavBackend *dav = op->dav;
+    size_t numrequests = 0;
+    while(dav) {
+        WebdavPList *set = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_set,
+                &set_count);
+        WebdavPList *remove = webdav_plist_clone_s(
+                op->sn->pool,
+                prev_remove,
+                &remove_count);
+        if((prev_set && !set) || (prev_remove && !remove)) {
+            // clone failed, OOM
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // create new WebdavProppatchRequest object for this backend
+        WebdavProppatchRequest *req = pool_malloc(
+                op->sn->pool,
+                sizeof(WebdavProppatchRequest));
+        memcpy(req, orig_request, sizeof(WebdavProppatchRequest));
+        req->dav = dav;
+        req->set = orig_request->set;
+        req->setcount = orig_request->setcount;
+        req->remove = orig_request->remove;
+        req->removecount = orig_request->removecount;
+        req->userdata = NULL;
+        
+        // check if we need to open the file because the backend wants it
+        if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS)
+                         == WS_WEBDAV_PROPPATCH_USE_VFS)
+        {
+            ctx = vfs_request_context(op->sn, op->rq);
+            if(!ctx) {
+                ret = REQ_ABORTED;
+                break;
+            }
+            
+            file = vfs_open(ctx, path, O_RDONLY);
+            if(!file) {
+                protocol_status(
+                        op->sn,
+                        op->rq,
+                        util_errno2status(ctx->vfs_errno),
+                        NULL);
+                ret = REQ_ABORTED;
+            }
+        }
+        
+        // execute proppatch_do
+        if(dav->proppatch_do(req, resource, file, &set, &remove)) {
+            // return later, because we need do execute proppatch_finish
+            // for all successfully called backends
+            ret = REQ_ABORTED;
+            break;
+        }
+        
+        // proppatch_do should remove all handled props from set and remove
+        // in the next iteration, the backend must use these reduced lists
+        prev_set = set;
+        prev_remove = remove;
+        
+        requests[numrequests++] = req;
+        
+        // continue with next backend
+        dav = dav->next;
+    }
+    
+    WSBool commit = FALSE;
+    if(ret == REQ_PROCEED && resource->err == 0) {
+        // no errors, no properties with errors -> save the changes
+        commit = TRUE; 
+    }
+    
+    // call proppatch_finish for each successfully called proppatch_do
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < numrequests) {
+        if(dav->proppatch_finish(requests[i], resource, file, commit)) {
+            ret = REQ_ABORTED;
+        }
+        i++;
+        dav = dav->next;
+    }
+    
+    if(file) {
+        vfs_close(file);
+    }
+    
+    if(resource->close(resource)) {
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+int webdav_op_proppatch_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource)
+{
+    return 0; // NOP
+}
+
+
+/****************************************************************************
+ * 
+ *                             VFS OPERATION
+ * 
+ ****************************************************************************/
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition)
+{
+    WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation));
+    if(!op) {
+        return NULL;
+    }
+    ZERO(op, sizeof(WebdavVFSOperation));
+    
+    op->sn = sn;
+    op->rq = rq;
+    op->dav = dav;
+    op->stat = NULL;
+    op->stat_errno = 0;
+    
+    // create VFS context
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        pool_free(sn->pool, op);
+        return NULL;
+    }
+    op->vfs = vfs;
+    
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    op->path = path;  
+    
+    return op;
+}
+
+WebdavVFSOperation webdav_vfs_sub_op(
+        WebdavVFSOperation *op,
+        char *path,
+        struct stat *s)
+{
+    WebdavVFSOperation sub;
+    sub.dav = op->dav;
+    sub.path = path;
+    sub.sn = op->sn;
+    sub.vfs = op->vfs;
+    sub.path = path;
+    sub.stat = s;
+    sub.stat_errno = 0;
+    return sub;
+}
+
+int webdav_op_iterate_children(
+        VFSContext *vfs,
+        int depth,
+        const char *href,
+        const char *path,
+        vfs_op_child_func func,
+        void *userdata)
+{
+    UcxAllocator *a = session_get_allocator(vfs->sn);
+    pool_handle_t *pool = vfs->sn->pool;
+    
+    PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm));
+    start_elm->href = pool_strdup(pool, href ? href : "");
+    start_elm->path = pool_strdup(pool, path ? path : "");
+    start_elm->hreflen = href ? strlen(href) : 0;
+    start_elm->pathlen = path ? strlen(path) : 0;
+    
+    UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm);
+    UcxList *stack_end = stack;
+    if(!stack) {
+        return 1;
+    }
+    
+    // reusable buffer for full child path and href
+    char *newpath = pool_malloc(pool, 256);
+    size_t newpathlen = 256;
+    
+    char *newhref = pool_malloc(pool, 256);
+    size_t newhreflen = 256;
+    
+    int err = 0;
+    while(stack && !err) {
+        PathSearchElm *cur_elm = stack->data;
+        
+        // when newpath is initialized with the parent path
+        // set path_buf_init to TRUE
+        WSBool href_buf_init = FALSE;
+        WSBool path_buf_init = FALSE;
+        
+        VFS_DIR dir = vfs_opendir(vfs, cur_elm->path);
+        if(!dir) {
+            log_ereport(
+                    LOG_FAILURE,
+                    "webdav: propfind: cannot open directory %d",
+                    vfs->vfs_errno);
+            err = 1;
+            break;
+        }
+        
+        VFS_ENTRY f;
+        while(vfs_readdir_stat(dir, &f)) {
+            if(f.stat_errno != 0) {
+                continue;
+            }
+            
+            size_t child_len = strlen(f.name);
+            
+            // create new path and href for the child
+            if(path_buf_concat(
+                    pool,
+                    &newhref,
+                    &newhreflen,
+                    &href_buf_init,
+                    cur_elm->href,
+                    cur_elm->hreflen,
+                    f.name,
+                    child_len))
+            {
+                err = 1;
+                break;
+            }
+            if(path_buf_concat(
+                    pool,
+                    &newpath,
+                    &newpathlen,
+                    &path_buf_init,
+                    cur_elm->path,
+                    cur_elm->pathlen,
+                    f.name,
+                    child_len))
+            {
+                err = 1;
+                break;
+            }
+            size_t childhreflen = cur_elm->hreflen + 1 + child_len;
+            size_t childpathlen = cur_elm->pathlen + 1 + child_len;
+            
+            // execute callback func for this file
+            if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) {
+                err = 1;
+                break;
+            }
+            
+            // depth of -1 means infinity
+            if(depth == -1 && S_ISDIR(f.stat.st_mode)) {
+                char *hrefcp = pool_malloc(pool, childhreflen + 1);
+                memcpy(hrefcp, newhref, childhreflen + 1);
+                hrefcp[childhreflen] = '\0';
+                
+                char *pathcp = pool_malloc(pool, childpathlen + 1);
+                memcpy(pathcp, newpath, childpathlen + 1);
+                pathcp[childpathlen] = '\0';
+                
+                PathSearchElm *new_elm = pool_malloc(pool,
+                                            sizeof(PathSearchElm));
+                new_elm->href = hrefcp;
+                new_elm->path = pathcp;
+                new_elm->hreflen = childhreflen;
+                new_elm->pathlen = childpathlen;
+                
+                // add the new_elm to the stack
+                // stack_end is always not NULL here, because we remove
+                // the first stack element at the end of the loop
+                UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm);
+                if(!newlistelm) {
+                    err = 1;
+                    break;
+                }
+                stack_end = newlistelm;
+            }
+        }
+        
+        vfs_closedir(dir);
+        
+        pool_free(pool, cur_elm->path);
+        pool_free(pool, cur_elm->href);
+        pool_free(pool, cur_elm);
+        
+        stack = ucx_list_remove_a(a, stack, stack);
+    }
+    
+    // in case of an error, we have to free all remaining stack elements
+    UCX_FOREACH(elm, stack) {
+        char *data = elm->data;
+        if(data != path) {
+            pool_free(pool, data);
+        }
+    }
+    
+    return err;
+}
+
+
+int webdav_vfs_stat(WebdavVFSOperation *op) {
+    if(op->stat) {
+        return 0;
+    } else if(op->stat_errno != 0) {
+        // previous stat failed
+        return 1;
+    }
+    
+    // stat file
+    struct stat sbuf;
+    int ret = vfs_stat(op->vfs, op->path, &sbuf);
+    if(!ret) {
+        // save result in op->stat and in s
+        op->stat = pool_malloc(op->sn->pool, sizeof(struct stat));
+        if(op->stat) {
+            memcpy(op->stat, &sbuf, sizeof(struct stat));
+        } else {
+            ret = 1;
+            op->stat_errno = ENOMEM;
+        }
+    } else {
+        op->stat_errno = errno;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) {
+    WSBool exec_vfs = TRUE;
+    
+    // requests for each backends
+    WebdavVFSRequest **requests = pool_calloc(
+            op->sn->pool,
+            webdav_num_backends(op->dav),
+            sizeof(WebdavVFSRequest*));
+    if(requests == NULL) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = REQ_PROCEED;
+    
+    // call opt_* func for each backend
+    WebdavBackend *dav = op->dav;
+    int called_backends = 0;
+    while(dav) {    
+        WebdavVFSRequest *request = NULL;
+        
+        // get vfs operation functions
+        vfs_op_func        op_func        = NULL;
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_func        = dav->opt_mkcol;
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_func        = dav->opt_delete;
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_func || op_finish_func) {
+            // we need a request object
+            request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest));
+            if(!request) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+            request->sn = op->sn;
+            request->rq = op->rq;
+            request->path = op->path;
+            request->userdata = NULL;
+            
+            requests[called_backends] = request;
+        }
+        
+        // exec backend func for this operation
+        // this will set 'done' to TRUE, if no further vfs call is required
+        WSBool done = FALSE;
+        called_backends++;
+        if(op_func) {
+            if(op_func(request, &done)) {
+                exec_vfs = FALSE;
+                ret = REQ_ABORTED;
+                break;
+            }
+        }
+        if(done) {
+            exec_vfs = FALSE;
+        }
+        
+        dav = dav->next;
+    }
+    
+    // if needed, call vfs func for this operation
+    if(exec_vfs) {
+        int r = 0;
+        if(type == WEBDAV_VFS_MKDIR) {
+            r = vfs_mkdir(op->vfs, op->path);
+        } else if(type == WEBDAV_VFS_DELETE) {
+            r = webdav_vfs_unlink(op);
+        }
+        
+        if(r) {
+            ret = REQ_ABORTED;
+        }
+    }
+    
+    WSBool success = ret == REQ_PROCEED ? TRUE : FALSE;
+    
+    // finish mkcol (cleanup) by calling opt_*_finish for each backend
+    dav = op->dav;
+    int i = 0;
+    while(dav && i < called_backends) {
+        // get vfs operation functions
+        vfs_op_finish_func op_finish_func = NULL;
+        
+        if(type == WEBDAV_VFS_MKDIR) {
+            op_finish_func = dav->opt_mkcol_finish;
+        } else if(type == WEBDAV_VFS_DELETE) {
+            op_finish_func = dav->opt_delete_finish;
+        }
+        
+        if(op_finish_func) {
+            if(op_finish_func(requests[i], success)) {
+                ret = REQ_ABORTED; // don't exit loop
+            }
+        }
+        
+        dav = dav->next;
+        i++;
+    }
+    
+    return ret;
+}
+
+int webdav_vfs_unlink(WebdavVFSOperation *op) {
+    // stat the file first, to check if the file is a directory
+    if(webdav_vfs_stat(op)) {
+        return 1; // error
+    } else {
+        if(!S_ISDIR(op->stat->st_mode)) {
+            return vfs_unlink(op->vfs, op->path);
+        } else {
+            return vfs_rmdir(op->vfs, op->path);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/operation.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,199 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef OPERATION_H
+#define OPERATION_H
+
+#include "../public/webdav.h"
+#include <ucx/list.h>
+#include <ucx/map.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int(*response_close_func)(WebdavOperation *, WebdavResource *);
+
+typedef struct WebdavVFSOperation WebdavVFSOperation;
+
+typedef struct WebdavCopy         WebdavCopy;
+typedef struct CopyResource       CopyResource;
+    
+struct WebdavOperation {
+    WebdavBackend          *dav;
+    Request                *rq;
+    Session                *sn;
+    
+    WebdavProppatchRequest *proppatch;    /* proppatch request or NULL */
+    WebdavPList            *reqprops;     /* requested properties */
+    UcxList                *requests;     /* backend specific request objects */
+    
+    WebdavResponse         *response;
+    
+    response_close_func    response_close;
+    
+    VFS_DIR                parent;        /* current directory */
+    struct stat            *stat;         /* current stat object */
+};
+
+struct WebdavVFSOperation {
+    WebdavBackend          *dav;
+    Request                *rq;
+    Session                *sn;
+    
+    VFSContext             *vfs;
+    
+    char                   *path;
+    struct stat            *stat;
+    int                    stat_errno;
+};
+
+struct WebdavCopy {
+    WebdavResponse response;
+    Session *sn;
+    Request *rq;
+    CopyResource *current;
+    
+    char *src_href;
+    char *src_path;
+    char *dst_href;
+    char *dst_path;
+};
+
+struct CopyResource {
+    WebdavResource resource;
+    UcxMap *properties;
+};
+
+enum WebdavVFSOpType {
+    WEBDAV_VFS_MKDIR = 0,
+    WEBDAV_VFS_DELETE
+};
+
+typedef enum WebdavVFSOpType WebdavVFSOpType;
+
+typedef int(*vfs_op_func)(WebdavVFSRequest *, WSBool *);
+typedef int(*vfs_op_finish_func)(WebdavVFSRequest *, WSBool);
+
+typedef int(*vfs_op_child_func)(
+        VFSContext *,
+        const char *,                     /* href */
+        const char *,                     /* path */
+        VFSDir *,                         /* parent dir */
+        struct stat *,                    /* child stat */
+        void *);                          /* user data */
+
+/*
+ * counts the number of backends
+ */
+size_t webdav_num_backends(WebdavBackend *dav);
+
+WebdavOperation* webdav_create_propfind_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavPList *reqprops,
+        UcxList *requests,
+        WebdavResponse *response);
+
+int webdav_op_propfind_begin(
+        WebdavOperation *op,
+        const char *href,
+        VFS_DIR parent,
+        struct stat *s);
+
+int webdav_op_propfind_children(
+        WebdavOperation *op,
+        VFSContext *vfs,
+        const char *href,
+        const char *path);
+
+int webdav_op_propfiond_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource);
+
+int webdav_op_propfind_finish(WebdavOperation *op);
+
+WebdavOperation* webdav_create_proppatch_operation(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WebdavProppatchRequest *proppatch,
+        WebdavResponse *response);
+
+int webdav_op_proppatch(
+        WebdavOperation *op,
+        const char *href,
+        const char *path);
+
+int webdav_op_proppatch_close_resource(
+        WebdavOperation *op,
+        WebdavResource *resource);
+
+
+WebdavVFSOperation* webdav_vfs_op(
+        Session *sn,
+        Request *rq,
+        WebdavBackend *dav,
+        WSBool precondition);
+
+WebdavVFSOperation webdav_vfs_sub_op(
+        WebdavVFSOperation *op,
+        char *path,
+        struct stat *s);
+
+int webdav_op_iterate_children(
+        VFSContext *vfs,
+        int depth,
+        const char *href,
+        const char *path,
+        vfs_op_child_func func,
+        void *userdata);
+
+int webdav_vfs_stat(WebdavVFSOperation *op);
+
+int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type);
+
+int webdav_vfs_unlink(WebdavVFSOperation *op);
+
+
+WebdavCopy* webdav_copy_create(
+        Session *sn,
+        Request *rq,
+        VFSContext *vfs,
+        char *from_href,
+        char *from_path,
+        char *to_href,
+        char *to_path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OPERATION_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/requestparser.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,496 @@
+/*
+ * 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"
+#include "webdav.h"
+
+#define xstreq(a, b) !strcmp((const char*)a, (const char*)b)
+
+void proplist_free(pool_handle_t *pool, WebdavPList *list) {
+    while(list) {
+        WebdavPList *next = list->next;
+        pool_free(pool, list);
+        list = next;
+    }
+}
+
+WebdavProperty* prop_create(
+        pool_handle_t *pool,
+        WSNamespace *ns,
+        const char *name)
+{
+    WebdavProperty *prop = pool_malloc(pool, sizeof(WebdavProperty));
+    memset(prop, 0, sizeof(WebdavProperty));
+    prop->lang = NULL;
+    prop->name = (char*)name;
+    prop->namespace = ns;
+    return prop;
+}
+
+static 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 = webdav_property_key((const char*)ns, (const char*)name);
+        if(!k.data) {
+            *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            return 1;
+        }
+        void *c = ucx_map_get(propmap, k);
+        if(!c) {
+            if(ucx_map_put(propmap, k, (void*)1)) {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+            
+            // no duplicate
+            // create property elment and add it to the list
+            WebdavProperty *prop = prop_create(sn->pool, pnode->ns, name);
+            if(proppatch) {
+                prop->value.node = pnode->children;
+                prop->vtype = WS_VALUE_XML_NODE;
+            }
+            if(prop) {
+                if(webdav_plist_add(sn->pool, plist_begin, plist_end, prop)) {
+                    *error = proppatch ?
+                        PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+                }
+                (*propcount)++;
+            } else {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+        } else if(proppatch) {
+            *error = PROPPATCH_PARSER_DUPLICATE;
+        }
+        
+        free((void*)k.data);
+        if(*error) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+WebdavPropfindRequest* propfind_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPFIND_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    WSBool allprop = FALSE;
+    WSBool propname = FALSE;
+    WebdavPList *plist_begin = NULL;
+    WebdavPList *plist_end = NULL;
+    size_t propcount = 0;
+    int depth = webdav_getdepth(rq);
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is DAV:propfind
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "propfind")))
+    {
+        *error = PROPFIND_PARSER_NO_PROPFIND;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    UcxMap *propmap = ucx_map_new(32);
+    if(!propmap) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->ns->href, "DAV:") && !allprop && !propname) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "prop")) {
+                    ret = parse_prop(
+                            sn,
+                            node,
+                            propmap,
+                            &plist_begin,
+                            &plist_end,
+                            &propcount,
+                            0, // proppatch = false
+                            error);
+                } else if(xstreq(node->name, "allprop")) {
+                    allprop = TRUE;
+                } else if(xstreq(node->name, "propname")) {
+                    propname = TRUE;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // no allocated content must be freed
+    
+    if(ret) {
+        // parse_prop failed
+        // in this case, error is already set
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    if(!allprop && !propname && propcount == 0) {
+        *error = PROPFIND_PARSER_NO_PROPERTIES;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPropfindRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavPropfindRequest));
+    if(!request) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->properties = NULL;
+    request->propcount = 0;
+    request->depth = depth;
+    request->doc = doc;
+    if(allprop) {
+        request->allprop = TRUE;
+        request->propname = FALSE; // we cannot have allprop and propname
+    } else if(propname) {
+        request->allprop = FALSE;
+        request->propname = TRUE;
+    } else {
+        request->allprop = FALSE;
+        request->propname = FALSE;
+        request->properties = plist_begin;
+        request->propcount = propcount;
+    }
+    
+    if(!request->properties && plist_begin) {
+        proplist_free(sn->pool, plist_begin);
+    }
+    return request;
+}
+
+WebdavProppatchRequest* proppatch_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    return webdav_parse_set(
+            sn, rq, buf, buflen, "DAV:", "propertyupdate", TRUE, error);
+}
+
+static xmlNode* find_child(xmlNode *node, const char *name) {
+    xmlNode *c = node->children;
+    while(c) {
+        if(c->ns) {
+            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, name)) {
+                return c;
+            }
+        }
+        c = c->next;
+    }
+    return NULL;
+}
+
+WebdavProppatchRequest* webdav_parse_set(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        const char *rootns,
+        const char *rootname,
+        WSBool allowremove,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, rootns)
+            && xstreq(root->name, rootname)))
+    {
+        *error = PROPPATCH_PARSER_NO_PROPERTYUPDATE;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    UcxMap *propmap = ucx_map_new(32); // map for duplicate checking
+    if(!propmap) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPList *set_begin = NULL;
+    WebdavPList *set_end = NULL;
+    WebdavPList *remove_begin = NULL;
+    WebdavPList *remove_end = NULL;
+    size_t set_count = 0;
+    size_t remove_count = 0;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(node->ns && xstreq(node->ns->href, "DAV:")) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "set")) {
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &set_begin,
+                            &set_end,
+                            &set_count,
+                            TRUE, // proppatch = true
+                            error);
+                } else if(xstreq(node->name, "remove")) {
+                    if(!allowremove) {
+                        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+                        ret = 1;
+                        break;
+                    }
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &remove_begin,
+                            &remove_end,
+                            &remove_count,
+                            TRUE, // proppatch = true
+                            error);
+                }
+                    
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // allocated content must not be freed
+    
+    if(set_count + remove_count == 0) {
+        *error = PROPPATCH_PARSER_NO_PROPERTIES;
+        ret = 1;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavProppatchRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavProppatchRequest));
+    if(!request) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    request->set = set_begin;
+    request->setcount = set_count;
+    request->remove = remove_begin;
+    request->removecount = remove_count;
+    return request;
+}
+
+WebdavLockRequest* lock_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = LOCK_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "lockinfo")))
+    {
+        *error = LOCK_PARSER_NO_LOCKINFO;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockScope lockscope = WEBDAV_LOCK_SCOPE_UNKNOWN;
+    WebdavLockType locktype = WEBDAV_LOCK_TYPE_UNKNOWN;
+    WSXmlNode *owner = NULL;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(
+                node->type == XML_ELEMENT_NODE
+                && node->ns
+                && xstreq(node->ns->href, "DAV:"))
+        {
+            char *name = (char*)node->name;
+            if(xstreq(name, "lockscope")) {
+                xmlNode *s = node->children;
+                while(s) {
+                    if(
+                            s->type == XML_ELEMENT_NODE
+                            && s->ns
+                            && xstreq(s->ns->href, "DAV:"))
+                    {
+                        if(xstreq(s->name, "exclusive")) {
+                            lockscope = WEBDAV_LOCK_EXCLUSIVE;
+                        } else if(xstreq(s->name, "shared")) {
+                            lockscope = WEBDAV_LOCK_SHARED;
+                        } else {
+                            // don't ignore unknown lockscope
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    s = s->next;
+                }
+            } else if(xstreq(name, "locktype")) {
+                xmlNode *t = node->children;
+                while(t) {
+                    if(
+                            t->type == XML_ELEMENT_NODE
+                            && t->ns
+                            && xstreq(t->ns->href, "DAV:"))
+                    {
+                        if(xstreq(t->name, "write")) {
+                            locktype = WEBDAV_LOCK_WRITE;
+                        } else {
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    t = t->next;
+                }
+            } else if(xstreq(name, "owner")) {
+                owner = node->children;
+            }
+        }
+        node = node->next;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavLockRequest));
+    if(!request) {
+        *error = LOCK_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    
+    if(locktype == WEBDAV_LOCK_TYPE_UNKNOWN) {
+        locktype = WEBDAV_LOCK_WRITE;
+    }
+    if(lockscope == WEBDAV_LOCK_SCOPE_UNKNOWN) {
+        lockscope = WEBDAV_LOCK_EXCLUSIVE;
+    }
+    request->scope = lockscope;
+    request->type = locktype;
+    request->owner = owner;
+    return request;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/requestparser.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,106 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef REQUESTPARSER_H
+#define REQUESTPARSER_H
+
+#include "../public/webdav.h"
+
+#include <libxml/tree.h>
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define PROPFIND_PARSER_OK                   0
+#define PROPFIND_PARSER_NO_PROPFIND          1
+#define PROPFIND_PARSER_NO_PROPERTIES        2
+#define PROPFIND_PARSER_INVALID_REQUEST      3
+#define PROPFIND_PARSER_OOM                  4
+#define PROPFIND_PARSER_ERROR                5
+    
+#define PROPPATCH_PARSER_OK                  0
+#define PROPPATCH_PARSER_NO_PROPERTYUPDATE   1
+#define PROPPATCH_PARSER_NO_PROPERTIES       2
+#define PROPPATCH_PARSER_INVALID_REQUEST     3
+#define PROPPATCH_PARSER_DUPLICATE           4
+#define PROPPATCH_PARSER_OOM                 5
+#define PROPPATCH_PARSER_ERROR               6
+
+#define LOCK_PARSER_OK                       0
+#define LOCK_PARSER_NO_LOCKINFO              1
+#define LOCK_PARSER_INVALID_REQUEST          2
+#define LOCK_PARSER_UNKNOWN_ELEMENT          3
+#define LOCK_PARSER_OOM                      4
+#define LOCK_PARSER_ERROR                    5
+
+
+WebdavProperty* prop_create(
+        pool_handle_t *pool,
+        WSNamespace *ns,
+        const char *name);
+    
+WebdavPropfindRequest* propfind_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+WebdavProppatchRequest* proppatch_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+WebdavProppatchRequest* webdav_parse_set(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        const char *rootns,
+        const char *rootname,
+        WSBool allowremove,
+        int *error);
+
+WebdavLockRequest* lock_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* REQUESTPARSER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/search.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,33 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "search.h"
+
+int webdav_search (pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/search.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SEARCH_H
+#define SEARCH_H
+
+#include "../public/webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int webdav_search (pblock *pb, Session *sn, Request *rq);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SEARCH_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/versioning.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "versioning.h"
+
+int webdav_version_control(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_checkout(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_checkin(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_uncheckout(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_update(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_label(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_merge(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/versioning.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef VERSIONING_H
+#define VERSIONING_H
+
+#include "../public/webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int webdav_version_control(pblock *pb, Session *sn, Request *rq);
+int webdav_checkout(pblock *pb, Session *sn, Request *rq);
+int webdav_checkin(pblock *pb, Session *sn, Request *rq);
+int webdav_uncheckout(pblock *pb, Session *sn, Request *rq);
+int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq);
+int webdav_update(pblock *pb, Session *sn, Request *rq);
+int webdav_label(pblock *pb, Session *sn, Request *rq);
+int webdav_merge(pblock *pb, Session *sn, Request *rq);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VERSIONING_H */
+
--- a/src/server/webdav/webdav.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/webdav.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,1268 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <ucx/buffer.h>
+#include <ucx/list.h>
+
 #include "webdav.h"
 
+#include "search.h"
+#include "versioning.h"
+#include "multistatus.h"
+#include "requestparser.h"
+#include "operation.h"
 
+#include "../util/pblock.h"
+#include "../util/util.h"
+#include "../daemon/session.h"
+#include "../daemon/http.h"
+#include "../daemon/protocol.h"
+#include "../daemon/vfs.h"
+
+/*
+ * http method fptr mapping
+ * key: http method name (string)
+ * value: SAF fptr
+ */
+static UcxMap *method_handler_map;
+
+/*
+ * webdav backend types
+ * key: backend name (string)
+ * value: WebdavBackend*
+ */
+static UcxMap *webdav_type_map;
+
+static WebdavBackend default_backend;
+
+static WSNamespace dav_namespace;
+
+static WebdavProperty dav_resourcetype_empty;
+static WebdavProperty dav_resourcetype_collection;
+static WSXmlData dav_resourcetype_collection_value;
+
+#define WEBDAV_RESOURCE_TYPE_COLLECTION "<D:collection/>"
+
+static void init_default_backend(void) {
+    memset(&default_backend, 0, sizeof(WebdavBackend));
+    default_backend.propfind_init = default_propfind_init;
+    default_backend.propfind_do = default_propfind_do;
+    default_backend.propfind_finish = default_propfind_finish;
+    default_backend.proppatch_do = default_proppatch_do;
+    default_backend.proppatch_finish = default_proppatch_finish;
+    default_backend.opt_mkcol = NULL;
+    default_backend.opt_delete = NULL;
+    default_backend.settings = WS_WEBDAV_PROPFIND_USE_VFS;
+    default_backend.instance = NULL;
+}
+
+int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate) {
+    WebdavType *webdavType = malloc(sizeof(WebdavType));
+    webdavType->init = webdavInit;
+    webdavType->create = webdavCreate;
+    return ucx_map_cstr_put(webdav_type_map, name, webdavType);
+}
+
+WebdavType* webdav_get_type(scstr_t dav_class) {
+    return ucx_map_sstr_get(webdav_type_map, dav_class);
+}
+
+void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error) {
+    *error = 0;
+    if(dav_class->init) {
+        void *initData = dav_class->init(cfg, pool, config);
+        if(!initData) {
+            *error = 1;
+        }
+        return initData;
+    } else {
+        return NULL;
+    }
+}
+
+WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData) {
+    WebdavType *webdavType = ucx_map_cstr_get(webdav_type_map, dav_class);
+    if(!webdavType) {
+        log_ereport(LOG_MISCONFIG, "webdav_create: unkown dav type %s", dav_class);
+        return NULL;
+    }
+    
+    return webdavType->create(sn, rq, pb, initData);
+}
+
+static WSBool webdav_is_initialized = FALSE;
+
+int webdav_init(pblock *pb, Session *sn, Request *rq) {
+    if(webdav_is_initialized) {
+        return REQ_NOACTION;
+    }
+    webdav_is_initialized = TRUE;
+    
+    webdav_type_map = ucx_map_new(8);
+    if(!webdav_type_map) {
+        return REQ_ABORTED;
+    }
+    
+    method_handler_map = ucx_map_new(64);
+    if(!method_handler_map) {
+        return REQ_ABORTED;
+    }
+    
+    init_default_backend();
+    ucx_map_cstr_put(webdav_type_map, "default", &default_backend);
+    
+    ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options);
+    ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind);
+    ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch);
+    ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol);
+    ucx_map_cstr_put(method_handler_map, "POST", webdav_post);
+    ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete);
+    ucx_map_cstr_put(method_handler_map, "PUT", webdav_put);
+    ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy);
+    ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move);
+    ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock);
+    ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock);
+    ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report);
+    ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl);
+    
+    ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search);
+    
+    ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control);
+    ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout);
+    ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin);
+    ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout);
+    ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace);
+    ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update);
+    ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label);
+    ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge);
+    
+    dav_namespace.href = (xmlChar*)"DAV:";
+    dav_namespace.prefix = (xmlChar*)"D";
+    
+    dav_resourcetype_empty.namespace = &dav_namespace;
+    dav_resourcetype_empty.name = "resourcetype";
+    
+    dav_resourcetype_collection_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION;
+    dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1;
+    
+    dav_resourcetype_collection.namespace = &dav_namespace;
+    dav_resourcetype_collection.name = "resourcetype";
+    dav_resourcetype_collection.value.data = dav_resourcetype_collection_value;
+    dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA;
+    
+    return REQ_PROCEED;
+}
+
+
+int webdav_service(pblock *pb, Session *sn, Request *rq) {
+    if(!method_handler_map) {
+        log_ereport(LOG_FAILURE, "WebDAV module not initialized");
+        protocol_status(sn, rq, 500, NULL);
+        return REQ_ABORTED;
+    }
+    char *method = pblock_findkeyval(pb_key_method, rq->reqpb);
+    
+    FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method);
+    if(!saf) {
+        return REQ_NOACTION;
+    }
+    
+    return saf(pb, sn, rq);
+}
+
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq) {
+    if(!sn->inbuf) {
+        //request body required, set http response code
+        protocol_status(sn, rq, 400, NULL);
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(
+            NULL,
+            sn->inbuf->maxsize,
+            UCX_BUFFER_AUTOEXTEND);
+    if(!buf) {
+        protocol_status(sn, rq, 500, NULL);
+        return NULL;
+    }
+    
+    char in[2048];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        if(ucx_buffer_write(in, 1, r, buf) != r) {
+            protocol_status(sn, rq, 500, NULL);
+            ucx_buffer_free(buf);
+            return NULL;
+        }
+    }
+    
+    return buf;
+}
+
+int webdav_options(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    UcxAllocator *a = session_get_allocator(sn);
+    
+    int error = 0;
+    WebdavPropfindRequest *propfind = propfind_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!propfind) {
+        switch(error) {
+            // TODO: handle all errors
+            default: return REQ_ABORTED;
+        }
+    }
+    
+    WebdavBackend *dav =  rq->davCollection ?
+                              rq->davCollection : &default_backend;
+    
+    
+    // requested uri and path
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    // The multistatus response object contains responses for all
+    // requested resources. At the end the Multistatus object will be
+    // serialized to xml
+    Multistatus *ms = multistatus_response(sn, rq);
+    if(!ms) {
+        return REQ_ABORTED;
+    }
+    
+    
+    int ret = webdav_propfind_do(dav, propfind, (WebdavResponse*)ms, NULL, path, uri);
+    
+    // if propfind was successful, send the result to the client
+    if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) {
+        ret = REQ_ABORTED;
+        // TODO: log error
+    } else {
+        // TODO: error response
+    }
+    
+    // cleanup
+    if(propfind->doc) {
+        xmlFreeDoc(propfind->doc);
+    }
+    
+    return ret;
+}
+
+/*
+ * Initializes Backend Chain
+ * 
+ * Calls propfind_init of each Backend and generates a list of custom
+ * WebdavPropfindRequest objects for each backend
+ */
+int webdav_propfind_init(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *uri,
+        UcxList **out_req)
+{   
+    pool_handle_t *pool = propfind->sn->pool;
+    UcxAllocator *a = session_get_allocator(propfind->sn);
+    
+    // list of individual WebdavPropfindRequest objects for each Backend
+    UcxList *requestObjects = NULL;
+    
+    // new properties after init, start with clone of original plist
+    WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties);
+    size_t newPropCount = propfind->propcount;
+    
+    // Call propfind_init for each Backend
+    // propfind_init can return a new property list, which
+    // will be passed to the next backend
+
+    WebdavBackend *davList = dav;
+    while(davList) {
+        // create WebdavPropfindRequest copy
+        WebdavPropfindRequest *pReq = pool_malloc(
+                pool,
+                sizeof(WebdavPropfindRequest));
+        memcpy(pReq, propfind, sizeof(WebdavPropfindRequest));
+        // use new plist after previous init (or orig. plist in the first run)
+        pReq->properties = newProp;
+        pReq->propcount = newPropCount;
+        pReq->dav = davList;
+        
+        // add new WebdavPropfindRequest object to list for later use
+        requestObjects = ucx_list_append_a(a, requestObjects, pReq);
+        if(!requestObjects) {
+            return REQ_ABORTED; // OOM
+        }
+        
+        // create plist copy as out-plist for init
+        newProp = webdav_plist_clone(pool, newProp);
+        
+        // run init: this can generate a new properties list (newProp)
+        //           which will be passed to the next backend
+        if(davList->propfind_init(pReq, path, uri, &newProp)) {
+            return REQ_ABORTED;
+        }
+        
+        newPropCount = webdav_plist_size(newProp);
+        
+        davList = davList->next;
+    }
+    
+    *out_req = requestObjects;
+    return REQ_PROCEED;
+}
+
+int webdav_propfind_do(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path,
+        char *uri)
+{
+    Session *sn = propfind->sn;
+    Request *rq = propfind->rq;
+    
+    // VFS settings are only taken from the first backend
+    uint32_t settings = dav->settings;
+    
+    // list of individual WebdavPropfindRequest objects for each Backend
+    UcxList *requestObjects = NULL;
+    
+    // Initialize all Webdav Backends
+    if(webdav_propfind_init(dav, propfind, path, uri, &requestObjects)) {
+        return REQ_ABORTED;
+    }
+    
+    WebdavOperation *op = webdav_create_propfind_operation(
+            sn,
+            rq,
+            dav,
+            propfind->properties,
+            requestObjects,
+            response);
+    
+    // some Backends can list all children by themselves, but some
+    // require the VFS for this
+    WSBool usevfs = (settings & WS_WEBDAV_PROPFIND_USE_VFS)
+                        == WS_WEBDAV_PROPFIND_USE_VFS;
+    struct stat s;
+    struct stat *statptr = NULL;
+    
+    if(usevfs && !vfs) {
+        vfs = vfs_request_context(sn, rq);
+        if(!vfs) {
+            return REQ_ABORTED;
+        }
+        
+        if(vfs_stat(vfs, path, &s)) {
+            protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+            return REQ_ABORTED;
+        }
+        statptr = &s;
+        if(!S_ISDIR(s.st_mode)) {
+            // the file is not a directory, therefore we don't need the VFS
+            usevfs = FALSE;
+        }
+    }
+    if(propfind->depth == 0) {
+        usevfs = FALSE;
+    }
+    
+    int ret = REQ_PROCEED;
+    
+    // create WebdavResource object for requested resource
+    if(!webdav_op_propfind_begin(op, uri, NULL, statptr)) {
+        // propfind for the requested resource was successful
+        
+        // usevfsdir is TRUE if
+        //   the webdav backend has not disabled vfs usage
+        //   the file is a directory
+        //   depth is not 0
+        // in this case we need to execute propfind_do for all children
+        if(usevfs) {
+            if(webdav_op_propfind_children(op, vfs, uri, path)) {
+                ret = REQ_ABORTED;
+            }
+        }
+    }
+    
+    // finish the propfind request
+    // this function should cleanup all resources, therefore we execute it
+    // even if a previous function failed
+    if(webdav_op_propfind_finish(op)) {
+        // TODO: log error
+        ret = REQ_ABORTED;
+    }
+    
+    return ret;
+}
+
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    UcxBuffer *reqbody = rqbody2buffer(sn, rq);
+    if(!reqbody) {
+        return REQ_ABORTED;
+    }
+    
+    int error = 0;
+    WebdavProppatchRequest *proppatch = proppatch_parse(
+            sn,
+            rq,
+            reqbody->space,
+            reqbody->size,
+            &error);
+    ucx_buffer_free(reqbody);
+    if(!proppatch) {
+        switch(error) {
+            // TODO: handle all errors
+            default: return REQ_ABORTED;
+        }
+    }
+     
+    WebdavBackend *dav =  rq->davCollection ?
+                              rq->davCollection : &default_backend;
+    
+    // requested uri and path
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    // The multistatus response object contains responses for all
+    // requested resources. At the end the Multistatus object will be
+    // serialized to xml
+    Multistatus *ms = multistatus_response(sn, rq);
+    if(!ms) {
+        return REQ_ABORTED;
+    }
+    ms->proppatch = TRUE;
+    
+    // WebdavResponse is the public interface used by Backends
+    // for adding resources to the response
+    WebdavResponse *response = (WebdavResponse*)ms;
+    
+    WebdavOperation *op = webdav_create_proppatch_operation(
+            sn,
+            rq,
+            dav,
+            proppatch,
+            response);
+    
+    int ret = REQ_PROCEED;
+    
+    // Execute proppatch
+    if(webdav_op_proppatch(op, uri, path)) {
+        ret = REQ_ABORTED;
+    }
+    
+    // send response
+    if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) {
+        ret = REQ_ABORTED;
+        // TODO: log error
+    } else {
+        // TODO: error response
+    }
+    
+    // cleanup
+    xmlFreeDoc(proppatch->doc);
+    
+    return ret;
+}
+
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    int ret = REQ_ABORTED;
+    if(!webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR)) {
+        pblock_nvinsert("content-length", "0", rq->srvhdrs);
+        protocol_status(sn, rq, 201, NULL);
+        protocol_start_response(sn, rq);
+        ret = REQ_PROCEED;
+    } else {
+        int status_code = 500;
+        if(op->vfs->vfs_errno == EEXIST) {
+            // 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL.
+            status_code = 405;
+        } else if(op->vfs->vfs_errno == ENOENT) {
+            // 409 (Conflict) - A collection cannot be made at the Request-URI until
+            // one or more intermediate collections have been created.  The server
+            // MUST NOT create those intermediate collections automatically.
+            status_code = 409;
+        }
+        protocol_status(sn, rq, status_code, NULL);
+    }
+    
+    return ret;
+}
+
+int webdav_post(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+typedef struct DeleteFile {
+    char *path;
+    struct stat s;
+} DeleteFile;
+
+typedef struct DeleteLists {
+    UcxAllocator *a;
+    UcxList *dirs_begin;
+    UcxList *dirs_end;
+    UcxList *files_begin;
+    UcxList *files_end;
+} DeleteOp;
+
+static int deletelist_add(
+        VFSContext *vfs,
+        const char *href,
+        const char *path,
+        VFSDir *parent,
+        struct stat *s,
+        void *userdata)
+{
+    DeleteOp *op = userdata;
+    
+    // create object for this file
+    DeleteFile *file = almalloc(op->a, sizeof(DeleteFile));
+    if(!file) {
+        return 1;
+    }
+    file->path = sstrdup_a(op->a, sstr((char*)path)).ptr;
+    if(!file->path) {
+        return 1;
+    }
+    file->s = *s;
+    
+    // determine which list to use
+    UcxList **begin;
+    UcxList **end;
+    if(S_ISDIR(s->st_mode)) {
+        begin = &op->dirs_begin;
+        end = &op->dirs_end;
+    } else {
+        begin = &op->files_begin;
+        end = &op->files_end;
+    }
+    
+    // add file to list
+    UcxList *elm = ucx_list_append_a(op->a, NULL, file);
+    if(!elm) {
+        alfree(op->a, file->path); // at least do some cleanup, although it
+        alfree(op->a, file);       // isn't really necessary
+        return 1;
+    }
+    if(*begin == NULL) {
+        *begin = elm;
+        *end = elm;
+    } else {
+        ucx_list_concat(*end, elm);
+        *end = elm;
+    }
+    
+    return 0;
+}
+
+static int webdav_delete_collection(WebdavVFSOperation *op)
+{
+    DeleteOp del;
+    ZERO(&del, sizeof(DeleteOp));
+    del.a = session_get_allocator(op->sn);
+    
+    // get a list of all files
+    if(webdav_op_iterate_children(op->vfs, -1, NULL, op->path,
+            deletelist_add, &del))
+    {
+        return 1;
+    }
+    
+    // add root to list of dir list
+    DeleteFile root;
+    root.path = op->path;
+    root.s = *op->stat;
+    UcxList root_elm;
+    root_elm.data = &root;
+    root_elm.prev = NULL;
+    root_elm.next = del.dirs_begin;
+    
+    if(del.dirs_begin) {
+        del.dirs_begin->prev = &root_elm;
+        del.dirs_begin = &root_elm;
+    } else {
+        del.dirs_begin = &root_elm;
+        del.dirs_end = &root_elm;
+    }
+    
+    // delete files first
+    UCX_FOREACH(elm, del.files_begin) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    // delete directories, reverse order
+    for(UcxList *elm=del.dirs_end;elm;elm=elm->prev) {
+        DeleteFile *file = elm->data;
+        WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s);
+        if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+int webdav_delete(pblock *pb, Session *sn, Request *rq) { 
+    WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE);
+    if(!op) {
+        return REQ_ABORTED;
+    }
+    
+    // stat to find out if the resource is a collection
+    struct stat s;
+    if(vfs_stat(op->vfs, op->path, &s)) {
+        sys_set_error_status(op->vfs);
+        return REQ_ABORTED;
+    }
+    op->stat = &s;
+    
+    int ret;
+    if(S_ISDIR(s.st_mode)) {
+        ret = webdav_delete_collection(op);
+    } else {
+        ret = webdav_vfs_op_do(op, WEBDAV_VFS_DELETE);
+    }
+    
+    // send response
+    if(ret == REQ_PROCEED) {
+        pblock_nvinsert("content-length", "0", rq->srvhdrs);
+        protocol_status(sn, rq, 204, NULL);
+        protocol_start_response(sn, rq);
+    } else {
+        protocol_status(sn, rq, 500, NULL);
+    }
+    
+    return ret;
+}
+
+int webdav_put(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat s;
+    int create_file = 0;
+    if(vfs_stat(vfs, path, &s)) {
+        if(vfs->vfs_errno == ENOENT) {
+            create_file = O_CREAT;
+        } else {
+            protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+            return REQ_ABORTED;
+        }
+    } else if(S_ISDIR(s.st_mode)) {
+        // PUT on collections is not allowed
+        protocol_status(sn, rq, PROTOCOL_METHOD_NOT_ALLOWED, NULL);
+        return REQ_ABORTED;
+    }
+    
+    SYS_FILE fd = vfs_open(vfs, path, O_WRONLY | O_TRUNC | create_file);
+    if(!fd) {
+        // if it fails, vfs_open sets http status code
+        return REQ_ABORTED;
+    }
+    
+    // TODO: check permissions, lock, ...
+    
+    // all checks done
+    
+    char *expect = pblock_findkeyval(pb_key_expect, rq->headers);
+    if(expect) {
+        if(!strcasecmp(expect, "100-continue")) {
+            if(http_send_continue(sn)) {
+                return REQ_ABORTED;
+            }
+        }
+    }
+    
+    char in[4096];
+    int r;
+    while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
+        int w = 0;
+        while(w < r) {
+            w += system_fwrite(fd, in, r);
+        }
+    }
+    
+    system_fclose(fd);
+    
+    int status = create_file ? PROTOCOL_CREATED : PROTOCOL_NO_CONTENT;
+    pblock_nvinsert("content-length", "0", rq->srvhdrs);
+    protocol_status(sn, rq, status, NULL);
+    protocol_start_response(sn, rq);
+    
+    return REQ_PROCEED;
+}
+
+int webdav_copy(pblock *pb, Session *sn, Request *rq) {
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
+    
+    char *destination = pblock_findval("destination", rq->headers);
+    if(!destination) {
+        protocol_status(sn, rq, PROTOCOL_BAD_REQUEST, NULL);
+        return REQ_ABORTED;
+    }
+    
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    if(!vfs) {
+        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
+        return REQ_ABORTED;
+    }
+    
+    struct stat src_s;
+    if(vfs_stat(vfs, path, &src_s)) {
+        protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL);
+        return REQ_ABORTED;
+    }
+    
+    // TODO: if src is a directory, make sure the uri has a trailing path separator
+    
+    
+    return REQ_ABORTED;
+}
+
+int webdav_move(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_lock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_unlock(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_report(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+int webdav_acl(pblock *pb, Session *sn, Request *rq) {
+    return REQ_ABORTED;
+}
+
+
+
+/* ------------------------ default webdav backend  ------------------------ */
+
+int default_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char* path,
+        const char *href,
+        WebdavPList **outplist)
+{
+    DefaultWebdavData *data = pool_malloc(
+            rq->sn->pool,
+            sizeof(DefaultWebdavData));
+    if(!data) {
+        return 1;
+    }
+    rq->userdata = data;
+    
+    data->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0);
+    
+    return 0;
+}
+
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        WebdavResource *resource,
+        struct stat *s)
+{
+    DefaultWebdavData *data = request->userdata;
+    
+    // add all requested vfs properties like getcontentlength ...
+    if(webdav_add_vfs_properties(
+            resource,
+            request->sn->pool,
+            data->vfsproperties,
+            s))
+    {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int default_propfind_finish(WebdavPropfindRequest *rq) {
+    return 0;
+}
+
+int default_proppatch_do(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WebdavPList **setInOut,
+            WebdavPList **removeInOut)
+{
+    return 0;
+}
+
+int default_proppatch_finish(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WSBool commit)
+{
+    return 0;
+}
+
+
+
+/* ------------------------------ Utils ------------------------------ */
+
+UcxKey webdav_property_key(const char *ns, const char *name) {
+    UcxKey key;
+    sstr_t data = ucx_sprintf("%s\n%s", name, ns);
+    key.data = data.ptr;
+    key.len = data.length;
+    key.hash = ucx_hash(data.ptr, data.length);
+    return key;
+}
+
+
+
+
+/* ------------------------------ public API ------------------------------ */
+
+int webdav_getdepth(Request *rq) {
+    char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers);
+    int depth = 0;
+    if(depth_str) {
+        size_t dlen = strlen(depth_str);
+        if(!memcmp(depth_str, "infinity", dlen)) {
+            depth = -1;
+        } else if(dlen == 1 && depth_str[0] == '1') {
+            depth = 1;
+        }
+    }
+    return depth;
+}
+
+int webdav_plist_add(
+        pool_handle_t *pool,
+        WebdavPList **begin,
+        WebdavPList **end,
+        WebdavProperty *prop)
+{
+    WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList));
+    if(!elm) {
+        return 1;
+    }
+    elm->prev = *end;
+    elm->next = NULL;
+    elm->property = prop;
+    
+    if(!*begin) {
+        *begin = elm;
+        *end = elm;
+        return 0;
+    }
+    
+    (*end)->next = elm;
+    *end = elm;
+    
+    return 0;
+}
+
+WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) {
+    return webdav_plist_clone_s(pool, list, NULL);
+}
+
+WebdavPList* webdav_plist_clone_s(
+        pool_handle_t *pool,
+        WebdavPList *list,
+        size_t *newlen)
+{
+    WebdavPList *new_list = NULL;     // start of the new list
+    WebdavPList *new_list_end = NULL; // end of the new list
+    
+    size_t len = 0;
+    
+    WebdavPList *elm = list;
+    while(elm) {
+        // copy list item
+        WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList));
+        if(!new_elm) {
+            if(newlen) *newlen = 0;
+            return NULL;
+        }
+        new_elm->property = elm->property; // new list contains original ptr
+        new_elm->prev = new_list_end;
+        new_elm->next = NULL;
+        
+        if(new_list_end) {
+            new_list_end->next = new_elm;
+        } else {
+            new_list = new_elm;
+        }
+        new_list_end = new_elm;
+        
+        len++;
+        elm = elm->next;
+    }
+    
+    if(newlen) *newlen = len;
+    return new_list;
+}
+
+size_t webdav_plist_size(WebdavPList *list) {
+    size_t count = 0;
+    WebdavPList *elm = list;
+    while(elm) {
+        count++;
+        elm = elm->next;
+    }
+    return count;
+}
+
+WebdavPListIterator webdav_plist_iterator(WebdavPList **list) {
+    WebdavPListIterator i;
+    i.list = list;
+    i.cur = NULL;
+    i.next = *list;
+    i.index = 0;
+    return i;
+}
+
+int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur) {
+    if(i->cur) {
+        i->index++;
+    }
+    
+    i->cur = i->next;
+    i->next = i->cur ? i->cur->next : NULL;
+    *cur = i->cur;
+    
+    return i->cur != NULL;
+}
+
+void webdav_plist_iterator_remove_current(WebdavPListIterator *i) {
+    WebdavPList *cur = i->cur;
+    if(cur->prev) {
+        cur->prev->next = cur->next;
+        if(cur->next) {
+            cur->next->prev = cur->prev;
+        }
+    } else {
+        *i->list = cur->next;
+        if(cur->next) {
+            cur->next->prev = NULL;
+        }
+    }
+}
+
+int webdav_nslist_add(
+        pool_handle_t *pool,
+        WebdavNSList **begin,
+        WebdavNSList **end,
+        WSNamespace *ns)
+{
+    // same as webdav_plist_add but with different type
+    WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList));
+    if(!elm) {
+        return 1;
+    }
+    elm->prev = *end;
+    elm->next = NULL;
+    elm->namespace = ns;
+    
+    if(!*begin) {
+        *begin = elm;
+        *end = elm;
+        return 0;
+    }
+    
+    (*end)->next = elm;
+    *end = elm;
+    
+    return 0;
+}
+
+
+WSNamespace* webdav_dav_namespace(void) {
+    return &dav_namespace;
+}
+
+WebdavProperty* webdav_resourcetype_collection(void) {
+    return &dav_resourcetype_collection;
+}
+
+WebdavProperty* webdav_resourcetype_empty(void) {
+    return &dav_resourcetype_empty;
+}
+
+WebdavProperty* webdav_dav_property(
+        pool_handle_t *pool,
+        const char *name)
+{
+    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+    if(!property) {
+        return NULL;
+    }
+    memset(property, 0, sizeof(WebdavProperty));
+    
+    property->namespace = &dav_namespace;
+    property->name = name;
+    return property;
+}
+
+int webdav_resource_add_dav_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *name,
+        const char *str,
+        size_t len)
+{
+    WebdavProperty *property = webdav_dav_property(pool, name);
+    if(!property) {
+        return 1;
+    }
+    
+    property->name = pool_strdup(pool, name);
+    if(!property->name) {
+        return 1;
+    }
+    
+    char *value = pool_malloc(pool, len+1);
+    if(!value) {
+        return 1;
+    }
+    memcpy(value, str, len);
+    value[len] = '\0';
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    return res->addproperty(res, property, 200);
+}
+
+int webdav_resource_add_stringproperty(
+        WebdavResource *res,
+        pool_handle_t pool,
+        const char *xmlns_prefix,
+        const char *xmlns_href,
+        const char *name,
+        const char *str,
+        size_t len)
+{
+    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+    if(!property) {
+        return 1;
+    }
+    memset(property, 0, sizeof(WebdavProperty));
+    
+    property->name = pool_strdup(pool, name);
+    if(!property->name) {
+        return 1;
+    }
+    
+    xmlNs *ns = pool_malloc(pool, sizeof(xmlNs));
+    if(!ns) {
+        return 1;
+    }
+    memset(ns, 0, sizeof(xmlNs));
+    ns->prefix = (const xmlChar*)pool_strdup(pool, xmlns_prefix);
+    ns->href = (const xmlChar*)pool_strdup(pool, xmlns_href);
+    if(!ns->prefix || !ns->href) {
+        return 1;
+    }
+    
+    char *value = pool_malloc(pool, len+1);
+    if(!value) {
+        return 1;
+    }
+    memcpy(value, str, len);
+    value[len] = '\0';
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    property->value.text.str = value;
+    property->value.text.length = len;
+    property->vtype = WS_VALUE_TEXT;
+    
+    return res->addproperty(res, property, 200);
+}
+
+int webdav_property_set_value(
+        WebdavProperty *p,
+        pool_handle_t *pool,
+        char *value)
+{
+    WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode));
+    if(!node) {
+        return 1;
+    }
+    ZERO(node, sizeof(WSXmlNode));
+    
+    node->content = (xmlChar*)value;
+    node->type = XML_TEXT_NODE;
+    
+    p->value.node = node;
+    p->vtype = WS_VALUE_XML_NODE;
+    return 0;
+}
+
+WebdavVFSProperties webdav_vfs_properties(
+        WebdavPList **plistInOut,
+        WSBool removefromlist,
+        WSBool allprop,
+        uint32_t flags)
+{
+    WebdavVFSProperties ret;
+    ZERO(&ret, sizeof(WebdavVFSProperties));
+    
+    WSBool etag = 1;
+    WSBool creationdate = 1;
+    
+    WebdavPListIterator i = webdav_plist_iterator(plistInOut);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WSNamespace *ns = cur->property->namespace;
+        if(ns && !strcmp((const char*)ns->href, "DAV:")) {
+            const char *name = cur->property->name;
+            WSBool remove_prop = removefromlist;
+            if(!strcmp(name, "getlastmodified")) {
+                ret.getlastmodified = 1;
+            } else if(!strcmp(name, "getcontentlength")) {
+                ret.getcontentlength = 1;
+            } else if(!strcmp(name, "resourcetype")) {
+                ret.getresourcetype = 1;
+            } else if(etag && !strcmp(name, "getetag")) {
+                ret.getetag = 1;
+            } else if(creationdate && !strcmp(name, "creationdate")) {
+                ret.creationdate = 1;
+            } else {
+                remove_prop = FALSE;
+            }
+            
+            if(remove_prop) {
+                webdav_plist_iterator_remove_current(&i);
+            }
+        }
+    }
+    
+    if(allprop) {
+        ret.creationdate = 1;
+        ret.getcontentlength = 1;
+        ret.getetag = 1;
+        ret.getlastmodified = 1;
+        ret.getresourcetype = 1;
+    }
+    
+    return ret;
+}
+
+int webdav_add_vfs_properties(
+        WebdavResource *res,
+        pool_handle_t *pool,
+        WebdavVFSProperties properties,
+        struct stat *s)
+{
+    if(properties.getresourcetype) {
+        if(S_ISDIR(s->st_mode)) {
+            res->addproperty(res, &dav_resourcetype_collection, 200);
+        } else {
+            res->addproperty(res, &dav_resourcetype_empty, 200);
+        }
+    }
+    if(properties.getcontentlength) {
+        char *buf = pool_malloc(pool, 64);
+        if(!buf) {
+            return 1;
+        }
+        uint64_t contentlength = s->st_size;
+        snprintf(buf, 64, "%" PRIu64, contentlength);
+        if(webdav_resource_add_dav_stringproperty(res, pool, "getcontentlength", buf, strlen(buf))) {
+            return 1;
+        }
+    }
+    if(properties.getlastmodified) {
+        char *buf = pool_malloc(pool, HTTP_DATE_LEN+1);
+        if(!buf) {
+            return 1;
+        }
+        buf[HTTP_DATE_LEN] = 0;
+        
+        struct tm mtms;
+        struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms);
+        
+        if(mtm) {
+            strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
+            if(webdav_resource_add_dav_stringproperty(res, pool, "getlastmodified", buf, strlen(buf))) {
+                return 1;
+            }
+        } else {
+            return 1;
+        }
+    }
+    if(properties.creationdate) {
+        // TODO
+    }
+    if(properties.getetag) {
+        char *buf = pool_malloc(pool, 96);
+        if(!buf) {
+            return 1;
+        }
+        snprintf(buf,
+            96,
+            "\"%x-%x\"",
+            (int)s->st_size,
+            (int)s->st_mtim.tv_sec);
+        if(webdav_resource_add_dav_stringproperty(res, pool, "getetag", buf, strlen(buf))) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
--- a/src/server/webdav/webdav.h	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/server/webdav/webdav.h	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2013 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,16 +30,100 @@
 #define	WEBDAV_H
 
 #include "../public/webdav.h"
-#include "../util/strbuf.h"
 
 #include <ucx/map.h>
 #include <ucx/list.h>
+#include <ucx/buffer.h>
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
+   
+typedef struct WebdavType {
+    webdav_init_func   init;
+    webdav_create_func create;
+} WebdavType;
+    
+typedef struct DefaultWebdavData {
+    WebdavVFSProperties vfsproperties;
+} DefaultWebdavData;
+    
+WebdavType* webdav_get_type(scstr_t dav_class);
+
+void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error);  
+
+int webdav_init(pblock *pb, Session *sn, Request *rq);
+    
+int webdav_service(pblock *pb, Session *sn, Request *rq);
+
+/*
+ * returns a buffer containing the request body
+ * 
+ * this function sets an http response code in case of an error
+ * or missing request body
+ */
+UcxBuffer* rqbody2buffer(Session *sn, Request *rq);
 
 
+int webdav_options(pblock *pb, Session *sn, Request *rq);
+
+int webdav_propfind(pblock *pb, Session *sn, Request *rq);
+
+int webdav_propfind_init(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        const char *path,
+        const char *uri,
+        UcxList **out_req);
+
+int webdav_propfind_do(
+        WebdavBackend *dav,
+        WebdavPropfindRequest *propfind,
+        WebdavResponse *response,
+        VFSContext *vfs,
+        char *path,
+        char *uri);
+
+
+int webdav_proppatch(pblock *pb, Session *sn, Request *rq);
+int webdav_mkcol(pblock *pb, Session *sn, Request *rq);
+int webdav_post(pblock *pb, Session *sn, Request *rq);
+int webdav_delete(pblock *pb, Session *sn, Request *rq);
+int webdav_put(pblock *pb, Session *sn, Request *rq);
+int webdav_copy(pblock *pb, Session *sn, Request *rq);
+int webdav_move(pblock *pb, Session *sn, Request *rq);
+int webdav_lock(pblock *pb, Session *sn, Request *rq);
+int webdav_unlock(pblock *pb, Session *sn, Request *rq);
+int webdav_report(pblock *pb, Session *sn, Request *rq);
+int webdav_acl(pblock *pb, Session *sn, Request *rq);
+int webdav_search (pblock *pb, Session *sn, Request *rq);
+
+
+int default_propfind_init(
+        WebdavPropfindRequest *rq,
+        const char *path,
+        const char *href,
+        WebdavPList **outplist);
+int default_propfind_do(
+        WebdavPropfindRequest *request,
+        WebdavResponse *response,
+        VFS_DIR parent,
+        WebdavResource *resource,
+        struct stat *s);
+int default_propfind_finish(WebdavPropfindRequest *rq);
+int default_proppatch_do(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WebdavPList **setInOut,
+            WebdavPList **removeInOut);
+int default_proppatch_finish(
+            WebdavProppatchRequest *request,
+            WebdavResource *response,
+            VFSFile *file,
+            WSBool commit);
+
+UcxKey webdav_property_key(const char *ns, const char *name);
 
 #ifdef	__cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,734 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ucx/string.h>
+#include <ucx/map.h>
+#include <ucx/buffer.h>
+
+#include "../util/util.h"
+#include "../util/pool.h"
+
+#include "xml.h"
+
+/*****************************************************************************
+ *    Utility functions
+ *****************************************************************************/
+
+/*
+ * generates a string key for an xml namespace
+ * format: prefix '\0' href
+ */
+static sstr_t xml_namespace_key(UcxAllocator *a, WSNamespace *ns) {
+    sstr_t key = sstrcat_a(a, 3,
+            ns->prefix ? sstr((char*)ns->prefix) : S("\0"),
+            S("\0"),
+            sstr((char*)ns->href));
+    return key;
+}
+
+
+/*****************************************************************************
+ *    Public functions
+ *****************************************************************************/
+
+/* ------------------------ wsxml_iterator ------------------------ */
+
+typedef struct StackElm {
+    WSXmlNode *node; // list of nodes
+    //WSXmlNode *parent; // if not NULL, call endcb after node->next is NULL
+    int endonly;
+    struct StackElm *next;
+} StackElm;
+
+#define STACK_PUSH(stack, elm) if(stack) { elm->next = stack; } stack = elm;
+
+int wsxml_iterator(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        wsxml_func begincb,
+        wsxml_func endcb,
+        void *udata)
+{  
+    if(!node) {
+        return 0;
+    }
+    
+    StackElm *stack = pool_malloc(pool, sizeof(StackElm));
+    if(!stack) {
+        return 1; // OOM
+    }
+    stack->next = NULL;
+    stack->node = node;
+    stack->endonly = 0;
+    //stack->parent = NULL;
+    
+    int ret = 0;
+    int br = 0;
+    while(stack) {
+        StackElm *cur = stack;
+        WSXmlNode *xmlnode = cur->node; // get top stack element
+        stack = cur->next;              // and remove it
+        cur->next = NULL;
+        
+        while(xmlnode && !cur->endonly) {
+            // element begin callback
+            if(begincb(xmlnode, udata)) {
+                br = 1;
+                break; // I don't like break with labels - is this wrong?
+            }
+            
+            if(xmlnode->children) {
+                // put the children on the stack
+                // the next stack iteration will process the children
+                StackElm *newelm = pool_malloc(pool, sizeof(StackElm));
+                if(!newelm) {
+                    ret = 1;
+                    br = 1;
+                    break;
+                }
+                newelm->next = NULL;
+                newelm->node = xmlnode->children;
+                // setting the parent will make sure endcb will be called
+                // for the current xmlnode after all children are processed
+                //newelm->parent = xmlnode;
+                newelm->endonly = 0;
+                
+                // if xmlnode->next is not NULL, there are still nodes at
+                // this level, therefore we have to put these also on the
+                // stack
+                // this way, the remaining nodes are processed after all
+                // children and the end tag are processed
+                if(xmlnode->next) {
+                    StackElm *nextelm = pool_malloc(pool, sizeof(StackElm));
+                    if(!nextelm) {
+                        ret = 1;
+                        br = 1;
+                        break;
+                    }
+                    nextelm->node = xmlnode->next;
+                    nextelm->next = NULL;
+                    nextelm->endonly = 0;
+                    STACK_PUSH(stack, nextelm);
+                }
+                
+                // we have to put the end tag of the current element
+                // on the stack to ensure endcb is called for the current
+                // element, after all children are processed
+                // reuse cur
+                cur->node = xmlnode;
+                cur->endonly = 1;
+                STACK_PUSH(stack, cur);
+                
+                cur = NULL;
+                
+                // now we can put the children on the stack
+                STACK_PUSH(stack, newelm);
+                // break, because we don't want to process xmlnode->next now
+                break;
+            } else {
+                // no children means, the end callback can be called directly
+                // after the begin callback (no intermediate nodes)
+                cur->node = NULL;
+                if(endcb(xmlnode, udata)) {
+                    br = 1;
+                    break;
+                }
+            }
+            
+            // continue with next node at this level
+            xmlnode = xmlnode->next;
+        }
+        if(br) {
+            break; // break because of an error
+        }
+        
+        if(cur && cur->node) {
+            //xmlNode *endNode = cur->parent ? cur->parent : cur->node;
+            xmlNode *endNode = cur->node;
+            if(endcb(endNode, udata)) {
+                break;
+            }
+            pool_free(pool, cur);
+        }
+    }
+    
+    // free all remaining elements
+    StackElm *elm = stack;
+    while(elm) {
+        StackElm *next = elm->next;
+        pool_free(pool, elm);
+        elm = next;
+    }
+
+    return ret;
+}
+
+/* ------------------- wsxml_get_required_namespaces ------------------- */
+
+typedef struct WSNsCollector {
+    UcxAllocator *a;
+    UcxMap *nsmap;
+    WebdavNSList *def;
+    int error;
+} WSNsCollector;
+
+static int nslist_node_begin(xmlNode *node, void *userdata) {
+    WSNsCollector *col = userdata;
+    // namespace required for all elements
+    if(node->type == XML_ELEMENT_NODE && node->ns) {
+        // we create a list of unique prefix-href namespaces by putting
+        // all namespaces in a map
+        sstr_t nskey = xml_namespace_key(col->a, node->ns);
+        if(!nskey.ptr) {
+            col->error = 1;
+            return 1;
+        }
+        if(ucx_map_sstr_put(col->nsmap, nskey, node->ns)) {
+            col->error = 1;
+            return 1;
+        }
+        
+        // collect all namespace definitions for removing these namespaces
+        // from col->nsmap later
+        WSNamespace *def = node->nsDef;
+        while(def) {
+            WebdavNSList *newdef = col->a->malloc(
+                    col->a->pool, sizeof(WebdavNSList));
+            if(!newdef) {
+                col->error = 1;
+                return 1;
+            }
+            newdef->namespace = def;
+            newdef->prev = NULL;
+            newdef->next = NULL;
+            // prepend newdef to the list
+            if(col->def) {
+                newdef->next = col->def;
+                col->def->prev = newdef;
+            }
+            col->def = newdef;
+            
+            // continue with next namespace definition
+            def = def->next;
+        }
+    }
+    return 0;
+}
+
+static int nslist_node_end(xmlNode *node, void *userdata) {
+    return 0;
+}
+
+WebdavNSList* wsxml_get_required_namespaces(
+        pool_handle_t *pool,
+        WSXmlNode *node,
+        int *error)
+{
+    if(error) *error = 0;
+    
+    UcxAllocator a = util_pool_allocator(pool);
+    UcxMap *nsmap = ucx_map_new_a(&a, 16);
+    if(!nsmap) {
+        if(error) *error = 1;
+        return NULL;
+    }
+    
+    WSNsCollector col;
+    col.a = &a;
+    col.nsmap = nsmap;
+    col.def = NULL;
+    
+    // iterate over all xml elements
+    // this will fill the hashmap with all namespaces
+    // all namespace definitions are added to col.def
+    WebdavNSList *list = NULL;
+    WebdavNSList *end = NULL;
+    if(wsxml_iterator(pool, node, nslist_node_begin, nslist_node_end, &col)) {
+        if(error) *error = 1;
+    } else {
+        // remove all namespace definitions from the map
+        // what we get is a map that contains all missing namespace definitions
+        WebdavNSList *def = col.def;
+        while(def) {
+            sstr_t nskey = xml_namespace_key(&a, def->namespace);
+            if(!nskey.ptr) {
+                if(error) *error = 1;
+                break;
+            }
+            ucx_map_sstr_remove(nsmap, nskey);
+            def = def->next;
+        }
+        
+        // convert nsmap to a list
+        UcxMapIterator i = ucx_map_iterator(nsmap);
+        WSNamespace *ns;
+        UCX_MAP_FOREACH(key, ns, i) {
+            WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList));
+            if(!newelm) {
+                if(error) *error = 1;
+                list = NULL;
+                break;
+            }
+            newelm->namespace = ns;
+            newelm->next = NULL;
+            newelm->prev = end; // NULL or the end of list
+            if(end) {
+                end->next = newelm; // append new element
+            } else {
+                list = newelm; // start new list
+            }
+            end = newelm;
+        }
+    }
+    
+    ucx_map_free(nsmap);
+    return list;
+}
+
+
+static ssize_t buf_writefunc(void *buf, const char *s, size_t len) {
+    int w = ucx_buffer_write(s, 1, len, buf);
+    return w == 0 ? IO_ERROR : w;
+}
+
+WSXmlData* wsxml_node2data(
+        pool_handle_t *pool,
+        WSXmlNode *node)
+{
+    UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    if(!buf) {
+        return NULL;
+    }
+    
+    int error = 0;
+    WebdavNSList *nslist = wsxml_get_required_namespaces(pool, node, &error);
+    if(error) {
+        return NULL;
+    }
+    
+    Writer writer;
+    char buffer[512];
+    writer_init_with_stream(&writer, buf, buf_writefunc, buffer, 512);
+    
+    WSXmlData *data = NULL;
+    if(!wsxml_write_nodes(pool, &writer, NULL, node) && !writer_flush(&writer)) {
+        data = pool_malloc(pool, sizeof(WSXmlData));
+        if(data) {
+            data->data = pool_malloc(pool, buf->size + 1);
+            if(data->data) {
+                memcpy(data->data, buf->space, buf->size);
+                data->data[buf->size] = '\0';
+                data->length = buf->size;
+                data->namespaces = nslist;
+            }
+        }
+    }
+    
+    ucx_buffer_free(buf);
+    
+    return data;
+}
+
+char* wsxml_nslist2string(pool_handle_t *pool, WebdavNSList *nslist) {
+    if(!nslist) return NULL;
+    
+    // get required string length
+    size_t len = 0;
+    WebdavNSList *elm = nslist;
+    while(elm) {
+        WSNamespace *ns = elm->namespace;
+        if(ns) {
+            if(ns->prefix) len += strlen((const char*)ns->prefix);
+            if(ns->href) len += strlen((const char*)ns->href);
+            len += 2; // 1 char for ':', 1 char for \n or \0
+        }
+        elm = elm->next;
+    }
+    
+    // alloc string
+    char *str = pool_malloc(pool, len);
+    if(!str) {
+        return NULL;
+    }
+    char *pos = str;
+    
+    // copy namespace definitions to the string
+    elm = nslist;
+    while(elm) {
+        WSNamespace *ns = elm->namespace;
+        if(ns) {
+            if(ns->prefix) {
+                size_t prefixlen = strlen((const char*)ns->prefix);
+                memcpy(pos, ns->prefix, prefixlen);
+                pos[prefixlen] = ':';
+                pos += prefixlen + 1;
+            } else {
+                pos[0] = ':';
+                pos++;
+            }
+            if(ns->href) {
+                size_t hreflen = strlen((const char*)ns->href);
+                memcpy(pos, ns->href, hreflen);
+                pos[hreflen] = elm->next ? '\n' : '\0';
+                pos += hreflen + 1;
+            } else {
+                pos[0] = elm->next ? '\n' : '\0';
+                pos++;
+            }      
+        }
+        elm = elm->next;
+    }
+    
+    return str;
+}
+
+WebdavNSList* wsxml_string2nslist(pool_handle_t *pool, char *nsliststr) {
+    if(!nsliststr) return NULL;
+    size_t len = strlen(nsliststr);
+    WebdavNSList *list_start = NULL;
+    WebdavNSList *list_current = NULL;
+    
+    char *prefix = nsliststr;
+    size_t prefix_start = 0;
+    size_t prefix_len = 0;
+    char *href = NULL;
+    size_t href_start = len;
+    size_t i;
+    for(i=0;i<=len;i++) {
+        char c = nsliststr[i];
+        if(c == '\n' || c == '\0') {
+            if(i > href_start) {
+                WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList));
+                if(!elm) {
+                    break;
+                }
+                elm->prev = list_current;
+                elm->next = NULL;
+                WSNamespace *ns = pool_malloc(pool, sizeof(WSNamespace));
+                elm->namespace = ns;
+                if(!ns) {
+                    break;
+                }
+                memset(ns, 0, sizeof(WSNamespace));
+                ns->prefix = prefix_len > 0 ? (xmlChar*)sstrdup_pool(pool, sstrn(prefix, prefix_len)).ptr : NULL;
+                ns->href = (xmlChar*)sstrdup_pool(pool, sstrn(href, i-href_start)).ptr;
+                if(list_current) {
+                    list_current->next = elm;
+                } else {
+                    list_start = elm; 
+                }
+                list_current = elm;
+            }
+            prefix_start = i + 1;
+            prefix = nsliststr + prefix_start;
+            prefix_len = 0;
+            href_start = len;
+            href = NULL;
+        } else if(!href && c == ':') {
+            prefix_len = i - prefix_start;
+            href_start = i + 1;
+            href = nsliststr + href_start;
+        }
+    }
+    
+    if(i < len) {
+        // error, cleanup
+        while(list_start) {
+            if(list_start->namespace) {
+                WSNamespace *ns = list_start->namespace;
+                if(ns->prefix) {
+                    pool_free(pool, (char*)ns->prefix);
+                }
+                if(ns->href) {
+                    pool_free(pool, (char*)ns->href);
+                }
+                pool_free(pool, ns);
+            }
+            WebdavNSList *next = list_start->next;
+            pool_free(pool, list_start);
+            list_start = next;
+        }
+        list_start = NULL;
+    }
+    
+    return list_start;
+}
+
+/*****************************************************************************
+ *    Non public functions
+ *****************************************************************************/
+
+typedef struct XmlWriter {
+    /*
+     * Memory pool for temp memory allocations
+     */
+    pool_handle_t *pool;
+    
+    /*
+     * Buffered output stream
+     */
+    Writer *out;
+    
+    /*
+     * Map for all previously defined namespaces
+     * key: (char*) namespace prefix
+     * value: WSNamespace*
+     */
+    UcxMap *namespaces;
+    
+    /*
+     * Should namespace definitions be created
+     */
+    WSBool define_namespaces;
+} XmlWriter;
+
+/*
+ * Serialize an XML text node
+ * This replaces some special characters with entity refs
+ * type: 0 = element text, 1 = attribute text
+ */
+static void xml_ser_text(Writer *out, int type, const char *text) {
+    size_t start = 0;
+    size_t i;
+    sstr_t entityref = { NULL, 0 };
+    for(i=0;text[i]!='\0';i++) {
+        char c = text[i];
+        if(c == '&') {
+            entityref = S("&amp;");
+        } else if(type == 0) {
+            if(c == '<') {
+                entityref = S("&lt;");
+            } else if(c == '>') {
+                entityref = S("&gt;");
+            }
+        } else {
+            if(c == '\"') {
+                entityref = S("&quot;");
+            } else if(c == '\'') {
+                entityref = S("&apos;");
+            }
+        }
+        
+        if(entityref.ptr) {
+            size_t len = i-start;
+            if(len > 0) {
+                writer_put(out, text+start, len);
+            }
+            writer_puts(out, entityref);
+            entityref.ptr = NULL;
+            entityref.length = 0;
+            start = i+1;
+        }
+    }
+    size_t len = i-start;
+    if(len > 0) {
+        writer_put(out, text+start, len);
+    }
+}
+
+/*
+ * Serialize an XML element node
+ */
+static void xml_ser_element(XmlWriter *xw, xmlNode *node) {
+    Writer *out = xw->out;
+    writer_putc(out, '<');
+    
+    // write prefix and ':'
+    if(node->ns && node->ns->prefix) {
+        writer_puts(out, sstr((char*)node->ns->prefix));
+        writer_putc(out, ':');
+    }
+    
+    // node name
+    writer_puts(out, sstr((char*)node->name));
+    
+    // namespace definitions
+    if(xw->define_namespaces) {
+        xmlNs *nsdef = node->nsDef;
+        while(nsdef) {
+            // we define only namespaces without prefix or namespaces
+            // with prefix, that are not already defined
+            // xw->namespaces contains all namespace, that were defined
+            // before xml serialization
+            if(!nsdef->prefix) {
+                writer_puts(out, S(" xmlns=\""));
+                writer_puts(out, sstr((char*)nsdef->href));
+                writer_putc(out, '"');
+            } else {
+                WSNamespace *n = xw->namespaces ?
+                    ucx_map_cstr_get(xw->namespaces, (char*)nsdef->prefix) :
+                    NULL;
+                if(!n) {
+                    writer_puts(out, S(" xmlns:"));
+                    writer_puts(out, sstr((char*)nsdef->prefix));
+                    writer_puts(out, S("=\""));
+                    writer_puts(out, sstr((char*)nsdef->href));
+                    writer_putc(out, '"');
+                }
+            }
+            
+            nsdef = nsdef->next;
+        }
+    }
+    
+    // attributes
+    xmlAttr *attr = node->properties;
+    while(attr) {
+        // format: ' [<prefix>:]<name>="<value>"'
+        writer_putc(out, ' ');
+        // optional namespace
+        if(attr->ns && attr->ns->prefix) {
+            writer_puts(out, sstr((char*)attr->ns->prefix));
+            writer_putc(out, ':');
+        }
+        // <name>="
+        writer_puts(out, sstr((char*)attr->name));
+        writer_puts(out, S("=\""));
+        // value
+        xmlNode *value = attr->children;
+        while(value) {
+            if(value->content) {
+                xml_ser_text(out, 1, (const char*)value->content);
+            }
+            value = value->next;
+        }
+        // trailing quote
+        writer_putc(out, '"');
+        
+        attr = attr->next;
+    }
+    
+    if(node->children) {
+        writer_putc(out, '>');
+    } else {
+        writer_puts(out, S("/>"));
+    }
+}
+
+static int xml_ser_node_begin(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    switch(node->type) {
+        case XML_ELEMENT_NODE: xml_ser_element(xw, node); break;
+        case XML_ATTRIBUTE_NODE: break;
+        case XML_TEXT_NODE: {
+            xml_ser_text(xw->out, 0, (const char*)node->content);
+            break;
+        }
+        case XML_CDATA_SECTION_NODE: {
+            break;
+        }
+        case XML_ENTITY_REF_NODE: break;
+        case XML_ENTITY_NODE: break;
+        case XML_PI_NODE: break;
+        case XML_COMMENT_NODE: break;
+        case XML_DOCUMENT_NODE: break;
+        case XML_DOCUMENT_TYPE_NODE: break;
+        case XML_DOCUMENT_FRAG_NODE: break;
+        case XML_NOTATION_NODE: break;
+        case XML_HTML_DOCUMENT_NODE: break;
+        case XML_DTD_NODE: break;
+        case XML_ELEMENT_DECL: break;
+        case XML_ATTRIBUTE_DECL: break;
+        case XML_ENTITY_DECL: break;
+        case XML_NAMESPACE_DECL: break;
+        case XML_XINCLUDE_START: break;
+        case XML_XINCLUDE_END: break;
+        default: break;
+    }
+    return 0;
+}
+
+static int xml_ser_node_end(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    Writer *out = xw->out;
+    if(node->type == XML_ELEMENT_NODE) {
+        if(node->children) {
+            writer_puts(xw->out, S("</"));
+            // write prefix and ':'
+            if(node->ns && node->ns->prefix) {
+                writer_puts(out, sstr((char*)node->ns->prefix));
+                writer_putc(out, ':');
+            }
+            // name and close tag
+            writer_puts(out, sstr((char*)node->name));
+            writer_putc(out, '>');
+            
+        } // element was already closed in xml_ser_node_begin
+    }
+    return 0;
+}
+
+
+static int xml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        WSBool createdefs,
+        xmlNode *node)
+{
+    XmlWriter xmlwriter;
+    xmlwriter.pool = pool;
+    xmlwriter.out = out;
+    xmlwriter.namespaces = nsdefs;
+    xmlwriter.define_namespaces = createdefs;
+    
+    // iterate over xml nodes
+    // this includes node->children and node->next
+    int err = wsxml_iterator(
+            pool,
+            node,
+            xml_ser_node_begin,
+            xml_ser_node_end,
+            &xmlwriter);
+    if(err) {
+        return -1;
+    }
+    
+    return out->error;
+}
+
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, nsdefs, TRUE, node);
+}
+
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, NULL, FALSE, node);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/xml.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,69 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEBDAV_XML_H
+#define WEBDAV_XML_H
+
+#include "../public/webdav.h"
+#include <libxml/tree.h>
+
+#include <ucx/map.h>
+#include <ucx/ucx.h>
+
+#include "../util/writer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'.
+ */
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node);
+
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'
+ * without creating any namespace definitions. Therefore all namespaces must
+ * be already defined and previously written to 'out'.
+ */
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WEBDAV_XML_H */
+
--- a/src/tools/Makefile	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/tools/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -30,7 +30,7 @@
 
 include $(BUILD_ROOT)/config.mk
 
-CFLAGS += -I..
+CFLAGS += -I../ucx/
 LDFLAGS += -L../../build/lib -lucx -lwscfg
 
 # list of source files
--- a/src/tools/wstool.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/tools/wstool.c	Sat Sep 24 16:26:10 2022 +0200
@@ -32,7 +32,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "../server/config/serverconf.h"
+#include "../server/config/serverconfig.h"
 
 #include "srvctrlsocket.h"
 
@@ -61,8 +61,8 @@
 }
 
 int tool_get_tmpdir(char *configfile) {
-    ServerConfig *serverconf = load_server_config(configfile);
-    UcxList *list = ucx_map_sstr_get(serverconf->objects, sstrn("Runtime", 7));
+    ServerConfig *serverconf = serverconfig_load(configfile);
+    UcxList *list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Runtime"));
     if(!list) {
         fprintf(stderr, "Error: No Runtime element in %s\n", configfile);
         return -1;
@@ -71,8 +71,11 @@
         fprintf(stderr, "Error: Multiple Runtime elements in %s\n", configfile);
         return -1;
     }
-    ServerConfigObject *runtime = list->data;
-    sstr_t tmp = cfg_directivelist_get_str(runtime->directives, sstr("Temp"));
+    ConfigNode *runtime = list->data;
+    scstr_t tmp = serverconfig_directive_value(runtime, SC("Temp"));
+    
+    ucx_list_free(list);
+    
     if(!tmp.ptr) {
         fprintf(stderr, "Error: No Temp directive in Runtime Object\n");
         return -1;
--- a/src/ucx/Makefile	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/Makefile	Sat Sep 24 16:26:10 2022 +0200
@@ -31,7 +31,8 @@
 include $(BUILD_ROOT)/config.mk
 
 # list of source files
-SRC  = utils.c
+SRC  = ucx.c
+SRC += utils.c
 SRC += list.c
 SRC += map.c
 SRC += avl.c
@@ -43,6 +44,7 @@
 SRC += logging.c
 SRC += buffer.c
 SRC += stack.c
+SRC += array.c
 
 OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/ucx/%$(OBJ_EXT))
 
--- a/src/ucx/allocator.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/allocator.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,10 +26,11 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ucx/allocator.h"
+
 #include <stdlib.h>
-#include "allocator.h"
 
-UcxAllocator default_allocator = {
+static UcxAllocator default_allocator = {
     NULL,
     ucx_default_malloc,
     ucx_default_calloc,
--- a/src/ucx/allocator.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * Allocator for custom memory management.
- * 
- * A UCX allocator consists of a pointer to the memory area / pool and four
- * function pointers to memory management functions operating on this memory
- * area / pool. These functions shall behave equivalent to the standard libc
- * functions <code>malloc(), calloc(), realloc()</code> and <code>free()</code>.
- * 
- * The signature of the memory management functions is based on the signature
- * of the respective libc function but each of them takes the pointer to the
- * memory area / pool as first argument.
- * 
- * As the pointer to the memory area / pool can be arbitrarily chosen, any data
- * can be provided to the memory management functions. A UcxMempool is just
- * one example.
- * 
- * @see mempool.h
- * @see UcxMap
- * 
- * @file   allocator.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_ALLOCATOR_H
-#define	UCX_ALLOCATOR_H
-
-#include "ucx.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * A function pointer to the allocators <code>malloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_malloc)(void *pool, size_t n);
-
-/**
- * A function pointer to the allocators <code>calloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size);
-
-/**
- * A function pointer to the allocators <code>realloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n);
-
-/**
- * A function pointer to the allocators <code>free()</code> function.
- * @see UcxAllocator
- */
-typedef void(*ucx_allocator_free)(void *pool, void *data);
-
-/**
- * UCX allocator data structure containing memory management functions.
- */
-typedef struct {
-    /** Pointer to an area of memory or a complex memory pool.
-     * This pointer will be passed to any memory management function as first
-     * argument.
-     */
-    void *pool;
-    /**
-     * The <code>malloc()</code> function for this allocator.
-     */
-    ucx_allocator_malloc  malloc;
-    /**
-     * The <code>calloc()</code> function for this allocator.
-     */
-    ucx_allocator_calloc  calloc;
-    /**
-     * The <code>realloc()</code> function for this allocator.
-     */
-    ucx_allocator_realloc realloc;
-    /**
-     * The <code>free()</code> function for this allocator.
-     */
-    ucx_allocator_free    free;
-} UcxAllocator;
-
-/**
- * Returns a pointer to the default allocator.
- * 
- * The default allocator contains wrappers to the standard libc memory
- * management functions. Use this function to get a pointer to a globally
- * available allocator. You may also define an own UcxAllocator by assigning
- * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable
- * to any function that takes a UcxAllocator as argument. Note that using
- * this function is the recommended way of passing a default allocator, thus
- * it never runs out of scope.
- * 
- * @return a pointer to the default allocator
- * 
- * @see UCX_ALLOCATOR_DEFAULT
- */
-UcxAllocator *ucx_default_allocator();
-
-/**
- * A wrapper for the standard libc <code>malloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param n argument passed to <code>malloc()</code>
- * @return return value of <code>malloc()</code>
- */
-void *ucx_default_malloc(void *ignore, size_t n);
-/**
- * A wrapper for the standard libc <code>calloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param n argument passed to <code>calloc()</code>
- * @param size  argument passed to <code>calloc()</code>
- * @return return value of <code>calloc()</code>
- */
-void *ucx_default_calloc(void *ignore, size_t n, size_t size);
-/**
- * A wrapper for the standard libc <code>realloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param data argumend passed to <code>realloc()</code>
- * @param n argument passed to <code>realloc()</code>
- * @return return value of <code>realloc()</code>
- */
-void *ucx_default_realloc(void *ignore, void *data, size_t n);
-/**
- * A wrapper for the standard libc <code>free()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param data argument passed to <code>free()</code>
- */
-void ucx_default_free(void *ignore, void *data);
-
-/**
- * Shorthand for calling an allocators malloc function.
- * @param allocator the allocator to use
- * @param n size of space to allocate
- * @return a pointer to the allocated memory area
- */
-#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n))
-
-/**
- * Shorthand for calling an allocators calloc function.
- * @param allocator the allocator to use
- * @param n the count of elements the space should be allocated for
- * @param size the size of each element
- * @return a pointer to the allocated memory area
- */
-#define alcalloc(allocator, n, size) \
-        ((allocator)->calloc((allocator)->pool, n, size))
-
-/**
- * Shorthand for calling an allocators realloc function.
- * @param allocator the allocator to use
- * @param ptr the pointer to the memory area that shall be reallocated
- * @param n the new size of the allocated memory area
- * @return a pointer to the reallocated memory area
- */
-#define alrealloc(allocator, ptr, n) \
-        ((allocator)->realloc((allocator)->pool, ptr, n))
-
-/**
- * Shorthand for calling an allocators free function.
- * @param allocator the allocator to use
- * @param ptr the pointer to the memory area that shall be freed
- */
-#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr))
-
-/**
- * Convenient macro for a default allocator <code>struct</code> definition.
- */
-#define UCX_ALLOCATOR_DEFAULT {NULL, \
-        ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \
-        ucx_default_free }
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_ALLOCATOR_H */
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/array.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,467 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Mike Becker, 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.
+ */
+
+#define _GNU_SOURCE /* we want to use qsort_r(), if available */
+#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */
+
+
+#include "ucx/array.h"
+#include "ucx/utils.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef UCX_ARRAY_DISABLE_QSORT
+#ifdef __GLIBC__
+#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
+#define ucx_array_sort_impl qsort_r
+#endif /* glibc version >= 2.8 */
+#elif /* not  __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__)
+#define ucx_array_sort_impl ucx_qsort_r
+#define USE_UCX_QSORT_R
+#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun)
+#if __STDC_VERSION__ >= 201112L
+#define ucx_array_sort_impl qsort_s
+#endif
+#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */
+#endif /* UCX_ARRAY_DISABLE_QSORT */
+
+#ifndef ucx_array_sort_impl
+#define ucx_array_sort_impl ucx_mergesort
+#endif
+
+static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) {
+    size_t required_capacity = array->capacity;
+    while (reqcap > required_capacity) {
+        if (required_capacity * 2 < required_capacity)
+            return 1;
+        required_capacity <<= 1;
+    }
+    if (ucx_array_reserve(array, required_capacity)) {
+        return 1;
+    }
+    return 0;
+}
+
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t index, void* data) {
+    
+    if(!alloc || !capacity || !array) {
+        errno = EINVAL;
+        return 1;
+    }
+    
+    size_t newcapacity = *capacity;
+    while(index >= newcapacity) {
+        if(ucx_szmul(newcapacity, 2, &newcapacity)) {
+            errno = EOVERFLOW;
+            return 1;
+        }        
+    }
+
+    size_t memlen, offset;
+    if(ucx_szmul(newcapacity, elmsize, &memlen)) {
+        errno = EOVERFLOW;
+        return 1;
+    }
+    /* we don't need to check index*elmsize - it is smaller than memlen */
+    
+    
+    void* newptr = alrealloc(alloc, *array, memlen);
+    if(newptr == NULL) {
+        errno = ENOMEM; /* we cannot assume that every allocator sets this */
+        return 1;
+    }
+    *array = newptr;
+    *capacity = newcapacity;
+    
+    
+    char* dest = *array;
+    dest += elmsize*index;
+    memcpy(dest, data, elmsize);
+    
+    return 0;
+}
+
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t index, void* data) {
+    
+    return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*),
+            index, &data);
+}
+
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize) {
+    return ucx_array_new_a(capacity, elemsize, ucx_default_allocator());
+}
+
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    UcxArray* array = almalloc(allocator, sizeof(UcxArray));
+    if(array) {
+        ucx_array_init_a(array, capacity, elemsize, allocator);
+    }
+    return array;
+}
+
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) {
+    ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator());
+}
+
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    
+    array->allocator = allocator;
+    array->elemsize = elemsize;
+    array->size = 0;
+    array->data = alcalloc(allocator, capacity, elemsize);
+    
+    if (array->data) {
+        array->capacity = capacity;
+    } else {
+        array->capacity = 0;
+    }
+}
+
+int ucx_array_clone(UcxArray* dest, UcxArray const* src) {
+    if (ucx_array_ensurecap(dest, src->capacity)) {
+        return 1;
+    }
+    
+    dest->elemsize = src->elemsize;
+    dest->size = src->size;
+    
+    if (dest->data) {
+        memcpy(dest->data, src->data, src->size*src->elemsize);
+    }
+    
+    return 0;
+}
+
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data) {
+    
+    if (array1->size != array2->size || array1->elemsize != array2->elemsize) {
+        return 0;
+    } else {
+        if (array1->size == 0)
+            return 1;
+        
+        size_t elemsize;
+        if (cmpfnc == NULL) {
+            cmpfnc = ucx_cmp_mem;
+            elemsize = array1->elemsize;
+            data = &elemsize;
+        }
+        
+        for (size_t i = 0 ; i < array1->size ; i++) {
+            int r = cmpfnc(
+                    ucx_array_at(array1, i),
+                    ucx_array_at(array2, i),
+                    data);
+            if (r != 0)
+                return 0;
+        }
+        return 1;
+    }
+}
+
+void ucx_array_destroy(UcxArray *array) {
+    if(array->data)
+        alfree(array->allocator, array->data);
+    array->data = NULL;
+    array->capacity = array->size = 0;
+}
+
+void ucx_array_free(UcxArray *array) {
+    ucx_array_destroy(array);
+    alfree(array->allocator, array);
+}
+
+int ucx_array_append_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    void* dest = ucx_array_at(array, array->size);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    array->size += count;
+    
+    return 0;
+}
+
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    if (array->size > 0) {
+        void *dest = ucx_array_at(array, count);
+        memmove(dest, array->data, array->elemsize*array->size);
+    }
+    
+    if (data) {
+        memcpy(array->data, data, array->elemsize*count);
+    } else {
+        memset(array->data, 0, array->elemsize*count);
+    }
+    array->size += count;
+        
+    return 0;
+}
+
+int ucx_array_set_from(UcxArray *array, size_t index,
+        void *data, size_t count) {
+    if (ucx_array_ensurecap(array, index + count))
+        return 1;
+    
+    if (index+count > array->size) {
+        array->size = index+count;
+    }
+    
+    void *dest = ucx_array_at(array, index);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    
+    return 0;
+}
+
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2) {
+    
+    if (array1->elemsize != array2->elemsize)
+        return 1;
+    
+    size_t capacity = array1->capacity+array2->capacity;
+        
+    if (array1->capacity < capacity) {
+        if (ucx_array_reserve(array1, capacity)) {
+            return 1;
+        }
+    }
+    
+    void* dest = ucx_array_at(array1, array1->size);
+    memcpy(dest, array2->data, array2->size*array2->elemsize);
+    
+    array1->size += array2->size;
+    
+    return 0;
+}
+
+void *ucx_array_at(UcxArray const *array, size_t index) {
+    char* memory = array->data;
+    char* loc = memory + index*array->elemsize;
+    return loc;
+}
+
+size_t ucx_array_find(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    
+    size_t elemsize;
+    if (cmpfnc == NULL) {
+        cmpfnc = ucx_cmp_mem;
+        elemsize = array->elemsize;
+        data = &elemsize;
+    }
+
+    if (array->size > 0) {
+        for (size_t i = 0 ; i < array->size ; i++) {
+            void* ptr = ucx_array_at(array, i);
+            if (cmpfnc(ptr, elem, data) == 0) {
+                return i;
+            }
+        }
+        return array->size;
+    } else {
+        return 0;
+    }
+}
+
+int ucx_array_contains(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    return ucx_array_find(array, elem, cmpfnc, data) != array->size;
+}
+
+static void ucx_mergesort_merge(void *arrdata,size_t elemsize,
+        cmp_func cmpfnc, void *data,
+        size_t start, size_t mid, size_t end) { 
+    
+    char* array = arrdata;
+    
+    size_t rightstart = mid + 1; 
+  
+    if (cmpfnc(array + mid*elemsize,
+            array + rightstart*elemsize, data) <= 0) {
+        /* already sorted */
+        return;
+    }
+  
+    /* we need memory for one element */
+    void *value = malloc(elemsize);
+    
+    while (start <= mid && rightstart <= end) { 
+        if (cmpfnc(array + start*elemsize,
+                array + rightstart*elemsize, data) <= 0) { 
+            start++; 
+        } else {
+            /* save the value from the right */
+            memcpy(value, array + rightstart*elemsize, elemsize);
+                        
+            /* shift all left elements one element to the right */
+            size_t shiftcount = rightstart-start;
+            void *startptr = array + start*elemsize;
+            void *dest = array + (start+1)*elemsize;
+            memmove(dest, startptr, shiftcount*elemsize);
+            
+            /* bring the first value from the right to the left */
+            memcpy(startptr, value, elemsize);
+  
+            start++; 
+            mid++; 
+            rightstart++; 
+        }
+    }
+    
+    /* free the temporary memory */
+    free(value);
+} 
+  
+static void ucx_mergesort_impl(void *arrdata, size_t elemsize,
+        cmp_func cmpfnc, void *data, size_t l, size_t r) { 
+    if (l < r) {
+        size_t m = l + (r - l) / 2; 
+  
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); 
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); 
+        ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r);
+    } 
+}
+
+static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize,
+        cmp_func cmpfnc, void *data) {
+    
+    ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1);
+}
+
+#ifdef USE_UCX_QSORT_R
+struct cmpfnc_swapargs_info {
+    cmp_func func;
+    void *data;
+};
+
+static int cmp_func_swap_args(void *data, const void *x, const void *y) {
+    struct cmpfnc_swapargs_info* info = data;
+    return info->func(x, y, info->data);
+}
+
+static void ucx_qsort_r(void *array, size_t count, size_t elemsize,
+		     cmp_func cmpfnc, void *data) {
+    struct cmpfnc_swapargs_info info;
+    info.func = cmpfnc;
+    info.data = data;
+    qsort_r(array, count, elemsize, &info, cmp_func_swap_args);
+}
+#endif /* USE_UCX_QSORT_R */
+
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) {
+    ucx_array_sort_impl(array->data, array->size, array->elemsize,
+            cmpfnc, data);
+}
+
+void ucx_array_remove(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, index+1);
+        memmove(dest, src, (array->size - index)*array->elemsize);
+    }
+}
+
+void ucx_array_remove_fast(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {       
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, array->size);
+        memcpy(dest, src, array->elemsize);
+    }
+}
+
+int ucx_array_shrink(UcxArray* array) {
+    void* newptr = alrealloc(array->allocator, array->data,
+                array->size*array->elemsize);
+    if (newptr) {
+        array->data = newptr;
+        array->capacity = array->size;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+int ucx_array_resize(UcxArray* array, size_t capacity) {
+    if (array->capacity >= capacity) {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            if (array->size > array->capacity) {
+                array->size = array->capacity;
+            }
+            return 0;
+        } else {
+            return 1;
+        }
+    } else {
+        return ucx_array_reserve(array, capacity);
+    }
+}
+
+int ucx_array_reserve(UcxArray* array, size_t capacity) {
+    if (array->capacity > capacity) {
+        return 0;
+    } else {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+
+int ucx_array_grow(UcxArray* array, size_t count) {
+    return ucx_array_reserve(array, array->size+count);
+}
--- a/src/ucx/avl.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/avl.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,7 +26,9 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "avl.h"
+#include "ucx/avl.h"
+
+#include <limits.h>
 
 #define ptrcast(ptr) ((void*)(ptr))
 #define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree))
@@ -134,6 +136,23 @@
     alfree(al, tree);
 }
 
+static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node,
+        ucx_destructor destr) {
+    if (node) {
+        ucx_avl_free_content_node(al, node->left, destr);
+        ucx_avl_free_content_node(al, node->right, destr);
+        if (destr) {
+            destr(node->value);
+        } else {
+            alfree(al, node->value);
+        }
+    }
+}
+
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) {
+    ucx_avl_free_content_node(tree->allocator, tree->root, destr);
+}
+
 UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) {
     UcxAVLNode *n = tree->root;
     int cmpresult;
@@ -149,6 +168,46 @@
     return n ? n->value : NULL;
 }
 
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = tree->root;
+    UcxAVLNode *closest = NULL;
+
+    intmax_t cmpresult;
+    intmax_t closest_dist;
+    closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX;
+    
+    while (n && (cmpresult = dfnc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        if (mode == UCX_AVL_FIND_CLOSEST) {
+            intmax_t dist = cmpresult;
+            if (dist < 0) dist *= -1;
+            if (dist < closest_dist) {
+                closest_dist = dist;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) {
+            if (cmpresult > closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) {
+            if (cmpresult < closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        }
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    return n ? n : closest;
+}
+
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode);
+    return n ? n->value : NULL;
+}
+
 int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) {
     return ucx_avl_put_s(tree, key, value, NULL);
 }
@@ -272,3 +331,43 @@
 size_t ucx_avl_count(UcxAVLTree *tree) {
     return ucx_avl_countn(tree->root);
 }
+
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) {
+    if (node->left) {
+        UcxAVLNode* n = node->left;
+        while (n->right) {
+            n = n->right;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->right == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
+
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) {
+    if (node->right) {
+        UcxAVLNode* n = node->right;
+        while (n->left) {
+            n = n->left;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->left == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
--- a/src/ucx/avl.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-
-/**
- * @file avl.h
- * 
- * AVL tree implementation.
- * 
- * This binary search tree implementation allows average O(1) insertion and
- * removal of elements (excluding binary search time).
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_AVL_H
-#define UCX_AVL_H
-
-#include "ucx.h"
-#include "allocator.h"
-#include <inttypes.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UCX AVL Node type.
- * 
- * @see UcxAVLNode
- */
-typedef struct UcxAVLNode UcxAVLNode;
-
-/**
- * UCX AVL Node.
- */
-struct UcxAVLNode {
-    /**
-     * The key for this node.
-     */
-    intptr_t key;
-    /**
-     * Data contained by this node.
-     */
-    void *value;
-    /**
-     * The height of this (sub)-tree.
-     */
-    size_t height;
-    /**
-     * Parent node.
-     */
-    UcxAVLNode *parent;
-    /**
-     * Root node of left subtree.
-     */
-    UcxAVLNode *left;
-    /**
-     * Root node of right subtree.
-     */
-    UcxAVLNode *right;
-};
-
-/**
- * UCX AVL Tree.
- */
-typedef struct {
-    /**
-     * The UcxAllocator that shall be used to manage the memory for node data.
-     */
-    UcxAllocator *allocator;
-    /**
-     * Root node of the tree.
-     */
-    UcxAVLNode *root;
-    /**
-     * Compare function that shall be used to compare the UcxAVLNode keys.
-     * @see UcxAVLNode.key
-     */
-    cmp_func cmpfunc;
-    /**
-     * Custom user data.
-     * This data will also be provided to the cmpfunc.
-     */
-    void *userdata;
-} UcxAVLTree;
-
-/**
- * Initializes a new UcxAVLTree with a default allocator.
- * 
- * @param cmpfunc the compare function that shall be used
- * @return a new UcxAVLTree object
- * @see ucx_avl_new_a()
- */
-UcxAVLTree *ucx_avl_new(cmp_func cmpfunc);
-
-/**
- * Initializes a new UcxAVLTree with the specified allocator.
- * 
- * The cmpfunc should be capable of comparing two keys within this AVL tree.
- * So if you want to use null terminated strings as keys, you could use the
- * ucx_strcmp() function here.
- * 
- * @param cmpfunc the compare function that shall be used
- * @param allocator the UcxAllocator that shall be used
- * @return a new UcxAVLTree object
- */
-UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator);
-
-/**
- * Destroys a UcxAVLTree.
- * @param tree the tree to destroy
- */
-void ucx_avl_free(UcxAVLTree *tree);
-
-/**
- * Macro for initializing a new UcxAVLTree with the default allocator and a
- * ucx_ptrcmp() compare function.
- * 
- * @return a new default UcxAVLTree object
- */
-#define ucx_avl_default_new() ucx_avl_new_a(ucx_ptrcmp, ucx_default_allocator())
-
-/**
- * Gets the node from the tree, that is associated with the specified key.
- * @param tree the UcxAVLTree
- * @param key the key
- * @return the node (or <code>NULL</code>, if the key is not present)
- */
-UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key);
-
-/**
- * Gets the value from the tree, that is associated with the specified key.
- * @param tree the UcxAVLTree
- * @param key the key
- * @return the value (or <code>NULL</code>, if the key is not present)
- */
-void *ucx_avl_get(UcxAVLTree *tree, intptr_t key);
-
-/**
- * Puts a key/value pair into the tree.
- * 
- * Attention: use this function only, if a possible old value does not need
- * to be preserved.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param value the new value
- * @return zero, if and only if the operation succeeded
- */
-int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value);
-
-/**
- * Puts a key/value pair into the tree.
- * 
- * This is a secure function which saves the old value to the variable pointed
- * at by oldvalue.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param value the new value
- * @param oldvalue optional: a pointer to the location where a possible old
- * value shall be stored
- * @return zero, if and only if the operation succeeded
- */
-int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue);
-
-/**
- * Removes a node from the AVL tree.
- * 
- * Note: the specified node is logically removed. The tree implementation
- * decides which memory area is freed. In most cases the here provided node
- * is freed, so it's further use is generally undefined.
- * 
- * @param tree the UcxAVLTree
- * @param node the node to remove
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node);
-
-/**
- * Removes an element from the AVL tree.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove(UcxAVLTree *tree, intptr_t key);
-
-/**
- * Removes an element from the AVL tree.
- * 
- * This is a secure function which saves the old key and value data from node
- * to the variables at the location of oldkey and oldvalue (if specified), so
- * they can be freed afterwards (if necessary).
- * 
- * Note: the returned key in oldkey is possibly not the same as the provided
- * key for the lookup (in terms of memory location).
- * 
- * @param tree the UcxAVLTree
- * @param key the key of the element to remove
- * @param oldkey optional: a pointer to the location where the old key shall be
- * stored
- * @param oldvalue optional: a pointer to the location where the old value
- * shall be stored
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
-        intptr_t *oldkey, void **oldvalue);
-
-/**
- * Counts the nodes in the specified UcxAVLTree.
- * @param tree the AVL tree
- * @return the node count
- */
-size_t ucx_avl_count(UcxAVLTree *tree);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_AVL_H */
-
--- a/src/ucx/buffer.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/buffer.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,7 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "buffer.h"
+#include "ucx/buffer.h"
+
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
@@ -64,8 +65,9 @@
 
 UcxBuffer* ucx_buffer_extract(
         UcxBuffer *src, size_t start, size_t length, int flags) {
-    
-    if (src->size == 0 || length == 0 || start+length > src->capacity) {
+    if (src->size == 0 || length == 0 ||
+        ((size_t)-1) - start < length || start+length > src->capacity)
+    {
         return NULL;
     }
 
@@ -149,7 +151,10 @@
 
 size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
         UcxBuffer *buffer) {
-    size_t len = size * nitems;
+    size_t len;
+    if(ucx_szmul(size, nitems, &len)) {
+        return 0;
+    }
     size_t required = buffer->pos + len;
     if (buffer->pos > required) {
         return 0;
@@ -183,7 +188,10 @@
 
 size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
         UcxBuffer *buffer) {
-    size_t len = size * nitems;
+    size_t len;
+    if(ucx_szmul(size, nitems, &len)) {
+        return 0;
+    }
     if (buffer->pos + len > buffer->size) {
         len = buffer->size - buffer->pos;
         if (size > 1) len -= len%size;
@@ -223,12 +231,67 @@
     if (ucx_buffer_eof(buffer)) {
         return EOF;
     } else {
-        int c = buffer->space[buffer->pos];
+        int c = ((unsigned char*)buffer->space)[buffer->pos];
         buffer->pos++;
         return c;
     }
 }
 
-size_t ucx_buffer_puts(UcxBuffer *buffer, char *str) {
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) {
     return ucx_buffer_write((const void*)str, 1, strlen(str), buffer);
 }
+
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->space, buffer->space + shift, buffer->size - shift);
+        buffer->size -= shift;
+        
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) {
+    size_t req_capacity = buffer->size + shift;
+    size_t movebytes;
+    
+    // auto extend buffer, if required and enabled
+    if (buffer->capacity < req_capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+    
+    memmove(buffer->space + shift, buffer->space, movebytes);
+    buffer->size = shift+movebytes;
+    
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+    
+    return 0;
+}
+
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) {
+    if (shift < 0) {
+        return ucx_buffer_shift_left(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return ucx_buffer_shift_right(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
--- a/src/ucx/buffer.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,270 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-/**
- * @file buffer.h
- * 
- * Advanced buffer implementation.
- * 
- * Instances of UcxBuffer can be used to read from or to write to like one
- * would do with a stream. This allows the use of ucx_stream_copy() to copy
- * contents from one buffer to another.
- * 
- * Some features for convenient use of the buffer
- * can be enabled. See the documentation of the macro constants for more
- * information.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_BUFFER_H
-#define	UCX_BUFFER_H
-
-#include "ucx.h"
-#include <sys/types.h>
-#include <stdio.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * No buffer features enabled (all flags cleared).
- */
-#define UCX_BUFFER_DEFAULT      0x00
-
-/**
- * If this flag is enabled, the buffer will automatically free its contents.
- */
-#define UCX_BUFFER_AUTOFREE     0x01
-
-/**
- * If this flag is enabled, the buffer will automatically extends its capacity.
- */
-#define UCX_BUFFER_AUTOEXTEND   0x02
-
-/** UCX Buffer. */
-typedef struct {
-    /** A pointer to the buffer contents. */
-    char *space;
-    /** Current position of the buffer. */
-    size_t pos;
-    /** Current capacity (i.e. maximum size) of the buffer. */
-    size_t capacity;
-    /** Current size of the buffer content. */
-    size_t size;
-    /**
-     * Flag register for buffer features.
-     * @see #UCX_BUFFER_DEFAULT
-     * @see #UCX_BUFFER_AUTOFREE
-     * @see #UCX_BUFFER_AUTOEXTEND
-     */
-    int flags;
-} UcxBuffer;
-
-/**
- * Creates a new buffer.
- * 
- * <b>Note:</b> you may provide <code>NULL</code> as argument for
- * <code>space</code>. Then this function will allocate the space and enforce
- * the #UCX_BUFFER_AUTOFREE flag.
- * 
- * @param space pointer to the memory area, or <code>NULL</code> to allocate
- * new memory
- * @param capacity the capacity of the buffer
- * @param flags buffer features (see UcxBuffer.flags)
- * @return the new buffer
- */
-UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags);
-
-/**
- * Destroys a buffer.
- * 
- * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer
- * are also freed.
- * 
- * @param buffer the buffer to destroy
- */
-void ucx_buffer_free(UcxBuffer* buffer);
-
-/**
- * Creates a new buffer and fills it with extracted content from another buffer.
- * 
- * <b>Note:</b> the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer.
- * 
- * @param src the source buffer
- * @param start the start position of extraction
- * @param length the count of bytes to extract (must not be zero)
- * @param flags feature mask for the new buffer
- * @return a new buffer containing the extraction
- */
-UcxBuffer* ucx_buffer_extract(UcxBuffer *src,
-        size_t start, size_t length, int flags);
-
-/**
- * A shorthand macro for the full extraction of the buffer.
- * 
- * @param src the source buffer
- * @param flags feature mask for the new buffer
- * @return a new buffer with the extracted content
- */
-#define ucx_buffer_clone(src,flags) \
-    ucx_buffer_extract(src, 0, (src)->capacity, flags)
-
-/**
- * Moves the position of the buffer.
- * 
- * The new position is relative to the <code>whence</code> argument.
- *
- * SEEK_SET marks the start of the buffer.
- * SEEK_CUR marks the current position.
- * SEEK_END marks the end of the buffer.
- * 
- * With an offset of zero, this function sets the buffer position to zero
- * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position
- * unchanged (SEEK_CUR).
- * 
- * @param buffer
- * @param offset position offset relative to <code>whence</code>
- * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END
- * @return 0 on success, non-zero if the position is invalid
- *
- */
-int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence);
-
-/**
- * Clears the buffer by resetting the position and deleting the data.
- * 
- * The data is deleted by a zeroing it with call to <code>memset()</code>.
- * 
- * @param buffer the buffer to be cleared
- */
-#define ucx_buffer_clear(buffer) memset(buffer->space, 0, buffer->size); \
-        buffer->size = 0; buffer->pos = 0;
-
-/**
- * Tests, if the buffer position has exceeded the buffer capacity.
- * 
- * @param buffer the buffer to test
- * @return non-zero, if the current buffer position has exceeded the last
- * available byte of the buffer.
- */
-int ucx_buffer_eof(UcxBuffer *buffer);
-
-
-/**
- * Extends the capacity of the buffer.
- * 
- * <b>Note:</b> The buffer capacity increased by a power of two. I.e.
- * the buffer capacity is doubled, as long as it would not hold the current
- * content plus the additional required bytes.
- * 
- * <b>Attention:</b> the argument provided is the number of <i>additional</i>
- * bytes the buffer shall hold. It is <b>NOT</b> the total number of bytes the
- * buffer shall hold.
- * 
- * @param buffer the buffer to extend
- * @param additional_bytes the number of additional bytes the buffer shall
- * <i>at least</i> hold
- * @return 0 on success or a non-zero value on failure
- */
-int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes);
-
-/**
- * Writes data to a UcxBuffer.
- * 
- * The position of the buffer is increased by the number of bytes written.
- * 
- * @param ptr a pointer to the memory area containing the bytes to be written
- * @param size the length of one element
- * @param nitems the element count
- * @param buffer the UcxBuffer to write to
- * @return the total count of bytes written
- */
-size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer);
-
-/**
- * Reads data from a UcxBuffer.
- * 
- * The position of the buffer is increased by the number of bytes read.
- * 
- * @param ptr a pointer to the memory area where to store the read data
- * @param size the length of one element
- * @param nitems the element count
- * @param buffer the UcxBuffer to read from
- * @return the total number of elements read
- */
-size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer);
-
-/**
- * Writes a character to a buffer.
- * 
- * The least significant byte of the argument is written to the buffer. If the
- * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled,
- * the buffer capacity is extended by ucx_buffer_extend(). If the feature is
- * disabled or buffer extension fails, <code>EOF</code> is returned.
- * 
- * On successful write the position of the buffer is increased.
- * 
- * @param buffer the buffer to write to
- * @param c the character to write as <code>int</code> value
- * @return the byte that has bean written as <code>int</code> value or
- * <code>EOF</code> when the end of the stream is reached and automatic
- * extension is not enabled or not possible
- */
-int ucx_buffer_putc(UcxBuffer *buffer, int c);
-
-/**
- * Gets a character from a buffer.
- * 
- * The current position of the buffer is increased after a successful read.
- * 
- * @param buffer the buffer to read from
- * @return the character as <code>int</code> value or <code>EOF</code>, if the
- * end of the buffer is reached
- */
-int ucx_buffer_getc(UcxBuffer *buffer);
-
-/**
- * Writes a string to a buffer.
- * 
- * @param buffer the buffer
- * @param str the string
- * @return the number of bytes written
- */
-size_t ucx_buffer_puts(UcxBuffer *buffer, char *str);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_BUFFER_H */
-
--- a/src/ucx/list.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/list.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,13 +26,13 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "list.h"
+#include "ucx/list.h"
 
-UcxList *ucx_list_clone(UcxList *l, copy_func fnc, void *data) {
+UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) {
     return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data);
 }
 
-UcxList *ucx_list_clone_a(UcxAllocator *alloc, UcxList *l,
+UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l,
         copy_func fnc, void *data) {
     UcxList *ret = NULL;
     while (l) {
@@ -77,6 +77,7 @@
 }
 
 void ucx_list_free_content(UcxList* list, ucx_destructor destr) {
+    if (!destr) destr = free;
     while (list != NULL) {
         destr(list->data);
         list = list->next;
@@ -171,7 +172,8 @@
     return (UcxList*)(index == 0 ? e : NULL);
 }
 
-ssize_t ucx_list_find(UcxList *l, void *elem, cmp_func fnc, void *cmpdata) {
+ssize_t ucx_list_find(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
     ssize_t index = 0;
     UCX_FOREACH(e, l) {
         if (fnc) {
@@ -188,7 +190,8 @@
     return -1;
 }
 
-int ucx_list_contains(UcxList *l, void *elem, cmp_func fnc, void *cmpdata) {
+int ucx_list_contains(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
     return ucx_list_find(l, elem, fnc, cmpdata) > -1;
 }
 
@@ -205,15 +208,15 @@
     return s;
 }
 
-static UcxList *ucx_list_sort_merge(int length,
-        UcxList* restrict ls, UcxList* restrict le, UcxList* restrict re,
+static UcxList *ucx_list_sort_merge(size_t length,
+        UcxList* ls, UcxList* le, UcxList* re,
         cmp_func fnc, void* data) {
 
     UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length);
     UcxList *rc, *lc;
 
     lc = ls; rc = le;
-    int n = 0;
+    size_t n = 0;
     while (lc && lc != le && rc != re) {
         if (fnc(lc->data, rc->data, data) <= 0) {
             sorted[n] = lc;
@@ -254,9 +257,9 @@
     }
 
     UcxList *lc;
-    int ln = 1;
+    size_t ln = 1;
 
-    UcxList *restrict ls = l, *restrict le, *restrict re;
+    UcxList *ls = l, *le, *re;
     
     // check how many elements are already sorted
     lc = ls;
@@ -270,7 +273,7 @@
         return l; // this list is already sorted :)
     } else {
         UcxList *rc;
-        int rn = 1;
+        size_t rn = 1;
         rc = le;
         // skip already sorted elements
         while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) {
@@ -333,3 +336,93 @@
     alfree(alloc, e);
     return l;
 }
+
+
+static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata,
+        int op) {
+    
+    UcxList *res = NULL;
+    UcxList *cur = NULL;
+    const UcxList *src = left;
+    
+    do {
+        UCX_FOREACH(node, src) {
+            void* elem = node->data;
+            if (
+                (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) ||
+                (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) ||
+                (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) {
+                UcxList *nl = almalloc(allocator, sizeof(UcxList));
+                nl->prev = cur;
+                nl->next = NULL;
+                if (cpfnc) {
+                    nl->data = cpfnc(elem, cpdata);
+                } else {
+                    nl->data = elem;
+                }
+                if (cur != NULL)
+                    cur->next = nl;
+                cur = nl;
+                if (res == NULL)
+                    res = cur;
+            }
+        }
+        if (op == 0 && src == left)
+            src = right;
+        else
+            src = NULL;
+    } while (src != NULL);
+    
+    return res;
+}
+
+UcxList* ucx_list_union(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_union_a(ucx_default_allocator(),
+            left, right, cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 0);
+}
+
+UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_intersection_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 1);
+}
+
+UcxList* ucx_list_difference(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_difference_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 2);
+}
--- a/src/ucx/list.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,393 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * Doubly linked list implementation.
- * 
- * @file   list.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_LIST_H
-#define	UCX_LIST_H
-
-#include "ucx.h"
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * Loop statement for UCX lists.
- * 
- * The first argument is the name of the iteration variable. The scope of
- * this variable is limited to the <code>UCX_FOREACH</code> statement.
- * 
- * The second argument is a pointer to the list. In most cases this will be the
- * pointer to the first element of the list, but it may also be an arbitrary
- * element of the list. The iteration will then start with that element.
- * 
- * @param list The first element of the list
- * @param elem The variable name of the element
- */
-#define UCX_FOREACH(elem,list) \
-        for (UcxList* elem = list ; elem != NULL ; elem = elem->next)
-
-/**
- * UCX list type.
- * @see UcxList
- */
-typedef struct UcxList UcxList;
-
-/**
- * UCX list structure.
- */
-struct UcxList {
-    /**
-     * List element payload.
-     */
-    void    *data;
-    /**
-     * Pointer to the next list element or <code>NULL</code>, if this is the
-     * last element.
-     */
-    UcxList *next;
-    /**
-     * Pointer to the previous list element or <code>NULL</code>, if this is
-     * the first element.
-     */
-    UcxList *prev;
-};
-
-/**
- * Creates an element-wise copy of a list.
- * 
- * This function clones the specified list by creating new list elements and
- * copying the data with the specified copy_func(). If no copy_func() is
- * specified, a shallow copy is created and the new list will reference the
- * same data as the source list.
- * 
- * @param list the list to copy
- * @param cpyfnc a pointer to the function that shall copy an element (may be
- * <code>NULL</code>)
- * @param data additional data for the copy_func()
- * @return a pointer to the copy
- */
-UcxList *ucx_list_clone(UcxList *list, copy_func cpyfnc, void* data);
-
-/**
- * Creates an element-wise copy of a list using a UcxAllocator.
- * 
- * See ucx_list_clone() for details.
- * 
- * You might want to pass the allocator via the <code>data</code> parameter,
- * to access it within the copy function for making deep copies.
- * 
- * @param allocator the allocator to use
- * @param list the list to copy
- * @param cpyfnc a pointer to the function that shall copy an element (may be
- * <code>NULL</code>)
- * @param data additional data for the copy_func()
- * @return a pointer to the copy
- * @see ucx_list_clone()
- */
-UcxList *ucx_list_clone_a(UcxAllocator *allocator, UcxList *list,
-        copy_func cpyfnc, void* data);
-
-/**
- * Compares two UCX lists element-wise by using a compare function.
- * 
- * Each element of the two specified lists are compared by using the specified
- * compare function and the additional data. The type and content of this
- * additional data depends on the cmp_func() used.
- * 
- * If the list pointers denote elements within a list, the lists are compared
- * starting with the denoted elements. Thus any previous elements are not taken
- * into account. This might be useful to check, if certain list tails match
- * each other.
- * 
- * @param list1 the first list
- * @param list2 the second list
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the two lists equal element-wise, 0 otherwise
- */
-int ucx_list_equals(const UcxList *list1, const UcxList *list2,
-        cmp_func cmpfnc, void* data);
-
-/**
- * Destroys the entire list.
- * 
- * The members of the list are not automatically freed, so ensure they are
- * otherwise referenced or destroyed by ucx_list_free_contents().
- * Otherwise, a memory leak is likely to occur.
- * 
- * <b>Caution:</b> the argument <b>MUST</b> denote an entire list (i.e. a call
- * to ucx_list_first() on the argument must return the argument itself)
- * 
- * @param list the list to free
- * @see ucx_list_free_contents()
- */
-void ucx_list_free(UcxList *list);
-
-/**
- * Destroys the entire list using a UcxAllocator.
- * 
- * See ucx_list_free() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list to free
- * @see ucx_list_free()
- */
-void ucx_list_free_a(UcxAllocator *allocator, UcxList *list);
-
-/**
- * Destroys the contents of the specified list by calling the specified
- * destructor on each of them.
- * 
- * Note, that the contents are not usable afterwards and the list should be
- * destroyed with ucx_list_free().
- * 
- * @param list the list for which the contents shall be freed
- * @param destr the destructor function (e.g. stdlib free())
- * @see ucx_list_free()
- */
-void ucx_list_free_content(UcxList* list, ucx_destructor destr);
-
-
-/**
- * Inserts an element at the end of the list.
- * 
- * This is generally an O(n) operation, as the end of the list is retrieved with
- * ucx_list_last().
- * 
- * @param list the list where to append the data, or <code>NULL</code> to
- * create a new list
- * @param data the data to insert
- * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
- * the newly created list otherwise
- */
-UcxList *ucx_list_append(UcxList *list, void *data);
-
-/**
- * Inserts an element at the end of the list using a UcxAllocator.
- * 
- * See ucx_list_append() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list where to append the data, or <code>NULL</code> to
- * create a new list
- * @param data the data to insert
- * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
- * the newly created list otherwise
- * @see ucx_list_append()
- */
-UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data);
-
-/**
- * Inserts an element at the beginning of the list.
- * 
- * You <i>should</i> overwrite the old list pointer by calling
- * <code>mylist = ucx_list_prepend(mylist, mydata);</code>. However, you may
- * also perform successive calls of ucx_list_prepend() on the same list pointer,
- * as this function always searchs for the head of the list with
- * ucx_list_first().
- * 
- * @param list the list where to insert the data or <code>NULL</code> to create
- * a new list
- * @param data the data to insert
- * @return a pointer to the new list head
- */
-UcxList *ucx_list_prepend(UcxList *list, void *data);
-
-/**
- * Inserts an element at the beginning of the list using a UcxAllocator.
- * 
- * See ucx_list_prepend() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list where to insert the data or <code>NULL</code> to create
- * a new list
- * @param data the data to insert
- * @return a pointer to the new list head
- * @see ucx_list_prepend()
- */
-UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data);
-
-/**
- * Concatenates two lists.
- * 
- * Either of the two arguments may be <code>NULL</code>.
- * 
- * This function modifies the references to the next/previous element of
- * the last/first element of <code>list1</code>/<code>
- * list2</code>.
- * 
- * @param list1 first list
- * @param list2 second list
- * @return if <code>list1</code> is <code>NULL</code>, <code>list2</code> is
- * returned, otherwise <code>list1</code> is returned
- */
-UcxList *ucx_list_concat(UcxList *list1, UcxList *list2);
-
-/**
- * Returns the first element of a list.
- * 
- * If the argument is the list pointer, it is directly returned. Otherwise
- * this function traverses to the first element of the list and returns the
- * list pointer.
- * 
- * @param elem one element of the list
- * @return the first element of the list, the specified element is a member of
- */
-UcxList *ucx_list_first(const UcxList *elem);
-
-/**
- * Returns the last element of a list.
- * 
- * If the argument has no successor, it is the last element and therefore
- * directly returned. Otherwise this function traverses to the last element of
- * the list and returns it.
- * 
- * @param elem one element of the list
- * @return the last element of the list, the specified element is a member of
- */
-UcxList *ucx_list_last(const UcxList *elem);
-
-/**
- * Returns the list element at the specified index.
- * 
- * @param list the list to retrieve the element from
- * @param index index of the element to return
- * @return the element at the specified index or <code>NULL</code>, if the
- * index is greater than the list size
- */
-UcxList *ucx_list_get(const UcxList *list, size_t index);
-
-/**
- * Returns the index of an element.
- * 
- * @param list the list where to search for the element
- * @param elem the element to find
- * @return the index of the element or -1 if the list does not contain the
- * element
- */
-ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem);
-
-/**
- * Returns the element count of the list.
- * 
- * @param list the list whose elements are counted
- * @return the element count
- */
-size_t ucx_list_size(const UcxList *list);
-
-/**
- * Returns the index of an element containing the specified data.
- *
- * This function uses a cmp_func() to compare the data of each list element
- * with the specified data. If no cmp_func is provided, the pointers are
- * compared.
- * 
- * If the list contains the data more than once, the index of the first
- * occurrence is returned.
- *  
- * @param list the list where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return the index of the element containing the specified data or -1 if the
- * data is not found in this list
- */
-ssize_t ucx_list_find(UcxList *list, void *elem, cmp_func cmpfnc, void *data);
-
-/**
- * Checks, if a list contains a specific element.
- * 
- * An element is found, if ucx_list_find() returns a value greater than -1.
- * 
- * @param list the list where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the list contains the specified element data
- * @see ucx_list_find()
- */
-int ucx_list_contains(UcxList *list, void *elem, cmp_func cmpfnc, void *data);
-
-/**
- * Sorts a UcxList with natural merge sort.
- * 
- * This function uses O(n) additional temporary memory for merge operations
- * that is automatically freed after each merge.
- * 
- * As the head of the list might change, you <b>MUST</b> call this function
- * as follows: <code>mylist = ucx_list_sort(mylist, mycmpfnc, mydata);</code>.
- * 
- * @param list the list to sort
- * @param cmpfnc the function that shall be used to compare the element data
- * @param data additional data for the cmp_func()
- * @return the sorted list
- */
-UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data);
-
-/**
- * Removes an element from the list.
- * 
- * If the first element is removed, the list pointer changes. So it is
- * <i>highly recommended</i> to <i>always</i> update the pointer by calling
- * <code>mylist = ucx_list_remove(mylist, myelem);</code>.
- * 
- * @param list the list from which the element shall be removed
- * @param element the element to remove
- * @return returns the updated list pointer or <code>NULL</code>, if the list
- * is now empty
- */
-UcxList *ucx_list_remove(UcxList *list, UcxList *element);
-
-/**
- * Removes an element from the list using a UcxAllocator.
- * 
- * See ucx_list_remove() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list from which the element shall be removed
- * @param element the element to remove
- * @return returns the updated list pointer or <code>NULL</code>, if the list
- * @see ucx_list_remove()
- */
-UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list,
-        UcxList *element);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_LIST_H */
-
--- a/src/ucx/logging.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/logging.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,7 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "logging.h"
+#include "ucx/logging.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
@@ -49,6 +50,8 @@
         ucx_map_int_put(logger->levels, l, (void*) "[WARNING]");
         l = UCX_LOGGER_INFO;
         ucx_map_int_put(logger->levels, l, (void*) "[INFO]");
+        l = UCX_LOGGER_DEBUG;
+        ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]");
         l = UCX_LOGGER_TRACE;
         ucx_map_int_put(logger->levels, l, (void*) "[TRACE]");
     }
@@ -68,12 +71,15 @@
         const unsigned int line, const char *format, ...) {
     if (level <= logger->level) {
         char msg[UCX_LOGGER_MSGMAX];
-        char *text;
+        const char *text;
         size_t k = 0;
         size_t n;
         
         if ((logger->mask & UCX_LOGGER_LEVEL) > 0) {
-            text = (char*) ucx_map_int_get(logger->levels, level);
+            text = (const char*) ucx_map_int_get(logger->levels, level);
+            if (!text) {
+                text = "[UNKNOWN]";
+            }
             n = strlen(text);
             n = n > 256 ? 256 : n;
             memcpy(msg+k, text, n);
@@ -85,13 +91,19 @@
             k += strftime(msg+k, 128, logger->dateformat, localtime(&now));
         }
         if ((logger->mask & UCX_LOGGER_SOURCE) > 0) {
+            char *fpart = strrchr(file, '/');
+            if (fpart) file = fpart+1;
+            fpart = strrchr(file, '\\');
+            if (fpart) file = fpart+1;
             n = strlen(file);
             memcpy(msg+k, file, n);
             k += n;
             k += sprintf(msg+k, ":%u ", line);
         }
         
-        msg[k++] = '-'; msg[k++] = ' ';
+        if (k > 0) {
+            msg[k++] = '-'; msg[k++] = ' ';
+        }
         
         va_list args;
         va_start (args, format);
--- a/src/ucx/logging.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * Logging API.
- * 
- * @file   logging.h
- * @author Mike Becker, Olaf Wintermann
- */
-#ifndef UCX_LOGGING_H
-#define UCX_LOGGING_H
-
-#include "ucx.h"
-#include "map.h"
-#include "string.h"
-#include <stdio.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* leave enough space for custom log levels */
-
-/** Log level for error messages. */
-#define UCX_LOGGER_ERROR        0x00
-    
-/** Log level for warning messages. */
-#define UCX_LOGGER_WARN         0x10
-
-/** Log level for information messages. */
-#define UCX_LOGGER_INFO         0x20
-
-/** Log level for debug messages. */
-#define UCX_LOGGER_DEBUG        0x30
-
-/** Log level for trace messages. */
-#define UCX_LOGGER_TRACE        0x40
-
-/**
- * Output flag for the log level. 
- * If this flag is set, the log message will contain the log level.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_LEVEL        0x01
-
-/**
- * Output flag for the timestmap.
- * If this flag is set, the log message will contain the timestmap.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_TIMESTAMP    0x02
-
-/**
- * Output flag for the source.
- * If this flag is set, the log message will contain the source file and line
- * number.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_SOURCE       0x04
-
-/**
- * The UCX Logger object.
- */
-typedef struct {
-    /** The stream this logger writes its messages to.*/
-    void *stream;
-
-    /**
-     * The write function that shall be used.
-     * For standard file or stdout loggers this might be standard fwrite
-     * (default).
-     */
-    write_func writer;
-
-    /**
-     * The date format for timestamp outputs including the delimiter
-     * (default: <code>"%F %T %z "</code>).
-     * @see UCX_LOGGER_TIMESTAMP
-     */
-    char *dateformat;
-
-    /**
-     * The level, this logger operates on.
-     * If a log command is issued, the message will only be logged, if the log
-     * level of the message is less or equal than the log level of the logger.
-     */
-    unsigned int level;
-
-    /**
-     * A configuration mask for automatic output. 
-     * For each flag that is set, the logger automatically outputs some extra
-     * information like the timestamp or the source file and line number.
-     * See the documentation for the flags for details.
-     */
-    unsigned int mask;
-
-    /**
-     * A map of valid log levels for this logger.
-     * 
-     * The keys represent all valid log levels and the values provide string
-     * representations, that are used, if the UCX_LOGGER_LEVEL flag is set.
-     * 
-     * The exact data types are <code>unsigned int</code> for the key and
-     * <code>const char*</code> for the value.
-     * 
-     * @see UCX_LOGGER_LEVEL
-     */
-    UcxMap* levels;
-} UcxLogger;
-
-/**
- * Creates a new logger.
- * @param stream the stream, which the logger shall write to
- * @param level the level on which the logger shall operate
- * @param mask configuration mask (cf. UcxLogger.mask)
- * @return a new logger object
- */
-UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask);
-
-/**
- * Destroys the logger.
- * 
- * The map containing the valid log levels is also automatically destroyed.
- * 
- * @param logger the logger to destroy
- */
-void ucx_logger_free(UcxLogger* logger);
-
-/**
- * Internal log function - use macros instead.
- * 
- * This function uses the <code>format</code> and variadic arguments for a
- * printf()-style output of the log message.
- * 
- * Dependent on the UcxLogger.mask some information is prepended. The complete
- * format is:
- * 
- * <code>[LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message</code>
- * 
- * <b>Attention:</b> the message (including automatically generated information)
- * is limited to 4096 characters. The level description is limited to
- * 256 characters and the timestamp string is limited to 128 characters.
- * 
- * @param logger the logger to use
- * @param level the level to log on
- * @param file information about the source file
- * @param line information about the source line number
- * @param format format string
- * @param ... arguments
- * @see ucx_logger_log()
- */
-void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
-        const unsigned int line, const char* format, ...);
-
-/**
- * Logs a message at the specified level.
- * @param logger the logger to use
- * @param level the level to log the message on
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_log(logger, level, ...) \
-    ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__)
-
-/**
- * Shortcut for logging an error message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_error(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__)
-
-/**
- * Shortcut for logging an information message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_info(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__)
-
-/**
- * Shortcut for logging a warning message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_warn(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__)
-
-/**
- * Shortcut for logging a debug message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_debug(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__)
-
-/**
- * Shortcut for logging a trace message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_trace(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* UCX_LOGGING_H */
--- a/src/ucx/map.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/map.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,11 +26,11 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ucx/map.h"
+
 #include <stdlib.h>
 #include <string.h>
 
-#include "map.h"
-
 UcxMap *ucx_map_new(size_t size) {
     return ucx_map_new_a(NULL, size);
 }
@@ -86,7 +86,11 @@
     UcxMapIterator iter = ucx_map_iterator(map);
     void *val;
     UCX_MAP_FOREACH(key, val, iter) {
-        destr(val);
+        if (destr) {
+            destr(val);
+        } else {
+            alfree(map->allocator, val);
+        }
     }
 }
 
@@ -99,8 +103,7 @@
     map->count = 0;
 }
 
-int ucx_map_copy(UcxMap *restrict from, UcxMap *restrict to,
-        copy_func fnc, void *data) {
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) {
     UcxMapIterator i = ucx_map_iterator(from);
     void *value;
     UCX_MAP_FOREACH(key, value, i) {
@@ -111,9 +114,14 @@
     return 0;
 }
 
-UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data) {
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) {
+    return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data);
+}
+
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+        UcxMap const *map, copy_func fnc, void *data) {
     size_t bs = (map->count * 5) >> 1;
-    UcxMap *newmap = ucx_map_new(bs > map->size ? bs : map->size);
+    UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size);
     if (!newmap) {
         return NULL;
     }
@@ -151,19 +159,22 @@
     UcxAllocator *allocator = map->allocator;
     
     if (key.hash == 0) {
-        key.hash = ucx_hash((char*)key.data, key.len);
+        key.hash = ucx_hash((const char*)key.data, key.len);
     }
+    
+    struct UcxMapKey mapkey;
+    mapkey.hash = key.hash;
 
-    size_t slot = key.hash%map->size;
-    UcxMapElement *restrict elm = map->map[slot];
-    UcxMapElement *restrict prev = NULL;
+    size_t slot = mapkey.hash%map->size;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *prev = NULL;
 
-    while (elm && elm->key.hash < key.hash) {
+    while (elm && elm->key.hash < mapkey.hash) {
         prev = elm;
         elm = elm->next;
     }
     
-    if (!elm || elm->key.hash != key.hash) {
+    if (!elm || elm->key.hash != mapkey.hash) {
         UcxMapElement *e = (UcxMapElement*)almalloc(
                 allocator, sizeof(UcxMapElement));
         if (!e) {
@@ -185,8 +196,9 @@
             return -1;
         }
         memcpy(kd, key.data, key.len);
-        key.data = kd;
-        elm->key = key;
+        mapkey.data = kd;
+        mapkey.len = key.len;
+        elm->key = mapkey;
         map->count++;
     }
     elm->data = data;
@@ -194,14 +206,14 @@
     return 0;
 }
 
-void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, _Bool remove) {
+static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) {
     if(key.hash == 0) {
-        key.hash = ucx_hash((char*)key.data, key.len);
+        key.hash = ucx_hash((const char*)key.data, key.len);
     }
     
     size_t slot = key.hash%map->size;
-    UcxMapElement *restrict elm = map->map[slot];
-    UcxMapElement *restrict pelm = NULL;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *pelm = NULL;
     while (elm && elm->key.hash <= key.hash) {
         if(elm->key.hash == key.hash) {
             int n = (key.len > elm->key.len) ? elm->key.len : key.len;
@@ -228,19 +240,19 @@
     return NULL;
 }
 
-void *ucx_map_get(UcxMap *map, UcxKey key) {
-    return ucx_map_get_and_remove(map, key, 0);
+void *ucx_map_get(UcxMap const *map, UcxKey key) {
+    return ucx_map_get_and_remove((UcxMap *)map, key, 0);
 }
 
 void *ucx_map_remove(UcxMap *map, UcxKey key) {
     return ucx_map_get_and_remove(map, key, 1);
 }
 
-UcxKey ucx_key(void *data, size_t len) {
+UcxKey ucx_key(const void *data, size_t len) {
     UcxKey key;
     key.data = data;
     key.len = len;
-    key.hash = ucx_hash((const char*) data, len);
+    key.hash = ucx_hash((const char*)data, len);
     return key;
 }
 
@@ -287,7 +299,7 @@
     return h;
 }
 
-UcxMapIterator ucx_map_iterator(UcxMap *map) {
+UcxMapIterator ucx_map_iterator(UcxMap const *map) {
     UcxMapIterator i;
     i.map = map;
     i.cur = NULL;
@@ -309,7 +321,9 @@
             if (e->data) {
                 i->cur = e;
                 *elm = e->data;
-                *key = e->key;
+                key->data = e->key.data;
+                key->hash = e->key.hash;
+                key->len = e->key.len;
                 return 1;
             }
 
@@ -326,3 +340,63 @@
     return 0;
 }
 
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata) {
+    return ucx_map_union_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata) {
+    UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata);
+    ucx_map_copy(second, result, cpfnc, cpdata);
+    return result;
+}
+
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+    return ucx_map_intersection_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata) {
+    UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ?
+            first->size : second->size);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    return result;
+}
+
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata) {
+    return ucx_map_difference_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+
+    UcxMap *result = ucx_map_new_a(allocator, first->size - second->count);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (!ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    ucx_map_rehash(result);
+    return result;
+}
\ No newline at end of file
--- a/src/ucx/map.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,423 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-/**
- * @file map.h
- * 
- * Hash map implementation.
- * 
- * This implementation uses murmur hash 2 and separate chaining with linked
- * lists.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_MAP_H
-#define	UCX_MAP_H
-
-#include "ucx.h"
-#include "string.h"
-#include "allocator.h"
-#include <stdio.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * Loop statement for UCX maps.
- * 
- * The <code>key</code> variable is implicitly defined, but the
- * <code>value</code> variable must be already declared as type information
- * cannot be inferred.
- * 
- * @param key the variable name for the key
- * @param value the variable name for the value
- * @param iter a UcxMapIterator
- * @see ucx_map_iterator()
- */
-#define UCX_MAP_FOREACH(key,value,iter) \
-        for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);)
-
-/** Type for the UCX map. @see UcxMap */
-typedef struct UcxMap          UcxMap;
-
-/** Type for a key of a UcxMap. @see UcxKey */
-typedef struct UcxKey          UcxKey;
-
-/** Type for an element of a UcxMap. @see UcxMapElement */
-typedef struct UcxMapElement   UcxMapElement;
-
-/** Type for an iterator over a UcxMap. @see UcxMapIterator */
-typedef struct UcxMapIterator  UcxMapIterator;
-
-/** Structure for the UCX map. */
-struct UcxMap {
-    /** An allocator that is used for the map elements. */
-    UcxAllocator  *allocator;
-    /** The array of map element lists. */
-    UcxMapElement **map;
-    /** The size of the map is the length of the element list array. */
-    size_t        size;
-    /** The count of elements currently stored in this map. */
-    size_t        count;
-};
-
-/** Structure for a key of a UcxMap. */
-struct UcxKey {
-    /** The key data. */
-    void   *data;
-    /** The length of the key data. */
-    size_t len;
-    /** The hash value of the key data. */
-    int    hash;
-};
-
-/** Structure for an element of a UcxMap. */
-struct UcxMapElement {
-    /** The value data. */
-    void          *data;
-    
-    /** A pointer to the next element in the current list. */
-    UcxMapElement *next;
-    
-    /** The corresponding key. */
-    UcxKey        key;
-};
-
-/** Structure for an iterator over a UcxMap. */
-struct UcxMapIterator {
-    /** The map to iterate over. */
-    UcxMap        *map;
-    
-    /** The current map element. */
-    UcxMapElement *cur;
-    
-    /**
-     * The current index of the element list array.
-     * <b>Attention: </b> this is <b>NOT</b> the element index! Do <b>NOT</b>
-     * manually iterate over the map by increasing this index. Use
-     * ucx_map_iter_next().
-     * @see UcxMap.map*/
-    size_t        index;
-};
-
-/**
- * Creates a new hash map with the specified size.
- * @param size the size of the hash map
- * @return a pointer to the new hash map
- */
-UcxMap *ucx_map_new(size_t size);
-
-/**
- * Creates a new hash map with the specified size using a UcxAllocator.
- * @param allocator the allocator to use
- * @param size the size of the hash map
- * @return a pointer to the new hash map
- */
-UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size);
-
-/**
- * Frees a hash map.
- * 
- * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
- * before calling this function to achieve that.
- * 
- * @param map the map to be freed
- * @see ucx_map_free_content()
- */
-void ucx_map_free(UcxMap *map);
-
-/**
- * Frees the contents of a hash map.
- * 
- * This is a convenience function that iterates over the map and passes all
- * values to the specified destructor function (e.g. stdlib free()).
- * 
- * You must ensure, that it is valid to pass each value in the map to the same
- * destructor function.
- * 
- * You should free or clear the map afterwards, as the contents will be invalid.
- * 
- * @param map for which the contents shall be freed
- * @param destr pointer to the destructor function
- * @see ucx_map_free()
- * @see ucx_map_clear()
- */
-void ucx_map_free_content(UcxMap *map, ucx_destructor destr);
-
-/**
- * Clears a hash map.
- * 
- * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
- * before calling this function to achieve that.
- * 
- * @param map the map to be cleared
- * @see ucx_map_free_content()
- */
-void ucx_map_clear(UcxMap *map);
-
-
-/**
- * Copies contents from a map to another map using a copy function.
- * 
- * <b>Note:</b> The destination map does not need to be empty. However, if it
- * contains data with keys that are also present in the source map, the contents
- * are overwritten.
- * 
- * @param from the source map
- * @param to the destination map
- * @param fnc the copy function or <code>NULL</code> if the pointer address
- * shall be copied
- * @param data additional data for the copy function
- * @return 0 on success or a non-zero value on memory allocation errors
- */
-int ucx_map_copy(UcxMap *restrict from, UcxMap *restrict to,
-        copy_func fnc, void *data);
-
-/**
- * Clones the map and rehashes if necessary.
- * 
- * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
- * This function <i>always</i> ensures a new UcxMap.size of at least
- * 2.5*UcxMap.count.
- * 
- * @param map the map to clone
- * @param fnc the copy function to use or <code>NULL</code> if the new and
- * the old map shall share the data pointers
- * @param data additional data for the copy function
- * @return the cloned map
- * @see ucx_map_copy()
- */
-UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data);
-
-/**
- * Increases size of the hash map, if necessary.
- * 
- * The load value is 0.75*UcxMap.size. If the element count exceeds the load
- * value, the map needs to be rehashed. Otherwise no action is performed and
- * this function simply returns 0.
- * 
- * The rehashing process ensures, that the UcxMap.size is at least
- * 2.5*UcxMap.count. So there is enough room for additional elements without
- * the need of another soon rehashing.
- * 
- * You can use this function to dramatically increase access performance.
- * 
- * @param map the map to rehash
- * @return 1, if a memory allocation error occurred, 0 otherwise
- */
-int ucx_map_rehash(UcxMap *map);
-
-/**
- * Puts a key/value-pair into the map.
- * 
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-int ucx_map_put(UcxMap *map, UcxKey key, void *value);
-
-/**
- * Retrieves a value by using a key.
- * 
- * @param map the map
- * @param key the key
- * @return the value
- */
-void* ucx_map_get(UcxMap *map, UcxKey key);
-
-/**
- * Removes a key/value-pair from the map by using the key.
- * 
- * @param map the map
- * @param key the key
- * @return the removed value
- */
-void* ucx_map_remove(UcxMap *map, UcxKey key);
-
-/**
- * Shorthand for putting data with a sstr_t key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_sstr_put(map, key, value) \
-    ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value)
-
-/**
- * Shorthand for putting data with a C string key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_cstr_put(map, key, value) \
-    ucx_map_put(map, ucx_key((void*)key, strlen(key)), (void*)value)
-
-/**
- * Shorthand for putting data with an integer key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_int_put(map, key, value) \
-    ucx_map_put(map, ucx_key((void*)&key, sizeof(key)), (void*)value)
-
-/**
- * Shorthand for getting data from the map with a sstr_t key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_sstr_get(map, key) \
-    ucx_map_get(map, ucx_key(key.ptr, key.length))
-
-/**
- * Shorthand for getting data from the map with a C string key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_cstr_get(map, key) \
-    ucx_map_get(map, ucx_key((void*)key, strlen(key)))
-
-/**
- * Shorthand for getting data from the map with an integer key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_int_get(map, key) \
-    ucx_map_get(map, ucx_key((void*)&key, sizeof(int)))
-
-/**
- * Shorthand for removing data from the map with a sstr_t key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_sstr_remove(map, key) \
-    ucx_map_remove(map, ucx_key(key.ptr, key.length))
-
-/**
- * Shorthand for removing data from the map with a C string key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_cstr_remove(map, key) \
-    ucx_map_remove(map, ucx_key((void*)key, strlen(key)))
-
-/**
- * Shorthand for removing data from the map with an integer key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_int_remove(map, key) \
-    ucx_map_remove(map, ucx_key((void*)&key, sizeof(key)))
-
-/**
- * Creates a UcxKey based on the given data.
- * 
- * This function implicitly computes the hash.
- * 
- * @param data the data for the key
- * @param len the length of the data
- * @return a UcxKey with implicitly computed hash
- * @see ucx_hash()
- */
-UcxKey ucx_key(void *data, size_t len);
-
-/**
- * Computes a murmur hash-2.
- * 
- * @param data the data to hash
- * @param len the length of the data
- * @return the murmur hash-2 of the data
- */
-int ucx_hash(const char *data, size_t len);
-
-/**
- * Creates an iterator for a map.
- * 
- * <b>Note:</b> A UcxMapIterator iterates over all elements in all element
- * lists successively. Therefore the order highly depends on the key hashes and
- * may vary under different map sizes. So generally you may <b>NOT</b> rely on
- * the iteration order.
- * 
- * <b>Note:</b> The iterator is <b>NOT</b> initialized. You need to call
- * ucx_map_iter_next() at least once before accessing any information. However,
- * it is not recommended to access the fields of a UcxMapIterator directly.
- * 
- * @param map the map to create the iterator for
- * @return an iterator initialized on the first element of the
- * first element list
- * @see ucx_map_iter_next()
- */
-UcxMapIterator ucx_map_iterator(UcxMap *map);
-
-/**
- * Proceeds to the next element of the map (if any).
- * 
- * Subsequent calls on the same iterator proceed to the next element and
- * store the key/value-pair into the memory specified as arguments of this
- * function.
- * 
- * If no further elements are found, this function returns zero and leaves the
- * last found key/value-pair in memory.
- * 
- * @param iterator the iterator to use
- * @param key a pointer to the memory where to store the key
- * @param value a pointer to the memory where to store the value
- * @return 1, if another element was found, 0 if all elements has been processed
- * @see ucx_map_iterator()
- */
-int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value);
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_MAP_H */
-
--- a/src/ucx/mempool.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/mempool.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,6 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ucx/mempool.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -34,8 +36,6 @@
 #endif
 #include <inttypes.h>
 
-#include "mempool.h"
-
 /** Capsule for destructible memory chunks. */
 typedef struct {
     /** The destructor for the memory chunk. */
@@ -56,18 +56,26 @@
     void           *ptr;
 } ucx_regdestr;
 
-UCX_EXTERN void ucx_mempool_shared_destr(void* ptr) {
+#ifdef __cplusplus
+extern "C"
+#endif
+void ucx_mempool_shared_destr(void* ptr) {
     ucx_regdestr *rd = (ucx_regdestr*)ptr;
     rd->destructor(rd->ptr);
 }
 
 UcxMempool *ucx_mempool_new(size_t n) {
+    size_t poolsz;
+    if(ucx_szmul(n, sizeof(void*), &poolsz)) {
+        return NULL;
+    }
+    
     UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool));
     if (!pool) {
         return NULL;
     }
     
-    pool->data = (void**) malloc(n * sizeof(void*));
+    pool->data = (void**) malloc(poolsz);
     if (pool->data == NULL) {
         free(pool);
         return NULL;
@@ -93,25 +101,39 @@
 }
 
 int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) {
-    void **data = (void**) realloc(pool->data, newcap*sizeof(void*));
+    if (newcap < pool->ndata) {
+        return 1;
+    }
+    
+    size_t newcapsz;
+    if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) {
+        return 1;
+    }
+    
+    void **data = (void**) realloc(pool->data, newcapsz);
     if (data) {
         pool->data = data; 
         pool->size = newcap;
-        return EXIT_SUCCESS;
+        return 0;
     } else {
-        return EXIT_FAILURE;
+        return 1;
     }
 }
 
 void *ucx_mempool_malloc(UcxMempool *pool, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
     if (pool->ndata >= pool->size) {
-        // The hard coded 16 is documented for this function and ucx_mempool_new
-        if (ucx_mempool_chcap(pool, pool->size + 16) == EXIT_FAILURE) {
+        size_t newcap = pool->size*2;
+        if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) {
             return NULL;
         }
     }
 
-    ucx_memchunk *mem = (ucx_memchunk*)malloc(sizeof(ucx_destructor) + n);
+    void *p = malloc(sizeof(ucx_destructor) + n);
+    ucx_memchunk *mem = (ucx_memchunk*)p;
     if (!mem) {
         return NULL;
     }
@@ -124,7 +146,12 @@
 }
 
 void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) {
-    void *ptr = ucx_mempool_malloc(pool, nelem*elsize);
+    size_t msz;
+    if(ucx_szmul(nelem, elsize, &msz)) {
+        return NULL;
+    }
+    
+    void *ptr = ucx_mempool_malloc(pool, msz);
     if (!ptr) {
         return NULL;
     }
@@ -133,6 +160,10 @@
 }
 
 void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
     char *mem = ((char*)ptr) - sizeof(ucx_destructor);
     char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor));
     if (!newm) {
@@ -147,7 +178,7 @@
         }
         fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
           (intptr_t)ptr, (intptr_t)pool);
-        exit(EXIT_FAILURE);
+        abort();
     } else {
         return newm + sizeof(ucx_destructor);
     }
@@ -172,7 +203,7 @@
     }
     fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
             (intptr_t)ptr, (intptr_t)pool);
-    exit(EXIT_FAILURE);
+    abort();
 }
 
 void ucx_mempool_destroy(UcxMempool *pool) {
--- a/src/ucx/mempool.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-/**
- * @file mempool.h
- * 
- * Memory pool implementation.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_MEMPOOL_H
-#define	UCX_MEMPOOL_H
-
-#include "ucx.h"
-#include <stddef.h>
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UCX mempool structure.
- */
-typedef struct {
-    /** UcxAllocator based on this pool */
-    UcxAllocator *allocator;
-    
-    /** List of pointers to pooled memory. */
-    void         **data;
-    
-    /** Count of pooled memory items. */
-    size_t       ndata;
-    
-    /** Memory pool size. */
-    size_t       size;
-} UcxMempool;
-
-/** Shorthand for a new default memory pool with a capacity of 16 elements. */
-#define ucx_mempool_new_default() ucx_mempool_new(16)
-
-
-/**
- * Creates a memory pool with the specified initial size.
- * 
- * As the created memory pool automatically grows in size by 16 elements, when
- * trying to allocate memory on a full pool, it is recommended that you use
- * a multiple of 16 for the initial size.
- * 
- * @param n initial pool size (should be a multiple of 16)
- * @return a pointer to the new memory pool
- */
-UcxMempool *ucx_mempool_new(size_t n);
-
-/**
- * Resizes a memory pool.
- * 
- * @param pool the pool to resize
- * @param newcap the new capacity
- * @return <code>EXIT_SUCCESS</code> on success or
- * <code>EXIT_FAILURE</code> on failure
- */
-int ucx_mempool_chcap(UcxMempool *pool, size_t newcap);
-
-/**
- * Changes the pool size to the next smallest multiple of 16.
- * 
- * You may use this macro, to reduce the pool size after freeing
- * many pooled memory items.
- * 
- * @param pool the pool to clamp
- * @return <code>EXIT_SUCCESS</code> on success or
- * <code>EXIT_FAILURE</code> on failure
- */
-#define ucx_mempool_clamp(pool) ucx_mempool_chcap(pool, \
-        (pool->ndata & ~0xF)+0x10)
-
-/**
- * Allocates pooled memory.
- * 
- * @param pool the memory pool
- * @param n amount of memory to allocate
- * @return a pointer to the allocated memory
- * @see ucx_allocator_malloc()
- */
-void *ucx_mempool_malloc(UcxMempool *pool, size_t n);
-/**
- * Allocates a pooled memory array.
- * 
- * The content of the allocated memory is set to zero.
- * 
- * @param pool the memory pool
- * @param nelem amount of elements to allocate
- * @param elsize amount of memory per element
- * @return a pointer to the allocated memory
- * @see ucx_allocator_calloc()
- */
-void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize);
-
-/**
- * Reallocates pooled memory.
- * 
- * If the memory to be reallocated is not contained by the specified pool, the
- * behavior is undefined.
- * 
- * @param pool the memory pool
- * @param ptr a pointer to the memory that shall be reallocated
- * @param n the new size of the memory
- * @return a pointer to the new location of the memory
- * @see ucx_allocator_realloc()
- */
-void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n);
-
-/**
- * Frees pooled memory.
- * 
- * Before freeing the memory, the specified destructor function (if any)
- * is called.
- * 
- * If you specify memory, that is not pooled by the specified memory pool, the
- * behavior is undefined.
- * 
- * @param pool the memory pool
- * @param ptr a pointer to the memory that shall be freed
- * @see ucx_mempool_set_destr()
- */
-void ucx_mempool_free(UcxMempool *pool, void *ptr);
-
-/**
- * Destroys a memory pool.
- * 
- * For each element the destructor function (if any) is called and the element
- * is freed.
- * 
- * Each of the registered destructor function that has no corresponding element
- * within the pool (namely those registered by ucx_mempool_reg_destr) is
- * called interleaving with the element destruction, but with guarantee to the
- * order in which they were registered (FIFO order).
- * 
- * 
- * @param pool the mempool to destroy
- */
-void ucx_mempool_destroy(UcxMempool *pool);
-
-/**
- * Sets a destructor function for the specified memory.
- * 
- * The destructor is automatically called when the memory is freed or the
- * pool is destroyed.
- * 
- * The only requirement for the specified memory is, that it <b>MUST</b> be
- * pooled memory by a UcxMempool or an element-compatible mempool. The pointer
- * to the destructor function is saved in a reserved area before the actual
- * memory.
- * 
- * @param ptr pooled memory
- * @param func a pointer to the destructor function
- * @see ucx_mempool_free()
- * @see ucx_mempool_destroy()
- */
-void ucx_mempool_set_destr(void *ptr, ucx_destructor func);
-
-/**
- * Registers a destructor function for the specified (non-pooled) memory.
- * 
- * This is useful, if you have memory that has not been allocated by a mempool,
- * but shall be managed by a mempool.
- * 
- * This function creates an entry in the specified mempool and the memory will
- * therefore (logically) convert to pooled memory.
- * 
- * @param pool the memory pool
- * @param ptr data the destructor is registered for
- * @param destr a pointer to the destructor function
- */
-void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_MEMPOOL_H */
-
--- a/src/ucx/properties.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/properties.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,12 +26,12 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ucx/properties.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "properties.h"
-
 UcxProperties *ucx_properties_new() {
     UcxProperties *parser = (UcxProperties*)malloc(
             sizeof(UcxProperties));
--- a/src/ucx/properties.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * @file properties.h
- * 
- * Load / store utilities for properties files.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_PROPERTIES_H
-#define	UCX_PROPERTIES_H
-
-#include "ucx.h"
-#include "map.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UcxProperties object for parsing properties data.
- * Most of the fields are for internal use only. You may configure the
- * properties parser, e.g. by changing the used delimiter or specifying 
- * up to three different characters that shall introduce comments.
- */
-typedef struct {
-    /**
-     * Input buffer (don't set manually).
-     * Automatically set by calls to ucx_properties_fill().
-     */
-    char   *buffer;
-    
-    /**
-     * Length of the input buffer (don't set manually).
-     * Automatically set by calls to ucx_properties_fill().
-     */
-    size_t buflen;
-    
-    /**
-     * Current buffer position (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t pos;
-    
-    /**
-     * Internal temporary buffer (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    char   *tmp;
-    
-    /**
-     * Internal temporary buffer length (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t tmplen;
-    
-    /**
-     * Internal temporary buffer capacity (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t tmpcap;
-    
-    /**
-     * Parser error code.
-     * This is always 0 on success and a nonzero value on syntax errors.
-     * The value is set by ucx_properties_next().
-     */
-    int    error;
-    
-    /**
-     * The delimiter that shall be used.
-     * This is '=' by default.
-     */
-    char   delimiter;
-    
-    /**
-     * The first comment character.
-     * This is '#' by default.
-     */
-    char   comment1;
-    
-    /**
-     * The second comment character.
-     * This is not set by default.
-     */
-    char   comment2;
-    
-    /**
-     * The third comment character.
-     * This is not set by default.
-     */
-    char   comment3;
-} UcxProperties;
-
-
-/**
- * Constructs a new UcxProperties object.
- * @return a pointer to the new UcxProperties object
- */
-UcxProperties *ucx_properties_new();
-
-/**
- * Destroys a UcxProperties object.
- * @param prop the UcxProperties object to destroy
- */
-void ucx_properties_free(UcxProperties *prop);
-
-/**
- * Sets the input buffer for the properties parser.
- * 
- * After calling this function, you may parse the data by calling
- * ucx_properties_next() until it returns 0. The function ucx_properties2map()
- * is a convenience function that reads as much data as possible by using this
- * function.
- * 
- * 
- * @param prop the UcxProperties object
- * @param buf a pointer to the new buffer
- * @param len the payload length of the buffer
- * @see ucx_properties_next()
- * @see ucx_properties2map()
- */
-void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
-
-/**
- * Retrieves the next key/value-pair.
- * 
- * This function returns a nonzero value as long as there are key/value-pairs
- * found. If no more key/value-pairs are found, you may refill the input buffer
- * with ucx_properties_fill().
- * 
- * <b>Attention:</b> the sstr_t.ptr pointers of the output parameters point to
- * memory within the input buffer of the parser and will get invalid some time.
- * If you want long term copies of the key/value-pairs, use sstrdup() after
- * calling this function.
- * 
- * @param prop the UcxProperties object
- * @param name a pointer to the sstr_t that shall contain the property name
- * @param value a pointer to the sstr_t that shall contain the property value
- * @return Nonzero, if a key/value-pair was successfully retrieved
- * @see ucx_properties_fill()
- */
-int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value);
-
-/**
- * Retrieves all available key/value-pairs and puts them into a UcxMap.
- * 
- * This is done by successive calls to ucx_properties_next() until no more
- * key/value-pairs can be retrieved. 
- * 
- * @param prop the UcxProperties object
- * @param map the target map
- * @return The UcxProperties.error code (i.e. 0 on success).
- * @see ucx_properties_fill()
- */
-int ucx_properties2map(UcxProperties *prop, UcxMap *map);
-
-/**
- * Loads a properties file to a UcxMap.
- * 
- * This is a convenience function that reads data from an input
- * stream until the end of the stream is reached.
- * 
- * @param map the map object to write the key/value-pairs to
- * @param file the <code>FILE*</code> stream to read from
- * @return 0 on success, or a non-zero value on error
- * 
- * @see ucx_properties_fill()
- * @see ucx_properties2map()
- */
-int ucx_properties_load(UcxMap *map, FILE *file);
-
-/**
- * Stores a UcxMap to a file.
- * 
- * The key/value-pairs are written by using the following format:
- * 
- * <code>[key] = [value]\\n</code>
- * 
- * @param map the map to store
- * @param file the <code>FILE*</code> stream to write to
- * @return 0 on success, or a non-zero value on error
- */
-int ucx_properties_store(UcxMap *map, FILE *file);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_PROPERTIES_H */
-
--- a/src/ucx/stack.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/stack.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,7 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "stack.h"
+#include "ucx/stack.h"
+
 #include <string.h>
 
 static size_t ucx_stack_align(size_t n) {
@@ -119,13 +120,15 @@
         return;
     }
     
-    size_t len = ucx_stack_topsize(stack);
-    if (len > n) {
-        len = n;
+    if (dest) {
+        size_t len = ucx_stack_topsize(stack);
+        if (len > n) {
+            len = n;
+        }
+
+        memcpy(dest, stack->top, len);
     }
     
-    memcpy(dest, stack->top, len);
-    
     ucx_stack_free(stack, stack->top);
 }
 
@@ -141,3 +144,22 @@
         return 0;
     }
 }
+
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) {
+    void *space = ucx_stack_malloc(stack, n);
+    if (space) {
+        memcpy(space, data, n);
+    }
+    return space;
+}
+
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data) {
+    
+    // skip the memset by using malloc
+    void *space = ucx_stack_malloc(stack, nelem*elsize);
+    if (space) {
+        memcpy(space, data, nelem*elsize);
+    }
+    return space;
+}
--- a/src/ucx/stack.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-/**
- * @file stack.h
- * 
- * Default stack memory allocation implementation.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_STACK_H
-#define	UCX_STACK_H
-
-#include "ucx.h"
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-
-/**
- * UCX stack structure.
- */
-typedef struct {
-    /** UcxAllocator based on this stack */
-    UcxAllocator allocator;
-    
-    /** Stack size. */
-    size_t size;
-    
-    /** Pointer to the bottom of the stack */
-    char *space;
-    
-    /** Pointer to the top of the stack */
-    char *top;
-} UcxStack;
-
-/**
- * Metadata for each UCX stack element.
- */
-struct ucx_stack_metadata {
-    /**
-     * Location of the previous element (<code>NULL</code> if this is the first)
-     */
-    char *prev;
-    
-    /** Size of this element */
-    size_t size;
-};
-
-/**
- * Initializes UcxStack structure with memory.
- * 
- * @param stack a pointer to an uninitialized stack structure
- * @param space the memory area that shall be managed
- * @param size size of the memory area
- * @return a new UcxStack structure
- */
-void ucx_stack_init(UcxStack *stack, char* space, size_t size);
-
-/**
- * Allocates stack memory.
- * 
- * @param stack a pointer to the stack
- * @param n amount of memory to allocate
- * @return a pointer to the allocated memory
- * @see ucx_allocator_malloc()
- */
-void *ucx_stack_malloc(UcxStack *stack, size_t n);
-
-/**
- * Alias for #ucx_stack_malloc().
- * @param stack a pointer to the stack
- * @param n amount of memory to allocate
- * @return a pointer to the allocated memory
- * @see ucx_stack_malloc
- */
-#define ucx_stack_push(stack, n) ucx_stack_malloc(stack, n)
-
-/**
- * Allocates an array of stack memory
- * 
- * The content of the allocated memory is set to zero.
- * 
- * @param stack a pointer to the stack
- * @param nelem amount of elements to allocate
- * @param elsize amount of memory per element
- * @return a pointer to the allocated memory
- * @see ucx_allocator_calloc()
- */
-void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
-
-/**
- * Alias for #ucx_stack_calloc().
- * 
- * @param stack a pointer to the stack
- * @param n amount of elements to allocate
- * @param elsize amount of memory per element
- * @return a pointer to the allocated memory
- * @see ucx_stack_calloc
- */
-#define ucx_stack_pusharr(stack,n,elsize) ucx_stack_calloc(stack,n,elssize)
-
-/**
- * Reallocates memory on the stack.
- * 
- * Shrinking memory is always safe. Extending memory can be very expensive. 
- * 
- * @param stack the stack
- * @param ptr a pointer to the memory that shall be reallocated
- * @param n the new size of the memory
- * @return a pointer to the new location of the memory
- * @see ucx_allocator_realloc()
- */
-void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n);
-
-/**
- * Frees memory on the stack.
- * 
- * Freeing stack memory behaves in a special way.
- * 
- * If the element, that should be freed, is the top most element of the stack,
- * it is removed from the stack. Otherwise it is marked as freed. Marked
- * elements are removed, when they become the top most elements of the stack.
- * 
- * @param stack a pointer to the stack
- * @param ptr a pointer to the memory that shall be freed
- */
-void ucx_stack_free(UcxStack *stack, void *ptr);
-
-
-/**
- * Returns the size of the top most element.
- * @param stack a pointer to the stack
- * @return the size of the top most element
- */
-#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\
-                                  (stack)->top - 1)->size : 0)
-
-/**
- * Removes the top most element from the stack and copies the content to <code>
- * dest</code>, if specified.
- * 
- * Use #ucx_stack_topsize()# to get the amount of memory that must be available
- * at the location of <code>dest</code>.
- * 
- * @param stack a pointer to the stack
- * @param dest the location where the contents shall be written to, or <code>
- * NULL</code>, if the element shall only be removed.
- * @see ucx_stack_free
- * @see ucx_stack_popn
- */
-#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1)
-
-/**
- * Removes the top most element from the stack and copies the content to <code>
- * dest</code>.
- * 
- * In contrast to #ucx_stack_pop() the <code>dest</code> pointer <code>MUST
- * NOT</code> be <code>NULL</code>.
- * 
- * @param stack a pointer to the stack
- * @param dest the location where the contents shall be written to
- * @param n copies at most n elements to <code>dest</code>
- * @see ucx_stack_pop
- */
-void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
-
-/**
- * Returns the remaining available memory on the specified stack.
- * 
- * @param stack a pointer to the stack
- * @return the remaining available memory
- */
-size_t ucx_stack_avail(UcxStack *stack);
-
-/**
- * Checks, if the stack is empty.
- * 
- * @param stack a pointer to the stack
- * @return nonzero, if the stack is empty, zero otherwise
- */
-#define ucx_stack_empty(stack) (!(stack)->top)
-
-/**
- * Computes a recommended size for the stack memory area. Note, that
- * reallocations have not been taken into account, so you might need to reserve
- * twice as much memory to allow many reallocations.
- * 
- * @param size the approximate payload
- * @param elems the approximate count of element allocations
- * @return a recommended size for the stack space based on the information
- * provided
- */
-#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \
-                                    (elems + 1))
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_STACK_H */
-
--- a/src/ucx/string.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/string.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,13 +26,19 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ucx/string.h"
+
+#include "ucx/allocator.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
+#include <stdint.h>
 #include <ctype.h>
 
-#include "string.h"
-#include "allocator.h"
+#ifndef _WIN32
+#include <strings.h> /* for strncasecmp() */
+#endif /* _WIN32 */
 
 sstr_t sstr(char *cstring) {
     sstr_t string;
@@ -48,13 +54,35 @@
     return string;
 }
 
-size_t sstrnlen(size_t n, sstr_t s, ...) {
+scstr_t scstr(const char *cstring) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = strlen(cstring);
+    return string;
+}
+
+scstr_t scstrn(const char *cstring, size_t length) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = length;
+    return string;
+}
+
+
+size_t scstrnlen(size_t n, ...) {
+    if (n == 0) return 0;
+    
     va_list ap;
-    size_t size = s.length;
-    va_start(ap, s);
+    va_start(ap, n);
+    
+    size_t size = 0;
 
-    for (size_t i = 1 ; i < n ; i++) {
-        sstr_t str = va_arg(ap, sstr_t);
+    for (size_t i = 0 ; i < n ; i++) {
+        scstr_t str = va_arg(ap, scstr_t);
+        if(SIZE_MAX - str.length < size) {
+            size = SIZE_MAX;
+            break;
+        }
         size += str.length;
     }
     va_end(ap);
@@ -65,8 +93,7 @@
 static sstr_t sstrvcat_a(
         UcxAllocator *a,
         size_t count,
-        sstr_t s1,
-        sstr_t s2,
+        scstr_t s1,
         va_list ap) {
     sstr_t str;
     str.ptr = NULL;
@@ -75,7 +102,13 @@
         return str;
     }
     
-    sstr_t *strings = (sstr_t*) calloc(count, sizeof(sstr_t));
+    scstr_t s2 = va_arg (ap, scstr_t);
+    
+    if(((size_t)-1) - s1.length < s2.length) {
+        return str;
+    }
+    
+    scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t));
     if(!strings) {
         return str;
     }
@@ -83,16 +116,25 @@
     // get all args and overall length
     strings[0] = s1;
     strings[1] = s2;
-    size_t strlen = s1.length + s2.length;
+    size_t slen = s1.length + s2.length;
+    int error = 0;
     for (size_t i=2;i<count;i++) {
-        sstr_t s = va_arg (ap, sstr_t);
+        scstr_t s = va_arg (ap, scstr_t);
         strings[i] = s;
-        strlen += s.length;
+        if(((size_t)-1) - s.length < slen) {
+            error = 1;
+            break;
+        }
+        slen += s.length;
+    }
+    if(error) {
+        free(strings);
+        return str;
     }
     
     // create new string
-    str.ptr = (char*) almalloc(a, strlen + 1);
-    str.length = strlen;
+    str.ptr = (char*) almalloc(a, slen + 1);
+    str.length = slen;
     if(!str.ptr) {
         free(strings);
         str.length = 0;
@@ -102,7 +144,7 @@
     // concatenate strings
     size_t pos = 0;
     for (size_t i=0;i<count;i++) {
-        sstr_t s = strings[i];
+        scstr_t s = strings[i];
         memcpy(str.ptr + pos, s.ptr, s.length);
         pos += s.length;
     }
@@ -114,20 +156,42 @@
     return str;
 }
 
-sstr_t sstrcat(size_t count, sstr_t s1, sstr_t s2, ...) {
+sstr_t scstrcat(size_t count, scstr_t s1, ...) {
     va_list ap;
-    va_start(ap, s2);
-    sstr_t s = sstrvcat_a(ucx_default_allocator(), count, s1, s2, ap);
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(ucx_default_allocator(), count, s1, ap);
+    va_end(ap);
+    return s;
+}
+
+sstr_t scstrcat_a(UcxAllocator *a, size_t count, scstr_t s1, ...) {
+    va_list ap;
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(a, count, s1, ap);
     va_end(ap);
     return s;
 }
 
-sstr_t sstrcat_a(UcxAllocator *a, size_t count, sstr_t s1, sstr_t s2, ...) {
-    va_list ap;
-    va_start(ap, s2);
-    sstr_t s = sstrvcat_a(a, count, s1, s2, ap);
-    va_end(ap);
-    return s;
+static int ucx_substring(
+        size_t str_length,
+        size_t start,
+        size_t length,
+        size_t *newlen,
+        size_t *newpos)
+{
+    *newlen = 0;
+    *newpos = 0;
+    
+    if(start > str_length) {
+        return 0;
+    }
+    
+    if(length > str_length - start) {
+        length = str_length - start;
+    }
+    *newlen = length;
+    *newpos = start;
+    return 1;
 }
 
 sstr_t sstrsubs(sstr_t s, size_t start) {
@@ -135,132 +199,301 @@
 }
 
 sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) {
-    sstr_t new_sstr;
-    if (start >= s.length) {
-        new_sstr.ptr = NULL;
-        new_sstr.length = 0;
-    } else {
-        if (length > s.length-start) {
-            length = s.length-start;
+    size_t pos;
+    sstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+scstr_t scstrsubs(scstr_t string, size_t start) {
+    return scstrsubsl(string, start, string.length-start);
+}
+
+scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) {
+    size_t pos;
+    scstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+
+static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) {
+    for(size_t i=0;i<length;i++) {
+        if(str[i] == chr) {
+            *pos = i;
+            return 1;
         }
-        new_sstr.ptr = &s.ptr[start];
-        new_sstr.length = length;
     }
-    return new_sstr;
+    return 0;
+}
+
+static int ucx_strrchr(const char *str, size_t length, int chr, size_t *pos) {
+    if(length > 0) {
+        for(size_t i=length ; i>0 ; i--) {
+            if(str[i-1] == chr) {
+                *pos = i-1;
+                return 1;
+            }
+        }
+    }
+    return 0;
 }
 
 sstr_t sstrchr(sstr_t s, int c) {
-    for(size_t i=0;i<s.length;i++) {
-        if(s.ptr[i] == c) {
-            return sstrsubs(s, i);
-        }
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
     }
-    sstr_t n;
-    n.ptr = NULL;
-    n.length = 0;
-    return n;
+    return sstrn(NULL, 0);
 }
 
 sstr_t sstrrchr(sstr_t s, int c) {
-    if (s.length > 0) {
-        for(size_t i=s.length;i>0;i--) {
-            if(s.ptr[i-1] == c) {
-                return sstrsubs(s, i-1);
-            }
-        }
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
     }
-    sstr_t n;
-    n.ptr = NULL;
-    n.length = 0;
-    return n;
+    return sstrn(NULL, 0);
+}
+
+scstr_t scstrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
 }
 
-sstr_t sstrstr(sstr_t string, sstr_t match) {
-    if (match.length == 0) {
-        return string;
+scstr_t scstrrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
+}
+
+#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \
+    ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index])
+
+#define ptable_w(useheap, ptable, index, src) do {\
+    if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\
+    else ((size_t*)ptable)[index] = src;\
+    } while (0);
+
+
+static const char* ucx_strstr(
+        const char *str,
+        size_t length,
+        const char *match,
+        size_t matchlen,
+        size_t *newlen)
+{
+    *newlen = length;
+    if (matchlen == 0) {
+        return str;
     }
     
-    for (size_t i = 0 ; i < string.length ; i++) {
-        sstr_t substr = sstrsubs(string, i);
-        if (sstrprefix(substr, match)) {
-            return substr;
+    const char *result = NULL;
+    size_t resultlen = 0;
+    
+    /*
+     * IMPORTANT:
+     * our prefix table contains the prefix length PLUS ONE
+     * this is our decision, because we want to use the full range of size_t
+     * the original algorithm needs a (-1) at one single place
+     * and we want to avoid that
+     */
+    
+    /* static prefix table */
+    static uint8_t s_prefix_table[256];
+    
+    /* check pattern length and use appropriate prefix table */
+    /* if the pattern exceeds static prefix table, allocate on the heap */
+    register int useheap = matchlen > 255;
+    register void* ptable = useheap ?
+        calloc(matchlen+1, sizeof(size_t)): s_prefix_table;
+    
+    /* keep counter in registers */
+    register size_t i, j;
+    
+    /* fill prefix table */
+    i = 0; j = 0;
+    ptable_w(useheap, ptable, i, j);
+    while (i < matchlen) {
+        while (j >= 1 && match[j-1] != match[i]) {
+            ptable_r(j, useheap, ptable, j-1);
         }
+        i++; j++;
+        ptable_w(useheap, ptable, i, j);
+    }
+
+    /* search */
+    i = 0; j = 1;
+    while (i < length) {
+        while (j >= 1 && str[i] != match[j-1]) {
+            ptable_r(j, useheap, ptable, j-1);
+        }
+        i++; j++;
+        if (j-1 == matchlen) {
+            size_t start = i - matchlen;
+            result = str + start;
+            resultlen = length - start;
+            break;
+        }
+    }
+
+    /* if prefix table was allocated on the heap, free it */
+    if (ptable != s_prefix_table) {
+        free(ptable);
     }
     
-    sstr_t emptystr;
-    emptystr.length = 0;
-    emptystr.ptr = NULL;
-    return emptystr;
+    *newlen = resultlen;
+    return result;
+}
+
+sstr_t scstrsstr(sstr_t string, scstr_t match) {
+    sstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
 }
 
-sstr_t* sstrsplit(sstr_t s, sstr_t d, ssize_t *n) {
-    return sstrsplit_a(ucx_default_allocator(), s, d, n);
+scstr_t scstrscstr(scstr_t string, scstr_t match) {
+    scstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
 }
 
-sstr_t* sstrsplit_a(UcxAllocator *allocator, sstr_t s, sstr_t d, ssize_t *n) {
+#undef ptable_r
+#undef ptable_w
+
+sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) {
+    return scstrsplit_a(ucx_default_allocator(), s, d, n);
+}
+
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) {
     if (s.length == 0 || d.length == 0) {
         *n = -1;
         return NULL;
     }
-
-    sstr_t* result;
-    ssize_t nmax = *n;
-    *n = 1;
-
-    /* special case: exact match - no processing needed */
-    if (sstrcmp(s, d) == 0) {
-        *n = 0;
-        return NULL;
+    
+    /* special cases: delimiter is at least as large as the string */
+    if (d.length >= s.length) {
+        /* exact match */
+        if (sstrcmp(s, d) == 0) {
+            *n = 0;
+            return NULL;
+        } else /* no match possible */ {
+            *n = 1;
+            sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t));
+            if(result) {
+                *result = sstrdup_a(allocator, s);
+            } else {
+                *n = -2;
+            }
+            return result;
+        }
     }
-    sstr_t sv = sstrdup(s);
-    if (sv.length == 0) {
-        *n = -2;
-        return NULL;
-    }
-
-    for (size_t i = 0 ; i < s.length ; i++) {
-        sstr_t substr = sstrsubs(sv, i);
-        if (sstrprefix(substr, d)) {
-            (*n)++;
-            for (size_t j = 0 ; j < d.length ; j++) {
-                sv.ptr[i+j] = 0;
-            }
-            i += d.length - 1; // -1, because the loop will do a i++
-        }
-        if ((*n) == nmax) break;
-    }
-    result = (sstr_t*) almalloc(allocator, sizeof(sstr_t)*(*n));
+    
+    ssize_t nmax = *n;
+    size_t arrlen = 16;
+    sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t));
 
     if (result) {
-        char *pptr = sv.ptr;
-        for (ssize_t i = 0 ; i < *n ; i++) {
-            size_t l = strlen(pptr);
-            char* ptr = (char*) almalloc(allocator, l + 1);
-            if (ptr) {
-                memcpy(ptr, pptr, l);
-                ptr[l] = 0;
+        scstr_t curpos = s;
+        ssize_t j = 1;
+        while (1) {
+            scstr_t match;
+            /* optimize for one byte delimiters */
+            if (d.length == 1) {
+                match = curpos;
+                for (size_t i = 0 ; i < curpos.length ; i++) {
+                    if (curpos.ptr[i] == *(d.ptr)) {
+                        match.ptr = curpos.ptr + i;
+                        break;
+                    }
+                    match.length--;
+                }
+            } else {
+                match = scstrscstr(curpos, d);
+            }
+            if (match.length > 0) {
+                /* is this our last try? */
+                if (nmax == 0 || j < nmax) {
+                    /* copy the current string to the array */
+                    scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr);
+                    result[j-1] = sstrdup_a(allocator, item);
+                    size_t processed = item.length + d.length;
+                    curpos.ptr += processed;
+                    curpos.length -= processed;
 
-                result[i] = sstrn(ptr, l);
-                pptr += l + d.length;
+                    /* allocate memory for the next string */
+                    j++;
+                    if (j > arrlen) {
+                        arrlen *= 2;
+                        size_t reallocsz;
+                        sstr_t* reallocated = NULL;
+                        if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) {
+                            reallocated = (sstr_t*) alrealloc(
+                                    allocator, result, reallocsz);
+                        }
+                        if (reallocated) {
+                            result = reallocated;
+                        } else {
+                            for (ssize_t i = 0 ; i < j-1 ; i++) {
+                                alfree(allocator, result[i].ptr);
+                            }
+                            alfree(allocator, result);
+                            *n = -2;
+                            return NULL;
+                        }
+                    }
+                } else {
+                    /* nmax reached, copy the _full_ remaining string */
+                    result[j-1] = sstrdup_a(allocator, curpos);
+                    break;
+                }
             } else {
-                for (ssize_t j = i-1 ; j >= 0 ; j--) {
-                    alfree(allocator, result[j].ptr);
-                }
-                alfree(allocator, result);
-                *n = -2;
+                /* no more matches, copy last string */
+                result[j-1] = sstrdup_a(allocator, curpos);
                 break;
             }
         }
+        *n = j;
     } else {
         *n = -2;
     }
-    
-    free(sv.ptr);
 
     return result;
 }
 
-int sstrcmp(sstr_t s1, sstr_t s2) {
+int scstrcmp(scstr_t s1, scstr_t s2) {
     if (s1.length == s2.length) {
         return memcmp(s1.ptr, s2.ptr, s1.length);
     } else if (s1.length > s2.length) {
@@ -270,7 +503,7 @@
     }
 }
 
-int sstrcasecmp(sstr_t s1, sstr_t s2) {
+int scstrcasecmp(scstr_t s1, scstr_t s2) {
     if (s1.length == s2.length) {
 #ifdef _WIN32
         return _strnicmp(s1.ptr, s2.ptr, s1.length);
@@ -284,11 +517,11 @@
     }
 }
 
-sstr_t sstrdup(sstr_t s) {
+sstr_t scstrdup(scstr_t s) {
     return sstrdup_a(ucx_default_allocator(), s);
 }
 
-sstr_t sstrdup_a(UcxAllocator *allocator, sstr_t s) {
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) {
     sstr_t newstring;
     newstring.ptr = (char*)almalloc(allocator, s.length + 1);
     if (newstring.ptr) {
@@ -303,21 +536,38 @@
     return newstring;
 }
 
-sstr_t sstrtrim(sstr_t string) {
-    sstr_t newstr = string;
+
+static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) {
+    const char *newptr = s;
+    size_t length = len;
     
-    while (newstr.length > 0 && isspace(*newstr.ptr)) {
-        newstr.ptr++;
-        newstr.length--;
+    while(length > 0 && isspace(*newptr)) {
+        newptr++;
+        length--;
     }
-    while (newstr.length > 0 && isspace(newstr.ptr[newstr.length-1])) {
-        newstr.length--;
+    while(length > 0 && isspace(newptr[length-1])) {
+        length--;
     }
     
+    *newlen = length;
+    return newptr - s;
+}
+
+sstr_t sstrtrim(sstr_t string) {
+    sstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
     return newstr;
 }
 
-int sstrprefix(sstr_t string, sstr_t prefix) {
+scstr_t scstrtrim(scstr_t string) {
+    scstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
+    return newstr;
+}
+
+int scstrprefix(scstr_t string, scstr_t prefix) {
     if (string.length == 0) {
         return prefix.length == 0;
     }
@@ -332,7 +582,7 @@
     }
 }
 
-int sstrsuffix(sstr_t string, sstr_t suffix) {
+int scstrsuffix(scstr_t string, scstr_t suffix) {
     if (string.length == 0) {
         return suffix.length == 0;
     }
@@ -348,7 +598,39 @@
     }
 }
 
-sstr_t sstrlower(sstr_t string) {
+int scstrcaseprefix(scstr_t string, scstr_t prefix) {
+    if (string.length == 0) {
+        return prefix.length == 0;
+    }
+    if (prefix.length == 0) {
+        return 1;
+    }
+    
+    if (prefix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubsl(string, 0, prefix.length);
+        return scstrcasecmp(subs, prefix) == 0;
+    }
+}
+
+int scstrcasesuffix(scstr_t string, scstr_t suffix) {
+    if (string.length == 0) {
+        return suffix.length == 0;
+    }
+    if (suffix.length == 0) {
+        return 1;
+    }
+    
+    if (suffix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubs(string, string.length-suffix.length);
+        return scstrcasecmp(subs, suffix) == 0;
+    }
+}
+
+sstr_t scstrlower(scstr_t string) {
     sstr_t ret = sstrdup(string);
     for (size_t i = 0; i < ret.length ; i++) {
         ret.ptr[i] = tolower(ret.ptr[i]);
@@ -356,7 +638,7 @@
     return ret;
 }
 
-sstr_t sstrlower_a(UcxAllocator *allocator, sstr_t string) {
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) {
     sstr_t ret = sstrdup_a(allocator, string);
     for (size_t i = 0; i < ret.length ; i++) {
         ret.ptr[i] = tolower(ret.ptr[i]);
@@ -364,7 +646,7 @@
     return ret;
 }
 
-sstr_t sstrupper(sstr_t string) {
+sstr_t scstrupper(scstr_t string) {
     sstr_t ret = sstrdup(string);
     for (size_t i = 0; i < ret.length ; i++) {
         ret.ptr[i] = toupper(ret.ptr[i]);
@@ -372,10 +654,154 @@
     return ret;
 }
 
-sstr_t sstrupper_a(UcxAllocator *allocator, sstr_t string) {
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) {
     sstr_t ret = sstrdup_a(allocator, string);
     for (size_t i = 0; i < ret.length ; i++) {
         ret.ptr[i] = toupper(ret.ptr[i]);
     }
     return ret;
 }
+
+#define REPLACE_INDEX_BUFFER_MAX 100
+
+struct scstrreplace_ibuf {
+    size_t* buf;
+    unsigned int len; /* small indices */
+    struct scstrreplace_ibuf* next;
+};
+
+static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) {
+    while (buf) {
+        struct scstrreplace_ibuf *next = buf->next;
+        free(buf->buf);
+        free(buf);
+        buf = next;
+    }
+}
+
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+                     scstr_t pattern, scstr_t replacement, size_t replmax) {
+
+    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+        return sstrdup(str);
+
+    /* Compute expected buffer length */
+    size_t ibufmax = str.length / pattern.length;
+    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+    if (ibuflen > REPLACE_INDEX_BUFFER_MAX) {
+        ibuflen = REPLACE_INDEX_BUFFER_MAX;
+    }
+
+    /* Allocate first index buffer */
+    struct scstrreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf));
+    if (!firstbuf) return sstrn(NULL, 0);
+    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+    if (!firstbuf->buf) {
+        free(firstbuf);
+        return sstrn(NULL, 0);
+    }
+
+    /* Search occurrences */
+    scstr_t searchstr = str;
+    size_t found = 0;
+    do {
+        scstr_t match = scstrscstr(searchstr, pattern);
+        if (match.length > 0) {
+            /* Allocate next buffer in chain, if required */
+            if (curbuf->len == ibuflen) {
+                struct scstrreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct scstrreplace_ibuf));
+                if (!nextbuf) {
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(NULL, 0);
+                }
+                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+                if (!nextbuf->buf) {
+                    free(nextbuf);
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(NULL, 0);
+                }
+                curbuf->next = nextbuf;
+                curbuf = nextbuf;
+            }
+
+            /* Record match index */
+            found++;
+            size_t idx = match.ptr - str.ptr;
+            curbuf->buf[curbuf->len++] = idx;
+            searchstr.ptr = match.ptr + pattern.length;
+            searchstr.length = str.length - idx - pattern.length;
+        } else {
+            break;
+        }
+    } while (searchstr.length > 0 && found < replmax);
+
+    /* Allocate result string */
+    sstr_t result;
+    {
+        ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
+        size_t rcount = 0;
+        curbuf = firstbuf;
+        do {
+            rcount += curbuf->len;
+            curbuf = curbuf->next;
+        } while (curbuf);
+        result.length = str.length + rcount * adjlen;
+        result.ptr = almalloc(allocator, result.length);
+        if (!result.ptr) {
+            scstrrepl_free_ibuf(firstbuf);
+            return sstrn(NULL, 0);
+        }
+    }
+
+    /* Build result string */
+    curbuf = firstbuf;
+    size_t srcidx = 0;
+    char* destptr = result.ptr;
+    do {
+        for (size_t i = 0; i < curbuf->len; i++) {
+            /* Copy source part up to next match*/
+            size_t idx = curbuf->buf[i];
+            size_t srclen = idx - srcidx;
+            if (srclen > 0) {
+                memcpy(destptr, str.ptr+srcidx, srclen);
+                destptr += srclen;
+                srcidx += srclen;
+            }
+
+            /* Copy the replacement and skip the source pattern */
+            srcidx += pattern.length;
+            memcpy(destptr, replacement.ptr, replacement.length);
+            destptr += replacement.length;
+        }
+        curbuf = curbuf->next;
+    } while (curbuf);
+    memcpy(destptr, str.ptr+srcidx, str.length-srcidx);
+
+    /* Free index buffer */
+    scstrrepl_free_ibuf(firstbuf);
+
+    return result;
+}
+
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t replacement, size_t replmax) {
+    return scstrreplacen_a(ucx_default_allocator(),
+            str, pattern, replacement, replmax);
+}
+
+
+// type adjustment functions
+scstr_t ucx_sc2sc(scstr_t str) {
+    return str;
+}
+scstr_t ucx_ss2sc(sstr_t str) {
+    scstr_t cs;
+    cs.ptr = str.ptr;
+    cs.length = str.length;
+    return cs;
+}
+scstr_t ucx_ss2c_s(scstr_t c) {
+    return c;
+}
--- a/src/ucx/string.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,457 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * Bounded string implementation.
- * 
- * The UCX strings (<code>sstr_t</code>) provide an alternative to C strings.
- * The main difference to C strings is, that <code>sstr_t</code> does <b>not
- * need to be <code>NULL</code>-terminated</b>. Instead the length is stored
- * within the structure.
- * 
- * When using <code>sstr_t</code>, developers must be full aware of what type
- * of string (<code>NULL</code>-terminated) or not) they are using, when 
- * accessing the <code>char* ptr</code> directly.
- * 
- * The UCX string module provides some common string functions, known from
- * standard libc, working with <code>sstr_t</code>.
- * 
- * @file   string.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_STRING_H
-#define	UCX_STRING_H
-
-#include "ucx.h"
-#include "allocator.h"
-#include <stddef.h>
-
-/** Shortcut for a <code>sstr_t struct</code> literal. */
-#define ST(s) { (char*)s, sizeof(s)-1 }
-
-/** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
-#define S(s) sstrn((char*)s, sizeof(s)-1)
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * The UCX string structure.
- */
-typedef struct {
-   /** A reference to the string (<b>not necessarily  <code>NULL</code>
-    * -terminated</b>) */
-    char   *ptr;
-    /** The length of the string */
-    size_t length;
-} sstr_t;
-
-/**
- * Creates a new sstr_t based on a C string.
- * 
- * The length is implicitly inferred by using a call to <code>strlen()</code>.
- *
- * <b>Note:</b> the sstr_t will hold a <i>reference</i> to the C string. If you
- * do want a copy, use sstrdup() on the return value of this function.
- * 
- * @param cstring the C string to wrap
- * @return a new sstr_t containing the C string
- * 
- * @see sstrn()
- */
-sstr_t sstr(char *cstring);
-
-/**
- * Creates a new sstr_t of the specified length based on a C string.
- *
- * <b>Note:</b> the sstr_t will hold a <i>reference</i> to the C string. If you
- * do want a copy, use sstrdup() on the return value of this function.
- * 
- * @param cstring  the C string to wrap
- * @param length   the length of the string
- * @return a new sstr_t containing the C string
- * 
- * @see sstr()
- * @see S()
- */
-sstr_t sstrn(char *cstring, size_t length);
-
-
-/**
- * Returns the cumulated length of all specified strings.
- *
- * At least one string must be specified.
- * 
- * <b>Attention:</b> if the count argument does not match the count of the
- * specified strings, the behavior is undefined.
- *
- * @param count    the total number of specified strings (so at least 1)
- * @param string   the first string
- * @param ...      all other strings
- * @return the cumulated length of all strings
- */
-size_t sstrnlen(size_t count, sstr_t string, ...);
-
-/**
- * Concatenates two or more strings.
- * 
- * The resulting string will be allocated by standard <code>malloc()</code>. 
- * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- *
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param s2      second string
- * @param ...     all remaining strings
- * @return the concatenated string
- */
-sstr_t sstrcat(size_t count, sstr_t s1, sstr_t s2, ...);
-
-/**
- * Concatenates two or more strings using a UcxAllocator.
- * 
- * See sstrcat() for details.
- *
- * @param a       the allocator to use
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param s2      second string
- * @param ...     all remaining strings
- * @return the concatenated string
- */
-sstr_t sstrcat_a(UcxAllocator *a, size_t count, sstr_t s1, sstr_t s2, ...);
-
-
-/**
- * Returns a substring starting at the specified location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
- * input string and will <b>NOT</b> be <code>NULL</code>-terminated.
- * Use sstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * 
- * @see sstrsubsl()
- * @see sstrchr()
- */
-sstr_t sstrsubs(sstr_t string, size_t start);
-
-/**
- * Returns a substring with a maximum length starting at the specified location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
- * input string and will <b>NOT</b> be <code>NULL</code>-terminated.
- * Use sstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @param length the maximum length of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * with a maximum length of <code>length</code>
- * 
- * @see sstrsubs()
- * @see sstrchr()
- */
-sstr_t sstrsubsl(sstr_t string, size_t start, size_t length);
-
-/**
- * Returns a substring starting at the location of the first occurrence of the
- * specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the first location of <code>chr</code>
- * 
- * @see sstrsubs()
- */
-sstr_t sstrchr(sstr_t string, int chr);
-
-/**
- * Returns a substring starting at the location of the last occurrence of the
- * specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the last location of <code>chr</code>
- * 
- * @see sstrsubs()
- */
-sstr_t sstrrchr(sstr_t string, int chr);
-
-/**
- * Returns a substring starting at the location of the first occurrence of the
- * specified string.
- * 
- * If the string does not contain the other string, an empty string is returned.
- * 
- * If <code>match</code> is an empty string, the complete <code>string</code> is
- * returned.
- * 
- * @param string the string to be scanned
- * @param match  string containing the sequence of characters to match
- * @return       a substring starting at the first occurrence of
- *               <code>match</code>, or an empty string, if the sequence is not
- *               present in <code>string</code>
- */
-sstr_t sstrstr(sstr_t string, sstr_t match);
-
-/**
- * Splits a string into parts by using a delimiter string.
- * 
- * This function will return <code>NULL</code>, if one of the following happens:
- * <ul>
- *   <li>the string length is zero</li>
- *   <li>the delimeter length is zero</li>
- *   <li>the string equals the delimeter</li>
- *   <li>memory allocation fails</li>
- * </ul>
- * 
- * The integer referenced by <code>count</code> is used as input and determines
- * the maximum size of the resulting array, i.e. the maximum count of splits to
- * perform + 1.
- * 
- * The integer referenced by <code>count</code> is also used as output and is
- * set to
- * <ul>
- *   <li>-2, on memory allocation errors</li>
- *   <li>-1, if either the string or the delimiter is an empty string</li>
- *   <li>0, if the string equals the delimiter</li>
- *   <li>1, if the string does not contain the delimiter</li>
- *   <li>the count of array items, otherwise</li>
- * </ul>
- * 
- * If the string starts with the delimiter, the first item of the resulting
- * array will be an empty string.
- * 
- * If the string ends with the delimiter and the maximum list size is not
- * exceeded, the last array item will be an empty string.
- * 
- * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
- * items must be manually passed to <code>free()</code>. Use sstrsplit_a() with
- * an allocator to managed memory, to avoid this.
- *
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- *         <code>NULL</code> on error
- * 
- * @see sstrsplit_a()
- */
-sstr_t* sstrsplit(sstr_t string, sstr_t delim, ssize_t *count);
-
-/**
- * Performing sstrsplit() using a UcxAllocator.
- * 
- * <i>Read the description of sstrsplit() for details.</i>
- * 
- * The memory for the sstr_t.ptr pointers of the array items and the memory for
- * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
- * function.
- * 
- * <b>Note:</b> the allocator is not used for memory that is freed within the
- * same call of this function (locally scoped variables).
- * 
- * @param allocator the UcxAllocator used for allocating memory
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- *         <code>NULL</code> on error
- * 
- * @see sstrsplit()
- */
-sstr_t* sstrsplit_a(UcxAllocator *allocator, sstr_t string, sstr_t delim,
-        ssize_t *count);
-
-/**
- * Compares two UCX strings with standard <code>memcmp()</code>.
- * 
- * At first it compares the sstr_t.length attribute of the two strings. The
- * <code>memcmp()</code> function is called, if and only if the lengths match.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the result of
- * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
- */
-int sstrcmp(sstr_t s1, sstr_t s2);
-
-/**
- * Compares two UCX strings ignoring the case.
- * 
- * At first it compares the sstr_t.length attribute of the two strings. If and
- * only if the lengths match, both strings are compared char by char ignoring
- * the case.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the difference between the
- * first two differing characters otherwise (i.e. 0 if the strings match and
- * no characters differ)
- */
-int sstrcasecmp(sstr_t s1, sstr_t s2);
-
-/**
- * Creates a duplicate of the specified string.
- * 
- * The new sstr_t will contain a copy allocated by standard
- * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
- * <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- * 
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see sstrdup_a()
- */
-sstr_t sstrdup(sstr_t string);
-
-/**
- * Creates a duplicate of the specified string using a UcxAllocator.
- * 
- * The new sstr_t will contain a copy allocated by the allocators
- * ucx_allocator_malloc function. So it is implementation depended, whether the
- * returned sstr_t.ptr pointer must be passed to the allocators
- * ucx_allocator_free function manually.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- * 
- * @param allocator a valid instance of a UcxAllocator
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see sstrdup()
- */
-sstr_t sstrdup_a(UcxAllocator *allocator, sstr_t string);
-
-/**
- * Omits leading and trailing spaces.
- * 
- * This function returns a new sstr_t containing a trimmed version of the
- * specified string.
- * 
- * <b>Note:</b> the new sstr_t references the same memory, thus you
- * <b>MUST NOT</b> pass the sstr_t.ptr of the return value to
- * <code>free()</code>. It is also highly recommended to avoid assignments like
- * <code>mystr = sstrtrim(mystr);</code> as you lose the reference to the
- * source string. Assignments of this type are only permitted, if the
- * sstr_t.ptr of the source string does not need to be freed or if another
- * reference to the source string exists.
- * 
- * @param string the string that shall be trimmed
- * @return a new sstr_t containing the trimmed string
- */
-sstr_t sstrtrim(sstr_t string);
-
-/**
- * Checks, if a string has a specific prefix.
- * @param string the string to check
- * @param prefix the prefix the string should have
- * @return 1, if and only if the string has the specified prefix, 0 otherwise
- */
-int sstrprefix(sstr_t string, sstr_t prefix);
-
-/**
- * Checks, if a string has a specific suffix.
- * @param string the string to check
- * @param suffix the suffix the string should have
- * @return 1, if and only if the string has the specified suffix, 0 otherwise
- */
-int sstrsuffix(sstr_t string, sstr_t suffix);
-
-/**
- * Returns a lower case version of a string.
- * 
- * This function creates a duplicate of the input string, first. See the
- * documentation of sstrdup() for the implications.
- * 
- * @param string the input string
- * @return the resulting lower case string
- * @see sstrdup()
- */
-sstr_t sstrlower(sstr_t string);
-
-/**
- * Returns a lower case version of a string.
- * 
- * This function creates a duplicate of the input string, first. See the
- * documentation of sstrdup_a() for the implications.
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting lower case string
- * @see sstrdup_a()
- */
-sstr_t sstrlower_a(UcxAllocator *allocator, sstr_t string);
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first. See the
- * documentation of sstrdup() for the implications.
- * 
- * @param string the input string
- * @return the resulting upper case string
- * @see sstrdup()
- */
-sstr_t sstrupper(sstr_t string);
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first. See the
- * documentation of sstrdup_a() for the implications.
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting upper case string
- * @see sstrdup_a()
- */
-sstr_t sstrupper_a(UcxAllocator *allocator, sstr_t string);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_STRING_H */
--- a/src/ucx/test.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/test.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,7 +26,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "test.h"
+#include "ucx/test.h"
 
 UcxTestSuite* ucx_test_suite_new() {
     UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite));
@@ -86,6 +86,6 @@
         elem->test(suite, output);
     }
     fwrite("\nAll test completed.\n", 1, 21, output);
-    fprintf(output, "  Total:   %d\n  Success: %d\n  Failure: %d\n",
+    fprintf(output, "  Total:   %u\n  Success: %u\n  Failure: %u\n",
             suite->success+suite->failure, suite->success, suite->failure);
 }
--- a/src/ucx/test.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
- 
-/**
- * @file: test.h
- * 
- * UCX Test Framework.
- * 
- * Usage of this test framework:
- *
- * **** IN HEADER FILE: ****
- *
- * <pre>
- * UCX_TEST(function_name)
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) // optional
- * </pre>
- *
- * **** IN SOURCE FILE: ****
- * <pre>
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
- *   // tests with UCX_TEST_ASSERT()
- * }
- * 
- * UCX_TEST(function_name) {
- *   // memory allocation and other stuff here
- *   #UCX_TEST_BEGIN
- *   // tests with UCX_TEST_ASSERT() and/or
- *   // calls with UCX_TEST_CALL_SUBROUTINE() here
- *   #UCX_TEST_END
- *   // cleanup of memory here
- * }
- * </pre>
- *
- * <b>Note:</b> if a test fails, a longjump is performed
- * back to the #UCX_TEST_BEGIN macro!
- * 
- * <b>Attention:</b> Do not call own functions within a test, that use
- * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE().
- * 
- *
- * @author Mike Becker
- * @author Olaf Wintermann
- *
- */
-
-#ifndef UCX_TEST_H
-#define	UCX_TEST_H
-
-#include "ucx.h"
-#include <stdio.h>
-#include <string.h>
-#include <setjmp.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-#ifndef __FUNCTION__
-
-/**
- * Alias for the <code>__func__</code> preprocessor macro.
- * Some compilers use <code>__func__</code> and others use __FUNCTION__.
- * We use __FUNCTION__ so we define it for those compilers which use
- * <code>__func__</code>.
- */
-#define __FUNCTION__ __func__
-#endif
-
-/** Type for the UcxTestSuite. */
-typedef struct UcxTestSuite UcxTestSuite;
-
-/** Pointer to a test function. */
-typedef void(*UcxTest)(UcxTestSuite*,FILE*);
-
-/** Type for the internal list of test cases. */
-typedef struct UcxTestList UcxTestList;
-
-/** Structure for the internal list of test cases. */
-struct UcxTestList {
-    
-    /** Test case. */
-    UcxTest test;
-    
-    /** Pointer to the next list element. */
-    UcxTestList *next;
-};
-
-/**
- * A test suite containing multiple test cases.
- */
-struct UcxTestSuite {
-    
-    /** The number of successful tests after the suite has been run. */
-    unsigned int success;
-    
-    /** The number of failed tests after the suite has been run. */
-    unsigned int failure;
-    
-    /**
-     * Internal list of test cases.
-     * Use ucx_test_register() to add tests to this list.
-     */
-    UcxTestList *tests;
-};
-
-/**
- * Creates a new test suite.
- * @return a new test suite
- */
-UcxTestSuite* ucx_test_suite_new();
-
-/**
- * Destroys a test suite.
- * @param suite the test suite to destroy
- */
-void ucx_test_suite_free(UcxTestSuite* suite);
-
-/**
- * Registers a test function with the specified test suite.
- * 
- * @param suite the suite, the test function shall be added to
- * @param test the test function to register
- * @return <code>EXIT_SUCCESS</code> on success or
- * <code>EXIT_FAILURE</code> on failure
- */
-int ucx_test_register(UcxTestSuite* suite, UcxTest test);
-
-/**
- * Runs a test suite and writes the test log to the specified stream.
- * @param suite the test suite to run
- * @param outstream the stream the log shall be written to
- */
-void ucx_test_run(UcxTestSuite* suite, FILE* outstream);
-
-/**
- * Macro for a #UcxTest function header.
- * 
- * Use this macro to declare and/or define a #UcxTest function.
- * 
- * @param name the name of the test function
- */
-#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_)
-
-/**
- * Marks the begin of a test.
- * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b>
- * #UCX_TEST_BEGIN.
- * 
- * @see #UCX_TEST_END
- */
-#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\
-        fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
-        fwrite("... ", 1, 4, _output_);\
-        jmp_buf _env_; \
-        if (!setjmp(_env_)) {
-
-/**
- * Checks a test assertion.
- * If the assertion is correct, the test carries on. If the assertion is not
- * correct, the specified message (terminated by a dot and a line break) is
- * written to the test suites output stream.
- * @param condition the condition to check
- * @param message the message that shall be printed out on failure
- */
-#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \
-        fwrite(message".\n", 1, 2+strlen(message), _output_); \
-        _suite_->failure++; \
-        longjmp(_env_, 1);\
-    }
-
-/**
- * Macro for a test subroutine function header.
- * 
- * Use this to declare and/or define a subroutine that can be called by using
- * UCX_TEST_CALL_SUBROUTINE().
- * 
- * @param name the name of the subroutine
- * @param ... the parameter list
- * 
- * @see UCX_TEST_CALL_SUBROUTINE()
- */
-#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\
-        FILE *_output_, jmp_buf _env_, __VA_ARGS__)
-
-/**
- * Macro for calling a test subroutine.
- * 
- * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this
- * macro.
- * 
- * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN-
- * #UCX_TEST_END-block.
- * 
- * @param name the name of the subroutine
- * @param ... the argument list
- * 
- * @see UCX_TEST_SUBROUTINE()
- */
-#define UCX_TEST_CALL_SUBROUTINE(name,...) \
-        name(_suite_,_output_,_env_,__VA_ARGS__);
-
-/**
- * Marks the end of a test.
- * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b>
- * #UCX_TEST_END.
- * 
- * @see #UCX_TEST_BEGIN
- */
-#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;}
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_TEST_H */
-
--- a/src/ucx/ucx.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/ucx.c	Sat Sep 24 16:26:10 2022 +0200
@@ -2,14 +2,23 @@
  * @mainpage UAP Common Extensions
  * Library with common and useful functions, macros and data structures.
  * <p>
- * Latest available source:<br/>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">
+ * https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ * 
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">
+ * https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
  * <a href="https://develop.uap-core.de/hg/ucx">
  * https://develop.uap-core.de/hg/ucx</a>
  * </p>
  * 
  * <h2>LICENCE</h2>
  * 
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -34,4 +43,20 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx.h"
+#include "ucx/ucx.h"
+
+int ucx_szmul_impl(size_t a, size_t b, size_t *result) {
+    if(a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    size_t r = a * b;
+    if(r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
+
--- a/src/ucx/ucx.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-/**
- * Main UCX Header providing most common definitions.
- * 
- * @file   ucx.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_H
-#define	UCX_H
-
-/** Major UCX version as integer constant. */
-#define UCX_VERSION_MAJOR   0
-
-/** Minor UCX version as integer constant. */
-#define UCX_VERSION_MINOR   10
-
-#include <stdlib.h>
-
-#ifdef _WIN32
-#if !(defined __ssize_t_defined || defined _SSIZE_T_)
-#include <BaseTsd.h>
-typedef SSIZE_T ssize_t;
-#define __ssize_t_defined
-#define _SSIZE_T_
-#endif /* __ssize_t_defined and _SSIZE_T */
-#else /* !_WIN32 */
-#include <sys/types.h>
-#endif /* _WIN32 */
-
-#ifdef	__cplusplus
-#ifndef _Bool
-#define _Bool bool
-#define restrict
-#endif
-/** Use C naming even when compiling with C++.  */
-#define UCX_EXTERN extern "C"
-extern "C" {
-#else
-/** Pointless in C. */
-#define UCX_EXTERN
-#endif
-    
-
-/**
- * A function pointer to a destructor function.
- * @see ucx_mempool_setdestr()
- * @see ucx_mempool_regdestr()
- */
-typedef void(*ucx_destructor)(void*);
-
-/**
- * Function pointer to a compare function.
- * 
- * The compare function shall take three arguments: the two values that shall be
- * compared and optional additional data.
- * The function shall then return -1 if the first argument is less than the
- * second argument, 1 if the first argument is greater than the second argument
- * and 0 if both arguments are equal. If the third argument is
- * <code>NULL</code>, it shall be ignored.
- */
-typedef int(*cmp_func)(void*,void*,void*);
-
-/**
- * Function pointer to a copy function.
- * 
- * The copy function shall create a copy of the first argument and may use
- * additional data provided by the second argument. If the second argument is
- * <code>NULL</code>, it shall be ignored.
-
- * <b>Attention:</b> if pointers returned by functions of this type may be
- * passed to <code>free()</code> depends on the implementation of the
- * respective <code>copy_func</code>.
- */
-typedef void*(*copy_func)(void*,void*);
-
-/**
- * Function pointer to a write function.
- * 
- * The signature of the write function shall be compatible to the signature
- * of standard <code>fwrite</code>, though it may use arbitrary data types for
- * source and destination.
- * 
- * The arguments shall contain (in ascending order): a pointer to the source,
- * the length of one element, the element count and a pointer to the
- * destination.
- */
-typedef size_t(*write_func)(const void*, size_t, size_t, void*);
-
-/**
- * Function pointer to a read function.
- * 
- * The signature of the read function shall be compatible to the signature
- * of standard <code>fread</code>, though it may use arbitrary data types for
- * source and destination.
- * 
- * The arguments shall contain (in ascending order): a pointer to the
- * destination, the length of one element, the element count and a pointer to
- * the source.
- */
-typedef size_t(*read_func)(void*, size_t, size_t, void*);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_H */
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/allocator.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,206 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * Allocator for custom memory management.
+ * 
+ * A UCX allocator consists of a pointer to the memory area / pool and four
+ * function pointers to memory management functions operating on this memory
+ * area / pool. These functions shall behave equivalent to the standard libc
+ * functions <code>malloc(), calloc(), realloc()</code> and <code>free()</code>.
+ * 
+ * The signature of the memory management functions is based on the signature
+ * of the respective libc function but each of them takes the pointer to the
+ * memory area / pool as first argument.
+ * 
+ * As the pointer to the memory area / pool can be arbitrarily chosen, any data
+ * can be provided to the memory management functions. A UcxMempool is just
+ * one example.
+ * 
+ * @see mempool.h
+ * @see UcxMap
+ * 
+ * @file   allocator.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define	UCX_ALLOCATOR_H
+
+#include "ucx.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * A function pointer to the allocators <code>malloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_malloc)(void *pool, size_t n);
+
+/**
+ * A function pointer to the allocators <code>calloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size);
+
+/**
+ * A function pointer to the allocators <code>realloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n);
+
+/**
+ * A function pointer to the allocators <code>free()</code> function.
+ * @see UcxAllocator
+ */
+typedef void(*ucx_allocator_free)(void *pool, void *data);
+
+/**
+ * UCX allocator data structure containing memory management functions.
+ */
+typedef struct {
+    /** Pointer to an area of memory or a complex memory pool.
+     * This pointer will be passed to any memory management function as first
+     * argument.
+     */
+    void *pool;
+    /**
+     * The <code>malloc()</code> function for this allocator.
+     */
+    ucx_allocator_malloc  malloc;
+    /**
+     * The <code>calloc()</code> function for this allocator.
+     */
+    ucx_allocator_calloc  calloc;
+    /**
+     * The <code>realloc()</code> function for this allocator.
+     */
+    ucx_allocator_realloc realloc;
+    /**
+     * The <code>free()</code> function for this allocator.
+     */
+    ucx_allocator_free    free;
+} UcxAllocator;
+
+/**
+ * Returns a pointer to the default allocator.
+ * 
+ * The default allocator contains wrappers to the standard libc memory
+ * management functions. Use this function to get a pointer to a globally
+ * available allocator. You may also define an own UcxAllocator by assigning
+ * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable
+ * to any function that takes a UcxAllocator as argument. Note that using
+ * this function is the recommended way of passing a default allocator, thus
+ * it never runs out of scope.
+ * 
+ * @return a pointer to the default allocator
+ * 
+ * @see UCX_ALLOCATOR_DEFAULT
+ */
+UcxAllocator *ucx_default_allocator();
+
+/**
+ * A wrapper for the standard libc <code>malloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>malloc()</code>
+ * @return return value of <code>malloc()</code>
+ */
+void *ucx_default_malloc(void *ignore, size_t n);
+/**
+ * A wrapper for the standard libc <code>calloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>calloc()</code>
+ * @param size  argument passed to <code>calloc()</code>
+ * @return return value of <code>calloc()</code>
+ */
+void *ucx_default_calloc(void *ignore, size_t n, size_t size);
+/**
+ * A wrapper for the standard libc <code>realloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argumend passed to <code>realloc()</code>
+ * @param n argument passed to <code>realloc()</code>
+ * @return return value of <code>realloc()</code>
+ */
+void *ucx_default_realloc(void *ignore, void *data, size_t n);
+/**
+ * A wrapper for the standard libc <code>free()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argument passed to <code>free()</code>
+ */
+void ucx_default_free(void *ignore, void *data);
+
+/**
+ * Shorthand for calling an allocators malloc function.
+ * @param allocator the allocator to use
+ * @param n size of space to allocate
+ * @return a pointer to the allocated memory area
+ */
+#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n))
+
+/**
+ * Shorthand for calling an allocators calloc function.
+ * @param allocator the allocator to use
+ * @param n the count of elements the space should be allocated for
+ * @param size the size of each element
+ * @return a pointer to the allocated memory area
+ */
+#define alcalloc(allocator, n, size) \
+        ((allocator)->calloc((allocator)->pool, n, size))
+
+/**
+ * Shorthand for calling an allocators realloc function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be reallocated
+ * @param n the new size of the allocated memory area
+ * @return a pointer to the reallocated memory area
+ */
+#define alrealloc(allocator, ptr, n) \
+        ((allocator)->realloc((allocator)->pool, ptr, n))
+
+/**
+ * Shorthand for calling an allocators free function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be freed
+ */
+#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr))
+
+/**
+ * Convenient macro for a default allocator <code>struct</code> definition.
+ */
+#define UCX_ALLOCATOR_DEFAULT {NULL, \
+        ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \
+        ucx_default_free }
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_ALLOCATOR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/array.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,460 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Mike Becker, 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.
+ */
+/**
+ * Dynamically allocated array implementation.
+ * 
+ * @file   array.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ARRAY_H
+#define	UCX_ARRAY_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX array type.
+ */
+typedef struct {
+    /**
+     * The current capacity of the array.
+     */
+    size_t capacity;
+    /**
+     * The actual number of elements in the array.
+     */
+    size_t size;
+    /**
+     * The size of an individual element in bytes.
+     */
+    size_t elemsize;
+    /**
+     * A pointer to the data.
+     */
+    void* data;
+    /**
+     * The allocator used for the data.
+     */
+    UcxAllocator* allocator;
+} UcxArray;
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_set(array, capacity, elmsize, idx, data) \
+    ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \
+                         elmsize, idx, data)
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t idx, void* data);
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_setptr(array, capacity, idx, ptr) \
+    ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \
+                            capacity, idx, ptr)
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t idx, void* ptr);
+
+
+/**
+ * Creates a new UCX array with the given capacity and element size.
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @return a pointer to a new UCX array structure
+ */
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize);
+
+/**
+ * Creates a new UCX array using the specified allocator.
+ * 
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ * @return a pointer to new UCX array structure
+ */
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Initializes a UCX array structure with the given capacity and element size.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ */
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize);
+
+/**
+ * Initializes a UCX array structure using the specified allocator.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ */
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Creates an shallow copy of an array.
+ * 
+ * This function clones the specified array by using memcpy().
+ * If the destination capacity is insufficient, an automatic reallocation is
+ * attempted.
+ * 
+ * Note: if the destination array is uninitialized, the behavior is undefined.
+ * 
+ * @param dest the array to copy to
+ * @param src the array to copy from
+ * @return zero on success, non-zero on reallocation failure.
+ */
+int ucx_array_clone(UcxArray* dest, UcxArray const* src);
+
+
+/**
+ * Compares two UCX arrays element-wise by using a compare function.
+ *
+ * Elements of the two specified arrays are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * This function always returns zero, if the element sizes of the arrays do
+ * not match and performs no comparisons in this case.
+ * 
+ * @param array1 the first array
+ * @param array2 the second array
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two arrays equal element-wise, 0 otherwise
+ */
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the array.
+ * 
+ * The data is freed and both capacity and count are reset to zero.
+ * If the array structure itself has been dynamically allocated, it has to be
+ * freed separately.
+ * 
+ * @param array the array to destroy
+ */
+void ucx_array_destroy(UcxArray *array);
+
+/**
+ * Destroys and frees the array.
+ * 
+ * @param array the array to free
+ */
+void ucx_array_free(UcxArray *array);
+
+/**
+ * Inserts elements at the end of the array.
+ * 
+ * This is an O(1) operation.
+ * The array will automatically grow, if the capacity is exceeded.
+ * If a pointer to data is provided, the data is copied into the array with
+ * memcpy(). Otherwise the new elements are completely zeroed.
+ * 
+ * @param array a pointer the array where to append the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are appended)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_set_from()
+ * @see ucx_array_append()
+ */
+int ucx_array_append_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Inserts elements at the beginning of the array.
+ * 
+ * This is an expensive operation, because the contents must be moved.
+ * If there is no particular reason to prepend data, you should use
+ * ucx_array_append_from() instead.
+ * 
+ * @param array a pointer the array where to prepend the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are inserted)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set_from()
+ * @see ucx_array_prepend()
+ */
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Sets elements starting at the specified index.
+ * 
+ * If the any index is out of bounds, the array automatically grows.
+ * The pointer to the data may be NULL, in which case the elements are zeroed. 
+ * 
+ * @param array a pointer the array where to set the data
+ * @param index the index of the element to set
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, the memory in the array is zeroed)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set()
+ */
+int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count);
+
+/**
+ * Concatenates two arrays.
+ * 
+ * The contents of the second array are appended to the first array in one
+ * single operation. The second array is otherwise left untouched.
+ * 
+ * The first array may grow automatically. If this fails, both arrays remain
+ * unmodified.
+ * 
+ * @param array1 first array
+ * @param array2 second array
+ * @return zero on success, non-zero if reallocation was necessary but failed 
+ * or the element size does not match
+ */
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2);
+
+/**
+ * Returns a pointer to the array element at the specified index.
+ * 
+ * @param array the array to retrieve the element from
+ * @param index index of the element to return
+ * @return a pointer to the element at the specified index or <code>NULL</code>,
+ * if the index is greater than the array size
+ */
+void *ucx_array_at(UcxArray const* array, size_t index);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, memcmp() is used.
+ * 
+ * If the array contains the data more than once, the index of the first
+ * occurrence is returned.
+ * If the array does not contain the data, the size of array is returned.
+ *  
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or the size of
+ * the array, if the data is not found in this array
+ */
+size_t ucx_array_find(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if an array contains a specific element.
+ * 
+ * An element is found, if ucx_array_find() returns a value less than the size.
+ * 
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the array contains the specified element data
+ * @see ucx_array_find()
+ */
+int ucx_array_contains(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxArray with the best available sort algorithm.
+ * 
+ * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS).
+ * The order of arguments is automatically adjusted for the FreeBSD and MacOS
+ * version of qsort_r().
+ * 
+ * If qsort_r() is not available, a merge sort algorithm is used, which is
+ * guaranteed to use no more additional memory than for exactly one element.
+ * 
+ * @param array the array to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func() or <code>NULL</code>
+ */
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is in general an expensive operation, because several elements may
+ * be moved. If the order of the elements is not relevant, use
+ * ucx_array_remove_fast() instead.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove(UcxArray *array, size_t index);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is an O(1) operation, but does not maintain the order of the elements.
+ * The last element in the array is moved to the location of the removed
+ * element.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove_fast(UcxArray *array, size_t index);
+
+/**
+ * Shrinks the memory to exactly fit the contents.
+ * 
+ * After this operation, the capacity equals the size.
+ * 
+ * @param array a pointer to the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_shrink(UcxArray* array);
+
+/**
+ * Sets the capacity of the array.
+ * 
+ * If the new capacity is smaller than the size of the array, the elements
+ * are removed and the size is adjusted accordingly.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the new capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_resize(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the array only, if the capacity is insufficient.
+ * 
+ * If the requested capacity is smaller than the current capacity, this
+ * function does nothing.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the guaranteed capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_reserve(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the capacity, if the specified number of elements would not fit.
+ * 
+ * A call to ucx_array_grow(array, count) is effectively the same as
+ * ucx_array_reserve(array, array->size+count).
+ * 
+ * @param array a pointer to the array
+ * @param count the number of elements that should additionally fit
+ * into the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_grow(UcxArray* array, size_t count);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_ARRAY_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/avl.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,353 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+
+/**
+ * @file avl.h
+ * 
+ * AVL tree implementation.
+ * 
+ * This binary search tree implementation allows average O(1) insertion and
+ * removal of elements (excluding binary search time).
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_AVL_H
+#define UCX_AVL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <inttypes.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX AVL Node type.
+ * 
+ * @see UcxAVLNode
+ */
+typedef struct UcxAVLNode UcxAVLNode;
+
+/**
+ * UCX AVL Node.
+ */
+struct UcxAVLNode {
+    /**
+     * The key for this node.
+     */
+    intptr_t key;
+    /**
+     * Data contained by this node.
+     */
+    void *value;
+    /**
+     * The height of this (sub)-tree.
+     */
+    size_t height;
+    /**
+     * Parent node.
+     */
+    UcxAVLNode *parent;
+    /**
+     * Root node of left subtree.
+     */
+    UcxAVLNode *left;
+    /**
+     * Root node of right subtree.
+     */
+    UcxAVLNode *right;
+};
+
+/**
+ * UCX AVL Tree.
+ */
+typedef struct {
+    /**
+     * The UcxAllocator that shall be used to manage the memory for node data.
+     */
+    UcxAllocator *allocator;
+    /**
+     * Root node of the tree.
+     */
+    UcxAVLNode *root;
+    /**
+     * Compare function that shall be used to compare the UcxAVLNode keys.
+     * @see UcxAVLNode.key
+     */
+    cmp_func cmpfunc;
+    /**
+     * Custom user data.
+     * This data will also be provided to the cmpfunc.
+     */
+    void *userdata;
+} UcxAVLTree;
+
+/**
+ * Initializes a new UcxAVLTree with a default allocator.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @return a new UcxAVLTree object
+ * @see ucx_avl_new_a()
+ */
+UcxAVLTree *ucx_avl_new(cmp_func cmpfunc);
+
+/**
+ * Initializes a new UcxAVLTree with the specified allocator.
+ * 
+ * The cmpfunc should be capable of comparing two keys within this AVL tree.
+ * So if you want to use null terminated strings as keys, you could use the
+ * ucx_cmp_str() function here.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @param allocator the UcxAllocator that shall be used
+ * @return a new UcxAVLTree object
+ */
+UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator);
+
+/**
+ * Destroys a UcxAVLTree.
+ * 
+ * Note, that the contents are not automatically freed.
+ * Use may use #ucx_avl_free_content() before calling this function.
+ * 
+ * @param tree the tree to destroy
+ * @see ucx_avl_free_content()
+ */
+void ucx_avl_free(UcxAVLTree *tree);
+
+/**
+ * Frees the contents of a UcxAVLTree.
+ * 
+ * This is a convenience function that iterates over the tree and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the tree's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free the entire tree afterwards, as the contents will be invalid.
+ * 
+ * @param tree for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_avl_free()
+ */
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr);
+
+/**
+ * Macro for initializing a new UcxAVLTree with the default allocator and a
+ * ucx_cmp_ptr() compare function.
+ * 
+ * @return a new default UcxAVLTree object
+ */
+#define ucx_avl_default_new() \
+    ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator())
+
+/**
+ * Gets the node from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the node (or <code>NULL</code>, if the key is not present)
+ */
+UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Gets the value from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the value (or <code>NULL</code>, if the key is not present)
+ */
+void *ucx_avl_get(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * A mode for #ucx_avl_find_node() with the same behavior as
+ * #ucx_avl_get_node().
+ */
+#define UCX_AVL_FIND_EXACT         0
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at least
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_LOWER_BOUNDED 1
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at most
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_UPPER_BOUNDED 2
+/**
+ * A mode for #ucx_avl_find_node() finding the node with a key that is as close
+ * to the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.
+ */
+#define UCX_AVL_FIND_CLOSEST       3
+
+/**
+ * Finds a node within the tree. The following modes are supported:
+ * <ul>
+ * <li>#UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()</li>
+ * <li>#UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to
+ * the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.</li> 
+ * </ul>
+ * 
+ * The distance function provided MUST agree with the compare function of
+ * the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the node (or <code>NULL</code>, if no node can be found)
+ */
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Finds a value within the tree.
+ * See #ucx_avl_find_node() for details.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the value (or <code>NULL</code>, if no value can be found)
+ */
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * Attention: use this function only, if a possible old value does not need
+ * to be preserved.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * This is a secure function which saves the old value to the variable pointed
+ * at by oldvalue.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @param oldvalue optional: a pointer to the location where a possible old
+ * value shall be stored
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue);
+
+/**
+ * Removes a node from the AVL tree.
+ * 
+ * Note: the specified node is logically removed. The tree implementation
+ * decides which memory area is freed. In most cases the here provided node
+ * is freed, so its further use is generally undefined.
+ * 
+ * @param tree the UcxAVLTree
+ * @param node the node to remove
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * This is a secure function which saves the old key and value data from node
+ * to the variables at the location of oldkey and oldvalue (if specified), so
+ * they can be freed afterwards (if necessary).
+ * 
+ * Note: the returned key in oldkey is possibly not the same as the provided
+ * key for the lookup (in terms of memory location).
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key of the element to remove
+ * @param oldkey optional: a pointer to the location where the old key shall be
+ * stored
+ * @param oldvalue optional: a pointer to the location where the old value
+ * shall be stored
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
+        intptr_t *oldkey, void **oldvalue);
+
+/**
+ * Counts the nodes in the specified UcxAVLTree.
+ * @param tree the AVL tree
+ * @return the node count
+ */
+size_t ucx_avl_count(UcxAVLTree *tree);
+
+/**
+ * Finds the in-order predecessor of the given node.
+ * @param node an AVL node
+ * @return the in-order predecessor of the given node, or <code>NULL</code> if
+ * the given node is the in-order minimum
+ */
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node);
+
+/**
+ * Finds the in-order successor of the given node.
+ * @param node an AVL node
+ * @return the in-order successor of the given node, or <code>NULL</code> if
+ * the given node is the in-order maximum
+ */
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_AVL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/buffer.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,339 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+/**
+ * @file buffer.h
+ * 
+ * Advanced buffer implementation.
+ * 
+ * Instances of UcxBuffer can be used to read from or to write to like one
+ * would do with a stream. This allows the use of ucx_stream_copy() to copy
+ * contents from one buffer to another.
+ * 
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_BUFFER_H
+#define	UCX_BUFFER_H
+
+#include "ucx.h"
+#include <sys/types.h>
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define UCX_BUFFER_DEFAULT      0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents.
+ */
+#define UCX_BUFFER_AUTOFREE     0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define UCX_BUFFER_AUTOEXTEND   0x02
+
+/** UCX Buffer. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    char *space;
+    /** Current position of the buffer. */
+    size_t pos;
+    /** Current capacity (i.e. maximum size) of the buffer. */
+    size_t capacity;
+    /** Current size of the buffer content. */
+    size_t size;
+    /**
+     * Flag register for buffer features.
+     * @see #UCX_BUFFER_DEFAULT
+     * @see #UCX_BUFFER_AUTOFREE
+     * @see #UCX_BUFFER_AUTOEXTEND
+     */
+    int flags;
+} UcxBuffer;
+
+/**
+ * Creates a new buffer.
+ * 
+ * <b>Note:</b> you may provide <code>NULL</code> as argument for
+ * <code>space</code>. Then this function will allocate the space and enforce
+ * the #UCX_BUFFER_AUTOFREE flag.
+ * 
+ * @param space pointer to the memory area, or <code>NULL</code> to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param flags buffer features (see UcxBuffer.flags)
+ * @return the new buffer
+ */
+UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags);
+
+/**
+ * Destroys a buffer.
+ * 
+ * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer
+ * are also freed.
+ * 
+ * @param buffer the buffer to destroy
+ */
+void ucx_buffer_free(UcxBuffer* buffer);
+
+/**
+ * Creates a new buffer and fills it with extracted content from another buffer.
+ * 
+ * <b>Note:</b> the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer.
+ * 
+ * @param src the source buffer
+ * @param start the start position of extraction
+ * @param length the count of bytes to extract (must not be zero)
+ * @param flags feature mask for the new buffer
+ * @return a new buffer containing the extraction
+ */
+UcxBuffer* ucx_buffer_extract(UcxBuffer *src,
+        size_t start, size_t length, int flags);
+
+/**
+ * A shorthand macro for the full extraction of the buffer.
+ * 
+ * @param src the source buffer
+ * @param flags feature mask for the new buffer
+ * @return a new buffer with the extracted content
+ */
+#define ucx_buffer_clone(src,flags) \
+    ucx_buffer_extract(src, 0, (src)->capacity, flags)
+
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ * 
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ * 
+ * If the offset is negative, the contents are shifted to the left where the
+ * first <code>shift</code> bytes are discarded.
+ * The new size of the buffer is the old size minus
+ * the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ * 
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ * 
+ * <b>Security note:</b> the shifting operation does <em>not</em> erase the
+ * previously occupied memory cells. You can easily do that manually, e.g. by
+ * calling <code>memset(buffer->space, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->size, 0, buffer->capacity-buffer->size)</code>
+ * for a left shift.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift);
+
+/**
+ * Shifts the buffer to the right.
+ * See ucx_buffer_shift() for details.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift);
+
+/**
+ * Shifts the buffer to the left.
+ * 
+ * See ucx_buffer_shift() for details. Note, however, that this method expects
+ * a positive shift offset.
+ * 
+ * Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return always zero
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift);
+
+
+/**
+ * Moves the position of the buffer.
+ * 
+ * The new position is relative to the <code>whence</code> argument.
+ *
+ * SEEK_SET marks the start of the buffer.
+ * SEEK_CUR marks the current position.
+ * SEEK_END marks the end of the buffer.
+ * 
+ * With an offset of zero, this function sets the buffer position to zero
+ * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position
+ * unchanged (SEEK_CUR).
+ * 
+ * @param buffer
+ * @param offset position offset relative to <code>whence</code>
+ * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ * 
+ * The data is deleted by a zeroing it with call to <code>memset()</code>.
+ * 
+ * @param buffer the buffer to be cleared
+ */
+#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \
+        (buffer)->size = 0; (buffer)->pos = 0;
+
+/**
+ * Tests, if the buffer position has exceeded the buffer capacity.
+ * 
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * available byte of the buffer.
+ */
+int ucx_buffer_eof(UcxBuffer *buffer);
+
+
+/**
+ * Extends the capacity of the buffer.
+ * 
+ * <b>Note:</b> The buffer capacity increased by a power of two. I.e.
+ * the buffer capacity is doubled, as long as it would not hold the current
+ * content plus the additional required bytes.
+ * 
+ * <b>Attention:</b> the argument provided is the number of <i>additional</i>
+ * bytes the buffer shall hold. It is <b>NOT</b> the total number of bytes the
+ * buffer shall hold.
+ * 
+ * @param buffer the buffer to extend
+ * @param additional_bytes the number of additional bytes the buffer shall
+ * <i>at least</i> hold
+ * @return 0 on success or a non-zero value on failure
+ */
+int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes);
+
+/**
+ * Writes data to a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes written.
+ * 
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the UcxBuffer to write to
+ * @return the total count of bytes written
+ */
+size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer);
+
+/**
+ * Reads data from a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes read.
+ * 
+ * @param ptr a pointer to the memory area where to store the read data
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the UcxBuffer to read from
+ * @return the total number of elements read
+ */
+size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer);
+
+/**
+ * Writes a character to a buffer.
+ * 
+ * The least significant byte of the argument is written to the buffer. If the
+ * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled,
+ * the buffer capacity is extended by ucx_buffer_extend(). If the feature is
+ * disabled or buffer extension fails, <code>EOF</code> is returned.
+ * 
+ * On successful write the position of the buffer is increased.
+ * 
+ * @param buffer the buffer to write to
+ * @param c the character to write as <code>int</code> value
+ * @return the byte that has bean written as <code>int</code> value or
+ * <code>EOF</code> when the end of the stream is reached and automatic
+ * extension is not enabled or not possible
+ */
+int ucx_buffer_putc(UcxBuffer *buffer, int c);
+
+/**
+ * Gets a character from a buffer.
+ * 
+ * The current position of the buffer is increased after a successful read.
+ * 
+ * @param buffer the buffer to read from
+ * @return the character as <code>int</code> value or <code>EOF</code>, if the
+ * end of the buffer is reached
+ */
+int ucx_buffer_getc(UcxBuffer *buffer);
+
+/**
+ * Writes a string to a buffer.
+ * 
+ * @param buffer the buffer
+ * @param str the string
+ * @return the number of bytes written
+ */
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str);
+
+/**
+ * Returns the complete buffer content as sstr_t.
+ * @param buffer the buffer
+ * @return the result of <code>sstrn()</code> with the buffer space and size
+ * as arguments
+ */
+#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size)
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_BUFFER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/list.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,512 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * Doubly linked list implementation.
+ * 
+ * @file   list.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_LIST_H
+#define	UCX_LIST_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX lists.
+ * 
+ * The first argument is the name of the iteration variable. The scope of
+ * this variable is limited to the <code>UCX_FOREACH</code> statement.
+ * 
+ * The second argument is a pointer to the list. In most cases this will be the
+ * pointer to the first element of the list, but it may also be an arbitrary
+ * element of the list. The iteration will then start with that element.
+ * 
+ * @param list The first element of the list
+ * @param elem The variable name of the element
+ */
+#define UCX_FOREACH(elem,list) \
+        for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next)
+
+/**
+ * UCX list type.
+ * @see UcxList
+ */
+typedef struct UcxList UcxList;
+
+/**
+ * UCX list structure.
+ */
+struct UcxList {
+    /**
+     * List element payload.
+     */
+    void    *data;
+    /**
+     * Pointer to the next list element or <code>NULL</code>, if this is the
+     * last element.
+     */
+    UcxList *next;
+    /**
+     * Pointer to the previous list element or <code>NULL</code>, if this is
+     * the first element.
+     */
+    UcxList *prev;
+};
+
+/**
+ * Creates an element-wise copy of a list.
+ * 
+ * This function clones the specified list by creating new list elements and
+ * copying the data with the specified copy_func(). If no copy_func() is
+ * specified, a shallow copy is created and the new list will reference the
+ * same data as the source list.
+ * 
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ */
+UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data);
+
+/**
+ * Creates an element-wise copy of a list using a UcxAllocator.
+ * 
+ * See ucx_list_clone() for details.
+ * 
+ * You might want to pass the allocator via the <code>data</code> parameter,
+ * to access it within the copy function for making deep copies.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ * @see ucx_list_clone()
+ */
+UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list,
+        copy_func cpyfnc, void* data);
+
+/**
+ * Compares two UCX lists element-wise by using a compare function.
+ * 
+ * Each element of the two specified lists are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * If the list pointers denote elements within a list, the lists are compared
+ * starting with the denoted elements. Thus any previous elements are not taken
+ * into account. This might be useful to check, if certain list tails match
+ * each other.
+ * 
+ * @param list1 the first list
+ * @param list2 the second list
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two lists equal element-wise, 0 otherwise
+ */
+int ucx_list_equals(const UcxList *list1, const UcxList *list2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the entire list.
+ * 
+ * The members of the list are not automatically freed, so ensure they are
+ * otherwise referenced or destroyed by ucx_list_free_contents().
+ * Otherwise, a memory leak is likely to occur.
+ * 
+ * <b>Caution:</b> the argument <b>MUST</b> denote an entire list (i.e. a call
+ * to ucx_list_first() on the argument must return the argument itself)
+ * 
+ * @param list the list to free
+ * @see ucx_list_free_contents()
+ */
+void ucx_list_free(UcxList *list);
+
+/**
+ * Destroys the entire list using a UcxAllocator.
+ * 
+ * See ucx_list_free() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to free
+ * @see ucx_list_free()
+ */
+void ucx_list_free_a(UcxAllocator *allocator, UcxList *list);
+
+/**
+ * Destroys the contents of the specified list by calling the specified
+ * destructor on each of them.
+ * 
+ * Note, that the contents are not usable afterwards and the list should be
+ * destroyed with ucx_list_free().
+ *
+ * If no destructor is specified (<code>NULL</code>), stdlib's free() is used.
+ * 
+ * @param list the list for which the contents shall be freed
+ * @param destr optional destructor function
+ * @see ucx_list_free()
+ */
+void ucx_list_free_content(UcxList* list, ucx_destructor destr);
+
+
+/**
+ * Inserts an element at the end of the list.
+ * 
+ * This is generally an O(n) operation, as the end of the list is retrieved with
+ * ucx_list_last().
+ * 
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ */
+UcxList *ucx_list_append(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the end of the list using a UcxAllocator.
+ * 
+ * See ucx_list_append() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ * @see ucx_list_append()
+ */
+UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+
+/**
+ * Inserts an element at the beginning of the list.
+ * 
+ * You <i>should</i> overwrite the old list pointer by calling
+ * <code>mylist = ucx_list_prepend(mylist, mydata);</code>. However, you may
+ * also perform successive calls of ucx_list_prepend() on the same list pointer,
+ * as this function always searchs for the head of the list with
+ * ucx_list_first().
+ * 
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ */
+UcxList *ucx_list_prepend(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the beginning of the list using a UcxAllocator.
+ * 
+ * See ucx_list_prepend() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ * @see ucx_list_prepend()
+ */
+UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+/**
+ * Concatenates two lists.
+ * 
+ * Either of the two arguments may be <code>NULL</code>.
+ * 
+ * This function modifies the references to the next/previous element of
+ * the last/first element of <code>list1</code>/<code>
+ * list2</code>.
+ * 
+ * @param list1 first list
+ * @param list2 second list
+ * @return if <code>list1</code> is <code>NULL</code>, <code>list2</code> is
+ * returned, otherwise <code>list1</code> is returned
+ */
+UcxList *ucx_list_concat(UcxList *list1, UcxList *list2);
+
+/**
+ * Returns the first element of a list.
+ * 
+ * If the argument is the list pointer, it is directly returned. Otherwise
+ * this function traverses to the first element of the list and returns the
+ * list pointer.
+ * 
+ * @param elem one element of the list
+ * @return the first element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_first(const UcxList *elem);
+
+/**
+ * Returns the last element of a list.
+ * 
+ * If the argument has no successor, it is the last element and therefore
+ * directly returned. Otherwise this function traverses to the last element of
+ * the list and returns it.
+ * 
+ * @param elem one element of the list
+ * @return the last element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_last(const UcxList *elem);
+
+/**
+ * Returns the list element at the specified index.
+ * 
+ * @param list the list to retrieve the element from
+ * @param index index of the element to return
+ * @return the element at the specified index or <code>NULL</code>, if the
+ * index is greater than the list size
+ */
+UcxList *ucx_list_get(const UcxList *list, size_t index);
+
+/**
+ * Returns the index of an element.
+ * 
+ * @param list the list where to search for the element
+ * @param elem the element to find
+ * @return the index of the element or -1 if the list does not contain the
+ * element
+ */
+ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem);
+
+/**
+ * Returns the element count of the list.
+ * 
+ * @param list the list whose elements are counted
+ * @return the element count
+ */
+size_t ucx_list_size(const UcxList *list);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, the pointers are
+ * compared.
+ * 
+ * If the list contains the data more than once, the index of the first
+ * occurrence is returned.
+ *  
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or -1 if the
+ * data is not found in this list
+ */
+ssize_t ucx_list_find(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if a list contains a specific element.
+ * 
+ * An element is found, if ucx_list_find() returns a value greater than -1.
+ * 
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the list contains the specified element data
+ * @see ucx_list_find()
+ */
+int ucx_list_contains(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxList with natural merge sort.
+ * 
+ * This function uses O(n) additional temporary memory for merge operations
+ * that is automatically freed after each merge.
+ * 
+ * As the head of the list might change, you <b>MUST</b> call this function
+ * as follows: <code>mylist = ucx_list_sort(mylist, mycmpfnc, mydata);</code>.
+ * 
+ * @param list the list to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func()
+ * @return the sorted list
+ */
+UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the list.
+ * 
+ * If the first element is removed, the list pointer changes. So it is
+ * <i>highly recommended</i> to <i>always</i> update the pointer by calling
+ * <code>mylist = ucx_list_remove(mylist, myelem);</code>.
+ * 
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * is now empty
+ */
+UcxList *ucx_list_remove(UcxList *list, UcxList *element);
+
+/**
+ * Removes an element from the list using a UcxAllocator.
+ * 
+ * See ucx_list_remove() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * @see ucx_list_remove()
+ */
+UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list,
+        UcxList *element);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_LIST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/logging.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,253 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * Logging API.
+ * 
+ * @file   logging.h
+ * @author Mike Becker, Olaf Wintermann
+ */
+#ifndef UCX_LOGGING_H
+#define UCX_LOGGING_H
+
+#include "ucx.h"
+#include "map.h"
+#include "string.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* leave enough space for custom log levels */
+
+/** Log level for error messages. */
+#define UCX_LOGGER_ERROR        0x00
+    
+/** Log level for warning messages. */
+#define UCX_LOGGER_WARN         0x10
+
+/** Log level for information messages. */
+#define UCX_LOGGER_INFO         0x20
+
+/** Log level for debug messages. */
+#define UCX_LOGGER_DEBUG        0x30
+
+/** Log level for trace messages. */
+#define UCX_LOGGER_TRACE        0x40
+
+/**
+ * Output flag for the log level. 
+ * If this flag is set, the log message will contain the log level.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_LEVEL        0x01
+
+/**
+ * Output flag for the timestmap.
+ * If this flag is set, the log message will contain the timestmap.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_TIMESTAMP    0x02
+
+/**
+ * Output flag for the source.
+ * If this flag is set, the log message will contain the source file and line
+ * number.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_SOURCE       0x04
+
+/**
+ * The UCX Logger object.
+ */
+typedef struct {
+    /** The stream this logger writes its messages to.*/
+    void *stream;
+
+    /**
+     * The write function that shall be used.
+     * For standard file or stdout loggers this might be standard fwrite
+     * (default).
+     */
+    write_func writer;
+
+    /**
+     * The date format for timestamp outputs including the delimiter
+     * (default: <code>"%F %T %z "</code>).
+     * @see UCX_LOGGER_TIMESTAMP
+     */
+    char *dateformat;
+
+    /**
+     * The level, this logger operates on.
+     * If a log command is issued, the message will only be logged, if the log
+     * level of the message is less or equal than the log level of the logger.
+     */
+    unsigned int level;
+
+    /**
+     * A configuration mask for automatic output. 
+     * For each flag that is set, the logger automatically outputs some extra
+     * information like the timestamp or the source file and line number.
+     * See the documentation for the flags for details.
+     */
+    unsigned int mask;
+
+    /**
+     * A map of valid log levels for this logger.
+     * 
+     * The keys represent all valid log levels and the values provide string
+     * representations, that are used, if the UCX_LOGGER_LEVEL flag is set.
+     * 
+     * The exact data types are <code>unsigned int</code> for the key and
+     * <code>const char*</code> for the value.
+     * 
+     * @see UCX_LOGGER_LEVEL
+     */
+    UcxMap* levels;
+} UcxLogger;
+
+/**
+ * Creates a new logger.
+ * @param stream the stream, which the logger shall write to
+ * @param level the level on which the logger shall operate
+ * @param mask configuration mask (cf. UcxLogger.mask)
+ * @return a new logger object
+ */
+UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask);
+
+/**
+ * Destroys the logger.
+ * 
+ * The map containing the valid log levels is also automatically destroyed.
+ * 
+ * @param logger the logger to destroy
+ */
+void ucx_logger_free(UcxLogger* logger);
+
+/**
+ * Internal log function - use macros instead.
+ * 
+ * This function uses the <code>format</code> and variadic arguments for a
+ * printf()-style output of the log message.
+ * 
+ * Dependent on the UcxLogger.mask some information is prepended. The complete
+ * format is:
+ * 
+ * <code>[LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message</code>
+ *
+ * The source file name is reduced to the actual file name. This is necessary to
+ * get consistent behavior over different definitions of the __FILE__ macro.
+ *
+ * <b>Attention:</b> the message (including automatically generated information)
+ * is limited to 4096 characters. The level description is limited to
+ * 256 characters and the timestamp string is limited to 128 characters.
+ * 
+ * @param logger the logger to use
+ * @param level the level to log on
+ * @param file information about the source file
+ * @param line information about the source line number
+ * @param format format string
+ * @param ... arguments
+ * @see ucx_logger_log()
+ */
+void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
+        const unsigned int line, const char* format, ...);
+
+/**
+ * Registers a custom log level.
+ * @param logger the logger
+ * @param level the log level as unsigned integer
+ * @param name a string literal describing the level
+ */
+#define ucx_logger_register_level(logger, level, name) {\
+        unsigned int l; \
+            l = level; \
+            ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \
+        } while (0);
+
+/**
+ * Logs a message at the specified level.
+ * @param logger the logger to use
+ * @param level the level to log the message on
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_log(logger, level, ...) \
+    ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an error message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_error(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an information message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_info(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a warning message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_warn(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a debug message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_debug(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a trace message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_trace(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_LOGGING_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/map.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,549 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+/**
+ * @file map.h
+ * 
+ * Hash map implementation.
+ * 
+ * This implementation uses murmur hash 2 and separate chaining with linked
+ * lists.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MAP_H
+#define	UCX_MAP_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX maps.
+ * 
+ * The <code>key</code> variable is implicitly defined, but the
+ * <code>value</code> variable must be already declared as type information
+ * cannot be inferred.
+ * 
+ * @param key the variable name for the key
+ * @param value the variable name for the value
+ * @param iter a UcxMapIterator
+ * @see ucx_map_iterator()
+ */
+#define UCX_MAP_FOREACH(key,value,iter) \
+        for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);)
+
+/** Type for the UCX map. @see UcxMap */
+typedef struct UcxMap          UcxMap;
+
+/** Type for a key of a UcxMap. @see UcxKey */
+typedef struct UcxKey          UcxKey;
+
+/** Type for an element of a UcxMap. @see UcxMapElement */
+typedef struct UcxMapElement   UcxMapElement;
+
+/** Type for an iterator over a UcxMap. @see UcxMapIterator */
+typedef struct UcxMapIterator  UcxMapIterator;
+
+/** Structure for the UCX map. */
+struct UcxMap {
+    /** An allocator that is used for the map elements. */
+    UcxAllocator  *allocator;
+    /** The array of map element lists. */
+    UcxMapElement **map;
+    /** The size of the map is the length of the element list array. */
+    size_t        size;
+    /** The count of elements currently stored in this map. */
+    size_t        count;
+};
+
+/** Structure to publicly denote a key of a UcxMap. */
+struct UcxKey {
+    /** The key data. */
+    const void *data;
+    /** The length of the key data. */
+    size_t     len;
+    /** A cache for the hash value of the key data. */
+    int        hash;
+};
+
+/** Internal structure for a key of a UcxMap. */
+struct UcxMapKey {
+    /** The key data. */
+    void    *data;
+    /** The length of the key data. */
+    size_t  len;
+    /** The hash value of the key data. */
+    int     hash;
+};
+
+/** Structure for an element of a UcxMap. */
+struct UcxMapElement {
+    /** The value data. */
+    void              *data;
+    
+    /** A pointer to the next element in the current list. */
+    UcxMapElement     *next;
+    
+    /** The corresponding key. */
+    struct UcxMapKey  key;
+};
+
+/** Structure for an iterator over a UcxMap. */
+struct UcxMapIterator {
+    /** The map to iterate over. */
+    UcxMap const  *map;
+    
+    /** The current map element. */
+    UcxMapElement *cur;
+    
+    /**
+     * The current index of the element list array.
+     * <b>Attention: </b> this is <b>NOT</b> the element index! Do <b>NOT</b>
+     * manually iterate over the map by increasing this index. Use
+     * ucx_map_iter_next().
+     * @see UcxMap.map*/
+    size_t        index;
+};
+
+/**
+ * Creates a new hash map with the specified size.
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new(size_t size);
+
+/**
+ * Creates a new hash map with the specified size using a UcxAllocator.
+ * @param allocator the allocator to use
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size);
+
+/**
+ * Frees a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be freed
+ * @see ucx_map_free_content()
+ */
+void ucx_map_free(UcxMap *map);
+
+/**
+ * Frees the contents of a hash map.
+ * 
+ * This is a convenience function that iterates over the map and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the map's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free or clear the map afterwards, as the contents will be invalid.
+ * 
+ * @param map for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_map_free()
+ * @see ucx_map_clear()
+ */
+void ucx_map_free_content(UcxMap *map, ucx_destructor destr);
+
+/**
+ * Clears a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be cleared
+ * @see ucx_map_free_content()
+ */
+void ucx_map_clear(UcxMap *map);
+
+
+/**
+ * Copies contents from a map to another map using a copy function.
+ * 
+ * <b>Note:</b> The destination map does not need to be empty. However, if it
+ * contains data with keys that are also present in the source map, the contents
+ * are overwritten.
+ * 
+ * @param from the source map
+ * @param to the destination map
+ * @param fnc the copy function or <code>NULL</code> if the pointer address
+ * shall be copied
+ * @param data additional data for the copy function
+ * @return 0 on success or a non-zero value on memory allocation errors
+ */
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ * 
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ * 
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ *
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ *
+ * @param allocator the allocator to use for the cloned map
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+                        UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Increases size of the hash map, if necessary.
+ * 
+ * The load value is 0.75*UcxMap.size. If the element count exceeds the load
+ * value, the map needs to be rehashed. Otherwise no action is performed and
+ * this function simply returns 0.
+ * 
+ * The rehashing process ensures, that the UcxMap.size is at least
+ * 2.5*UcxMap.count. So there is enough room for additional elements without
+ * the need of another soon rehashing.
+ * 
+ * You can use this function to dramatically increase access performance.
+ * 
+ * @param map the map to rehash
+ * @return 1, if a memory allocation error occurred, 0 otherwise
+ */
+int ucx_map_rehash(UcxMap *map);
+
+/**
+ * Puts a key/value-pair into the map.
+ * 
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+int ucx_map_put(UcxMap *map, UcxKey key, void *value);
+
+/**
+ * Retrieves a value by using a key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+void* ucx_map_get(UcxMap const *map, UcxKey key);
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ */
+void* ucx_map_remove(UcxMap *map, UcxKey key);
+
+/**
+ * Shorthand for putting data with a sstr_t key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_sstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value)
+
+/**
+ * Shorthand for putting data with a C string key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_cstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value)
+
+/**
+ * Shorthand for putting data with an integer key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_int_put(map, key, value) \
+    ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value)
+
+/**
+ * Shorthand for getting data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_sstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for getting data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_cstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for getting data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_int_get(map, key) \
+    ucx_map_get(map, ucx_key(&key, sizeof(int)))
+
+/**
+ * Shorthand for removing data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_sstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for removing data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_cstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for removing data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_int_remove(map, key) \
+    ucx_map_remove(map, ucx_key(&key, sizeof(key)))
+
+/**
+ * Creates a UcxKey based on the given data.
+ * 
+ * This function implicitly computes the hash.
+ * 
+ * @param data the data for the key
+ * @param len the length of the data
+ * @return a UcxKey with implicitly computed hash
+ * @see ucx_hash()
+ */
+UcxKey ucx_key(const void *data, size_t len);
+
+/**
+ * Computes a murmur hash-2.
+ * 
+ * @param data the data to hash
+ * @param len the length of the data
+ * @return the murmur hash-2 of the data
+ */
+int ucx_hash(const char *data, size_t len);
+
+/**
+ * Creates an iterator for a map.
+ * 
+ * <b>Note:</b> A UcxMapIterator iterates over all elements in all element
+ * lists successively. Therefore the order highly depends on the key hashes and
+ * may vary under different map sizes. So generally you may <b>NOT</b> rely on
+ * the iteration order.
+ * 
+ * <b>Note:</b> The iterator is <b>NOT</b> initialized. You need to call
+ * ucx_map_iter_next() at least once before accessing any information. However,
+ * it is not recommended to access the fields of a UcxMapIterator directly.
+ * 
+ * @param map the map to create the iterator for
+ * @return an iterator initialized on the first element of the
+ * first element list
+ * @see ucx_map_iter_next()
+ */
+UcxMapIterator ucx_map_iterator(UcxMap const *map);
+
+/**
+ * Proceeds to the next element of the map (if any).
+ * 
+ * Subsequent calls on the same iterator proceed to the next element and
+ * store the key/value-pair into the memory specified as arguments of this
+ * function.
+ * 
+ * If no further elements are found, this function returns zero and leaves the
+ * last found key/value-pair in memory.
+ * 
+ * @param iterator the iterator to use
+ * @param key a pointer to the memory where to store the key
+ * @param value a pointer to the memory where to store the value
+ * @return 1, if another element was found, 0 if all elements has been processed
+ * @see ucx_map_iterator()
+ */
+int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_MAP_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/mempool.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,209 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+/**
+ * @file mempool.h
+ * 
+ * Memory pool implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define	UCX_MEMPOOL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX mempool structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this pool */
+    UcxAllocator *allocator;
+    
+    /** List of pointers to pooled memory. */
+    void         **data;
+    
+    /** Count of pooled memory items. */
+    size_t       ndata;
+    
+    /** Memory pool size. */
+    size_t       size;
+} UcxMempool;
+
+/** Shorthand for a new default memory pool with a capacity of 16 elements. */
+#define ucx_mempool_new_default() ucx_mempool_new(16)
+
+
+/**
+ * Creates a memory pool with the specified initial size.
+ * 
+ * As the created memory pool automatically grows in size by factor two when
+ * trying to allocate memory on a full pool, it is recommended that you use
+ * a power of two for the initial size.
+ * 
+ * @param n initial pool size (should be a power of two, e.g. 16)
+ * @return a pointer to the new memory pool
+ * @see ucx_mempool_new_default()
+ */
+UcxMempool *ucx_mempool_new(size_t n);
+
+/**
+ * Resizes a memory pool.
+ * 
+ * This function will fail if the new capacity is not sufficient for the
+ * present data.
+ * 
+ * @param pool the pool to resize
+ * @param newcap the new capacity
+ * @return zero on success or non-zero on failure
+ */
+int ucx_mempool_chcap(UcxMempool *pool, size_t newcap);
+
+/**
+ * Allocates pooled memory.
+ * 
+ * @param pool the memory pool
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_mempool_malloc(UcxMempool *pool, size_t n);
+/**
+ * Allocates a pooled memory array.
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param pool the memory pool
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize);
+
+/**
+ * Reallocates pooled memory.
+ * 
+ * If the memory to be reallocated is not contained by the specified pool, the
+ * behavior is undefined.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n);
+
+/**
+ * Frees pooled memory.
+ * 
+ * Before freeing the memory, the specified destructor function (if any)
+ * is called.
+ * 
+ * If you specify memory, that is not pooled by the specified memory pool, the
+ * program will terminate with a call to <code>abort()</code>.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be freed
+ * @see ucx_mempool_set_destr()
+ */
+void ucx_mempool_free(UcxMempool *pool, void *ptr);
+
+/**
+ * Destroys a memory pool.
+ * 
+ * For each element the destructor function (if any) is called and the element
+ * is freed.
+ * 
+ * Each of the registered destructor function that has no corresponding element
+ * within the pool (namely those registered by ucx_mempool_reg_destr) is
+ * called interleaving with the element destruction, but with guarantee to the
+ * order in which they were registered (FIFO order).
+ * 
+ * 
+ * @param pool the mempool to destroy
+ */
+void ucx_mempool_destroy(UcxMempool *pool);
+
+/**
+ * Sets a destructor function for the specified memory.
+ * 
+ * The destructor is automatically called when the memory is freed or the
+ * pool is destroyed.
+ * A destructor for pooled memory <b>MUST NOT</b> free the memory itself,
+ * as this is done by the pool. Use a destructor to free any resources
+ * managed by the pooled object.
+ * 
+ * The only requirement for the specified memory is, that it <b>MUST</b> be
+ * pooled memory by a UcxMempool or an element-compatible mempool. The pointer
+ * to the destructor function is saved in a reserved area before the actual
+ * memory.
+ * 
+ * @param ptr pooled memory
+ * @param func a pointer to the destructor function
+ * @see ucx_mempool_free()
+ * @see ucx_mempool_destroy()
+ */
+void ucx_mempool_set_destr(void *ptr, ucx_destructor func);
+
+/**
+ * Registers a destructor function for the specified (non-pooled) memory.
+ *
+ * This is useful, if you have memory that has not been allocated by a mempool,
+ * but shall be managed by a mempool.
+ * 
+ * This function creates an entry in the specified mempool and the memory will
+ * therefore (logically) convert to pooled memory.
+ * <b>However, this does not cause the memory to be freed automatically!</b>.
+ * If you want to use this function, make the memory pool free non-pooled
+ * memory, the specified destructor function must call <code>free()</code>
+ * by itself. But keep in mind, that you then MUST NOT use this destructor
+ * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it
+ * would cause a double-free.
+ * 
+ * @param pool the memory pool
+ * @param ptr data the destructor is registered for
+ * @param destr a pointer to the destructor function
+ */
+void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_MEMPOOL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/properties.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,221 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * @file properties.h
+ * 
+ * Load / store utilities for properties files.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define	UCX_PROPERTIES_H
+
+#include "ucx.h"
+#include "map.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying 
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+    /**
+     * Input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    char   *buffer;
+    
+    /**
+     * Length of the input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    size_t buflen;
+    
+    /**
+     * Current buffer position (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t pos;
+    
+    /**
+     * Internal temporary buffer (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    char   *tmp;
+    
+    /**
+     * Internal temporary buffer length (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmplen;
+    
+    /**
+     * Internal temporary buffer capacity (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmpcap;
+    
+    /**
+     * Parser error code.
+     * This is always 0 on success and a nonzero value on syntax errors.
+     * The value is set by ucx_properties_next().
+     */
+    int    error;
+    
+    /**
+     * The delimiter that shall be used.
+     * This is '=' by default.
+     */
+    char   delimiter;
+    
+    /**
+     * The first comment character.
+     * This is '#' by default.
+     */
+    char   comment1;
+    
+    /**
+     * The second comment character.
+     * This is not set by default.
+     */
+    char   comment2;
+    
+    /**
+     * The third comment character.
+     * This is not set by default.
+     */
+    char   comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ * 
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ * 
+ * 
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ * 
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ * 
+ * <b>Attention:</b> the sstr_t.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ * 
+ * @param prop the UcxProperties object
+ * @param name a pointer to the sstr_t that shall contain the property name
+ * @param value a pointer to the sstr_t that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ * 
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
+ * 
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, UcxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ * 
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ * 
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ * 
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(UcxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ * 
+ * The key/value-pairs are written by using the following format:
+ * 
+ * <code>[key] = [value]\\n</code>
+ * 
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(UcxMap *map, FILE *file);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_PROPERTIES_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/stack.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,240 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+/**
+ * @file stack.h
+ * 
+ * Default stack memory allocation implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STACK_H
+#define	UCX_STACK_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * UCX stack structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this stack */
+    UcxAllocator allocator;
+    
+    /** Stack size. */
+    size_t size;
+    
+    /** Pointer to the bottom of the stack */
+    char *space;
+    
+    /** Pointer to the top of the stack */
+    char *top;
+} UcxStack;
+
+/**
+ * Metadata for each UCX stack element.
+ */
+struct ucx_stack_metadata {
+    /**
+     * Location of the previous element (<code>NULL</code> if this is the first)
+     */
+    char *prev;
+    
+    /** Size of this element */
+    size_t size;
+};
+
+/**
+ * Initializes UcxStack structure with memory.
+ * 
+ * @param stack a pointer to an uninitialized stack structure
+ * @param space the memory area that shall be managed
+ * @param size size of the memory area
+ * @return a new UcxStack structure
+ */
+void ucx_stack_init(UcxStack *stack, char* space, size_t size);
+
+/**
+ * Allocates stack memory.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory or <code>NULL</code> on stack
+ * overflow
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_stack_malloc(UcxStack *stack, size_t n);
+
+/**
+ * Allocates memory with #ucx_stack_malloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @param data a pointer to the data to copy
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_malloc
+ */
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data);
+
+/**
+ * Allocates an array of stack memory
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
+
+/**
+ * Allocates memory with #ucx_stack_calloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @param data a pointer to the data
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_calloc
+ */
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data);
+
+/**
+ * Reallocates memory on the stack.
+ * 
+ * Shrinking memory is always safe. Extending memory can be very expensive. 
+ * 
+ * @param stack the stack
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n);
+
+/**
+ * Frees memory on the stack.
+ * 
+ * Freeing stack memory behaves in a special way.
+ * 
+ * If the element, that should be freed, is the top most element of the stack,
+ * it is removed from the stack. Otherwise it is marked as freed. Marked
+ * elements are removed, when they become the top most elements of the stack.
+ * 
+ * @param stack a pointer to the stack
+ * @param ptr a pointer to the memory that shall be freed
+ */
+void ucx_stack_free(UcxStack *stack, void *ptr);
+
+
+/**
+ * Returns the size of the top most element.
+ * @param stack a pointer to the stack
+ * @return the size of the top most element
+ */
+#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\
+                                  (stack)->top - 1)->size : 0)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>, if specified.
+ * 
+ * Use #ucx_stack_topsize()# to get the amount of memory that must be available
+ * at the location of <code>dest</code>.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to, or <code>
+ * NULL</code>, if the element shall only be removed.
+ * @see ucx_stack_free
+ * @see ucx_stack_popn
+ */
+#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>.
+ * 
+ * This function copies at most <code>n</code> bytes to the destination, but
+ * the element is always freed as a whole.
+ * If the element was larger than <code>n</code>, the remaining data is lost.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to
+ * @param n copies at most n bytes to <code>dest</code>
+ * @see ucx_stack_pop
+ */
+void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
+
+/**
+ * Returns the remaining available memory on the specified stack.
+ * 
+ * @param stack a pointer to the stack
+ * @return the remaining available memory
+ */
+size_t ucx_stack_avail(UcxStack *stack);
+
+/**
+ * Checks, if the stack is empty.
+ * 
+ * @param stack a pointer to the stack
+ * @return nonzero, if the stack is empty, zero otherwise
+ */
+#define ucx_stack_empty(stack) (!(stack)->top)
+
+/**
+ * Computes a recommended size for the stack memory area. Note, that
+ * reallocations have not been taken into account, so you might need to reserve
+ * twice as much memory to allow many reallocations.
+ * 
+ * @param size the approximate payload
+ * @param elems the approximate count of element allocations
+ * @return a recommended size for the stack space based on the information
+ * provided
+ */
+#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \
+                                    (elems + 1))
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_STACK_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/string.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,1201 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * Bounded string implementation.
+ * 
+ * The UCX strings (<code>sstr_t</code>) provide an alternative to C strings.
+ * The main difference to C strings is, that <code>sstr_t</code> does <b>not
+ * need to be <code>NULL</code>-terminated</b>. Instead the length is stored
+ * within the structure.
+ * 
+ * When using <code>sstr_t</code>, developers must be full aware of what type
+ * of string (<code>NULL</code>-terminated) or not) they are using, when 
+ * accessing the <code>char* ptr</code> directly.
+ * 
+ * The UCX string module provides some common string functions, known from
+ * standard libc, working with <code>sstr_t</code>.
+ * 
+ * @file   string.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STRING_H
+#define	UCX_STRING_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+/*
+ * Use this macro to disable the shortcuts if you experience macro collision.
+ */
+#ifndef UCX_NO_SSTR_SHORTCUTS
+/**
+ * Shortcut for a <code>sstr_t struct</code>
+ * or <code>scstr_t struct</code> literal.
+ */
+#define ST(s) { s, sizeof(s)-1 }
+
+/** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
+#define S(s) sstrn(s, sizeof(s)-1)
+
+/** Shortcut for the conversion of a C string to a <code>scstr_t</code>. */
+#define SC(s) scstrn(s, sizeof(s)-1)
+#endif /* UCX_NO_SSTR_SHORTCUTS */
+
+/*
+ * Use this macro to disable the format macros.
+ */
+#ifndef UCX_NO_SSTR_FORMAT_MACROS
+/** Expands a sstr_t or scstr_t to printf arguments. */
+#define SFMT(s) (int) (s).length, (s).ptr
+
+/** Format specifier for a sstr_t or scstr_t. */
+#define PRIsstr ".*s"
+#endif /* UCX_NO_SSTR_FORMAT_MACROS */
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+  
+/**
+ * The UCX string structure.
+ */
+typedef struct {
+   /** A pointer to the string
+    * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+} sstr_t;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+typedef struct {
+    /** A constant pointer to the immutable string
+     * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    const char *ptr;
+    /** The length of the string */
+    size_t length;
+} scstr_t;
+
+#ifdef	__cplusplus
+}
+#endif
+
+
+#ifdef __cplusplus
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+inline scstr_t s2scstr(sstr_t s) {
+    scstr_t c;
+    c.ptr = s.ptr;
+    c.length = s.length;
+    return c;
+}
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+inline scstr_t s2scstr(scstr_t str) {
+    return str;
+}
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(s) s2scstr(s)
+#else
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+scstr_t ucx_sc2sc(scstr_t str);
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+scstr_t ucx_ss2sc(sstr_t str);
+
+#if __STDC_VERSION__ >= 201112L
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str)
+
+#elif defined(__GNUC__) || defined(__clang__)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) __builtin_choose_expr( \
+        __builtin_types_compatible_p(typeof(str), sstr_t), \
+        ucx_ss2sc, \
+        ucx_sc2sc)(str)
+
+#elif defined(__sun)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \
+	scstr_t ucx_tmp_var_c; \
+	ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\
+	ucx_tmp_var_c.length = ucx_tmp_var_str.length;\
+	ucx_tmp_var_c; })
+#else /* no generics and no builtins */
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * 
+ * This <b>internal</b> function (ab)uses the C standard an expects one single
+ * argument which is then implicitly converted to scstr_t without a warning.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @return the an immutable version of the provided string
+ */
+scstr_t ucx_ss2c_s();
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ucx_ss2c_s(str)
+#endif /* C11 feature test */
+
+#endif /* C++ */
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Creates a new sstr_t based on a C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstr().
+ * 
+ * @param cstring the C string to wrap
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstrn()
+ */
+sstr_t sstr(char *cstring);
+
+/**
+ * Creates a new sstr_t of the specified length based on a C string.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstrn().
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstr()
+ * @see S()
+ */
+sstr_t sstrn(char *cstring, size_t length);
+
+/**
+ * Creates a new scstr_t based on a constant C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function.
+ * 
+ * @param cstring the C string to wrap
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstrn()
+ */
+scstr_t scstr(const char *cstring);
+
+
+/**
+ * Creates a new scstr_t of the specified length based on a constant C string.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function. * 
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstr()
+ */
+scstr_t scstrn(const char *cstring, size_t length);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count of the
+ * specified strings, the behavior is undefined.
+ *
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the accumulated length of all strings
+ */
+size_t scstrnlen(size_t count, ...);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count of the
+ * specified strings, the behavior is undefined.
+ * 
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the cumulated length of all strings
+ */
+#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+sstr_t scstrcat(size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ * 
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see scstrcat()
+ */
+sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see sstrcat()
+ */
+#define sstrcat_a(alloc, count, s1, ...) \
+    scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see sstrsubsl()
+ * @see sstrchr()
+ */
+sstr_t sstrsubs(sstr_t string, size_t start);
+
+/**
+ * Returns a substring with the given length starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see sstrsubs()
+ * @see sstrchr()
+ */
+sstr_t sstrsubsl(sstr_t string, size_t start, size_t length);
+
+/**
+ * Returns a substring of an immutable string starting at the specified
+ * location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+* input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see scstrsubsl()
+ * @see scstrchr()
+ */
+scstr_t scstrsubs(scstr_t string, size_t start);
+
+/**
+ * Returns a substring of an immutable string with a maximum length starting
+ * at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see scstrsubs()
+ * @see scstrchr()
+ */
+scstr_t scstrsubsl(scstr_t string, size_t start, size_t length);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrchr(sstr_t string, int chr);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrrchr(sstr_t string, int chr);
+
+/**
+ * Returns an immutable substring starting at the location of the first
+ * occurrence of the specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrchr(scstr_t string, int chr);
+
+/**
+ * Returns an immutable substring starting at the location of the last
+ * occurrence of the specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrrchr(scstr_t string, int chr);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+sstr_t scstrsstr(sstr_t string, scstr_t match);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrstr(string, match) scstrsstr(string, SCSTR(match))
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+scstr_t scstrscstr(scstr_t string, scstr_t match);
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrscstr(string, match) scstrscstr(string, SCSTR(match))
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use scstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit_a()
+ */
+sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count);
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use sstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit_a()
+ */
+#define sstrsplit(string, delim, count) \
+    scstrsplit(SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Performing scstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of scstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit()
+ */
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim,
+        ssize_t *count);
+
+/**
+ * Performing sstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of sstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit()
+ */
+#define sstrsplit_a(allocator, string, delim, count) \
+    scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+int scstrcmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+int scstrcasecmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup_a()
+ */
+sstr_t scstrdup(scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see sstrdup_a()
+ */
+#define sstrdup(string) scstrdup(SCSTR(string))
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string))
+
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new sstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new sstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the sstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = sstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * sstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new sstr_t containing the trimmed string
+ */
+sstr_t sstrtrim(sstr_t string);
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new scstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new scstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the scstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = scstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * scstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new scstr_t containing the trimmed string
+ */
+scstr_t scstrtrim(scstr_t string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix))
+
+/**
+ * Checks, if a string has a specific suffix.
+ * 
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrsuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(suffix))
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrcaseprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrcaseprefix(string, prefix) \
+  scstrcaseprefix(SCSTR(string), SCSTR(prefix))
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrcasesuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrcasesuffix(string, suffix) \
+  scstrcasesuffix(SCSTR(string), SCSTR(suffix))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup()
+ */
+sstr_t scstrlower(scstr_t string);
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower(string) scstrlower(SCSTR(string))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+  * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string);
+
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup()
+ */
+sstr_t scstrupper(scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper(string) scstrupper(SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper_a(allocator, string) scstrupper_a(allocator, string)
+
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+        scstr_t pattern, scstr_t replacement, size_t replmax);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t replacement, size_t replmax);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplacen(str, pattern, replacement, replmax) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplace_a(allocator, str, pattern, replacement) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(replacement), SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplace(str, pattern, replacement) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX)
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_STRING_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/test.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,241 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+ 
+/**
+ * @file: test.h
+ * 
+ * UCX Test Framework.
+ * 
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with UCX_TEST_ASSERT()
+ * }
+ * 
+ * UCX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #UCX_TEST_BEGIN
+ *   // tests with UCX_TEST_ASSERT() and/or
+ *   // calls with UCX_TEST_CALL_SUBROUTINE() here
+ *   #UCX_TEST_END
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ *
+ * <b>Note:</b> if a test fails, a longjump is performed
+ * back to the #UCX_TEST_BEGIN macro!
+ * 
+ * <b>Attention:</b> Do not call own functions within a test, that use
+ * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE().
+ * 
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define	UCX_TEST_H
+
+#include "ucx.h"
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+/** Type for the UcxTestSuite. */
+typedef struct UcxTestSuite UcxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*UcxTest)(UcxTestSuite*,FILE*);
+
+/** Type for the internal list of test cases. */
+typedef struct UcxTestList UcxTestList;
+
+/** Structure for the internal list of test cases. */
+struct UcxTestList {
+    
+    /** Test case. */
+    UcxTest test;
+    
+    /** Pointer to the next list element. */
+    UcxTestList *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct UcxTestSuite {
+    
+    /** The number of successful tests after the suite has been run. */
+    unsigned int success;
+    
+    /** The number of failed tests after the suite has been run. */
+    unsigned int failure;
+    
+    /**
+     * Internal list of test cases.
+     * Use ucx_test_register() to add tests to this list.
+     */
+    UcxTestList *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @return a new test suite
+ */
+UcxTestSuite* ucx_test_suite_new();
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+void ucx_test_suite_free(UcxTestSuite* suite);
+
+/**
+ * Registers a test function with the specified test suite.
+ * 
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return <code>EXIT_SUCCESS</code> on success or
+ * <code>EXIT_FAILURE</code> on failure
+ */
+int ucx_test_register(UcxTestSuite* suite, UcxTest test);
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param outstream the stream the log shall be written to
+ */
+void ucx_test_run(UcxTestSuite* suite, FILE* outstream);
+
+/**
+ * Macro for a #UcxTest function header.
+ * 
+ * Use this macro to declare and/or define a #UcxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_)
+
+/**
+ * Marks the begin of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b>
+ * #UCX_TEST_BEGIN.
+ * 
+ * @see #UCX_TEST_END
+ */
+#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\
+        fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        fwrite("... ", 1, 4, _output_);\
+        jmp_buf _env_; \
+        if (!setjmp(_env_)) {
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \
+        fwrite(message".\n", 1, 2+strlen(message), _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    }
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * UCX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see UCX_TEST_CALL_SUBROUTINE()
+ */
+#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\
+        FILE *_output_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN-
+ * #UCX_TEST_END-block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see UCX_TEST_SUBROUTINE()
+ */
+#define UCX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_env_,__VA_ARGS__);
+
+/**
+ * Marks the end of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b>
+ * #UCX_TEST_END.
+ * 
+ * @see #UCX_TEST_BEGIN
+ */
+#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;}
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_TEST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/ucx.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,204 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * Main UCX Header providing most common definitions.
+ * 
+ * @file   ucx.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_H
+#define	UCX_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   2
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   1
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#ifdef _WIN32
+#if !(defined __ssize_t_defined || defined _SSIZE_T_)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#define __ssize_t_defined
+#define _SSIZE_T_
+#endif /* __ssize_t_defined and _SSIZE_T */
+#else /* !_WIN32 */
+#include <sys/types.h>
+#endif /* _WIN32 */
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+    
+
+/**
+ * A function pointer to a destructor function.
+ * @see ucx_mempool_setdestr()
+ * @see ucx_mempool_regdestr()
+ */
+typedef void(*ucx_destructor)(void*);
+
+/**
+ * Function pointer to a compare function.
+ * 
+ * The compare function shall take three arguments: the two values that shall be
+ * compared and optional additional data.
+ * The function shall then return -1 if the first argument is less than the
+ * second argument, 1 if the first argument is greater than the second argument
+ * and 0 if both arguments are equal. If the third argument is
+ * <code>NULL</code>, it shall be ignored.
+ */
+typedef int(*cmp_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a distance function.
+ * 
+ * The distance function shall take three arguments: the two values for which
+ * the distance shall be computed and optional additional data.
+ * The function shall then return the signed distance as integer value.
+ */
+typedef intmax_t(*distance_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a copy function.
+ * 
+ * The copy function shall create a copy of the first argument and may use
+ * additional data provided by the second argument. If the second argument is
+ * <code>NULL</code>, it shall be ignored.
+
+ * <b>Attention:</b> if pointers returned by functions of this type may be
+ * passed to <code>free()</code> depends on the implementation of the
+ * respective <code>copy_func</code>.
+ */
+typedef void*(*copy_func)(const void*,void*);
+
+/**
+ * Function pointer to a write function.
+ * 
+ * The signature of the write function shall be compatible to the signature
+ * of standard <code>fwrite</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the source,
+ * the length of one element, the element count and a pointer to the
+ * destination.
+ */
+typedef size_t(*write_func)(const void*, size_t, size_t, void*);
+
+/**
+ * Function pointer to a read function.
+ * 
+ * The signature of the read function shall be compatible to the signature
+ * of standard <code>fread</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the
+ * destination, the length of one element, the element count and a pointer to
+ * the source.
+ */
+typedef size_t(*read_func)(void*, size_t, size_t, void*);
+
+
+
+#if __GNUC__ >= 5 || defined(__clang__)
+#define UCX_MUL_BUILTIN
+
+#if __WORDSIZE == 32
+/**
+ * Alias for <code>__builtin_umul_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result)
+#else /* __WORDSIZE != 32 */
+/**
+ * Alias for <code>__builtin_umull_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result)
+#endif /* __WORDSIZE */
+
+#else /* no GNUC or clang bultin */
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+  *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result)
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t where the result should be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+int ucx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ucx/ucx/utils.h	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,508 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+
+/**
+ * @file utils.h
+ * 
+ * Compare, copy and printf functions.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <inttypes.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy().
+ */
+#define UCX_STREAM_COPY_BUFSIZE 4096
+
+/**
+ * Copies a string.
+ * @param s the string to copy
+ * @param data omitted
+ * @return a pointer to a copy of s1 that can be passed to free(void*)
+ */
+void *ucx_strcpy(const void *s, void *data);
+
+/**
+ * Copies a memory area.
+ * @param m a pointer to the memory area
+ * @param n a pointer to the size_t containing the size of the memory area
+ * @return a pointer to a copy of the specified memory area that can
+ * be passed to free(void*)
+ */
+void *ucx_memcpy(const void *m, void *n);
+
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @param n the maximum number of bytes that shall be copied
+ * @return the total number of bytes copied
+  */
+size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc,
+        char* buf, size_t bufsize, size_t n);
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @return total number of bytes copied
+ * 
+ * @see #UCX_STREAM_COPY_BUFSIZE
+ */
+#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1)
+
+/**
+ * Shorthand for ucx_stream_bncopy using a default copy buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n maximum number of bytes that shall be copied
+ * @return total number of bytes copied
+ */
+#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, n)
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @return total number of bytes copied
+ */
+#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        buf, bufsize, (size_t)-1)
+
+/**
+ * Wraps the strcmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param data omitted
+ * @return the result of strcmp(s1, s2)
+ */
+int ucx_cmp_str(const void *s1, const void *s2, void *data);
+
+/**
+ * Wraps the strncmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param n a pointer to the size_t containing the third strncmp parameter
+ * @return the result of strncmp(s1, s2, *n)
+ */
+int ucx_cmp_strn(const void *s1, const void *s2, void *n);
+
+/**
+ * Wraps the sstrcmp function.
+ * @param s1 sstr one
+ * @param s2 sstr two
+ * @param data ignored
+ * @return the result of sstrcmp(s1, s2)
+ */
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data);
+
+/**
+ * Compares two integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two real numbers of type float.
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @param data if provided: a pointer to precision (default: 1e-6f)
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int ucx_cmp_float(const void *f1, const void *f2, void *data);
+
+/**
+ * Compares two real numbers of type double.
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @param data if provided: a pointer to precision (default: 1e-14)
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int ucx_cmp_double(const void *d1, const void *d2, void *data);
+
+/**
+ * Compares two pointers.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param data omitted
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data);
+
+/**
+ * Compares two memory areas.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param n a pointer to the size_t containing the third parameter for memcmp
+ * @return the result of memcmp(ptr1, ptr2, *n)
+ */
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n);
+
+/**
+ * A <code>printf()</code> like function which writes the output to a stream by
+ * using a write_func().
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ */
+int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_fprintf().
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ap argument list
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap);
+
+/**
+ * A <code>printf()</code> like function which allocates space for a sstr_t
+ * the result is written to.
+ * 
+ * <b>Attention</b>: The sstr_t data is allocated with the allocators
+ * ucx_allocator_malloc() function. So it is implementation dependent, if
+ * the returned sstr_t.ptr pointer must be passed to the allocators
+ * ucx_allocator_free() function manually.
+ * 
+ * <b>Note</b>: The sstr_t.ptr of the return value will <i>always</i> be
+ * <code>NULL</code>-terminated.
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return a sstr_t containing the formatted string
+ */
+sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_asprintf().
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ap argument list
+ * @return a sstr_t containing the formatted string
+ * @see ucx_asprintf()
+ */
+sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap);
+
+/** Shortcut for ucx_asprintf() with default allocator. */
+#define ucx_sprintf(...) \
+    ucx_asprintf(ucx_default_allocator(), __VA_ARGS__)
+
+/**
+ * A <code>printf()</code> like function which writes the output to a
+ * UcxBuffer.
+ * 
+ * @param buffer the buffer the data is written to
+ * @param ... format string and additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \
+        (write_func)ucx_buffer_write, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_UTILS_H */
+
--- a/src/ucx/utils.c	Tue Aug 13 22:14:32 2019 +0200
+++ b/src/ucx/utils.c	Sat Sep 24 16:26:10 2022 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2017 Mike Becker, 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:
@@ -26,22 +26,23 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "utils.h"
+#include "ucx/utils.h"
+
 #include <math.h>
 #include <stdio.h>
 #include <limits.h>
 #include <errno.h>
 
 /* COPY FUCNTIONS */
-void* ucx_strcpy(void* s, void* data) {
-    char *str = (char*) s;
+void* ucx_strcpy(const void* s, void* data) {
+    const char *str = (const char*) s;
     size_t n = 1+strlen(str);
     char *cpy = (char*) malloc(n);
     memcpy(cpy, str, n);
     return cpy;
 }
 
-void* ucx_memcpy(void* m, void* n) {
+void* ucx_memcpy(const void* m, void* n) {
     size_t k = *((size_t*)n);
     void *cpy = malloc(k);
     memcpy(cpy, m, k);
@@ -87,17 +88,103 @@
 
 /* COMPARE FUNCTIONS */
 
-int ucx_strcmp(void *s1, void *s2, void *data) {
-    return strcmp((char*)s1, (char*)s2);
+int ucx_cmp_str(const void *s1, const void *s2, void *data) {
+    return strcmp((const char*)s1, (const char*)s2);
+}
+
+int ucx_cmp_strn(const void *s1, const void *s2, void *n) {
+    return strncmp((const char*)s1, (const char*)s2, *((size_t*) n));
+}
+
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data) {
+    sstr_t a = *(const sstr_t*) s1;
+    sstr_t b = *(const sstr_t*) s2;
+    return sstrcmp(a, b);
+}
+
+int ucx_cmp_int(const void *i1, const void *i2, void *data) {
+   int a = *((const int*) i1);
+   int b = *((const int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_longint(const void *i1, const void *i2, void *data) {
+   long int a = *((const long int*) i1);
+   long int b = *((const long int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_longlong(const void *i1, const void *i2, void *data) {
+   long long a = *((const long long*) i1);
+   long long b = *((const long long*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
 }
 
-int ucx_strncmp(void *s1, void *s2, void *n) {
-    return strncmp((char*)s1, (char*)s2, *((size_t*) n));
+int ucx_cmp_int16(const void *i1, const void *i2, void *data) {
+   int16_t a = *((const int16_t*) i1);
+   int16_t b = *((const int16_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_int32(const void *i1, const void *i2, void *data) {
+   int32_t a = *((const int32_t*) i1);
+   int32_t b = *((const int32_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
 }
 
-int ucx_intcmp(void *i1, void *i2, void *data) {
-   int a = *((int*) i1);
-   int b = *((int*) i2);
+int ucx_cmp_int64(const void *i1, const void *i2, void *data) {
+   int64_t a = *((const int64_t*) i1);
+   int64_t b = *((const int64_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint(const void *i1, const void *i2, void *data) {
+   unsigned int a = *((const unsigned int*) i1);
+   unsigned int b = *((const unsigned int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) {
+   unsigned long int a = *((const unsigned long int*) i1);
+   unsigned long int b = *((const unsigned long int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) {
+   unsigned long long a = *((const unsigned long long*) i1);
+   unsigned long long b = *((const unsigned long long*) i2);
    if (a == b) {
        return 0;
    } else {
@@ -105,9 +192,111 @@
    }
 }
 
-int ucx_floatcmp(void *f1, void *f2, void *epsilon) {
-   float a = *((float*) f1);
-   float b = *((float*) f2);
+int ucx_cmp_uint16(const void *i1, const void *i2, void *data) {
+   uint16_t a = *((const uint16_t*) i1);
+   uint16_t b = *((const uint16_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint32(const void *i1, const void *i2, void *data) {
+   uint32_t a = *((const uint32_t*) i1);
+   uint32_t b = *((const uint32_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint64(const void *i1, const void *i2, void *data) {
+   uint64_t a = *((const uint64_t*) i1);
+   uint64_t b = *((const uint64_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int*) i1);
+   intmax_t b = *((const int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long int*) i1);
+   intmax_t b = *((const long int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long long*) i1);
+   intmax_t b = *((const long long*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int16_t*) i1);
+   intmax_t b = *((const int16_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int32_t*) i1);
+   intmax_t b = *((const int32_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int64_t*) i1);
+   intmax_t b = *((const int64_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned int*) i1);
+   uintmax_t b = *((const unsigned int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long int*) i1);
+   uintmax_t b = *((const unsigned long int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long long*) i1);
+   uintmax_t b = *((const unsigned long long*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint16_t*) i1);
+   uintmax_t b = *((const uint16_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint32_t*) i1);
+   uintmax_t b = *((const uint32_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint64_t*) i1);
+   uintmax_t b = *((const uint64_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) {
+   float a = *((const float*) f1);
+   float b = *((const float*) f2);
    float e = !epsilon ? 1e-6f : *((float*)epsilon);
    if (fabsf(a - b) < e) {
        return 0;
@@ -116,9 +305,9 @@
    }
 }
 
-int ucx_doublecmp(void *d1, void *d2, void *epsilon) {
-   double a = *((float*) d1);
-   double b = *((float*) d2);
+int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) {
+   double a = *((const double*) d1);
+   double b = *((const double*) d2);
    double e = !epsilon ? 1e-14 : *((double*)epsilon);
    if (fabs(a - b) < e) {
        return 0;
@@ -127,9 +316,9 @@
    }
 }
 
-int ucx_ptrcmp(void *ptr1, void *ptr2, void *data) {
-    intptr_t p1 = (intptr_t) ptr1;
-    intptr_t p2 = (intptr_t) ptr2;
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) {
+    const intptr_t p1 = (const intptr_t) ptr1;
+    const intptr_t p2 = (const intptr_t) ptr2;
     if (p1 == p2) {
         return 0;
     } else {
@@ -137,7 +326,7 @@
     }
 }
 
-int ucx_memcmp(void *ptr1, void *ptr2, void *n) {
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) {
     return memcmp(ptr1, ptr2, *((size_t*)n));
 }
 
--- a/src/ucx/utils.h	Tue Aug 13 22:14:32 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2016 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.
- */
-
-/**
- * @file utils.h
- * 
- * Compare, copy and printf functions.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_UTILS_H
-#define UCX_UTILS_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "ucx.h"
-#include "string.h"
-#include "allocator.h"
-#include <inttypes.h>
-#include <string.h>
-#include <stdarg.h>
-
-/**
- * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy().
- */
-#define UCX_STREAM_COPY_BUFSIZE 4096
-
-/**
- * Copies a string.
- * @param s the string to copy
- * @param data omitted
- * @return a pointer to a copy of s1 that can be passed to free(void*)
- */
-void *ucx_strcpy(void *s, void *data);
-
-/**
- * Copies a memory area.
- * @param m a pointer to the memory area
- * @param n a pointer to the size_t containing the size of the memory area
- * @return a pointer to a copy of the specified memory area that can
- * be passed to free(void*)
- */
-void *ucx_memcpy(void *m, void *n);
-
-
-/**
- * Reads data from a stream and writes it to another stream.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if <code>NULL</code> was
- * provided for <code>buf</code>, this is the size of the buffer that shall be
- * implicitly created
- * @param n the maximum number of bytes that shall be copied
- * @return the total number of bytes copied
-  */
-size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc,
-        char* buf, size_t bufsize, size_t n);
-
-/**
- * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @return total number of bytes copied
- * 
- * @see #UCX_STREAM_COPY_BUFSIZE
- */
-#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1)
-
-/**
- * Shorthand for ucx_stream_bncopy using a default copy buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param n maximum number of bytes that shall be copied
- * @return total number of bytes copied
- */
-#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        NULL, UCX_STREAM_COPY_BUFSIZE, n)
-
-/**
- * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if <code>NULL</code> was
- * provided for <code>buf</code>, this is the size of the buffer that shall be
- * implicitly created
- * @return total number of bytes copied
- */
-#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        buf, bufsize, (size_t)-1)
-
-/**
- * Wraps the strcmp function.
- * @param s1 string one
- * @param s2 string two
- * @param data omitted
- * @return the result of strcmp(s1, s2)
- */
-int ucx_strcmp(void *s1, void *s2, void *data);
-
-/**
- * Wraps the strncmp function.
- * @param s1 string one
- * @param s2 string two
- * @param n a pointer to the size_t containing the third strncmp parameter
- * @return the result of strncmp(s1, s2, *n)
- */
-int ucx_strncmp(void *s1, void *s2, void *n);
-
-/**
- * Compares two integers of type int.
- * @param i1 pointer to integer one
- * @param i2 pointer to integer two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_intcmp(void *i1, void *i2, void *data);
-
-/**
- * Compares two real numbers of type float.
- * @param f1 pointer to float one
- * @param f2 pointer to float two
- * @param data if provided: a pointer to precision (default: 1e-6f)
- * @return -1, if *f1 is less than *f2, 0 if both are equal,
- * 1 if *f1 is greater than *f2
- */
-
-int ucx_floatcmp(void *f1, void *f2, void *data);
-
-/**
- * Compares two real numbers of type double.
- * @param d1 pointer to double one
- * @param d2 pointer to double two
- * @param data if provided: a pointer to precision (default: 1e-14)
- * @return -1, if *d1 is less than *d2, 0 if both are equal,
- * 1 if *d1 is greater than *d2
- */
-int ucx_doublecmp(void *d1, void *d2, void *data);
-
-/**
- * Compares two pointers.
- * @param ptr1 pointer one
- * @param ptr2 pointer two
- * @param data omitted
- * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
- * 1 if ptr1 is greater than ptr2
- */
-int ucx_ptrcmp(void *ptr1, void *ptr2, void *data);
-
-/**
- * Compares two memory areas.
- * @param ptr1 pointer one
- * @param ptr2 pointer two
- * @param n a pointer to the size_t containing the third parameter for memcmp
- * @return the result of memcmp(ptr1, ptr2, *n)
- */
-int ucx_memcmp(void *ptr1, void *ptr2, void *n);
-
-/**
- * A <code>printf()</code> like function which writes the output to a stream by
- * using a write_func().
- * @param stream the stream the data is written to
- * @param wfc the write function
- * @param fmt format string
- * @param ... additional arguments
- * @return the total number of bytes written
- */
-int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...);
-
-/**
- * <code>va_list</code> version of ucx_fprintf().
- * @param stream the stream the data is written to
- * @param wfc the write function
- * @param fmt format string
- * @param ap argument list
- * @return the total number of bytes written
- * @see ucx_fprintf()
- */
-int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap);
-
-/**
- * A <code>printf()</code> like function which allocates space for a sstr_t
- * the result is written to.
- * 
- * <b>Attention</b>: The sstr_t data is allocated with the allocators
- * ucx_allocator_malloc() function. So it is implementation dependent, if
- * the returned sstr_t.ptr pointer must be passed to the allocators
- * ucx_allocator_free() function manually.
- * 
- * <b>Note</b>: The sstr_t.ptr of the return value will <i>always</i> be
- * <code>NULL</code>-terminated.
- * 
- * @param allocator the UcxAllocator used for allocating the result sstr_t
- * @param fmt format string
- * @param ... additional arguments
- * @return a sstr_t containing the formatted string
- */
-sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...);
-
-/**
- * <code>va_list</code> version of ucx_asprintf().
- * 
- * @param allocator the UcxAllocator used for allocating the result sstr_t
- * @param fmt format string
- * @param ap argument list
- * @return a sstr_t containing the formatted string
- * @see ucx_asprintf()
- */
-sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap);
-
-/** Shortcut for ucx_asprintf() with default allocator. */
-#define ucx_sprintf(...) \
-    ucx_asprintf(ucx_default_allocator(), __VA_ARGS__)
-
-/**
- * A <code>printf()</code> like function which writes the output to a
- * UcxBuffer.
- * 
- * @param buffer the buffer the data is written to
- * @param ... format string and additional arguments
- * @return the total number of bytes written
- * @see ucx_fprintf()
- */
-#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \
-        (write_func)ucx_buffer_write, __VA_ARGS__)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* UCX_UTILS_H */
-
--- a/templates/config/init.conf	Tue Aug 13 22:14:32 2019 +0200
+++ b/templates/config/init.conf	Sat Sep 24 16:26:10 2022 +0200
@@ -3,4 +3,6 @@
 #
 
 Init fn="admin-init"
+Init fn="webdav-init"
 
+
--- a/templates/config/server.template	Tue Aug 13 22:14:32 2019 +0200
+++ b/templates/config/server.template	Sat Sep 24 16:26:10 2022 +0200
@@ -2,46 +2,54 @@
 # server.conf
 #
 
-<Runtime>
+Runtime
+{
 	Temp        /tmp/webserver-rw6pgl8b/
 	User        webservd
 	MimeFile    mime.types
-</Runtime>
+}
 
-<LogFile>
+LogFile
+{
 	File        logs/errors
 	Level       INFO
-</LogFile>
+}
 
-<AccessLog>
+AccessLog
+{
 	File        logs/access
-</AccessLog>
+}
 
-<EventHandler>
+EventHandler
+{
     Name        default
     Threads     1
     Default     true
-</EventHandler>
+}
 
-<Threadpool>
+Threadpool
+{
     Name        default
     MinThreads  4
     MaxThreads  32
-</Threadpool>
+}
 
-<Listener>
+Listener
+{
     Name        http-listener-1
     Port        9090
     DefaultVS   %%WS_HOST%%
-</Listener>
+}
 
-<Listener>
+Listener
+{
     Name        http-listener-2
     Port        9091
     DefaultVS   %%WS_HOST%%
-</Listener>
+}
 
-<VirtualServer>
+VirtualServer
+{
 	Name        %%WS_HOST%%
 	Host        %%WS_HOST%%
     Listener    http-listener-1
@@ -50,6 +58,9 @@
     ACLFile     acl.conf
     DAVFile     dav.conf
     DocRoot     docs/ 
-</VirtualServer>
+}
 
 
+
+
+

mercurial