Mon, 22 Jan 2024 17:27:47 +0100
add libidav code
--- 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" +}