--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/config/serverconfig.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,361 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 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 "serverconfig.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <ucx/buffer.h> +#include <ucx/utils.h> + +ServerConfig* serverconfig_load(const char *file) { + FILE *in = fopen(file, "r"); + if(in == NULL) { + return NULL; + } + + UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + if(!buf) { + fclose(in); + return NULL; + } + + ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write); + fclose(in); + + ServerConfig *scfg = serverconfig_parse(scstrn(buf->space, buf->size)); + + ucx_buffer_free(buf); + return scfg; +} + + +static CFGToken get_next_token(scstr_t content, int *pos) { + CFGToken token = { {NULL, 0}, CFG_NO_TOKEN }; + CFGTokenType type = CFG_TOKEN; + + int start = *pos; + + int token_begin = -1; + int token_end = content.length-1; + + int quote = 0; + int comment = 0; + + int i; + char prev = 0; + for(i=start;i<content.length;i++) { + char c = content.ptr[i]; + if(c == '\n') { + if(quote) { + *pos = i; + return token; // error + } else if(start == i) { + // single newline char token + type = CFG_TOKEN_NEWLINE; + token_begin = i; + token_end = i+1; + break; + } + + token_end = i; + if(token_begin < 0) { + // only space/comment token + token_begin = start; + type = comment ? CFG_TOKEN_COMMENT : CFG_TOKEN_SPACE; + } + // make sure next run will return current newline char as token + i--; + break; + } else if(quote) { + if(c == '"' && prev != '\\') { + quote = 0; + } + } else if(comment) { + // ignore + if(c == '\n') { + comment = 0; + } + } else if(c == '#') { + comment = 1; + } else if(isspace(c)) { + if(token_begin >= 0) { + token_end = i; + break; + } + } else if(c == '"') { + quote = 1; + if(token_begin < 0) { + token_begin = i; + } + } else if(token_begin < 0) { + token_begin = i; + } + prev = c; + } + + *pos = i + 1; + + if(token_begin < 0) { + return token; // error + } + + token.type = type; + token.content = scstrsubsl(content, token_begin, token_end - token_begin); + return token; +} + +/* +static void test_print_config(ConfigNode *parent) { + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + + if(node->type == CONFIG_NODE_SPACE) { + printf("sp: %s", node->text_begin.ptr); + } else if(node->type == CONFIG_NODE_COMMENT) { + printf("cm: %s", node->text_begin.ptr); + } else if(node->type == CONFIG_NODE_OBJECT) { + printf("o{: %s : %s", node->name.ptr, node->text_begin.ptr); + test_print_config(node); + printf("o}: %s", node->text_end.ptr); + } else if(node->type == CONFIG_NODE_DIRECTIVE) { + printf("di: %s", node->text_begin.ptr); + } else { + printf("fk: %s", node->text_begin.ptr); + } + } +} +*/ + +static void config_arg_set_value(UcxAllocator *a, ConfigArg *arg, CFGToken token) { + scstr_t nv = scstrchr(token.content, '='); + if(!nv.ptr) { + arg->value = sstrdup_a(a, token.content); + } else { + intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr); + scstr_t name = token.content; + name.length = (size_t)eq; + + scstr_t value = nv; + value.ptr++; + value.length--; + if(value.length > 1 && value.ptr[0] == '"' && value.ptr[value.length-1] == '"') { + value.ptr++; + value.length -= 2; // remove quote + } + + arg->name = sstrdup_a(a, name); + arg->value = sstrdup_a(a, value); + } +} + +ServerConfig* serverconfig_parse(scstr_t content) { + UcxMempool *mp = ucx_mempool_new(512); + if(!mp) return NULL; + UcxAllocator *a = mp->allocator; + + ServerConfig *config = ucx_mempool_malloc(mp, sizeof(ServerConfig)); + if(!config) { + ucx_mempool_destroy(mp); + return NULL; + } + config->mp = mp; + + // PARSE: + // first non space/comment token is directive/object name + // following tokens are arguments + // newline starts new directive + // '{' converts directive to object and following directives will + // be placed into the object + int pos = 0; // needed for tokenizer + CFGToken token; + + ConfigNode *root_obj = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + root_obj->type = CONFIG_NODE_OBJECT; + + UcxList *node_stack = ucx_list_prepend(NULL, root_obj); + + ConfigNode *current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + current->type = CONFIG_NODE_SPACE; + ConfigNode *obj = NULL; + int obj_closed = 0; + + int text_start = 0; + int err = 0; + while((token = get_next_token(content, &pos)).type != CFG_NO_TOKEN) { + //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr); + + switch(token.type) { + case CFG_NO_TOKEN: break; + case CFG_TOKEN_COMMENT: { + if(current->type == CONFIG_NODE_SPACE) { + current->type = CONFIG_NODE_COMMENT; + } + break; + } + case CFG_TOKEN_SPACE: break; + case CFG_TOKEN_NEWLINE: { + scstr_t line = scstrsubsl(content, text_start, pos - text_start); + text_start = pos; + + sstr_t line_cp = sstrdup_a(a, line); + + ConfigNode *parent = node_stack->data; + if(current->type == CONFIG_NODE_CLOSE_OBJECT) { + parent->text_end = line_cp; + node_stack = ucx_list_remove_a(a, node_stack, node_stack); + } else if(current->type == CONFIG_NODE_OPEN_OBJECT) { + sstr_t new_textbegin = sstrcat_a(a, 2, obj->text_begin, line_cp); + alfree(a, obj->text_begin.ptr); + alfree(a, line_cp.ptr); + obj->text_begin = new_textbegin; + } else { + current->text_begin = line_cp; + ConfigNode *parent = node_stack->data; + parent->children = ucx_list_append_a(a, parent->children, current); + } + + if(obj && obj->type == CONFIG_NODE_OBJECT) { + node_stack = ucx_list_prepend_a(a, node_stack, obj); + obj = NULL; + } + + current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + current->type = CONFIG_NODE_SPACE; + + obj_closed = 0; + break; + } + case CFG_TOKEN: { + if(!sstrcmp(token.content, S("{"))) { + if(!obj) { + err = 1; + break; + } + obj->type = CONFIG_NODE_OBJECT; + if(current != obj) { + current->type = CONFIG_NODE_OPEN_OBJECT; + } + } else if(!sstrcmp(token.content, S("}"))) { + obj_closed = 1; // force newline before next directive + obj = NULL; + current->type = CONFIG_NODE_CLOSE_OBJECT; + } else { + if(obj_closed) { + err = 1; + break; + } + + if(!current->name.ptr) { + current->name = sstrdup_a(a, token.content); + current->type = CONFIG_NODE_DIRECTIVE; + obj = current; + } else { + ConfigArg *arg = ucx_mempool_calloc(mp, 1, sizeof(ConfigArg)); + config_arg_set_value(a, arg, token); + current->args = ucx_list_append_a(a, current->args, arg); + } + } + break; + } + } + + if(err) { + break; + } + } + + if(pos < content.length || err) { + // content not fully parsed because of an error + ucx_mempool_destroy(mp); + return NULL; + } + + //test_print_config(&root_obj); + config->root = root_obj; + config->tab = sstrdup_a(a, SC("\t")); + + return config; +} + +void serverconfig_free(ServerConfig *cfg) { + ucx_mempool_destroy(cfg->mp); +} + +ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name) { + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + if(node->type == type && !sstrcasecmp(node->name, name)) { + return node; + } + } + return NULL; +} + +UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name) { + UcxList *nodes = NULL; + + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + if(node->type == type && !sstrcasecmp(node->name, name)) { + nodes = ucx_list_append(nodes, node); + } + } + + return nodes; +} + +scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name) { + ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name); + if(node && ucx_list_size(node->args) == 1) { + ConfigArg *arg = node->args->data; + return SCSTR(arg->value); + } + return scstrn(NULL, 0); +} + +sstr_t serverconfig_arg_name_value(UcxAllocator *a, scstr_t str, scstr_t *name) { + int valstart = 0; + for(int i=0;i<str.length;i++) { + if(str.ptr[i] == '=') { + if(name) { + name->ptr = str.ptr; + name->length = i; + } + valstart = i + 1; + break; + } + } + + sstr_t ret; + return ret; +}