#include "serverconfig.h"
#include "conf.h"
#include "logging.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <cx/buffer.h>
#include <cx/utils.h>
ServerConfig* serverconfig_load(
const char *file) {
CxMempool *mp = cxBasicMempoolCreate(
512);
if(!mp) {
return NULL;
}
ConfigParser2 parser;
memset(&parser,
0,
sizeof(ConfigParser2));
parser.mp = mp;
parser.filename = file;
parser.allow_hierarchy = true;
parser.delim =
"";
ConfigNode *root = serverconfig_load_file(&parser, file);
if(!root) {
cxMempoolDestroy(mp);
return NULL;
}
ServerConfig *scfg = cxMalloc(mp->allocator,
sizeof(ServerConfig));
if(!scfg) {
cxMempoolDestroy(mp);
return NULL;
}
scfg->root = root;
scfg->mp = mp;
scfg->tab = cx_str(
"\t");
return scfg;
}
ConfigNode* serverconfig_load_file(ConfigParser2 *parser,
const char *file) {
FILE *in = fopen(file,
"r");
if(in ==
NULL) {
parser->error =
CONFIG_PARSER_IO_ERROR;
parser->io_errno = errno;
return NULL;
}
CxBuffer buf;
if(cxBufferInit(&buf,
NULL,
16384, cxDefaultAllocator,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
fclose(in);
parser->error =
CONFIG_PARSER_OOM;
return NULL;
}
char readbuf[
2048];
size_t r;
while((r = fread(readbuf,
1,
2048, in)) >
0) {
cxBufferWrite(readbuf,
1, r, &buf);
}
fclose(in);
ConfigNode *root = serverconfig_parse(parser, cx_strn(buf.space, buf.size));
cxBufferDestroy(&buf);
return root;
}
static int scfg_char_is_delim(ConfigParser2 *parser,
char c) {
size_t len = strlen(parser->delim);
for(
int i=
0;i<len;i++) {
if(c == parser->delim[i]) {
return 1;
}
}
return 0;
}
static CFGToken get_next_token(ConfigParser2 *parser, 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;
ws_cfg_log(
LOG_FAILURE,
"cfgparser: file %s:%d:%d: error: %s",
parser->filename,
parser->linenum,
parser->linepos+
2,
"missing ''\"'' character before end of line");
return token;
}
else if(start == i) {
type =
CFG_TOKEN_NEWLINE;
token_begin = i;
token_end = i+
1;
parser->linenum++;
parser->linepos =
0;
break;
}
token_end = i;
if(token_begin <
0) {
token_begin = start;
type = comment ?
CFG_TOKEN_COMMENT :
CFG_TOKEN_SPACE;
}
i--;
break;
}
else if(quote) {
if(c ==
'""' && prev !=
'\\') {
quote =
0;
}
}
else if(comment) {
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(scfg_char_is_delim(parser, c)) {
if(token_begin >=
0) {
token_end = i;
i--;
break;
}
else {
token_begin = i;
token_end = i+
1;
break;
}
}
else if(token_begin <
0) {
token_begin = i;
}
prev = c;
parser->linepos++;
}
*pos = i +
1;
if(token_begin <
0) {
return token;
}
token.type = type;
token.content = cx_strsubsl(content, token_begin, token_end - token_begin);
return token;
}
static void config_arg_set_value(CxAllocator *a, ConfigParam *arg, CFGToken token) {
cxstring nv = (cxstring){
NULL,
0};
WSBool quotes = token.content.ptr[
0] ==
'\"';
if(!quotes) {
nv = cx_strchr(token.content,
'=');
}
if(!nv.ptr) {
if(quotes) {
token.content.ptr++;
token.content.length -=
2;
}
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;
}
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;
}
ConfigNode* serverconfig_parse(ConfigParser2 *parser, cxstring content) {
CxMempool *mp = parser->mp;
CxAllocator *a = (CxAllocator*)mp->allocator;
parser->linenum =
1;
parser->linepos =
1;
if(!parser->delim) {
parser->delim =
"";
}
int pos =
0;
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(parser, content, &pos)).type !=
CFG_NO_TOKEN) {
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) {
parent->text_end = line_cp;
if(parser->validateObjEnd && parser->validateObjEnd(parser, parent)) {
err =
1;
break;
}
ConfigNodeStack *remove_item = node_stack;
node_stack = node_stack->next;
cxFree(a, remove_item);
}
else if(current->type ==
CONFIG_NODE_OPEN_OBJECT) {
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 {
current->text_begin = line_cp;
CFG_NODE_ADD(&parent->children_begin, &parent->children_end, current);
if(current->type ==
CONFIG_NODE_DIRECTIVE &&
parser->validateDirective &&
parser->validateDirective(parser, current))
{
err =
1;
break;
}
}
if(obj && obj->type ==
CONFIG_NODE_OBJECT) {
if(parser->validateObjBegin && parser->validateObjBegin(parser, obj)) {
err =
1;
break;
}
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: {
if(!cx_strcmp(token.content, cx_str(
"{"))) {
if(!parser->allow_hierarchy) {
parser->error =
CONFIG_PARSER_SYNTAX_ERROR;
err =
1;
break;
}
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(
"}"))) {
if(!parser->allow_hierarchy) {
parser->error =
CONFIG_PARSER_SYNTAX_ERROR;
err =
1;
break;
}
obj_closed =
1;
obj =
NULL;
current->type =
CONFIG_NODE_CLOSE_OBJECT;
}
else {
if(obj_closed) {
err =
1;
break;
}
if(!current->name.ptr) {
current->name = cx_strdup_a(a, token.content);
current->type =
CONFIG_NODE_DIRECTIVE;
obj = current;
}
else {
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) {
return NULL;
}
return root_obj;
}
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 = cxLinkedListCreate(cxDefaultAllocator,
NULL,
CX_STORE_POINTERS);
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_object_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 };
}
cxstring serverconfig_directive_get_arg(ConfigNode *directive, cxstring arg_name) {
cxstring ret = (cxstring){
NULL,
0 };
ConfigParam *arg = directive->args;
while(arg) {
if(!cx_strcmp(arg_name, cx_strcast(arg->name))) {
ret = cx_strcast(arg->value);
break;
}
arg = arg->next;
}
return ret;
}
int serverconfig_validate_directive_name(
ConfigNode *directive,
const char *names[],
size_t numnames,
size_t *nameindex)
{
for(
size_t i=
0;i<numnames;i++) {
if(!cx_strcmp(cx_strcast(directive->name), cx_str(names[i]))) {
*nameindex = i;
return 0;
}
}
return 1;
}
int serverconfig_check_param_names(ConfigNode *directive, ConfigParam **err) {
ConfigParam *arg = directive->args;
while(arg) {
if(arg->name.length ==
0) {
*err = arg;
return 1;
}
arg = arg->next;
}
return 0;
}
ConfigNode* serverconfig_previous_dir_or_obj(ConfigNode *node) {
node = node->prev;
while(node) {
if(node->type ==
CONFIG_NODE_DIRECTIVE || node->type ==
CONFIG_NODE_OBJECT) {
return node;
}
node = node->prev;
}
return NULL;
}
size_t serverconfig_children_count(ConfigNode *node, ConfigNodeType type) {
size_t count =
0;
node = node->children_begin;
while(node) {
if(node->type == type) {
count++;
}
node = node->next;
}
return count;
}