add libidav code

Mon, 22 Jan 2024 17:27:47 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 22 Jan 2024 17:27:47 +0100
changeset 1
b5bb7b3cd597
parent 0
2483f517c562
child 2
fbdfaacc4182

add libidav code

.hgignore file | annotate | diff | comparison | revisions
libidav/Makefile file | annotate | diff | comparison | revisions
libidav/crypto.c file | annotate | diff | comparison | revisions
libidav/crypto.h file | annotate | diff | comparison | revisions
libidav/davqlexec.c file | annotate | diff | comparison | revisions
libidav/davqlexec.h file | annotate | diff | comparison | revisions
libidav/davqlparser.c file | annotate | diff | comparison | revisions
libidav/davqlparser.h file | annotate | diff | comparison | revisions
libidav/methods.c file | annotate | diff | comparison | revisions
libidav/methods.h file | annotate | diff | comparison | revisions
libidav/resource.c file | annotate | diff | comparison | revisions
libidav/resource.h file | annotate | diff | comparison | revisions
libidav/session.c file | annotate | diff | comparison | revisions
libidav/session.h file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
libidav/versioning.c file | annotate | diff | comparison | revisions
libidav/versioning.h file | annotate | diff | comparison | revisions
libidav/webdav.c file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
libidav/xml.c file | annotate | diff | comparison | revisions
libidav/xml.h file | annotate | diff | comparison | revisions
make/vs/idav.sln file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/idav/main.c file | annotate | diff | comparison | revisions
make/vs/libidav/libidav.vcxproj file | annotate | diff | comparison | revisions
make/vs/libidav/libidav.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/vcpkg.json file | annotate | diff | comparison | revisions
--- a/.hgignore	Sun Jan 21 16:30:18 2024 +0100
+++ b/.hgignore	Mon Jan 22 17:27:47 2024 +0100
@@ -4,6 +4,7 @@
 relre:^make/vs/.vs/.*
 relre:^make/vs/packages/.*
 relre:^make/vs/.*vcxproj\.user
+relre:^make/vs/vcpkg_installed/.*
 relre:^ui/winui/winui\.vcxproj\.user
 relre:^ui/winui/Generated Files
 relre:^ui/wpf/UIcore/obj
@@ -12,4 +13,4 @@
 relre:^ui/wpf/UIwrapper/UIwrapper/Debug
 relre:^ui/wpf/UIwrapper/UIwrapper/Release
 relre:^ui/wpf/UIwrapper/UIwrapper/x64
