1 /* vi:ai:et:ts=8 sw=2
2 */
3 /*
4 * wzdftpd - a modular and cool ftp server
5 * Copyright (C) 2002-2004 Pierre Chifflier
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 * As a special exemption, Pierre Chifflier
22 * and other respective copyright holders give permission to link this program
23 * with OpenSSL, and distribute the resulting executable, without including
24 * the source code for OpenSSL in the source distribution.
25 */
26 /** \file wzd_configfile.c
27 * \brief Simple config file parser (.ini like)
28 *
29 * \note Implementation was greatly inspired from gkeyfile.c, from the glib sources.
30 */
31
32 #include "wzd_all.h"
33
34 #ifndef WZD_USE_PCH
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <errno.h>
42
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif
46
47 #include <fcntl.h> /* O_RDONLY */
48
49 #include <ctype.h> /* isspace */
50
51 #include "wzd_structs.h"
52 #include "wzd_log.h"
53
54 #include "wzd_string.h"
55 #include "wzd_utf8.h"
56 #include "wzd_configfile.h"
57
58 #include "wzd_debug.h"
59
60 #endif /* WZD_USE_PCH */
61
62
63 #include "libwzd-base/dlist.h"
64
65
66
67 #define VALUE_LIST_SEPARATOR ","
68
69 typedef struct _wzd_configfile_group_t wzd_configfile_group_t;
70
71 typedef struct _wzd_configfile_keyvalue_t wzd_configfile_keyvalue_t;
72
73 struct _wzd_configfile_t {
74 List * groups;
75 wzd_string_t * parse_buffer;
76
77 wzd_configfile_group_t * current_group;
78
79 unsigned long flags;
80 };
81
82 struct _wzd_configfile_group_t {
83 char * name;
84
85 wzd_configfile_keyvalue_t * comment; /* comment at top */
86
87 DList * values;
88 };
89
90 struct _wzd_configfile_keyvalue_t {
91 char * key; /* NULL for comments */
92 char * value;
93 };
94
95 static void _configfile_group_init(wzd_configfile_group_t * group);
96 static void _configfile_group_free(wzd_configfile_group_t * group);
97 static wzd_configfile_keyvalue_t * _configfile_keyvalue_calloc(void);
98 static void _configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv);
99
100 static void config_init(wzd_configfile_t * config);
101 static void config_clear(wzd_configfile_t * config);
102
103 static int _config_cmp_keyvalue(const char *k1, const wzd_configfile_keyvalue_t *k2);
104 static int _config_cmp_groupname(const char *k1, const wzd_configfile_group_t *k2);
105
106 static int config_line_is_comment(const char * line);
107 static int config_line_is_group(const char * line);
108 static int config_line_is_keyvalue(const char * line);
109 static int config_parse_data(wzd_configfile_t * config, const char * data, size_t length);
110 static int config_parse_flush_buffer(wzd_configfile_t * config);
111 static int config_parse_line(wzd_configfile_t * config, const char * line, size_t length);
112 static int config_parse_comment(wzd_configfile_t * config, const char * line, size_t length);
113 static int config_parse_group(wzd_configfile_t * config, const char * line, size_t length);
114 static int config_parse_keyvalue(wzd_configfile_t * config, const char * line, size_t length);
115
116 static wzd_configfile_group_t * config_lookup_group(const wzd_configfile_t * config, const char *groupname);
117 static wzd_configfile_keyvalue_t * config_lookup_keyvalue(const wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key);
118
119 static int config_add_key(wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key, const char * value);
120 static int config_add_group(wzd_configfile_t * config, const char * groupname);
121
122 static int config_set_key_comment(wzd_configfile_t * config, const char * groupname, const char * key, const char * comment);
123 static int config_set_group_comment(wzd_configfile_t * config, const char * groupname, const char * comment);
124 static int config_set_top_comment(wzd_configfile_t * config, const char * comment);
125
126
127
128 /** \brief Creates a new empty wzd_configfile_t object.
129 */
config_new(void)130 wzd_configfile_t * config_new(void)
131 {
132 wzd_configfile_t * filenew;
133
134 filenew = wzd_malloc(sizeof(wzd_configfile_t));
135 config_init(filenew);
136
137 return filenew;
138 }
139
140 /** \brief Frees a wzd_configfile_t
141 */
config_free(wzd_configfile_t * file)142 void config_free(wzd_configfile_t * file)
143 {
144 if (!file) return;
145
146 config_clear(file);
147 wzd_free(file);
148 }
149
150 /** \brief Returns the list of groups contained in \a file
151 */
config_get_groups(const wzd_configfile_t * file)152 wzd_string_t ** config_get_groups(const wzd_configfile_t * file)
153 {
154 wzd_string_t ** array;
155 ListElmt * elmnt;
156 wzd_configfile_group_t * group = NULL;
157 int i = 0;
158
159 if (!file) return NULL;
160
161 array = wzd_malloc(sizeof(*array) * (list_size(file->groups)+1));
162
163 for (elmnt = list_head(file->groups); elmnt; elmnt = list_next(elmnt)) {
164 group = list_data(elmnt);
165 array[i++] = STR(group->name);
166 }
167
168 array[i] = NULL;
169
170 return array;
171 }
172
173 /** \brief Returns the list of keys contained in \a group
174 */
config_get_keys(const wzd_configfile_t * file,const char * groupname,int * errcode)175 wzd_string_t ** config_get_keys(const wzd_configfile_t * file, const char * groupname, int * errcode)
176 {
177 wzd_string_t ** array;
178 DListElmt * elmnt;
179 wzd_configfile_group_t * group;
180 wzd_configfile_keyvalue_t * kv;
181 int i = 0;
182
183 if (!file || !groupname) return 0;
184
185 group = config_lookup_group(file,groupname);
186 if (!group) {
187 if (errcode) *errcode = CF_ERROR_GROUP_NOT_FOUND;
188 return NULL;
189 }
190
191 array = wzd_malloc(sizeof(*array) * (dlist_size(group->values)+1));
192
193 for (elmnt = dlist_head(group->values); elmnt; elmnt = dlist_next(elmnt)) {
194 kv = list_data(elmnt);
195 array[i++] = STR(kv->key);
196 }
197
198 array[i] = NULL;
199
200 return array;
201 }
202
203 /** \brief Looks whether the config file has the group \a groupname.
204 * \return 1 if \a groupname is part of \a file
205 */
config_has_group(wzd_configfile_t * file,const char * groupname)206 int config_has_group(wzd_configfile_t * file, const char * groupname)
207 {
208 if (!file || !groupname) return 0;
209
210 return config_lookup_group(file,groupname) != NULL;
211 }
212
213 /** \brief Looks whether the config file has the key \a key in the group \a groupname.
214 * \return 1 if \a key is part of \a groupname
215 */
config_has_key(wzd_configfile_t * file,const char * groupname,const char * key)216 int config_has_key(wzd_configfile_t * file, const char * groupname, const char * key)
217 {
218 wzd_configfile_group_t * group;
219 wzd_configfile_keyvalue_t * kv;
220
221 if (!file || !groupname || !key) return 0;
222
223 group = config_lookup_group(file,groupname);
224 if (!group) return 0;
225
226 kv = config_lookup_keyvalue(file,group,key);
227
228 return kv != NULL;
229 }
230
231 /** \brief Returns the value associated with \a key under \a groupname.
232 * \return the value, or NULL if the key is not found
233 * The returned value is a pointer to the object, it must not be freed.
234 */
config_get_value(const wzd_configfile_t * file,const char * groupname,const char * key)235 char * config_get_value(const wzd_configfile_t * file, const char * groupname, const char * key)
236 {
237 wzd_configfile_group_t * group;
238 wzd_configfile_keyvalue_t * kv;
239
240 if (!file || !groupname || !key) return NULL;
241
242 group = config_lookup_group(file,groupname);
243 if (!group) return NULL;
244
245 kv = config_lookup_keyvalue(file,group,key);
246 if (!kv) return NULL;
247
248 return kv->value;
249 }
250
251 /** \brief Associates a new value with \a key under \a groupname.
252 *
253 * If \a key cannot be found then it is created. If \a groupname cannot be found then it is
254 * created.
255 */
config_set_value(wzd_configfile_t * file,const char * groupname,const char * key,const char * value)256 int config_set_value(wzd_configfile_t * file, const char * groupname, const char * key, const char * value)
257 {
258 wzd_configfile_group_t * group;
259 wzd_configfile_keyvalue_t * kv;
260
261 if (!file || !groupname || !key || !value) return CF_ERROR_INVALID_ARGS;
262
263 group = config_lookup_group(file,groupname);
264 if (!group) {
265 if (config_add_group(file, groupname)) return CF_ERROR_GROUP_NOT_FOUND;
266 group = config_lookup_group(file,groupname);
267 if (!group) return CF_ERROR_GROUP_NOT_FOUND;
268 }
269
270 kv = config_lookup_keyvalue(file,group,key);
271 if (!kv) {
272 return config_add_key(file,group,key,value);
273 } else {
274 wzd_free(kv->value);
275 kv->value = wzd_strdup(value);
276 }
277
278 return CF_OK;
279 }
280
281 /** \brief Returns the value associated with \a key under \a groupname as a string.
282 * \return the value, else \a errcode is set to nonzero.
283 */
config_get_string(const wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)284 wzd_string_t * config_get_string(const wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
285 {
286 char * value;
287 wzd_string_t * str_value = NULL;
288
289 if (!file || !groupname || !key) return NULL;
290 if (errcode) *errcode = CF_OK;
291
292 value = config_get_value(file,groupname,key);
293 if (!value) {
294 if (errcode) *errcode = CF_ERROR_NOT_FOUND;
295 return NULL;
296 }
297
298 #ifdef HAVE_UTF8
299 if (!utf8_valid(value,strlen(value))) {
300 if (errcode) *errcode = CF_ERROR_INVALID_ENCODING;
301 return NULL;
302 }
303 #endif
304
305 str_value = STR(value);
306
307 return str_value;
308 }
309
310 /** \brief Associates a new string value with \a key under \a groupname.
311 *
312 * If \a key cannot be found then it is created.
313 */
config_set_string(wzd_configfile_t * file,const char * groupname,const char * key,wzd_string_t * value)314 int config_set_string(wzd_configfile_t * file, const char * groupname, const char * key, wzd_string_t * value)
315 {
316 if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
317
318 return config_set_value(file, groupname, key, str_tochar(value));
319 }
320
321 /** \brief Returns the value associated with \a key under \a groupname as a string.
322 * \return a NULL-terminated string array,, or NULL and set \a errcode to nonzero.
323 * The array should be freed using str_deallocate_array()
324 */
config_get_string_list(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)325 wzd_string_t ** config_get_string_list(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
326 {
327 char * value;
328 wzd_string_t * str_value;
329 wzd_string_t ** array = NULL;
330 unsigned int i;
331
332 if (!file || !groupname || !key) return NULL;
333 if (errcode) *errcode = CF_OK;
334
335 value = config_get_value(file,groupname,key);
336 if (!value) {
337 if (errcode) *errcode = CF_ERROR_NOT_FOUND;
338 return NULL;
339 }
340
341 #ifdef HAVE_UTF8
342 if (!utf8_valid(value,strlen(value))) {
343 if (errcode) *errcode = CF_ERROR_INVALID_ENCODING;
344 return NULL;
345 }
346 #endif
347
348 str_value = STR(value);
349
350 array = str_split(str_value,VALUE_LIST_SEPARATOR,0);
351
352 str_deallocate(str_value);
353
354 /* remove leading spaces */
355 if (array) {
356 for (i=0; array[i]; i++) {
357 str_trim_left(array[i]);
358 }
359 }
360
361 return array;
362 }
363
364 /** \brief Associates a list of string values with \a key under \a groupname.
365 *
366 * If \a key cannot be found then it is created.
367 */
config_set_string_list(wzd_configfile_t * file,const char * groupname,const char * key,wzd_string_t ** value,size_t length)368 int config_set_string_list(wzd_configfile_t * file, const char * groupname, const char * key, wzd_string_t ** value, size_t length)
369 {
370 wzd_string_t * str;
371 size_t i;
372 int ret;
373
374 if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
375
376 str = str_allocate();
377 for (i=0; value[i] != NULL && i < length; i++) {
378 str_append(str, str_tochar(value[i]));
379 str_append(str, VALUE_LIST_SEPARATOR);
380 }
381 /* removes the last separator */
382 str_erase(str, str_length(str)-strlen(VALUE_LIST_SEPARATOR), -1);
383
384 ret = config_set_value(file, groupname, key, str_tochar(str));
385
386 str_deallocate(str);
387
388 return ret;
389 }
390
391 /** \brief Returns the value associated with \a key under \a groupname as a boolean.
392 * \return the value, else \a errcode is set to nonzero.
393 */
config_get_boolean(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)394 int config_get_boolean(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
395 {
396 char * value;
397
398 if (errcode) *errcode = CF_OK;
399
400 value = config_get_value(file,groupname,key);
401 if (!value) {
402 if (errcode) *errcode = CF_ERROR_NOT_FOUND;
403 return -1;
404 }
405
406 if (strcmp(value,"true")==0 || strcmp(value,"1")==0 || strcmp(value,"yes")==0) return 1;
407 if (strcmp(value,"false")==0 || strcmp(value,"0")==0 || strcmp(value,"no")==0 ) return 0;
408
409 if (errcode) *errcode = CF_ERROR_PARSE;
410
411 return -1;
412 }
413
414 /** \brief Associates a new boolean value with \a key under \a groupname.
415 *
416 * If \a key cannot be found then it is created.
417 */
config_set_boolean(wzd_configfile_t * file,const char * groupname,const char * key,int value)418 int config_set_boolean(wzd_configfile_t * file, const char * groupname, const char * key, int value)
419 {
420 char * result;
421
422 if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
423
424 result = (value) ? "true" : "false";
425
426 return config_set_value(file, groupname, key, result);
427 }
428
429 /** \brief Returns the value associated with \a key under \a groupname as an integer.
430 * \return the value, else \a errcode is set to nonzero.
431 */
config_get_integer(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)432 int config_get_integer(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
433 {
434 char * value;
435 char * end;
436 long longv;
437 int intv;
438
439 if (errcode) *errcode = CF_OK;
440
441 value = config_get_value(file,groupname,key);
442 if (!value) {
443 if (errcode) *errcode = CF_ERROR_NOT_FOUND;
444 return -1;
445 }
446
447 longv = strtoul(value,&end,10);
448 if (*value == '\0' || *end != '\0') {
449 if (errcode) *errcode = CF_ERROR_PARSE;
450 return -1;
451 }
452
453 intv = longv;
454 if (intv != longv || errno == ERANGE) {
455 if (errcode) *errcode = CF_ERROR_PARSE;
456 return -1;
457 }
458
459 return intv;
460 }
461
462 /** \brief Associates a new integer value with \a key under \a groupname.
463 *
464 * If \a key cannot be found then it is created.
465 */
config_set_integer(wzd_configfile_t * file,const char * groupname,const char * key,int value)466 int config_set_integer(wzd_configfile_t * file, const char * groupname, const char * key, int value)
467 {
468 wzd_string_t * str;
469 int ret;
470
471 if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
472
473 str = str_allocate();
474 str_sprintf(str,"%d",value);
475
476 ret = config_set_value(file, groupname, key, str_tochar(str));
477
478 str_deallocate(str);
479
480 return ret;
481 }
482
483 /** \brief Places a comment above \a key from \a groupname.
484 *
485 * If \a key is NULL then \a comment will be written above \a groupname.
486 * If both \a key and \a groupname are NULL, then \a comment will be written
487 * above the first group in the file.
488 */
config_set_comment(wzd_configfile_t * file,const char * groupname,const char * key,const char * comment)489 int config_set_comment(wzd_configfile_t * file, const char * groupname, const char * key, const char * comment)
490 {
491 if (!file) return CF_ERROR_INVALID_ARGS;
492
493 if (groupname && key)
494 return config_set_key_comment(file, groupname, key, comment);
495 else if (groupname)
496 return config_set_group_comment(file, groupname, comment);
497 else
498 return config_set_top_comment(file, comment);
499 }
500
501 /** \brief Removes a comment above \a key from \a groupname
502 *
503 * If \a key is \a NULL then the comment above \a groupname is removed.
504 * If both \a key and \a groupname are \a NULL, then the comment before the
505 * first group is removed.
506 */
config_remove_comment(wzd_configfile_t * file,const char * groupname,const char * key)507 int config_remove_comment(wzd_configfile_t * file, const char * groupname, const char * key)
508 {
509 if (!file) return CF_ERROR_INVALID_ARGS;
510
511 if (groupname && key)
512 return config_set_key_comment(file, groupname, key, NULL);
513 else if (groupname)
514 return config_set_group_comment(file, groupname, NULL);
515 else
516 return config_set_top_comment(file, NULL);
517 }
518
519 /** \brief Removes a \a key in \a groupname from the key file.
520 */
config_remove_key(wzd_configfile_t * file,const char * groupname,const char * key)521 int config_remove_key(wzd_configfile_t * file, const char * groupname, const char * key)
522 {
523 wzd_configfile_group_t * group;
524 wzd_configfile_keyvalue_t * kv;
525 DListElmt * element;
526
527 if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
528
529 group = config_lookup_group(file,groupname);
530 if (!group) {
531 return CF_ERROR_GROUP_NOT_FOUND;
532 }
533
534 /* find the key the comments are supposed to be associated with */
535 element = dlist_lookup_node(group->values,(void*)key);
536 if (!element) {
537 return CF_ERROR_NOT_FOUND;
538 } else {
539 dlist_remove(group->values,element,(void**)&kv);
540 _configfile_keyvalue_free(kv);
541 }
542
543 return CF_OK;
544 }
545
546 /** \brief Removes a \a groupname (and all associated keys and comments) from the key file.
547 */
config_remove_group(wzd_configfile_t * file,const char * groupname)548 int config_remove_group(wzd_configfile_t * file, const char * groupname)
549 {
550 ListElmt * element;
551 wzd_configfile_group_t * group;
552
553 if (!file || !groupname) return CF_ERROR_INVALID_ARGS;
554
555 element = list_lookup_node(file->groups, (void*)groupname);
556 if (!element) return CF_ERROR_GROUP_NOT_FOUND;
557
558 if (list_data(element) == file->current_group) {
559 if (list_head(file->groups) != element)
560 file->current_group = list_data(list_head(file->groups));
561 else
562 file->current_group = NULL;
563 }
564
565 list_remove(file->groups, element, (void**)&group);
566
567 if (group) {
568 _configfile_group_free(group);
569 }
570
571 return CF_OK;
572 }
573
574 /** Loads a key file from disk into an empty wzd_configfile_t structure.
575 *
576 * If the object cannot be created then the return value is non-zero.
577 */
config_load_from_file(wzd_configfile_t * config,const char * file,unsigned long flags)578 int config_load_from_file (wzd_configfile_t * config, const char * file, unsigned long flags)
579 {
580 int fd;
581 int ret;
582
583 if (!config || !file) return CF_ERROR_INVALID_ARGS;
584
585 fd = open(file, O_RDONLY, 0);
586 if (fd < 0)
587 return CF_ERROR_FILE;
588
589 ret = config_load_from_fd(config, fd, flags);
590
591 close(fd);
592 return ret;
593 }
594
595 /** Loads a key file from an opened file descriptor into an empty
596 * wzd_configfile_t structure.
597 *
598 * If the object cannot be created then the return value is non-zero.
599 */
config_load_from_fd(wzd_configfile_t * config,int fd,unsigned long flags)600 int config_load_from_fd (wzd_configfile_t * config, int fd, unsigned long flags)
601 {
602 char read_buf[4096];
603 int bytes_read;
604 int ret;
605 struct stat stat_buf;
606
607 if (!config || fd < 0) return CF_ERROR_INVALID_ARGS;
608
609 if (fstat(fd, &stat_buf) < 0)
610 return CF_ERROR_FILE;
611
612 if (!S_ISREG(stat_buf.st_mode))
613 return CF_ERROR_FILE;
614
615 if (stat_buf.st_size == 0)
616 return CF_ERROR_PARSE;
617
618 config->flags = flags;
619
620 do {
621 bytes_read = read(fd, read_buf, sizeof(read_buf));
622
623 if (bytes_read == 0) /* EOF */
624 break;
625
626 if (bytes_read < 0) {
627 if (errno == EINTR || errno == EAGAIN)
628 continue;
629
630 return CF_ERROR_PARSE;
631 }
632
633 /** \bug FIXME what happens if the last line is truncated ! */
634 ret = config_parse_data(config, read_buf, bytes_read);
635 } while (ret == CF_OK);
636
637 if (ret != CF_OK)
638 return ret;
639
640 config_parse_flush_buffer (config);
641
642 return ret;
643 }
644
645 /** Loads a key file from memory into an empty wzd_configfile_t structure.
646 *
647 * If the object cannot be created then the return value is non-zero.
648 */
config_load_from_data(wzd_configfile_t * config,const char * data,size_t length,unsigned long flags)649 int config_load_from_data (wzd_configfile_t * config, const char * data, size_t length, unsigned long flags)
650 {
651 int ret;
652
653 if (!config) return CF_ERROR_INVALID_ARGS;
654 if (!data) return CF_ERROR_INVALID_ARGS;
655 if (length == 0) return CF_ERROR_INVALID_ARGS;
656
657 if (length == (size_t)-1)
658 length = strlen (data);
659
660 #if 0
661 if (config->approximate_size > 0)
662 {
663 config_clear (config);
664 config_init (config);
665 }
666 #endif
667 config->flags = flags;
668
669 ret = config_parse_data (config, data, length);
670
671 if (ret) return ret;
672
673 config_parse_flush_buffer (config);
674
675 return ret;
676 }
677
678 /** outputs \a config as a wzd_string_t.
679 */
config_to_data(wzd_configfile_t * config,size_t * length)680 wzd_string_t * config_to_data (wzd_configfile_t * config, size_t * length)
681 {
682 wzd_string_t * data_string;
683 ListElmt * elmnt;
684 DListElmt * el;
685 wzd_configfile_group_t * group;
686 wzd_configfile_keyvalue_t * kv;
687
688 if (!config) return NULL;
689
690 data_string = str_allocate();
691
692 for (elmnt = list_head(config->groups); elmnt; elmnt = list_next(elmnt)) {
693 group = list_data(elmnt);
694
695 if (group->comment != NULL)
696 str_append_printf (data_string, "%s\n", group->comment->value);
697 if (group->name != NULL)
698 str_append_printf (data_string, "[%s]\n", group->name);
699
700 if (!group->values) continue;
701
702 for (el = dlist_head (group->values); el != NULL; el = dlist_next(el))
703 {
704 kv = dlist_data(el);
705
706 if (kv->key != NULL)
707 str_append_printf (data_string, "%s = %s\n", kv->key, kv->value);
708 else
709 str_append_printf (data_string, "%s\n", kv->value);
710 }
711 }
712
713 if (length)
714 *length = str_length(data_string);
715
716 return data_string;
717 }
718
719 /***************** static functions *****************/
720
_configfile_group_init(wzd_configfile_group_t * group)721 static void _configfile_group_init(wzd_configfile_group_t * group)
722 {
723 WZD_ASSERT_VOID(group != NULL);
724 group->name = NULL;
725 group->comment = NULL;
726 group->values = wzd_malloc(sizeof(List));
727 dlist_init(group->values,(void (*)(void*))_configfile_keyvalue_free);
728 group->values->test = (int (*)(const void*,const void*))_config_cmp_keyvalue;
729 }
730
_configfile_group_free(wzd_configfile_group_t * group)731 static void _configfile_group_free(wzd_configfile_group_t * group)
732 {
733 WZD_ASSERT_VOID(group != NULL);
734 wzd_free(group->name);
735 if (group->comment) _configfile_keyvalue_free(group->comment);
736 dlist_destroy(group->values);
737 wzd_free(group->values);
738 wzd_free(group);
739 }
740
_configfile_keyvalue_calloc(void)741 static wzd_configfile_keyvalue_t * _configfile_keyvalue_calloc(void)
742 {
743 wzd_configfile_keyvalue_t * kv;
744
745 kv = wzd_malloc(sizeof(*kv));
746 WZD_ASSERT_RETURN(kv != NULL, NULL);
747 kv->key = NULL;
748 kv->value = NULL;
749
750 return kv;
751 }
752
_configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv)753 static void _configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv)
754 {
755 WZD_ASSERT_VOID(kv != NULL);
756 wzd_free(kv->key);
757 wzd_free(kv->value);
758 wzd_free(kv);
759 }
760
config_init(wzd_configfile_t * config)761 static void config_init(wzd_configfile_t * config)
762 {
763 wzd_configfile_group_t * group;
764
765 if (!config) return;
766 config->groups = wzd_malloc(sizeof(List));
767 list_init(config->groups,(void (*)(void *))_configfile_group_free);
768 group = wzd_malloc(sizeof(wzd_configfile_group_t));
769 _configfile_group_init(group);
770 list_ins_next(config->groups,NULL,group);
771 config->groups->test = (int (*)(const void*,const void*))_config_cmp_groupname;
772 config->parse_buffer = str_allocate();
773 config->current_group = group;
774 config->flags = CF_FILE_NONE;
775 }
776
config_clear(wzd_configfile_t * config)777 static void config_clear(wzd_configfile_t * config)
778 {
779 if (!config) return;
780 list_destroy(config->groups);
781 wzd_free(config->groups);
782 str_deallocate(config->parse_buffer);
783 }
784
_config_cmp_keyvalue(const char * k1,const wzd_configfile_keyvalue_t * k2)785 static int _config_cmp_keyvalue(const char *k1, const wzd_configfile_keyvalue_t *k2)
786 {
787 WZD_ASSERT(k2 != NULL);
788 if (k1 == NULL || k2->key == NULL) return (!(k1 == k2->key));
789
790 return strcmp(k1,k2->key);
791 }
792
_config_cmp_groupname(const char * k1,const wzd_configfile_group_t * k2)793 static int _config_cmp_groupname(const char *k1, const wzd_configfile_group_t *k2)
794 {
795 WZD_ASSERT(k2 != NULL);
796 if (k1 == NULL || k2->name == NULL) return (!(k1 == k2->name));
797
798 return strcmp(k1,k2->name);
799 }
800
config_line_is_comment(const char * line)801 static int config_line_is_comment(const char * line)
802 {
803 return (*line == '#' || *line == '\0' || *line == '\n');
804 }
805
config_line_is_group(const char * line)806 static int config_line_is_group(const char * line)
807 {
808 const char * p;
809
810 p = line;
811 if (*p != '[') return 0;
812
813 while (*p && *p != ']')
814 p++;
815
816 if (!*p) return 0;
817
818 return 1;
819 }
820
config_line_is_keyvalue(const char * line)821 static int config_line_is_keyvalue(const char * line)
822 {
823 const char * p;
824
825 p = strchr(line,'=');
826 if (!p) return 0;
827
828 /* key must be non-empty */
829 if (*p == line[0])
830 return 0;
831
832 return 1;
833 }
834
config_parse_data(wzd_configfile_t * config,const char * data,size_t length)835 static int config_parse_data(wzd_configfile_t * config, const char * data, size_t length)
836 {
837 size_t i;
838 int ret;
839
840 if (!config || !data) return CF_ERROR_INVALID_ARGS;
841
842 for (i = 0; i < length; i++) {
843 if (data[i] == '\n')
844 {
845 if (i > 0 && str_length(config->parse_buffer) > 0 &&
846 str_tochar(config->parse_buffer)[str_length(config->parse_buffer) - 1] == '\r')
847 str_erase (config->parse_buffer, str_length(config->parse_buffer) - 1, 1);
848
849 /* if the line is ended with a \ then delete the last char and continue with next line
850 */
851 if (i > 0 && str_length(config->parse_buffer) > 0 &&
852 str_tochar(config->parse_buffer)[str_length(config->parse_buffer) - 1] == '\\') {
853 str_erase (config->parse_buffer, str_length(config->parse_buffer) - 1, 1);
854 continue;
855 }
856
857 /* When a newline is encountered flush the parse buffer so that the
858 * line can be parsed. Note that completely blank lines won't show
859 * up in the parse buffer, so they get parsed directly.
860 */
861 if (str_length(config->parse_buffer) > 0)
862 ret = config_parse_flush_buffer (config);
863 else
864 ret = config_parse_comment (config, "", 1);
865
866 if (ret) return ret; /* propagate error */
867 } else
868 str_append_c (config->parse_buffer, data[i]);
869 }
870
871 return CF_OK;
872 }
873
config_parse_flush_buffer(wzd_configfile_t * config)874 static int config_parse_flush_buffer(wzd_configfile_t * config)
875 {
876 int ret;
877
878 if (!config) return CF_ERROR_INVALID_ARGS;
879
880 #if DEBUG
881 if ((config->flags & CF_FILE_DEBUG)) {
882 out_err(LEVEL_INFO,"flushing buffer : [ %s ]\n",str_tochar(config->parse_buffer));
883 }
884 #endif
885
886 if (str_length(config->parse_buffer) > 0) {
887 ret = config_parse_line (config, str_tochar(config->parse_buffer), str_length(config->parse_buffer));
888 str_erase (config->parse_buffer, 0, -1);
889
890 #if DEBUG
891 if ((config->flags & CF_FILE_DEBUG)) {
892 if (ret) {
893 out_err(LEVEL_INFO,"ERROR: config_parse_line returned %d !\n",ret);
894 }
895 }
896 #endif
897
898 if (ret) return ret;
899 }
900
901 return CF_OK;
902 }
903
config_parse_line(wzd_configfile_t * config,const char * line,size_t length)904 static int config_parse_line(wzd_configfile_t * config, const char * line, size_t length)
905 {
906 const char * line_start;
907 int ret;
908
909 if (!config || !line) return CF_ERROR_INVALID_ARGS;
910
911 line_start = line;
912 while (isspace(*line_start)) line_start++;
913
914 if (config_line_is_comment(line_start))
915 ret = config_parse_comment(config,line,length);
916 else if (config_line_is_group(line_start))
917 ret = config_parse_group(config,line,length - (line_start - line));
918 else if (config_line_is_keyvalue(line_start))
919 ret = config_parse_keyvalue(config,line,length - (line_start - line));
920 else
921 return CF_ERROR_PARSE;
922
923 if (ret) return ret; /* propagate error */
924
925 return 0;
926 }
927
config_parse_comment(wzd_configfile_t * config,const char * line,size_t length)928 static int config_parse_comment(wzd_configfile_t * config, const char * line, size_t length)
929 {
930 wzd_configfile_keyvalue_t * kv;
931
932 if (!config || !line) return CF_ERROR_INVALID_ARGS;
933 if (!config->current_group) return CF_ERROR_NO_CURRENT_GROUP;
934
935 kv = _configfile_keyvalue_calloc();
936 kv->value = wzd_strndup(line,length);
937
938 dlist_ins_next(config->current_group->values,dlist_tail(config->current_group->values),kv);
939
940 return CF_OK;
941 }
942
config_parse_group(wzd_configfile_t * config,const char * line,size_t length)943 static int config_parse_group(wzd_configfile_t * config, const char * line, size_t length)
944 {
945 char * groupname;
946 const char *group_name_start, *group_name_end;
947
948 if (!config || !line) return CF_ERROR_INVALID_ARGS;
949
950 /* advance past opening '[' */
951 group_name_start = line + 1;
952 group_name_end = line + length - 1;
953
954 while (*group_name_end != ']')
955 group_name_end--;
956
957 groupname = wzd_strndup(group_name_start,group_name_end - group_name_start);
958 config_add_group(config,groupname);
959 wzd_free(groupname);
960
961 return CF_OK;
962 }
963
config_parse_keyvalue(wzd_configfile_t * config,const char * line,size_t length)964 static int config_parse_keyvalue(wzd_configfile_t * config, const char * line, size_t length)
965 {
966 char *key, *value, *key_end, *value_start;
967 size_t key_len, value_len;
968 int ret;
969
970 if (!config || !line) return CF_ERROR_INVALID_ARGS;
971 if (!config->current_group || !config->current_group->name) return CF_ERROR_NO_CURRENT_GROUP;
972
973 key_end = value_start = strchr (line, '=');
974 if (key_end == NULL) return CF_ERROR_PARSE;
975
976 key_end--;
977 value_start++;
978
979 /* Pull the key name from the line (chomping trailing whitespace) */
980 while (isspace (*key_end))
981 key_end--;
982
983 key_len = key_end - line + 2;
984 if (key_len > length) return CF_ERROR_PARSE;
985
986 key = wzd_strndup (line, key_len - 1);
987
988 /* Pull the value from the line (chugging leading whitespace)
989 */
990 while (isspace (*value_start))
991 value_start++;
992
993 value_len = line + length - value_start;
994 while (value_len > 0 && isspace(value_start[value_len-1]))
995 value_len--;
996
997 value = wzd_strndup (value_start, value_len);
998
999 /* if (config->start_group == NULL) return CF_ERROR_PARSE; */
1000
1001 ret = config_add_key(config,config->current_group,key,value);
1002
1003 wzd_free(key);
1004 wzd_free(value);
1005
1006 return ret;
1007 }
1008
config_lookup_group(const wzd_configfile_t * config,const char * groupname)1009 static wzd_configfile_group_t * config_lookup_group(const wzd_configfile_t * config, const char *groupname)
1010 {
1011 ListElmt * elmnt;
1012 wzd_configfile_group_t * group = NULL;
1013
1014 if (!config || !groupname) return NULL;
1015
1016 for (elmnt = list_head(config->groups); elmnt; elmnt = list_next(elmnt)) {
1017 group = list_data(elmnt);
1018 if (group && group->name && strcmp(group->name,groupname)==0) break;
1019 group = NULL;
1020 }
1021
1022 return group;
1023 }
1024
config_lookup_keyvalue(const wzd_configfile_t * config,wzd_configfile_group_t * group,const char * key)1025 static wzd_configfile_keyvalue_t * config_lookup_keyvalue(const wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key)
1026 {
1027 DListElmt * elmnt;
1028 wzd_configfile_keyvalue_t * kv = NULL;
1029
1030 if (!config || !group || !key) return NULL;
1031
1032 /** \todo this should be replaced by a direct lookup in a hash table */
1033 for (elmnt = dlist_head(group->values); elmnt; elmnt = dlist_next(elmnt)) {
1034 kv = dlist_data(elmnt);
1035 if (kv && kv->key && strcmp(kv->key,key)==0) break;
1036 kv = NULL;
1037 }
1038
1039 return kv;
1040 }
1041
config_add_key(wzd_configfile_t * config,wzd_configfile_group_t * group,const char * key,const char * value)1042 static int config_add_key(wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key, const char * value)
1043 {
1044 wzd_configfile_keyvalue_t * kv;
1045
1046 if (!config || !group) return CF_ERROR_INVALID_ARGS;
1047
1048 if ( (kv = config_lookup_keyvalue(config, group, key)) != NULL)
1049 {
1050 if (config->flags & CF_FILE_MERGE_MULTIPLE) {
1051 wzd_string_t * str;
1052
1053 str = STR(kv->value);
1054 str_append_printf(str, "%s %s", VALUE_LIST_SEPARATOR, value);
1055 wzd_free(kv->value);
1056 kv->value = wzd_strdup( str_tochar(str) );
1057 str_deallocate(str);
1058
1059 return CF_OK;
1060 }
1061 #if DEBUG
1062 out_err(LEVEL_HIGH,"*** key collision *** %s/%s: old [%s] / new [%s]\n",
1063 group->name, key, kv->value, value);
1064 #endif
1065 return CF_ERROR_KEY_ALREADY_EXISTS;
1066 }
1067
1068 kv = _configfile_keyvalue_calloc();
1069 kv->key = wzd_strdup(key);
1070 kv->value = wzd_strdup(value);
1071
1072 dlist_ins_next(group->values,dlist_tail(group->values),kv);
1073
1074 return CF_OK;
1075 }
1076
config_add_group(wzd_configfile_t * config,const char * groupname)1077 static int config_add_group(wzd_configfile_t * config, const char * groupname)
1078 {
1079 wzd_configfile_group_t * group;
1080
1081 if (!config) return CF_ERROR_INVALID_ARGS;
1082
1083 group = wzd_malloc(sizeof(wzd_configfile_group_t));
1084 _configfile_group_init(group);
1085 group->name = wzd_strdup(groupname);
1086 list_ins_next(config->groups,list_tail(config->groups),group);
1087
1088 config->current_group = group;
1089
1090 return CF_OK;
1091 }
1092
config_set_key_comment(wzd_configfile_t * config,const char * groupname,const char * key,const char * comment)1093 static int config_set_key_comment(wzd_configfile_t * config, const char * groupname, const char * key, const char * comment)
1094 {
1095 wzd_configfile_group_t * group;
1096 wzd_configfile_keyvalue_t * kv;
1097 DListElmt * element, * tmp, * current_node;
1098
1099 if (!config || !groupname) return CF_ERROR_INVALID_ARGS;
1100
1101 group = config_lookup_group(config,groupname);
1102 if (!group) return CF_ERROR_GROUP_NOT_FOUND;
1103
1104 /* find the key the comments are supposed to be associated with */
1105 element = dlist_lookup_node(group->values,(void*)key);
1106 if (!element) return CF_ERROR_NOT_FOUND;
1107
1108 /* free existing comments for that key */
1109 tmp = element->prev;
1110 while (tmp) {
1111 kv = dlist_data(tmp);
1112
1113 if (kv->key) break;
1114
1115 current_node = tmp;
1116 tmp = tmp->prev;
1117 dlist_remove(group->values,current_node,(void**)&kv);
1118 _configfile_keyvalue_free(kv);
1119 }
1120
1121 if (comment == NULL) return CF_OK;
1122
1123 /* add our comment */
1124 kv = _configfile_keyvalue_calloc();
1125 kv->value = wzd_strdup(comment);
1126
1127 dlist_ins_prev(group->values,element,kv);
1128
1129
1130 return CF_ERROR_PARSE;
1131 }
1132
config_set_group_comment(wzd_configfile_t * config,const char * groupname,const char * comment)1133 static int config_set_group_comment(wzd_configfile_t * config, const char * groupname, const char * comment)
1134 {
1135 wzd_configfile_group_t * group;
1136
1137 if (!config || !groupname) return CF_ERROR_INVALID_ARGS;
1138
1139 group = config_lookup_group(config,groupname);
1140 if (!group) return CF_ERROR_GROUP_NOT_FOUND;
1141
1142 /* remove any existing comment */
1143 if (group->comment) {
1144 _configfile_keyvalue_free(group->comment);
1145 group->comment = NULL;
1146 }
1147
1148 if (!comment) return CF_OK;
1149
1150 if (config_line_is_comment(comment)) {
1151 group->comment = wzd_malloc(sizeof(wzd_configfile_group_t));
1152 group->comment->key = NULL;
1153 group->comment->value = wzd_strdup(comment);
1154
1155 return CF_OK;
1156 }
1157
1158 return CF_ERROR_PARSE;
1159 }
1160
config_set_top_comment(wzd_configfile_t * config,const char * comment)1161 static int config_set_top_comment(wzd_configfile_t * config, const char * comment)
1162 {
1163 ListElmt * elmnt;
1164 wzd_configfile_group_t * group;
1165 wzd_configfile_keyvalue_t * kv;
1166
1167 if (!config->groups) return CF_ERROR_INVALID_ARGS;
1168
1169 elmnt = list_head(config->groups);
1170 group = list_data(elmnt);
1171 /* the last group is for comments only */
1172 if (!group || group->name) return CF_ERROR_INVALID_ARGS;
1173
1174 WZD_ASSERT(group->values != NULL);
1175
1176 while (dlist_size(group->values)>0) {
1177 dlist_remove(group->values,dlist_tail(group->values),(void**)&kv);
1178 _configfile_keyvalue_free(kv);
1179 }
1180
1181 if (!comment)
1182 return CF_OK;
1183
1184 if (config_line_is_comment(comment)) {
1185 kv = _configfile_keyvalue_calloc();
1186 kv->value = wzd_strdup(comment);
1187
1188 dlist_ins_next(group->values,NULL,kv);
1189
1190 return CF_OK;
1191 }
1192
1193 return CF_ERROR_PARSE;
1194 }
1195
1196