add existing code (build system, libs, initial mizucp code)
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 5 Jun 2021 07:39:33 +0000 (09:39 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 5 Jun 2021 07:39:33 +0000 (09:39 +0200)
172 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
configure [new file with mode: 0755]
libidav/Makefile [new file with mode: 0644]
libidav/crypto.c [new file with mode: 0644]
libidav/crypto.h [new file with mode: 0644]
libidav/davqlexec.c [new file with mode: 0644]
libidav/davqlexec.h [new file with mode: 0644]
libidav/davqlparser.c [new file with mode: 0644]
libidav/davqlparser.h [new file with mode: 0644]
libidav/methods.c [new file with mode: 0644]
libidav/methods.h [new file with mode: 0644]
libidav/resource.c [new file with mode: 0644]
libidav/resource.h [new file with mode: 0644]
libidav/session.c [new file with mode: 0644]
libidav/session.h [new file with mode: 0644]
libidav/utils.c [new file with mode: 0644]
libidav/utils.h [new file with mode: 0644]
libidav/versioning.c [new file with mode: 0644]
libidav/versioning.h [new file with mode: 0644]
libidav/webdav.c [new file with mode: 0644]
libidav/webdav.h [new file with mode: 0644]
libidav/xml.c [new file with mode: 0644]
libidav/xml.h [new file with mode: 0644]
make/Makefile.mk [new file with mode: 0644]
make/clang.mk [new file with mode: 0644]
make/configure.vm [new file with mode: 0644]
make/gcc.mk [new file with mode: 0644]
make/mingw.mk [new file with mode: 0644]
make/osx.mk [new file with mode: 0644]
make/package_unix.sh [new file with mode: 0755]
make/project.xml [new file with mode: 0644]
make/suncc.mk [new file with mode: 0644]
make/toolchain.sh [new file with mode: 0644]
make/windows.mk [new file with mode: 0644]
mizucp/Makefile [new file with mode: 0644]
mizucp/main.c [new file with mode: 0644]
mizucp/main.h [new file with mode: 0644]
mizunara/Makefile [new file with mode: 0644]
mizunara/main.c [new file with mode: 0644]
resource/.DS_Store [new file with mode: 0644]
resource/locales/de_DE.properties [new file with mode: 0644]
resource/locales/en_EN.properties [new file with mode: 0644]
resource/template.app/Contents/Info.plist [new file with mode: 0644]
resource/template.app/Contents/PkgInfo [new file with mode: 0644]
resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings [new file with mode: 0644]
resource/template.app/Contents/Resources/English.lproj/MainMenu.nib [new file with mode: 0644]
ucx/Makefile [new file with mode: 0644]
ucx/README [new file with mode: 0644]
ucx/allocator.c [new file with mode: 0644]
ucx/array.c [new file with mode: 0644]
ucx/avl.c [new file with mode: 0644]
ucx/buffer.c [new file with mode: 0644]
ucx/list.c [new file with mode: 0644]
ucx/logging.c [new file with mode: 0644]
ucx/map.c [new file with mode: 0644]
ucx/mempool.c [new file with mode: 0644]
ucx/properties.c [new file with mode: 0644]
ucx/stack.c [new file with mode: 0644]
ucx/string.c [new file with mode: 0644]
ucx/test.c [new file with mode: 0644]
ucx/ucx.c [new file with mode: 0644]
ucx/ucx/allocator.h [new file with mode: 0644]
ucx/ucx/array.h [new file with mode: 0644]
ucx/ucx/avl.h [new file with mode: 0644]
ucx/ucx/buffer.h [new file with mode: 0644]
ucx/ucx/list.h [new file with mode: 0644]
ucx/ucx/logging.h [new file with mode: 0644]
ucx/ucx/map.h [new file with mode: 0644]
ucx/ucx/mempool.h [new file with mode: 0644]
ucx/ucx/properties.h [new file with mode: 0644]
ucx/ucx/stack.h [new file with mode: 0644]
ucx/ucx/string.h [new file with mode: 0644]
ucx/ucx/test.h [new file with mode: 0644]
ucx/ucx/ucx.h [new file with mode: 0644]
ucx/ucx/utils.h [new file with mode: 0644]
ucx/utils.c [new file with mode: 0644]
ui/Makefile [new file with mode: 0644]
ui/common/context.c [new file with mode: 0644]
ui/common/context.h [new file with mode: 0644]
ui/common/document.c [new file with mode: 0644]
ui/common/document.h [new file with mode: 0644]
ui/common/object.c [new file with mode: 0644]
ui/common/object.h [new file with mode: 0644]
ui/common/objs.mk [new file with mode: 0644]
ui/common/properties.c [new file with mode: 0644]
ui/common/properties.h [new file with mode: 0644]
ui/common/types.c [new file with mode: 0644]
ui/common/types.h [new file with mode: 0644]
ui/gtk/Makefile [new file with mode: 0644]
ui/gtk/button.c [new file with mode: 0644]
ui/gtk/button.h [new file with mode: 0644]
ui/gtk/container.c [new file with mode: 0644]
ui/gtk/container.h [new file with mode: 0644]
ui/gtk/display.c [new file with mode: 0644]
ui/gtk/display.h [new file with mode: 0644]
ui/gtk/dnd.c [new file with mode: 0644]
ui/gtk/dnd.h [new file with mode: 0644]
ui/gtk/draw_cairo.c [new file with mode: 0644]
ui/gtk/draw_cairo.h [new file with mode: 0644]
ui/gtk/draw_gdk.c [new file with mode: 0644]
ui/gtk/draw_gdk.h [new file with mode: 0644]
ui/gtk/entry.c [new file with mode: 0644]
ui/gtk/entry.h [new file with mode: 0644]
ui/gtk/graphics.c [new file with mode: 0644]
ui/gtk/graphics.h [new file with mode: 0644]
ui/gtk/image.c [new file with mode: 0644]
ui/gtk/image.h [new file with mode: 0644]
ui/gtk/menu.c [new file with mode: 0644]
ui/gtk/menu.h [new file with mode: 0644]
ui/gtk/model.c [new file with mode: 0644]
ui/gtk/model.h [new file with mode: 0644]
ui/gtk/objs.mk [new file with mode: 0644]
ui/gtk/range.c [new file with mode: 0644]
ui/gtk/range.h [new file with mode: 0644]
ui/gtk/text.c [new file with mode: 0644]
ui/gtk/text.h [new file with mode: 0644]
ui/gtk/toolbar.c [new file with mode: 0644]
ui/gtk/toolbar.h [new file with mode: 0644]
ui/gtk/toolkit.c [new file with mode: 0644]
ui/gtk/toolkit.h [new file with mode: 0644]
ui/gtk/tree.c [new file with mode: 0644]
ui/gtk/tree.h [new file with mode: 0644]
ui/gtk/window.c [new file with mode: 0644]
ui/motif/Makefile [new file with mode: 0644]
ui/motif/button.c [new file with mode: 0644]
ui/motif/button.h [new file with mode: 0644]
ui/motif/container.c [new file with mode: 0644]
ui/motif/container.h [new file with mode: 0644]
ui/motif/dnd.c [new file with mode: 0644]
ui/motif/dnd.h [new file with mode: 0644]
ui/motif/graphics.c [new file with mode: 0644]
ui/motif/graphics.h [new file with mode: 0644]
ui/motif/image.c [new file with mode: 0644]
ui/motif/image.h [new file with mode: 0644]
ui/motif/label.c [new file with mode: 0644]
ui/motif/label.h [new file with mode: 0644]
ui/motif/list.c [new file with mode: 0644]
ui/motif/list.h [new file with mode: 0644]
ui/motif/menu.c [new file with mode: 0644]
ui/motif/menu.h [new file with mode: 0644]
ui/motif/objs.mk [new file with mode: 0644]
ui/motif/range.c [new file with mode: 0644]
ui/motif/range.h [new file with mode: 0644]
ui/motif/stock.c [new file with mode: 0644]
ui/motif/stock.h [new file with mode: 0644]
ui/motif/text.c [new file with mode: 0644]
ui/motif/text.h [new file with mode: 0644]
ui/motif/toolbar.c [new file with mode: 0644]
ui/motif/toolbar.h [new file with mode: 0644]
ui/motif/toolkit.c [new file with mode: 0644]
ui/motif/toolkit.h [new file with mode: 0644]
ui/motif/tree.c [new file with mode: 0644]
ui/motif/tree.h [new file with mode: 0644]
ui/motif/window.c [new file with mode: 0644]
ui/ui/button.h [new file with mode: 0644]
ui/ui/container.h [new file with mode: 0644]
ui/ui/display.h [new file with mode: 0644]
ui/ui/dnd.h [new file with mode: 0644]
ui/ui/entry.h [new file with mode: 0644]
ui/ui/graphics.h [new file with mode: 0644]
ui/ui/image.h [new file with mode: 0644]
ui/ui/menu.h [new file with mode: 0644]
ui/ui/properties.h [new file with mode: 0644]
ui/ui/range.h [new file with mode: 0644]
ui/ui/stock.h [new file with mode: 0644]
ui/ui/text.h [new file with mode: 0644]
ui/ui/toolbar.h [new file with mode: 0644]
ui/ui/toolkit.h [new file with mode: 0644]
ui/ui/tree.h [new file with mode: 0644]
ui/ui/ui.h [new file with mode: 0644]
ui/ui/window.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5a5d528
--- /dev/null
@@ -0,0 +1,2 @@
+build
+config.mk
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7162bba
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,38 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+all: config.mk
+       $(MAKE) -f make/Makefile.mk
+
+config.mk:
+       ./configure
+
+clean: FORCE
+       rm -fR build/*
+
+FORCE:
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..44f8e8a
--- /dev/null
+++ b/configure
@@ -0,0 +1,728 @@
+#!/bin/sh
+
+
+PREFIX=/usr
+EPREFIX=$PREFIX
+
+BINDIR=
+SBINDIR=
+LIBDIR=
+LIBEXECDIR=
+DATADIR=
+SYSCONFDIR=
+SHAREDSTATEDIR=
+LOCALSTATEDIR=
+INCLUDEDIR=
+INFODIR=
+MANDIR=
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+       echo "Cannot create tmp dir"
+       echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+
+# help text
+printhelp()
+{
+       echo "Usage: $0 [OPTIONS]..."
+       cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+
+Options:
+  --toolkit=(gtk3|motif)
+
+__EOF__
+}
+
+#
+# parse arguments 
+#
+for ARG in $@
+do
+    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 ;;
+       "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+               "-"*) echo "unknown option: $ARG"; exit 1 ;;
+       esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+       BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+       SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+       LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+       LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+       DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+       SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+       SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+       LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+       INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+       INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+       MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+       PLATFORM_NAME=$p
+       break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+
+PREFIX=$PREFIX
+EPREFIX=$EPREFIX
+
+BINDIR=$BINDIR
+SBINDIR=$SBINDIR
+LIBDIR=$LIBDIR
+LIBEXECDIR=$LIBEXECDIR
+DATADIR=$DATADIR
+SYSCONFDIR=$SYSCONFDIR
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+LOCALSTATEDIR=$LOCALSTATEDIR
+INCLUDEDIR=$INCLUDEDIR
+INFODIR=$INFODIR
+MANDIR=$MANDIR
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+dependency_curl()
+{
+    printf "checking for curl... "
+    # dependency curl platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        CFLAGS="$CFLAGS -I/mingw/include"    
+        LDFLAGS="$LDFLAGS -lcurl"    
+               echo yes
+        return 0
+    done
+       
+    # dependency curl platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        curl-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `curl-config --cflags`"
+        else
+            break
+        fi
+        curl-config --ldflags > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `curl-config --ldflags`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency curl 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG libcurl
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libcurl`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libcurl`"
+               echo yes
+        return 0
+    done
+       
+    # dependency curl 
+    while true
+    do
+        curl-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `curl-config --cflags`"
+        else
+            break
+        fi
+        curl-config --ldflags > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `curl-config --ldflags`"
+        else
+            break
+        fi
+        which curl-config > /dev/null
+        if [ $? -ne 0 ]; then
+               break
+        fi
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_gtk3()
+{
+    printf "checking for gtk3... "
+    # dependency gtk3 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG gtk+-3.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-3.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-3.0`"
+        CFLAGS="$CFLAGS -DUI_GTK3"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_openssl()
+{
+    printf "checking for openssl... "
+    # dependency openssl platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -lssl -lcrypto"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -framework CoreFoundation"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl platform="bsd"
+    while true
+    do
+       if isnotplatform "bsd"; then
+            break
+        fi
+               if isplatform "macos"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -lssl -lcrypto"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG openssl
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags openssl`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs openssl`"
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_motif()
+{
+    printf "checking for motif... "
+    # dependency motif 
+    while true
+    do
+        CFLAGS="$CFLAGS -DUI_MOTIF"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_libxml2()
+{
+    printf "checking for libxml2... "
+    # dependency libxml2 platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG libxml-2.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libxml-2.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libxml-2.0`"
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 
+    while true
+    do
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# general dependencies
+CFLAGS=
+LDFLAGS=
+while true
+do
+    if isnotplatform "unix"; then
+        break
+    fi
+    if isplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+               cat >> $TEMP_DIR/make.mk << __EOF__
+OBJ_EXT = o
+LIB_EXT = a
+PACKAGE_SCRIPT = package_unix.sh
+
+__EOF__
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    while true
+    do
+        
+        LDFLAGS="$LDFLAGS -lpthread"    
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    if isnotplatform "bsd"; then
+        break
+    fi
+    if isplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+        CFLAGS="$CFLAGS -I/usr/local/include"    
+        LDFLAGS="$LDFLAGS -L/usr/local/lib"    
+        
+        break
+    done
+    
+    break
+done
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# OPTION VALUES
+#
+checkopt_toolkit_gtk3()
+{
+       VERR=0
+       dependency_gtk3
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+
+__EOF__
+       return 0
+}
+checkopt_toolkit_motif()
+{
+       VERR=0
+       dependency_motif
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = motif
+
+__EOF__
+       return 0
+}
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+# Target: tk
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+
+# Features
+
+# Option: --toolkit
+if [ -z $OPT_TOOLKIT ]; then
+       SAVED_ERROR=$ERROR
+       SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+       ERROR=0
+       while true
+       do
+               checkopt_toolkit_motif
+               if [ $? -eq 0 ]; then
+                       echo "  toolkit: motif" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               checkopt_toolkit_gtk3
+               if [ $? -eq 0 ]; then
+                       echo "  toolkit: gtk3" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               break
+       done
+       if [ $ERROR -ne 0 ]; then
+               SAVED_ERROR=1
+       fi
+       ERROR=$SAVED_ERROR
+       DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+       if false; then
+               false
+       elif [ $OPT_TOOLKIT = "gtk3" ]; then
+               echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+               checkopt_toolkit_gtk3
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       elif [ $OPT_TOOLKIT = "motif" ]; then
+               echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+               checkopt_toolkit_motif
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       fi
+fi
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "TK_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "TK_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "TK_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+# Target: dav
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+dependency_curl
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED curl "
+       ERROR=1
+fi
+dependency_libxml2
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libxml2 "
+       ERROR=1
+fi
+dependency_openssl
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED openssl "
+       ERROR=1
+fi
+
+# Features
+
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "DAV_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "DAV_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "DAV_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+if [ $ERROR -ne 0 ]; then
+       echo
+       echo "Error: Unresolved dependencies"
+       echo $DEPENDENCIES_FAILED
+       rm -Rf $TEMP_DIR
+       exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "Options:"
+cat $TEMP_DIR/options
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
diff --git a/libidav/Makefile b/libidav/Makefile
new file mode 100644 (file)
index 0000000..659d0c5
--- /dev/null
@@ -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.
+#
+
+BUILD_ROOT=../
+include ../config.mk
+
+# list of source files
+SRC  = webdav.c
+SRC += session.c
+SRC += resource.c
+SRC += methods.c
+SRC += utils.c
+SRC += davqlparser.c
+SRC += davqlexec.c
+SRC += crypto.c
+SRC += xml.c
+SRC += versioning.c
+
+OBJ = $(SRC:%.c=../build/libidav/%.$(OBJ_EXT))
+
+all: ../build/ucx ../build/lib/libidav.$(LIB_EXT)
+
+../build/lib/libidav.$(LIB_EXT): $(OBJ)
+       $(AR) $(ARFLAGS) $(AOFLAGS)../build/lib/libidav.$(LIB_EXT) $(OBJ)
+
+../build/libidav/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(DAV_CFLAGS) -c -I../ucx -o $@ $<
+
diff --git a/libidav/crypto.c b/libidav/crypto.c
new file mode 100644 (file)
index 0000000..e9d25c3
--- /dev/null
@@ -0,0 +1,1526 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include "utils.h"
+
+#include "crypto.h"
+
+/* -------------------- OpenSSL Crypto Functions -------------------- */
+#ifdef DAV_USE_OPENSSL
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+
+static EVP_CIPHER_CTX* create_evp_cipher_ctx() {
+    EVP_CIPHER_CTX *ctx = malloc(sizeof(EVP_CIPHER_CTX));
+    EVP_CIPHER_CTX_init(ctx);
+    return ctx;
+}
+
+static void free_evp_cipher_ctx(EVP_CIPHER_CTX *ctx) {
+    EVP_CIPHER_CTX_cleanup(ctx);
+    free(ctx);
+}
+
+#define EVP_CIPHER_CTX_new() create_evp_cipher_ctx()
+#define EVP_CIPHER_CTX_free(ctx) free_evp_cipher_ctx(ctx)
+
+#endif
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    return !RAND_bytes(buf, len);
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    SHA256_Init(&dec->sha256);
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+    
+    return dec;
+}
+
+void aes_decrypter_init(AESDecrypter *dec) {
+    //EVP_CIPHER_CTX_init(&dec->ctx);
+    dec->ctx = EVP_CIPHER_CTX_new();
+    dec->init = 1;
+    if(dec->key->type == DAV_KEY_AES128) {
+        EVP_DecryptInit_ex(
+                dec->ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                dec->key->data,
+                dec->ivtmp);
+    } else if(dec->key->type == DAV_KEY_AES256) {
+        EVP_DecryptInit_ex(
+                dec->ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                dec->key->data,
+                dec->ivtmp);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        size_t n = 16 - dec->ivpos;
+        size_t cp = n > len ? len : n;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    int outlen = len + 16;
+    unsigned char *out = malloc(outlen);
+    EVP_DecryptUpdate(dec->ctx, out, &outlen, buf, len);
+    ssize_t wlen = dec->write(out, 1, outlen, dec->stream);
+    SHA256_Update(&dec->sha256, out, wlen);
+    free(out);
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init) {
+        void *out = malloc(128);
+        int len = 0;
+        EVP_DecryptFinal_ex(dec->ctx, out, &len);
+        dec->write(out, 1, len, dec->stream);
+        SHA256_Update(&dec->sha256, out, len);
+        free(out);
+        //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+        EVP_CIPHER_CTX_free(dec->ctx);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    free(dec);
+}
+
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(!RAND_bytes(iv, 16)) {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+    SHA256_Init(&enc->sha256);
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 16;
+    
+    //EVP_CIPHER_CTX_init(&enc->ctx);
+    enc->ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_EncryptInit_ex(enc->ctx, EVP_aes_128_cbc(), NULL, key->data, enc->iv);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_EncryptInit_ex(enc->ctx, EVP_aes_256_cbc(), NULL, key->data, enc->iv);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    if(enc->tmp) {
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    void *in = malloc(len);
+    size_t in_len = enc->read(in, 1, len, enc->stream);
+    
+    SHA256_Update(&enc->sha256, in, in_len);
+    
+    unsigned char *out = NULL;
+    int outlen = 0;
+    size_t ivl = enc->ivlen;
+    if(in_len != 0) {
+        outlen = len + 32;
+        out = malloc(outlen + ivl);
+        if(ivl > 0) {
+            memcpy(out, enc->iv, ivl);
+        }
+        EVP_EncryptUpdate(enc->ctx, out + ivl, &outlen, in, in_len);
+        if(in_len != len) {
+            int newoutlen = 16;
+            EVP_EncryptFinal_ex(enc->ctx, out + ivl + outlen, &newoutlen);
+            outlen += newoutlen;
+            enc->end = 1;
+        }
+    } else {
+        out = malloc(16);
+        EVP_EncryptFinal_ex(enc->ctx, out, &outlen);
+        enc->end = 1;
+    }
+    enc->tmp = (char*)out;
+    enc->tmplen = outlen + ivl;
+    enc->tmpoff = 0;
+    
+    if(enc->ivlen > 0) {
+        enc->ivlen = 0;
+    }
+    
+    free(in);
+    
+    return aes_read(buf, s, n, enc);
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    if(enc->tmp) {
+        free(enc->tmp);
+    }
+    if(enc->iv) {
+        free(enc->iv);
+    }
+    //EVP_CIPHER_CTX_cleanup(&enc->ctx);
+    EVP_CIPHER_CTX_free(enc->ctx);
+    free(enc);
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 16;
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    unsigned char iv[16];
+    if(!RAND_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    //EVP_CIPHER_CTX ctx;
+    //EVP_CIPHER_CTX_init(&ctx);
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_EncryptInit_ex(
+                ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                (unsigned char*)key->data,
+                iv);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_EncryptInit_ex(
+                ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                (unsigned char*)key->data,
+                iv);
+    } else {
+        //EVP_CIPHER_CTX_cleanup(&ctx);
+        EVP_CIPHER_CTX_free(ctx);
+        return NULL;
+    }
+    
+    //int len = strlen(in);
+    int buflen = len + 64;
+    unsigned char *buf = calloc(1, buflen);
+    memcpy(buf, iv, 16);
+    
+    int l = buflen - 16;
+    EVP_EncryptUpdate(ctx, buf + 16, &l, (unsigned char*)in, len);
+    
+    int f = 0;
+    EVP_EncryptFinal_ex(ctx, buf + 16 + l, &f);
+    char *out = util_base64encode((char*)buf, 16 + l + f);
+    free(buf);
+    EVP_CIPHER_CTX_free(ctx);
+    //EVP_CIPHER_CTX_cleanup(&ctx);
+    
+    return out;
+}
+
+char* aes_decrypt(const char *in, size_t *length, DavKey *key) {
+    int len;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &len);
+    
+    //EVP_CIPHER_CTX ctx;
+    //EVP_CIPHER_CTX_init(&ctx);
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_DecryptInit_ex(
+                ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                key->data,
+                buf);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_DecryptInit_ex(
+                ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                key->data,
+                buf);
+    } else {
+        //EVP_CIPHER_CTX_cleanup(&ctx);
+        EVP_CIPHER_CTX_free(ctx);
+        return NULL;
+    }
+    
+    unsigned char *out = malloc(len + 1);
+    int outlen = len;
+    unsigned char *in_buf = buf + 16;
+    int inlen = len - 16;
+    int f = 0; 
+    
+    EVP_DecryptUpdate(ctx, out, &outlen, in_buf, inlen);
+    EVP_DecryptFinal_ex(ctx, out + outlen, &f);
+    out[outlen + f] = '\0';
+    free(buf);
+    //EVP_CIPHER_CTX_cleanup(&ctx);
+    EVP_CIPHER_CTX_free(ctx);
+    
+    *length = outlen + f;
+    return (char*)out;
+}
+
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf){
+    SHA256_Final((unsigned char*)buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    DAV_SHA_CTX ctx;
+    SHA256_Init(&ctx);
+    SHA256_Update(&ctx, data, len);
+    SHA256_Final(hash, &ctx);
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    SHA256_Init(ctx);
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    SHA256_Final(buf, ctx);
+    free(ctx);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int crypto_pw2key_error = 0;
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!crypto_pw2key_error) {
+        fprintf(stderr, "Error: password key derivation not supported on this platform: openssl to old\n");
+        crypto_pw2key_error = 1;
+    }
+    return 0;
+}
+
+#else
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[32];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    // generate key
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: {
+            PKCS5_PBKDF2_HMAC(
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    EVP_sha256(),
+                    keylen,
+                    keydata);
+            break;
+        }
+        case DAV_PWFUNC_PBKDF2_SHA512: {
+            PKCS5_PBKDF2_HMAC(
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    EVP_sha512(),
+                    keylen,
+                    keydata);
+            break;
+        }
+        default: return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+#endif
+
+#endif
+
+
+/* -------------------- Apple Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_COMMON_CRYPTO
+
+#define RANDOM_BUFFER_LENGTH 256
+static char randbuf[RANDOM_BUFFER_LENGTH];
+static int rbufpos = RANDOM_BUFFER_LENGTH;
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    if(len + rbufpos > RANDOM_BUFFER_LENGTH) {
+        int devr = open("/dev/urandom", O_RDONLY);
+        if(devr == -1) {
+            return 1;
+        }
+        
+        if(read(devr, randbuf, RANDOM_BUFFER_LENGTH) < RANDOM_BUFFER_LENGTH) {
+            close(devr);
+            return 1;
+        }
+        
+        rbufpos = 0;
+        if(len > RANDOM_BUFFER_LENGTH) {
+            int err = 0;
+            if(read(devr, buf, len) < len) {
+                err = 1;
+            }
+            close(devr);
+            return err;
+        }
+        
+        close(devr);
+    }
+    
+    char *r = randbuf;
+    memcpy(buf, r + rbufpos, len);
+    rbufpos += len;
+    
+    return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    CC_SHA256_Init(&dec->sha256);
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+    
+    return dec;
+}
+
+
+void aes_decrypter_init(AESDecrypter *dec) {
+    //EVP_CIPHER_CTX_init(&dec->ctx);
+    dec->init = 1;
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(dec->key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+    } else if(dec->key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+    dec->ctx = cryptor;
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        size_t n = 16 - dec->ivpos;
+        size_t cp = n > len ? len : n;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    int outlen = len + 16;
+    unsigned char *out = malloc(outlen);
+    
+    CCCryptorStatus status;
+    size_t avail = outlen;
+    size_t moved = 0;
+    status = CCCryptorUpdate(dec->ctx, buf, len, out, avail, &moved);
+    
+    ssize_t wlen = dec->write(out, 1, moved, dec->stream);
+    CC_SHA256_Update(&dec->sha256, out, wlen);
+    free(out);
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init) {
+        void *out = malloc(128);
+        size_t len = 0;
+        //EVP_DecryptFinal_ex(dec->ctx, out, &len);
+        CCCryptorFinal(dec->ctx, out, 128, &len);
+        
+        
+        dec->write(out, 1, len, dec->stream);
+        CC_SHA256_Update(&dec->sha256, out, len);
+        free(out);
+        //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+        //EVP_CIPHER_CTX_free(dec->ctx);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+    enc->ctx = cryptor;
+    CC_SHA256_Init(&enc->sha256);
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 16;
+    
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    if(enc->tmp) {
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    void *in = malloc(len);
+    size_t in_len = enc->read(in, 1, len, enc->stream);
+    
+    CC_SHA256_Update(&enc->sha256, in, in_len);
+    
+    unsigned char *out = NULL;
+    size_t outlen = 0;
+    size_t ivl = enc->ivlen;
+    if(in_len != 0) {
+        outlen = len + 32;
+        out = malloc(outlen + ivl);
+        if(ivl > 0) {
+            memcpy(out, enc->iv, ivl);
+        }
+        
+        CCCryptorStatus status;
+        size_t avail = outlen;
+        status = CCCryptorUpdate(enc->ctx, in, in_len, out + ivl, avail, &outlen);
+        if(in_len != len) {
+            size_t newoutlen = 16;
+            status = CCCryptorFinal(enc->ctx, out + ivl + outlen, 16, &newoutlen);
+            outlen += newoutlen;
+            enc->end = 1;
+        }
+    } else {
+        out = malloc(32);
+        CCCryptorStatus status;
+        size_t avail = outlen;
+        status = CCCryptorFinal(enc->ctx, out, 32, &outlen);
+        enc->end = 1;
+    }
+    enc->tmp = (char*)out;
+    enc->tmplen = outlen + ivl;
+    enc->tmpoff = 0;
+    
+    if(enc->ivlen > 0) {
+        enc->ivlen = 0;
+    }
+    
+    free(in);
+    
+    return aes_read(buf, s, n, enc);
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 16;
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    if(enc->tmp) {
+        free(enc->tmp);
+    }
+    if(enc->iv) {
+        free(enc->iv);
+    }
+    // TODO: cleanup cryptor
+    free(enc);
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    unsigned char iv[16];
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else {
+        return NULL;
+    }
+    
+    if(status != kCCSuccess) {
+        return NULL;
+    }
+    
+    int buflen = len + 64;
+    char *buf = calloc(1, buflen);
+    memcpy(buf, iv, 16);
+    
+    int pos = 16;
+    size_t avail = buflen - 16;
+    size_t moved;
+    char *out = buf + 16;
+    
+    status = CCCryptorUpdate(cryptor, in,
+         len, out, avail,
+         &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    pos += moved;
+    avail -= moved;
+    out += moved;
+    
+    status = CCCryptorFinal(cryptor, out, avail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    pos += moved;
+    
+    char *b64enc = util_base64encode(buf, pos);
+    free(buf);
+    
+    return b64enc;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+    int inlen;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+    } else {
+        free(buf);
+        return NULL;
+    }
+    
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    char *out = malloc(inlen + 1);
+    size_t outavail = inlen;
+    size_t outlen = 0;
+    
+    unsigned char *inbuf = buf + 16;
+    inlen -= 16;
+    
+    size_t moved = 0;
+    status = CCCryptorUpdate(cryptor, inbuf, inlen, out, outavail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        free(out);
+        // TODO cryptor
+        return NULL;
+    }
+    
+    outlen += moved;
+    outavail -= moved;
+    
+    status = CCCryptorFinal(cryptor, out + outlen, outavail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        free(out);
+        // TODO cryptor
+        return NULL;
+    }
+    
+    outlen += moved;
+    out[outlen] = 0;
+    
+    *len = outlen;
+    return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+    CC_SHA256_Final(buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    CC_SHA256((const unsigned char*)data, len, hash);
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    CC_SHA256_Init(ctx);
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    CC_SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    CC_SHA256_Final(buf, ctx);
+    free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[32];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    // generate key
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: {
+            int result = CCKeyDerivationPBKDF(
+                    kCCPBKDF2,
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    kCCPRFHmacAlgSHA256,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    keydata,
+                    keylen);
+            if(result) {
+                return NULL;
+            }
+            break;
+        }
+        case DAV_PWFUNC_PBKDF2_SHA512: {
+            int result = CCKeyDerivationPBKDF(
+                    kCCPBKDF2,
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    kCCPRFHmacAlgSHA512,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    keydata,
+                    keylen);
+            if(result) {
+                return NULL;
+            }
+            break;
+        }
+        default: return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+
+#endif
+
+/* -------------------- Windows Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_CNG
+
+static void cng_cleanup(BCRYPT_ALG_HANDLE hAesAlg, BCRYPT_KEY_HANDLE hKey, BCRYPT_HASH_HANDLE hHash, void *pbObject) {
+    if(hAesAlg) {
+        BCryptCloseAlgorithmProvider(hAesAlg,0);
+    }
+    if(hKey) {
+        BCryptDestroyKey(hKey);
+    }
+    if(hHash) {
+        BCryptDestroyHash(hHash);
+    }
+    if(pbObject) {
+        free(pbObject);
+    }
+}
+
+static int cng_init_key(BCRYPT_ALG_HANDLE *alg, BCRYPT_KEY_HANDLE *key, void **keyobj, DavKey *aesKey) {
+    BCRYPT_ALG_HANDLE hAesAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey    = NULL;
+    
+    void *pbKeyObject     = NULL;
+    ULONG keyObjectLength = 0;
+    
+    ULONG result = 0;
+    
+    // check DavKey and get AES key length
+    if(!aesKey) {
+        return 1;
+    }
+    
+    ULONG aesKeyLength = 0;
+    if(aesKey->type == DAV_KEY_AES128) {
+        aesKeyLength = 16;
+    } else if(aesKey->type == DAV_KEY_AES256) {
+        aesKeyLength = 32;
+    }
+    if(aesKeyLength > aesKey->length || !aesKey->data) {
+        // invalid DavKey
+        return 1;
+    }
+    
+    // initialize BCrypt stuff
+    if(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0)) {
+        fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+        return 1;
+    }
+    
+    if(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjectLength, sizeof(DWORD), &result, 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot get BCRYPT_OBJECT_LENGTH\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    if(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot set CBC mode\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    pbKeyObject = calloc(1, keyObjectLength);
+    if(!pbKeyObject) {
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    // init key
+    if(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, keyObjectLength, aesKey->data, aesKeyLength, 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot set key\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    *alg = hAesAlg;
+    *key = hKey;
+    *keyobj = pbKeyObject;
+    
+    return 0;
+}
+
+static int cng_hash_init(WinBCryptSHACTX *ctx) {
+    if(BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)) {
+        fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+        return 1;
+    }
+    
+    ULONG hashObjectLen;
+    ULONG result;
+    if(BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjectLen, sizeof(DWORD), &result, 0)) {
+        cng_cleanup(ctx->hAlg, NULL, NULL, NULL);
+        return 1;
+    }
+    
+    ctx->pbHashObject = calloc(1, hashObjectLen);
+    
+    if(BCryptCreateHash(ctx->hAlg, &ctx->hHash, ctx->pbHashObject, hashObjectLen, NULL, 0, 0)) {
+        cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+        return 1;
+    }
+    
+    return 0;
+}
+
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    if(BCryptGenRandom(NULL, (unsigned char*)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
+        return 1;
+    }
+    return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    if(!dec) {
+        return NULL;
+    }
+    if(cng_hash_init(&dec->sha256)) {
+        free(dec);
+        return NULL;
+    }
+    
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+      
+    return dec;
+}
+
+static void aes_decrypter_init(AESDecrypter *dec) {
+    if(cng_init_key(&dec->ctx.hAlg, &dec->ctx.hKey, &dec->ctx.pbKeyObject, dec->key)) {
+        fprintf(stderr, "Error: cng_init_key failed\n");
+        exit(-1);
+    }
+    // copy iv
+    memcpy(dec->ctx.pbIV, dec->ivtmp, 16);
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        dec->init = 1;
+        
+        size_t n = 16 - dec->ivpos;
+        size_t cp = n > len ? len : n;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    // the cipher text must be a multiply of 16
+    // remaining bytes are stored in ctx.buf and must be added to cibuf
+    // the next time
+    size_t cbufalloc = len + 64;
+    ULONG clen = 0;
+    char *cbuf = malloc(cbufalloc);
+    
+    // add previous remaining bytes
+    if(dec->ctx.buflen > 0) {
+        memcpy(cbuf, dec->ctx.buf, dec->ctx.buflen);
+        clen = dec->ctx.buflen;
+    }
+    // add current bytes
+    memcpy(cbuf + clen, buf, len);
+    clen += len;
+    
+    // check if the message fits the blocksize
+    int remaining = clen % 16;
+    if(remaining == 0) {
+        // decrypt last block next time, or in aes_decrypter_shutdown
+        // this makes sure, that shutdown always decrypts the last block
+        // with BCRYPT_BLOCK_PADDING flag
+        remaining = 16;
+    }
+    
+    // add remaining bytes to ctx.buf for the next aes_write run
+    clen -= remaining;
+    memcpy(dec->ctx.buf, cbuf + clen, remaining);
+    dec->ctx.buflen = remaining;
+    
+    // ready to decrypt the message
+    ULONG outlen = clen + 32;
+    unsigned char *out = malloc(outlen);
+       
+    // decrypt
+    if(clen > 0) {
+        ULONG enc_len = 0;
+        ULONG status = BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &enc_len, 0);
+        if(status > 0) {
+            fprintf(stderr, "Error: BCryptDecrypt failed: 0x%X\n", status);
+            free(out);
+            free(cbuf);
+            return 0;
+        }      
+        outlen = enc_len;
+    }
+    
+    // write decrypted data to the output stream and update the hash
+    dec->write(out, 1, outlen, dec->stream);
+    BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+    
+    free(out);
+    free(cbuf);
+    
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init && dec->ctx.buflen > 0) { 
+        ULONG outlen = 64;
+        char out[64];
+        if(BCryptDecrypt(dec->ctx.hKey, dec->ctx.buf, dec->ctx.buflen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+            fprintf(stderr, "Error: BCryptDecrypt failed\n");
+            return;
+        }
+        dec->write(out, 1, outlen, dec->stream);
+        BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    cng_cleanup(dec->ctx.hAlg, dec->ctx.hKey, NULL, dec->ctx.pbKeyObject);
+    cng_cleanup(dec->sha256.hAlg, NULL, dec->sha256.hHash, dec->sha256.pbHashObject);
+    free(dec);
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(dav_rand_bytes(iv, 16)) {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = calloc(1, sizeof(AESEncrypter));
+    if(cng_hash_init(&enc->sha256)) {
+        free(iv);
+        free(enc);
+        return NULL;
+    }
+    
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 0;
+    
+    if(cng_init_key(&enc->ctx.hAlg, &enc->ctx.hKey, &enc->ctx.pbKeyObject, key)) {
+        fprintf(stderr, "Error: cng_init_key failed\n");
+        exit(-1);
+    }
+    
+    enc->ctx.buflen = 0;
+    memcpy(enc->ctx.pbIV, iv, 16);
+    
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    size_t nread = 0; 
+    
+    if(enc->tmp) {
+        // the temp buffer contains bytes that are already encrypted, but
+        // the last aes_read had not enough read buffer space
+        
+        // in case we have a tmp buf, we just return this 
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->ivlen < 16) {
+        size_t copy_iv_len = 16 - enc->ivlen;
+        copy_iv_len = len > copy_iv_len ? copy_iv_len : len;
+        
+        memcpy(buf, enc->iv, copy_iv_len);
+        buf += copy_iv_len;
+        len -= copy_iv_len;
+        nread = copy_iv_len;
+        
+        enc->ivlen += copy_iv_len;
+        
+        if(len == 0) {
+            return copy_iv_len / s;
+        }
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    size_t remaining = len % 16;
+    len -= remaining;
+    
+    if(len > 256) {
+        len -= 16; // optimization for avoiding tmp buffer usage
+    }
+    
+    size_t inalloc = len;
+    ULONG  inlen = 0;
+    unsigned char *in = malloc(inalloc);
+    
+    // fill the input buffer
+    while(inlen < inalloc) {
+        size_t r = enc->read(in + inlen, 1, inalloc - inlen, enc->stream);
+        if(r == 0) {
+            enc->end = 1;
+            break;
+        }
+        inlen += r;
+    }
+    
+    if(inlen == 0) {
+        return nread / s;
+    }
+    
+    // hash read data
+    BCryptHashData(enc->sha256.hHash, in, inlen, 0);
+    
+    // create output buffer
+    ULONG outalloc = inlen + 16;
+    ULONG outlen = 0;
+    char *out = malloc(outalloc);
+    
+    // encrypt
+    int flags = 0;
+    if(inlen % 16 != 0) {
+        enc->end = 1;
+    }
+    if(enc->end) {
+        flags = BCRYPT_BLOCK_PADDING;
+    }
+    if(BCryptEncrypt(enc->ctx.hKey, in, inlen, NULL, enc->ctx.pbIV, 16, out, outalloc, &outlen, flags)) {
+        fprintf(stderr, "Error: BCryptEncrypt failed\n");
+    }
+    
+    // check if the output fits in buf, if not, save the remaining bytes in tmp
+    if(outlen > len) {
+        size_t tmplen = outlen - len;
+        char *tmp = malloc(tmplen);
+        memcpy(tmp, out+len, tmplen);
+        
+        enc->tmp = tmp;
+        enc->tmplen = tmplen;
+        enc->tmpoff = 0;
+        
+        outlen = len;
+    }
+    
+    // fill read buffer and return
+    memcpy(buf, out, outlen);
+    nread += outlen;
+    
+    free(in);
+    free(out);
+    
+    return nread / s;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    enc->end = 1;
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 0;
+    memcpy(enc->ctx.pbIV, enc->iv, 16);
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    // create random IV
+    char iv[16];
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    // initialize bcrypt stuff
+    BCRYPT_ALG_HANDLE hAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey = NULL;
+    void *pbKeyObject = NULL;
+    if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+        return NULL;
+    }
+    
+    // create output buffer
+    ULONG outlen = len + 128;
+    char *out = malloc(outlen);
+    
+    // the output must start with the IV
+    memcpy(out, iv, 16);
+    char *encbuf = out + 16;
+    ULONG enclen = outlen - 16;
+    ULONG encoutlen = 0;
+    
+    // encrypt
+    if(BCryptEncrypt(hKey, (PUCHAR)in, len, NULL, (PUCHAR)iv, 16, encbuf, enclen, &encoutlen, BCRYPT_BLOCK_PADDING)) {
+        fprintf(stderr, "Error: BCryptEncrypt failed\n");
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        free(out);
+        return NULL;
+    }
+    
+    outlen = encoutlen + 16; // length of encrypted data + 16 bytes IV
+    
+    // base64 encode
+    char *outstr = util_base64encode(out, outlen);
+    
+    cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+    free(out);
+    
+    return outstr;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+    BCRYPT_ALG_HANDLE hAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey = NULL;
+    void *pbKeyObject = NULL;
+    if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+        return NULL;
+    }
+    
+    int inlen;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+    if(inlen < 16 || !buf) {
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        if(buf) {
+            free(buf);
+        }
+        return NULL;
+    }
+    
+    // encrypted data starts with IV
+    char iv[16];
+    memcpy(iv, buf, 16);
+    
+    // decrypt data
+    char *data = buf + 16; // encrypted data starts after IV
+    size_t datalen = inlen - 16;
+    
+    // create output buffer
+    ULONG outlen = inlen;
+    char *out = malloc(outlen + 1);
+    
+    // decrypt
+    if(BCryptDecrypt(hKey, data, datalen, NULL, iv, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        free(out);
+        free(buf);
+        return NULL;
+    }
+    
+    // decrypt finished, return
+    out[outlen] = 0;
+    *len = (size_t)outlen;
+    return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+    BCryptFinishHash(sha256->hHash, buf, DAV_SHA256_DIGEST_LENGTH, 0);
+}
+
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    DAV_SHA_CTX *ctx = dav_hash_init();
+    if(ctx) {
+        dav_hash_update(ctx, data, len);
+        dav_hash_final(ctx, hash);
+    }
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    if(!ctx) {
+        return NULL;
+    }
+    if(cng_hash_init(ctx)) {
+        free(ctx);
+        return NULL;
+    }
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0);
+    
+    // cleanup
+    cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+    free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[128];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    LPCWSTR algid;
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: algid = BCRYPT_SHA256_ALGORITHM; break;
+        case DAV_PWFUNC_PBKDF2_SHA512: algid = BCRYPT_SHA512_ALGORITHM; break;
+        default: return NULL;
+    }
+    
+    // open algorithm provider
+    BCRYPT_ALG_HANDLE hAlg;
+    ULONG status = BCryptOpenAlgorithmProvider(&hAlg, algid, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
+    if(status > 0) {
+        fprintf(stderr, "Error: dav_pw2key: BCryptOpenAlgorithmProvider failed: 0x%X\n", (unsigned int)status);
+        return NULL;
+    }
+    
+    // derive key
+    status =  BCryptDeriveKeyPBKDF2(
+            hAlg,
+            (PUCHAR)password,
+            len,
+            (PUCHAR)salt,
+            saltlen,
+            DAV_CRYPTO_ITERATION_COUNT,
+            keydata,
+            128,
+            0);
+    
+    BCryptCloseAlgorithmProvider(hAlg,0);
+    
+    if(status) {
+        fprintf(stderr, "Error: dav_pw2key: BCryptDeriveKeyPBKDF2 failed: 0x%X\n", (unsigned int)status);
+        return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+#endif
+
+
+
+UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) {
+    UcxBuffer *encbuf = ucx_buffer_new(
+            NULL,
+            in->size+16,
+            UCX_BUFFER_AUTOEXTEND);
+    
+    AESEncrypter *enc = aes_encrypter_new(
+            key,
+            in,
+            (dav_read_func)ucx_buffer_read,
+            NULL);
+    if(!enc) {
+        ucx_buffer_free(encbuf);
+        return NULL;
+    }
+    
+    char buf[1024];
+    size_t r;
+    while((r = aes_read(buf, 1, 1024, enc)) > 0) {
+        ucx_buffer_write(buf, 1, r, encbuf);
+    }
+    aes_encrypter_close(enc);
+    
+    encbuf->pos = 0;
+    return encbuf;
+}
+
+UcxBuffer* aes_decrypt_buffer(UcxBuffer *in, DavKey *key) {
+    UcxBuffer *decbuf = ucx_buffer_new(
+            NULL,
+            in->size,
+            UCX_BUFFER_AUTOEXTEND);
+    AESDecrypter *dec = aes_decrypter_new(
+            key,
+            decbuf,
+            (dav_write_func)ucx_buffer_write);
+    
+    aes_write(in->space, 1, in->size, dec);
+    aes_decrypter_shutdown(dec);
+    aes_decrypter_close(dec);
+    decbuf->pos = 0;
+    return decbuf;
+}
diff --git a/libidav/crypto.h b/libidav/crypto.h
new file mode 100644 (file)
index 0000000..7ea6597
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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 DAV_CRYPTO_H
+#define        DAV_CRYPTO_H
+
+#include "webdav.h"
+#include <ucx/string.h>
+
+#ifdef __APPLE__
+/* macos */
+
+#define DAV_CRYPTO_COMMON_CRYPTO
+
+#define DAV_AES_CTX              CCCryptorRef
+#define DAV_SHA_CTX              CC_SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <CommonCrypto/CommonCrypto.h>
+#include <CommonCrypto/CommonDigest.h>
+
+#elif defined(_WIN32)
+
+#define DAV_CRYPTO_CNG
+
+#include <windows.h>
+#include <bcrypt.h>
+
+typedef struct WinBCryptCTX {
+    BCRYPT_ALG_HANDLE hAlg;
+    BCRYPT_KEY_HANDLE hKey;
+    void              *pbKeyObject;
+    unsigned char     pbIV[16];
+    
+    unsigned char     buf[16];
+    ULONG             buflen;
+} WinBCryptCTX;
+
+typedef struct WinBCryptSHACTX {
+    BCRYPT_ALG_HANDLE  hAlg;
+    BCRYPT_HASH_HANDLE hHash;    
+    void               *pbHashObject;
+} WinBCryptSHACTX;
+
+#define DAV_AES_CTX              WinBCryptCTX
+#define DAV_SHA_CTX              WinBCryptSHACTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#else
+/* unix/linux */
+
+#define DAV_USE_OPENSSL
+
+#define DAV_AES_CTX              EVP_CIPHER_CTX*
+#define DAV_SHA_CTX              SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#if defined(__sun) && defined(__SunOS_5_10)
+#include <sha2.h>
+#define SHA256_Init     SHA256Init
+#define SHA256_Update   SHA256Update
+#define SHA256_Final    SHA256Final
+#else
+#include <openssl/sha.h>
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DAV_PWFUNC_PBKDF2_SHA256 0
+#define DAV_PWFUNC_PBKDF2_SHA512 1
+    
+#define DAV_CRYPTO_ITERATION_COUNT 4000
+    
+typedef struct {
+    DAV_AES_CTX    ctx;
+    DAV_SHA_CTX    sha256;
+    void           *stream;
+    dav_write_func write;
+    DavKey         *key;
+    int            init;
+    unsigned char  ivtmp[16];
+    size_t         ivpos;
+} AESDecrypter;
+
+typedef struct {
+    DAV_AES_CTX    ctx;
+    DAV_SHA_CTX    sha256;
+    void           *iv;
+    size_t         ivlen;
+    void           *stream;
+    dav_read_func  read;
+    dav_seek_func  seek;
+    char           *tmp;
+    size_t         tmplen;
+    size_t         tmpoff;
+    int            end;
+} AESEncrypter;
+
+typedef struct DavHashContext DavHashContext;
+
+int dav_rand_bytes(unsigned char *buf, size_t len);
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func);
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec);
+void aes_decrypter_shutdown(AESDecrypter *dec);
+void aes_decrypter_close(AESDecrypter *dec);
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc);
+void aes_encrypter_close(AESEncrypter *enc);
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin);
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key);
+char* aes_decrypt(const char *in, size_t *len, DavKey *key);
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf);
+
+char* dav_create_hash(const char *data, size_t len);
+
+DAV_SHA_CTX* dav_hash_init(void);
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf);
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc);
+
+UcxBuffer* aes_encrypt_buffer(UcxBuffer *buf, DavKey *key);
+UcxBuffer* aes_decrypt_buffer(UcxBuffer *buf, DavKey *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_CRYPTO_H */
+
diff --git a/libidav/davqlexec.c b/libidav/davqlexec.c
new file mode 100644 (file)
index 0000000..9eb6ec5
--- /dev/null
@@ -0,0 +1,1466 @@
+/*
+ * 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 <inttypes.h>
+
+#include <ucx/utils.h>
+#include <ucx/map.h>
+#include "davqlexec.h"
+#include "utils.h"
+#include "methods.h"
+#include "session.h"
+#include "resource.h"
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap) {
+    DavQLArgList *args = malloc(sizeof(DavQLArgList));
+    if(!args) {
+        return NULL;
+    }
+    args->first = NULL;
+    
+    DavQLArg *cur = NULL;
+    UCX_FOREACH(elm, st->args) {
+        intptr_t type = (intptr_t)elm->data;
+        DavQLArg *arg = calloc(1, sizeof(DavQLArg));
+        if(!arg) {
+            dav_ql_free_arglist(args);
+            return NULL;
+        }
+        arg->type = type;
+        switch(type) {
+            case 'd': {
+                arg->value.d = va_arg(ap, int);
+                break;
+            }
+            case 'u': {
+                arg->value.u = va_arg(ap, unsigned int);
+                break;
+            }
+            case 's': {
+                arg->value.s = va_arg(ap, char*);
+                break;
+            }
+            case 't': {
+                arg->value.t = va_arg(ap, time_t);
+                break;
+            }
+            default: {
+                free(arg);
+                dav_ql_free_arglist(args);
+                return NULL;
+            }
+        }
+        if(cur) {
+            cur->next = arg;
+        } else {
+            args->first = arg;
+        }
+        cur = arg;
+    }
+    args->current = args->first;
+    return args;
+}
+
+void dav_ql_free_arglist(DavQLArgList *args) {
+    DavQLArg *arg = args->first;
+    while(arg) {
+        DavQLArg *next = arg->next;
+        free(arg);
+        arg = next;
+    }
+    free(args);
+}
+
+static DavQLArg* arglist_get(DavQLArgList *args) {
+    DavQLArg *a = args->current;
+    if(a) {
+        args->current = a->next;
+    }
+    return a;
+}
+
+int dav_ql_getarg_int(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 'd') {
+        return a->value.d;
+    }
+    return 0;
+}
+
+unsigned int dav_ql_getarg_uint(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 'u') {
+        return a->value.u;
+    }
+    return 0;
+}
+
+char* dav_ql_getarg_str(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 's') {
+        return a->value.s;
+    }
+    return "";
+}
+
+time_t dav_ql_getarg_time(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 't') {
+        return a->value.t;
+    }
+    return 0;
+}
+
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...) {
+    va_list ap;
+    va_start(ap, st);
+    DavResult result = dav_statement_execv(sn, st, ap);
+    va_end(ap);
+    return result;
+}
+
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap) {
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    // make sure the statement was successfully parsed
+    if(st->type == DAVQL_ERROR) {
+        return result;
+    }
+    
+    if(st->type == DAVQL_SELECT) {
+        return dav_exec_select(sn, st, ap);
+    } else {
+        // TODO
+    }
+    
+    return result;
+}
+
+sstr_t dav_format_string(UcxAllocator *a, sstr_t fstr, DavQLArgList *ap, davqlerror_t *error) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    
+    int placeholder = 0;
+    for(int i=0;i<fstr.length;i++) {
+        char c = fstr.ptr[i];
+        if(placeholder) {
+            if(c == '%') {
+                // no placeholder, %% transposes to %
+                ucx_buffer_putc(buf, c);
+            } else {
+                // detect placeholder type and insert arg
+                int err = 0;
+                switch(c) {
+                    case 's': {
+                        char *arg = dav_ql_getarg_str(ap);
+                        ucx_buffer_puts(buf, arg);
+                        break;
+                    }
+                    case 'd': {
+                        int arg = dav_ql_getarg_int(ap);
+                        ucx_bprintf(buf, "%d", arg);
+                        break;
+                    }
+                    case 'u': {
+                        unsigned int arg = dav_ql_getarg_uint(ap);
+                        ucx_bprintf(buf, "%u", arg);
+                        break;
+                    }
+                    case 't': {
+                        // time arguments not supported for strings
+                        err = 1;
+                        break;
+                    }
+                    default: {
+                        *error = DAVQL_UNKNOWN_FORMATCHAR;
+                        err = 1;
+                    }
+                }
+                if(err) {
+                    ucx_buffer_free(buf);
+                    sstr_t n;
+                    n.ptr = NULL;
+                    n.length = 0;
+                    return n;
+                }
+            }
+            placeholder = 0;
+        } else {
+            if(c == '%') {
+                placeholder = 1;
+            } else {
+                ucx_buffer_putc(buf, c);
+            }
+        }
+    }
+    *error = DAVQL_OK;
+    
+    sstr_t ret = sstrdup_a(a, sstrn(buf->space, buf->size));
+    ucx_buffer_free(buf);
+    return ret;
+}
+
+static int fl_add_properties(DavSession *sn, UcxMempool *mp, UcxMap *map, DavQLExpression *expression) {
+    if(!expression) {
+        return 0;
+    }
+    
+    if(expression->type == DAVQL_IDENTIFIER) {
+        DavProperty *property = ucx_mempool_malloc(mp, sizeof(DavProperty));
+
+        char *name;
+        DavNamespace *ns = dav_get_property_namespace(
+                sn->context,
+                sstrdup_a(mp->allocator, expression->srctext).ptr,
+                &name);
+        if(!ns) {
+            return -1;
+        }
+        
+        property->ns = ns;
+        property->name = name;
+        property->value = NULL;
+        
+        ucx_map_sstr_put(map, expression->srctext, property);
+    }
+    
+    if(expression->left) {
+        if(fl_add_properties(sn, mp, map, expression->left)) {
+            return -1;
+        }
+    }
+    if(expression->right) {
+        if(fl_add_properties(sn, mp, map, expression->right)) {
+            return -1;
+        }
+    }
+    
+    return 0;
+}
+
+static UcxBuffer* fieldlist2propfindrequest(DavSession *sn, UcxMempool *mp, UcxList *fields, int *isallprop) {
+    UcxMap *properties = ucx_map_new(32);
+    *isallprop = 0;
+    
+    UCX_FOREACH(elm, fields) {
+        DavQLField *field = elm->data;
+        if(!sstrcmp(field->name, S("*"))) {
+            ucx_map_free(properties);
+            *isallprop = 1;
+            return create_allprop_propfind_request();
+        } else if(!sstrcmp(field->name, S("-"))) {
+            ucx_map_free(properties);
+            return create_propfind_request(sn, NULL, "propfind", 0);
+        } else {
+            if(fl_add_properties(sn, mp, properties, field->expr)) {
+                // TODO: set error
+                ucx_map_free(properties);
+                return NULL;
+            }
+        }
+    }
+    
+    UcxMapIterator i = ucx_map_iterator(properties);
+    UcxKey key;
+    DavProperty *value;
+    UcxList *list = NULL;
+    UCX_MAP_FOREACH(key, value, i) {
+        list = ucx_list_append(list, value);
+    }
+    
+    UcxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0);
+    ucx_list_free(list);
+    ucx_map_free(properties);
+    return reqbuf;
+}
+
+static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, UcxList *fields) {
+    UcxMap *new_properties = ucx_map_new_a(sn->mp->allocator, 32);
+    DavResourceData *data = (DavResourceData*)res->data;
+    
+    // add basic properties
+    void *value;
+    
+    sstr_t cl_keystr = dav_property_key("DAV:", "getcontentlength");
+    UcxKey cl_key = ucx_key(cl_keystr.ptr, cl_keystr.length);
+    value = ucx_map_get(data->properties, cl_key);
+    if(value) {
+        ucx_map_put(new_properties, cl_key, value);
+    }
+    
+    sstr_t cd_keystr = dav_property_key("DAV:", "creationdate");
+    UcxKey cd_key = ucx_key(cd_keystr.ptr, cd_keystr.length);
+    value = ucx_map_get(data->properties, cd_key);
+    if(value) {
+        ucx_map_put(new_properties, cd_key, value);
+    }
+    
+    sstr_t lm_keystr = dav_property_key("DAV:", "getlastmodified");
+    UcxKey lm_key = ucx_key(lm_keystr.ptr, lm_keystr.length);
+    value = ucx_map_get(data->properties, lm_key);
+    if(value) {
+        ucx_map_put(new_properties, lm_key, value);
+    }
+    
+    sstr_t ct_keystr = dav_property_key("DAV:", "getcontenttype");
+    UcxKey ct_key = ucx_key(ct_keystr.ptr, ct_keystr.length);
+    value = ucx_map_get(data->properties, ct_key);
+    if(value) {
+        ucx_map_put(new_properties, ct_key, value);
+    }
+    
+    sstr_t rt_keystr = dav_property_key("DAV:", "resourcetype");
+    UcxKey rt_key = ucx_key(rt_keystr.ptr, rt_keystr.length);
+    value = ucx_map_get(data->properties, rt_key);
+    if(value) {
+        ucx_map_put(new_properties, rt_key, value);
+    }
+    
+    sstr_t cn_keystr = dav_property_key(DAV_NS, "crypto-name");
+    UcxKey cn_key = ucx_key(cn_keystr.ptr, cn_keystr.length);
+    value = ucx_map_get(data->properties, cn_key);
+    if(value) {
+        ucx_map_put(new_properties, cn_key, value);
+    }
+    
+    sstr_t ck_keystr = dav_property_key(DAV_NS, "crypto-key");
+    UcxKey ck_key = ucx_key(ck_keystr.ptr, ck_keystr.length);
+    value = ucx_map_get(data->properties, ck_key);
+    if(value) {
+        ucx_map_put(new_properties, ck_key, value);
+    }
+    
+    sstr_t ch_keystr = dav_property_key(DAV_NS, "crypto-hash");
+    UcxKey ch_key = ucx_key(ch_keystr.ptr, ch_keystr.length);
+    value = ucx_map_get(data->properties, ch_key);
+    if(value) {
+        ucx_map_put(new_properties, ch_key, value);
+    }
+    
+    // add properties from field list
+    UCX_FOREACH(elm, fields) {
+        DavCompiledField *field = elm->data;
+        DavQLStackObj field_result;
+        if(!dav_exec_expr(field->code, res, &field_result)) {
+            sstr_t str;
+            str.ptr = NULL;
+            str.length = 0;
+            DavXmlNode *node = NULL;
+            if(field_result.type == 0) {
+                str = ucx_asprintf(
+                        sn->mp->allocator,
+                        "%d",
+                        field_result.data.integer);
+            } else if(field_result.type == 1) {
+                if(field_result.data.string) {
+                    str = sstrdup_a(sn->mp->allocator, sstrn(
+                            field_result.data.string,
+                            field_result.length));
+                }
+            } else if(field_result.type == 2) {
+                node = dav_copy_node(field_result.data.node);
+            } else {
+                // unknown type
+                // TODO: error
+                resource_free_properties(sn, new_properties);
+                return -1;
+            }
+            if(str.ptr) {
+                node = dav_session_malloc(sn, sizeof(DavXmlNode));
+                memset(node, 0, sizeof(DavXmlNode));
+                node->type = DAV_XML_TEXT;
+                node->content = str.ptr;
+                node->contentlength = str.length;
+            }
+            if(node) {
+                sstr_t key = dav_property_key(field->ns, field->name);
+                
+                DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+                namespace->prefix = NULL;
+                namespace->name = dav_session_strdup(sn, field->ns);
+
+                DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+                prop->name = dav_session_strdup(sn, field->name);
+                prop->ns = namespace;
+                prop->value = node;
+                
+                ucx_map_sstr_put(new_properties, key, prop);
+                free(key.ptr);
+            }
+        } else {
+            // TODO: error
+            resource_free_properties(sn, new_properties);
+            return -1;
+        }
+    }
+    
+    ucx_map_remove(data->properties, cl_key);
+    ucx_map_remove(data->properties, cd_key);
+    ucx_map_remove(data->properties, lm_key);
+    ucx_map_remove(data->properties, ct_key);
+    ucx_map_remove(data->properties, rt_key);
+    ucx_map_remove(data->properties, cn_key);
+    ucx_map_remove(data->properties, ck_key);
+    ucx_map_remove(data->properties, ch_key);
+    
+    resource_free_properties(sn, data->properties);
+    data->properties = new_properties;
+    
+    free(cl_keystr.ptr);
+    free(cd_keystr.ptr);
+    free(lm_keystr.ptr);
+    free(ct_keystr.ptr);
+    free(rt_keystr.ptr);
+    free(cn_keystr.ptr);
+    free(ck_keystr.ptr);
+    free(ch_keystr.ptr);
+    
+    return 0;
+}
+
+/*
+ * execute a davql select statement
+ */
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) {
+    UcxMempool *mp = ucx_mempool_new(128);
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    DavQLArgList *args = dav_ql_get_args(st, ap);
+    if(!args) {
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, args, (ucx_destructor)dav_ql_free_arglist);
+    
+    int isallprop;
+    UcxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp, st->fields, &isallprop);
+    if(!rqbuf) {
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, rqbuf, (ucx_destructor)ucx_buffer_free);
+    
+    // compile field list
+    UcxList *cfieldlist = NULL;
+    UCX_FOREACH(elm, st->fields) {
+        DavQLField *field = elm->data;
+        if(sstrcmp(field->name, S("*")) && sstrcmp(field->name, S("-"))) {
+            // compile field expression
+            UcxBuffer *code = dav_compile_expr(
+                    sn->context,
+                    mp->allocator,
+                    field->expr,
+                    args);
+            if(!code) {
+                // TODO: set error string
+                return result;
+            }
+            ucx_mempool_reg_destr(mp, code, (ucx_destructor)ucx_buffer_free);
+            DavCompiledField *cfield = ucx_mempool_malloc(
+                    mp,
+                    sizeof(DavCompiledField));
+            
+            char *ns;
+            char *name;
+            dav_get_property_namespace_str(
+                sn->context,
+                sstrdup_a(mp->allocator, field->name).ptr,
+                &ns,
+                &name);
+            if(!ns || !name) {
+                // TODO: set error string
+                return result;
+            }
+            cfield->ns = ns;
+            cfield->name = name;
+            cfield->code = code;
+            cfieldlist = ucx_list_append_a(mp->allocator, cfieldlist, cfield);
+        } 
+    }
+    
+    // get path string
+    davqlerror_t error;
+    sstr_t path = dav_format_string(mp->allocator, st->path, args, &error);
+    if(error) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    
+    int depth = st->depth == DAV_DEPTH_PLACEHOLDER ?
+            dav_ql_getarg_int(args) : st->depth;
+    
+    UcxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args);
+    if(st->where && !where) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    if(where) {
+        ucx_mempool_reg_destr(mp, where, (ucx_destructor)ucx_buffer_free);
+    }
+    
+    // compile order criterion
+    UcxList *ordercr = NULL;
+    UCX_FOREACH(elm, st->orderby) {
+        DavQLOrderCriterion *oc = elm->data;
+        DavQLExpression *column = oc->column;
+        //printf("%.*s %s\n", column->srctext.length, column->srctext.ptr, oc->descending ? "desc" : "asc");
+        if(column->type == DAVQL_IDENTIFIER) {
+            // TODO: remove code duplication (add_cmd)
+            davqlresprop_t resprop;
+            sstr_t propertyname = sstrchr(column->srctext, ':');
+            if(propertyname.length > 0) {
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                        sn->context,
+                        sstrdup_a(mp->allocator, column->srctext).ptr,
+                        &ns,
+                        &name);
+                if(ns && name) {
+                    DavOrderCriterion *cr = ucx_mempool_malloc(mp, sizeof(DavOrderCriterion));
+                    cr->type = 1;
+                    sstr_t keystr = dav_property_key_a(mp->allocator, ns, name);
+                    cr->column.property = ucx_key(keystr.ptr, keystr.length);
+                    cr->descending = oc->descending;
+                    ordercr = ucx_list_append_a(mp->allocator, ordercr, cr);
+                } else {
+                    // error
+                    // TODO: cleanup
+                    ucx_mempool_destroy(mp);
+                    return result;
+                }
+            } else if(dav_identifier2resprop(column->srctext, &resprop)) {
+                DavOrderCriterion *cr = ucx_mempool_malloc(mp, sizeof(DavOrderCriterion));
+                cr->type = 0;
+                cr->column.resprop = resprop;
+                cr->descending = oc->descending;
+                ordercr = ucx_list_append_a(mp->allocator, ordercr, cr);
+            } else {
+                // error
+                // TODO: cleanup
+                ucx_mempool_destroy(mp);
+                return result;
+            }
+            
+        } else if(column->type == DAVQL_NUMBER) {
+            // TODO: implement
+            fprintf(stderr, "order by number not supported\n");
+            return result;
+        } else {
+            // something is broken
+            // TODO: cleanup
+            ucx_mempool_destroy(mp);
+            return result;
+        }
+    }
+    
+    DavResource *selroot = dav_resource_new(sn, path.ptr);
+    
+    UcxList *stack = NULL; // stack with DavResource* elements
+    // initialize the stack with the requested resource
+    DavQLRes *res = ucx_mempool_malloc(mp, sizeof(DavQLRes));
+    res->resource = selroot;
+    res->depth = 0;
+    stack = ucx_list_prepend(stack, res);
+    
+    // reuseable response buffer
+    UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    if(!rpbuf) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, rpbuf, (ucx_destructor)ucx_buffer_free);
+    
+    result.result = selroot;
+    result.status = 0;
+    
+    // do a propfind request for each resource on the stack
+    while(stack) {
+        DavQLRes *sr = stack->data; // get first element from the stack
+        stack = ucx_list_remove(stack, stack); // remove first element
+        DavResource *root = sr->resource;
+        
+        util_set_url(sn, dav_resource_get_href(sr->resource));
+        CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+        long http_status = 0;
+        curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+        //printf("rpbuf: %s %s\n%.*s\n\n", sr->resource->path, sr->resource->href, rpbuf->pos, rpbuf->space);
+        
+        if(ret == CURLE_OK && http_status == 207) {
+            // in case of an redirect we have to adjust resource->href
+            dav_set_effective_href(sn, root);
+            
+            // propfind request successful, now parse the response
+            char *url = "http://url/";
+            PropfindParser *parser = create_propfind_parser(rpbuf, url);
+            // TODO: test if parser is null
+            ResponseTag response;
+            int r;
+            while((r = get_propfind_response(parser, &response)) != 0) {
+                if(r == -1) {
+                    // error
+                    result.status = -1;
+                    // TODO: free resources
+                    cleanup_response(&response);
+                    break;
+                }
+                
+                // the propfind multistatus response contains responses
+                // for the requested resource and all childs
+                // determine if the response is a child or not
+                if(hrefeq(sn, root->href, response.href)) {
+                    // response is the currently requested resource
+                    // and not a child
+                    
+                    // add properties
+                    add_properties(root, &response);
+                    cleanup_response(&response);
+                    
+                    if(root == selroot) {                     
+                        // The current root is the root of the select query.
+                        // In this case we have to check the where clause.
+                        // If root is not selroot, the where clause was
+                        // already checked for the resource before it was
+                        // added to the stack.
+                        DavQLStackObj where_result;
+                        if(!dav_exec_expr(where, root, &where_result)) {                           
+                            if(where_result.data.integer != 0) { 
+                                if(!reset_properties(sn, &result, root, cfieldlist)) {
+                                    continue;
+                                }
+                                result.status = -1;
+                            }
+                        }
+                        result.result = NULL;
+                        result.status = -1;
+                        dav_resource_free_all(selroot);
+                        ucx_list_free(stack);
+                        break;
+                    }
+                } else {
+                    DavResource *child = response2resource(
+                            sn,
+                            &response,
+                            root->path);
+                    cleanup_response(&response);
+                    // check where clause
+                    DavQLStackObj where_result;
+                    if(!dav_exec_expr(where, child, &where_result)) {
+                        if(where_result.data.integer != 0) {
+                            if(!reset_properties(sn, &result, child, cfieldlist)) {
+                                //resource_add_child(root, child);
+                                resource_add_ordered_child(root, child, ordercr);
+                                if(child->iscollection &&
+                                    (depth < 0 || depth > sr->depth+1))
+                                {
+                                    DavQLRes *rs = ucx_mempool_malloc(
+                                            mp,
+                                            sizeof(DavQLRes));
+                                    rs->resource = child;
+                                    rs->depth = sr->depth + 1;
+                                    stack = ucx_list_prepend(stack, rs);
+                                }
+                            } else {
+                                dav_resource_free(child);
+                            }
+                        } else {
+                            dav_resource_free(child);
+                        }
+                    }
+                }    
+            }
+            destroy_propfind_parser(parser);
+        } else  {
+            dav_session_set_error(sn, ret, http_status);
+            result.result = NULL;
+            result.status = -1;
+            dav_resource_free_all(selroot);
+            break;
+        }
+        
+        // reset response buffer
+        ucx_buffer_seek(rpbuf, SEEK_SET, 0);
+    }
+    
+    ucx_mempool_destroy(mp);
+    return result;
+}
+
+static int count_func_args(DavQLExpression *expr) {
+    int count = 0;
+    DavQLExpression *arg = expr->right;
+    while(arg) {
+        count++;
+        if(arg->op == DAVQL_ARGLIST) {
+            arg = arg->right;
+        } else {
+            break;
+        }
+    }
+    return count;
+}
+
+int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop) {
+    if(!sstrcmp(src, S("name"))) {
+        *prop = DAVQL_RES_NAME;
+    } else if(!sstrcmp(src, S("path"))) {
+        *prop = DAVQL_RES_PATH;
+    } else if(!sstrcmp(src, S("href"))) {
+        *prop = DAVQL_RES_HREF;
+    } else if(!sstrcmp(src, S("contentlength"))) {
+        *prop = DAVQL_RES_CONTENTLENGTH;
+    } else if(!sstrcmp(src, S("contenttype"))) {
+        *prop = DAVQL_RES_CONTENTTYPE;
+    } else if(!sstrcmp(src, S("creationdate"))) {
+        *prop = DAVQL_RES_CREATIONDATE;
+    } else if(!sstrcmp(src, S("lastmodified"))) {
+        *prop = DAVQL_RES_LASTMODIFIED;
+    } else if(!sstrcmp(src, S("iscollection"))) {
+        *prop = DAVQL_RES_ISCOLLECTION;
+    } else {
+        return 0;
+    }
+    return 1;
+}
+
+static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) {
+    if(!expr) {
+        return 0;
+    }
+     
+    int numcmd = 1;
+    DavQLCmd cmd;
+    memset(&cmd, 0, sizeof(DavQLCmd));
+    davqlerror_t error;
+    
+    sstr_t src = expr->srctext;
+    switch(expr->type) {
+        default: break;
+        case DAVQL_NUMBER: {   
+            cmd.type = DAVQL_CMD_INT;
+            if(src.ptr[0] == '%') {
+                cmd.data.integer = dav_ql_getarg_int(ap);
+            } else if(util_strtoint(src.ptr, &cmd.data.integer)) {
+                ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            
+            break;
+        }
+        case DAVQL_STRING: {
+            cmd.type = DAVQL_CMD_STRING;
+            cmd.data.string = dav_format_string(a, src, ap, &error);
+            ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_TIMESTAMP: {
+            if(src.ptr[0] == '%') {
+                cmd.type = DAVQL_CMD_TIMESTAMP;
+                cmd.data.timestamp = dav_ql_getarg_time(ap);
+                ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            break;
+        }
+        case DAVQL_IDENTIFIER: {
+            sstr_t propertyname = sstrchr(src, ':');
+            cmd.type = DAVQL_CMD_RES_IDENTIFIER;
+            if(propertyname.length > 0) {
+                cmd.type = DAVQL_CMD_PROP_IDENTIFIER;
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                        ctx,
+                        sstrdup_a(a, src).ptr,
+                        &ns,
+                        &name);
+                if(ns && name) {
+                    cmd.data.property.ns = ns;
+                    cmd.data.property.name = name;
+                } else {
+                    // error
+                    return -1;
+                }
+            } else if(!dav_identifier2resprop(src, &cmd.data.resprop)) {
+                if(!sstrcmp(src, S("true"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 1;
+                } else if(!sstrcmp(src, S("false"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 0;
+                } else {
+                    // error, unknown identifier
+                    return -1;
+                }
+            }
+            ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_UNARY: {
+            numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+            switch(expr->op) {
+                case DAVQL_ADD: {
+                    // noop
+                    numcmd = 0;
+                    break;
+                }
+                case DAVQL_SUB: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_SUB;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEG: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_NEG;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+        case DAVQL_BINARY: {
+            numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+            numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+            switch(expr->op) {
+                case DAVQL_ADD: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_ADD;
+                    break;
+                }
+                case DAVQL_SUB: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_SUB;
+                    break;
+                }
+                case DAVQL_MUL: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_MUL;
+                    break;
+                }
+                case DAVQL_DIV: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_DIV;
+                    break;
+                }
+                case DAVQL_AND: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_AND;
+                    break;
+                }
+                case DAVQL_OR: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_OR;
+                    break;
+                }
+                case DAVQL_XOR: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_XOR;
+                    break;
+                }
+                default: break;
+            }
+            ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_LOGICAL: {
+            if(expr->left && expr->right && expr->op != DAVQL_LOR) {
+                numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+            }
+            
+            switch(expr->op) {
+                case DAVQL_NOT: {
+                    numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_NOT;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LAND: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_AND;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LOR: {
+                    int nleft = add_cmd(ctx, a, bcode, expr->left, ap);
+                    
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_OR_L;
+                    DavQLCmd *or_l = (DavQLCmd*)(bcode->space + bcode->pos);
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+                    or_l->data.integer = nright + 1;
+                    
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_OR;
+                    cmd.data.integer = 0;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd += nleft + nright;
+                    break;
+                }
+                case DAVQL_LXOR: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_XOR;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_EQ: {
+                    cmd.type = DAVQL_CMD_OP_EQ;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEQ: {
+                    cmd.type = DAVQL_CMD_OP_NEQ;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LT: {
+                    cmd.type = DAVQL_CMD_OP_LT;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GT: {
+                    cmd.type = DAVQL_CMD_OP_GT;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LE: {
+                    cmd.type = DAVQL_CMD_OP_LE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GE: {
+                    cmd.type = DAVQL_CMD_OP_GE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LIKE: {
+                    cmd.type = DAVQL_CMD_OP_LIKE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_UNLIKE: {
+                    cmd.type = DAVQL_CMD_OP_UNLIKE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+        case DAVQL_FUNCCALL: {
+            switch(expr->op) {
+                case DAVQL_CALL: {
+                    int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+                    // TODO: count args
+                    DavQLExpression *funcid = expr->left;
+                    if(!funcid && funcid->type != DAVQL_IDENTIFIER) {
+                        // fail
+                        return -1;
+                    }
+                    
+                    // numargs
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = count_func_args(expr);
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    // TODO: resolve function name
+                    cmd.type = DAVQL_CMD_CALL;
+                    cmd.data.func = NULL;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd = 2;
+                    numcmd += nright;
+                    break;
+                }
+                case DAVQL_ARGLIST: {
+                    numcmd = 0;
+                    numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                    numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+    }
+    return numcmd;
+}
+
+UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList  *ap) {
+    UcxBuffer *bcode = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    if(!bcode) {
+        return NULL;
+    }
+    
+    if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) {
+        ucx_buffer_free(bcode);
+        return NULL;
+    }
+    
+    return bcode;
+}
+
+static int cmd_str_cmp(DavQLStackObj obj1, DavQLStackObj obj2, davqlcmdtype_t cmd) {
+    sstr_t s1 = obj1.type == 1 ?
+        sstrn(obj1.data.string, obj1.length) :
+        ucx_sprintf("%" PRId64, obj1.data.integer);
+    sstr_t s2 = obj1.type == 1 ?
+        sstrn(obj2.data.string, obj2.length) :
+        ucx_sprintf("%" PRId64, obj2.data.integer);
+    
+    int res = 0;
+    switch(cmd) {
+        case DAVQL_CMD_OP_EQ: {
+            res = sstrcmp(s1, s2) == 0;
+            break;
+        }
+        case DAVQL_CMD_OP_NEQ: {
+            res = sstrcmp(s1, s2) != 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LT: {
+            res = sstrcmp(s1, s2) < 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GT: {
+            res = sstrcmp(s1, s2) > 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LE: {
+            res  = sstrcmp(s1, s2) <= 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GE: {
+            res = sstrcmp(s1, s2) >= 0;
+            break;
+        }
+        default: break;
+    }
+    
+    if(obj1.type == 0) {
+        free(s1.ptr);
+    }
+    if(obj2.type == 0) {
+        free(s2.ptr);
+    }
+    
+    return res;
+}
+
+int dav_exec_expr(UcxBuffer *bcode, DavResource *res, DavQLStackObj *result) {
+    if(!bcode) {
+        result->type = 0;
+        result->length = 0;
+        result->data.integer = 1;
+        return 0;
+    }
+    
+    size_t count = bcode->pos / sizeof(DavQLCmd);
+    DavQLCmd *cmds = (DavQLCmd*)bcode->space;
+    
+    // create execution stack
+    size_t stsize = 64;
+    size_t stpos = 0;
+    DavQLStackObj *stack = calloc(stsize, sizeof(DavQLStackObj));
+#define DAVQL_PUSH(obj) \
+        if(stpos == stsize) { \
+            stsize += 64; \
+            DavQLStackObj *stack_newptr; \
+            stack_newptr = realloc(stack, stsize * sizeof(DavQLStackObj)); \
+            if(stack_newptr) { \
+                stack = stack_newptr; \
+            } else { \
+                free(stack); \
+                return -1; \
+            }\
+        } \
+        stack[stpos++] = obj;
+#define DAVQL_PUSH_INT(intval) \
+        { \
+            DavQLStackObj intobj; \
+            intobj.type = 0; \
+            intobj.length = 0; \
+            intobj.data.integer = intval; \
+            DAVQL_PUSH(intobj); \
+        }
+#define DAVQL_POP() stack[--stpos]
+    
+    DavQLStackObj obj;
+    int ret = 0;
+    for(size_t i=0;i<count;i++) {
+        DavQLCmd cmd = cmds[i];
+        switch(cmd.type) {
+            case DAVQL_CMD_INT: {
+                //printf("int %lld\n", cmd.data.integer);
+                obj.type = 0;
+                obj.length = 0;
+                obj.data.integer = cmd.data.integer;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_STRING: {
+                //printf("string \"%.*s\"\n", cmd.data.string.length, cmd.data.string.ptr);
+                obj.type = 1;
+                obj.length = cmd.data.string.length;
+                obj.data.string = cmd.data.string.ptr;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_TIMESTAMP: {
+                //printf("timestamp %d\n", cmd.data.timestamp);
+                obj.type = 0;
+                obj.length = 0;
+                obj.data.integer = (int64_t)cmd.data.timestamp;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_RES_IDENTIFIER: {
+                //char *rid[8] = {"name", "path", "href", "contentlength", "contenttype", "creationdate", "lastmodified", "iscollection"};
+                //printf("resprop %s\n", rid[cmd.data.resprop]);
+                switch(cmd.data.resprop) {
+                    case DAVQL_RES_NAME: {
+                        obj.type = 1;
+                        obj.length = strlen(res->name);
+                        obj.data.string = res->name;
+                        break;
+                    }
+                    case DAVQL_RES_PATH: {
+                        obj.type = 1;
+                        obj.length = strlen(res->path);
+                        obj.data.string = res->path;
+                        break;
+                    }
+                    case DAVQL_RES_HREF: {
+                        obj.type = 1;
+                        obj.length = strlen(res->href);
+                        obj.data.string = res->href;
+                        break;
+                    }
+                    case DAVQL_RES_CONTENTLENGTH: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->contentlength;
+                        break;
+                    }
+                    case DAVQL_RES_CONTENTTYPE: {
+                        obj.type = 1;
+                        obj.length = strlen(res->contenttype);
+                        obj.data.string = res->contenttype;
+                        break;
+                    }
+                    case DAVQL_RES_CREATIONDATE: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->creationdate;
+                        break;
+                    }
+                    case DAVQL_RES_LASTMODIFIED: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->lastmodified;
+                        break;
+                    }
+                    case DAVQL_RES_ISCOLLECTION: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->iscollection;
+                        break;
+                    }
+                }
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_PROP_IDENTIFIER: {
+                //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name);
+                //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                if(dav_xml_isstring(value)) {
+                    obj.type = 1;
+                    obj.length = (uint32_t)value->contentlength;
+                    obj.data.string = value->content;
+                } else {
+                    obj.type = 2;
+                    obj.length = 0;
+                    obj.data.node = value;
+                }
+                DAVQL_PUSH(obj);
+                break;
+            }
+            //case DAVQL_CMD_OP_UNARY_ADD: {
+            //    printf("uadd\n");
+            //    break;
+            //}
+            case DAVQL_CMD_OP_UNARY_SUB: {
+                //printf("usub\n");
+                obj = DAVQL_POP();
+                if(obj.type == 0) {
+                    obj.data.integer = -obj.data.integer;
+                    DAVQL_PUSH(obj);
+                } else {
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_UNARY_NEG: {
+                //printf("uneg\n");
+                obj = DAVQL_POP();
+                if(obj.type == 0) {
+                    obj.data.integer = obj.data.integer == 0 ? 1 : 0;
+                    DAVQL_PUSH(obj);
+                } else {
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_ADD: {
+                //printf("add\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer + obj2.data.integer);
+                } else {
+                    // TODO: string concat
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_SUB: {
+                //printf("sub\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer - obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_MUL: {
+                //printf("mul\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer * obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_DIV: {
+                //printf("div\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer / obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_AND: {
+                //printf("and\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer & obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_OR: {
+                //printf("or\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer | obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_XOR: {
+                //printf("xor\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer ^ obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_NOT: {
+                //printf("not\n");
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_AND: {
+                //printf("land\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(v1 && v2);
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_OR_L: {
+                //printf("or_l %d\n", cmd.data.integer);
+                DavQLStackObj obj1 = stack[stpos];
+                if((obj1.type == 0 && obj1.data.integer) || (obj1.type == 1 && obj1.data.string)) {
+                    stpos--;
+                    DAVQL_PUSH_INT(1);
+                    i += cmd.data.integer; // jump, skip right subtree of 'or'
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_OR: {
+                //printf("or\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(v1 || v2);
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_XOR: {
+                //printf("lxor\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(!v1 != !v2);
+                break;
+            }
+            case DAVQL_CMD_OP_EQ: {
+                //printf("eq\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer == obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_NEQ: {
+                //printf("neq\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer != obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LT: {
+                //printf("lt\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer < obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_GT: {
+                //printf("gt\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer > obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LE: {
+                //printf("le\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer <= obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_GE: {
+                //printf("ge\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer >= obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LIKE: {
+                //printf("like\n");
+                break;
+            }
+            case DAVQL_CMD_OP_UNLIKE: {
+                //printf("unlike\n");
+                break;
+            }
+            case DAVQL_CMD_CALL: {
+                //printf("call %x\n", cmd.data.func);
+                break;
+            }
+        }
+    }
+    
+    if(stpos == 1) {
+        *result = stack[0];
+    } else {
+        ret = -1;
+    }
+    free(stack);
+    
+    return ret;
+}
diff --git a/libidav/davqlexec.h b/libidav/davqlexec.h
new file mode 100644 (file)
index 0000000..11a6258
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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 DAVQLEXEC_H
+#define        DAVQLEXEC_H
+
+#include <stdarg.h>
+#include "davqlparser.h"
+#include "webdav.h"
+
+#include <ucx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+typedef struct DavQLCmd      DavQLCmd;
+typedef struct DavQLStackObj DavQLStackObj;
+typedef struct DavQLRes      DavQLRes;
+
+typedef struct DavQLArg      DavQLArg;
+typedef struct DavQLArgList  DavQLArgList;
+
+typedef void*(*davql_func)(); // TODO: interface?
+
+struct DavQLArg {
+    int type;
+    union DavQLArgValue{
+        int          d;
+        unsigned int u;
+        char         *s;
+        time_t       t;
+    } value;
+    DavQLArg *next;
+};
+
+struct DavQLArgList {
+    DavQLArg *first;
+    DavQLArg *current;
+};
+
+typedef enum {
+    DAVQL_OK = 0,
+    DAVQL_UNSUPPORTED_FORMATCHAR,
+    DAVQL_UNKNOWN_FORMATCHAR
+} davqlerror_t;
+
+typedef enum {
+    DAVQL_CMD_INT = 0,
+    DAVQL_CMD_STRING,
+    DAVQL_CMD_TIMESTAMP,
+    DAVQL_CMD_RES_IDENTIFIER,
+    DAVQL_CMD_PROP_IDENTIFIER,
+    //DAVQL_CMD_OP_UNARY_ADD,
+    DAVQL_CMD_OP_UNARY_SUB,
+    DAVQL_CMD_OP_UNARY_NEG,
+    DAVQL_CMD_OP_BINARY_ADD,
+    DAVQL_CMD_OP_BINARY_SUB,
+    DAVQL_CMD_OP_BINARY_MUL,
+    DAVQL_CMD_OP_BINARY_DIV,
+    DAVQL_CMD_OP_BINARY_AND,
+    DAVQL_CMD_OP_BINARY_OR,
+    DAVQL_CMD_OP_BINARY_XOR,
+    DAVQL_CMD_OP_LOGICAL_NOT,
+    DAVQL_CMD_OP_LOGICAL_AND,
+    DAVQL_CMD_OP_LOGICAL_OR_L,
+    DAVQL_CMD_OP_LOGICAL_OR,
+    DAVQL_CMD_OP_LOGICAL_XOR,
+    DAVQL_CMD_OP_EQ,
+    DAVQL_CMD_OP_NEQ,
+    DAVQL_CMD_OP_LT,
+    DAVQL_CMD_OP_GT,
+    DAVQL_CMD_OP_LE,
+    DAVQL_CMD_OP_GE,
+    DAVQL_CMD_OP_LIKE,
+    DAVQL_CMD_OP_UNLIKE,
+    DAVQL_CMD_CALL
+} davqlcmdtype_t;
+
+typedef enum {
+    DAVQL_RES_NAME = 0,
+    DAVQL_RES_PATH,
+    DAVQL_RES_HREF,
+    DAVQL_RES_CONTENTLENGTH,
+    DAVQL_RES_CONTENTTYPE,
+    DAVQL_RES_CREATIONDATE,
+    DAVQL_RES_LASTMODIFIED,
+    DAVQL_RES_ISCOLLECTION
+} davqlresprop_t;
+
+struct DavQLCmd {
+    davqlcmdtype_t type;
+    union DavQLCmdData {
+        int64_t        integer;
+        sstr_t         string;
+        time_t         timestamp;
+        davqlresprop_t resprop;
+        DavPropName    property;
+        davql_func     func;
+    } data;
+};
+
+struct DavQLStackObj {
+    int32_t  type; // 0: int, 1: string, 2: xml
+    uint32_t length;
+    union DavQLStackData {
+        int64_t    integer;
+        char       *string;
+        DavXmlNode *node;
+    } data;
+};
+
+struct DavQLRes {
+    DavResource *resource;
+    int depth;
+};
+
+typedef struct DavCompiledField {
+    char *ns;
+    char *name;
+    UcxBuffer *code;
+} DavCompiledField;
+
+typedef struct DavOrderCriterion {
+    int type; // 0: resprop, 1: property
+    union DavQLColumn {
+        davqlresprop_t resprop;
+        UcxKey property;
+    } column;
+    _Bool descending;
+} DavOrderCriterion;
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap);
+void dav_ql_free_arglist(DavQLArgList *args);
+
+int dav_ql_getarg_int(DavQLArgList *args);
+unsigned int dav_ql_getarg_uint(DavQLArgList *args);
+char* dav_ql_getarg_str(DavQLArgList *args);
+time_t dav_ql_getarg_time(DavQLArgList *args);
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...);
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap);
+
+UcxBuffer* dav_path_string(sstr_t src, DavQLArgList *args, davqlerror_t *error);
+sstr_t dav_format_string(UcxAllocator *a, sstr_t fstr, DavQLArgList *ap, davqlerror_t *error);
+
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap);
+
+int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop);
+
+UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap);
+
+int dav_exec_expr(UcxBuffer *bcode, DavResource *res, DavQLStackObj *result);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLEXEC_H */
+
diff --git a/libidav/davqlparser.c b/libidav/davqlparser.c
new file mode 100644 (file)
index 0000000..38f5694
--- /dev/null
@@ -0,0 +1,1817 @@
+/*
+ * 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 "davqlparser.h"
+#include <ucx/utils.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#define sfmtarg(s) ((int)(s).length), (s).ptr
+
+// ------------------------------------------------------------------------
+//                        D E B U G E R
+// ------------------------------------------------------------------------
+
+static const char* _map_querytype(davqltype_t type) {
+    switch(type) {
+    case DAVQL_ERROR: return "ERROR";
+    case DAVQL_SELECT: return "SELECT";
+    case DAVQL_SET: return "SET";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_exprtype(davqlexprtype_t type) {
+    switch(type) {
+    case DAVQL_UNDEFINED_TYPE: return "undefined";
+    case DAVQL_NUMBER: return "NUMBER";
+    case DAVQL_STRING: return "STRING";
+    case DAVQL_TIMESTAMP: return "TIMESTAMP";
+    case DAVQL_IDENTIFIER: return "IDENTIFIER";
+    case DAVQL_UNARY: return "UNARY";
+    case DAVQL_BINARY: return "BINARY";
+    case DAVQL_LOGICAL: return "LOGICAL";
+    case DAVQL_FUNCCALL: return "FUNCCALL";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_specialfield(int info) {
+    switch(info) {
+    case 0: return "";
+    case 1: return "with wildcard";
+    case 2: return "(resource data only)";
+    default: return "with mysterious identifier";
+    }
+}
+
+static const char* _map_operator(davqloperator_t op) {
+    // don't use string array, because enum values may change
+    switch(op) {
+    case DAVQL_NOOP: return "no operator";
+    case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ",";
+    case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-";
+    case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/";
+    case DAVQL_AND: return "&"; case DAVQL_OR: return "|";
+    case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~";
+    case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND";
+    case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR";
+    case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!=";
+    case DAVQL_LT: return "<"; case DAVQL_GT: return ">";
+    case DAVQL_LE: return "<="; case DAVQL_GE: return ">=";
+    case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE";
+    default: return "unknown";
+    }
+}
+
+static void dav_debug_ql_fnames_print(DavQLStatement *stmt) {
+    if (stmt->fields) {
+        printf("Field names: ");
+        UCX_FOREACH(field, stmt->fields) {
+            DavQLField *f = field->data;
+            printf("%.*s, ", sfmtarg(f->name));
+        }
+        printf("\b\b  \b\b\n");
+    }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+    // Basic information
+    size_t fieldcount = ucx_list_size(stmt->fields);
+    int specialfield = 0;
+    if (stmt->fields) {
+        DavQLField* firstfield = (DavQLField*)stmt->fields->data;
+        if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+            switch (firstfield->expr->srctext.ptr[0]) {
+            case '*': specialfield = 1; break;
+            case '-': specialfield = 2; break;
+            }
+        }
+    }
+    if (specialfield) {
+        fieldcount--;
+    }
+    printf("Statement: %.*s\nType: %s\nField count: %zu %s\n",
+        sfmtarg(stmt->srctext),
+        _map_querytype(stmt->type),
+        fieldcount,
+        _map_specialfield(specialfield));
+    
+    dav_debug_ql_fnames_print(stmt);
+    printf("Path: %.*s\nHas where clause: %s\n",
+        sfmtarg(stmt->path),
+        stmt->where ? "yes" : "no");
+    
+    // WITH attributes
+    if (stmt->depth == DAV_DEPTH_INFINITY) {
+        printf("Depth: infinity\n");
+    } else if (stmt->depth == DAV_DEPTH_PLACEHOLDER) {
+        printf("Depth: placeholder\n");
+    } else {
+        printf("Depth: %d\n", stmt->depth);
+    }
+    
+    // order by clause
+    printf("Order by: ");
+    if (stmt->orderby) {
+        UCX_FOREACH(crit, stmt->orderby) {
+            DavQLOrderCriterion *critdata = crit->data;
+            printf("%.*s %s%s", sfmtarg(critdata->column->srctext),
+                critdata->descending ? "desc" : "asc",
+                crit->next ? ", " : "\n");
+        }
+    } else {
+        printf("nothing\n");
+    }
+    
+    // error messages
+    if (stmt->errorcode) {
+        printf("\nError code: %d\nError: %s\n",
+            stmt->errorcode, stmt->errormessage);
+    }
+}
+
+static int dav_debug_ql_expr_selected(DavQLExpression *expr) {
+    if (!expr) {
+        printf("Currently no expression selected.\n");
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static void dav_debug_ql_expr_print(DavQLExpression *expr) {
+    if (dav_debug_ql_expr_selected(expr)) {
+        sstr_t empty = ST("(empty)");
+        printf(
+            "Text: %.*s\nType: %s\nOperator: %s\n",
+            sfmtarg(expr->srctext),
+            _map_exprtype(expr->type),
+            _map_operator(expr->op));
+        if (expr->left || expr->right) {
+            printf("Left hand: %.*s\nRight hand: %.*s\n",
+                sfmtarg(expr->left?expr->left->srctext:empty),
+                sfmtarg(expr->right?expr->right->srctext:empty));
+        }
+    }
+}
+
+static void dav_debug_ql_field_print(DavQLField *field) {
+    if (field) {
+        printf("Name: %.*s\n", sfmtarg(field->name));
+        if (field->expr) {
+            dav_debug_ql_expr_print(field->expr);
+        } else {
+            printf("No expression.\n");
+        }
+    } else {
+        printf("No field selected.\n");
+    }
+}
+
+static void dav_debug_ql_tree_print(DavQLExpression *expr, int depth) {
+    if (expr) {
+        if (expr->left) {
+            printf("%*c%s\n", depth, ' ', _map_operator(expr->op));
+            dav_debug_ql_tree_print(expr->left, depth+1);
+            dav_debug_ql_tree_print(expr->right, depth+1);
+        } else if (expr->type == DAVQL_UNARY) {
+            printf("%*c%s %.*s\n", depth, ' ', _map_operator(expr->op),
+                sfmtarg(expr->srctext));
+        } else {
+            printf("%*c%.*s\n", depth, ' ', sfmtarg(expr->srctext));
+        }
+    }
+}
+
+#define DQLD_CMD_Q     0
+#define DQLD_CMD_PS    1
+#define DQLD_CMD_PE    2
+#define DQLD_CMD_PF    3
+#define DQLD_CMD_PT    4
+#define DQLD_CMD_F    10
+#define DQLD_CMD_W    11
+#define DQLD_CMD_O    12
+#define DQLD_CMD_L    21
+#define DQLD_CMD_R    22
+#define DQLD_CMD_N    23
+#define DQLD_CMD_P    24
+#define DQLD_CMD_H   100
+
+static int dav_debug_ql_command() {
+    printf("> ");
+    
+    char buffer[8];
+    fgets(buffer, 8, stdin);
+     // discard remaining chars
+    if (!strchr(buffer, '\n')) {
+        int chr;
+        while ((chr = fgetc(stdin) != '\n') && chr != EOF);
+    }
+    
+    if (!strcmp(buffer, "q\n")) {
+        return DQLD_CMD_Q;
+    } else if (!strcmp(buffer, "ps\n")) {
+        return DQLD_CMD_PS;
+    } else if (!strcmp(buffer, "pe\n")) {
+        return DQLD_CMD_PE;
+    } else if (!strcmp(buffer, "pf\n")) {
+        return DQLD_CMD_PF;
+    } else if (!strcmp(buffer, "pt\n")) {
+        return DQLD_CMD_PT;
+    } else if (!strcmp(buffer, "l\n")) {
+        return DQLD_CMD_L;
+    } else if (!strcmp(buffer, "r\n")) {
+        return DQLD_CMD_R;
+    } else if (!strcmp(buffer, "h\n")) {
+        return DQLD_CMD_H;
+    } else if (!strcmp(buffer, "f\n")) {
+        return DQLD_CMD_F;
+    } else if (!strcmp(buffer, "w\n")) {
+        return DQLD_CMD_W;
+    } else if (!strcmp(buffer, "o\n")) {
+        return DQLD_CMD_O;
+    } else if (!strcmp(buffer, "n\n")) {
+        return DQLD_CMD_N;
+    } else if (!strcmp(buffer, "p\n")) {
+        return DQLD_CMD_P;
+    } else {
+        return -1;
+    }
+}
+
+void dav_debug_statement(DavQLStatement *stmt) {
+    if (!stmt) {
+        fprintf(stderr, "Debug DavQLStatement failed: null pointer");
+        return;
+    }
+
+    printf("Starting DavQL debugger (type 'h' for help)...\n\n");
+    dav_debug_ql_stmt_print(stmt);
+    
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    DavQLExpression *examineexpr = NULL;
+    UcxList *examineelem = NULL;
+    int examineclause = 0;
+    
+    while(1) {
+        int cmd = dav_debug_ql_command();
+        switch (cmd) {
+        case DQLD_CMD_Q: return;
+        case DQLD_CMD_PS: dav_debug_ql_stmt_print(stmt); break;
+        case DQLD_CMD_PE: dav_debug_ql_expr_print(examineexpr); break;
+        case DQLD_CMD_PT: dav_debug_ql_tree_print(examineexpr, 1); break;
+        case DQLD_CMD_PF: dav_debug_ql_fnames_print(stmt); break;
+        case DQLD_CMD_F:
+            examineclause = DQLD_CMD_F;
+            examineelem = stmt->fields;
+            if (stmt->fields) {
+                DavQLField* field = ((DavQLField*)stmt->fields->data);
+                examineexpr = field->expr;
+                dav_debug_ql_field_print(field);
+            } else {
+                examineexpr = NULL;
+            }
+            break;
+        case DQLD_CMD_W:
+            examineclause = 0; examineelem = NULL;
+            examineexpr = stmt->where;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_O:
+            examineclause = DQLD_CMD_O;
+            examineelem = stmt->orderby;
+            examineexpr = stmt->orderby ?
+                ((DavQLOrderCriterion*)stmt->orderby->data)->column : NULL;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            if (examineelem) {
+                UcxList *newelem = (cmd == DQLD_CMD_N ?
+                    examineelem->next : examineelem->prev);
+                if (newelem) {
+                    examineelem = newelem;
+                    if (examineclause == DQLD_CMD_O) {
+                        examineexpr = ((DavQLOrderCriterion*)
+                            examineelem->data)->column;
+                        dav_debug_ql_expr_print(examineexpr);
+                    } else if (examineclause == DQLD_CMD_F) {
+                        DavQLField* field = (DavQLField*)examineelem->data;
+                        examineexpr = field->expr;
+                        dav_debug_ql_field_print(field);
+                    } else {
+                        printf("Examining unknown clause type.");
+                    }
+                } else {
+                    printf("Reached end of list.\n");
+                }
+            } else {
+                printf("Currently not examining an expression list.\n");
+            }
+            break;
+        case DQLD_CMD_L:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->left) {
+                    examineexpr = examineexpr->left;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no left subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_R:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->right) {
+                    examineexpr = examineexpr->right;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no right subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_H:
+            printf(
+                "\nCommands:\n"
+                "ps:  print statement information\n"
+                "o:   examine order by clause\n"
+                "f:   examine field list\n"
+                "pf:  print field names\n"
+                "w:   examine where clause\n"
+                "n:   examine next expression "
+                    "(in order by clause or field list)\n"
+                "p:   examine previous expression "
+                    "(in order by clause or field list)\n"
+                "q:   quit\n\n"
+                "\nExpression examination:\n"
+                "pe:  print expression information\n"
+                "pt:  print full syntax tree of current (sub-)expression\n"
+                "l:   enter left subtree\n"
+                "r:   enter right subtree\n");
+            break;
+        default: printf("unknown command\n");
+        }
+    }
+}
+
+// ------------------------------------------------------------------------
+//                         P A R S E R
+// ------------------------------------------------------------------------
+
+#define _error_context "(%.*s[->]%.*s%.*s)"
+#define _error_invalid "invalid statement"
+#define _error_out_of_memory "out of memory"
+#define _error_unexpected_token "unexpected token " _error_context
+#define _error_invalid_token "invalid token " _error_context
+#define _error_missing_path "expected path " _error_context
+#define _error_missing_from "expecting FROM keyword " _error_context
+#define _error_missing_at "expecting AT keyword " _error_context
+#define _error_missing_by "expecting BY keyword " _error_context
+#define _error_missing_as "expecting alias ('as <identifier>') " _error_context
+#define _error_missing_identifier "expecting identifier " _error_context
+#define _error_missing_par "missing closed parenthesis " _error_context
+#define _error_missing_assign "expecting assignment ('=') " _error_context
+#define _error_missing_where "SET statements must have a WHERE clause or " \
+                        "explicitly use ANYWHERE " _error_context
+#define _error_invalid_depth "invalid depth " _error_context
+#define _error_missing_expr "missing expression " _error_context
+#define _error_invalid_expr "invalid expression " _error_context
+#define _error_invalid_unary_op "invalid unary operator "  _error_context
+#define _error_invalid_logical_op "invalid logical operator "  _error_context
+#define _error_invalid_fmtspec "invalid format specifier " _error_context
+#define _error_invalid_string "string expected " _error_context
+#define _error_invalid_order_criterion "invalid order criterion " _error_context
+
+#define token_sstr(token) (((DavQLToken*)(token)->data)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, UcxList *token) {
+    
+    // we try to achieve two things: get as many information as possible
+    // and recover the concrete source string (and not the token strings)
+    sstr_t emptystring = ST("");
+    sstr_t prev = token->prev ? (token->prev->prev ?
+        token_sstr(token->prev->prev) : token_sstr(token->prev))
+        : emptystring;
+    sstr_t tokenstr = token_sstr(token);
+    sstr_t next = token->next ? (token->next->next ?
+        token_sstr(token->next->next) : token_sstr(token->next))
+        : emptystring;
+    
+    int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr;
+    char *pn = tokenstr.ptr + tokenstr.length;
+    int ln = next.ptr+next.length - pn;
+    
+    stmt->errorcode = errorcode;
+    stmt->errormessage = ucx_sprintf(errormsg,
+        lp, prev.ptr,
+        sfmtarg(tokenstr),
+        ln, pn).ptr;
+}
+
+#define dqlsec_alloc_failed(ptr, stmt)                                  \
+                    if (!(ptr)) do {                                    \
+                        (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;  \
+                        return 0;                                       \
+                    } while(0)
+#define dqlsec_malloc(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = malloc(sizeof(type)), stmt)
+#define dqlsec_mallocz(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = calloc(1, sizeof(type)), stmt)
+
+#define dqlsec_list_append_or_free(stmt, list, data)            \
+    do {                                                        \
+        UcxList *_dqlsecbak_ = list;                            \
+        list = ucx_list_append(list, data);                     \
+        if (!list) {                                            \
+            free(data);                                         \
+            data = NULL;                                        \
+            (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;      \
+            list = _dqlsecbak_;                                 \
+            return 0;                                           \
+        }                                                       \
+    } while(0)
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+    sstr_t keywords[] ={ST("select"), ST("set"), ST("from"), ST("at"), ST("as"),
+        ST("where"), ST("anywhere"), ST("like"), ST("unlike"), ST("and"),
+        ST("or"), ST("not"), ST("xor"), ST("with"), ST("infinity"),
+        ST("order"), ST("by"), ST("asc"), ST("desc")
+    };
+    for (int i = 0 ; i < sizeof(keywords)/sizeof(sstr_t) ; i++) {
+        if (!sstrcasecmp(token->value, keywords[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+    sstr_t operators[] = {ST("and"), ST("or"), ST("not"), ST("xor"),
+        ST("like"), ST("unlike")
+    };
+    for (int i = 0 ; i < sizeof(operators)/sizeof(sstr_t) ; i++) {
+        if (!sstrcasecmp(token->value, operators[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static UcxList* dav_parse_add_token(UcxList *tokenlist, DavQLToken *token) {
+    
+    // determine token class (order of if-statements is very important!)
+    char firstchar = token->value.ptr[0];
+
+    if (isdigit(firstchar)) {
+        token->tokenclass = DAVQL_TOKEN_NUMBER;
+        // check, if all characters are digits
+        for (size_t i = 1 ; i < token->value.length ; i++) {
+            if (!isdigit(token->value.ptr[i])) {
+                token->tokenclass = DAVQL_TOKEN_INVALID;
+                break;
+            }
+        }
+    } else if (firstchar == '%') {
+        token->tokenclass = DAVQL_TOKEN_FMTSPEC;
+    } else if (token->value.length == 1) {
+        switch (firstchar) {
+        case '(': token->tokenclass = DAVQL_TOKEN_OPENP; break;
+        case ')': token->tokenclass = DAVQL_TOKEN_CLOSEP; break;
+        case ',': token->tokenclass = DAVQL_TOKEN_COMMA; break;
+        default:
+            token->tokenclass = strchr(special_token_symbols, firstchar) ?
+                DAVQL_TOKEN_OPERATOR : DAVQL_TOKEN_IDENTIFIER;
+        }
+    } else if (islongoperator(token)) {
+        token->tokenclass = DAVQL_TOKEN_OPERATOR;
+    } else if (firstchar == '\'') {
+        token->tokenclass = DAVQL_TOKEN_STRING;
+    } else if (firstchar == '`') {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+    } else if (iskeyword(token)) {
+        token->tokenclass = DAVQL_TOKEN_KEYWORD;
+    } else {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+        // TODO: check for illegal characters
+    }
+    
+    // remove quotes (extreme cool feature)
+    if (token->tokenclass == DAVQL_TOKEN_STRING ||
+        (token->tokenclass == DAVQL_TOKEN_IDENTIFIER && firstchar == '`')) {
+        
+        char lastchar = token->value.ptr[token->value.length-1];
+        if (firstchar == lastchar) {
+            token->value.ptr++;
+            token->value.length -= 2;
+        } else {
+            token->tokenclass = DAVQL_TOKEN_INVALID;
+        }
+    }
+    
+    
+    UcxList *ret = ucx_list_append(tokenlist, token);
+    if (ret) {
+        return ret;
+    } else {
+        ucx_list_free(tokenlist);
+        return NULL;
+    }
+}
+
+static UcxList* dav_parse_tokenize(sstr_t src) {
+#define alloc_token() do {token = malloc(sizeof(DavQLToken));\
+        if(!token) {ucx_list_free(tokens); return NULL;}} while(0)
+#define add_token() do {tokens = dav_parse_add_token(tokens, token); \
+        if(!tokens) {return NULL;}} while(0)
+    UcxList *tokens = NULL;
+    
+    DavQLToken *token = NULL;
+    char insequence = '\0';
+    for (size_t i = 0 ; i < src.length ; i++) {
+        // quoted strings / identifiers are a single token
+        if (src.ptr[i] == '\'' || src.ptr[i] == '`') {
+            if (src.ptr[i] == insequence) {
+                // lookahead for escaped string quotes
+                if (src.ptr[i] == '\'' && i+2 < src.length &&
+                    src.ptr[i+1] == src.ptr[i] && src.ptr[i+2] == src.ptr[i]) {
+                    token->value.length += 3;
+                    i += 2;
+                } else {
+                    // add quoted token to list
+                    token->value.length++;
+                    add_token();
+                    token = NULL;
+                    insequence = '\0';
+                }
+            } else if (insequence == '\0') {
+                insequence = src.ptr[i];
+                // always create new token for quoted strings
+                if (token) {
+                    add_token();
+                }
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 1;
+            } else {
+                // add other kind of quotes to token
+                token->value.length++;
+            }
+        } else if (insequence) {
+            token->value.length++;
+        } else if (isspace(src.ptr[i])) {
+            // add token before spaces to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+        } else if (strchr(special_token_symbols, src.ptr[i])) {
+            // add token before special symbol to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+            // add special symbol as single token to list
+            alloc_token();
+            token->value.ptr = src.ptr + i;
+            token->value.length = 1;
+            add_token();
+            // set tokenizer ready to read more tokens
+            token = NULL;
+        } else {
+            // if this is a new token, create memory for it
+            if (!token) {
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 0;
+            }
+            // extend token length when reading more bytes
+            token->value.length++;
+        }
+    }
+    
+    if (token) {
+        add_token();
+    }
+    
+    alloc_token();
+    token->tokenclass = DAVQL_TOKEN_END;
+    token->value = S("");
+    UcxList *ret = ucx_list_append(tokens, token);
+    if (ret) {
+        return ret;
+    } else {
+        ucx_list_free(tokens);
+        return NULL;
+    }
+#undef alloc_token
+#undef add_token
+}
+
+static void dav_free_expression(DavQLExpression *expr) {
+    if (expr) {
+        if (expr->left) {
+            dav_free_expression(expr->left);
+        }
+        if (expr->right) {
+            dav_free_expression(expr->right);
+        }
+        free(expr);
+    }
+}
+
+static void dav_free_field(DavQLField *field) {
+    dav_free_expression(field->expr);
+    free(field);
+}
+
+static void dav_free_order_criterion(DavQLOrderCriterion *crit) {
+    if (crit->column) { // do it null-safe though column is expected to be set
+        dav_free_expression(crit->column);
+    }
+    free(crit);
+}
+
+#define token_is(token, expectedclass) (token && \
+    (((DavQLToken*)(token)->data)->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+    !sstrcasecmp(((DavQLToken*)(token)->data)->value, S(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,UcxList*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv,
+        exprparser_f parseR) {
+    
+    if (!token) {
+        return 0;
+    }
+    
+    int total_consumed = 0, consumed;
+    
+    // save temporarily on stack (copy to heap later on)
+    DavQLExpression left, right;
+    
+    // RULE:    LEFT, [Operator, RIGHT]
+    memset(&left, 0, sizeof(DavQLExpression));
+    consumed = parseL(stmt, token, &left);
+    if (!consumed || stmt->errorcode) {
+        return 0;
+    }
+    total_consumed += consumed;
+    token = ucx_list_get(token, consumed);
+
+    char *op;
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) &&
+            (op = strchr(opc, token_sstr(token).ptr[0]))) {
+        expr->op = opv[op-opc];
+        expr->type = DAVQL_BINARY;
+        total_consumed++;
+        token = token->next;
+        memset(&right, 0, sizeof(DavQLExpression));
+        consumed = parseR(stmt, token, &right);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+        total_consumed += consumed;
+    }
+    
+    if (expr->op == DAVQL_NOOP) {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    } else {
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &left, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &right, sizeof(DavQLExpression));
+        
+        expr->srctext.ptr = expr->left->srctext.ptr;
+        expr->srctext.length = 
+            expr->right->srctext.ptr -
+            expr->left->srctext.ptr + expr->right->srctext.length;
+    }
+    
+    return total_consumed;
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, sstr_t str) {
+    int placeholder = 0;
+    for (size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        if (placeholder) {
+            if (c != '%') {
+                stmt->args = ucx_list_append(
+                        stmt->args,
+                        (void*)(intptr_t)c);
+            }
+            placeholder = 0;
+        } else if (c == '%') {
+            placeholder = 1;
+        }
+    }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext = token_sstr(token);
+    if (token_is(token, DAVQL_TOKEN_NUMBER)) {
+        expr->type = DAVQL_NUMBER;
+    } else if (token_is(token, DAVQL_TOKEN_STRING)) {
+        expr->type = DAVQL_STRING;
+        // check for format specifiers and add args
+        dav_add_fmt_args(stmt, expr->srctext);
+    } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) {
+        expr->type = DAVQL_TIMESTAMP;
+    } else if (token_is(token, DAVQL_TOKEN_FMTSPEC)
+            && expr->srctext.length == 2) {
+        switch (expr->srctext.ptr[1]) {
+        case 'd': expr->type = DAVQL_NUMBER; break;
+        case 's': expr->type = DAVQL_STRING; break;
+        case 't': expr->type = DAVQL_TIMESTAMP; break;
+        default:
+            dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+                _error_invalid_fmtspec, stmt, token);
+            return 0;
+        }
+        // add fmtspec type to query arg list
+        stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)expr->srctext.ptr[1]);
+    } else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext.ptr = token_sstr(token).ptr;
+    expr->srctext.length = 0;
+    expr->left = expr->right = NULL; // in case we fail, we want them to be sane
+    
+    int total_consumed = 0;
+    
+    // RULE:    Expression, {",", Expression};
+    DavQLExpression *arglist = expr;
+    DavQLExpression arg;
+    char *lastchar = expr->srctext.ptr;
+    int consumed;
+    do {
+        memset(&arg, 0, sizeof(DavQLExpression));
+        consumed = dav_parse_expression(stmt, token, &arg);
+        if (consumed) {
+            lastchar = arg.srctext.ptr + arg.srctext.length;
+            total_consumed += consumed;
+            token = ucx_list_get(token, consumed);
+            // look ahead for a comma
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++;
+                token = token->next;
+                /* we have more arguments, so put the current argument to the
+                 * left subtree and create a new node to the right
+                 */
+                dqlsec_malloc(stmt, arglist->left, DavQLExpression);
+                memcpy(arglist->left, &arg, sizeof(DavQLExpression));
+                arglist->srctext.ptr = arg.srctext.ptr;
+                arglist->op = DAVQL_ARGLIST;
+                arglist->type = DAVQL_FUNCCALL;
+                dqlsec_mallocz(stmt, arglist->right, DavQLExpression);
+                arglist = arglist->right;
+            } else {
+                // this was the last argument, so write it to the current node
+                memcpy(arglist, &arg, sizeof(DavQLExpression));
+                consumed = 0;
+            }
+        }
+    } while (consumed && !stmt->errorcode);
+    
+    // recover source text
+    arglist = expr;
+    while (arglist && arglist->type == DAVQL_FUNCCALL) {
+        arglist->srctext.length = lastchar - arglist->srctext.ptr;
+        arglist = arglist->right;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    // RULE:    Identifier, "(", ArgumentList, ")";
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER) &&
+            token_is(token->next, DAVQL_TOKEN_OPENP)) {
+
+        expr->type = DAVQL_FUNCCALL;
+        expr->op = DAVQL_CALL;
+        
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->left->type = DAVQL_IDENTIFIER;
+        expr->left->srctext = token_sstr(token);
+        expr->right = NULL;
+        
+        token = token->next->next;
+        
+        DavQLExpression arg;
+        int argtokens = dav_parse_arglist(stmt, token, &arg);
+        if (stmt->errorcode) {
+            // if an error occurred while parsing the arglist, return now
+            return 2;
+        }
+        if (argtokens) {
+            token = ucx_list_get(token, argtokens);
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &arg, sizeof(DavQLExpression));
+        } else {
+            // arg list may be empty
+            expr->right = NULL;
+        }
+        
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            return 3 + argtokens;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                stmt, token);
+            return 2; // it MUST be a function call, but it is invalid
+        }
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    UcxList *firsttoken = token; // save for srctext recovery
+    
+    DavQLExpression* atom = expr;
+    int total_consumed = 0;
+   
+    // optional unary operator
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        char *op = strchr("+-~", token_sstr(token).ptr[0]);
+        if (op) {
+            expr->type = DAVQL_UNARY;
+            switch (*op) {
+            case '+': expr->op = DAVQL_ADD; break;
+            case '-': expr->op = DAVQL_SUB; break;
+            case '~': expr->op = DAVQL_NEG; break;
+            }
+            dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+            atom = expr->left;
+            total_consumed++;
+            token = token->next;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP,
+                _error_invalid_unary_op, stmt, token);
+            return 0;
+        }
+    }
+    
+    // RULE:    (ParExpression | AtomicExpression)
+    if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        token = token->next; total_consumed++;
+        // RULE:    "(", Expression, ")"
+        int consumed = dav_parse_expression(stmt, token, atom);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+                _error_invalid_expr, stmt, token);
+            return 0;
+        }
+        token = ucx_list_get(token, consumed);
+        total_consumed += consumed;
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            token = token->next; total_consumed++;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR,
+                _error_missing_par, stmt, token);
+            return 0;
+        }
+    } else {
+        // RULE:    FunctionCall
+        int consumed = dav_parse_funccall(stmt, token, atom);
+        if (consumed) {
+            total_consumed += consumed;
+        } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+            // RULE:    Identifier
+            total_consumed++;
+            atom->type = DAVQL_IDENTIFIER;
+            atom->srctext = token_sstr(token);
+        } else {
+            // RULE:    Literal
+            total_consumed += dav_parse_literal(stmt, token, atom);
+        }
+    }
+    
+    // recover source text
+    expr->srctext.ptr = token_sstr(firsttoken).ptr;
+    if (total_consumed > 0) {
+        sstr_t lasttoken =
+            token_sstr(ucx_list_get(firsttoken, total_consumed-1));
+        expr->srctext.length =
+            lasttoken.ptr - expr->srctext.ptr + lasttoken.length;
+    } else {
+        // the expression should not be used anyway, but we want to be safe
+        expr->srctext.length = 0;
+    }
+    
+    
+    return total_consumed;
+}
+
+static int dav_parse_bitexpr(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_unary_expr,
+        "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR},
+        dav_parse_bitexpr);
+}
+
+static int dav_parse_multexpr(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_bitexpr,
+        "*/", (int[]){DAVQL_MUL, DAVQL_DIV},
+        dav_parse_multexpr);
+}
+
+static int dav_parse_expression(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_multexpr,
+        "+-", (int[]){DAVQL_ADD, DAVQL_SUB},
+        dav_parse_expression);
+}
+
+static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token,
+        DavQLField *field) {
+    int total_consumed = 0, consumed;
+    
+    // RULE:    Expression, " as ", Identifier;
+    DavQLExpression *expr;
+    dqlsec_mallocz(stmt, expr, DavQLExpression);
+    consumed = dav_parse_expression(stmt, token, expr);
+    if (stmt->errorcode) {
+        dav_free_expression(expr);
+        return 0;
+    }
+    if (expr->type == DAVQL_UNDEFINED_TYPE) {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+            _error_invalid_expr, stmt, token);
+        return 0;
+    }
+
+    token = ucx_list_get(token, consumed);
+    total_consumed += consumed;    
+    
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) {
+        token = token->next; total_consumed++;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_as, stmt, token);
+        return 0;
+    }
+
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+        field->name = token_sstr(token);
+        field->expr = expr;
+        return total_consumed + 1;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_identifier, stmt, token);
+        return 0;
+    }
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) {
+    
+    // RULE:    "-"
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        dqlsec_list_append_or_free(stmt, stmt->fields, field);
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        return 1;
+    }
+    
+    // RULE:    "*", {",", NamedExpression}
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        dqlsec_list_append_or_free(stmt, stmt->fields, field);
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        
+        int total_consumed = 0;
+        int consumed = 1;
+        
+        do {
+            token = ucx_list_get(token, consumed);
+            total_consumed += consumed;
+            
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++; token = token->next;
+                DavQLField localfield;
+                consumed = dav_parse_named_field(stmt, token, &localfield);
+                if (!stmt->errorcode && consumed) {
+                    DavQLField *field;
+                    dqlsec_malloc(stmt, field, DavQLField);
+                    memcpy(field, &localfield, sizeof(DavQLField));
+                    dqlsec_list_append_or_free(stmt, stmt->fields, field);
+                }                
+            } else {
+                consumed = 0;
+            }
+        } while (consumed > 0);
+        
+        return total_consumed;
+    }
+    
+    // RULE:    FieldExpression, {",", FieldExpression}
+    {
+        int total_consumed = 0, consumed;
+        do {
+            // RULE:    NamedField | Identifier
+            DavQLField localfield;
+            consumed = dav_parse_named_field(stmt, token, &localfield);
+            if (consumed) {
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                memcpy(field, &localfield, sizeof(DavQLField));
+                dqlsec_list_append_or_free(stmt, stmt->fields, field);
+                token = ucx_list_get(token, consumed);
+                total_consumed += consumed;
+            } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)
+                // look ahead, if the field is JUST the identifier
+                && (token_is(token->next, DAVQL_TOKEN_COMMA) ||
+                    tokenvalue_is(token->next, "from"))) {
+                
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+                field->expr->type = DAVQL_IDENTIFIER;
+                field->expr->srctext = field->name = token_sstr(token);
+                dqlsec_list_append_or_free(stmt, stmt->fields, field);
+
+                consumed = 1;
+                total_consumed++;
+                token = token->next;
+                
+                // we found a valid solution, so erase any errors
+                stmt->errorcode = 0;
+                if (stmt->errormessage) {
+                    free(stmt->errormessage);
+                    stmt->errormessage = NULL;
+                }
+            } else {
+                // dav_parse_named_field has already thrown a good error
+                consumed = 0;
+            }
+            
+            // field has been parsed, now try to get a comma
+            if (consumed) {
+                consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+                if (consumed) {
+                    token = token->next;
+                    total_consumed++;
+                }
+            }
+        } while (consumed);
+
+        return total_consumed;
+    }
+}
+
+// forward declaration
+static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token,
+        DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token,
+        DavQLExpression *expr) {
+    
+    expr->type = DAVQL_LOGICAL;
+    expr->srctext = token_sstr(token);
+    
+    int total_consumed = 0;
+
+    DavQLExpression bexpr;
+    memset(&bexpr, 0, sizeof(DavQLExpression));
+    total_consumed = dav_parse_expression(stmt, token, &bexpr);
+    if (!total_consumed || stmt->errorcode) {
+        return 0;
+    }
+    token = ucx_list_get(token, total_consumed);
+
+    UcxList* optok = token;
+    // RULE:    Expression, (" like " | " unlike "), String
+    if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok,
+            "like") || tokenvalue_is(optok, "unlike"))) {
+
+        total_consumed++;
+        token = token->next;
+        if (token_is(token, DAVQL_TOKEN_STRING)) {
+            expr->op = tokenvalue_is(optok, "like") ?
+                DAVQL_LIKE : DAVQL_UNLIKE;
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+            dqlsec_mallocz(stmt, expr->right, DavQLExpression);
+            expr->right->type = DAVQL_STRING;
+            expr->right->srctext = token_sstr(token);
+            expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+            
+            // fmt args
+            dav_add_fmt_args(stmt, expr->right->srctext);
+
+            return total_consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_STRING,
+                _error_invalid_string, stmt, token);
+            return 0;
+        }        
+    }
+    // RULE:    Expression, Comparison, Expression
+    else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (
+            tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") ||
+            tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) {
+
+        total_consumed++;
+        token = token->next;
+
+        if (tokenvalue_is(optok, "=")) {
+            expr->op = DAVQL_EQ;
+        } else {
+            if (tokenvalue_is(token, "=")) {
+                if (tokenvalue_is(optok, "!")) {
+                    expr->op = DAVQL_NEQ;
+                } else if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LE;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GE;
+                }
+                total_consumed++;
+                token = token->next;
+            } else {
+                if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LT;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GT;
+                }
+            }
+        }
+
+        DavQLExpression rexpr;
+        memset(&rexpr, 0, sizeof(DavQLExpression));
+        int consumed = dav_parse_expression(stmt, token, &rexpr);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(
+                DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+
+        total_consumed += consumed;
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &rexpr, sizeof(DavQLExpression));
+        
+        expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+
+        return total_consumed;
+    }
+    // RULE:    FunctionCall | Identifier;
+    else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) {
+        memcpy(expr, &bexpr, sizeof(DavQLExpression));
+
+        return total_consumed;
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token,
+        DavQLExpression *expr) {
+    
+    // RULE:    "not ", LogicalExpression
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) {
+        expr->type = DAVQL_LOGICAL;
+        expr->op = DAVQL_NOT;
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->srctext = token_sstr(token);
+
+        token = token->next;
+        int consumed = dav_parse_bool_expr(stmt, token, expr->left);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (consumed) {
+            sstr_t lasttok = token_sstr(ucx_list_get(token, consumed-1));
+            expr->srctext.length =
+                lasttok.ptr - expr->srctext.ptr + lasttok.length;
+            return consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+    }
+    // RULE:    "(", LogicalExpression, ")"
+    else if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        int consumed = dav_parse_logical_expr(stmt, token->next, expr);
+        if (consumed) {
+            token = ucx_list_get(token->next, consumed);
+
+            if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+                token = token->next;
+                return consumed + 2;
+            } else {
+                dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                    stmt, token);
+                return 0;
+            }
+        } else {
+            // don't handle errors here, we can also try a boolean primary
+            stmt->errorcode = 0;
+            if (stmt->errormessage) {
+                free(stmt->errormessage);
+            }
+        }
+    }
+    
+    // RULE:    BooleanPrimary
+    return dav_parse_bool_prim(stmt, token, expr);
+}
+
+static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token,
+        DavQLExpression *expr) {
+    
+    UcxList *firsttoken = token;
+    int total_consumed = 0;
+    
+    // RULE:    BooleanLiteral, [LogicalOperator, LogicalExpression];
+    DavQLExpression left, right;
+    memset(&left, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_bool_expr(stmt, token, &left);
+    if (stmt->errorcode) {
+        return 0;
+    }
+    if (!consumed) {
+        dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+            _error_missing_expr, stmt, token);
+        return 0;
+    }
+    total_consumed += consumed;
+    token = ucx_list_get(token, consumed);
+
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        expr->type = DAVQL_LOGICAL;
+
+        davqloperator_t op = DAVQL_NOOP;
+        if (tokenvalue_is(token, "and")) {
+            op = DAVQL_LAND;
+        } else if (tokenvalue_is(token, "or")) {
+            op = DAVQL_LOR;
+        } else if (tokenvalue_is(token, "xor")) {
+            op = DAVQL_LXOR;
+        }
+
+        if (op == DAVQL_NOOP) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP,
+                _error_invalid_logical_op, stmt, token);
+            return 0;
+        } else {
+            expr->op = op;
+            total_consumed++;
+            token = token->next;
+
+            memset(&right, 0, sizeof(DavQLExpression));
+            consumed = dav_parse_logical_expr(stmt, token, &right);
+            if (stmt->errorcode) {
+                return 0;
+            }
+            if (!consumed) {
+                dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                    _error_missing_expr, stmt, token);
+                return 0;
+            }
+            total_consumed += consumed;
+            token = ucx_list_get(token, consumed);
+
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &left, sizeof(DavQLExpression));
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &right, sizeof(DavQLExpression));
+        }
+    } else {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    }
+    
+    // set type and recover source text
+    if (total_consumed > 0) {        
+        expr->srctext.ptr = token_sstr(firsttoken).ptr;
+        sstr_t lasttok = token_sstr(ucx_list_get(firsttoken, total_consumed-1));
+        expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, UcxList *token) {
+    dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+    
+    return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) {
+
+    int total_consumed = 0;
+    
+    // RULE:    "depth", "=", (Number | "infinity")
+    if (tokenvalue_is(token, "depth")) {
+        token = token->next; total_consumed++;
+        if (tokenvalue_is(token, "=")) {
+            token = token->next; total_consumed++;
+            if (tokenvalue_is(token, "infinity")) {
+                stmt->depth = DAV_DEPTH_INFINITY;
+                token = token->next; total_consumed++;
+            } else {
+                DavQLExpression *depthexpr;
+                dqlsec_mallocz(stmt, depthexpr, DavQLExpression);
+                
+                int consumed = dav_parse_expression(stmt, token, depthexpr);
+
+                if (consumed) {
+                    if (depthexpr->type == DAVQL_NUMBER) {
+                        if (depthexpr->srctext.ptr[0] == '%') {
+                            stmt->depth = DAV_DEPTH_PLACEHOLDER;
+                        } else {
+                            sstr_t depthstr = depthexpr->srctext;
+                            char *conv = malloc(depthstr.length+1);
+                            if (!conv) {
+                                dav_free_expression(depthexpr);
+                                stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+                                return 0;
+                            }
+                            char *chk;
+                            memcpy(conv, depthstr.ptr, depthstr.length);
+                            conv[depthstr.length] = '\0';
+                            stmt->depth = strtol(conv, &chk, 10);
+                            if (*chk || stmt->depth < -1) {
+                                dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                                    _error_invalid_depth, stmt, token);
+                            }
+                            free(conv);
+                        }
+                        total_consumed += consumed;
+                    } else {
+                        dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                            _error_invalid_depth, stmt, token);
+                    }
+                }
+                
+                dav_free_expression(depthexpr);
+            }
+        }
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token,
+    DavQLOrderCriterion *crit) {
+    
+    // RULE:    (Identifier | Number), [" asc"|" desc"];
+    DavQLExpression expr;
+    memset(&expr, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_expression(stmt, token, &expr);
+    if (stmt->errorcode || !consumed) {
+        return 0;
+    }
+    
+    if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) {
+        dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION,
+            _error_invalid_order_criterion, stmt, token);
+        return 0;
+    }
+    
+    dqlsec_malloc(stmt, crit->column, DavQLExpression);
+    memcpy(crit->column, &expr, sizeof(DavQLExpression));
+    
+    token = ucx_list_get(token, consumed);
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && (
+            tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) {
+        
+        crit->descending = tokenvalue_is(token, "desc");
+        
+        return consumed+1;
+    } else {
+        crit->descending = 0;
+        return consumed;
+    }
+}
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) {
+    
+    int total_consumed = 0, consumed;
+    
+    DavQLOrderCriterion crit;
+    
+    // RULE:    OrderByCriterion, {",", OrderByCriterion};
+    do {
+        consumed = dav_parse_order_crit(stmt, token, &crit);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+        token = ucx_list_get(token, consumed);
+        total_consumed += consumed;
+        
+        DavQLOrderCriterion *criterion;
+        dqlsec_malloc(stmt, criterion, DavQLOrderCriterion);
+        memcpy(criterion, &crit, sizeof(DavQLOrderCriterion));
+        dqlsec_list_append_or_free(stmt, stmt->orderby, criterion);
+        
+        if (token_is(token, DAVQL_TOKEN_COMMA)) {
+            total_consumed++;
+            token = token->next;
+        } else {
+            consumed = 0;
+        }
+    } while (consumed);
+    
+    return total_consumed;
+}
+
+
+static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) {
+    
+    // RULE:    Assignment, {",", Assignment}
+    int total_consumed = 0, consumed;
+    do {
+        // RULE:    Identifier, "=", Expression
+        if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+
+            // Identifier
+            DavQLField *field;
+            dqlsec_malloc(stmt, field, DavQLField);
+            field->name = token_sstr(token);
+            total_consumed++;
+            token = token->next;
+            
+            // "="
+            if (!token_is(token, DAVQL_TOKEN_OPERATOR)
+                    || !tokenvalue_is(token, "=")) {
+                dav_free_field(field);
+                
+                dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN,
+                    _error_missing_assign, stmt, token);
+                return total_consumed;
+            }
+            total_consumed++;
+            token = token->next;
+
+            // Expression
+            dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+            consumed = dav_parse_expression(stmt, token, field->expr);
+            if (stmt->errorcode) {
+                dav_free_field(field);
+                return total_consumed;
+            }
+            token = ucx_list_get(token, consumed);
+            total_consumed += consumed;
+            
+            // Add assignment to list and check if there's another one
+            dqlsec_list_append_or_free(stmt, stmt->fields, field);
+            consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+            if (consumed) {
+                token = token->next;
+                total_consumed++;
+            }
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                    _error_missing_identifier, stmt, token);
+            return total_consumed;
+        }
+    } while (consumed);
+
+    return total_consumed;
+}
+
+static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) {
+    if (token_is(tokens, DAVQL_TOKEN_STRING)) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        return 1;
+    } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR)
+            && tokenvalue_is(tokens, "/")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        int consumed = 1;
+        while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) &&
+                !token_is(tokens, DAVQL_TOKEN_END)) {
+            sstr_t toksstr = token_sstr(tokens);
+            stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length;
+            tokens = tokens->next;
+            consumed++;
+        }
+        return consumed;
+    } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) &&
+            tokenvalue_is(tokens, "%s")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)'s');
+        return 1;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_path, stmt, tokens);
+        return 0;
+    }
+}
+
+/**
+ * Parser of a select statement.
+ * @param stmt the statement object that shall contain the syntax tree
+ * @param tokens the token list
+ */
+static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) {
+    stmt->type = DAVQL_SELECT;
+
+    // Consume field list
+    tokens = ucx_list_get(tokens, dav_parse_fieldlist(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume FROM keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "from")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_from, stmt, tokens);
+        return;
+    }
+    
+    // Consume path
+    tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    //dav_add_fmt_args(stmt, stmt->path); // add possible path args
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = ucx_list_get(tokens,
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume where clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = ucx_list_get(tokens,
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // useless, but the user may want to explicitly express his intent
+        tokens = tokens->next;
+        stmt->where = NULL;
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume order by clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "order")) {
+        tokens = tokens->next;
+        if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+                && tokenvalue_is(tokens, "by")) {
+            tokens = tokens->next;
+            tokens = ucx_list_get(tokens,
+                dav_parse_orderby_clause(stmt, tokens));
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_by, stmt, tokens);
+            return;
+        }
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    
+    if (tokens) {
+        if (token_is(tokens, DAVQL_TOKEN_INVALID)) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN,
+                _error_invalid_token, stmt, tokens);
+        } else if (!token_is(tokens, DAVQL_TOKEN_END)) {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _error_unexpected_token, stmt, tokens);
+        }
+    }
+}
+
+static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) {
+    stmt->type = DAVQL_SET;
+    
+    // Consume assignments
+    tokens = ucx_list_get(tokens, dav_parse_assignments(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume AT keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "at")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_at, stmt, tokens);
+        return;
+    }
+
+    // Consume path
+    tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = ucx_list_get(tokens,
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume mandatory where clause (or anywhere keyword)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = ucx_list_get(tokens,
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // no-op, but we want the user to be explicit about this
+        tokens = tokens->next;
+        stmt->where = NULL;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_where, stmt, tokens);
+        return;
+    }
+}
+
+DavQLStatement* dav_parse_statement(sstr_t srctext) {
+    DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement));
+    
+    // if we can't even get enough memory for the statement object or an error
+    // message, we can simply die without returning anything
+    if (!stmt) {
+        return NULL;
+    }
+    char *oommsg = strdup(_error_out_of_memory);
+    if (!oommsg) {
+        free(stmt);
+        return NULL;
+    }
+    
+    // default values
+    stmt->type = -1;
+    stmt->depth = 1;
+    
+    // save trimmed source text
+    stmt->srctext = sstrtrim(srctext);
+    
+    if (stmt->srctext.length) {   
+        // tokenization
+        UcxList* tokens = dav_parse_tokenize(stmt->srctext);
+
+        if (tokens) {
+            // use first token to determine query type
+
+            if (tokenvalue_is(tokens, "select")) {
+                dav_parse_select_statement(stmt, tokens->next);
+            } else if (tokenvalue_is(tokens, "set")) {
+                dav_parse_set_statement(stmt, tokens->next);
+            } else {
+                stmt->type = DAVQL_ERROR;
+                stmt->errorcode = DAVQL_ERROR_INVALID;
+                stmt->errormessage = strdup(_error_invalid);
+            }
+
+            // free token data
+            UCX_FOREACH(token, tokens) {
+                free(token->data);
+            }
+            ucx_list_free(tokens);
+        } else {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+        }
+    } else {
+        stmt->type = DAVQL_ERROR;
+        stmt->errorcode = DAVQL_ERROR_INVALID;
+        stmt->errormessage = strdup(_error_invalid);
+    }
+    
+    if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) {
+        stmt->type = DAVQL_ERROR;
+        stmt->errormessage = oommsg;
+    } else {
+        free(oommsg);
+    }
+    
+    return stmt;
+}
+
+void dav_free_statement(DavQLStatement *stmt) {
+    UCX_FOREACH(expr, stmt->fields) {
+        dav_free_field(expr->data);
+    }
+    ucx_list_free(stmt->fields);
+    
+    if (stmt->where) {
+        dav_free_expression(stmt->where);
+    }
+    if (stmt->errormessage) {
+        free(stmt->errormessage);
+    }
+    UCX_FOREACH(crit, stmt->orderby) {
+        dav_free_order_criterion(crit->data);
+    }
+    ucx_list_free(stmt->orderby);
+    ucx_list_free(stmt->args);
+    free(stmt);
+}
diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h
new file mode 100644 (file)
index 0000000..fa52910
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * 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 DAVQLPARSER_H
+#define        DAVQLPARSER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include "ucx/string.h"
+#include "ucx/list.h"
+
+/**
+ * Enumeration of possible statement types.
+ */
+typedef enum {DAVQL_ERROR, DAVQL_SELECT, DAVQL_SET} davqltype_t;
+
+/**
+ * Enumeration of possible token classes.
+ */
+typedef enum {
+    DAVQL_TOKEN_INVALID, DAVQL_TOKEN_KEYWORD,
+    DAVQL_TOKEN_IDENTIFIER, DAVQL_TOKEN_FMTSPEC,
+    DAVQL_TOKEN_STRING, DAVQL_TOKEN_NUMBER, DAVQL_TOKEN_TIMESTAMP,
+    DAVQL_TOKEN_COMMA, DAVQL_TOKEN_OPENP, DAVQL_TOKEN_CLOSEP,
+    DAVQL_TOKEN_OPERATOR, DAVQL_TOKEN_END
+} davqltokenclass_t;
+
+/**
+ * Enumeration of possible expression types.
+ */
+typedef enum {
+    DAVQL_UNDEFINED_TYPE,
+    DAVQL_NUMBER, DAVQL_STRING, DAVQL_TIMESTAMP, DAVQL_IDENTIFIER,
+    DAVQL_UNARY, DAVQL_BINARY, DAVQL_LOGICAL, DAVQL_FUNCCALL
+} davqlexprtype_t;
+
+/**
+ * Enumeration of possible expression operators.
+ */
+typedef enum {
+    DAVQL_NOOP, DAVQL_CALL, DAVQL_ARGLIST, // internal representations
+    DAVQL_ADD, DAVQL_SUB, DAVQL_MUL, DAVQL_DIV,
+    DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG,  // airthmetic
+    DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, // logical
+    DAVQL_EQ, DAVQL_NEQ, DAVQL_LT, DAVQL_GT, DAVQL_LE, DAVQL_GE,
+    DAVQL_LIKE, DAVQL_UNLIKE // comparisons
+} davqloperator_t;
+
+typedef struct {
+    davqltokenclass_t tokenclass;
+    sstr_t value;
+} DavQLToken;
+
+/**
+ * An expression within a DAVQL query.
+ */
+typedef struct _davqlexpr DavQLExpression;
+
+/**
+ * The structure for type DavQLExpression.
+ */
+struct _davqlexpr {
+    /**
+     * The original expression text.
+     * Contains the literal value, if type is LITERAL.
+     */
+    sstr_t srctext;
+    /**
+     * The expression type.
+     */
+    davqlexprtype_t type;
+    /**
+     * Operator.
+     */
+    davqloperator_t op;
+    /**
+     * Left or single operand.
+     * <code>NULL</code> for literals or identifiers.
+     */
+    DavQLExpression *left;
+    /**
+     * Right operand.
+     * <code>NULL</code> for literals, identifiers or unary expressions.
+     */
+    DavQLExpression *right;
+};
+
+/**
+ * A tuple representing an order criterion.
+ */
+typedef struct {
+    /**
+     * The column.
+     */
+    DavQLExpression *column;
+    /**
+     * True, if the result shall be sorted descending, false otherwise.
+     * Default is false (ascending).
+     */
+    _Bool descending;
+} DavQLOrderCriterion;
+
+/**
+ * A tuple representing a field.
+ */
+typedef struct {
+    /**
+     * The field name.
+     * <ul>
+     * <li>SELECT: the identifier or an alias name</li>
+     * <li>SET: the identifier</li>
+     * </ul>
+     */
+    sstr_t name;
+    /**
+     * The field expression.
+     * <ul>
+     * <li>SELECT: the queried property (identifier) or an expression</li>
+     * <li>SET: the expression for the value to be set</li>
+     * </ul>
+     */
+    DavQLExpression *expr;
+} DavQLField;
+
+/**
+ * Query statement object.
+ * Contains the binary information about the parsed query.
+ * 
+ * The grammar for a DavQLStatement is:
+ * 
+ * <pre>
+ * Keyword = "select" | "set" | "from" | "at" | "as"
+ *         | "where" | "anywhere" | "like" | "unlike"
+ *         | "and" | "or" | "not" | "xor" | "with" | "infinity"
+ *         | "order" | "by" | "asc" | "desc";
+ * 
+ * Expression        = AddExpression;
+ * AddExpression     = MultExpression, [AddOperator, AddExpression];
+ * MultExpression    = BitwiseExpression, [MultOperator, MultExpression];
+ * BitwiseExpression = UnaryExpression, [BitwiseOperator, BitwiseExpression];
+ * UnaryExpression   = [UnaryOperator], (ParExpression | AtomicExpression);
+ * AtomicExpression  = FunctionCall | Identifier | Literal;
+ * ParExpression     = "(", Expression, ")";
+ * 
+ * BitwiseOperator = "&" | "|" | "^";
+ * MultOperator    = "*" | "/";
+ * AddOperator     = "+" | "-";
+ * UnaryOperator   = "+" | "-" | "~";
+ * 
+ * FunctionCall    = Identifier, "(", [ArgumentList], ")";
+ * ArgumentList    = Expression, {",", Expression};
+ * Identifier      = IdentifierChar - ?Digit?, {IdentifierChar}
+ *                 | "`", ?Character? - "`", {?Character? - "`"}, "`";
+ * IdentifierChar  = ?Character? - (" "|",");
+ * Literal         = Number | String | Timestamp;
+ * Number          = ?Digit?, {?Digit?} | "%d";
+ * String          = "'", {?Character? - "'" | "'''"} , "'" | "%s";
+ * Timestamp       = "%t"; // TODO: maybe introduce a real literal 
+ * 
+ * LogicalExpression = BooleanExpression, [LogicalOperator, LogicalExpression];
+ * BooleanExpression = "not ", BooleanExpression
+ *                   | "(", LogicalExpression, ")"
+ *                   | BooleanPrimary;
+ * BooleanPrimary    = Expression, (" like " | " unlike "), String
+ *                   | Expression, Comparison, Expression
+ *                   | FunctionCall | Identifier;
+ * 
+ * LogicalOperator = " and " | " or " | " xor ";
+ * Comparison      = | "=" | "<" | ">" | "<=" | ">=" | "!=";
+ * 
+ * FieldExpressions = "-"
+ *                  | "*", {",", NamedField}
+ *                  | FieldExpression, {",", FieldExpression};
+ * FieldExpression  = NamedField | Identifier;
+ * NamedField       = Expression, " as ", Identifier;
+ * 
+ * Assignments   = Assignment, {",", Assignment};
+ * Assignment    = Identifier, "=", Expression;
+ * 
+ * Path     = String
+ *          | "/", [PathNode, {"/", PathNode}], ["/"];
+ * PathNode = {{?Character? - "/"} - Keyword};
+ * 
+ * WithClause = "depth", "=", (Number | "infinity");
+ * 
+ * OrderByClause    = OrderByCriterion, {",", OrderByCriterion};
+ * OrderByCriterion = (Identifier | Number), [" asc"|" desc"];
+ * 
+ * </pre>
+ * 
+ * Note: mandatory spaces are part of the grammar. But you may also insert an
+ * arbitrary amount of optional spaces between two symbols if they are not part
+ * of an literal, identifier or the path.
+ * 
+ * <b>SELECT:</b>
+ * <pre>
+ * SelectStatement = "select ", FieldExpressions,
+ * " from ", Path,
+ * [" with ", WithClause],
+ * [(" where ", LogicalExpression) | " anywhere"],
+ * [" order by ", OrderByClause];
+  * </pre>
+ * 
+ * <b>SET:</b>
+ * <pre>
+ * SetStatement = "set ",Assignments,
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
+ * </pre>
+ * 
+ */
+typedef struct {
+    /**
+     * The original query text.
+     */
+    sstr_t srctext;
+    /**
+     * The statement type.
+     */
+    davqltype_t type;
+    /**
+     * Error code, if any error occurred. Zero otherwise.
+     */
+    int errorcode;
+    /**
+     * Error message, if any error occurred.
+     */
+    char* errormessage;
+    /**
+     * The list of DavQLFields.
+     */
+    UcxList* fields;
+    /**
+     * A string that denotes the queried path.
+     */
+    sstr_t path;
+    /**
+     * Logical expression for selection.
+     * <code>NULL</code>, if there is no where clause.
+     */
+    DavQLExpression* where;
+    /**
+     * The list of DavQLOrderCriterions.
+     * This is <code>NULL</code> for SET queries and may be <code>NULL</code>
+     * if the result doesn't need to be sorted.
+     */
+    UcxList* orderby;
+    /**
+     * The recursion depth for the statement.
+     * Defaults to 1.
+     * Magic numbers are DAV_DEPTH_INFINITY for infinity and
+     * DAV_DEPTH_PLACEHOLDER for a placeholder.
+     */
+    int depth;
+    /**
+     * A list of all required arguments
+     */
+    UcxList* args;
+} DavQLStatement;
+
+/** Infinity recursion depth for a DavQLStatement. */
+#define DAV_DEPTH_INFINITY -1
+
+/** Depth needs to be specified at runtime. */
+#define DAV_DEPTH_PLACEHOLDER -2
+
+/** Unexpected token. */
+#define DAVQL_ERROR_UNEXPECTED_TOKEN 1
+
+/** A token has been found, for which no token class is applicable. */
+#define DAVQL_ERROR_INVALID_TOKEN 2
+
+/** A token that has been expected was not found. */
+#define DAVQL_ERROR_MISSING_TOKEN 11
+
+/** An expression has been expected, but was not found. */
+#define DAVQL_ERROR_MISSING_EXPR 12
+
+/** A closed parenthesis ')' is missing. */
+#define DAVQL_ERROR_MISSING_PAR 13
+
+/** An assignment operator '=' is missing. */
+#define DAVQL_ERROR_MISSING_ASSIGN 14
+
+/** The type of the expression could not be determined. */
+#define DAVQL_ERROR_INVALID_EXPR 21
+
+/** An operator has been found for an unary expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_UNARY_OP 22
+
+/** An operator has been found for a logical expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_LOGICAL_OP 23
+
+/** Invalid format specifier. */
+#define DAVQL_ERROR_INVALID_FMTSPEC 24
+
+/** A string has been expected. */
+#define DAVQL_ERROR_INVALID_STRING 25
+
+/** The order criterion is invalid (must be an identifier or field index). */
+#define DAVQL_ERROR_INVALID_ORDER_CRITERION 26
+
+/** The depth is invalid. */
+#define DAVQL_ERROR_INVALID_DEPTH 101
+
+/** Nothing about the statement seems legit. */
+#define DAVQL_ERROR_INVALID -1
+
+/** A call to malloc or calloc failed. */
+#define DAVQL_ERROR_OUT_OF_MEMORY -2
+
+/**
+ * Starts an interactive debugger for a DavQLStatement.
+ * 
+ * @param stmt the statement to debug
+ */
+void dav_debug_statement(DavQLStatement *stmt);
+
+/**
+ * Parses a statement.
+ * @param stmt the sstr_t containing the statement
+ * @return a DavQLStatement object
+ */
+DavQLStatement* dav_parse_statement(sstr_t stmt);
+
+/**
+ * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement.
+ */
+#define dav_parse_cstr_statement(stmt) dav_parse_statement(S(stmt))
+
+/**
+ * Frees a DavQLStatement.
+ * @param stmt the statement object to free
+ */
+void dav_free_statement(DavQLStatement *stmt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLPARSER_H */
+
diff --git a/libidav/methods.c b/libidav/methods.c
new file mode 100644 (file)
index 0000000..e54da5d
--- /dev/null
@@ -0,0 +1,1350 @@
+/*
+ * 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 "utils.h"
+#include "methods.h"
+#include "crypto.h"
+#include "session.h"
+#include "xml.h"
+
+#include <ucx/utils.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+
+int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin) {
+    return ucx_buffer_seek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
+}
+
+/* ----------------------------- PROPFIND ----------------------------- */
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        UcxBuffer *request,
+        UcxBuffer *response)
+{
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND");
+    
+    // always try to get information about possible children
+    int depth = 1;
+    
+    int maxretry = 2;
+    
+    struct curl_slist *headers = NULL;
+    CURLcode ret = 0;
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    UcxMap *respheaders = ucx_map_new(32);
+    util_capture_header(handle, respheaders);
+    
+    for(int i=0;i<maxretry;i++) {
+        if (depth == 1) {
+            headers = curl_slist_append(headers, "Depth: 1");
+        } else if (depth == -1) {
+            headers = curl_slist_append(headers, "Depth: infinity");
+        } else {
+            headers = curl_slist_append(headers, "Depth: 0");
+        }
+        headers = curl_slist_append(headers, "Content-Type: text/xml");
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+        // reset buffers and perform request
+        request->pos = 0;
+        response->size = response->pos = 0;
+        ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+        curl_slist_free_all(headers);
+        headers = NULL;
+        
+        /*
+         * Handle two cases:
+         * 1. We communicate with IIS and get a X-MSDAVEXT_Error: 589831
+         *    => try with depth 0 next time, it's not a collection
+         * 2. Other cases
+         *    => the server handled our request and we can stop requesting
+         */
+        char *msdavexterror;
+        msdavexterror = ucx_map_cstr_get(respheaders, "x-msdavext_error");
+        int iishack =  depth == 1 &&
+            msdavexterror && !strncmp(msdavexterror, "589831;", 7);
+        
+        if(iishack) {
+            depth = 0;
+        } else {
+            break;
+        }
+    }
+    
+    // deactivate header capturing and free captured map
+    util_capture_header(handle, NULL);
+    ucx_map_free_content(respheaders, free);
+    ucx_map_free(respheaders);
+       
+    return ret;
+}
+
+UcxBuffer* create_allprop_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOFREE);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propfind xmlns:D=\"DAV:\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:allprop/></D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+UcxBuffer* create_cryptoprop_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOFREE);
+    scstr_t s;
+    
+    s = SC("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = SC("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = SC("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    int add_crypto_name = 1;
+    int add_crypto_key = 1;
+    int add_crypto_hash = 1;
+    char *crypto_ns = "idav";
+    UcxMap *namespaces = ucx_map_new(8);
+    UCX_FOREACH(elm, properties) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            ucx_map_cstr_put(namespaces, p->ns->prefix, p->ns);
+        }
+        
+        // if the properties list contains the idav properties crypto-name
+        // and crypto-key, mark them as existent 
+        if(!strcmp(p->ns->name, DAV_NS)) {
+            if(!strcmp(p->name, "crypto-name")) {
+                add_crypto_name = 0;
+                crypto_ns = p->ns->prefix;
+            } else if(!strcmp(p->name, "crypto-key")) {
+                add_crypto_key = 0;
+                crypto_ns = p->ns->prefix;
+            } else if(!strcmp(p->name, "crypto-hash")) {
+                add_crypto_hash = 0;
+                crypto_ns = p->ns->prefix;
+            }
+        }
+    }
+    
+    DavNamespace idav_ns;
+    if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn) && !nocrypt) {
+        idav_ns.prefix = "idav";
+        idav_ns.name = DAV_NS;
+        ucx_map_cstr_put(namespaces, "idav", &idav_ns);
+    }
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    ucx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
+    
+    UcxMapIterator mapi = ucx_map_iterator(namespaces);
+    UcxKey key;
+    DavNamespace *ns;
+    UCX_MAP_FOREACH(key, ns, mapi) {
+        s = S(" xmlns:");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(ns->prefix);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S("=\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(ns->name);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S("\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    s = S(">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // default properties
+    s = S("<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:creationdate />\n<D:getlastmodified />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:getcontentlength />\n<D:getcontenttype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:resourcetype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // crypto properties
+    if(DAV_CRYPTO(sn) && !nocrypt) {
+        if(add_crypto_name) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-name />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_key) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-key />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_hash) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-hash />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // extra properties
+    UCX_FOREACH(elm, properties) {
+        DavProperty *prop = elm->data;
+        s = S("<");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(prop->ns->prefix);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S(":");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(prop->name);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S(" />\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    // end
+    ucx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
+    
+    ucx_map_free(namespaces);
+    return buf;
+}
+
+UcxBuffer* create_basic_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);  
+    s = S(DAV_NS);
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("\" >\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // properties
+    s = S("<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<D:resourcetype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-key />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-name />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-hash />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("</D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // end
+    s = S("</D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+PropfindParser* create_propfind_parser(UcxBuffer *response, char *url) {
+    PropfindParser *parser = malloc(sizeof(PropfindParser));
+    if(!parser) {
+        return NULL;
+    }
+    parser->document = xmlReadMemory(response->space, response->pos, url, NULL, 0);
+    parser->current = NULL;
+    if(parser->document) {
+        xmlNode *xml_root = xmlDocGetRootElement(parser->document);
+        if(xml_root) {
+            xmlNode *node = xml_root->children;
+            while(node) {
+                // find first response tag
+                if(node->type == XML_ELEMENT_NODE) {
+                    if(xstreq(node->name, "response")) {
+                        parser->current = node;
+                        break;
+                    }
+                }
+                node = node->next;
+            }
+            return parser;
+        } else {
+            xmlFreeDoc(parser->document);
+        }
+    }
+    free(parser);
+    return NULL;
+}
+
+void destroy_propfind_parser(PropfindParser *parser) {
+    if(parser->document) {
+        xmlFreeDoc(parser->document);
+    }
+    free(parser);
+}
+
+int get_propfind_response(PropfindParser *parser, ResponseTag *result) {
+    if(parser->current == NULL) {
+        return 0;
+    }
+    
+    char *href = NULL;
+    int iscollection = 0;
+    UcxList *properties = NULL; // xmlNode list
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    result->properties = NULL;
+    
+    xmlNode *node = parser->current->children;
+    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) {
+                    // error
+                    return -1;
+                }
+                href = (char*)href_node->content;
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int ok = 0;
+                // get the status code
+                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) {
+                                // error
+                                return -1;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                // error
+                                return -1;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            if(!sstrcmp(status_str, S("200"))) {
+                                ok = 1;
+                            }
+                        }
+                    }    
+                    n = n->next;
+                }
+                // if status is ok, get all properties
+                if(ok) {
+                    n = prop_node->children;
+                    while(n) {
+                        if(n->type == XML_ELEMENT_NODE) {
+                            properties = ucx_list_append(properties, n);
+                            if(xstreq(n->name, "resourcetype")) {
+                                if(parse_resource_type(n)) {
+                                    iscollection = TRUE;
+                                }
+                            } else if(xstreq(n->ns->href, DAV_NS)) {
+                                if(xstreq(n->name, "crypto-name")) {
+                                    crypto_name = util_xml_get_text(n);
+                                } else if(xstreq(n->name, "crypto-key")) {
+                                    crypto_key = util_xml_get_text(n);
+                                }
+                            }
+                        }
+                        n = n->next;
+                    }
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    result->href = util_url_path(href);
+    result->iscollection = iscollection;
+    result->properties = properties;
+    result->crypto_name = crypto_name;
+    result->crypto_key = crypto_key;
+    
+    // find next response tag
+    xmlNode *next = parser->current->next;
+    while(next) {
+        if(next->type == XML_ELEMENT_NODE) {
+            if(xstreq(next->name, "response")) {
+                break;
+            }
+        }
+        next = next->next;
+    }
+    parser->current = next;
+    
+    return 1;
+}
+
+void cleanup_response(ResponseTag *result) {
+    if(result) {
+        ucx_list_free(result->properties);
+    }
+}
+
+int hrefeq(DavSession *sn, char *href1, char *href2) {
+    sstr_t href_s = sstr(util_url_decode(sn, href1));
+    sstr_t href_r = sstr(util_url_decode(sn, href2));
+    int ret = 0;
+    if(!sstrcmp(href_s, href_r)) {
+        ret = 1;
+    } else if(href_s.length == href_r.length + 1) {
+        if(href_s.ptr[href_s.length-1] == '/') {
+            href_s.length--;
+            if(!sstrcmp(href_s, href_r)) {
+                ret = 1;
+            }
+        }
+    } else if(href_r.length == href_s.length + 1) {
+        if(href_r.ptr[href_r.length-1] == '/') {
+            href_r.length--;
+            if(!sstrcmp(href_s, href_r)) {
+                ret = 1;
+            }
+        }
+    } 
+
+    free(href_s.ptr);
+    free(href_r.ptr);
+    
+    return ret;
+}
+
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response) {
+    char *url = NULL;
+    curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url);
+    if(!root) {
+        printf("methods.c: TODO: remove\n");
+        root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove
+    }
+    
+    //printf("%.*s\n\n", response->size, response->space);
+    xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0);
+    if(!doc) {
+        // TODO: free stuff
+        sn->error = DAV_ERROR;
+        return NULL;
+    }
+    
+    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(root, node);
+            }
+        }
+        node = node->next;
+    }
+    xmlFreeDoc(doc);
+    
+    return root;
+}
+
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path) {
+    // create resource
+    char *name = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_NAME(sn) && response->crypto_name && (key = dav_context_get_key(sn->context, response->crypto_key))) {
+        if(!response->crypto_key) {
+            sn->error = DAV_ERROR;
+            dav_session_set_errstr(sn, "Missing crypto-key property");
+            return NULL;
+        }
+        name = util_decrypt_str_k(sn, response->crypto_name, key);
+        if(!name) {
+            sn->error = DAV_ERROR;
+            dav_session_set_errstr(sn, "Cannot decrypt resource name");
+            return NULL;
+        }
+    } else {
+        sstr_t resname = sstr(util_resource_name(response->href));
+        int nlen = 0;
+        char *uname = curl_easy_unescape(
+                sn->handle,
+                resname.ptr,
+                resname.length,
+                &nlen);
+        name = dav_session_strdup(sn, uname);
+        curl_free(uname);
+    }
+
+    char *href = dav_session_strdup(sn, response->href);
+    DavResource *res = NULL;
+    if(parent_path) {
+        res = dav_resource_new_full(sn, parent_path, name, href);
+    } else {
+        res = dav_resource_new_href(sn, href);
+    }
+    dav_session_free(sn, name);
+    
+    add_properties(res, response); 
+    return res;
+}
+
+void add_properties(DavResource *res, ResponseTag *response) {
+    res->iscollection = response->iscollection;
+    
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    char *crypto_key = NULL;
+    
+    // add properties
+    UCX_FOREACH(elm, response->properties) {
+        xmlNode *prop = elm->data;
+        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+        
+        if (decrypt_props &&
+            prop->children &&
+            prop->children->type == XML_TEXT_NODE &&
+            xstreq(prop->ns->href, DAV_NS))
+        {
+            if(xstreq(prop->name, "crypto-prop")) {
+                crypto_prop = prop;
+            } else if(xstreq(prop->name, "crypto-key")) {
+                crypto_key = util_xml_get_text(prop);
+            }
+        }
+    }
+    
+    if(crypto_prop && crypto_key) {
+        char *crypto_prop_content = util_xml_get_text(crypto_prop);
+        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+        if(crypto_prop_content) {
+            UcxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+            resource_set_crypto_properties(res, cprops);
+        }
+    }
+    
+    set_davprops(res);
+}
+
+int parse_response_tag(DavResource *resource, xmlNode *node) {
+    DavSession *sn = resource->session;
+    
+    //DavResource *res = resource;
+    DavResource *res = NULL;
+    char *href = NULL;
+    UcxList *properties = NULL; // xmlNode list
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    int iscollection = 0; // TODO: remove
+    
+    node = node->children;
+    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) {
+                    // error
+                    sn->error = DAV_ERROR;
+                    return 1;
+                }
+                //char *href = (char*)href_node->content;
+                href = util_url_path((char*)href_node->content);
+                
+                char *href_s = util_url_decode(resource->session, href);
+                char *href_r = util_url_decode(resource->session, resource->href);
+                
+                if(hrefeq(sn, href_s, href_r)) {
+                    res = resource;
+                }   
+                
+                free(href_s);
+                free(href_r);
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int ok = 0;
+                // get the status code
+                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) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            if(!sstrcmp(status_str, S("200"))) {
+                                ok = 1;
+                            }
+                        }
+                    }    
+                    n = n->next;
+                }
+                // if status is ok, get all properties
+                if(ok) {
+                    n = prop_node->children;
+                    while(n) {
+                        if(n->type == XML_ELEMENT_NODE) {
+                            properties = ucx_list_append(properties, n);
+                            if(xstreq(n->name, "resourcetype")) {
+                                if(parse_resource_type(n)) {
+                                    iscollection = TRUE;
+                                }
+                            } else if(xstreq(n->ns->href, DAV_NS)) {
+                                if(xstreq(n->name, "crypto-name")) {
+                                    crypto_name = util_xml_get_text(n);
+                                } else if(xstreq(n->name, "crypto-key")) {
+                                    crypto_key = util_xml_get_text(n);
+                                }
+                            }
+                        }
+                        n = n->next;
+                    }
+                }
+            }
+        }
+        
+        node = node->next;
+    }
+    
+    if(!res) {
+        // create new resource object
+        char *name = NULL;
+        if(DAV_DECRYPT_NAME(sn) && crypto_name) {
+            if(!crypto_key) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Missing crypto-key property");
+                return -1;
+            }
+            name = util_decrypt_str(sn, crypto_name, crypto_key);
+            if(!name) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Cannot decrypt resource name");
+                return -1;
+            }
+        } else {
+            sstr_t resname = sstr(util_resource_name(href));
+            int nlen = 0;
+            char *uname = curl_easy_unescape(
+                    sn->handle,
+                    resname.ptr,
+                    resname.length,
+                    &nlen);
+            name = dav_session_strdup(sn, uname);
+            curl_free(uname);
+        }
+        
+        href = dav_session_strdup(sn, href);
+        res = dav_resource_new_full(sn, resource->path, name, href);
+        
+        dav_session_free(sn, name);
+    }
+    res->iscollection = iscollection;
+    
+    // add properties
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    
+    UCX_FOREACH(elm, properties) {
+        xmlNode *prop = elm->data;
+        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+        
+        if (decrypt_props &&
+            prop->children &&
+            prop->children->type == XML_TEXT_NODE &&
+            xstreq(prop->ns->href, DAV_NS))
+        {
+            if(xstreq(prop->name, "crypto-prop")) {
+                crypto_prop = prop;
+            }
+        }
+    }
+    ucx_list_free(properties);
+    
+    if(crypto_prop && crypto_key) {
+        char *crypto_prop_content = util_xml_get_text(crypto_prop);
+        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+        if(crypto_prop_content && key) {
+            UcxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+            resource_set_crypto_properties(res, cprops);
+        }
+    }
+    
+    
+    set_davprops(res);
+    if(res != resource) {
+        resource_add_child(resource, res);
+    }
+    
+    return 0;
+}
+
+void set_davprops(DavResource *res) {
+    char *cl = dav_get_string_property_ns(res, "DAV:", "getcontentlength");
+    char *ct = dav_get_string_property_ns(res, "DAV:", "getcontenttype");
+    char *cd = dav_get_string_property_ns(res, "DAV:", "creationdate");
+    char *lm = dav_get_string_property_ns(res, "DAV:", "getlastmodified");
+    
+    res->contenttype = ct;
+    if(cl) {
+        char *end = NULL;
+        res->contentlength = strtoull(cl, &end, 0);
+    }
+    res->creationdate = util_parse_creationdate(cd);
+    res->lastmodified = util_parse_lastmodified(lm);
+}
+
+int parse_resource_type(xmlNode *node) {
+    int collection = FALSE;
+    xmlNode *c = node->children;
+    while(c) {
+        if(c->type == XML_ELEMENT_NODE) {
+            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, "collection")) {
+                collection = TRUE;
+                break;
+            }
+        }
+        c = c->next;
+    }
+    return collection;
+}
+
+
+/* ----------------------------- PROPPATCH ----------------------------- */
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        UcxBuffer *request,
+        UcxBuffer *response)
+{       
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH");
+    
+    struct curl_slist *headers = NULL;
+    headers = curl_slist_append(headers, "Content-Type: text/xml"); 
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    ucx_buffer_seek(request, 0, SEEK_SET);
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    curl_slist_free_all(headers);
+    
+    //printf("proppatch: \n%.*s\n", request->size, request->space);
+    
+    return ret;
+}
+
+UcxBuffer* create_proppatch_request(DavResourceData *data) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    scstr_t s;
+    
+    UcxMap *namespaces = ucx_map_new(8);
+    char prefix[8];
+    int pfxnum = 0;
+    UCX_FOREACH(elm, data->set) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            snprintf(prefix, 8, "x%d", pfxnum++);
+            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
+        }
+    }
+    UCX_FOREACH(elm, data->remove) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            snprintf(prefix, 8, "x%d", pfxnum++);
+            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
+        }
+    }
+    
+    s = SC("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    s = SC("<D:propertyupdate xmlns:D=\"DAV:\"");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    UcxMapIterator mapi = ucx_map_iterator(namespaces);
+    UcxKey key;
+    char *pfxval;
+    UCX_MAP_FOREACH(key, pfxval, mapi) {
+        s = SC(" xmlns:");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = scstr(pfxval);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = SC("=\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = scstrn(key.data, key.len);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = SC("\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    s = SC(">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(data->set) {
+        s = SC("<D:set>\n<D:prop>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        UCX_FOREACH(elm, data->set) {
+            DavProperty *property = elm->data;
+            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
+            if(!prefix) {
+                prefix = "D";
+            }
+            
+            // begin tag
+            s = SC("<");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(">");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            
+            // content
+            DavXmlNode *content = property->value;
+            if(content->type == DAV_XML_TEXT && !content->next) {
+                ucx_buffer_write(content->content, 1, content->contentlength, buf);
+            } else {
+                dav_print_node(buf, (write_func)ucx_buffer_write, namespaces, content);
+            }
+            
+            // end tag
+            s = SC("</");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(">\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        s = SC("</D:prop>\n</D:set>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    if(data->remove) {
+        s = SC("<D:remove>\n<D:prop>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        UCX_FOREACH(elm, data->remove) {
+            DavProperty *property = elm->data;
+            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
+            
+            s = SC("<");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(" />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        s = SC("</D:prop>\n</D:remove>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = SC("</D:propertyupdate>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // cleanup namespace map
+    ucx_map_free_content(namespaces, free);
+    ucx_map_free(namespaces);
+    
+    return buf;
+}
+
+UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:set>\n<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(DAV_ENCRYPT_NAME(sn)) {
+        s = S("<idav:crypto-name>");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        char *crname = aes_encrypt(name, strlen(name), key);
+        ucx_buffer_puts(buf, crname);
+        free(crname);
+        s = S("</idav:crypto-name>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = S("<idav:crypto-key>");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    ucx_buffer_puts(buf, key->name);
+    s = S("</idav:crypto-key>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(hash) {
+        s = S("<idav:crypto-hash>");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        ucx_buffer_puts(buf, hash);
+        s = S("</idav:crypto-hash>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = S("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+/* ----------------------------- PUT ----------------------------- */
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+    //fwrite(buf, s, n, stdout);
+    return s*n;
+}
+
+CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, dav_read_func read_func, dav_seek_func seek_func, size_t length) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+    
+    // clear headers
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = NULL;
+        if(create) {
+            url = util_parent_path(url);
+            ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+            free(url);
+        } else {
+            ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        }
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    UcxBuffer *buf = NULL;
+    if(!read_func) {
+        buf = ucx_buffer_new(data, length, 0);
+        buf->size = length;
+        data = buf;
+        read_func = (dav_read_func)ucx_buffer_read;
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+    } else if(length == 0) {
+        headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)1);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, seek_func);
+    curl_easy_setopt(handle, CURLOPT_READDATA, data);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    if(buf) {
+        ucx_buffer_free(buf);
+    }
+    
+    return ret;
+}
+
+CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response) { 
+    CURL *handle = sn->handle;
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    return ret;
+}
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock) {
+    CURL *handle = sn->handle;
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        url = util_parent_path(url);
+        char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        free(url);
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL");
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    return ret;
+}
+
+
+CURLcode do_head_request(DavSession *sn) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "HEAD");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
+    
+    // clear headers
+    struct curl_slist *headers = NULL;
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_easy_setopt(handle, CURLOPT_NOBODY, 0L);
+    return ret;
+}
+
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override) { 
+    CURL *handle = sn->handle;
+    if(copy) {
+        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "COPY");
+    } else {
+        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MOVE");
+    }
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+    }
+    //sstr_t deststr = ucx_sprintf("Destination: %s", dest);
+    sstr_t deststr = sstrcat(2, S("Destination: "), sstr(dest));
+    headers = curl_slist_append(headers, deststr.ptr);
+    if(override) {
+        headers = curl_slist_append(headers, "Overwrite: T");
+    } else {
+        headers = curl_slist_append(headers, "Overwrite: F");
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    free(deststr.ptr);
+    curl_slist_free_all(headers);
+    headers = NULL;
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    return ret;
+}
+
+
+UcxBuffer* create_lock_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:lockinfo xmlns:D=\"DAV:\">\n"
+          "<D:lockscope><D:exclusive/></D:lockscope>\n"
+          "<D:locktype><D:write/></D:locktype>\n"
+          "<D:owner><D:href>http://davutils.org/libidav/</D:href></D:owner>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("</D:lockinfo>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock) {
+    lock->locktoken = NULL;
+    lock->timeout = NULL;
+    
+    xmlDoc *doc = xmlReadMemory(response->space, response->size, NULL, NULL, 0);
+    if(!doc) {
+        sn->error = DAV_ERROR;
+        return -1;
+    }
+    
+    char *timeout = NULL;
+    char *locktoken = NULL;
+    
+    int ret = -1;
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    DavBool lockdiscovery = 0;
+    if(xml_root) {
+        xmlNode *node = xml_root->children;
+        while(node) {
+            if(node->type == XML_ELEMENT_NODE) {
+                if(xstreq(node->name, "lockdiscovery")) {
+                    node = node->children;
+                    lockdiscovery = 1;
+                    continue;
+                }
+                
+                if(xstreq(node->name, "activelock")) {
+                    node = node->children;
+                    continue;
+                }
+                
+                if(lockdiscovery) {
+                    if(xstreq(node->name, "timeout")) {
+                        timeout = util_xml_get_text(node);
+                    } else if(xstreq(node->name, "locktoken")) {
+                        xmlNode *n = node->children;
+                        while(n) {
+                            if(xstreq(n->name, "href")) {
+                                locktoken = util_xml_get_text(n);
+                                break;
+                            }
+                            n = n->next;
+                        }
+                    }
+                }
+            }
+            node = node->next;
+        }
+    }
+    
+    if(timeout && locktoken) {
+        lock->timeout = strdup(timeout);
+        lock->locktoken = strdup(locktoken);
+        ret = 0;
+    }
+    
+    xmlFreeDoc(doc);
+    return ret;
+}
+
+CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout) { 
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK");  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+    request->pos = 0;
+    
+    // clear headers    
+    struct curl_slist *headers = NULL;
+    
+    if(timeout != 0) {
+        sstr_t thdr;
+        if(timeout < 0) {
+            thdr = ucx_sprintf("%s", "Timeout: Infinite");
+        } else {
+            thdr = ucx_sprintf("Timeout: Second-%u", (unsigned int)timeout);
+        }
+        headers = curl_slist_append(headers, thdr.ptr);
+        free(thdr.ptr);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    
+    if(headers) {
+        curl_slist_free_all(headers);
+    }
+    
+    return ret;
+}
+
+CURLcode do_unlock_request(DavSession *sn, char *locktoken) {   
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "UNLOCK");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    // set lock-token header
+    sstr_t ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken);
+    struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr);
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    free(ltheader.ptr);
+    
+    return ret;
+}
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    // set lock-token header
+    sstr_t ltheader;
+    struct curl_slist *headers = NULL;
+    if(locktoken) {
+        ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken);
+        headers = curl_slist_append(NULL, ltheader.ptr);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    if(locktoken) {
+        curl_slist_free_all(headers);
+        free(ltheader.ptr);
+    }
+    
+    return ret;
+}
+
+
+CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT");
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    struct curl_slist *headers = NULL;
+    headers = curl_slist_append(headers, "Content-Type: text/xml");
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    request->pos = 0;
+    response->size = response->pos = 0;
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    
+    curl_slist_free_all(headers);
+    
+    return ret;
+}
diff --git a/libidav/methods.h b/libidav/methods.h
new file mode 100644 (file)
index 0000000..660e738
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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 METHODS_H
+#define        METHODS_H
+
+#include "webdav.h"
+#include "resource.h"
+
+#include <ucx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct PropfindParser PropfindParser;
+typedef struct ResponseTag    ResponseTag;
+typedef struct LockDiscovery  LockDiscovery;
+
+struct PropfindParser {
+    xmlDoc *document;
+    xmlNode *current;
+};
+
+struct ResponseTag {
+    char    *href;
+    int     iscollection;
+    UcxList *properties;
+    char    *crypto_name;
+    char    *crypto_key;
+};
+
+struct LockDiscovery {
+    char    *timeout;
+    char    *locktoken;
+};
+
+int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin);
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        UcxBuffer *request,
+        UcxBuffer *response);
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        UcxBuffer *request,
+        UcxBuffer *response);
+
+CURLcode do_put_request(
+        DavSession *sn,
+        char *lock,
+        DavBool create,
+        void *data,
+        dav_read_func read_func,
+        dav_seek_func seek_func,
+        size_t length);
+
+UcxBuffer* create_allprop_propfind_request(void);
+UcxBuffer* create_cryptoprop_propfind_request(void);
+UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt);
+UcxBuffer* create_basic_propfind_request(void);
+
+PropfindParser* create_propfind_parser(UcxBuffer *response, char *url);
+void destroy_propfind_parser(PropfindParser *parser);
+int get_propfind_response(PropfindParser *parser, ResponseTag *result);
+void cleanup_response(ResponseTag *result);
+
+int hrefeq(DavSession *sn, char *href1, char *href2);
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path);
+void add_properties(DavResource *res, ResponseTag *response);
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response);
+int parse_response_tag(DavResource *resource, xmlNode *node);
+void set_davprops(DavResource *res);
+
+/*
+ * parses the content of a resourcetype element
+ * returns 1 if the resourcetype is a collection, 0 otherwise
+ */
+int parse_resource_type(xmlNode *node);
+
+UcxBuffer* create_proppatch_request(DavResourceData *data);
+UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash);
+
+CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response);
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock);
+
+CURLcode do_head_request(DavSession *sn);
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override);
+
+UcxBuffer* create_lock_request(void);
+int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock);
+CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout);
+CURLcode do_unlock_request(DavSession *sn, char *locktoken);
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken);
+
+CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METHODS_H */
+
diff --git a/libidav/resource.c b/libidav/resource.c
new file mode 100644 (file)
index 0000000..8853e44
--- /dev/null
@@ -0,0 +1,1557 @@
+/*
+ * 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 <stdbool.h>
+#include <libxml/tree.h>
+
+#include "utils.h"
+#include "session.h"
+#include "methods.h"
+#include "crypto.h"
+#include "ucx/buffer.h"
+#include "ucx/utils.h"
+
+#include "resource.h"
+#include "xml.h"
+#include "davqlexec.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+DavResource* dav_resource_new(DavSession *sn, char *path) {
+    //char *href = util_url_path(url);
+    //DavResource *res = dav_resource_new_href(sn, href);
+    char *parent = util_parent_path(path);
+    char *name = util_resource_name(path); 
+    char *href = dav_session_create_plain_href(sn, path);
+    
+    DavResource *res = dav_resource_new_full(sn, parent, name, href);
+    free(parent);
+    return res;
+}
+
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *name) {
+    char *path = util_concat_path(parent->path, name);
+    char *href = dav_session_create_plain_href(sn, path);
+    DavResource *res = dav_resource_new_full(sn, parent->path, name, href);
+    free(path);
+    return res;
+}
+
+
+DavResource* dav_resource_new_href(DavSession *sn, char *href) {  
+    DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    resource_set_info(res, href);
+    
+    // initialize resource data
+    res->data = resource_data_new(sn);
+    
+    return res;
+}
+
+DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href) {
+    sstr_t n = sstr(name);
+    // the name must not contain path separators
+    if(n.length > 0 && href) {
+        for(int i=0;i<n.length-1;i++) {
+            char c = n.ptr[i];
+            if(c == '/' || c == '\\') {
+                n = sstr(util_resource_name(href));
+                break;
+            }
+        }
+    }
+    // remove trailing '/'
+    if(n.length > 0 && n.ptr[n.length-1] == '/') {
+        n.length--;
+    }
+    
+    DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    res->name = sstrdup_a(sn->mp->allocator, n).ptr;
+    
+    char *path = util_concat_path(parent_path, name); 
+    res->path = dav_session_strdup(sn, path);
+    
+    res->href = href;
+    
+    // initialize resource data
+    res->data = resource_data_new(sn);
+    
+    // cache href/path
+    if(href) {
+        dav_session_cache_path(sn, sstr(path), sstr(href));
+    }
+    free(path);
+    
+    return res;
+}
+
+void resource_free_properties(DavSession *sn, UcxMap *properties) {
+    if(!properties) return;
+    
+    UcxMapIterator i = ucx_map_iterator(properties);
+    DavProperty *property;
+    UCX_MAP_FOREACH(key, property, i) {
+        // TODO: free everything
+        dav_session_free(sn, property);
+    }
+    ucx_map_free(properties);
+}
+
+void dav_resource_free(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    dav_session_free(sn, res->name);
+    dav_session_free(sn, res->path);
+    if(res->href) {
+        dav_session_free(sn, res->href);
+    }
+    
+    DavResourceData *data = res->data;
+    resource_free_properties(sn, data->properties);
+    resource_free_properties(sn, data->crypto_properties);
+    
+    UCX_FOREACH(elm, data->set) {
+        DavProperty *p = elm->data;
+        dav_session_free(sn, p->ns->name);
+        if(p->ns->prefix) {
+            dav_session_free(sn, p->ns->prefix);
+        }
+        dav_session_free(sn, p->ns);
+        
+        dav_session_free(sn, p->name);
+        dav_session_free(sn, p->value);
+        dav_session_free(sn, p);
+    }
+    
+    UCX_FOREACH(elm, data->remove) {
+        DavProperty *p = elm->data;
+        dav_session_free(sn, p->ns->name);
+        if(p->ns->prefix) {
+            dav_session_free(sn, p->ns->prefix);
+        }
+        dav_session_free(sn, p->ns);
+        
+        dav_session_free(sn, p->name);
+        dav_session_free(sn, p);
+    }
+    
+    if(!data->read && data->content) {
+        dav_session_free(sn, data->content);
+    }
+    dav_session_free(sn, data);
+    
+    dav_session_free(sn, res);
+}
+
+void dav_resource_free_all(DavResource *res) {
+    DavResource *child = res->children;
+    dav_resource_free(res);
+    while(child) {
+        DavResource *next = child->next;
+        dav_resource_free_all(child);
+        child = next;
+    }
+}
+
+void resource_set_href(DavResource *res, sstr_t href) {
+    res->href = sstrdup_a(res->session->mp->allocator, href).ptr;
+}
+
+void resource_set_info(DavResource *res, char *href_str) {
+    char *url_str = NULL;
+    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
+    sstr_t name = sstr(util_resource_name(href_str));
+    sstr_t href = sstr(href_str);
+    
+    sstr_t base_href = sstr(util_url_path(res->session->base_url));
+    sstr_t path = sstrsubs(href, base_href.length - 1);
+    
+    UcxAllocator *a = res->session->mp->allocator;
+    CURL *handle = res->session->handle;
+    
+    int nlen = 0;
+    char *uname = curl_easy_unescape(handle, name.ptr, name.length , &nlen);
+    int plen = 0;
+    char *upath = curl_easy_unescape(handle, path.ptr, path.length, &plen); 
+    
+    res->name = sstrdup_a(a, sstrn(uname, nlen)).ptr;
+    res->href = sstrdup_a(a, href).ptr;
+    res->path = sstrdup_a(a, sstrn(upath, plen)).ptr;
+    
+    curl_free(uname);
+    curl_free(upath);
+}
+
+DavResourceData* resource_data_new(DavSession *sn) {
+    DavResourceData *data = ucx_mempool_malloc(
+            sn->mp,
+            sizeof(DavResourceData));
+    if(!data) {
+        return NULL;
+    }
+    data->properties = ucx_map_new_a(sn->mp->allocator, 32);
+    data->crypto_properties = NULL;
+    data->set = NULL;
+    data->remove = NULL;
+    data->crypto_set = NULL;
+    data->crypto_remove = NULL;
+    data->read = NULL;
+    data->content = NULL;
+    data->seek = NULL;
+    data->length = 0;
+    return data;
+}
+
+char* dav_resource_get_href(DavResource *resource) {
+    if(!resource->href) {
+        resource->href = dav_session_get_href(
+                resource->session,
+                resource->path);
+    }
+    return resource->href;
+}
+
+void resource_add_prop(DavResource *res, const char *ns, const char *name, DavXmlNode *val) {
+    DavSession *sn = res->session;
+    
+    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+    namespace->prefix = NULL;
+    namespace->name = dav_session_strdup(sn, ns);
+    
+    DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+    prop->name = dav_session_strdup(sn, name);
+    prop->ns = namespace;
+    prop->value = val;
+    
+    sstr_t key = dav_property_key(ns, name);
+    ucx_map_sstr_put(((DavResourceData*)res->data)->properties, key, prop);
+    free(key.ptr);
+}
+
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val) {
+    if(!val) {
+        return;
+    }
+    
+    resource_add_prop(res, ns, name, dav_convert_xml(res->session, val));
+}
+
+void resource_add_string_property(DavResource *res, char *ns, char *name, char *val) {
+    if(!val) {
+        return;
+    }
+    
+    resource_add_prop(res, ns, name, dav_text_node(res->session, val));
+}
+
+void resource_set_crypto_properties(DavResource *res, UcxMap *cprops) {
+    DavResourceData *data = res->data;
+    resource_free_properties(res->session, data->crypto_properties);
+    data->crypto_properties = cprops;
+}
+
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name) {
+    sstr_t keystr = dav_property_key(ns, name);
+    UcxKey key = ucx_key(keystr.ptr, keystr.length);
+    DavXmlNode *ret = resource_get_property_k(res, key);
+    free(keystr.ptr);
+    
+    return ret;
+}
+
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name) {
+    sstr_t keystr = dav_property_key(ns, name);
+    UcxKey key = ucx_key(keystr.ptr, keystr.length);
+    DavXmlNode *ret = resource_get_encrypted_property_k(res, key);
+    free(keystr.ptr);
+    
+    return ret;
+}
+
+DavXmlNode* resource_get_property_k(DavResource *res, UcxKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = ucx_map_get(data->properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = ucx_map_get(data->crypto_properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+sstr_t dav_property_key(const char *ns, const char *name) {
+    return dav_property_key_a(ucx_default_allocator(), ns, name);
+}
+
+sstr_t dav_property_key_a(UcxAllocator *a, const char *ns, const char *name) {
+    scstr_t ns_str = scstr(ns);
+    scstr_t name_str = scstr(name);
+    
+    return sstrcat_a(a, 4, ns_str, S("\0"), name_str, S("\0"));
+}
+
+
+
+
+void resource_add_child(DavResource *parent, DavResource *child) {
+    child->next = NULL;
+    if(parent->children) {
+        DavResource *last = parent->children;
+        while(last->next) {
+            last = last->next;
+        }
+        last->next = child;
+        child->prev = last;
+    } else {
+        child->prev = NULL;
+        parent->children = child;
+    }
+    child->parent = parent;
+}
+
+static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion *cr) {
+    if(!(res1 && res2)) {
+        return 0;
+    }
+    
+    int ret;
+    if(cr->type == 0) {
+        switch(cr->column.resprop) {
+            case DAVQL_RES_NAME: {
+                ret = strcmp(res1->name, res2->name);
+                break;
+            }
+            case DAVQL_RES_PATH: {
+                ret = strcmp(res1->path, res2->path);
+                break;
+            }
+            case DAVQL_RES_HREF: {
+                ret = strcmp(res1->href, res2->href);
+                break;
+            }
+            case DAVQL_RES_CONTENTLENGTH: {
+                int c = res1->contentlength == res2->contentlength;
+                ret = c ? 0 : (res1->contentlength < res2->contentlength?-1:1);
+                break;
+            }
+            case DAVQL_RES_CONTENTTYPE: {
+                ret = strcmp(res1->contenttype, res2->contenttype);
+                break;
+            }
+            case DAVQL_RES_CREATIONDATE: {
+                int c = res1->creationdate == res2->creationdate;
+                ret = c ? 0 : (res1->creationdate < res2->creationdate?-1:1);
+                break;
+            }
+            case DAVQL_RES_LASTMODIFIED: {
+                int c = res1->lastmodified == res2->lastmodified;
+                ret = c ? 0 : (res1->lastmodified < res2->lastmodified?-1:1);
+                break;
+            }
+            case DAVQL_RES_ISCOLLECTION: {
+                int c = res1->iscollection == res2->iscollection;
+                ret = c ? 0 : (res1->iscollection < res2->iscollection?-1:1);
+                break;
+            }
+            default: ret = 0;
+        }
+    } else if(cr->type == 1) {
+        DavXmlNode *xvalue1 = resource_get_property_k(res1, cr->column.property);
+        DavXmlNode *xvalue2 = resource_get_property_k(res2, cr->column.property);
+        char *value1 = dav_xml_getstring(xvalue1);
+        char *value2 = dav_xml_getstring(xvalue2);
+        if(!value1) {
+            ret = value2 ? -1 : 0;
+        } else if(!value2) {
+            ret = value1 ? 1 : 0;
+        } else {
+            ret = strcmp(value1, value2);
+        }
+    } else {
+        return 0;
+    }
+    
+    return cr->descending ? -ret : ret;
+}
+
+void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr) {
+    if(!ordercr) {
+        resource_add_child(parent, child);
+        return;
+    }
+    
+    child->parent = parent;
+    
+    if(!parent->children) {
+        child->next = NULL;
+        child->prev = NULL;
+        parent->children = child;
+    } else {
+        DavResource *resource = parent->children;
+        while(resource) {
+            int r = 0;
+            UCX_FOREACH(elm, ordercr) {
+                DavOrderCriterion *cr = elm->data;
+                r = resource_cmp(child, resource, cr);
+                if(r != 0) {
+                    break;
+                }
+            }
+            
+            if(r < 0) {
+                // insert child before resource
+                child->prev = resource->prev;
+                child->next = resource;
+                if(resource->prev) {
+                    resource->prev->next = child;
+                } else {
+                    parent->children = child;
+                }
+                resource->prev = child;
+                break;
+            } if(!resource->next) {
+                // append child
+                child->prev = resource;
+                child->next = NULL;
+                resource->next = child;
+                break;
+            } else {
+                resource = resource->next;
+            }
+        }
+    }
+}
+
+char* dav_get_string_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    if(!pns || !pname) {
+        return NULL;
+    }
+    return dav_get_string_property_ns(res, pns, pname);
+}
+
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name) {
+    DavXmlNode *prop = dav_get_property_ns(res, ns, name);
+    if(!prop) {
+        return NULL;
+    }
+    return dav_xml_getstring(prop);
+}
+
+DavXmlNode* dav_get_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    if(!pns || !pname) {
+        return NULL;
+    }
+    return dav_get_property_ns(res, pns, pname);
+}
+
+static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const char *ns, const char *name) {
+    if(!ns || !name) {
+        return NULL;
+    }
+    
+    DavResourceData *data = res->data;
+    
+    DavXmlNode *property = NULL;
+    UcxList *remove_list = NULL;
+    UcxList *set_list = NULL;
+    
+    if(encrypted) {
+        // check if crypto_properties because it will only be created
+        // if the resource has encrypted properties
+        if(!data->crypto_properties) {
+            return NULL;
+        }
+        property = resource_get_encrypted_property(res, ns, name);
+        remove_list = data->crypto_remove;
+        set_list = data->crypto_set;
+    } else {
+        property = resource_get_property(res, ns, name);
+        remove_list = data->remove;
+        set_list = data->set;
+    }
+    
+    // resource_get_property only returns persistent properties
+    // check the remove and set list
+    if(property) {
+        // if the property is in the remove list, we return NULL
+        UCX_FOREACH(elm, remove_list) {
+            DavProperty *p = elm->data;
+            if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+                return NULL;
+            }
+        }
+    }
+    // the set list contains property updates
+    // we return an updated property if possible
+    UCX_FOREACH(elm, set_list) {
+        DavProperty *p = elm->data;
+        if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+            return p->value; // TODO: fix
+        }
+    }
+    // no property update
+    
+    return property;
+}
+
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name) {
+    DavXmlNode *property_value = get_property_ns(res, FALSE, ns, name);
+    
+    if(!property_value && DAV_DECRYPT_PROPERTIES(res->session)) {
+        property_value = get_property_ns(res, TRUE, ns, name);
+    }
+    
+    return property_value;
+}
+
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name) {
+    return get_property_ns(res, TRUE, ns, name);
+}
+
+static DavProperty* createprop(DavSession *sn, const char *ns, const char *name) {
+    DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+    property->name = dav_session_strdup(sn, name);
+    property->value = NULL;
+    
+    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+    namespace->prefix = NULL;
+    namespace->name = dav_session_strdup(sn, ns);
+    
+    property->ns = namespace;
+    
+    return property;
+}
+
+void dav_set_string_property(DavResource *res, char *name, char *value) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_set_string_property_ns(res, pns, pname, value);
+}
+
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    DavSession *sn = res->session;
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+    } else {
+        data->set = ucx_list_append_a(a, data->set, property);
+    }
+}
+
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_set_property_ns(res, pns, pname, value);
+}
+
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    DavSession *sn = res->session;
+    UcxAllocator *a = sn->mp->allocator; 
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(sn, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+    } else {
+        data->set = ucx_list_append_a(a, data->set, property);
+    }
+}
+
+void dav_remove_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_remove_property_ns(res, pns, pname);
+}
+
+void dav_remove_property_ns(DavResource *res, char *ns, char *name) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    UcxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        data->crypto_remove = ucx_list_append_a(a, data->crypto_remove, property);
+    } else {
+        data->remove = ucx_list_append_a(a, data->remove, property);
+    }
+}
+
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+}
+
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+}
+
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
+    DavResourceData *data = res->data;
+    UcxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    data->crypto_remove = ucx_list_append_a(a, data->crypto_remove, property);
+}
+
+static int compare_propname(const void *a, const void *b) {
+    const DavPropName *p1 = a;
+    const DavPropName *p2 = b;
+    
+    int result = strcmp(p1->ns, p2->ns);
+    if(result) {
+        return result;
+    } else {
+        return strcmp(p1->name, p2->name);
+    }
+}
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count) {
+    DavResourceData *data = res->data;
+    
+    *count = data->properties->count;
+    DavPropName *names = dav_session_calloc(
+            res->session,
+            *count,
+            sizeof(DavPropName));
+    
+    
+    UcxMapIterator i = ucx_map_iterator(data->properties);
+    DavProperty *value;
+    int j = 0;
+    UCX_MAP_FOREACH(key, value, i) {
+        DavPropName *name = &names[j];
+        
+        name->ns = value->ns->name;
+        name->name = value->name;
+        
+        j++;
+    }
+    
+    qsort(names, *count, sizeof(DavPropName), compare_propname);
+    
+    return names;
+}
+
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    DavResourceData *data = res->data;
+    data->content = stream;
+    data->read = read_func;
+    data->seek = seek_func;
+    data->length = 0;
+}
+
+void dav_set_content_data(DavResource *res, char *content, size_t length) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    data->content = dav_session_malloc(sn, length);
+    memcpy(data->content, content, length);
+    data->read = NULL;
+    data->seek = NULL;
+    data->length = length;
+}
+
+void dav_set_content_length(DavResource *res, size_t length) {
+    DavResourceData *data = res->data;
+    data->length = length;
+}
+
+
+int dav_load(DavResource *res) {
+    UcxBuffer *rqbuf = create_allprop_propfind_request();
+    int ret = dav_propfind(res->session, res, rqbuf);
+    ucx_buffer_free(rqbuf);
+    return ret;
+}
+
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
+    UcxMempool *mp = ucx_mempool_new(64);
+    
+    UcxList *proplist = NULL;
+    for(size_t i=0;i<numprop;i++) {
+        DavProperty *p = ucx_mempool_malloc(mp, sizeof(DavProperty));
+        p->name = properties[i].name;
+        p->ns = ucx_mempool_malloc(mp, sizeof(DavNamespace));
+        p->ns->name = properties[i].ns;
+        if(!strcmp(properties[i].ns, "DAV:")) {
+            p->ns->prefix = "D";
+        } else {
+            p->ns->prefix = ucx_asprintf(mp->allocator, "x%d", i).ptr;
+        }
+        p->value = NULL;
+        proplist = ucx_list_append_a(mp->allocator, proplist, p);
+    }
+    
+    UcxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
+    int ret = dav_propfind(res->session, res, rqbuf);
+    ucx_buffer_free(rqbuf);
+    ucx_mempool_destroy(mp);
+    return ret;
+}
+
+
+/*
+ * read wrapper with integrated hashing
+ */
+
+typedef struct {
+    DAV_SHA_CTX *sha;
+    void *stream;
+    dav_read_func read;
+    dav_seek_func seek;
+    int error;
+} HashStream;
+
+static void init_hash_stream(HashStream *hstr, void *stream, dav_read_func readfn, dav_seek_func seekfn) {
+    hstr->sha = NULL;
+    hstr->stream = stream;
+    hstr->read = readfn;
+    hstr->seek = seekfn;
+    hstr->error = 0;
+}
+
+static size_t dav_read_h(void *buf, size_t size, size_t nelm, void *stream) {
+    HashStream *s = stream;
+    if(!s->sha) {
+        s->sha = dav_hash_init();
+    }
+     
+    size_t r = s->read(buf, size, nelm, s->stream);
+    dav_hash_update(s->sha, buf, r);
+    return r;
+}
+
+static int dav_seek_h(void *stream, long offset, int whence) {
+    HashStream *s = stream;
+    if(offset == 0 && whence == SEEK_SET) {
+        unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
+        dav_hash_final(s->sha, buf);
+        s->sha = NULL;
+    } else {
+        s->error = 1;
+    }
+    return s->seek(s->stream, offset, whence);
+}
+
+
+int dav_store(DavResource *res) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(sn, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    // store content
+    if(data->content) {
+        int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key;
+        CURLcode ret;
+        if(encryption) {
+            AESEncrypter *enc = NULL;
+            UcxBuffer *buf = NULL;
+            if(data->read) {
+                enc = aes_encrypter_new(
+                        sn->key,
+                        data->content,
+                        data->read,
+                        data->seek);
+            } else {
+                buf = ucx_buffer_new(data->content, data->length, 0);
+                buf->size = data->length;
+                enc = aes_encrypter_new(
+                        sn->key,
+                        buf,
+                        (dav_read_func)ucx_buffer_read,
+                        (dav_seek_func)dav_buffer_seek);
+            }
+              
+            // put resource
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    enc,
+                    (dav_read_func)aes_read,
+                    (dav_seek_func)aes_encrypter_reset,
+                    0);
+            
+            // get sha256 hash
+            dav_get_hash(&enc->sha256, (unsigned char*)data->hash);
+            char *enc_hash = aes_encrypt(data->hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+            
+            aes_encrypter_close(enc);
+            if(buf) {
+                ucx_buffer_free(buf);
+            }
+            
+            // add crypto properties
+            // TODO: store the properties later
+            if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
+                free(enc_hash);
+                return 1;
+            }
+            resource_add_string_property(res, DAV_NS, "crypto-hash", enc_hash);
+            free(enc_hash);
+        } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+            HashStream hstr;
+            UcxBuffer *iobuf = NULL;
+            if(!data->read) {
+                iobuf = ucx_buffer_new(data->content, data->length, 0);
+                iobuf->size = data->length;
+                init_hash_stream(
+                        &hstr,
+                        iobuf,
+                        (dav_read_func)ucx_buffer_read,
+                        (dav_seek_func)ucx_buffer_seek);
+            } else {
+                init_hash_stream(
+                        &hstr,
+                        data->content,
+                        data->read,
+                        data->seek);
+            }
+            
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    &hstr,
+                    dav_read_h,
+                    (dav_seek_func)dav_seek_h,
+                    data->length);
+            
+            if(hstr.sha) {
+                dav_hash_final(hstr.sha, (unsigned char*)data->hash);
+                char *hash = util_hexstr((unsigned char*)data->hash, 32);
+                dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
+                free(hash);
+            }
+        } else {
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    data->content,
+                    data->read,
+                    data->seek,
+                    data->length);
+        }
+        
+        long status = 0;
+        curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &status);
+        if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+            res->session->error = 0;
+            // cleanup node data
+            if(!data->read) {
+                ucx_mempool_free(sn->mp, data->content);
+            }
+            data->content = NULL;
+            data->read = NULL;
+            data->length = 0;
+        } else {
+            dav_session_set_error(sn, ret, status);
+            return 1;
+        }
+    }
+    
+    // generate crypto-prop content
+    if(DAV_ENCRYPT_PROPERTIES(sn) && sn->key && (data->crypto_set || data->crypto_remove)) {
+        DavResource *crypto_res = dav_resource_new_href(sn, res->href);
+        int ret = 1;
+        
+        if(crypto_res) {
+            UcxBuffer *rqbuf = create_cryptoprop_propfind_request();
+            ret = dav_propfind(res->session, res, rqbuf);
+            ucx_buffer_free(rqbuf);
+        }
+        
+        if(!ret) {
+            DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
+            UcxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
+            if(!crypto_props) {
+                // resource hasn't encrypted properties yet
+                crypto_props = ucx_map_new(32); // create new map
+            }
+            
+            // remove all properties
+            UCX_FOREACH(elm, data->crypto_remove) {
+