diff -r 2483f517c562 -r b5bb7b3cd597 libidav/davqlexec.c --- /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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#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;itype == 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;iname); + 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; +}