--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/davql.c Mon Aug 26 14:42:09 2013 +0200 @@ -0,0 +1,702 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 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 "davql.h" +#include "methods.h" +#include "utils.h" + +DavQuery dav_ql_parse(char *query, va_list ap) { + DavQuery davquery; + davquery.command = DAV_QUERY_ERROR; + davquery.command_data = NULL; + sstr_t q = sstr(query); + q = sstrtrim(q); + + // get query command + sstr_t cmd; + cmd.ptr = NULL; + int i; + for(i=0;i<q.length;i++) { + if(q.ptr[i] == ' ') { + cmd = sstrsubsl(q, 0, i); + break; + } + } + if(!cmd.ptr) { + fprintf(stderr, "DQL syntax error\n"); + return davquery; + } + + cmd = sstrtrim(cmd); + q = sstrtrim(sstrsubs(q, i)); + if(!sstrcmp(cmd, S("get"))) { + davquery.command = DAV_QUERY_GET; + davquery.command_data = dav_ql_parse_get(q, ap); + } + + return davquery; +} + +DavGetQuery* dav_ql_parse_get(sstr_t q, va_list ap) { + sstr_t property_query = q; + q = util_getsubstr_until_token(q, S("from"), &property_query); + + sstr_t from_query = q; + sstr_t cond = util_getsubstr_until_token(q, S("where"), &from_query); + + // insert variable values + UcxBuffer *fbuf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); + int var = 0; + for(int i=0;i<from_query.length;i++) { + char c = from_query.ptr[i]; + if(c == '%') { + if(var) { + ucx_buffer_putc(fbuf, '%'); // previous '%' + } else { + var = 1; + } + } else if(var) { + switch(c) { + case 's': { + char *arg = va_arg(ap, char*); + ucx_buffer_puts(fbuf, arg); + break; + } + default: { + ucx_buffer_putc(fbuf, '%'); + ucx_buffer_putc(fbuf, c); + } + } + var = 0; + } else { + ucx_buffer_putc(fbuf, c); + } + } + + // condition + DavQOp *condition = NULL; + size_t oplen = 0; + if(cond.ptr) { + //printf("cond: {%.*s}\n", cond.length, cond.ptr); + UcxList *ops = NULL; + if(dav_parse_condition(&ops, cond, ap)) { + // TODO: error + printf("parse error\n"); + return NULL; + } + oplen = ucx_list_size(ops); + condition = calloc(sizeof(DavQOp), oplen); + int l = 0; + UCX_FOREACH(elm, ops) { + condition[l] = *(DavQOp*)elm->data; + free(elm->data); + l++; + } + ucx_list_free(ops); + } + + DavGetQuery *getquery = malloc(sizeof(DavGetQuery)); + getquery->properties = sstrdup(property_query); + getquery->from = sstrn(fbuf->space, fbuf->pos); + if(condition) { + getquery->condition = condition; + getquery->condlen = oplen; + } else { + getquery->condition = NULL; + getquery->condlen = 0; + } + return getquery; +} + +void free_get_query(DavGetQuery *q) { + free(q->from.ptr); + free(q->properties.ptr); + free(q); +} + +int parse_path_query(sstr_t query, char **path, int *depth) { + if(query.length == 1) { + if(query.ptr[0] == '/') { + *path = sstrdup(query).ptr; + *depth = 1; + return 0; + } else { + *path = NULL; + return 1; + } + } + + if(query.ptr[query.length-1] == '*') { + *depth = -1; + *path = sstrdup(sstrsubsl(query, 0, query.length-1)).ptr; + } else { + *path = sstrdup(query).ptr; + *depth = 1; + } + + return 0; +} + +int dav_parse_condition(UcxList **ops, sstr_t cond, va_list ap) { + sstr_t token; + DavQOp *op1 = NULL; // level 1 operator + DavQOp *op2 = NULL; // level 2 operator + DavQOp *op3 = NULL; // level 3 operator + while((token = condition_parser_next_token(&cond)).length > 0) { + //printf("token: {%.*s}[%d]\n", token.length, token.ptr, token.length); + int64_t type = 0; + int tkop = condition_operator_type(token, &type); + DavQOp *operation = malloc(sizeof(DavQOp)); + if(tkop > 0) { + // operator token + operation->type = DAVQOP_OPERATOR; + operation->val = NULL; + operation->intval = type; + switch(tkop) { + case 1: { + // operators: + - / * not + // add operation after next non operator token + op1 = operation; + break; + } + case 2: { + // operators: < > == != <= >= + if(op2) { + *ops = ucx_list_append(*ops, op2); + } + op2 = operation; + break; + } + case 3: { + // operators: and or xor + if(op2) { + *ops = ucx_list_append(*ops, op2); + op2 = NULL; + } + if(op3) { + *ops = ucx_list_append(*ops, op3); + } + op3 = operation; + break; + } + } + } else { + if(token.ptr[0] == '"' || token.ptr[0] == '\'') { + operation->type = DAVQOP_STRING; + operation->val = token.ptr+1; + operation->intval = token.length-2; + } else if(!sstrcmp(token, S("true")) || + !sstrcmp(token, S("false"))) + { + operation->type = DAVQOP_INTEGER; + operation->val = NULL; + operation->intval = util_getboolean(token.ptr); + } else if(token.length == 2 && token.ptr[0] == '%') { + switch(token.ptr[1]) { + case 's': { + char *arg = va_arg(ap, char*); + operation->type = DAVQOP_STRING; + operation->val = arg; + operation->intval = strlen(arg); + break; + } + case 'd': { + operation->type = DAVQOP_INTEGER; + operation->val = NULL; + operation->intval = va_arg(ap, int); + break; + } + case 't': { + operation->type = DAVQOP_INTEGER; + operation->val = NULL; + operation->intval = va_arg(ap, time_t); + break; + } + default: { + operation->type = DAVQOP_STRING; + operation->val = token.ptr; + operation->intval = token.length; + } + } + } else { + sstr_t d = sstrdup(token); + int64_t val = 0; + int intval = util_strtoint(d.ptr, &val); + free(d.ptr); + if(intval) { + operation->type = DAVQOP_INTEGER; + operation->val = NULL; + operation->intval = val; + } else { + if(!sstrcmp(token, S("contentlength"))) { + operation->type = DAVQOP_RESPROP; + } else if(!sstrcmp(token, S("lastmodified"))) { + operation->type = DAVQOP_RESPROP; + } else if(!sstrcmp(token, S("creationdate"))) { + operation->type = DAVQOP_RESPROP; + } else if(!sstrcmp(token, S("name"))) { + operation->type = DAVQOP_RESPROP; + } else if(!sstrcmp(token, S("path"))) { + operation->type = DAVQOP_RESPROP; + } else if(!sstrcmp(token, S("iscollection"))) { + operation->type = DAVQOP_RESPROP; + } else { + operation->type = DAVQOP_PROPERTY; + } + operation->val = token.ptr; + operation->intval = token.length; + } + } + + // add operation + *ops = ucx_list_append(*ops, operation); + if(op1) { + // add level 1 operator + *ops = ucx_list_append(*ops, op1); + op1 = NULL; + } + } + } + if(op1) { + *ops = ucx_list_append(*ops, op1); + } + if(op2) { + *ops = ucx_list_append(*ops, op2); + } + if(op3) { + *ops = ucx_list_append(*ops, op3); + } + return 0; +} + +sstr_t condition_parser_next_token(sstr_t *str) { + sstr_t s = *str; + sstr_t t; + t.ptr = NULL; + t.length = 0; + // remove leading space + int i; + for(i=0;i<s.length;i++) { + if(s.ptr[i] > 32) { + break; + } + } + s.length -= i; + s.ptr += i; + + if(s.length == 0) { + *str = s; + return t; + } + + // check for single char operators + switch(s.ptr[0]) { + case '<': + case '>': + case '+': + case '-': + case '*': + case '/': { + t.ptr = s.ptr; + t.length = 1; + str->ptr = s.ptr + 1; + str->length = s.length - 1; + return t; + } + } + + if(s.length > 1) { + // check for double char operators + int16_t op = *(int16_t*)s.ptr; + if(op == '==' || op == '!=' || op == '>=' || op == '=<') { + t.ptr = s.ptr; + t.length = 2; + str->ptr = s.ptr + 2; + str->length = s.length - 2; + return t; + } + } else { + t.ptr = s.ptr; + t.length = 1; + str->ptr = s.ptr + 1; + str->length = s.length - 1; + return t; + } + + // TODO: brackets + + // check for string literal + if(s.ptr[0] == '\'' || s.ptr[0] == '"') { + for(i=1;i<s.length;i++) { + if(s.ptr[0] == s.ptr[i]) { + i++; + break; + } + } + t.ptr = s.ptr; + t.length = i; + str->ptr = s.ptr + i; + str->length = s.length - i; + return t; + } + + for(i=0;i<s.length;i++) { + char c = s.ptr[i]; + if((c < 33) || (c > 41 && c < 48) || (c > 59 && c < 63)) { + break; + } + } + t.ptr = s.ptr; + t.length = i; + str->ptr = s.ptr + i; + str->length = s.length - i; + return t; +} + +int condition_operator_type(sstr_t token, int64_t *type) { + // returns the operator level and sets the type + + if(token.ptr[0] == '"' || token.ptr[0] == '\'' || token.ptr[0] == '(') { + return 0; + } + + if(token.length == 1) { + switch(token.ptr[0]) { + case '+': *type = 1; return 1; + case '-': *type = 2; return 1; + case '*': *type = 3; return 1; + case '/': *type = 4; return 1; + case '<': *type = 5; return 2; + case '>': *type = 6; return 2; + } + } + if(!sstrcmp(token, S("not"))) { + *type = 0; + return 1; + } + + if(!sstrcmp(token, S("=="))) { + *type = 7; + return 2; + } + if(!sstrcmp(token, S("!="))) { + *type = 8; + return 2; + } + if(!sstrcmp(token, S("<="))) { + *type = 9; + return 2; + } + if(!sstrcmp(token, S(">="))) { + *type = 10; + return 2; + } + + if(!sstrcmp(token, S("and"))) { + *type = 11; + return 3; + } + if(!sstrcmp(token, S("or"))) { + *type = 12; + return 3; + } + if(!sstrcmp(token, S("xor"))) { + *type = 13; + return 3; + } + + return 0; +} + +int condition_eval(DavResource *res, DavQOp *cond, size_t len) { + DavQOp stack[128]; + int stackpos = 0; + for(int i=0;i<len;i++) { + DavQOp op = cond[i]; + switch(op.type) { + case DAVQOP_OPERATOR: { + if(op.intval == 0) { + // not operator + if(stackpos < 1) { + // error + printf("no data on stack\n"); + return 0; + } + int pos = stackpos-1; + if(stack[pos].type == DAVQOP_INTEGER) { + //printf("not %" PRId64 "\n", stack[pos].intval); + stack[pos].intval = !stack[pos].intval; + } else { + // error + printf("wrong value for 'not' operator\n"); + return 0; + } + } else { + DavQOp val1 = stack[stackpos-2]; + DavQOp val2 = stack[stackpos-1]; + DavQOp result; + if(val1.type == DAVQOP_INTEGER) { + if(val2.type == DAVQOP_INTEGER) { + result = compare_intint( + op.intval, + val1.intval, + val2.intval); + } else { + result = compare_intstr(op.intval, val1, val2); + } + } else { + if(val2.type == DAVQOP_INTEGER) { + result = compare_strint(op.intval, val1, val2); + } else { + result = compare_strstr(op.intval, val1, val2); + } + } + stack[stackpos-2] = result; + stackpos--; + } + break; + } + case DAVQOP_STRING: + case DAVQOP_INTEGER: + case DAVQOP_TIME: { + if(op.type == DAVQOP_STRING) { + //printf("put on stack: '%s'\n", op.val); + } else { + //printf("put on stack[%d]: %" PRId64 "\n", stackpos, op.intval); + } + stack[stackpos++] = op; + break; + } + case DAVQOP_PROPERTY: { + sstr_t pname = sstrn(op.val, op.intval); + pname = sstrdup(pname); + char *property_value = dav_get_property(res, pname.ptr); + free(pname.ptr); + DavQOp value; + value.type = DAVQOP_STRING; + if(property_value) { + //printf("put on stack: \"%s\"\n", property_value); + value.val = property_value; + value.intval = strlen(property_value); + } else { + //printf("put on stack: null string\n"); + value.val = NULL; + value.intval = 0; + } + stack[stackpos++] = value; + break; + } + case DAVQOP_RESPROP: { + sstr_t name = sstrn(op.val, op.intval); + DavQOp value; + value.type = DAVQOP_INTEGER; + value.val = NULL; + if(!sstrcmp(name, S("contentlength"))) { + //printf("put contentlength\n"); + value.intval = res->contentlength; + } else if(!sstrcmp(name, S("lastmodified"))) { + //printf("put getlastmodified\n"); + value.intval = res->lastmodified; + } else if(!sstrcmp(name, S("creationdate"))) { + value.intval = res->creationdate; + } else if(!sstrcmp(name, S("name"))) { + value.type = DAVQOP_STRING; + value.val = res->name; + value.intval = strlen(res->name); + } else if(!sstrcmp(name, S("path"))) { + value.type = DAVQOP_STRING; + value.val = res->path; + value.intval = strlen(res->path); + } else if(!sstrcmp(name, S("iscollection"))) { + value.type = DAVQOP_INTEGER; + value.val = NULL; + value.intval = res->iscollection; + } + stack[stackpos++] = value; + break; + } + } + } + if(stackpos != 1) { + return 0; + } + DavQOp result = stack[0]; + //printf("result: %" PRId64 "\n", result.intval); + return (int)result.intval; +} + +DavQOp compare_intint(int op, int64_t v1, int64_t v2) { + DavQOp res; + res.type = DAVQOP_INTEGER; + res.val = NULL; + res.intval = 0; + switch(op) { + case 5: { + // < + //printf("compare: %" PRId64 " < %" PRId64 "\n", v1, v2); + res.intval = v1 < v2; + break; + } + case 6: { + // > + //printf("compare: %" PRId64 " > %" PRId64 "\n", v1, v2); + res.intval = v1 > v2; + break; + } + case 7: { + // == + //printf("compare: %" PRId64 " == %" PRId64 "\n", v1, v2); + res.intval = v1 == v2; + break; + } + case 8: { + // != + //printf("compare: %" PRId64 " != %" PRId64 "\n", v1, v2); + res.intval = v1 != v2; + break; + } + case 9: { + // <= + //printf("compare: %" PRId64 " <= %" PRId64 "\n", v1, v2); + res.intval = v1 <= v2; + break; + } + case 10: { + // >= + //printf("compare: %" PRId64 " >= %" PRId64 "\n", v1, v2); + res.intval = v1 >= v2; + break; + } + case 11: { + // and + //printf("compare: %" PRId64 " and %" PRId64 "\n", v1, v2); + res.intval = v1 && v2; + break; + } + case 12: { + // or + //printf("compare: %" PRId64 " or %" PRId64 "\n", v1, v2); + res.intval = v1 || v2; + break; + } + case 13: { + // xor + //printf("compare: %" PRId64 " xor %" PRId64 "\n", v1, v2); + res.intval = v1 ^ v2; + break; + } + } + return res; +} + +DavQOp compare_strint(int op, DavQOp v1, DavQOp v2) { + int64_t v1int; + sstr_t s1 = sstrn(v1.val, v1.intval); + s1 = sstrdup(s1); + if(util_strtoint(s1.ptr, &v1int)) { + free(s1.ptr); + return compare_intint(op, v1int, v2.intval); + } else { + free(s1.ptr); + // TODO + } +} + +DavQOp compare_intstr(int op, DavQOp v1, DavQOp v2) { + // TODO +} + +DavQOp compare_strstr(int op, DavQOp v1, DavQOp v2) { + DavQOp res; + res.type = DAVQOP_INTEGER; + res.val = NULL; + res.intval = 0; + sstr_t s1 = sstrn(v1.val, v1.intval); + sstr_t s2 = sstrn(v2.val, v2.intval); + switch(op) { + case 5: { + // < + //printf("str compare: %.*s < %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.length < s2.length; + break; + } + case 6: { + // > + //printf("str compare: %.*s > %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.length > s2.length; + break; + } + case 7: { + // == + //printf("str compare: %.*s == %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = sstrcmp(s1, s2) == 0; + break; + } + case 8: { + // != + //printf("str compare: %.*s != %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = sstrcmp(s1, s2) != 0; + break; + } + case 9: { + // <= + //printf("str compare: %.*s <= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.length <= s2.length; + break; + } + case 10: { + // >= + //printf("str compare: %.*s >= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.length >= s2.length; + break; + } + case 11: { + // and + //printf("str compare: %.*s and %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.ptr && s2.ptr; + break; + } + case 12: { + // or + //printf("str compare: %.*s or %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = s1.ptr || s2.ptr; + break; + } + case 13: { + // xor + //printf("str compare: %.*s xor %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr); + res.intval = (intptr_t)s1.ptr ^ (intptr_t)s2.ptr; + break; + } + } + return res; +}