-relre:^ui/wpf/UIwrapper/ipch
\ No newline at end of file
+relre:^ui/wpf/UIwrapper/ipch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/Makefile	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,57 @@
+#
+# 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.
+#
+
+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)$@ $(OBJ)
+
+../build/libidav/%$(OBJ_EXT): %.c
+	$(CC) -I../ucx $(CFLAGS) $(DAV_CFLAGS) -c -o $@ $<
+
+cppcheck: $(SRC)
+	$(CPPCHECK) $(CPPCHECK_CONFIG) -I../ucx $(CPPCHECK_FLAGS) $+ 2>> ../$(CPPCHECK_LOG)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/crypto.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1542 @@
+/*
+ * 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 <fcntl.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#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);
+        // I think we don't need this
+        /*
+        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);
+        // TODO: check if this still works
+        /*
+        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;
+       
+    // decrypt
+    if(clen > 0) {
+        unsigned char* out = malloc(outlen);
+
+        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);
+        (char*)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
+
+
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key) {
+    CxBuffer *encbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!encbuf) {
+        return NULL;
+    }
+    
+    AESEncrypter *enc = aes_encrypter_new(
+            key,
+            in,
+            (dav_read_func)cxBufferRead,
+            NULL);
+    if(!enc) {
+        cxBufferFree(encbuf);
+        return NULL;
+    }
+    
+    char buf[1024];
+    size_t r;
+    while((r = aes_read(buf, 1, 1024, enc)) > 0) {
+        cxBufferWrite(buf, 1, r, encbuf);
+    }
+    aes_encrypter_close(enc);
+    
+    encbuf->pos = 0;
+    return encbuf;
+}
+
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key) {
+    CxBuffer *decbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!decbuf) {
+        return NULL;
+    }
+    AESDecrypter *dec = aes_decrypter_new(
+            key,
+            decbuf,
+            (dav_write_func)cxBufferWrite);
+    if(!dec) {
+        cxBufferFree(decbuf);
+        return NULL;
+    }
+    
+    aes_write(in->space, 1, in->size, dec);
+    aes_decrypter_shutdown(dec);
+    aes_decrypter_close(dec);
+    decbuf->pos = 0;
+    return decbuf;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/crypto.h	Mon Jan 22 17:27:47 2024 +0100
@@ -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 <cx/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);
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key);
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DAV_CRYPTO_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/davqlexec.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1485 @@
+/*
+ * 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 <cx/utils.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.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;
+    
+    if(!st->args) {
+        args->first = NULL;
+        args->current = NULL;
+        return args;
+    }
+    
+    DavQLArg *cur = NULL;
+    CxIterator i = cxListIterator(st->args);
+    cx_foreach(void*, data, i) {
+        intptr_t type = (intptr_t)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;
+}
+
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, a, CX_BUFFER_AUTO_EXTEND);
+    
+    int placeholder = 0;
+    for(int i=0;i<fstr.length;i++) {
+        char c = fstr.ptr[i];
+        if(placeholder) {
+            if(c == '%') {
+                // no placeholder, %% transposes to %
+                cxBufferPut(&buf, c);
+            } else {
+                // detect placeholder type and insert arg
+                int err = 0;
+                switch(c) {
+                    case 's': {
+                        char *arg = dav_ql_getarg_str(ap);
+                        cxBufferPutString(&buf, arg);
+                        break;
+                    }
+                    case 'd': {
+                        int arg = dav_ql_getarg_int(ap);
+                        cx_bprintf(&buf, "%d", arg);
+                        break;
+                    }
+                    case 'u': {
+                        unsigned int arg = dav_ql_getarg_uint(ap);
+                        cx_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) {
+                    cxBufferDestroy(&buf);
+                    return (cxmutstr){NULL,0};
+                }
+            }
+            placeholder = 0;
+        } else {
+            if(c == '%') {
+                placeholder = 1;
+            } else {
+                cxBufferPut(&buf, c);
+            }
+        }
+    }
+    if(cxBufferPut(&buf, '\0')) {
+        *error = DAVQL_OOM;
+        cxBufferDestroy(&buf);
+        return (cxmutstr){NULL, 0};
+    }
+    *error = DAVQL_OK;
+    
+    return cx_mutstrn(buf.space, buf.size-1);
+}
+
+static int fl_add_properties(DavSession *sn, const CxAllocator *a, CxMap *map, DavQLExpression *expression) {
+    if(!expression) {
+        return 0;
+    }
+    
+    if(expression->type == DAVQL_IDENTIFIER) {
+        DavProperty *property = cxMalloc(a, sizeof(DavProperty));
+
+        char *name;
+        DavNamespace *ns = dav_get_property_namespace(
+                sn->context,
+                cx_strdup_a(a, expression->srctext).ptr,
+                &name);
+        if(!ns) {
+            return -1;
+        }
+        
+        property->ns = ns;
+        property->name = name;
+        property->value = NULL;
+        
+        cxMapPut(map, cx_hash_key(expression->srctext.ptr, expression->srctext.length), property);
+    }
+    
+    if(expression->left) {
+        if(fl_add_properties(sn, a, map, expression->left)) {
+            return -1;
+        }
+    }
+    if(expression->right) {
+        if(fl_add_properties(sn, a, map, expression->right)) {
+            return -1;
+        }
+    }
+    
+    return 0;
+}
+
+static CxBuffer* fieldlist2propfindrequest(DavSession *sn, const CxAllocator *a, CxList *fields, int *isallprop) {
+    CxMap *properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    *isallprop = 0;
+    
+    CxIterator i = cxListIterator(fields);
+    cx_foreach(DavQLField*, field, i) {
+        if(!cx_strcmp(field->name, CX_STR("*"))) {
+            cxMapDestroy(properties);
+            *isallprop = 1;
+            return create_allprop_propfind_request();
+        } else if(!cx_strcmp(field->name, CX_STR("-"))) {
+            cxMapDestroy(properties);
+            return create_propfind_request(sn, NULL, "propfind", 0);
+        } else {
+            if(fl_add_properties(sn, a, properties, field->expr)) {
+                // TODO: set error
+                cxMapDestroy(properties);
+                return NULL;
+            }
+        }
+    }
+    
+    i = cxMapIteratorValues(properties);
+    CxList *list = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    cx_foreach(DavProperty*, value, i) {
+        cxListAdd(list, value);
+    }
+    
+    CxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0);
+    cxListDestroy(list);
+    cxMapDestroy(properties);
+    return reqbuf;
+}
+
+static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, CxList *fields) {
+    CxMap *new_properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
+    DavResourceData *data = (DavResourceData*)res->data;
+    
+    // add basic properties
+    void *value;
+    
+    cxmutstr cl_keystr = dav_property_key("DAV:", "getcontentlength");
+    CxHashKey cl_key = cx_hash_key(cl_keystr.ptr, cl_keystr.length);
+    value = cxMapGet(data->properties, cl_key);
+    if(value) {
+        cxMapPut(new_properties, cl_key, value);
+    }
+    
+    cxmutstr cd_keystr = dav_property_key("DAV:", "creationdate");
+    CxHashKey cd_key = cx_hash_key(cd_keystr.ptr, cd_keystr.length);
+    value = cxMapGet(data->properties, cd_key);
+    if(value) {
+        cxMapPut(new_properties, cd_key, value);
+    }
+    
+    cxmutstr lm_keystr = dav_property_key("DAV:", "getlastmodified");
+    CxHashKey lm_key = cx_hash_key(lm_keystr.ptr, lm_keystr.length);
+    value = cxMapGet(data->properties, lm_key);
+    if(value) {
+        cxMapPut(new_properties, lm_key, value);
+    }
+    
+    cxmutstr ct_keystr = dav_property_key("DAV:", "getcontenttype");
+    CxHashKey ct_key = cx_hash_key(ct_keystr.ptr, ct_keystr.length);
+    value = cxMapGet(data->properties, ct_key);
+    if(value) {
+        cxMapPut(new_properties, ct_key, value);
+    }
+    
+    cxmutstr rt_keystr = dav_property_key("DAV:", "resourcetype");
+    CxHashKey rt_key = cx_hash_key(rt_keystr.ptr, rt_keystr.length);
+    value = cxMapGet(data->properties, rt_key);
+    if(value) {
+        cxMapPut(new_properties, rt_key, value);
+    }
+    
+    cxmutstr cn_keystr = dav_property_key(DAV_NS, "crypto-name");
+    CxHashKey cn_key = cx_hash_key(cn_keystr.ptr, cn_keystr.length);
+    value = cxMapGet(data->properties, cn_key);
+    if(value) {
+        cxMapPut(new_properties, cn_key, value);
+    }
+    
+    cxmutstr ck_keystr = dav_property_key(DAV_NS, "crypto-key");
+    CxHashKey ck_key = cx_hash_key(ck_keystr.ptr, ck_keystr.length);
+    value = cxMapGet(data->properties, ck_key);
+    if(value) {
+        cxMapPut(new_properties, ck_key, value);
+    }
+    
+    cxmutstr ch_keystr = dav_property_key(DAV_NS, "crypto-hash");
+    CxHashKey ch_key = cx_hash_key(ch_keystr.ptr, ch_keystr.length);
+    value = cxMapGet(data->properties, ch_key);
+    if(value) {
+        cxMapPut(new_properties, ch_key, value);
+    }
+    
+    // add properties from field list
+    if(fields) {
+        CxIterator i = cxListIterator(fields);
+        cx_foreach(DavCompiledField*, field, i) {
+            DavQLStackObj field_result;
+            if(!dav_exec_expr(field->code, res, &field_result)) {
+                cxmutstr str;
+                str.ptr = NULL;
+                str.length = 0;
+                DavXmlNode *node = NULL;
+                if(field_result.type == 0) {
+                    str = cx_asprintf_a(
+                            sn->mp->allocator,
+                            "%" PRId64,
+                            field_result.data.integer);
+                } else if(field_result.type == 1) {
+                    if(field_result.data.string) {
+                        str = cx_strdup_a(sn->mp->allocator, cx_strn(
+                                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) {
+                    cxmutstr 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;
+
+                    cxMapPut(new_properties, cx_hash_key(key.ptr, key.length), prop);
+                    free(key.ptr);
+                }
+            } else {
+                // TODO: error
+                resource_free_properties(sn, new_properties);
+                return -1;
+            }
+        }
+    }
+    
+    cxMapRemove(data->properties, cl_key);
+    cxMapRemove(data->properties, cd_key);
+    cxMapRemove(data->properties, lm_key);
+    cxMapRemove(data->properties, ct_key);
+    cxMapRemove(data->properties, rt_key);
+    cxMapRemove(data->properties, cn_key);
+    cxMapRemove(data->properties, ck_key);
+    cxMapRemove(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) {
+    CxMempool *mp = cxMempoolCreate(128, NULL);
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    DavQLArgList *args = dav_ql_get_args(st, ap);
+    if(!args) {
+        return result;
+    }
+    cxMempoolRegister(mp, args, (cx_destructor_func)dav_ql_free_arglist);
+    
+    int isallprop;
+    CxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp->allocator, st->fields, &isallprop);
+    if(!rqbuf) {
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    cxMempoolRegister(mp, rqbuf, (cx_destructor_func)cxBufferFree);
+    
+    // compile field list
+    CxList *cfieldlist = cxLinkedListCreate(mp->allocator, NULL, CX_STORE_POINTERS);
+    if(st->fields) {
+        CxIterator i = cxListIterator(st->fields);
+        cx_foreach(DavQLField*, field, i) {
+            if(cx_strcmp(field->name, CX_STR("*")) && cx_strcmp(field->name, CX_STR("-"))) {
+                // compile field expression
+                CxBuffer *code = dav_compile_expr(
+                        sn->context,
+                        mp->allocator,
+                        field->expr,
+                        args);
+                if(!code) {
+                    // TODO: set error string
+                    return result;
+                }
+                DavCompiledField *cfield = cxMalloc(
+                        mp->allocator,
+                        sizeof(DavCompiledField));
+
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                    sn->context,
+                    cx_strdup_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;
+                cxListAdd(cfieldlist, cfield);
+            } 
+        }
+    }
+    
+    // get path string
+    davqlerror_t error;
+    cxmutstr path = dav_format_string(mp->allocator, st->path, args, &error);
+    if(error) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    int depth = st->depth == DAV_DEPTH_PLACEHOLDER ?
+            dav_ql_getarg_int(args) : st->depth;
+    
+    CxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args);
+    if(st->where && !where) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    // compile order criterion
+    CxList *ordercr = NULL;
+    if(st->orderby) {
+        ordercr = cxLinkedListCreate(mp->allocator, NULL, sizeof(DavOrderCriterion));
+        CxIterator i = cxListIterator(st->orderby);
+        cx_foreach(DavQLOrderCriterion*, oc, i) {
+            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;
+                cxstring propertyname = cx_strchr(column->srctext, ':');
+                if(propertyname.length > 0) {
+                    char *ns;
+                    char *name;
+                    dav_get_property_namespace_str(
+                            sn->context,
+                            cx_strdup_a(mp->allocator, column->srctext).ptr,
+                            &ns,
+                            &name);
+                    if(ns && name) {
+                        DavOrderCriterion cr;
+                        cr.type = 1;
+                        cxmutstr keystr = dav_property_key_a(mp->allocator, ns, name);
+                        cr.column.property = cx_hash_key(keystr.ptr, keystr.length);
+                        cr.descending = oc->descending;
+                        cxListAdd(ordercr, &cr);
+                    } else {
+                        // error
+                        // TODO: cleanup
+                        cxMempoolDestroy(mp);
+                        return result;
+                    }
+                } else if(dav_identifier2resprop(column->srctext, &resprop)) {
+                    DavOrderCriterion cr;
+                    cr.type = 0;
+                    cr.column.resprop = resprop;
+                    cr.descending = oc->descending;
+                    cxListAdd(ordercr, &cr);
+                } else {
+                    // error
+                    // TODO: cleanup
+                    cxMempoolDestroy(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
+                cxMempoolDestroy(mp);
+                return result;
+            }
+        }
+    }
+    
+    DavResource *selroot = dav_resource_new(sn, path.ptr);
+    
+    CxList *stack = cxLinkedListCreateSimple(sizeof(DavQLRes));
+    // initialize the stack with the requested resource
+    DavQLRes res;
+    res.resource = selroot;
+    res.depth = 0;
+    cxListInsert(stack, 0, &res);
+    
+    // reuseable response buffer
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, mp->allocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!rpbuf) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    result.result = selroot;
+    result.status = 0;
+    
+    // do a propfind request for each resource on the stack
+    while(stack->size > 0) {
+        DavQLRes *sr_ptr = cxListAt(stack, 0); // get first element from the stack
+        DavResource *root = sr_ptr->resource;
+        int res_depth = sr_ptr->depth;
+        cxListRemove(stack, 0); // remove first element
+        
+        util_set_url(sn, dav_resource_get_href(root));
+        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\n%.*s\n\n", root->href, (int)rpbuf->size, rpbuf->space);
+        //fflush(stdout);
+        
+        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);
+            if(!parser) {
+                result.status = -1;
+                break;
+            }
+            
+            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);
+                        cxListDestroy(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 > res_depth+1))
+                                {
+                                    DavQLRes rs;
+                                    rs.resource = child;
+                                    rs.depth = res_depth + 1;
+                                    cxListInsert(stack, 0, &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
+        cxBufferSeek(rpbuf, SEEK_SET, 0);
+    }
+    
+    cxMempoolDestroy(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(cxstring src, davqlresprop_t *prop) {
+    if(!cx_strcmp(src, CX_STR("name"))) {
+        *prop = DAVQL_RES_NAME;
+    } else if(!cx_strcmp(src, CX_STR("path"))) {
+        *prop = DAVQL_RES_PATH;
+    } else if(!cx_strcmp(src, CX_STR("href"))) {
+        *prop = DAVQL_RES_HREF;
+    } else if(!cx_strcmp(src, CX_STR("contentlength"))) {
+        *prop = DAVQL_RES_CONTENTLENGTH;
+    } else if(!cx_strcmp(src, CX_STR("contenttype"))) {
+        *prop = DAVQL_RES_CONTENTTYPE;
+    } else if(!cx_strcmp(src, CX_STR("creationdate"))) {
+        *prop = DAVQL_RES_CREATIONDATE;
+    } else if(!cx_strcmp(src, CX_STR("lastmodified"))) {
+        *prop = DAVQL_RES_LASTMODIFIED;
+    } else if(!cx_strcmp(src, CX_STR("iscollection"))) {
+        *prop = DAVQL_RES_ISCOLLECTION;
+    } else {
+        return 0;
+    }
+    return 1;
+}
+
+static int add_cmd(DavContext *ctx, const CxAllocator *a, CxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) {
+    if(!expr) {
+        return 0;
+    }
+     
+    int numcmd = 1;
+    DavQLCmd cmd;
+    memset(&cmd, 0, sizeof(DavQLCmd));
+    davqlerror_t error;
+    
+    cxstring 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)) {
+                cxBufferWrite(&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);
+            cxBufferWrite(&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);
+                cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            break;
+        }
+        case DAVQL_IDENTIFIER: {
+            cxstring propertyname = cx_strchr(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,
+                        cx_strdup_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(!cx_strcmp(src, CX_STR("true"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 1;
+                } else if(!cx_strcmp(src, CX_STR("false"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 0;
+                } else {
+                    // error, unknown identifier
+                    return -1;
+                }
+            }
+            cxBufferWrite(&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;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEG: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_NEG;
+                    cxBufferWrite(&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;
+            }
+            cxBufferWrite(&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;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LAND: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_AND;
+                    cxBufferWrite(&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);
+                    cxBufferWrite(&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;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd += nleft + nright;
+                    break;
+                }
+                case DAVQL_LXOR: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_XOR;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_EQ: {
+                    cmd.type = DAVQL_CMD_OP_EQ;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEQ: {
+                    cmd.type = DAVQL_CMD_OP_NEQ;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LT: {
+                    cmd.type = DAVQL_CMD_OP_LT;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GT: {
+                    cmd.type = DAVQL_CMD_OP_GT;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LE: {
+                    cmd.type = DAVQL_CMD_OP_LE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GE: {
+                    cmd.type = DAVQL_CMD_OP_GE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LIKE: {
+                    cmd.type = DAVQL_CMD_OP_LIKE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_UNLIKE: {
+                    cmd.type = DAVQL_CMD_OP_UNLIKE;
+                    cxBufferWrite(&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);
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    // TODO: resolve function name
+                    cmd.type = DAVQL_CMD_CALL;
+                    cmd.data.func = NULL;
+                    cxBufferWrite(&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;
+}
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList  *ap) {
+    CxBuffer *bcode = cxBufferCreate(NULL, 512, a, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!bcode) {
+        return NULL;
+    }
+    
+    if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) {
+        cxBufferFree(bcode);
+        return NULL;
+    }
+    
+    return bcode;
+}
+
+static int cmd_str_cmp(DavQLStackObj obj1, DavQLStackObj obj2, davqlcmdtype_t cmd) {
+    cxmutstr s1m = obj1.type == 1 ?
+        cx_mutstrn(obj1.data.string, obj1.length) :
+        cx_asprintf("%" PRId64, obj1.data.integer);
+    cxmutstr s2m = obj1.type == 1 ?
+        cx_mutstrn(obj2.data.string, obj2.length) :
+        cx_asprintf("%" PRId64, obj2.data.integer);
+    
+    cxstring s1 = cx_strcast(s1m);
+    cxstring s2 = cx_strcast(s2m);
+    
+    int res = 0;
+    switch(cmd) {
+        case DAVQL_CMD_OP_EQ: {
+            res = cx_strcmp(s1, s2) == 0;
+            break;
+        }
+        case DAVQL_CMD_OP_NEQ: {
+            res = cx_strcmp(s1, s2) != 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LT: {
+            res = cx_strcmp(s1, s2) < 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GT: {
+            res = cx_strcmp(s1, s2) > 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LE: {
+            res  = cx_strcmp(s1, s2) <= 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GE: {
+            res = cx_strcmp(s1, s2) >= 0;
+            break;
+        }
+        default: break;
+    }
+    
+    if(obj1.type == 0) {
+        free(s1m.ptr);
+    }
+    if(obj2.type == 0) {
+        free(s2m.ptr);
+    }
+    
+    return res;
+}
+
+int dav_exec_expr(CxBuffer *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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/davqlexec.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,187 @@
+/*
+ * 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 <cx/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,
+    DAVQL_OOM
+} 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;
+        cxmutstr       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;
+    CxBuffer *code;
+} DavCompiledField;
+
+typedef struct DavOrderCriterion {
+    int type; // 0: resprop, 1: property
+    union DavQLColumn {
+        davqlresprop_t resprop;
+        CxHashKey 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);
+
+CxBuffer* dav_path_string(cxmutstr src, DavQLArgList *args, davqlerror_t *error);
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error);
+
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap);
+
+int dav_identifier2resprop(cxstring src, davqlresprop_t *prop);
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap);
+
+int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result);
+
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DAVQLEXEC_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/davqlparser.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1860 @@
+/*
+ * 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 <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/printf.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: ");
+        CxIterator i = cxListIterator(stmt->fields);
+        cx_foreach(DavQLField *, f, i) {
+            printf("%.*s, ", (int)f->name.length, f->name.ptr);
+        }
+        printf("\b\b  \b\b\n");
+    }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+    // Basic information
+    size_t fieldcount = stmt->fields ? stmt->fields->size : 0;
+    int specialfield = 0;
+    if (stmt->fields && stmt->fields->size > 0) {
+        DavQLField* firstfield = (DavQLField*)cxListAt(stmt->fields, 0);
+        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",
+        (int)stmt->srctext.length, stmt->srctext.ptr,
+        _map_querytype(stmt->type),
+        fieldcount,
+        _map_specialfield(specialfield));
+    
+    dav_debug_ql_fnames_print(stmt);
+    printf("Path: %.*s\nHas where clause: %s\n",
+        (int)stmt->path.length, stmt->path.ptr,
+        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) {
+        CxIterator i = cxListIterator(stmt->orderby);
+        cx_foreach(DavQLOrderCriterion*, critdata, i) {
+            printf("%.*s %s%s", (int)critdata->column->srctext.length, critdata->column->srctext.ptr,
+                critdata->descending ? "desc" : "asc",
+                i.index+1 < stmt->orderby->size ? ", " : "\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)) {
+        cxstring empty = CX_STR("(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;
+    CxList *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 && stmt->fields->size > 0) {
+                DavQLField* field = cxListAt(stmt->fields, 0);
+                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 && stmt->orderby->size > 0 ?
+                ((DavQLOrderCriterion*)cxListAt(stmt->orderby, 0))->column : NULL;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            printf("TODO: port code to ucx 3\n");
+            /*
+            if (examineelem) {
+                CxList *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) ((token)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, DavQLToken *token) {
+    
+    // we try to achieve two things: get as many information as possible
+    // and recover the concrete source string (and not the token strings)
+    cxstring emptystring = CX_STR("");
+    cxstring prev = token->prev ? (token->prev->prev ?
+        token_sstr(token->prev->prev) : token_sstr(token->prev))
+        : emptystring;
+    cxstring tokenstr = token_sstr(token);
+    cxstring 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;
+    const char *pn = tokenstr.ptr + tokenstr.length;
+    int ln = next.ptr+next.length - pn;
+    
+    stmt->errorcode = errorcode;
+    stmt->errormessage = cx_asprintf(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)
+
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+    cxstring keywords[] ={CX_STR("select"), CX_STR("set"), CX_STR("from"), CX_STR("at"), CX_STR("as"),
+        CX_STR("where"), CX_STR("anywhere"), CX_STR("like"), CX_STR("unlike"), CX_STR("and"),
+        CX_STR("or"), CX_STR("not"), CX_STR("xor"), CX_STR("with"), CX_STR("infinity"),
+        CX_STR("order"), CX_STR("by"), CX_STR("asc"), CX_STR("desc")
+    };
+    for (int i = 0 ; i < sizeof(keywords)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, keywords[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+    cxstring operators[] = {CX_STR("and"), CX_STR("or"), CX_STR("not"), CX_STR("xor"),
+        CX_STR("like"), CX_STR("unlike")
+    };
+    for (int i = 0 ; i < sizeof(operators)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, operators[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int dav_stmt_add_field(DavQLStatement *stmt, DavQLField *field) {
+    if(!stmt->fields) {
+        stmt->fields = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        if(!stmt->fields) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+        }
+    }
+    
+    if(cxListAdd(stmt->fields, field)) {
+        stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+    }
+    
+    return 0;
+}
+    
+
+static void tokenlist_free(DavQLToken *tokenlist) {
+    DavQLToken *token = tokenlist;
+    while(token) {
+        DavQLToken *next = token->next;
+        free(token);
+        token = next;
+    }
+}
+
+static int dav_parse_add_token(DavQLToken **begin, DavQLToken **end, 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;
+        }
+    }
+    
+    cx_linked_list_add((void**)begin, (void**)end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return 0;
+}
+
+
+
+static DavQLToken* dav_parse_tokenize(cxstring src) {
+#define alloc_token() do {token = calloc(1, sizeof(DavQLToken));\
+        if(!token) {tokenlist_free(tokens_begin); return NULL;}} while(0)
+#define add_token() if(dav_parse_add_token(&tokens_begin, &tokens_end, token)) return NULL;
+    
+    DavQLToken *tokens_begin = NULL;
+    DavQLToken *tokens_end = 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 = CX_STR("");
+    
+    cx_linked_list_add((void**)&tokens_begin, (void**)&tokens_end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return tokens_begin;
+#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);
+    }
+}
+
+#define token_is(token, expectedclass) (token && \
+    (token->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+    !cx_strcasecmp(token->value, cx_str(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,DavQLToken*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, DavQLToken* 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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 fmt_args_add(DavQLStatement *stmt, void *data) {
+    if(!stmt->args) {
+        stmt->args = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    }
+    cxListAdd(stmt->args, data);
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, cxstring str) {
+    int placeholder = 0;
+    for (size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        if (placeholder) {
+            if (c != '%') {
+                fmt_args_add(stmt, (void*)(intptr_t)c);
+            }
+            placeholder = 0;
+        } else if (c == '%') {
+            placeholder = 1;
+        }
+    }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, DavQLToken* 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
+        fmt_args_add(stmt, (void*)(intptr_t)expr->srctext.ptr[1]);
+    } else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, DavQLToken* 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;
+    const 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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, DavQLToken* 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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) {
+        cxstring lasttoken =
+            token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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, DavQLToken* 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, DavQLToken* 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, DavQLToken* 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, DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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, DavQLToken *token) {
+    
+    // RULE:    "-"
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        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);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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));
+                    if(dav_stmt_add_field(stmt, field)) {
+                        free(field);
+                        return 0;
+                    }
+                }                
+            } 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));
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+                token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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);
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+
+                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, DavQLToken *token,
+        DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed);
+
+    DavQLToken* 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, DavQLToken *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) {
+            cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, 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, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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;
+        cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(firsttoken, 0, offsetof(DavQLToken, next), total_consumed-1));
+        expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, DavQLToken *token) {
+    dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+    
+    return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, DavQLToken *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 {
+                            cxstring 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, DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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, DavQLToken *token) {
+    
+    int total_consumed = 0, consumed;
+    
+    DavQLOrderCriterion crit;
+    
+    if(!stmt->orderby) {
+        stmt->orderby = cxLinkedListCreateSimple(sizeof(DavQLOrderCriterion));
+        if(!stmt->orderby) {
+            return 0;
+        }
+    }
+    
+    // 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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+        total_consumed += consumed;
+        
+        if(cxListAdd(stmt->orderby, &crit)) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 0;
+        }
+        
+        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, DavQLToken *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 = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            total_consumed += consumed;
+            
+            // Add assignment to list and check if there's another one
+            if(dav_stmt_add_field(stmt, field)) {
+                free(field);
+                return 0;
+            }
+            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, DavQLToken *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)) {
+            cxstring 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;
+        fmt_args_add(stmt, (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, DavQLToken *tokens) {
+    stmt->type = DAVQL_SELECT;
+
+    // Consume field list
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+                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, DavQLToken *tokens) {
+    stmt->type = DAVQL_SET;
+    
+    // Consume assignments
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            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 = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            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(cxstring 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 = cx_strtrim(srctext);
+    
+    if (stmt->srctext.length) {   
+        // tokenization
+        DavQLToken* 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
+            tokenlist_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) {
+    if(stmt->fields) {
+        stmt->fields->simple_destructor = (cx_destructor_func)dav_free_field;
+        cxListDestroy(stmt->fields);
+    }
+    
+    if (stmt->where) {
+        dav_free_expression(stmt->where);
+    }
+    if (stmt->errormessage) {
+        free(stmt->errormessage);
+    }
+    
+    if(stmt->orderby) {
+        stmt->orderby->simple_destructor = (cx_destructor_func)dav_free_order_criterion;
+        cxListDestroy(stmt->orderby);
+    }
+    if(stmt->args) {
+        cxListDestroy(stmt->args);
+    }
+    free(stmt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/davqlparser.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,374 @@
+/*
+ * 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 <cx/string.h>
+#include <cx/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 DavQLToken DavQLToken;    
+struct DavQLToken {
+    davqltokenclass_t tokenclass;
+    cxstring value;
+    DavQLToken *prev;
+    DavQLToken *next;
+};
+
+/**
+ * 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.
+     */
+    cxstring 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>
+     */
+    cxstring 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.
+     */
+    cxstring 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.
+     */
+    CxList* fields;
+    /**
+     * A string that denotes the queried path.
+     */
+    cxstring 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.
+     */
+    CxList* 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
+     */
+    CxList* 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(cxstring stmt);
+
+/**
+ * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement.
+ */
+#define dav_parse_cstr_statement(stmt) dav_parse_statement(cx_str(stmt))
+
+/**
+ * Frees a DavQLStatement.
+ * @param stmt the statement object to free
+ */
+void dav_free_statement(DavQLStatement *stmt);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DAVQLPARSER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/methods.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1365 @@
+/*
+ * 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 <cx/utils.h>
+#include <cx/printf.h>
+#include <cx/hash_map.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin) {
+    return cxBufferSeek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
+}
+
+/* ----------------------------- PROPFIND ----------------------------- */
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        CxBuffer *request,
+        CxBuffer *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, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    CxMap *respheaders = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    respheaders->simple_destructor = free;
+    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 = cxMapGet(respheaders, cx_hash_key_str("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);
+    cxMapDestroy(respheaders);
+       
+    return ret;
+}
+
+CxBuffer* create_allprop_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:allprop/></D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+CxBuffer* create_cryptoprop_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    int add_crypto_name = 1;
+    int add_crypto_key = 1;
+    int add_crypto_hash = 1;
+    char *crypto_ns = "idav";
+    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    if(properties) {
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavProperty*, p, i) {
+            if(strcmp(p->ns->name, "DAV:")) {
+                cxMapPut(namespaces, cx_hash_key_str(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;
+        cxMapPut(namespaces, cx_hash_key_str("idav"), &idav_ns);
+    }
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    cx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
+    
+    CxIterator mapi = cxMapIteratorValues(namespaces);
+    cx_foreach(DavNamespace*, ns, mapi) {
+        s = CX_STR(" xmlns:");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(ns->prefix);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("=\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(ns->name);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    s = CX_STR(">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // default properties
+    s = CX_STR("<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:creationdate />\n<D:getlastmodified />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:getcontentlength />\n<D:getcontenttype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:resourcetype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // crypto properties
+    if(DAV_CRYPTO(sn) && !nocrypt) {
+        if(add_crypto_name) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-name />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_key) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-key />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_hash) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-hash />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // extra properties
+    if(properties) {
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavProperty*, prop, i) {
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prop->ns->prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prop->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(" />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // end
+    cx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
+    
+    cxMapDestroy(namespaces);
+    return buf;
+}
+
+CxBuffer* create_basic_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
+    cxBufferWrite(s.ptr, 1, s.length, buf);  
+    s = CX_STR(DAV_NS);
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("\" >\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // properties
+    s = CX_STR("<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<D:resourcetype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-key />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-name />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-hash />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("</D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // end
+    s = CX_STR("</D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+PropfindParser* create_propfind_parser(CxBuffer *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;
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    result->properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
+    
+    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;
+                            }
+                            cxstring status_str = cx_str((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                // error
+                                return -1;
+                            }
+                            status_str = cx_strsubsl(status_str, 9, 3);
+                            if(!cx_strcmp(status_str, CX_STR("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) {
+                            cxListAdd(result->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->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) {
+        cxListDestroy(result->properties);
+    }
+}
+
+int hrefeq(DavSession *sn, const char *href1, const char *href2) {
+    cxmutstr href_s = cx_mutstr(util_url_decode(sn, href1));
+    cxmutstr href_r = cx_mutstr(util_url_decode(sn, href2));
+    int ret = 0;
+    if(!cx_strcmp(cx_strcast(href_s), cx_strcast(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(!cx_strcmp(cx_strcast(href_s), cx_strcast(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(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+                ret = 1;
+            }
+        }
+    } 
+
+    free(href_s.ptr);
+    free(href_r.ptr);
+    
+    return ret;
+}
+
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *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 {
+        cxstring resname = cx_str(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
+    if(response->properties) {
+        CxIterator i = cxListIterator(response->properties);
+        cx_foreach(xmlNode*, prop, i) {
+            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) {
+            CxMap *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;
+    const char *href = NULL;
+    CxList *properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // 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((const 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;
+                            }
+                            cxstring status_str = cx_str((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            status_str = cx_strsubsl(status_str, 9, 3);
+                            if(!cx_strcmp(status_str, CX_STR("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) {
+                            cxListAdd(properties, n);
+                            if(xstreq(n->name, "resourcetype")) {
+                                if(parse_resource_type(n)) {
+                                    iscollection = TRUE;
+                                }
+                            } else if(n->ns && 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 {
+            cxstring resname = cx_str(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);
+        }
+        
+        char *href_cp = dav_session_strdup(sn, href);
+        res = dav_resource_new_full(sn, resource->path, name, href_cp);
+        
+        dav_session_free(sn, name);
+    }
+    res->iscollection = iscollection;
+    
+    // add properties
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    
+    CxIterator i = cxListIterator(properties);
+    cx_foreach(xmlNode*, prop, i) {
+        if(!prop->ns) {
+            continue;
+        }
+        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;
+            }
+        }
+    }
+    cxListDestroy(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) {
+            CxMap *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,
+        CxBuffer *request,
+        CxBuffer *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 = cx_asprintf("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, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    cxBufferSeek(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;
+}
+
+CxBuffer* create_proppatch_request(DavResourceData *data) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    namespaces->simple_destructor = free;
+    
+    char prefix[8];
+    int pfxnum = 0;
+    if(data->set) {
+        CxIterator i = cxListIterator(data->set);
+        cx_foreach(DavProperty*, p, i) {
+            if(strcmp(p->ns->name, "DAV:")) {
+                snprintf(prefix, 8, "x%d", pfxnum++);
+                cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+            }
+        }
+    }
+    if(data->remove) {
+        CxIterator i = cxListIterator(data->remove);
+        cx_foreach(DavProperty*, p, i) {
+            if(strcmp(p->ns->name, "DAV:")) {
+                snprintf(prefix, 8, "x%d", pfxnum++);
+                cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+            }
+        }
+    }
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\"");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    CxIterator mapi = cxMapIterator(namespaces);
+    cx_foreach(CxMapEntry*, entry, mapi) {
+        s = CX_STR(" xmlns:");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(entry->value);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("=\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_strn(entry->key->data, entry->key->len);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    s = CX_STR(">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(data->set) {
+        s = CX_STR("<D:set>\n<D:prop>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        CxIterator i = cxListIterator(data->set);
+        cx_foreach(DavProperty*, property, i) {
+            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+            if(!prefix) {
+                prefix = "D";
+            }
+            
+            // begin tag
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(">");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            
+            // content
+            DavXmlNode *content = property->value;
+            if(content->type == DAV_XML_TEXT && !content->next) {
+                cxBufferWrite(content->content, 1, content->contentlength, buf);
+            } else {
+                dav_print_node(buf, (cx_write_func)cxBufferWrite, namespaces, content);
+            }
+            
+            // end tag
+            s = CX_STR("</");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(">\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        s = CX_STR("</D:prop>\n</D:set>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    if(data->remove) {
+        s = CX_STR("<D:remove>\n<D:prop>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        CxIterator i = cxListIterator(data->remove);
+        cx_foreach(DavProperty*, property, i) {
+            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+            
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(" />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        s = CX_STR("</D:prop>\n</D:remove>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("</D:propertyupdate>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // cleanup namespace map
+    cxMapDestroy(namespaces);
+    
+    return buf;
+}
+
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:set>\n<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(DAV_ENCRYPT_NAME(sn)) {
+        s = CX_STR("<idav:crypto-name>");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        char *crname = aes_encrypt(name, strlen(name), key);
+        cxBufferPutString(buf, crname);
+        free(crname);
+        s = CX_STR("</idav:crypto-name>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("<idav:crypto-key>");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    cxBufferPutString(buf, key->name);
+    s = CX_STR("</idav:crypto-key>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(hash) {
+        s = CX_STR("<idav:crypto-hash>");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        cxBufferPutString(buf, hash);
+        s = CX_STR("</idav:crypto-hash>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
+    cxBufferWrite(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 = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+            free(url);
+        } else {
+            ltheader = cx_asprintf("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);
+    
+    CxBuffer *buf = NULL;
+    if(!read_func) {
+        buf = cxBufferCreate(data, length, cxDefaultAllocator, 0);
+        buf->size = length;
+        data = buf;
+        read_func = (dav_read_func)cxBufferRead;
+        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_SEEKDATA, data);
+    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) {
+        cxBufferFree(buf);
+    }
+    
+    return ret;
+}
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *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 = cx_asprintf("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, cxBufferWrite);
+    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 = cx_asprintf("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 = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+    }
+    //cxstring deststr = ucx_sprintf("Destination: %s", dest);
+    cxmutstr deststr = cx_strcat(2, CX_STR("Destination: "), cx_str(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;
+}
+
+
+CxBuffer* create_lock_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<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");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("</D:lockinfo>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+int parse_lock_response(DavSession *sn, CxBuffer *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, CxBuffer *request, CxBuffer *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) {
+        cxmutstr thdr;
+        if(timeout < 0) {
+            thdr = cx_asprintf("%s", "Timeout: Infinite");
+        } else {
+            thdr = cx_asprintf("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, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    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
+    cxmutstr ltheader = cx_asprintf("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
+    cxmutstr ltheader;
+    struct curl_slist *headers = NULL;
+    if(locktoken) {
+        ltheader = cx_asprintf("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, CxBuffer *request, CxBuffer *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, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/methods.h	Mon Jan 22 17:27:47 2024 +0100
@@ -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 <cx/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 {
+    const char *href;
+    int        iscollection;
+    CxList     *properties;
+    const char *crypto_name;
+    const char *crypto_key;
+};
+
+struct LockDiscovery {
+    char    *timeout;
+    char    *locktoken;
+};
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin);
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        CxBuffer *request,
+        CxBuffer *response);
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        CxBuffer *request,
+        CxBuffer *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);
+
+CxBuffer* create_allprop_propfind_request(void);
+CxBuffer* create_cryptoprop_propfind_request(void);
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt);
+CxBuffer* create_basic_propfind_request(void);
+
+PropfindParser* create_propfind_parser(CxBuffer *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, const char *href1, const 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, CxBuffer *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);
+
+CxBuffer* create_proppatch_request(DavResourceData *data);
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash);
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *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);
+
+CxBuffer* create_lock_request(void);
+int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock);
+CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *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, CxBuffer *request, CxBuffer *response);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* METHODS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/resource.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1875 @@
+/*
+ * 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 <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.h>
+#include <cx/array_list.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, const char *path) {
+    //char *href = util_url_path(url);
+    //DavResource *res = dav_resource_new_href(sn, href);
+    char *parent = util_parent_path(path);
+    const 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, const 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, const char *href) {  
+    DavResource *res = cxCalloc(sn->mp->allocator, 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, const char *parent_path, const char *name, char *href) {
+    cxstring n = cx_str(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 = cx_str(util_resource_name(href));
+                break;
+            }
+        }
+    }
+    // remove trailing '/'
+    if(n.length > 0 && n.ptr[n.length-1] == '/') {
+        n.length--;
+    }
+    
+    DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    res->name = cx_strdup_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, cx_str(path), cx_str(href));
+    }
+    free(path);
+    
+    return res;
+}
+
+void resource_free_properties(DavSession *sn, CxMap *properties) {
+    if(!properties) return;
+    
+    CxIterator i = cxMapIteratorValues(properties);
+    DavProperty *property;
+    cx_foreach(DavProperty*, property, i) {
+        // TODO: free everything
+        dav_session_free(sn, property);
+    }
+    cxMapDestroy(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);
+    
+    if(data->set) {
+        CxIterator i = cxListIterator(data->set);
+        cx_foreach(DavProperty *, p, i) {
+            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_free_xml_node_sn(sn, p->value);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(data->remove) {
+        CxIterator i = cxListIterator(data->remove);
+        cx_foreach(DavProperty *, p, i) {
+            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->crypto_set) {
+        CxIterator i = cxListIterator(data->crypto_set);
+        cx_foreach(DavProperty *, p, i) {
+            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_free_xml_node_sn(sn, p->value);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(data->crypto_remove) {
+        CxIterator i = cxListIterator(data->crypto_remove);
+        cx_foreach(DavProperty *, p, i) {
+            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, cxstring href) {
+    res->href = cx_strdup_a(res->session->mp->allocator, href).ptr;
+}
+
+void resource_set_info(DavResource *res, const char *href_str) {
+    char *url_str = NULL;
+    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
+    cxstring name = cx_str(util_resource_name(href_str));
+    cxstring href = cx_str(href_str);
+    
+    cxstring base_href = cx_str(util_url_path(res->session->base_url));
+    cxstring path = cx_strsubs(href, base_href.length - 1);
+    
+    const CxAllocator *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 = cx_strdup_a(a, cx_strn(uname, nlen)).ptr;
+    res->href = cx_strdup_a(a, href).ptr;
+    res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr;
+    
+    curl_free(uname);
+    curl_free(upath);
+}
+
+DavResourceData* resource_data_new(DavSession *sn) {
+    DavResourceData *data = cxMalloc(
+            sn->mp->allocator,
+            sizeof(DavResourceData));
+    if(!data) {
+        return NULL;
+    }
+    data->properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 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;
+    
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+    cxMapPut(((DavResourceData*)res->data)->properties, key, prop);
+    free(keystr.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, CxMap *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) {
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_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) {
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_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, CxHashKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = cxMapGet(data->properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = cxMapGet(data->crypto_properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+cxmutstr dav_property_key(const char *ns, const char *name) {
+    return dav_property_key_a(cxDefaultAllocator, ns, name);
+}
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name) {
+    cxstring ns_str = cx_str(ns);
+    cxstring name_str = cx_str(name);
+    
+    return cx_strcat_a(a, 4, ns_str, CX_STR("\0"), name_str, CX_STR("\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, CxList *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;
+            CxIterator i = cxListIterator(ordercr);
+            cx_foreach(DavOrderCriterion*, cr, i) {
+                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;
+    CxList *remove_list = NULL;
+    CxList *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 && remove_list) {
+        // if the property is in the remove list, we return NULL
+        CxIterator i = cxListIterator(remove_list);
+        cx_foreach(DavProperty*, p, i) {
+            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
+    if(set_list) {
+        CxIterator i = cxListIterator(set_list);
+        cx_foreach(DavProperty*, p, i) {
+            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);
+}
+
+static int add2propertylist(const CxAllocator *a, CxList **list, DavProperty *property) {
+    if(!*list) {
+        CxList *newlist = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+        if(!newlist) {
+            return 1;
+        }
+        *list = newlist;
+    }
+    cxListAdd(*list, property);
+    return 0;
+}
+
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    DavSession *sn = res->session;
+    const CxAllocator *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)) {
+        add2propertylist(a, &data->crypto_set, property);
+    } else {
+        add2propertylist(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;
+    const CxAllocator *a = sn->mp->allocator; 
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(sn, ns, name);
+    // TODO: this function should copy the value
+    //       but we also need a function, that doesn't create a copy
+    property->value = value;
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        add2propertylist(a, &data->crypto_set, property);
+    } else {
+        add2propertylist(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;
+    const CxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        add2propertylist(a, &data->crypto_remove, property);
+    } else {
+        add2propertylist(a, &data->remove, property);
+    }
+}
+
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    const CxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    const CxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
+    DavResourceData *data = res->data;
+    const CxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    add2propertylist(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->size;
+    DavPropName *names = dav_session_calloc(
+            res->session,
+            *count,
+            sizeof(DavPropName));
+    
+    
+    CxIterator i = cxMapIteratorValues(data->properties);
+    DavProperty *value;
+    int j = 0;
+    cx_foreach(DavProperty*, 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) {
+    CxBuffer *rqbuf = create_allprop_propfind_request();
+    int ret = dav_propfind(res->session, res, rqbuf);
+    cxBufferFree(rqbuf);
+    return ret;
+}
+
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
+    CxMempool *mp = cxMempoolCreate(64, NULL);
+    const CxAllocator *a = mp->allocator;
+    
+    CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop);
+    for(size_t i=0;i<numprop;i++) {
+        DavProperty p;
+        p.name = properties[i].name;
+        p.ns = cxMalloc(a, sizeof(DavNamespace));
+        p.ns->name = properties[i].ns;
+        if(!strcmp(properties[i].ns, "DAV:")) {
+            p.ns->prefix = "D";
+        } else {
+            p.ns->prefix = cx_asprintf_a(a, "x%d", (int)i).ptr;
+        }
+        p.value = NULL;
+        cxListAdd(proplist, &p);
+    }
+    
+    CxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
+    int ret = dav_propfind(res->session, res, rqbuf);
+    cxBufferFree(rqbuf);
+    cxMempoolDestroy(mp);
+    return ret;
+}
+
+
+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;
+            CxBuffer *buf = NULL;
+            if(data->read) {
+                enc = aes_encrypter_new(
+                        sn->key,
+                        data->content,
+                        data->read,
+                        data->seek);
+            } else {
+                buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+                buf->size = data->length;
+                enc = aes_encrypter_new(
+                        sn->key,
+                        buf,
+                        (dav_read_func)cxBufferRead,
+                        (dav_seek_func)cxBufferSeek);
+            }
+              
+            // 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) {
+                cxBufferFree(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;
+            CxBuffer *iobuf = NULL;
+            if(!data->read) {
+                iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+                iobuf->size = data->length;
+                init_hash_stream(
+                        &hstr,
+                        iobuf,
+                        (dav_read_func)cxBufferRead,
+                        (dav_seek_func)cxBufferSeek);
+            } 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) {
+                cxFree(sn->mp->allocator, 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) {
+            CxBuffer *rqbuf = create_cryptoprop_propfind_request();
+            ret = dav_propfind(res->session, res, rqbuf);
+            cxBufferFree(rqbuf);
+        }
+        
+        if(!ret) {
+            DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
+            CxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
+            if(!crypto_props) {
+                // resource hasn't encrypted properties yet
+                crypto_props = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); // create new map
+            }
+            
+            // remove all properties
+            if(data->crypto_remove) {
+                CxIterator i = cxListIterator(data->crypto_remove);
+                cx_foreach(DavProperty *, property, i) {
+                    if(crypto_props->size == 0) {
+                        break; // map already empty, can't remove any more
+                    }
+
+                    cxmutstr key = dav_property_key(property->ns->name, property->name);
+                    DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length));
+                    if(existing_prop) {
+                        // TODO: free existing_prop
+                    }                
+                    free(key.ptr);
+                }
+            }
+            
+            // set properties
+            if(data->crypto_set) {
+                CxIterator i = cxListIterator(data->crypto_set);
+                cx_foreach(DavProperty *, property, i) {
+                    cxmutstr keystr = dav_property_key(property->ns->name, property->name);
+                    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+                    DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key);
+                    cxMapPut(crypto_props, key, property);
+                    if(existing_prop) {
+                        // TODO: free existing_prop
+                    }  
+                    free(keystr.ptr);
+                }
+            }
+            
+            DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props);
+            if(crypto_prop_value) {
+                DavProperty *new_crypto_prop = createprop(sn, DAV_NS, "crypto-prop");
+                new_crypto_prop->value = crypto_prop_value;
+                add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop);
+            }
+            
+            dav_resource_free(crypto_res);
+        }
+        
+        if(ret) {
+            return 1;
+        }
+    }
+    
+    // store properties
+    int r = 0;
+    sn->error = DAV_OK;
+    if(data->set || data->remove) {
+        CxBuffer *request = create_proppatch_request(data);
+        CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        //printf("request:\n%.*s\n\n", request->pos, request->space);
+
+        CURLcode ret = do_proppatch_request(sn, locktoken, request, response);
+        long status = 0;
+        curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+        if(ret == CURLE_OK && status == 207) {
+            //printf("%s\n", response->space);
+            // TODO: parse response
+            // TODO: cleanup node data correctly
+            data->set = NULL;
+            data->remove = NULL;
+        } else {
+            dav_session_set_error(sn, ret, status);
+            r = -1;
+        }
+        
+        cxBufferFree(request);
+        cxBufferFree(response);
+    }
+     
+    return r;
+}
+
+#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
+static void set_progressfunc(DavResource *res) {
+    CURL *handle = res->session->handle;
+    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
+    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, res);
+    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
+}
+
+static void unset_progressfunc(DavResource *res) {
+    CURL *handle = res->session->handle;
+    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, NULL);
+    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, NULL);
+    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
+}
+#else
+static void set_progressfunc(DavResource *res) {
+    
+}
+static void unset_progressfunc(DavResource *res) {
+    
+}
+#endif
+
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_fnc) { 
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(res->session, dav_resource_get_href(res));
+    
+    // check encryption
+    AESDecrypter *dec = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_CONTENT(sn)) {
+        char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+        if(keyname) {
+            key = dav_context_get_key(sn->context, keyname);
+            if(key) {
+                dec = aes_decrypter_new(key, stream, write_fnc);
+                stream = dec;
+                write_fnc = (dav_write_func)aes_write;
+            }
+        }
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_fnc);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, stream);
+    
+    if(sn->get_progress) {
+        set_progressfunc(res);
+    }
+    
+    long status = 0;
+    CURLcode ret = dav_session_curl_perform(sn, &status);
+    
+    if(sn->get_progress) {
+        unset_progressfunc(res);
+    }
+    
+    char *hash = NULL;
+    if(dec) {
+        aes_decrypter_shutdown(dec); // get final bytes
+        
+        // get hash
+        unsigned char sha[DAV_SHA256_DIGEST_LENGTH];
+        dav_get_hash(&dec->sha256, sha);
+        hash = util_hexstr(sha, DAV_SHA256_DIGEST_LENGTH);
+        
+        aes_decrypter_close(dec);
+    }
+    
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        int verify_failed = 0;
+        if(DAV_DECRYPT_CONTENT(sn) && key) {
+            // try to verify the content
+            char *res_hash = dav_get_string_property_ns(res, DAV_NS, "crypto-hash");
+
+            if(res_hash) {
+                size_t len = 0;
+                char *dec_hash = aes_decrypt(res_hash, &len, key);
+                char *hex_hash = util_hexstr((unsigned char*)dec_hash, len);
+                if(strcmp(hash, hex_hash)) {
+                    verify_failed = 1;
+                }
+                free(dec_hash);
+                free(hex_hash);
+            }
+        }
+        if(hash) {
+            free(hash);
+        }
+        
+        if(verify_failed) {
+            res->session->error = DAV_CONTENT_VERIFICATION_ERROR;
+            return 1;
+        }
+        
+        res->session->error = DAV_OK;
+        return 0;
+    } else {
+        if(hash) {
+            free(hash);
+        }
+        dav_session_set_error(res->session, ret, status);
+        return 1;
+    }
+}
+
+DavResource* dav_create_child(DavResource *parent, char *name) {
+    DavResource *res = dav_resource_new_child(parent->session, parent, name);
+    if(dav_create(res)) {
+        dav_resource_free(res);
+        return NULL;
+    } else {
+        return res;
+    }
+}
+
+int dav_delete(DavResource *res) {
+    CURL *handle = res->session->handle;
+    util_set_url(res->session, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CxBuffer *response = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CURLcode ret = do_delete_request(res->session, locktoken, response);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    int r = 0;
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        res->session->error = DAV_OK;
+        res->exists = 0;
+        
+        // TODO: parse response
+        // TODO: free res
+    } else {
+        dav_session_set_error(res->session, ret, status);
+        r = 1;
+    }
+    
+    cxBufferFree(response);
+    return r;
+}
+
+static int create_ancestors(DavSession *sn, char *href, char *path) {
+    CURL *handle = sn->handle;
+    CURLcode code;
+    
+    DavLock *lock = dav_get_lock(sn, path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    long status = 0;
+    int ret = 0;
+    
+    if(strlen(path) <= 1) {
+        return 0;
+    }
+    
+    char *p = util_parent_path(path);
+    char *h = util_parent_path(href);
+    
+    for(int i=0;i<2;i++) {
+        util_set_url(sn, h);
+        code = do_mkcol_request(sn, locktoken);
+        curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
+        if(status == 201) {
+            // resource successfully created
+            char *name = (char*)util_resource_name(p);
+            int len = strlen(name);
+            if(name[len - 1] == '/') {
+                name[len - 1] = '\0';
+            }
+            if(resource_add_crypto_info(sn, h, name, NULL)) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Cannot set crypto properties for ancestor");
+            }
+            break;
+        } else if(status == 405) {
+            // parent already exists
+            break;
+        } else if(status == 409) {
+            // parent doesn't exist
+            if(create_ancestors(sn, h, p)) {
+                ret = 1;
+                break;
+            }
+        } else {
+            dav_session_set_error(sn, code, status);
+            ret = 1;
+            break;
+        }
+    }
+    
+    free(p);
+    free(h);
+    return ret;
+}
+
+static int create_resource(DavResource *res, int *status) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode code;
+    if(res->iscollection) {
+        code = do_mkcol_request(sn, locktoken);
+    } else {
+        code = do_put_request(sn, locktoken, TRUE, "", NULL, NULL, 0); 
+    }
+    long s = 0;
+    curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &s);
+    *status = s;
+    if(code == CURLE_OK && (s >= 200 && s < 300)) {
+        sn->error = DAV_OK;
+        // if the session has encrypted file names, add crypto infos
+        if(!resource_add_crypto_info(sn, res->href, res->name, NULL)) {
+            // do a minimal propfind request
+            CxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0);
+            int ret = dav_propfind(sn, res, rqbuf);
+            cxBufferFree(rqbuf);
+            return ret;
+        } else {
+            return 1;
+        }
+    } else {
+        dav_session_set_error(sn, code, s);
+        return 1;
+    }
+}
+
+int dav_create(DavResource *res) {
+    int status;
+    if(!create_resource(res, &status)) {
+        // resource successfully created
+        res->exists = 1;
+        return 0;
+    }
+    
+    if(status == 403 || status == 409 || status == 404) {
+        // create intermediate collections
+        if(create_ancestors(res->session, res->href, res->path)) {
+            return 1;
+        }
+    }
+    
+    return create_resource(res, &status);
+}
+
+int dav_exists(DavResource *res) {
+    if(!dav_load_prop(res, NULL, 0)) {
+        res->exists = 1;
+        return 1;
+    } else {
+        if(res->session->error == DAV_NOT_FOUND) {
+            res->exists = 0;
+        }
+        return 0;
+    }
+}
+
+static int dav_cp_mv_url(DavResource *res, char *desturl, _Bool copy, _Bool override) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(sn, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode ret = do_copy_move_request(sn, desturl, locktoken, copy, override);
+    
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        return 0;
+    } else {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+}
+
+static int dav_cp_mv(DavResource *res, char *newpath, _Bool copy, _Bool override) {
+    char *dest = dav_session_get_href(res->session, newpath);
+    char *desturl = util_get_url(res->session, dest);
+    dav_session_free(res->session, dest);
+    
+    int ret = dav_cp_mv_url(res, desturl, copy, override);
+    free(desturl);
+    return ret;
+}
+
+int dav_copy(DavResource *res, char *newpath) {
+    return dav_cp_mv(res, newpath, true, false);
+}
+
+int dav_move(DavResource *res, char *newpath) {
+    return dav_cp_mv(res, newpath, false, false);
+}
+
+int dav_copy_o(DavResource *res, char *newpath, DavBool override) {
+    return dav_cp_mv(res, newpath, true, override);
+}
+
+int dav_move_o(DavResource *res, char *newpath, DavBool override) {
+    return dav_cp_mv(res, newpath, false, override);
+}
+
+int dav_copyto(DavResource *res, char *url, DavBool override) {
+    return dav_cp_mv_url(res, url, true, override);
+}
+
+int dav_moveto(DavResource *res, char *url, DavBool override) {
+    return dav_cp_mv_url(res, url, false, override);
+}
+
+int dav_lock(DavResource *res) {
+    return dav_lock_t(res, 0);
+}
+
+int dav_lock_t(DavResource *res, time_t timeout) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    CxBuffer *request = create_lock_request();
+    CxBuffer *response = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CURLcode ret = do_lock_request(sn, request, response, timeout);
+    
+    //printf("\nlock\n");
+    //printf("%.*s\n\n", request->size, request->space);
+    //printf("%.*s\n\n", response->size, response->space);
+    
+    cxBufferFree(request);
+    
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        LockDiscovery lock;
+        int parse_error = parse_lock_response(sn, response, &lock);
+        cxBufferFree(response);
+        if(parse_error) {
+            sn->error = DAV_ERROR;
+            return -1;
+        }
+        
+        DavLock *l = dav_create_lock(sn, lock.locktoken, lock.timeout);
+        free(lock.locktoken);
+        free(lock.timeout);
+        
+        int r = 0;
+        if(res->iscollection) {
+            r = dav_add_collection_lock(sn, res->path, l);
+        } else {
+            r = dav_add_resource_lock(sn, res->path, l);
+        }
+        
+        if(r == 0) {
+            return 0;
+        } else {
+            (void)dav_unlock(res);
+            sn->error = DAV_ERROR;
+            dav_destroy_lock(sn, l);
+            return -1;
+        }
+    } else {
+        dav_session_set_error(sn, ret, status);
+        cxBufferFree(response);
+        return -1;
+    }
+}
+
+int dav_unlock(DavResource *res) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    if(!lock) {
+        sn->error = DAV_ERROR;
+        return -1;
+    }
+    
+    CURLcode ret = do_unlock_request(sn, lock->token);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        dav_remove_lock(sn, res->path, lock);
+    } else {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+    
+    return 0;
+}
+
+
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash) {
+    if(!DAV_IS_ENCRYPTED(sn)) {
+        return 0;
+    }
+    
+    CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
+    CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    util_set_url(sn, href);
+    // TODO: lock
+    CURLcode ret = do_proppatch_request(sn, NULL, request, response);
+    cxBufferFree(request);
+    long status = 0;
+    curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        // TODO: parse response
+        sn->error = DAV_OK;   
+        cxBufferFree(response);
+        return 0;
+    } else {
+        dav_session_set_error(sn, ret, status);
+        cxBufferFree(response);
+        return 1;
+    }
+}
+
+/* ----------------------------- crypto-prop  ----------------------------- */
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) {
+    if(!sn->key) {
+        return NULL;
+    }
+    
+    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // create an xml document containing all properties
+    CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    nsmap->simple_destructor = free;
+    cxMapPut(nsmap, cx_hash_key_str("DAV:"), strdup("D"));
+    
+    cxBufferPutString(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    cxBufferPutString(content, "<D:prop xmlns:D=\"DAV:\">\n");
+    
+    CxIterator i = cxMapIteratorValues(properties);
+    DavProperty *prop;
+    cx_foreach(DavProperty*, prop, i) {
+        DavXmlNode pnode;
+        pnode.type = DAV_XML_ELEMENT;
+        pnode.namespace = prop->ns->name;
+        pnode.name = prop->name;
+        pnode.prev = NULL;
+        pnode.next = NULL;
+        pnode.children = prop->value;
+        pnode.parent = NULL;
+        pnode.attributes = NULL;
+        pnode.content = NULL;
+        pnode.contentlength = 0;
+        
+        dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode);
+        cxBufferPut(content, '\n');
+    }
+    
+    cxBufferPutString(content, "</D:prop>");
+    
+    cxMapDestroy(nsmap);
+    
+    // encrypt xml document
+    char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
+    cxBufferDestroy(content);
+    
+    DavXmlNode *ret = NULL;
+    if(crypto_prop_content) {
+        ret = dav_text_node(sn, crypto_prop_content);
+        free(crypto_prop_content);
+    }    
+    return ret;
+}
+
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) {
+    if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) {
+        return NULL;
+    }
+    
+    return parse_crypto_prop_str(sn, key, node->content);
+}
+
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) {
+    size_t len = 0;
+    char *dec_str = aes_decrypt(content, &len, key);
+    
+    xmlDoc *doc = xmlReadMemory(dec_str, len, NULL, NULL, 0);
+    free(dec_str);
+    if(!doc) {
+        return NULL;
+    }
+    
+    int err = 0;
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    if(xml_root) {
+        if(
+                !xml_root->ns ||
+                !xstreq(xml_root->name, "prop") ||
+                !xstreq(xml_root->ns->href, "DAV:"))
+        {
+            err = 1;
+        }
+    } else {
+        err = 1;
+    }
+    
+    if(err) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    // ready to get the properties
+    CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    xmlNode *n = xml_root->children;
+    while(n) {
+        if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) {
+            DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+            property->name = dav_session_strdup(sn, (const char*)n->name);
+            property->ns = dav_session_malloc(sn, sizeof(DavNamespace));
+            property->ns->name = dav_session_strdup(sn, (const char*)n->ns->href);
+            property->ns->prefix = n->ns->prefix ?
+                    dav_session_strdup(sn, (const char*)n->ns->prefix) : NULL;
+            property->value = n->children ? dav_convert_xml(sn, n->children) : NULL;
+            
+            cxmutstr key = dav_property_key(property->ns->name, property->name);
+            cxMapPut(map, cx_hash_key(key.ptr, key.length), property);
+            free(key.ptr);
+        }
+        n = n->next;
+    }
+    
+    xmlFreeDoc(doc);
+    if(map->size == 0) {
+        cxMapDestroy(map);
+        return NULL;
+    }
+    return map;
+}
+
+
+/* ----------------------------- streams  ----------------------------- */
+
+static size_t in_write(const char *ptr, size_t size, size_t nitems, void *in_stream) {
+    DavInputStream *in = in_stream;
+    size_t len = size * nitems;
+    
+    if(in->alloc < len) {
+        char *newb = realloc(in->buffer, len);
+        if(!newb) {
+            if(in->buffer) free(in->buffer);
+            in->eof = 1;
+            return 0;
+        }
+        
+        in->buffer = newb;
+        in->alloc = len;
+    }
+    
+    memcpy(in->buffer, ptr, len);
+    
+    in->size = len;
+    in->pos = 0;
+    
+    return nitems;
+}
+
+/*
+DavInputStream* dav_inputstream_open(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    DavInputStream *in = dav_session_malloc(sn, sizeof(DavInputStream));
+    if(!in) {
+        return NULL;
+    }
+    memset(in, 0, sizeof(DavInputStream));
+    
+    in->res = res;
+    
+    in->c = curl_easy_duphandle(sn->handle); 
+    char *url = util_get_url(sn, dav_resource_get_href(res));
+    curl_easy_setopt(in->c, CURLOPT_URL, url);
+    free(url);
+    
+    in->m = curl_multi_init();
+    
+    curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(in->c, CURLOPT_PUT, 0L);
+    curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L);
+    
+    curl_multi_add_handle(in->m, in->c);
+    
+    dav_write_func write_fnc = (dav_write_func)in_write;
+    void *stream = in;
+    
+    // check encryption 
+    AESDecrypter *dec = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_CONTENT(sn)) {
+        char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+        if(keyname) {
+            key = dav_context_get_key(sn->context, keyname);
+            if(key) {
+                dec = aes_decrypter_new(key, stream, write_fnc);
+                stream = dec;
+                write_fnc = (dav_write_func)aes_write;
+            }
+        }
+    }
+    
+    curl_easy_setopt(in->c, CURLOPT_WRITEFUNCTION, write_fnc);
+    curl_easy_setopt(in->c, CURLOPT_WRITEDATA, stream);
+    
+    in->dec = dec;
+    
+    return in;
+}
+
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in) {
+    size_t len = in->size - in->pos;
+    size_t rl = size * nitems;
+    if(len > 0) {
+        len = rl > len ? len : rl;
+        len -= len % size;
+        memcpy(buf, in->buffer + in->pos, len);
+        in->pos += len;
+        return len / size;
+    }
+    in->size = 0;
+    
+    if(in->eof) {
+        if(in->dec) {
+            aes_decrypter_shutdown(in->dec); // get final bytes
+            aes_decrypter_close(in->dec);
+            in->dec = NULL;
+        } else {
+            return 0;
+        }
+    } else {
+        int running;
+        while(!in->eof && in->size == 0) {
+            CURLMcode r = curl_multi_perform(in->m, &running);
+            if(r != CURLM_OK || running == 0) {
+                in->eof = 1;
+                break;
+            }
+
+            int numfds;
+            if(curl_multi_poll(in->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+                in->eof = 1;
+            }
+        }
+    }
+    
+    return in->size > 0 ? dav_read(buf, size, nitems, in) : 0;
+}
+
+void dav_inputstream_close(DavInputStream *in) {
+    curl_multi_cleanup(in->m);
+    curl_easy_cleanup(in->c);
+    if(in->buffer) free(in->buffer);
+    dav_session_free(in->res->session, in);
+}
+
+
+static size_t out_read(char *ptr, size_t size, size_t nitems, void *out_stream) {
+    DavOutputStream *out = out_stream;
+    size_t len = size * nitems;
+    size_t available = out->size - out->pos;
+    if(available == 0) {
+        return 0;
+    }
+    
+    size_t r = len > available ? available : len;
+    r -= r % size;
+    memcpy(ptr, out->buffer + out->pos, r);
+    
+    out->pos += r;
+
+    return r / size;
+}
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+    return s*n;
+}
+
+DavOutputStream* dav_outputstream_open(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    DavOutputStream *out = dav_session_malloc(sn, sizeof(DavOutputStream));
+    if(!out) {
+        return NULL;
+    }
+    memset(out, 0, sizeof(DavOutputStream));
+    
+    out->res = res;
+    
+    out->c = curl_easy_duphandle(sn->handle); 
+    char *url = util_get_url(sn, dav_resource_get_href(res));
+    curl_easy_setopt(out->c, CURLOPT_URL, url);
+    free(url);
+    
+    out->m = curl_multi_init();
+    curl_multi_add_handle(out->m, out->c);
+    
+    void *stream = out;
+    dav_read_func read_fnc = (dav_read_func)out_read;
+    
+    // if encryption or hashing in enabled, we need a stream wrapper
+    if(DAV_ENCRYPT_CONTENT(sn) && sn->key) {
+        AESEncrypter *enc = aes_encrypter_new(sn->key, out, (dav_read_func)out_read, NULL);
+        out->enc = enc;
+        stream = enc;
+        read_fnc = (dav_read_func)aes_read;
+    } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+        HashStream *hstr = dav_session_malloc(sn, sizeof(HashStream));
+        out->hstr = hstr;
+        init_hash_stream(hstr, out, (dav_read_func)out_read, NULL);
+        stream = hstr;
+        read_fnc = (dav_read_func)dav_read_h;
+    }
+    
+    curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL);
+    curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(out->c, CURLOPT_PUT, 1L);
+    curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L);
+    curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc);
+    curl_easy_setopt(out->c, CURLOPT_READDATA, stream);
+    curl_easy_setopt(out->c, CURLOPT_SEEKFUNCTION, NULL);
+    curl_easy_setopt(out->c, CURLOPT_INFILESIZE, -1);
+    curl_easy_setopt(out->c, CURLOPT_INFILESIZE_LARGE, -1L);
+    
+    curl_easy_setopt(out->c, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(out->c, CURLOPT_WRITEDATA, NULL);
+    
+    return out;
+}
+
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out) {
+    if(out->eof) return 0;
+    
+    out->buffer = buf;
+    out->size = size * nitems;
+    out->pos = 0;
+    
+    int running;
+    while(!out->eof && (out->size == 0 || out->size - out->pos > 0)) {
+        CURLMcode r = curl_multi_perform(out->m, &running);
+        if(r != CURLM_OK || running == 0) {
+            out->eof = 1;
+            break;
+        }
+        
+        int numfds;
+        if(curl_multi_poll(out->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+            out->eof = 1;
+        }
+    }
+    
+    return (out->size - out->pos) / size;
+}
+
+int dav_outputstream_close(DavOutputStream *out) {
+    DavSession *sn = out->res->session;
+    DavResource *res = out->res;
+    DavResourceData *data = res->data;
+    
+    int ret = 0;
+    
+    dav_write(NULL, 1, 0, out);
+    
+    curl_multi_cleanup(out->m);
+    curl_easy_cleanup(out->c);
+    
+    int store = 0;
+    if(out->enc) {
+        // get sha256 hash
+        char hash[32];
+        dav_get_hash(&out->enc->sha256, (unsigned char*)data->hash);
+        aes_encrypter_close(out->enc);
+        char *enc_hash = aes_encrypt(hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+        // add crypto properties
+        if(resource_add_crypto_info(sn, out->res->href, out->res->name, enc_hash)) {
+            ret = 1;
+        }
+        free(enc_hash);
+    } else if(out->hstr) {
+        dav_hash_final(out->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);
+        dav_session_free(sn, out->hstr);
+        store = 1;
+    }
+    
+    if(store) {
+        ret = dav_store(out->res);
+    }
+    
+    dav_session_free(out->res->session, out);
+    
+    return ret;
+}
+
+*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/resource.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,142 @@
+/*
+ * 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 RESOURCE_H
+#define	RESOURCE_H
+
+#include "webdav.h"
+#include "crypto.h"
+#include <cx/string.h>
+#include <cx/hash_key.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct DavResourceData   DavResourceData;
+
+struct DavResourceData {
+    CxMap  *properties;
+    CxList *set;
+    CxList *remove;
+    CxList *crypto_set;
+    CxList *crypto_remove;
+    
+    /*
+     * properties encapsulated in a crypto-prop property or NULL
+     */
+    CxMap *crypto_properties;
+    
+    /*
+     * char* or stream
+     */
+    void      *content;
+    /*
+     * if NULL, content is a char*
+     */
+    dav_read_func read;
+    /*
+     * curl seek func
+     */
+    dav_seek_func seek;
+    /*
+     * content length
+     */
+    size_t    length;
+    
+    /*
+     * sha256 content hash
+     */
+    char hash[32];
+};
+
+/*
+ * read wrapper with integrated hashing
+ */
+typedef struct {
+    DAV_SHA_CTX *sha;
+    void *stream;
+    dav_read_func read;
+    dav_seek_func seek;
+    int error;
+} HashStream;
+
+struct DavInputStream {
+    DavResource *res;
+    CURLM *m;
+    CURL *c;
+    AESDecrypter *dec;
+    char *buffer;
+    size_t alloc;
+    size_t size;
+    size_t pos;
+    int eof;
+};
+
+struct DavOutputStream {
+    DavResource *res;
+    CURLM *m;
+    CURL *c;
+    AESEncrypter *enc;
+    HashStream *hstr;
+    const char *buffer;
+    size_t size;
+    size_t pos;
+    int eof;
+};
+
+DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href);
+
+void resource_free_properties(DavSession *sn, CxMap *properties);
+
+void resource_set_href(DavResource *res, cxstring href);
+
+void resource_set_info(DavResource *res, const char *href_str);
+DavResourceData* resource_data_new(DavSession *sn);
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val);
+void resource_set_crypto_properties(DavResource *res, CxMap *cprops);
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key);
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key);
+void resource_add_child(DavResource *parent, DavResource *child);
+void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr);
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash);
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name);
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties);
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node);
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* RESOURCE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/session.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,622 @@
+/*
+ * 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 <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+
+#include "utils.h"
+#include "session.h"
+#include "resource.h"
+#include "methods.h"
+
+DavSession* dav_session_new(DavContext *context, char *base_url) {
+    if(!base_url) {
+        return NULL;
+    }
+    cxstring url = cx_str(base_url);
+    if(url.length == 0) {
+        return NULL;
+    }
+    DavSession *sn = malloc(sizeof(DavSession));
+    memset(sn, 0, sizeof(DavSession));
+    sn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL);
+    sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    sn->key = NULL;
+    sn->errorstr = NULL;
+    sn->error = DAV_OK;
+    sn->flags = 0;
+    
+    dav_session_set_baseurl(sn, base_url);
+    
+    sn->handle = curl_easy_init();
+    curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
+    
+    // lock manager is created on-demand
+    sn->locks = NULL;
+
+    // set proxy
+    DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy
+                                                  : context->http_proxy;
+    
+    if (proxy->url) {
+        curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
+        if (proxy->username) {
+            curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
+                proxy->username);
+            if (proxy->password) {
+                curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
+                    proxy->password);
+            } else {
+                // TODO: prompt
+            }
+        }
+        if(proxy->no_proxy) {
+            curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
+                proxy->no_proxy);
+        }
+    }
+    
+    // set url
+#if LIBCURL_VERSION_NUM >= 0x072D00
+    curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
+#endif
+    curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
+    
+    // add to context
+    cxListAdd(context->sessions, sn);
+    sn->context = context;
+    
+    return sn;
+}
+
+DavSession* dav_session_new_auth(
+        DavContext *context,
+        char *base_url,
+        char *user,
+        char *password)
+{
+    DavSession *sn = dav_session_new(context, base_url);
+    if(!sn) {
+        return NULL;
+    }
+    dav_session_set_auth(sn, user, password);
+    return sn;
+}
+
+void dav_session_set_auth(DavSession *sn, char *user, char *password) {
+    if(user && password) {
+        size_t ulen = strlen(user);
+        size_t plen = strlen(password);
+        size_t upwdlen = ulen + plen + 2;
+        char *upwdbuf = malloc(upwdlen);
+        snprintf(upwdbuf, upwdlen, "%s:%s", user, password);
+        curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
+        free(upwdbuf);
+    }
+}
+
+void dav_session_set_baseurl(DavSession *sn, char *base_url) {
+    const CxAllocator *a = sn->mp->allocator;
+    if(sn->base_url) {
+        cxFree(a, sn->base_url);
+    }
+    
+    cxstring url = cx_str(base_url);
+    if(url.ptr[url.length - 1] == '/') {
+        cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
+        sn->base_url = url_m.ptr;
+    } else {
+        char *url_str = cxMalloc(a, url.length + 2);
+        memcpy(url_str, base_url, url.length);
+        url_str[url.length]     = '/';
+        url_str[url.length + 1] = '\0';
+        sn->base_url = url_str;
+    }
+}
+
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
+    sn->key = key;
+    // TODO: review sanity
+    if(flags != 0) {
+        sn->flags |= flags;
+    } else {
+        sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
+    }
+}
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
+    sn->auth_prompt = func;
+    sn->authprompt_userdata = userdata;
+}
+
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) {
+    sn->get_progress = get;
+    sn->put_progress = put;
+    sn->progress_userdata = userdata;
+}
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
+    return dav_session_curl_perform_buf(sn, NULL, NULL, status);
+}
+
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) {
+    CURLcode ret = curl_easy_perform(sn->handle);
+    long http_status;
+    curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+    if(ret == CURLE_OK) {
+        if(sn->logfunc) {
+            char *log_method;
+            char *log_url;
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url);
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method);
+            char *log_reqbody = NULL;
+            size_t log_reqbodylen = 0;
+            char *log_rpbody = NULL;
+            size_t log_rpbodylen = 0;
+            if(request) {
+                log_reqbody = request->space;
+                log_reqbodylen = request->size;
+            }
+            if(response) {
+                log_rpbody = response->space;
+                log_rpbodylen = response->size;
+            }
+            sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
+        }
+        
+        if(http_status == 401 && sn->auth_prompt) {
+            if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
+                if(request) {
+                    cxBufferSeek(request, 0, SEEK_SET);
+                }
+                if(response) {
+                    cxBufferSeek(response, 0, SEEK_SET);
+                }
+                ret = curl_easy_perform(sn->handle);
+                curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+            }
+        }
+        
+    }
+    
+    if(status) {
+        *status = http_status;
+    }
+    return ret;
+}
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->get_progress) {
+        sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->put_progress) {
+        sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
+    if(status > 0) {
+        switch(status) {
+            default: {
+                switch(c) {
+                    default: sn->error = DAV_ERROR;
+                }
+                break;
+            }
+            case 401: sn->error = DAV_UNAUTHORIZED; break;
+            case 403: sn->error = DAV_FORBIDDEN; break;
+            case 404: sn->error = DAV_NOT_FOUND; break;
+            case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
+            case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
+            case 409: sn->error = DAV_CONFLICT; break;
+            case 412: sn->error = DAV_PRECONDITION_FAILED; break;
+            case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
+            case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
+            case 423: sn->error = DAV_LOCKED; break;
+            case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
+        }
+    } else {
+        switch(c) {
+            case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
+            case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
+            case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
+            case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
+            case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
+            case CURLE_SSL_CONNECT_ERROR:
+            case CURLE_PEER_FAILED_VERIFICATION:
+            case CURLE_SSL_ENGINE_NOTFOUND:
+            case CURLE_SSL_ENGINE_SETFAILED:
+            case CURLE_SSL_CERTPROBLEM:
+            case CURLE_SSL_CIPHER:
+//#ifndef CURLE_SSL_CACERT
+//            case CURLE_SSL_CACERT:
+//#endif
+            case CURLE_SSL_CACERT_BADFILE:
+            case CURLE_SSL_SHUTDOWN_FAILED:
+            case CURLE_SSL_CRL_BADFILE:
+            case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
+            default: sn->error = DAV_ERROR; break;
+        }
+    }
+    if(c != CURLE_OK) {
+        dav_session_set_errstr(sn, curl_easy_strerror(c));
+    } else {
+        dav_session_set_errstr(sn, NULL);
+    }
+}
+
+void dav_session_set_errstr(DavSession *sn, const char *str) {
+    if(sn->errorstr) {
+        dav_session_free(sn, sn->errorstr);
+    }
+    char *errstr = NULL;
+    if(str) {
+        errstr = dav_session_strdup(sn, str);
+    }
+    sn->errorstr = errstr;
+}
+
+void dav_session_destroy(DavSession *sn) { 
+    // remove session from context
+    CxList *sessions = sn->context->sessions;
+    ssize_t i = cxListFind(sessions, sn);
+    if(i >= 0) {
+        cxListRemove(sessions, i);
+    } else {
+        printf("Error: session not found in ctx->sessions\n");
+        dav_session_destructor(sn);
+    }
+}
+
+void dav_session_destructor(DavSession *sn) {
+    cxMempoolDestroy(sn->mp);
+    curl_easy_cleanup(sn->handle);
+    free(sn);
+}
+
+
+void* dav_session_malloc(DavSession *sn, size_t size) {
+    return cxMalloc(sn->mp->allocator, size);
+}
+
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
+    return cxCalloc(sn->mp->allocator, nelm, size);
+}
+
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
+    return cxRealloc(sn->mp->allocator, ptr, size);
+}
+
+void  dav_session_free(DavSession *sn, void *ptr) {
+    cxFree(sn->mp->allocator, ptr);
+}
+
+char* dav_session_strdup(DavSession *sn, const char *str) {
+    return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr;
+}
+
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path) {
+    if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
+        // non encrypted file names
+        char *url = util_path_to_url(sn, path);
+        char *href = dav_session_strdup(sn, util_url_path(url));
+        free(url);
+        return href;
+    } else {
+        return NULL;
+    }
+}
+
+char* dav_session_get_href(DavSession *sn, const char *path) {
+    if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
+        cxstring p = cx_str(path);
+        CxBuffer href;
+        CxBuffer pbuf;
+        cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        int start = 0;
+        int begin = 0;
+        
+        // check path cache
+        char *cp = strdup(path);
+        //printf("cp: %s\n", cp);
+        while(strlen(cp) > 1) {
+            char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
+            if(cached) {
+                start = strlen(cp);
+                begin = start;
+                cxBufferPutString(&href, cached);
+                break;
+            } else {
+                // check, if the parent path is cached
+                char *f = cp;
+                cp = util_parent_path(cp);
+                free(f);
+            }
+        }
+        free(cp);
+        if(href.pos == 0) {
+            // if there are no cached elements we have to add the base url path
+            // to the href buffer
+            cxBufferPutString(&href, util_url_path(sn->base_url));
+        }
+        
+        // create resource for name lookup
+        cxmutstr rp = cx_strdup(cx_strn(path, start));
+        DavResource *root = dav_resource_new(sn, rp.ptr);
+        free(rp.ptr);
+        resource_set_href(root, cx_strn(href.space, href.pos));
+        
+        // create request buffer for propfind requests
+        CxBuffer *rqbuf = create_basic_propfind_request();
+        
+        cxstring remaining = cx_strsubs(p, start);
+        CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX);
+        DavResource *res = root;
+        cxBufferPutString(&pbuf, res->path);
+        // iterate over all remaining path elements
+        cxstring elm;
+        while(cx_strtok_next(&elms, &elm)) {
+            if(elm.length > 0) {
+                //printf("elm: %.*s\n", elm.length, elm.ptr);
+                DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
+                
+                // if necessary add a path separator
+                if(pbuf.space[pbuf.pos-1] != '/') {
+                    if(href.space[href.pos-1] != '/') {
+                        cxBufferPut(&href, '/');
+                    }
+                    cxBufferPut(&pbuf, '/');
+                }
+                // add last path/href to the cache
+                cxstring pp = cx_strn(pbuf.space, pbuf.size);
+                cxstring hh = cx_strn(href.space, href.size);
+                dav_session_cache_path(sn, pp, hh);
+                
+                cxBufferWrite(elm.ptr, 1, elm.length, &pbuf);
+                if(child) {
+                    // href is already URL encoded, so don't encode again
+                    cxBufferPutString(&href, util_resource_name(child->href));
+                    res = child;
+                } else if(DAV_ENCRYPT_NAME(sn)) {
+                    char *random_name = util_random_str();
+                    cxBufferPutString(&href, random_name);
+                    free(random_name);
+                } else {
+                    // path is not URL encoded, so we have to do this here
+                    cxstring resname = cx_str(util_resource_name((const char*)path));
+                    // the name of collections ends with
+                    // a trailing slash, which MUST NOT be encoded
+                    if(resname.ptr[resname.length-1] == '/') {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length-1);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        cxBufferPut(&href, '/');
+                        curl_free(esc);
+                    } else  {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        curl_free(esc);
+                    }
+                }
+            }
+        }
+        
+        // if necessary add a path separator
+        if(p.ptr[p.length-1] == '/') {
+            if(href.space[href.pos-1] != '/') {
+                cxBufferPut(&href, '/');
+            }
+            cxBufferPut(&pbuf, '/');
+        }
+        // add the final path to the cache
+        cxstring pp = cx_strn(pbuf.space, pbuf.size);
+        cxstring hh = cx_strn(href.space, href.size);
+        dav_session_cache_path(sn, pp, hh);
+        
+        cxmutstr href_str = cx_strdup_a(
+                sn->mp->allocator,
+                cx_strn(href.space, href.size));
+        
+        // cleanup
+        dav_resource_free_all(root);
+        cxBufferFree(rqbuf);
+        
+        cxBufferDestroy(&pbuf);
+        cxBufferDestroy(&href);
+        
+        return href_str.ptr;
+    } else {
+        return dav_session_create_plain_href(sn, path);
+    }
+}
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) {
+    if(res && !dav_propfind(sn, res, rqbuf)) {
+        DavResource *child = res->children;
+        while(child) {
+            if(!strcmp(child->name, name)) {
+                return child;
+            }
+            child = child->next;
+        }
+    }
+    return NULL;
+}
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
+    CxHashKey path_key = cx_hash_key(path.ptr, path.length);
+    char *elm = cxMapGet(sn->pathcache, path_key);
+    if(!elm) {
+        cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
+        cxMapPut(sn->pathcache, path_key, href_s.ptr);
+    }
+}
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) {
+    DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
+    lock->path = NULL;
+    lock->token = dav_session_strdup(sn, token);
+    
+    // TODO: timeout
+    
+    return lock;
+}
+
+void dav_destroy_lock(DavSession *sn, DavLock *lock) {
+    dav_session_free(sn, lock->token);
+    if(lock->path) {
+        dav_session_free(sn, lock->path);
+    }
+    dav_session_free(sn, lock);
+}
+
+
+static int dav_lock_cmp(void const *left, void const *right) {
+    const DavLock *l = left;
+    const DavLock *r = right;
+    return strcmp(l->path, r->path);
+}
+
+static int create_lock_manager(DavSession *sn) {
+    // create lock manager
+    DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager));
+    locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16);
+    locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS);
+    sn->locks = locks;
+    return 0;
+}
+
+static DavLockManager* get_lock_manager(DavSession *sn) {
+    DavLockManager *locks = sn->locks;
+    if(!locks) {
+        if(create_lock_manager(sn)) {
+            return NULL;
+        }
+        locks = sn->locks;
+    }
+    return locks;
+}
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    CxHashKey path_key = cx_hash_key_str(path);
+    if(cxMapGet(locks->resource_locks, path_key)) {
+        return -1;
+    }
+    
+    cxMapPut(locks->resource_locks, path_key, lock);
+    return 0;
+}
+
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    lock->path = dav_session_strdup(sn, path);
+    cxListAdd(locks->collection_locks, lock);
+    cxListSort(locks->collection_locks);
+    
+    return 0;
+}
+
+DavLock* dav_get_lock(DavSession *sn, const char *path) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return NULL;
+    }
+    
+    cxstring p = cx_str(path);
+    
+    DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
+    if(lock) {
+        return lock;
+    }
+    
+    CxIterator i = cxListIterator(locks->collection_locks);
+    cx_foreach(DavLock*, col_lock, i) {
+        int cmd = strcmp(path, col_lock->path);
+        if(cmd == 0) {
+            return col_lock;
+        } else if(cx_strprefix(p, cx_str(col_lock->path)))  {
+            return col_lock;
+        } else if(cmd > 0) {
+            break;
+        }
+    }
+    
+    return NULL;
+}
+
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return;
+    }
+    
+    if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
+        return;
+    }
+    
+    CxMutIterator i = cxListMutIterator(locks->collection_locks);
+    int rm = 0;
+    cx_foreach(DavLock* , cl, i) {
+        if(rm) {
+            break;
+        }
+        if(cl == lock) {
+            cxIteratorFlagRemoval(i);
+            rm = 1;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/session.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,120 @@
+/*
+ * 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_SESSION_H
+#define	DAV_SESSION_H
+
+#include <cx/buffer.h>
+#include "webdav.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+// initial size of the session mempool
+#define DAV_SESSION_MEMPOOL_SIZE 1024
+// initial size of the path cache map
+#define DAV_PATH_CACHE_SIZE 32
+    
+#define DAV_ENCRYPT_NAME(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_NAME) == DAV_SESSION_ENCRYPT_NAME)
+    
+#define DAV_DECRYPT_NAME(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_NAME) == DAV_SESSION_DECRYPT_NAME)
+    
+#define DAV_ENCRYPT_CONTENT(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_CONTENT) == DAV_SESSION_ENCRYPT_CONTENT)
+    
+#define DAV_DECRYPT_CONTENT(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_CONTENT) == DAV_SESSION_DECRYPT_CONTENT)
+
+#define DAV_IS_ENCRYPTED(sn) \
+    (DAV_ENCRYPT_NAME(sn) || DAV_ENCRYPT_CONTENT(sn))
+    
+#define DAV_CRYPTO(sn) \
+    (DAV_ENCRYPT_NAME(sn) || DAV_DECRYPT_NAME(sn) || \
+     DAV_ENCRYPT_CONTENT(sn) || DAV_DECRYPT_CONTENT(sn))
+
+#define DAV_ENCRYPT_PROPERTIES(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_PROPERTIES) == DAV_SESSION_ENCRYPT_PROPERTIES)
+
+#define DAV_DECRYPT_PROPERTIES(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_PROPERTIES) == DAV_SESSION_DECRYPT_PROPERTIES)
+    
+/*
+typedef struct DavPathCacheElement {
+    char *name;
+    char *encrypted_name;
+    int  exists;
+} DavPathCacheElement;
+*/
+    
+typedef struct DavLock DavLock;
+struct DavLock {
+    char *path;
+    char *token;
+};
+
+typedef struct DavLockManager {
+    CxMap  *resource_locks;
+    CxList *collection_locks;
+} DavLockManager;
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status);
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status);
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status);
+void dav_session_set_errstr(DavSession *sn, const char *str);
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path);
+
+char* dav_session_get_href(DavSession *sn, const char *path);
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name);
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href);
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout);
+void dav_destroy_lock(DavSession *sn, DavLock *lock);
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock);
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock);
+
+DavLock* dav_get_lock(DavSession *sn, const char *path);
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DAV_SESSION_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/utils.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1219 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <libxml/tree.h>
+#include <curl/curl.h>
+
+#ifdef _WIN32
+#include <conio.h>
+#define getpasswordchar() getch()
+#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\')
+#define PATH_SEPARATOR '\\'
+#else
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <termios.h>
+#define getpasswordchar() getchar()
+#define IS_PATH_SEPARATOR(c) (c == '/')
+#define PATH_SEPARATOR '/'
+#endif
+
+#include "webdav.h"
+#include "utils.h"
+#include "crypto.h"
+#include "session.h"
+
+/*
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/rand.h>
+*/
+
+static size_t extractval(cxstring str, char *result, char delim) {
+    size_t n = 0;
+    for(size_t i = 0; i < str.length ; i++) {
+        if(isdigit(str.ptr[i])) {
+            result[n++] = str.ptr[i];
+        } else if(str.ptr[i] != delim) {
+            return 0;
+        }
+    }
+    result[n] = '\0';
+    return n;
+}
+
+static time_t parse_iso8601(char *iso8601str) {
+
+    // safety
+    if(!iso8601str) {
+        return 0;
+    }
+    
+    // local vars
+    struct tm tparts;
+    memset(&tparts, 0, sizeof(struct tm));
+    long val;
+    char conv[16];
+    
+    // work on the trimmed string
+    cxstring date = cx_strtrim(cx_str(iso8601str));
+
+    cxstring time = cx_strchr(date, 'T');
+    if(time.length == 0) {
+        return 0;
+    }
+    date.length = time.ptr - date.ptr;
+    time.ptr++; time.length--;
+    
+    cxstring tzinfo;
+    if((tzinfo = cx_strchr(time, 'Z')).length > 0 ||
+        (tzinfo = cx_strchr(time, '+')).length > 0 ||
+        (tzinfo = cx_strchr(time, '-')).length > 0) {
+        
+        time.length = tzinfo.ptr - time.ptr;
+    }
+
+    // parse date
+    if((date.length != 8 && date.length != 10)
+            || extractval(date, conv , '-') != 8) {
+        return 0;
+    }
+    val = atol(conv);
+    if(val < 19000000L) {
+        return 0;
+    }
+    tparts.tm_mday = val % 100;
+    tparts.tm_mon = (val % 10000) / 100 - 1;
+    tparts.tm_year = val / 10000 - 1900;
+    
+    // parse time and skip possible fractional seconds
+    cxstring frac;
+    if((frac = cx_strchr(time, '.')).length > 0 ||
+        (frac = cx_strchr(time, ',')).length > 0) {
+        time.length = frac.ptr - time.ptr;
+    }
+    if((time.length != 6 && time.length != 8)
+            || extractval(time, conv , ':') != 6) {
+        return 0;
+    }
+    val = atol(conv);
+    tparts.tm_sec = val % 100;
+    tparts.tm_min = (val % 10000) / 100;
+    tparts.tm_hour = val / 10000;
+
+
+    // parse time zone (if any)
+    if(tzinfo.length == 0) {
+        // local time
+        tparts.tm_isdst = -1;
+        return mktime(&tparts);
+    } else if(!cx_strcmp(tzinfo, cx_str("Z"))) {
+#if defined(__FreeBSD__)
+        return timegm(&tparts);
+#elif defined(_WIN32)
+        return _mkgmtime(&tparts);
+#else
+        return mktime(&tparts) - timezone;
+#endif
+    } else if(tzinfo.ptr[0] == '+' || tzinfo.ptr[0] == '-') {
+        int sign = (tzinfo.ptr[0] == '+') ? -1 : 1;
+
+        if(tzinfo.length > 6) {
+            return 0;
+        } else {
+            tzinfo.ptr++; tzinfo.length--;
+            extractval(tzinfo, conv, ':');
+            val = atol(conv);
+            val = 60 * (val / 100) + (val % 100);
+#if defined(__FreeBSD__)
+            return timegm(&tparts) + (time_t) (60 * val * sign);
+#elif defined(_WIN32)
+            return _mkgmtime(&tparts) + (time_t)(60 * val * sign);
+#else
+            return mktime(&tparts) - timezone + (time_t) (60 * val * sign);            
+#endif
+        }
+    } else {
+        return 0;
+    }
+}
+
+
+time_t util_parse_creationdate(char *str) {
+    // parse a ISO-8601 date (rfc-3339)
+    // example: 2012-11-29T21:35:35Z
+    if(!str) {
+        return 0;
+    }
+    
+    return parse_iso8601(str);
+}
+
+time_t util_parse_lastmodified(char *str) {
+    // parse a rfc-1123 date
+    // example: Thu, 29 Nov 2012 21:35:35 GMT
+    if(!str) {
+        return 0;
+    } else {
+        time_t result = curl_getdate(str, NULL);
+        if(result == -1) {
+            // fall back to the ISO-8601 format (e.g. Microsoft Sharepoint
+            // illegally uses this format for lastmodified, but also some
+            // users might want to give an ISO-8601 date)
+            return util_parse_creationdate(str);
+        } else {
+            return result;
+        }
+    }
+}
+
+int util_getboolean(const char *v) {
+    if(v[0] == 'T' || v[0] == 't') {
+        return 1;
+    }
+    return 0;
+}
+
+int util_strtouint(const char *str, uint64_t *value) {
+    char *end;
+    errno = 0;
+    uint64_t val = strtoull(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_strtoint(const char *str, int64_t *value) {
+    char *end;
+    errno = 0;
+    int64_t val = strtoll(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_szstrtouint(const char *str, uint64_t *value) {
+    char *end;
+    errno = 0;
+    size_t len = strlen(str);
+    uint64_t val = strtoull(str, &end, 0);
+    if(end == str+len) {
+        *value = val;
+        return 1;
+    } else if(end == str+len-1) {
+        uint64_t mul = 1;
+        switch(end[0]) {
+            case 'k':
+            case 'K': mul = 1024; break;
+            case 'm':
+            case 'M': mul = 1024*1024; break;
+            case 'g':
+            case 'G': mul = 1024*1024*1024; break;
+            default: return 0;
+        }
+        
+        uint64_t result = 0;
+        if(util_uint_mul(val, mul, &result)) {
+            return 0;
+        }
+        *value = result;
+        return 1;
+    }
+    return 0;
+}
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result) {
+    if(a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    uint64_t r = a * b;
+    if(r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
+
+char* util_url_base_s(cxstring url) {
+    size_t i = 0;
+    if(url.length > 0) {
+        int slmax;
+        if(cx_strprefix(url, cx_str("http://"))) {
+            slmax = 3;
+        } else if(cx_strprefix(url, cx_str("https://"))) {
+            slmax = 3;
+        } else {
+            slmax = 1;
+        }
+        int slashcount = 0;
+        for(i=0;i<url.length;i++) {
+            if(url.ptr[i] == '/') {
+                slashcount++;
+                if(slashcount == slmax) {
+                    i++;
+                    break;
+                }
+            }
+        }
+    }
+    cxstring server = cx_strsubsl(url, 0, i);
+    return cx_strdup(server).ptr;
+}
+
+char* util_url_base(char *url) {
+    return util_url_base_s(cx_str(url));
+}
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#endif
+
+const char* util_url_path(const char *url) {
+    const char *path = NULL;
+    size_t len = strlen(url);
+    int slashcount = 0;
+    int slmax;
+    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+        slmax = 3;
+    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+        slmax = 3;
+    } else {
+        slmax = 1;
+    }
+    char c;
+    for(int i=0;i<len;i++) {
+        c = url[i];
+        if(c == '/') {
+            slashcount++;
+            if(slashcount == slmax) {
+                path = url + i;
+                break;
+            }
+        }
+    } 
+    if(!path) {
+        path = url + len; // empty string
+    }
+    return path;
+}
+
+char* util_url_decode(DavSession *sn, const char *url) {
+    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+    char *ret = strdup(unesc);
+    curl_free(unesc);
+    return ret;
+}
+
+static size_t util_header_callback(char *buffer, size_t size,
+        size_t nitems, void *data) {
+    
+    cxstring sbuffer = cx_strn(buffer, size*nitems);
+    
+    CxMap *map = (CxMap*) data;
+    
+    // if we get a status line, clear the map and exit
+    if(cx_strprefix(sbuffer, cx_str("HTTP/"))) {
+        // TODO: use new map destructor   ucx_map_free_content(map, free);
+        cxMapClear(map);
+        return size*nitems;
+    }
+    
+    // if we get the terminating CRLF, just exit
+    if(!cx_strcmp(sbuffer, cx_str("\r\n"))) {
+        return 2;
+    }
+    
+    cxstring key = sbuffer;
+    cxstring value = cx_strchr(sbuffer, ':');
+    
+    if(value.length == 0) {
+        return 0; // invalid header line
+    }
+    
+    key.length = value.ptr - key.ptr;
+    value.ptr++; value.length--;
+    
+    cxmutstr key_cp = cx_strdup(cx_strtrim(key));
+    cx_strlower(key_cp);
+    cxmutstr value_cp = cx_strdup(cx_strtrim(value));
+        
+    cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr);
+    
+    free(key_cp.ptr);
+    
+    return sbuffer.length;
+}
+
+int util_path_isrelated(const char *path1, const char *path2) {
+    cxstring p1 = cx_str(path1);
+    cxstring p2 = cx_str(path2);
+    
+    if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) {
+        p1.length--;
+    }
+    if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) {
+        p2.length--;
+    }
+    
+    if(p2.length < p1.length) {
+        return 0;
+    }
+    
+    if(!cx_strcmp(p1, p2)) {
+        return 1;
+    }
+    
+    if(cx_strprefix(p2, p1)) {
+        if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+#ifdef _WIN32
+int util_path_isabsolut(const char *path) {
+    if(strlen(path) < 3) {
+        return 0;
+    }
+    
+    // check if first char is A-Z or a-z
+    char c = path[0];
+    if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) {
+        return 0;
+    }
+    
+    if(path[1] == ':' && path[2] == '\\') {
+        return 1;
+    }
+    return 0;
+}
+#else
+int util_path_isabsolut(const char *path) {
+    return path[0] == '/';
+}
+#endif
+
+char* util_path_normalize(const char *path) {
+    size_t len = strlen(path);
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, len+1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    if(path[0] == '/') {
+        cxBufferPut(&buf, '/');
+    }
+    
+    int add_separator = 0;
+    int seg_start = 0;
+    for(int i=0;i<=len;i++) {
+        char c = path[i];
+        if(IS_PATH_SEPARATOR(c) || c == '\0') {
+            const char *seg_ptr = path+seg_start;
+            int seg_len = i - seg_start;
+            if(IS_PATH_SEPARATOR(seg_ptr[0])) {
+                seg_ptr++;
+                seg_len--;
+            }
+            
+            if(seg_len > 0) {
+                cxstring seg = cx_strn(seg_ptr, seg_len);
+                if(!cx_strcmp(seg, CX_STR(".."))) {
+                    for(int j=buf.pos;j>=0;j--) {
+                        char t = j < buf.pos ? buf.space[j] : 0;
+                        if(IS_PATH_SEPARATOR(t) || j == 0) {
+                            buf.pos = j;
+                            buf.size = j;
+                            buf.space[j] = 0;
+                            add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0;
+                            break;
+                        }
+                    }
+                } else if(!cx_strcmp(seg, CX_STR("."))) {
+                    // ignore
+                } else {
+                    if(add_separator) {
+                        cxBufferPut(&buf, PATH_SEPARATOR);
+                    }
+                    cxBufferWrite(seg_ptr, 1, seg_len, &buf);
+                    add_separator = 1;
+                }
+            }
+            
+            seg_start = i;
+        }
+    }
+    
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
+}
+
+static char* create_relative_path(const char *abspath, const char *base) {
+    size_t path_len = strlen(abspath);
+    size_t base_len = strlen(base);
+    
+    if(IS_PATH_SEPARATOR(abspath[path_len-1])) {
+        path_len--;
+    }
+    if(IS_PATH_SEPARATOR(base[base_len-1])) {
+        base_len--;
+    }
+    // get base parent
+    for(int i=base_len-1;i>=0;i--) {
+        if(IS_PATH_SEPARATOR(base[i])) {
+            base_len = i+1;
+            break;
+        }
+    }
+    
+    size_t max = path_len > base_len ? base_len : path_len;
+    
+    // get prefix of abspath and base
+    // this dir is the root of the link
+    size_t i;
+    size_t last_dir = 0;
+    for(i=0;i<max;i++) {
+        char c = abspath[i];
+        if(c != base[i]) {
+            break;
+        } else if(IS_PATH_SEPARATOR(c)) {
+            last_dir = i;
+        }
+    }
+    
+    char *ret = NULL;
+    CxBuffer out;
+    if(last_dir+1 < base_len) {
+        // base is deeper than the link root, we have to go backwards
+        int dircount = 0;
+        for(int i=last_dir+1;i<base_len;i++) {
+            if(IS_PATH_SEPARATOR(base[i])) {
+                dircount++;
+            }
+        }
+        
+        cxBufferInit(&out, NULL, dircount*3+path_len-last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        for(int i=0;i<dircount;i++) {
+            cxBufferPutString(&out, "../");
+        }
+    } else {
+        cxBufferInit(&out, NULL, path_len - last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    }
+    
+    cxBufferPutString(&out, abspath + last_dir + 1);
+    cxBufferPut(&out, 0);
+    
+    return out.space;
+}
+
+#ifdef _WIN32
+char* util_create_relative_path(const char *abspath, const char *base) {
+    char *abspath_converted = strdup(abspath);
+    char *base_converted = strdup(base);
+    size_t abs_len = strlen(abspath_converted);
+    size_t base_len = strlen(base_converted);
+    
+    for(int i=0;i<abs_len;i++) {
+        if(abspath_converted[i] == '\\') {
+            abspath_converted[i] = '/';
+        }
+    }
+    for(int i=0;i<base_len;i++) {
+        if(base_converted[i] == '\\') {
+            base_converted[i] = '/';
+        }
+    }
+    
+    char *ret = create_relative_path(abspath_converted, base_converted);
+    free(abspath_converted);
+    free(base_converted);
+    return ret;
+}
+#else
+char* util_create_relative_path(const char *abspath, const char *base) {
+    return create_relative_path(abspath, base);
+}
+#endif
+
+
+void util_capture_header(CURL *handle, CxMap* map) {
+    if(map) {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, util_header_callback);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, map);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, NULL);
+    }
+}
+
+const char* util_resource_name(const char *url) {
+    cxstring urlstr = cx_str(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    cxstring resname = cx_strrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+int util_mkdir(char *path, mode_t mode) {
+#ifdef _WIN32
+    return mkdir(path);
+#else
+    return mkdir(path, mode);
+#endif
+}
+
+char* util_concat_path(const char *url_base, const char *p) {
+    cxstring base = cx_str((char*)url_base);
+    cxstring path;
+    if(p) {
+        path = cx_str((char*)p);
+    } else {
+        path = CX_STR("");
+    }
+    
+    int add_separator = 0;
+    if(base.length != 0 && base.ptr[base.length-1] == '/') {
+        if(path.ptr[0] == '/') {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != '/') {
+            add_separator = 1;
+        }
+    }
+    
+    cxmutstr url;
+    if(add_separator) {
+        url = cx_strcat(3, base, CX_STR("/"), path);
+    } else {
+        url = cx_strcat(2, base, path);
+    }
+    
+    return url.ptr;
+}
+
+char* util_get_url(DavSession *sn, const char *href) {
+    cxstring base = cx_str(sn->base_url);
+    cxstring href_str = cx_str(href);
+    
+    const char *base_path = util_url_path(sn->base_url);
+    base.length -= strlen(base_path);
+    
+    cxmutstr url = cx_strcat(2, base, href_str);
+    return url.ptr;
+}
+
+void util_set_url(DavSession *sn, const char *href) {
+    char *url = util_get_url(sn, href);
+    curl_easy_setopt(sn->handle, CURLOPT_URL, url);
+    free(url);
+}
+
+char* util_path_to_url(DavSession *sn, const char *path) {
+    CxBuffer url;
+    cxBufferInit(&url, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // add base url
+    cxBufferWrite(sn->base_url, 1, strlen(sn->base_url), &url);
+    // remove trailing slash
+    cxBufferSeek(&url, -1, SEEK_CUR);
+    
+    cxstring p = cx_str(path);
+    
+    CxStrtokCtx tkctx = cx_strtok(p, CX_STR("/"), INT_MAX);
+    cxstring node;
+    while(cx_strtok_next(&tkctx, &node)) {
+        if(node.length > 0) {
+            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+            cxBufferPut(&url, '/');
+            cxBufferWrite(esc, 1, strlen(esc), &url);
+            curl_free(esc);
+        }
+    }
+    
+    if(path[p.length-1] == '/') {
+        cxBufferPut(&url, '/');
+    }
+    cxBufferPut(&url, 0);
+    
+    return url.space;
+}
+
+char* util_parent_path(const char *path) {
+    const char *name = util_resource_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength) {
+    char *str = malloc(16);
+    uint64_t size = contentlength;
+    
+    if(iscollection) {
+        str[0] = '\0'; // currently no information for collections
+    } else if(size < 0x400) {
+        snprintf(str, 16, "%" PRIu64 " bytes", size);
+    } else if(size < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.1f KiB", s);
+        } else {
+            snprintf(str, 16, "%.0f KiB", s);
+        }
+    } else if(size < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.1f MiB", s);
+        } else {
+            size /= 0x100000;
+            snprintf(str, 16, "%.0f MiB", s);
+        }
+    } else if(size < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.1f GiB", s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f GiB", s);
+        }
+    } else {
+        size /= 1024;
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.1f TiB", s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f TiB", s);
+        }
+    }
+    return str;
+}
+
+char* util_date_str(time_t tm) {
+    struct tm t;
+    struct tm n;
+    time_t now = time(NULL);
+#ifdef _WIN32
+    memcpy(&t, localtime(&tm), sizeof(struct tm));
+    memcpy(&n, localtime(&now), sizeof(struct tm));
+#else
+    localtime_r(&tm, &t);
+    localtime_r(&now, &n);
+#endif /* _WIN32 */
+    char *str = malloc(16);
+    if(t.tm_year == n.tm_year) {
+        strftime(str, 16, "%b %d %H:%M", &t);
+    } else {
+        strftime(str, 16, "%b %d  %Y", &t);
+    }
+    return str;
+}
+
+
+char* util_xml_get_text(const xmlNode *elm) {
+    xmlNode *node = elm->children;
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+
+char* util_base64decode(const char *in) {
+    int len = 0;
+    return util_base64decode_len(in, &len);
+}
+
+#define WHITESPACE 64
+#define EQUALS     65
+#define INVALID    66
+static char b64dectable[] = {
+    66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
+    54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+    10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,
+    29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66
+};
+char* util_base64decode_len(const char* in, int *outlen) {
+    /* code is mostly from wikibooks */
+    
+    if(!in) {
+        *outlen = 0;
+        return NULL;
+    }
+    
+    size_t inlen = strlen(in);
+    size_t bufsize = (inlen*3) / 4;
+    char *outbuf = malloc(bufsize+1);
+    *outlen = -1;
+    
+    unsigned char *out = (unsigned char*)outbuf;
+    
+    const char *end = in + inlen;
+    char iter = 0;
+    uint32_t buf = 0;
+    size_t len = 0;
+    
+    while (in < end) {
+        unsigned char c = b64dectable[*in++];
+        
+        switch (c) {
+            case WHITESPACE: continue; /* skip whitespace */
+            case INVALID: {
+                  /* invalid input */
+                outbuf[0] = 0;
+                return outbuf;
+            }
+            case EQUALS: {
+                /* pad character, end of data */
+                in = end;
+                continue;
+            }
+            default: {
+                buf = buf << 6 | c;
+                iter++; // increment the number of iteration
+                /* If the buffer is full, split it into bytes */
+                if (iter == 4) {
+                    if ((len += 3) > bufsize) {
+                        /* buffer overflow */
+                        outbuf[0] = 0;
+                        return outbuf;
+                    }
+                    *(out++) = (buf >> 16) & 255;
+                    *(out++) = (buf >> 8) & 255;
+                    *(out++) = buf & 255;
+                    buf = 0; iter = 0;
+
+                }
+            }
+        }
+    }
+   
+    if (iter == 3) {
+        if ((len += 2) > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 10) & 255;
+        *(out++) = (buf >> 2) & 255;
+    }
+    else if (iter == 2) {
+        if (++len > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 4) & 255;
+    }
+
+    *outlen = len; /* modify to reflect the actual output size */
+    outbuf[len] = 0;
+    return outbuf;
+}
+
+
+static char* b64enctable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char* util_base64encode(const char *in, size_t len) {
+    // calculate length of base64 output and create buffer
+    size_t outlen = 4 * ((len + 2) / 3);
+    int pad = len % 3;
+    
+    char *out = malloc(outlen + 1);
+    out[outlen] = 0;
+    size_t pos = 0;
+    
+    // encode blocks of 3 bytes
+    size_t i;
+    size_t blockend = len - pad;
+    for(i=0;i<blockend;i++) {
+        unsigned char b1 = in[i++];
+        unsigned char b2 = in[i++];
+        unsigned char b3 = in[i];
+        uint32_t inb = b1 << 16 | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+    }
+    
+    // encode last bytes
+    if(pad > 0) {
+        char p[3] = {0, 0, 0};
+        for(int j=0;i<len;i++) {
+            p[j++] = in[i];
+        }
+        unsigned char b1 = p[0];
+        unsigned char b2 = p[1];
+        unsigned char b3 = p[2];
+        uint32_t inb = (b1 << 16) | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+        for(int k=outlen-1;k>=outlen-(3-pad);k--) {
+            out[k] = '=';
+        }
+    }
+    
+    return out;
+}
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_encrypt_str_k(sn, str, k);
+}
+
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    char *enc_str = aes_encrypt(str, strlen(str), key);
+    char *ret_str = dav_session_strdup(sn, enc_str);
+    free(enc_str);
+    return ret_str;
+}
+
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_decrypt_str_k(sn, str, k);
+}
+
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    size_t len = 0;
+    char *dec_str = aes_decrypt(str, &len, key);
+    char *ret_str = dav_session_strdup(sn, dec_str);
+    free(dec_str);
+    return ret_str;
+}
+
+char* util_random_str() {
+    unsigned char *str = malloc(25);
+    str[24] = '\0';
+    
+    cxstring t = CX_STR(
+            "01234567890"
+            "abcdefghijklmnopqrstuvwxyz"
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    const unsigned char *table = (const unsigned char*)t.ptr;
+    
+#ifdef DAV_USE_OPENSSL
+    RAND_bytes(str, 24);
+#else
+    dav_rand_bytes(str, 24);
+#endif
+    for(int i=0;i<24;i++) {
+        int c = str[i] % t.length;
+        str[i] = table[c];
+    }
+    
+    return (char*)str;
+}
+
+/*
+ * gets a substring from 0 to the appearance of the token
+ * tokens are separated by space
+ * sets sub to the substring and returns the remaining string
+ */
+// TODO: remove if it isn't used
+/*
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
+    int i;
+    int token_start = -1;
+    int token_end = -1;
+    for(i=0;i<=str.length;i++) {
+        int c;
+        if(i == str.length) {
+            c = ' ';
+        } else {
+            c = str.ptr[i];
+        }
+        if(c < 33) {
+            if(token_start != -1) {
+                token_end = i;
+                size_t len = token_end - token_start;
+                sstr_t tk = sstrsubsl(str, token_start, len);
+                //printf("token: {%.*s}\n", token.length, token.ptr);
+                if(!sstrcmp(tk, token)) {
+                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+                    break;
+                }
+                token_start = -1;
+                token_end = -1;
+            }
+        } else {
+            if(token_start == -1) {
+                token_start = i;
+            }
+        }
+    }
+    
+    if(i < str.length) {
+        return sstrtrim(sstrsubs(str, i));
+    } else {
+        str.ptr = NULL;
+        str.length = 0;
+        return str;
+    }
+}
+*/
+
+cxmutstr util_readline(FILE *stream) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    int c;
+    while((c = fgetc(stream)) != EOF) {
+        if(c == '\n') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    
+    cxmutstr str = cx_strdup(cx_strtrim(cx_strn(buf.space, buf.size)));
+    cxBufferDestroy(&buf);
+    return str;
+}
+
+char* util_password_input(char *prompt) {
+    fprintf(stderr, "%s", prompt);
+    fflush(stderr);
+    
+#ifndef _WIN32
+    // hide terminal input
+    struct termios oflags, nflags;
+    if(isatty(fileno(stdin))) {
+        tcgetattr(fileno(stdin), &oflags);
+        nflags = oflags;
+        nflags.c_lflag &= ~ECHO;
+        nflags.c_lflag |= ECHONL;
+        if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
+            perror("tcsetattr");
+        }
+    }
+    
+#endif
+    
+    // read password input
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    int c = 0;
+    while((c = getpasswordchar()) != EOF) {
+        if(c == '\n' || c == '\r') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    cxBufferPut(&buf, 0);
+    fflush(stdin);
+    
+#ifndef _WIN32
+    // restore terminal settings
+    if (isatty(fileno(stdin)) && tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
+        perror("tcsetattr");
+    }
+#endif
+    
+    return buf.space;
+}
+
+int util_exec_command(char *command, CxBuffer *outbuf) {
+#ifdef _WIN32
+    fprintf(stderr, "util_exec_command unsupported\n");
+    return 1;
+#else
+    
+    int pout[2];
+    if(pipe(pout)) {
+        perror("pipe");
+        return 1;
+    }
+    
+    int ret = 0;
+    
+    // close stdin and stderr, use pipe for stdout
+    posix_spawn_file_actions_t actions;
+    posix_spawn_file_actions_init(&actions);
+    posix_spawn_file_actions_addclose(&actions, 0);
+    posix_spawn_file_actions_adddup2(&actions, pout[1], 1);
+    posix_spawn_file_actions_addclose(&actions, 2);
+    
+    char *args[4];
+    args[0] = "sh";
+    args[1] = "-c";
+    args[2] = command;
+    args[3] = NULL;
+    
+    pid_t pid; // child pid
+    ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, args, NULL);
+    
+    close(pout[1]);
+    
+    if(!ret) {
+        ssize_t r;
+        char buf[1024];
+        while((r = read(pout[0], buf, 1024)) > 0) {
+            cxBufferWrite(buf, 1, r, outbuf);
+        }
+    }
+    
+    // wait for child process
+    ret = 1;
+    waitpid(pid, &ret, 0);
+    
+    posix_spawn_file_actions_destroy(&actions);
+    close(pout[0]);
+    
+    return ret;
+#endif
+}
+
+char* util_hexstr(const unsigned char *data, size_t len) {
+    size_t buflen = 2*len + 4;
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, buflen + 1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    for(int i=0;i<len;i++) {
+        cx_bprintf(&buf, "%02x", data[i]);
+    }
+    cxBufferPut(&buf, 0);
+    return buf.space;
+}
+
+void util_remove_trailing_pathseparator(char *path) {
+    size_t len = strlen(path);
+    if(len < 2) {
+        return;
+    }
+    
+    if(path[len-1] == '/') {
+        path[len-1] = '\0';
+    }
+}
+
+char* util_file_hash(const char *path) {
+    FILE *in = fopen(path, "r");
+    if(!in) {
+        return NULL;
+    }
+    
+    DAV_SHA_CTX *sha = dav_hash_init();
+    char *buf = malloc(16384);
+    
+    size_t r;
+    while((r = fread(buf, 1, 16384, in)) > 0) {
+        dav_hash_update(sha, buf, r);
+    }
+    
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    dav_hash_final(sha, hash);
+    free(buf);
+    fclose(in);
+    
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/utils.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,141 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UTILS_H
+#define	UTILS_H
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <io.h>
+#endif /* _WIN32 */
+
+#include <sys/types.h>
+#include <libxml/tree.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <curl/curl.h>
+#include "webdav.h"
+
+#ifdef _WIN32
+#ifndef mode_t
+#define mode_t int
+#endif
+#endif
+
+#ifndef S_IRWXG
+/* if one is not defined, the others are probably also not defined */
+#define S_IRWXU 0700
+#define S_IRWXG  070
+#define S_IRGRP  040
+#define S_IWGRP  020
+#define S_IXGRP  010
+#define S_IRWXO   07
+#define S_IROTH   04
+#define S_IWOTH   02
+#define S_IXOTH   01
+#endif /* S_IRWXG */
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+time_t util_parse_creationdate(char *str);
+time_t util_parse_lastmodified(char *str);
+
+int util_mkdir(char *path, mode_t mode);
+
+char* util_url_base(char *url);
+char* util_url_base_s(cxstring url);
+const char* util_url_path(const char *url);
+char* util_url_decode(DavSession *sn, const char *url);
+const char* util_resource_name(const char *url);
+char* util_concat_path(const char *url_base, const char *path);
+char* util_get_url(DavSession *sn, const char *href);
+void util_set_url(DavSession *sn, const char *href);
+
+/*
+ * returns true if path1 and path2 are equal or if path2 is a child of path1
+ */
+int util_path_isrelated(const char *path1, const char *path2);
+
+int util_path_isabsolut(const char *path);
+
+char* util_path_normalize(const char *path);
+char* util_create_relative_path(const char *abspath, const char *base);
+
+void util_capture_header(CURL *handle, CxMap* map);
+
+char* util_path_to_url(DavSession *sn, const char *path);
+char* util_parent_path(const char *path);
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength);
+char* util_date_str(time_t tm);
+
+int util_getboolean(const char *v);
+int util_strtouint(const char *str, uint64_t *value);
+int util_strtoint(const char *str, int64_t *value);
+int util_szstrtouint(const char *str, uint64_t *value);
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result);
+
+char* util_xml_get_text(const xmlNode *elm);
+
+char* util_base64decode(const char *in);
+char* util_base64decode_len(const char *in, int *outlen);
+char* util_base64encode(const char *in, size_t len);
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+
+char* util_random_str();
+
+//sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub);
+
+cxmutstr util_readline(FILE *stream);
+char* util_password_input(char *prompt);
+
+int util_exec_command(char *command, CxBuffer *outbuf);
+
+char* util_hexstr(const unsigned char *data, size_t len);
+
+void util_remove_trailing_pathseparator(char *path);
+
+char* util_file_hash(const char *path);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UTILS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/versioning.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,175 @@
+/*
+ * 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 "versioning.h"
+
+#include "methods.h"
+#include "utils.h"
+#include "session.h"
+
+static int basic_deltav_op(DavResource *res, char *method) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode ret = do_simple_request(sn, method, locktoken);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(!(ret == CURLE_OK && (status >= 200 && status < 300))) {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+    return 0;
+}
+
+int dav_versioncontrol(DavResource *res) {
+    return basic_deltav_op(res, "VERSION-CONTROL");
+}
+
+int dav_checkout(DavResource *res) {
+    return basic_deltav_op(res, "CHECKOUT");
+}
+
+int dav_checkin(DavResource *res) {
+    return basic_deltav_op(res, "CHECKIN");
+}
+
+int dav_uncheckout(DavResource *res) {
+    return basic_deltav_op(res, "UNCHECKOUT");
+}
+
+DavResource* dav_versiontree(DavResource *res, char *properties) {
+    DavSession *sn = res->session;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    CxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, cx_str(properties));
+        
+        // check if the list already contains a D:version-name property
+        int add_vname = 1;
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty *, p, i) {
+            if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) {
+                add_vname = 0;
+                break;
+            }
+        }
+        if(add_vname) {
+            // we need at least the D:version-name prop
+            DavProperty p;
+            p.ns = dav_get_namespace(sn->context, "D");
+            p.name = strdup("version-name");
+            p.value = NULL;
+            cxListInsert(proplist, 0, &p);
+        }
+    }
+    
+    
+    
+    // create a version-tree request, which is almost the same as propfind
+    CxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1);
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // do the request
+    CURLcode ret = do_report_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+    int error = 0;
+    DavResource *versions = NULL;
+    if(ret == CURLE_OK && status == 207) {
+        sn->error = DAV_OK;
+        
+        // parse multistatus response
+        PropfindParser *parser = create_propfind_parser(rpbuf, NULL);
+        if(parser) {
+            DavResource *list_end = NULL;
+            
+            ResponseTag response;
+            int r;
+            
+            // we don't want name decryption for version resources
+            int snflags = sn->flags;
+            sn->flags = 0;
+            while((r = get_propfind_response(parser, &response)) != 0) {
+                if(r == -1) {
+                    res->session->error = DAV_ERROR;
+                    error = 1;
+                    break;
+                }
+                DavResource *v = response2resource(sn, &response, NULL);
+                // add version to list
+                if(!versions) {
+                    versions = v;
+                } else {
+                    list_end->next = v;
+                }
+                list_end = v;
+                
+                cleanup_response(&response);
+            }
+            sn->flags = snflags;
+            
+            destroy_propfind_parser(parser);
+        } else {
+            sn->error = DAV_ERROR;
+            error = 1;
+        }
+    } else {
+        dav_session_set_error(sn, ret, status);
+        error = 1;
+    }
+    
+    // cleanup
+    if(proplist) {
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty*, p, i) {
+            free(p->name);
+        }
+        cxListDestroy(proplist);
+    }
+    
+    if(error && versions) {
+        DavResource *cur = versions;
+        while(cur) {
+            DavResource *next = cur->next;
+            dav_resource_free(cur);
+            cur = next;
+        }
+        versions = NULL;
+    }
+    
+    return versions;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/versioning.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,45 @@
+/*
+ * 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 VERSIONING_H
+#define VERSIONING_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VERSIONING_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/webdav.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,428 @@
+/*
+ * 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 <libxml/tree.h>
+
+#include "utils.h"
+#include "webdav.h"
+#include "session.h"
+#include "methods.h"
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+#include <cx/compare.h>
+#include "davqlparser.h"
+#include "davqlexec.h"
+
+
+DavContext* dav_context_new(void) {
+    // initialize
+    DavContext *context = calloc(1, sizeof(DavContext));
+    if(!context) {
+        return NULL;
+    }
+    context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_intptr, CX_STORE_POINTERS);
+    context->sessions->destructor_data = dav_session_destructor;
+    context->http_proxy = calloc(1, sizeof(DavProxy));
+    if(!context->http_proxy) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->https_proxy = calloc(1, sizeof(DavProxy));
+    if(!context->https_proxy) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->namespaces) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->namespaceinfo) {
+        dav_context_destroy(context);
+    }
+    context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->keys) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    // add DAV: namespace
+    if(dav_add_namespace(context, "D", "DAV:")) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    
+    // add idav namespace
+    if(dav_add_namespace(context, "idav", DAV_NS)) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    // add idavprops namespace
+    if(dav_add_namespace(context, "idavprops", DAV_PROPS_NS)) {
+        dav_context_destroy(context);
+        return NULL;
+    }   
+
+    return context;
+}
+
+void dav_context_destroy(DavContext *ctx) {
+    // destroy all sessions assoziated with this context
+    // ctx->sessions destructor must be dav_session_destructor
+    cxListDestroy(ctx->sessions);
+    
+    if(ctx->http_proxy) {
+        free(ctx->http_proxy);
+    }
+    if(ctx->https_proxy) {
+        free(ctx->https_proxy);
+    }
+    
+    if(ctx->namespaces) {
+        CxIterator i = cxMapIteratorValues(ctx->namespaces);
+        cx_foreach(DavNamespace*, ns, i) {
+            if(!ns) continue;
+            if(ns->prefix) {
+                free(ns->prefix);
+            }
+            if(ns->name) {
+                free(ns->name);
+            }
+            free(ns);
+        }
+        cxMapDestroy(ctx->namespaces);
+    }
+    if(ctx->namespaceinfo) {
+        // TODO: implement
+    }
+    if(ctx->keys) {
+        CxIterator i = cxMapIteratorValues(ctx->keys);
+        cx_foreach(DavKey*, key, i) {
+            if(!key) continue;
+            if(key->name) {
+                free(key->name);
+            }
+            if(key->data) {
+                free(key->data);
+            }
+            free(key);
+        }
+        cxMapDestroy(ctx->keys);
+    }    
+    
+    free(ctx);
+}
+
+void dav_context_add_key(DavContext *context, DavKey *key) {
+    cxMapPut(context->keys, cx_hash_key_str(key->name), key);
+}
+
+DavKey* dav_context_get_key(DavContext *context, const char *name) {
+    if(name) {
+        return cxMapGet(context->keys, cx_hash_key_str(name));
+    }
+    return NULL;
+}
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *name) {
+    DavNamespace *namespace = malloc(sizeof(DavNamespace));
+    if(!namespace) {
+        return 1;
+    }
+    
+    char *p = strdup(prefix);
+    char *n = strdup(name);
+    
+    int err = 0;
+    if(p && n) {
+        namespace->prefix = p;
+        namespace->name = n;
+        err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace);
+    }
+    
+    if(err) {
+        free(namespace);
+        if(p) free(p);
+        if(n) free(n);
+    }
+    
+    return err;
+}
+
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
+    return cxMapGet(context->namespaces, cx_hash_key_str(prefix));
+}
+
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) {
+    return cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
+}
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
+    CxHashKey hkey = cx_hash_key_str(ns);
+    DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey);
+    if(!info) {
+        info = calloc(1, sizeof(DavNSInfo));
+        info->encrypt = encrypt;
+        cxMapPut(context->namespaceinfo, hkey, info);
+    } else {
+        info->encrypt = encrypt;
+    }
+    return 0;
+}
+
+int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
+    DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns));
+    if(info) {
+        return info->encrypt;
+    }
+    return 0;
+}
+
+void dav_get_property_namespace_str(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **ns,
+        char **name)
+{
+    // TODO: rewrite using dav_get_property_ns
+    
+    char *pname = strchr(prefixed_name, ':');
+    char *pns = "DAV:";
+    if(pname) {
+        DavNamespace *ns = dav_get_namespace_s(
+                ctx,
+                cx_strn(prefixed_name, pname-prefixed_name));
+        if(ns) {
+            pns = ns->name;
+            pname++;
+        } else {
+            pns = NULL;
+            pname = NULL;
+        }
+    } else {
+        pname = prefixed_name;
+    }
+    *ns = pns;
+    *name = pname;
+}
+
+DavNamespace* dav_get_property_namespace(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **name)
+{
+    char *pname = strchr(prefixed_name, ':');
+    if(pname) {
+        DavNamespace *ns = dav_get_namespace_s(
+                ctx,
+                cx_strn(prefixed_name, pname-prefixed_name));
+        if(ns) {
+            *name = pname +1;
+            return ns;
+        } else {
+            *name = NULL;
+            return NULL;
+        }
+    } else {
+        *name = prefixed_name;
+        return dav_get_namespace_s(ctx, cx_str("D"));
+    }
+}
+
+// TODO: add sstr_t version of dav_get_property_ns
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource) {
+    char *eff_url;
+    curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url);
+    if(eff_url) {
+        const char *href = util_url_path(eff_url);
+        if(strcmp(href, resource->href)) {
+            dav_session_free(sn, resource->href);
+            resource->href = dav_session_strdup(sn, href);
+        }
+    }
+}
+
+DavResource* dav_get(DavSession *sn, char *path, const char *properties) {  
+    CURL *handle = sn->handle;
+    DavResource *resource = dav_resource_new(sn, path);
+    util_set_url(sn, dav_resource_get_href(resource));
+    
+    CxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, cx_str(properties));
+    }
+    CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0);
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    //fwrite(rqbuf->space, 1, rqbuf->size, stdout);
+    //printf("\n");
+    
+    CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        dav_set_effective_href(sn, resource);
+        
+        //printf("response\n%s\n", rpbuf->space);
+        // TODO: use PropfindParser
+        resource = parse_propfind_response(sn, resource, rpbuf);
+        resource->exists = 1;
+        sn->error = DAV_OK;
+    } else  {
+        dav_session_set_error(sn, ret, status);
+        dav_resource_free(resource);
+        resource = NULL;
+    }
+    
+    cxBufferFree(rqbuf);
+    cxBufferFree(rpbuf);
+    
+    if(proplist) {
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty*, p, i) {
+            free(p->name);
+        }
+        cxListDestroy(proplist);
+    }
+    
+    return resource;
+}
+
+
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) {
+    // clean resource properties
+    DavResourceData *data = root->data;
+    cxMapClear(data->properties); // TODO: free existing content
+    
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(root));
+     
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    DavResource *resource = root;
+    CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    long error = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        //printf("response\n%s\n", rpbuf->space); 
+        dav_set_effective_href(sn, resource);
+        // TODO: use PropfindParser
+        resource = parse_propfind_response(sn, resource, rpbuf);
+        sn->error = DAV_OK;
+        root->exists = 1;
+    } else  {
+        dav_session_set_error(sn, ret, status);
+        error = 1;
+    }
+    cxBufferFree(rpbuf);
+    return error;
+}
+
+CxList* parse_properties_string(DavContext *context, cxstring str) {
+    CxList *proplist = cxLinkedListCreateSimple(sizeof(DavProperty));
+    
+    CxStrtokCtx tok = cx_strtok(str, cx_str(","), INT_MAX);
+    cxstring s;
+    while(cx_strtok_next(&tok, &s)) {
+        cxstring nsname = cx_strchr(s, ':');
+        if(nsname.length > 0) {
+            cxstring nspre = cx_strsubsl(s, 0, nsname.ptr - s.ptr);
+            nsname.ptr++;
+            nsname.length--;
+            
+            DavProperty dp;
+            cxstring pre = cx_strtrim(nspre);
+            dp.ns = dav_get_namespace_s(context, pre);
+            dp.name = cx_strdup(nsname).ptr;
+            dp.value = NULL;
+            if(dp.ns && dp.name) {
+                cxListAdd(proplist, &dp);
+            } else {
+                free(dp.name);
+            }
+        }
+    }
+    
+    return proplist;
+}
+
+DavResource* dav_query(DavSession *sn, char *query, ...) {
+    DavQLStatement *stmt = dav_parse_statement(cx_str(query));
+    if(!stmt) {
+        sn->error = DAV_ERROR;
+        return NULL;
+    }
+    if(stmt->errorcode != 0) {
+        sn->error = DAV_QL_ERROR;
+        dav_free_statement(stmt);
+        return NULL;
+    }
+    
+    va_list ap;
+    va_start(ap, query);
+    DavResult result = dav_statement_execv(sn, stmt, ap);
+    va_end(ap);
+    
+    dav_free_statement(stmt);
+    
+    if(result.status == -1) {
+        if(result.result) {
+            dav_resource_free(result.result);
+            result.result = NULL;
+        }
+    }
+    
+    return result.result;
+}
+
+
+
+
+void dav_verbose_log(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen)
+{
+    fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status);
+    fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body);
+    fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/webdav.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,414 @@
+/*
+ * 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 WEBDAV_H
+#define	WEBDAV_H
+
+#include <inttypes.h>
+#include <cx/map.h>
+#include <cx/mempool.h>
+#include <cx/linked_list.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <curl/curl.h>
+#include <libxml/tree.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef char DavBool;
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+typedef struct DavContext      DavContext;
+typedef struct DavProxy        DavProxy;
+typedef struct DavSession      DavSession;
+typedef struct DavResource     DavResource;
+typedef struct DavResult       DavResult;
+typedef struct DavNamespace    DavNamespace;
+typedef struct DavProperty     DavProperty;
+typedef struct DavPropName     DavPropName;
+typedef struct DavKey          DavKey;
+typedef struct DavNSInfo       DavNSInfo;
+typedef struct DavXmlNode      DavXmlNode;
+typedef struct DavXmlAttr      DavXmlAttr;
+
+typedef struct DavInputStream  DavInputStream;
+typedef struct DavOutputStream DavOutputStream;
+
+typedef size_t(*dav_read_func)(void*, size_t, size_t, void*);
+typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*);
+typedef int(*dav_seek_func)(const void *, long, int);
+
+typedef int(*dav_auth_func)(DavSession *, void *);
+typedef void(*dav_progress_func)(DavResource *, int64_t, int64_t, void *);
+
+
+typedef void(*dav_rqlog_func)(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen);
+
+enum DavError {
+    DAV_OK = 0,
+    DAV_ERROR,
+    DAV_NOT_FOUND,
+    DAV_UNAUTHORIZED,
+    DAV_FORBIDDEN,
+    DAV_METHOD_NOT_ALLOWED,
+    DAV_CONFLICT,
+    DAV_LOCKED,
+    DAV_UNSUPPORTED_PROTOCOL,
+    DAV_COULDNT_RESOLVE_PROXY,
+    DAV_COULDNT_RESOLVE_HOST,
+    DAV_COULDNT_CONNECT,
+    DAV_TIMEOUT,
+    DAV_SSL_ERROR,
+    DAV_QL_ERROR,
+    DAV_CONTENT_VERIFICATION_ERROR,
+    DAV_PRECONDITION_FAILED,
+    DAV_REQUEST_ENTITY_TOO_LARGE,
+    DAV_REQUEST_URL_TOO_LONG,
+    DAV_PROXY_AUTH_REQUIRED,
+    DAV_NET_AUTH_REQUIRED
+};
+
+typedef enum DavError DavError;
+
+enum DavXmlNodeType {
+    DAV_XML_NONE = 0,
+    DAV_XML_ELEMENT,
+    DAV_XML_TEXT
+};
+
+typedef enum DavXmlNodeType DavXmlNodeType;
+
+#define DAV_SESSION_ENCRYPT_CONTENT     0x0001
+#define DAV_SESSION_ENCRYPT_NAME        0x0002
+#define DAV_SESSION_ENCRYPT_PROPERTIES  0x0004
+#define DAV_SESSION_DECRYPT_CONTENT     0x0008
+#define DAV_SESSION_DECRYPT_NAME        0x0010
+#define DAV_SESSION_DECRYPT_PROPERTIES  0x0020
+#define DAV_SESSION_STORE_HASH          0x0040
+
+#define DAV_SESSION_CONTENT_ENCRYPTION  0x0009
+#define DAV_SESSION_FULL_ENCRYPTION     0x003f
+
+
+#define DAV_NS       "http://davutils.org/"
+#define DAV_PROPS_NS "http://davutils.org/props/"
+
+struct DavNamespace {
+    char *prefix;
+    char *name;
+};
+
+struct DavResource {
+    DavSession    *session;
+    DavResource   *prev;
+    DavResource   *next;
+    DavResource   *parent;
+    DavResource   *children;
+    char          *name;
+    char          *path;
+    char          *href;
+    uint64_t      contentlength;
+    char          *contenttype;
+    time_t        creationdate;
+    time_t        lastmodified;
+    void          *data;
+    int           iscollection;
+    int           exists;
+};
+
+struct DavSession {
+    DavContext    *context;
+    CURL          *handle;
+    char          *base_url;
+    CxMempool     *mp;
+    CxMap         *pathcache;
+    DavKey        *key;
+    void          *locks;
+    uint32_t      flags;
+    DavError      error;
+    char          *errorstr;
+    
+    int(*auth_prompt)(DavSession *sn, void *userdata);
+    void *authprompt_userdata;
+    
+    dav_rqlog_func logfunc;
+    
+    void(*get_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+    void(*put_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+    void *progress_userdata;
+};
+
+struct DavContext {
+    CxMap    *namespaces;
+    CxMap    *namespaceinfo;
+    CxMap    *keys;
+    CxList   *sessions;
+    DavProxy *http_proxy;
+    DavProxy *https_proxy;
+};
+
+struct DavProxy {
+    char *url;
+    char *username;
+    char *password;
+    char *no_proxy;
+};
+
+struct DavProperty {
+    DavNamespace *ns;
+    char         *name;
+    DavXmlNode   *value;
+};
+
+struct DavPropName {
+    char *ns;
+    char *name;
+};
+
+struct DavResult {
+    DavResource *result;
+    int         status;
+};
+
+#define DAV_KEY_AES128     0
+#define DAV_KEY_AES256     1
+
+struct DavKey {
+    char    *name;
+    int     type;
+    void    *data;
+    size_t  length;
+};
+
+struct DavNSInfo {
+    char    *prefix;
+    DavBool encrypt;
+};
+
+struct DavXmlNode {
+    DavXmlNodeType type;
+    
+    char           *namespace;
+    char           *name;
+    
+    DavXmlNode     *prev;
+    DavXmlNode     *next;
+    DavXmlNode     *children;
+    DavXmlNode     *parent;
+    
+    DavXmlAttr     *attributes;
+    
+    char           *content;
+    size_t         contentlength;
+};
+
+struct DavXmlAttr {
+    char *name;
+    char *value;
+    DavXmlAttr *next;
+};
+
+DavContext* dav_context_new(void);
+void dav_context_destroy(DavContext *ctx);
+
+void dav_context_add_key(DavContext *context, DavKey *key);
+DavKey* dav_context_get_key(DavContext *context, const char *name);
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *ns);
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix);
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix);
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt);
+int dav_namespace_is_encrypted(DavContext *context, const char *ns);
+
+DavSession* dav_session_new(DavContext *context, char *base_url);
+DavSession* dav_session_new_auth(
+        DavContext *context,
+        char *base_url,
+        char *user,
+        char *password);
+void dav_session_set_auth(DavSession *sn, char *user, char *password);
+void dav_session_set_baseurl(DavSession *sn, char *base_url);
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags);
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata);
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata);
+
+void dav_session_destroy(DavSession *sn);
+
+void dav_session_destructor(DavSession *sn);
+
+void* dav_session_malloc(DavSession *sn, size_t size);
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size);
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size);
+void  dav_session_free(DavSession *sn, void *ptr);
+char* dav_session_strdup(DavSession *sn, const char *str);
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource);
+DavResource* dav_get(DavSession *sn, char *path, const char *properties);
+
+CxList* parse_properties_string(DavContext *context, cxstring str);
+
+DavResource* dav_query(DavSession *sn, char *query, ...);
+
+cxmutstr dav_property_key(const char *ns, const char *name);
+void dav_get_property_namespace_str(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **ns,
+        char **name);
+DavNamespace* dav_get_property_namespace(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **name);
+
+/* ------------------------ resource functions ------------------------ */
+
+DavResource* dav_resource_new(DavSession *sn, const char *path);
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name);
+DavResource* dav_resource_new_href(DavSession *sn, const char *href);
+
+void dav_resource_free(DavResource *res);
+void dav_resource_free_all(DavResource *res);
+
+char* dav_resource_get_href(DavResource *resource);
+
+DavResource* dav_create_child(DavResource *parent, char *name);
+int dav_delete(DavResource *res);
+int dav_create(DavResource *res);
+int dav_exists(DavResource *res);
+
+int dav_copy(DavResource *res, char *newpath);
+int dav_move(DavResource *res, char *newpath);
+int dav_copy_o(DavResource *res, char *newpath, DavBool override);
+int dav_move_o(DavResource *res, char *newpath, DavBool override);
+int dav_copyto(DavResource *res, char *url, DavBool override);
+int dav_moveto(DavResource *res, char *url, DavBool override);
+
+int dav_lock(DavResource *res);
+int dav_lock_t(DavResource *res, time_t timeout);
+int dav_unlock(DavResource *res);
+
+DavXmlNode* dav_get_property(DavResource *res, char *name);
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name);
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name);
+char* dav_get_string_property(DavResource *res, char *name);
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name);
+void dav_set_string_property(DavResource *res, char *name, char *value);
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value);
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_remove_property(DavResource *res, char *name);
+void dav_remove_property_ns(DavResource *res, char *ns, char *name);
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name);
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count);
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+void dav_set_content_data(DavResource *res, char *content, size_t length);
+void dav_set_content_length(DavResource *res, size_t length);
+
+int dav_load(DavResource *res);
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop);
+int dav_store(DavResource *res);
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_func);
+
+DavInputStream* dav_inputstream_open(DavResource *res);
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in);
+void dav_inputstream_close(DavInputStream *in);
+
+DavOutputStream* dav_outputstream_open(DavResource *res);
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out);
+int dav_outputstream_close(DavOutputStream *out);
+
+void dav_verbose_log(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen);
+
+// private
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf);
+
+
+/* --------------------------- DeltaV ---------------------------- */
+
+int dav_versioncontrol(DavResource *res);
+int dav_checkout(DavResource *res);
+int dav_checkin(DavResource *res);
+int dav_uncheckout(DavResource *res);
+DavResource* dav_versiontree(DavResource *res, char *properties);
+
+/* ------------------------ xml functions ------------------------ */
+char* dav_xml_getstring(DavXmlNode *node);
+DavBool dav_xml_isstring(DavXmlNode *node);
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node);
+DavXmlNode* dav_text_node(DavSession *sn, const char *text);
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text);
+
+DavXmlNode* dav_copy_node(DavXmlNode *node);
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node);
+void dav_free_xml_node(DavXmlNode *node);
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name);
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text);
+DavXmlNode* dav_xml_createtextnode(const char *text);
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child);
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value);
+char* dav_xml_get_attr(DavXmlNode *node, const char *name);
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* WEBDAV_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/xml.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,438 @@
+/*
+ * 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 <cx/utils.h>
+#include <cx/printf.h>
+
+#include "xml.h"
+
+static DavXmlNodeType convert_type(xmlElementType type) {
+    DavXmlNodeType ct;
+    switch(type) {
+        default: ct = DAV_XML_NONE; break;
+        case XML_ELEMENT_NODE: ct = DAV_XML_ELEMENT; break;
+        case XML_TEXT_NODE: ct = DAV_XML_TEXT;
+    }
+    return ct;
+}
+
+typedef struct {
+    xmlNode    *node;
+    DavXmlNode *parent;
+} ConvXmlElm;
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) {
+    if(!node) {
+        return NULL;
+    }
+    DavXmlNodeType newnt = convert_type(node->type);
+    if(newnt == DAV_XML_NONE) {
+        return NULL;
+    }
+    
+    const CxAllocator *a = sn->mp->allocator;
+    
+    ConvXmlElm ce;
+    ce.node = node;
+    ce.parent = NULL;
+    CxList *stack = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(ConvXmlElm));
+    if(!stack) {
+        return NULL;
+    }
+    cxListInsert(stack, 0, &ce);
+    
+    DavXmlNode *ret = NULL;
+    
+    while(stack->size > 0) {
+        ConvXmlElm *c = cxListAt(stack, 0);
+        xmlNode *n = c->node;
+        DavXmlNode *c_parent = c->parent;
+        DavXmlNode *prev = NULL;
+        cxListRemove(stack, 0);
+        while(n) {
+            DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+            if(!ret) {
+                ret = newxn;
+            }
+            newxn->type = convert_type(n->type);
+            newxn->parent = c_parent;
+            if(c_parent && !c_parent->children) {
+                c_parent->children = newxn;
+            }
+            newxn->prev = prev;
+            if(prev) {
+                prev->next = newxn;
+            }
+            
+            if(newxn->type == DAV_XML_ELEMENT) {
+                newxn->name = dav_session_strdup(sn, (char*)n->name);
+                if(n->ns && n->ns->href) {
+                    newxn->namespace = dav_session_strdup(sn, (char*)n->ns->href);
+                }
+                
+                xmlAttr *attr = n->properties;
+                DavXmlAttr *newattr = NULL;
+                DavXmlAttr *newattr_last = NULL;
+                while(attr) {
+                    DavXmlAttr *na = cxCalloc(a, 1, sizeof(DavXmlAttr));
+                    na->name = dav_session_strdup(sn, (char*)attr->name);
+                    if(attr->children && attr->children->type == XML_TEXT_NODE) {
+                        na->value = dav_session_strdup(sn, (char*)attr->children->content);
+                    }
+                    if(!newattr) {
+                        newattr = na;
+                    } else {
+                        newattr_last->next = na;
+                    }
+                    newattr_last = na;
+                    
+                    attr = attr->next;
+                }
+                newxn->attributes = newattr;
+                
+                if(n->children) {
+                    ConvXmlElm convc;
+                    convc.node = n->children;
+                    convc.parent = newxn;
+                    cxListInsert(stack, 0, &convc);
+                }
+            } else if(newxn->type == DAV_XML_TEXT) {
+                cxmutstr content = cx_strdup_a(a, cx_str((char*)n->content));
+                newxn->content = content.ptr;
+                newxn->contentlength = content.length;
+            }
+            
+            prev = newxn;
+            n = n->next;
+        }
+    }
+    
+    return ret;
+}
+
+void dav_print_xml(DavXmlNode *node) {
+    if(node->type == DAV_XML_ELEMENT) {
+        printf("<%s", node->name);
+        DavXmlAttr *attr = node->attributes;
+        while(attr) {
+            printf(" %s=\"%s\"", attr->name, attr->value);
+            attr = attr->next;
+        }
+        putchar('>');
+        
+        DavXmlNode *child = node->children;
+        if(child) {
+            dav_print_xml(child);
+        }
+        
+        printf("</%s>", node->name);
+    } else {
+        fwrite(node->content, 1, node->contentlength, stdout);
+        fflush(stdout);
+    }
+    if(node->next) {
+        dav_print_xml(node->next);
+    }
+}
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node) {
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            char *tagend = node->children ? ">" : " />";          
+            char *prefix = NULL;
+            char *prefix_fr = NULL;
+            if(node->namespace) {
+                prefix = cxMapGet(nsmap, cx_hash_key_str(node->namespace));
+                if(!prefix) {
+                    cxmutstr newpre = cx_asprintf("x%d", (int)nsmap->size+1);
+                    // TODO: fix namespace declaration
+                    //ucx_map_cstr_put(nsmap, node->namespace, newpre.ptr);
+                    prefix = newpre.ptr;
+                    prefix_fr = prefix;
+                    cx_fprintf(
+                            stream,
+                            writef,
+                            "<%s:%s xmlns:%s=\"%s\"",
+                            prefix,
+                            node->name,
+                            prefix,
+                            node->namespace);
+                } else {
+                    cx_fprintf(stream, writef, "<%s:%s", prefix, node->name);
+                }
+            } else {
+                cx_fprintf(stream, writef, "<%s", node->name);
+            }
+            
+            DavXmlAttr *attr = node->attributes;
+            while(attr) {
+                cx_fprintf(stream, writef, " %s=\"%s\"", attr->name, attr->value);
+                attr = attr->next;
+            }
+            writef(tagend, 1, strlen(tagend), stream); // end xml tag
+            
+            if(node->children) {
+                dav_print_node(stream, writef, nsmap, node->children);
+                if(prefix) {
+                    cx_fprintf(stream, writef, "</%s:%s>", prefix, node->name);
+                } else {
+                    cx_fprintf(stream, writef, "</%s>", node->name);
+                }
+            }
+            
+            if(prefix_fr) {
+                free(prefix_fr);
+            }
+        } else if(node->type == DAV_XML_TEXT) {
+            writef(node->content, 1, node->contentlength, stream);
+        }
+        
+        node = node->next;
+    }
+}
+
+/* ------------------------- public API ------------------------- */
+
+char* dav_xml_getstring(DavXmlNode *node) {
+    if(node && node->type == DAV_XML_TEXT) {
+        return node->content;
+    } else {
+        return NULL;
+    }
+}
+
+DavBool dav_xml_isstring(DavXmlNode *node) {
+    if(node && node->type == DAV_XML_TEXT && !node->next) {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node) {
+    node = node->next;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            return node;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+DavXmlNode* dav_text_node(DavSession *sn, const char *text) {
+    const CxAllocator *a = sn->mp->allocator; 
+    DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+    newxn->type = DAV_XML_TEXT;
+    cxmutstr content = cx_strdup_a(a, cx_str(text));
+    newxn->content = content.ptr;
+    newxn->contentlength = content.length;
+    return newxn;
+}
+
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text) {
+    const CxAllocator *a = sn->mp->allocator; 
+    DavXmlNode *newelm = cxCalloc(a, 1, sizeof(DavXmlNode));
+    newelm->type = DAV_XML_ELEMENT;
+    newelm->namespace = cx_strdup_a(a, cx_str(ns)).ptr;
+    newelm->name = cx_strdup_a(a, cx_str(name)).ptr;
+    newelm->children = dav_text_node(sn, text);
+    return newelm;
+}
+
+static void dav_free_xml_node_a(const CxAllocator *a, DavXmlNode *node) {
+    if(node->name) cxFree(a, node->name);
+    if(node->namespace) cxFree(a, node->namespace);
+    if(node->content) cxFree(a, node->content);
+    DavXmlAttr *attr = node->attributes;
+    while(attr) {
+        if(attr->name) cxFree(a, attr->name);
+        if(attr->value) cxFree(a, attr->value);
+        attr = attr->next;
+    }
+    DavXmlNode *children = node->children;
+    while(children) {
+        DavXmlNode *next_ch = children->next;
+        dav_free_xml_node_a(a, children);
+        children = next_ch;
+    }
+    cxFree(a, node);
+}
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node) {
+    dav_free_xml_node_a(sn->mp->allocator, node);
+}
+
+void dav_free_xml_node(DavXmlNode *node) {
+    dav_free_xml_node_a(cxDefaultAllocator, node);
+}
+
+DavXmlAttr* dav_copy_xml_attr(DavXmlAttr *attr) {
+    if(!attr) {
+        return NULL;
+    }
+    DavXmlAttr *newattr = NULL;
+    DavXmlAttr *prev = NULL;
+    while(attr) {
+        DavXmlAttr *n = calloc(1, sizeof(DavXmlAttr));
+        n->name = strdup(attr->name);
+        n->value = strdup(attr->value);
+        if(prev) {
+            prev->next = n;
+        } else {
+            newattr = n;
+        }
+        prev = n;
+        attr = attr->next;
+    }
+    return newattr;
+}
+
+DavXmlNode* dav_copy_node(DavXmlNode *node) {
+    DavXmlNode *ret = NULL;
+    DavXmlNode *prev = NULL;
+    while(node) {
+        DavXmlNode *copy = calloc(1, sizeof(DavXmlNode));
+        copy->type = node->type;
+        if(node->type == DAV_XML_ELEMENT) {
+            copy->namespace = strdup(node->namespace);
+            copy->name = strdup(node->name);
+            copy->children = dav_copy_node(node->children);
+            copy->attributes = dav_copy_xml_attr(node->attributes);
+        } else {
+            copy->contentlength = node->contentlength;
+            copy->content = malloc(node->contentlength+1);
+            memcpy(copy->content, node->content, node->contentlength);
+            copy->content[copy->contentlength] = 0;
+        }
+        if(!ret) {
+            ret = copy;
+        }
+        if(prev) {
+            prev->next = copy;
+            copy->prev = prev;
+        }
+        prev = copy;
+        node = node->next;
+    }
+    return ret;
+}
+
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    return node;
+}
+
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    
+    DavXmlNode *textnode = dav_xml_createtextnode(text);
+    node->children = textnode;
+    
+    return node;
+}
+
+DavXmlNode* dav_xml_createtextnode(const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_TEXT;
+    cxmutstr content = cx_strdup(cx_str((char*)text));
+    node->content = content.ptr;
+    node->contentlength = content.length;
+    return node;
+}
+
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child) {
+    DavXmlNode *last_child = NULL;
+    DavXmlNode *c = node->children;
+    while(c) {
+        last_child = c;
+        c = c->next;
+    }
+    if(last_child) {
+        last_child->next = child;
+        child->prev = last_child;
+    } else {
+        node->children = child;
+    }
+}
+
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value) {
+    DavXmlAttr *attr = calloc(1, sizeof(DavXmlAttr));
+    attr->name = strdup(name);
+    attr->value = strdup(value);
+    
+    if(node->attributes) { 
+        DavXmlAttr *end = node->attributes;
+        DavXmlAttr* last = end;
+        while(end) {
+            last = end;
+            end = end->next;
+        }
+        last->next = attr;
+    } else {
+        node->attributes = attr;
+    }
+}
+
+char* dav_xml_get_attr(DavXmlNode *node, const char *name) {
+    DavXmlAttr *attr = node->attributes;
+    while(attr) {
+        if(!strcmp(attr->name, name)) {
+            return attr->value;
+        }
+        
+        attr = attr->next;
+    }
+    return NULL;
+}
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len) {
+    xmlDoc *doc = xmlReadMemory(str, len, NULL, NULL, 0);
+    if(!doc) {
+        return NULL;
+    }
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    if(!xml_root) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    DavXmlNode *x = dav_convert_xml(sn, xml_root);
+    xmlFreeDoc(doc);
+    return x;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/xml.h	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,49 @@
+/*
+ * 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_XML_H
+#define DAV_XML_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node);
+
+void dav_print_xml(DavXmlNode *node);
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_XML_H */
+
--- a/make/vs/idav.sln	Sun Jan 21 16:30:18 2024 +0100
+++ b/make/vs/idav.sln	Mon Jan 22 17:27:47 2024 +0100
@@ -5,6 +5,8 @@
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libidav", "libidav\libidav.vcxproj", "{C29C0378-6548-48E8-9426-31922515212A}"
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "uicommon", "uicommon\uicommon.vcxproj", "{8B88698E-C185-4383-99FE-0C34D6DEED2E}"
@@ -33,6 +35,18 @@
 		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64
 		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32
 		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.ActiveCfg = Debug|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.Build.0 = Debug|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.ActiveCfg = Debug|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.Build.0 = Debug|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.ActiveCfg = Debug|Win32
+		{C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.Build.0 = Debug|Win32
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.ActiveCfg = Release|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.Build.0 = Release|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|x64.ActiveCfg = Release|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|x64.Build.0 = Release|x64
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|x86.ActiveCfg = Release|Win32
+		{C29C0378-6548-48E8-9426-31922515212A}.Release|x86.Build.0 = Release|Win32
 		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64
 		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64
 		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64
--- a/make/vs/idav/idav.vcxproj	Sun Jan 21 16:30:18 2024 +0100
+++ b/make/vs/idav/idav.vcxproj	Mon Jan 22 17:27:47 2024 +0100
@@ -115,7 +115,7 @@
       <SDLCheck>true</SDLCheck>
       <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ConformanceMode>true</ConformanceMode>
-      <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
--- a/make/vs/idav/idav.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
+++ b/make/vs/idav/idav.vcxproj.filters	Mon Jan 22 17:27:47 2024 +0100
@@ -14,4 +14,23 @@
       <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
     </Filter>
   </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="main.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
--- a/make/vs/idav/main.c	Sun Jan 21 16:30:18 2024 +0100
+++ b/make/vs/idav/main.c	Mon Jan 22 17:27:47 2024 +0100
@@ -2,7 +2,7 @@
 #include <Windows.h>
 #endif
 
-
+#include <ui/ui.h>
 
 int idav_main(void) {
 	return 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/libidav/libidav.vcxproj	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{c29c0378-6548-48e8-9426-31922515212a}</ProjectGuid>
+    <RootNamespace>libidav</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <TargetExt>.dll</TargetExt>
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Label="Vcpkg">
+    <VcpkgEnableManifest>true</VcpkgEnableManifest>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <LanguageStandard_C>stdc11</LanguageStandard_C>
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include</AdditionalIncludeDirectories>
+      <AdditionalOptions>
+      </AdditionalOptions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>..\vcpkg_installed\x64-windows\x64-windows\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>charset.lib;iconv.lib;libcurl.lib;libxml2.lib;lzma.lib;zlib.lib;bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <PostBuildEvent>
+      <Command>xcopy /y $(SolutionDir)vcpkg_installed\x64-windows\x64-windows\bin\*.dll $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</Command>
+    </PostBuildEvent>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\libidav\crypto.c" />
+    <ClCompile Include="..\..\..\libidav\davqlexec.c" />
+    <ClCompile Include="..\..\..\libidav\davqlparser.c" />
+    <ClCompile Include="..\..\..\libidav\methods.c" />
+    <ClCompile Include="..\..\..\libidav\resource.c" />
+    <ClCompile Include="..\..\..\libidav\session.c" />
+    <ClCompile Include="..\..\..\libidav\utils.c" />
+    <ClCompile Include="..\..\..\libidav\versioning.c" />
+    <ClCompile Include="..\..\..\libidav\webdav.c" />
+    <ClCompile Include="..\..\..\libidav\xml.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\libidav\crypto.h" />
+    <ClInclude Include="..\..\..\libidav\davqlexec.h" />
+    <ClInclude Include="..\..\..\libidav\davqlparser.h" />
+    <ClInclude Include="..\..\..\libidav\methods.h" />
+    <ClInclude Include="..\..\..\libidav\resource.h" />
+    <ClInclude Include="..\..\..\libidav\session.h" />
+    <ClInclude Include="..\..\..\libidav\utils.h" />
+    <ClInclude Include="..\..\..\libidav\versioning.h" />
+    <ClInclude Include="..\..\..\libidav\webdav.h" />
+    <ClInclude Include="..\..\..\libidav\xml.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/libidav/libidav.vcxproj.filters	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\libidav\crypto.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\davqlexec.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\davqlparser.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\methods.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\resource.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\session.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\utils.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\versioning.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\webdav.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\libidav\xml.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\libidav\crypto.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\davqlexec.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\davqlparser.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\methods.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\resource.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\session.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\utils.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\versioning.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\webdav.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\libidav\xml.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/vcpkg.json	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,10 @@
+{
+  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+  "name": "libidav",
+  "dependencies": [
+    "libxml2",
+    "curl",
+    "pcre"
+  ],
+  "builtin-baseline": "2c401863dd54a640aeb26ed736c55489c079323b"
+}

mercurial