libidav/davqlexec.c

Sun, 17 Dec 2023 15:33:50 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 15:33:50 +0100
changeset 800
30d484806c2b
parent 786
f9d6e4cbcb62
child 816
839fefbdedc7
permissions
-rw-r--r--

fix faulty string to int conversion utilities

Probably it was expected that errno is set to EINVAL when illegal characters are encountered. But this is not standard and does not happen on every system, allowing illegal strings to be parsed as valid integers.

/*
 * 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/basic_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 = cxBasicMempoolCreate(128);
    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;
}

mercurial