1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 #include "serverconfig.h"
31 #include "conf.h"
32 #include "logging.h"
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <errno.h>
39
40 #include <cx/buffer.h>
41 #include <cx/utils.h>
42
43 ServerConfig* serverconfig_load(
const char *file) {
44 CxMempool *mp = cxBasicMempoolCreate(
512);
45 if(!mp) {
46 return NULL;
47 }
48
49 ConfigParser2 parser;
50 memset(&parser,
0,
sizeof(ConfigParser2));
51 parser.mp = mp;
52 parser.filename = file;
53 parser.allow_hierarchy = true;
54 parser.delim =
"";
55 ConfigNode *root = serverconfig_load_file(&parser, file);
56 if(!root) {
57 cxMempoolDestroy(mp);
58 return NULL;
59 }
60
61 ServerConfig *scfg = cxMalloc(mp->allocator,
sizeof(ServerConfig));
62 if(!scfg) {
63 cxMempoolDestroy(mp);
64 return NULL;
65 }
66 scfg->root = root;
67 scfg->mp = mp;
68 scfg->tab = cx_str(
"\t");
69
70 return scfg;
71 }
72
73 ConfigNode* serverconfig_load_file(ConfigParser2 *parser,
const char *file) {
74 FILE *in = fopen(file,
"r");
75 if(in ==
NULL) {
76 parser->error =
CONFIG_PARSER_IO_ERROR;
77 parser->io_errno = errno;
78 return NULL;
79 }
80
81
82 CxBuffer buf;
83 if(cxBufferInit(&buf,
NULL,
16384, cxDefaultAllocator,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
84 fclose(in);
85 parser->error =
CONFIG_PARSER_OOM;
86 return NULL;
87 }
88
89
90 char readbuf[
2048];
91 size_t r;
92 while((r = fread(readbuf,
1,
2048, in)) >
0) {
93 cxBufferWrite(readbuf,
1, r, &buf);
94 }
95 fclose(in);
96
97 ConfigNode *root = serverconfig_parse(parser, cx_strn(buf.space, buf.size));
98 cxBufferDestroy(&buf);
99 return root;
100 }
101
102 static int scfg_char_is_delim(ConfigParser2 *parser,
char c) {
103 size_t len = strlen(parser->delim);
104 for(
int i=
0;i<len;i++) {
105 if(c == parser->delim[i]) {
106 return 1;
107 }
108 }
109 return 0;
110 }
111
112 static CFGToken get_next_token(ConfigParser2 *parser, cxstring content,
int *pos) {
113 CFGToken token = { {
NULL,
0},
CFG_NO_TOKEN };
114 CFGTokenType type =
CFG_TOKEN;
115
116 int start = *pos;
117
118 int token_begin = -
1;
119 int token_end = content.length-
1;
120
121 int quote =
0;
122 int comment =
0;
123
124 int i;
125 char prev =
0;
126 for(i=start;i<content.length;i++) {
127 char c = content.ptr[i];
128 if(c ==
'\n') {
129 if(quote) {
130 *pos = i;
131 ws_cfg_log(
132 LOG_FAILURE,
133 "cfgparser: file %s:%d:%d: error: %s",
134 parser->filename,
135 parser->linenum,
136 parser->linepos+
2,
137 "missing ''\"'' character before end of line");
138 return token;
139 }
else if(start == i) {
140
141 type =
CFG_TOKEN_NEWLINE;
142 token_begin = i;
143 token_end = i+
1;
144 parser->linenum++;
145 parser->linepos =
0;
146 break;
147 }
148
149 token_end = i;
150 if(token_begin <
0) {
151
152 token_begin = start;
153 type = comment ?
CFG_TOKEN_COMMENT :
CFG_TOKEN_SPACE;
154 }
155
156 i--;
157 break;
158 }
else if(quote) {
159 if(c ==
'""' && prev !=
'\\') {
160 quote =
0;
161 }
162 }
else if(comment) {
163
164 if(c ==
'\n') {
165 comment =
0;
166 }
167 }
else if(c ==
'#') {
168 comment =
1;
169 }
else if(isspace(c)) {
170 if(token_begin >=
0) {
171 token_end = i;
172 break;
173 }
174 }
else if(c ==
'""') {
175 quote =
1;
176 if(token_begin <
0) {
177 token_begin = i;
178 }
179 }
else if(scfg_char_is_delim(parser, c)) {
180 if(token_begin >=
0) {
181 token_end = i;
182 i--;
183 break;
184 }
else {
185 token_begin = i;
186 token_end = i+
1;
187 break;
188 }
189 }
else if(token_begin <
0) {
190 token_begin = i;
191 }
192 prev = c;
193
194 parser->linepos++;
195 }
196
197 *pos = i +
1;
198
199 if(token_begin <
0) {
200 return token;
201 }
202
203 token.type = type;
204 token.content = cx_strsubsl(content, token_begin, token_end - token_begin);
205 return token;
206 }
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230 static void config_arg_set_value(CxAllocator *a, ConfigParam *arg, CFGToken token) {
231 cxstring nv = (cxstring){
NULL,
0};
232 WSBool quotes = token.content.ptr[
0] ==
'\"';
233 if(!quotes) {
234 nv = cx_strchr(token.content,
'=');
235 }
236 if(!nv.ptr) {
237 if(quotes) {
238
239 token.content.ptr++;
240 token.content.length -=
2;
241 }
242 arg->value = cx_strdup_a(a, token.content);
243 }
else {
244 intptr_t eq = (
intptr_t)(nv.ptr - token.content.ptr);
245 cxstring name = token.content;
246 name.length = (
size_t)eq;
247
248 cxstring value = nv;
249 value.ptr++;
250 value.length--;
251 if(value.length >
1 && value.ptr[
0] ==
'""' && value.ptr[value.length-
1] ==
'""') {
252 value.ptr++;
253 value.length -=
2;
254 }
255
256 arg->name = cx_strdup_a(a, name);
257 arg->value = cx_strdup_a(a, value);
258 }
259 }
260
261 static int nodestack_prepend(CxAllocator *a, ConfigNodeStack **stack, ConfigNode *node) {
262 ConfigNodeStack *elm = cxMalloc(a,
sizeof(ConfigNodeStack));
263 if(!elm)
return 1;
264 elm->node = node;
265 elm->next =
NULL;
266 cx_linked_list_prepend((
void**)stack,
NULL, -
1, offsetof(ConfigNodeStack, next), elm);
267 return 0;
268 }
269
270 ConfigNode* serverconfig_parse(ConfigParser2 *parser, cxstring content) {
271 CxMempool *mp = parser->mp;
272 CxAllocator *a = (CxAllocator*)mp->allocator;
273
274 parser->linenum =
1;
275 parser->linepos =
1;
276
277 if(!parser->delim) {
278 parser->delim =
"";
279 }
280
281
282
283
284
285
286
287 int pos =
0;
288 CFGToken token;
289
290 ConfigNode *root_obj = cxCalloc(a,
1,
sizeof(ConfigNode));
291 root_obj->type =
CONFIG_NODE_OBJECT;
292
293 ConfigNodeStack *node_stack = cxMalloc(a,
sizeof(ConfigNodeStack));
294 node_stack->node = root_obj;
295 node_stack->next =
NULL;
296
297 ConfigNode *current = cxCalloc(a,
1,
sizeof(ConfigNode));
298 current->type =
CONFIG_NODE_SPACE;
299 ConfigNode *obj =
NULL;
300 int obj_closed =
0;
301
302 int text_start =
0;
303 int err =
0;
304 while((token = get_next_token(parser, content, &pos)).type !=
CFG_NO_TOKEN) {
305
306
307 switch(token.type) {
308 case CFG_NO_TOKEN:
break;
309 case CFG_TOKEN_COMMENT: {
310 if(current->type ==
CONFIG_NODE_SPACE) {
311 current->type =
CONFIG_NODE_COMMENT;
312 }
313 break;
314 }
315 case CFG_TOKEN_SPACE:
break;
316 case CFG_TOKEN_NEWLINE: {
317 cxstring line = cx_strsubsl(content, text_start, pos - text_start);
318 text_start = pos;
319
320 cxmutstr line_cp = cx_strdup_a(a, line);
321
322 ConfigNode *parent = node_stack->node;
323 if(current->type ==
CONFIG_NODE_CLOSE_OBJECT) {
324
325
326 parent->text_end = line_cp;
327
328 if(parser->validateObjEnd && parser->validateObjEnd(parser, parent)) {
329
330 err =
1;
331 break;
332 }
333
334 ConfigNodeStack *remove_item = node_stack;
335 node_stack = node_stack->next;
336 cxFree(a, remove_item);
337 }
else if(current->type ==
CONFIG_NODE_OPEN_OBJECT) {
338
339
340 cxmutstr new_textbegin = cx_strcat_a(a,
2, obj->text_begin, line_cp);
341 cxFree(a, obj->text_begin.ptr);
342 cxFree(a, line_cp.ptr);
343 obj->text_begin = new_textbegin;
344 }
else {
345
346
347
348 current->text_begin = line_cp;
349 CFG_NODE_ADD(&parent->children_begin, &parent->children_end, current);
350
351
352 if(current->type ==
CONFIG_NODE_DIRECTIVE &&
353 parser->validateDirective &&
354 parser->validateDirective(parser, current))
355 {
356 err =
1;
357 break;
358 }
359 }
360
361
362
363
364 if(obj && obj->type ==
CONFIG_NODE_OBJECT) {
365
366 if(parser->validateObjBegin && parser->validateObjBegin(parser, obj)) {
367
368 err =
1;
369 break;
370 }
371
372
373 nodestack_prepend(a, &node_stack, obj);
374 obj =
NULL;
375 }
376
377 current = cxCalloc(a,
1,
sizeof(ConfigNode));
378 current->type =
CONFIG_NODE_SPACE;
379
380 obj_closed =
0;
381 break;
382 }
383 case CFG_TOKEN: {
384
385
386
387 if(!cx_strcmp(token.content, cx_str(
"{"))) {
388
389 if(!parser->allow_hierarchy) {
390 parser->error =
CONFIG_PARSER_SYNTAX_ERROR;
391 err =
1;
392 break;
393 }
394
395
396
397 if(!obj) {
398 err =
1;
399 break;
400 }
401 obj->type =
CONFIG_NODE_OBJECT;
402 if(current != obj) {
403 current->type =
CONFIG_NODE_OPEN_OBJECT;
404 }
405 }
else if(!cx_strcmp(token.content, cx_str(
"}"))) {
406
407 if(!parser->allow_hierarchy) {
408 parser->error =
CONFIG_PARSER_SYNTAX_ERROR;
409 err =
1;
410 break;
411 }
412
413 obj_closed =
1;
414 obj =
NULL;
415 current->type =
CONFIG_NODE_CLOSE_OBJECT;
416 }
else {
417 if(obj_closed) {
418 err =
1;
419 break;
420 }
421
422 if(!current->name.ptr) {
423
424 current->name = cx_strdup_a(a, token.content);
425 current->type =
CONFIG_NODE_DIRECTIVE;
426 obj = current;
427 }
else {
428
429
430 ConfigParam *arg = cxCalloc(a,
1,
sizeof(ConfigParam));
431 config_arg_set_value(a, arg, token);
432 CFG_PARAM_ADD(¤t->args,
NULL, arg);
433 }
434 }
435 break;
436 }
437 }
438
439 if(err) {
440 break;
441 }
442 }
443
444 if(pos < content.length || err) {
445
446 return NULL;
447 }
448
449
450 return root_obj;
451 }
452
453 void serverconfig_free(ServerConfig *cfg) {
454 cxMempoolDestroy(cfg->mp);
455 }
456
457 ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, cxstring name) {
458 for(ConfigNode *node=parent->children_begin;node;node=node->next) {
459 if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) {
460 return node;
461 }
462 }
463 return NULL;
464 }
465
466 CxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, cxstring name) {
467 CxList *nodes = cxLinkedListCreate(cxDefaultAllocator,
NULL,
CX_STORE_POINTERS);
468
469 for(ConfigNode *node=parent->children_begin;node;node=node->next) {
470 if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) {
471 cxListAdd(nodes, node);
472 }
473 }
474
475 return nodes;
476 }
477
478 cxstring serverconfig_object_directive_value(ConfigNode *obj, cxstring name) {
479 ConfigNode *node = serverconfig_get_node(obj,
CONFIG_NODE_DIRECTIVE, name);
480 if(node &&
CFG_NUM_PARAMS(node->args) ==
1) {
481 ConfigParam *arg = node->args;
482 return (cxstring){ arg->value.ptr, arg->value.length };
483 }
484 return (cxstring){
NULL,
0 };
485 }
486
487 cxstring serverconfig_directive_get_arg(ConfigNode *directive, cxstring arg_name) {
488 cxstring ret = (cxstring){
NULL,
0 };
489 ConfigParam *arg = directive->args;
490 while(arg) {
491 if(!cx_strcmp(arg_name, cx_strcast(arg->name))) {
492 ret = cx_strcast(arg->value);
493 break;
494 }
495 arg = arg->next;
496 }
497
498 return ret;
499 }
500
501
502
503
504
505
506 int serverconfig_validate_directive_name(
507 ConfigNode *directive,
508 const char *names[],
509 size_t numnames,
510 size_t *nameindex)
511 {
512 for(
size_t i=
0;i<numnames;i++) {
513 if(!cx_strcmp(cx_strcast(directive->name), cx_str(names[i]))) {
514 *nameindex = i;
515 return 0;
516 }
517 }
518 return 1;
519 }
520
521 int serverconfig_check_param_names(ConfigNode *directive, ConfigParam **err) {
522 ConfigParam *arg = directive->args;
523 while(arg) {
524 if(arg->name.length ==
0) {
525 *err = arg;
526 return 1;
527 }
528 arg = arg->next;
529 }
530 return 0;
531 }
532
533 ConfigNode* serverconfig_previous_dir_or_obj(ConfigNode *node) {
534 node = node->prev;
535 while(node) {
536 if(node->type ==
CONFIG_NODE_DIRECTIVE || node->type ==
CONFIG_NODE_OBJECT) {
537 return node;
538 }
539 node = node->prev;
540 }
541 return NULL;
542 }
543
544 size_t serverconfig_children_count(ConfigNode *node, ConfigNodeType type) {
545 size_t count =
0;
546 node = node->children_begin;
547 while(node) {
548 if(node->type == type) {
549 count++;
550 }
551 node = node->next;
552 }
553 return count;
554 }
555