Sun, 06 Nov 2022 17:41:39 +0100
prepare serverconfig parser to be also used for obj.conf and init.conf
/* * 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 "conf.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <cx/buffer.h> #include <cx/utils.h> ServerConfig* serverconfig_load(const char *file) { FILE *in = fopen(file, "r"); if(in == NULL) { return NULL; } CxMempool *mp = cxBasicMempoolCreate(512); if(!mp) { fclose(in); return NULL; } CxBuffer buf; if(cxBufferInit(&buf, NULL, 16384, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { fclose(in); cxMempoolDestroy(mp); return NULL; } //ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write); char readbuf[2048]; size_t r; while((r = fread(readbuf, 1, 2048, in)) > 0) { cxBufferWrite(readbuf, 1, r, &buf); } fclose(in); ConfigParser2 parser; ZERO(&parser, sizeof(ConfigParser2)); parser.mp = mp; parser.allow_hierarchy = true; ServerConfig *scfg = serverconfig_parse(&parser, cx_strn(buf.space, buf.size)); cxBufferDestroy(&buf); return scfg; } static CFGToken get_next_token(cxstring 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 = cx_strsubsl(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(CxAllocator *a, ConfigParam *arg, CFGToken token) { cxstring nv = cx_strchr(token.content, '='); if(!nv.ptr) { arg->value = cx_strdup_a(a, token.content); } else { intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr); cxstring name = token.content; name.length = (size_t)eq; cxstring 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 = cx_strdup_a(a, name); arg->value = cx_strdup_a(a, value); } } static int nodestack_prepend(CxAllocator *a, ConfigNodeStack **stack, ConfigNode *node) { ConfigNodeStack *elm = cxMalloc(a, sizeof(ConfigNodeStack)); if(!elm) return 1; elm->node = node; elm->next = NULL; cx_linked_list_prepend((void**)stack, NULL, -1, offsetof(ConfigNodeStack, next), elm); return 0; } ServerConfig* serverconfig_parse(ConfigParser2 *parser, cxstring content) { CxMempool *mp = parser->mp; CxAllocator *a = (CxAllocator*)mp->allocator; ServerConfig *config = cxMalloc(a, sizeof(ServerConfig)); if(!config) { cxMempoolDestroy(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 = cxCalloc(a, 1, sizeof(ConfigNode)); root_obj->type = CONFIG_NODE_OBJECT; ConfigNodeStack *node_stack = cxMalloc(a, sizeof(ConfigNodeStack)); node_stack->node = root_obj; node_stack->next = NULL; ConfigNode *current = cxCalloc(a, 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: { cxstring line = cx_strsubsl(content, text_start, pos - text_start); text_start = pos; cxmutstr line_cp = cx_strdup_a(a, line); ConfigNode *parent = node_stack->node; if(current->type == CONFIG_NODE_CLOSE_OBJECT) { // this is a newline after a object is closed with '}' // the line containing "}\n" should be added to the object parent->text_end = line_cp; // validate if(parser->validateObjEnd && parser->validateObjEnd(parser, parent)) { // validation failed err = 1; break; } // done with this object, remove it from the stack ConfigNodeStack *remove_item = node_stack; node_stack = node_stack->next; cxFree(a, remove_item); } else if(current->type == CONFIG_NODE_OPEN_OBJECT) { // newline after a object is opened with '{' // append '{' to the object text cxmutstr new_textbegin = cx_strcat_a(a, 2, obj->text_begin, line_cp); cxFree(a, obj->text_begin.ptr); cxFree(a, line_cp.ptr); obj->text_begin = new_textbegin; } else { // normal line containing a directive, space or comment if(parser->validateDirective && parser->validateDirective(parser, current)) { err = 1; break; } // add it to parent node current->text_begin = line_cp; CFG_NODE_ADD(&parent->children_begin, &parent->children_end, current); } // obj points to the previous node that started as a directive // the type is set to CONFIG_NODE_OBECT if it was followed by // a '{' character if(obj && obj->type == CONFIG_NODE_OBJECT) { // new object started if(parser->validateObjBegin && parser->validateObjBegin(parser, obj)) { // validation callback failed err = 1; break; } // add it to the stack nodestack_prepend(a, &node_stack, obj); obj = NULL; } current = cxCalloc(a, 1, sizeof(ConfigNode)); current->type = CONFIG_NODE_SPACE; obj_closed = 0; break; } case CFG_TOKEN: { // normal text token // either a directive/obj name, parameter or { } if(!cx_strcmp(token.content, cx_str("{"))) { // check if the parser allows an object hierarchy if(!parser->allow_hierarchy) { parser->error = CONFIG_PARSER_SYNTAX_ERROR; err = 1; break; } // obj is pointing to the previous node that started // a directive if(!obj) { err = 1; break; } obj->type = CONFIG_NODE_OBJECT; if(current != obj) { current->type = CONFIG_NODE_OPEN_OBJECT; } } else if(!cx_strcmp(token.content, cx_str("}"))) { // check if the parser allows an object hierarchy if(!parser->allow_hierarchy) { parser->error = CONFIG_PARSER_SYNTAX_ERROR; err = 1; break; } 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) { // currently this could be a directive or object current->name = cx_strdup_a(a, token.content); current->type = CONFIG_NODE_DIRECTIVE; obj = current; // potential object } else { // name already set, therefore this token must // be a parameter ConfigParam *arg = cxCalloc(a, 1, sizeof(ConfigParam)); config_arg_set_value(a, arg, token); CFG_PARAM_ADD(¤t->args, NULL, arg); } } break; } } if(err) { break; } } if(pos < content.length || err) { // content not fully parsed because of an error cxMempoolDestroy(mp); return NULL; } //test_print_config(&root_obj); config->root = root_obj; config->tab = cx_strdup_a(a, cx_str("\t")); return config; } void serverconfig_free(ServerConfig *cfg) { cxMempoolDestroy(cfg->mp); } ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, cxstring name) { for(ConfigNode *node=parent->children_begin;node;node=node->next) { if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) { return node; } } return NULL; } CxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, cxstring name) { CxList *nodes = cxPointerLinkedListCreate(cxDefaultAllocator, cx_cmp_ptr); for(ConfigNode *node=parent->children_begin;node;node=node->next) { if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) { cxListAdd(nodes, node); } } return nodes; } cxstring serverconfig_directive_value(ConfigNode *obj, cxstring name) { ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name); if(node && CFG_NUM_PARAMS(node->args) == 1) { ConfigParam *arg = node->args; return (cxstring){ arg->value.ptr, arg->value.length }; } return (cxstring){ NULL, 0 }; } cxmutstr serverconfig_arg_name_value(CxAllocator *a, cxstring str, cxstring *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; } } cxmutstr ret; return ret; }