canfigger v0.3.2
Lightweight config file parser library
Loading...
Searching...
No Matches
canfigger.c
1/*
2This file is part of canfigger<https://github.com/andy5995/canfigger>
3
4MIT License
5
6Copyright (c) 2024 Andy Alt(arch_stanton5995@proton.me)
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in all
16copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25*/
26
27#include <ctype.h> // isspace()
28#include <errno.h>
29#include <stdbool.h>
30#include <stdio.h>
31#include <stdlib.h> // free(), malloc()
32#include <string.h>
33
34#ifdef _WIN32
35#include <windows.h>
36#include <shlobj.h>
37#endif
38
39// This is only required for version info and can be removed
40// if you're copying the canfigger source files to use as
41// an embedded library with your own project (i.e., not building
42// canfigger with the build system it's shipped with).
43#include "canfigger_version.h"
44
45#include "canfigger.h"
46
47static char *grab_str_segment(char *a, char **dest, const int c);
48
50struct line
51{
52 size_t len;
53 char *start;
54 char *end;
55};
57
58
59static char *
60strclone(const char *src, size_t n)
61{
62 char *dest = NULL;
63 if (n == 0)
64 {
65 dest = malloc(strlen(src) + 1);
66 if (dest)
67 strcpy(dest, src);
68 }
69 else
70 {
71 dest = malloc(n + 1);
72 if (dest)
73 {
74 memcpy(dest, src, n);
75 dest[n] = '\0';
76 }
77 }
78
79 if (!dest)
80 perror("canfigger: malloc");
81 return dest;
82}
83
84
85void
86canfigger_free_current_attr_str_advance(struct attributes *attributes,
87 char **attr)
88{
89 if (!attributes)
90 {
91 *attr = NULL;
92 return;
93 }
94
95 if (attributes->current && attributes->iter_ptr)
96 free(attributes->current);
97
98 if (!attributes->iter_ptr)
99 {
100 free(attributes->current);
101 attributes->current = NULL;
102 *attr = NULL;
103 return;
104 }
105
106 attributes->iter_ptr = grab_str_segment(attributes->iter_ptr,
107 &attributes->current, '\n');
108
109 if (*attributes->current)
110 {
111 *attr = attributes->current;
112 return;
113 }
114
115 // If we're here, that means strdup() failed to allocate memory in grab_str_segment()
116 // If an expected attribute isn't returned, the caller may want to terminate
117 // the remainder of the loop that's iterating through the entire linked list
118 // and exit the program.
119 *attr = NULL;
120 return;
121}
122
123
124void
125canfigger_free_current_key_node_advance(struct Canfigger **node)
126{
127 if (*node)
128 {
129 if ((*node)->attributes)
130 {
131 if ((*node)->attributes->current)
132 {
133 free((*node)->attributes->current);
134 (*node)->attributes->current = NULL;
135 }
136
137 if ((*node)->attributes->str)
138 {
139 free((*node)->attributes->str);
140 (*node)->attributes->str = NULL;
141 }
142
143 free((*node)->attributes);
144 (*node)->attributes = NULL;
145 }
146
147 if ((*node)->value)
148 {
149 free((*node)->value);
150 (*node)->value = NULL;
151 }
152
153 free((*node)->key);
154 (*node)->key = NULL;
155
156 struct Canfigger *temp_node = (*node)->next;
157 free(*node);
158 *node = temp_node;
159 }
160
161 return;
162}
163
164
165void
167{
168 if (*node)
169 {
170 while (*node)
172 }
173
174 return;
175}
176
177
178/*
179 * returns a pointer to the first character after lc
180 * If lc appears more than once, the pointer
181 * will move past that as well.
182 *
183 * Ex1: "__Hello World": the pointer will be set to the 'H'.
184 * Ex2: "_H_ello World": Again, the pointer will be set to the 'H'.
185 */
186static char *
187erase_lead_char(const int lc, char *haystack)
188{
189 char *ptr = haystack;
190 if (*ptr != lc)
191 return ptr;
192
193 while (*ptr == lc)
194 ptr++;
195
196 return ptr;
197}
198
199
200static void
201truncate_whitespace(char *str)
202{
203 if (!str)
204 return;
205
206 char *pos_0 = str;
207 /* Advance pointer until NULL terminator is found
208 * Don't try to use strchr() because you'll get a different
209 * result if the pointer is already at '\0'. */
210 while (*str != '\0')
211 str++;
212
213 /* set pointer to segment preceding NULL terminator */
214 if (str != pos_0)
215 str--;
216 else
217 return;
218
219 while (isspace((unsigned char) *str))
220 {
221 *str = '\0';
222 if (str != pos_0)
223 str--;
224 else
225 break;
226 }
227
228 return;
229}
230
231
232static char *
233grab_str_segment(char *a, char **dest, const int c)
234{
235 a = erase_lead_char(' ', a);
236
237 char *b = strchr(a, c);
238 if (!b)
239 {
240 *dest = strclone(a, 0);
241 return b;
242 }
243
244 size_t len = b - a;
245 *dest = strclone(a, len);
246 if (!*dest)
247 return NULL;
248
249 truncate_whitespace(*dest);
250 return b + 1;
251}
252
253static void *
254malloc_wrap(size_t size)
255{
256 void *retval = malloc(size);
257 if (retval)
258 return retval;
259
260 perror("canfigger: malloc");
261
262 return NULL;
263}
264
265static void
266add_key_node(struct Canfigger **root, struct Canfigger **cur_node)
267{
268 struct Canfigger *tmp_node = malloc_wrap(sizeof(struct Canfigger));
269 if (!tmp_node)
270 return;
271
272 if (*root)
273 (*cur_node)->next = tmp_node;
274 else
275 *root = tmp_node;
276
277 *cur_node = tmp_node;
278
279 return;
280}
281
282
283static char *
284read_entire_file(const char *filename)
285{
286 char *buffer = NULL;
287 long file_size;
288 size_t n_bytes;
289
290 FILE *fp = fopen(filename, "rb");
291 if (!fp)
292 {
293 fprintf(stderr, "canfigger: Failed to open %s: %s\n", filename,
294 strerror(errno));
295 return NULL;
296 }
297
298 if (fseek(fp, 0, SEEK_END) != 0 || (file_size = ftell(fp)) < 0
299 || fseek(fp, 0, SEEK_SET) != 0)
300 {
301 fprintf(stderr, "canfigger: Failed to determine size of %s: %s\n",
302 filename, strerror(errno));
303 goto done;
304 }
305
306 buffer = malloc_wrap(file_size + 1);
307 if (!buffer)
308 goto done;
309
310 n_bytes = fread(buffer, 1, file_size, fp);
311 if (n_bytes != (size_t) file_size)
312 {
313 if (ferror(fp))
314 fprintf(stderr, "canfigger: Error reading %s: %s\n", filename,
315 strerror(errno));
316 else
317 fprintf(stderr,
318 "canfigger: Partial read of %s: expected %ld bytes, got %zu bytes\n",
319 filename, file_size, n_bytes);
320 free(buffer);
321 buffer = NULL;
322 }
323 else
324 buffer[file_size] = '\0';
325
326done:
327 fclose(fp);
328 return buffer;
329}
330
331
332static void
333free_incomplete_node(struct Canfigger **node)
334{
335 if (*node)
336 {
337 if ((*node)->key)
338 free((*node)->key);
339
340 if ((*node)->value)
341 free((*node)->value);
342
343 if ((*node)->attributes)
344 {
345 free((*node)->attributes->str);
346 free((*node)->attributes);
347 }
348 }
349 free(*node);
350
351 return;
352}
353
354
355struct Canfigger *
356canfigger_parse_file(const char *file, const int delimiter)
357{
358 struct Canfigger *root = NULL, *cur_node = NULL;
359
360 char *file_contents = read_entire_file(file);
361 if (file_contents == NULL)
362 return NULL;
363
364 /* Skip UTF-8 BOM (EF BB BF) if present. Some editors (especially on Windows)
365 * prepend these three bytes silently; without this check they would be
366 * prepended to the first key, corrupting any strcmp against it.
367 * Array accesses are safe: read_entire_file allocates file_size+1 bytes with
368 * a null terminator, and && short-circuits — [1] is only read when [0]
369 * matched 0xEF (so at least 1 file byte exists), and [2] only when [1]
370 * matched 0xBB (so at least 2 exist). */
371 char *parse_start = file_contents;
372 if ((unsigned char) parse_start[0] == 0xEF &&
373 (unsigned char) parse_start[1] == 0xBB &&
374 (unsigned char) parse_start[2] == 0xBF)
375 parse_start += 3;
376
377 struct line line;
378 line.start = parse_start;
379
380 bool node_complete;
381
382 for (;;)
383 {
384 line.end = strchr(line.start, '\n');
385 line.len =
386 line.end ? (size_t) (line.end - line.start) : strlen(line.start);
387
388 /* End of file with no remaining content */
389 if (line.len == 0 && !line.end)
390 break;
391
392 char *tmp_line = malloc_wrap(line.len + 1);
393 if (!tmp_line)
394 {
395 canfigger_free_list(&root);
396 free(file_contents);
397 return NULL;
398 }
399
400 memcpy(tmp_line, line.start, line.len);
401 tmp_line[line.len] = '\0';
402 line.start = line.end ? line.end + 1 : line.start + line.len;
403
404 char *line_ptr = tmp_line;
405 truncate_whitespace(line_ptr);
406
407 while (isspace((unsigned char) *line_ptr))
408 line_ptr = erase_lead_char(*line_ptr, line_ptr);
409
410 if (*line_ptr == '\0' || *line_ptr == '#' || *line_ptr == '[')
411 {
412 free(tmp_line);
413 if (!line.end)
414 break;
415 continue;
416 }
417
418 node_complete = false;
419 struct Canfigger *prev_node = cur_node;
420 add_key_node(&root, &cur_node);
421 if (cur_node == prev_node)
422 {
423 free(tmp_line);
424 break;
425 }
426
427 // Get key
428 cur_node->key = NULL;
429 line_ptr = grab_str_segment(line_ptr, &cur_node->key, '=');
430 if (!cur_node->key)
431 {
432 free(tmp_line);
433 free_incomplete_node(&cur_node);
434 break;
435 }
436
437 // Get value
438 cur_node->value = NULL;
439
440 if (line_ptr)
441 {
442 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
443 if (!cur_node->value)
444 {
445 free(tmp_line);
446 free_incomplete_node(&cur_node);
447 break;
448 }
449 }
450
451 // Handle attributes
452 if (line_ptr)
453 {
454 cur_node->attributes = malloc_wrap(sizeof(struct attributes));
455 if (!cur_node->attributes)
456 {
457 free(tmp_line);
458 free_incomplete_node(&cur_node);
459 break;
460 }
461
462 struct attributes *attr_ptr = cur_node->attributes;
463 attr_ptr->current = NULL;
464
465 attr_ptr->str = strclone(line_ptr, 0);
466 if (!attr_ptr->str)
467 {
468 free(tmp_line);
469 free_incomplete_node(&cur_node);
470 break;
471 }
472
473 attr_ptr->iter_ptr = attr_ptr->str;
474
475 // Change the delimiter, which will be used later
476 // in canfigger_free_current_attr_str_advance()
477 char *delimiter_ptr = strchr(attr_ptr->iter_ptr, delimiter);
478 while (delimiter_ptr)
479 {
480 *delimiter_ptr = '\n';
481 delimiter_ptr = strchr(delimiter_ptr, delimiter);
482 }
483 }
484 else
485 cur_node->attributes = NULL;
486
487 cur_node->next = NULL;
488 node_complete = true;
489 free(tmp_line);
490 if (!line.end)
491 break;
492 }
493
494 if (!root)
495 {
496 free(file_contents);
497 return NULL;
498 }
499
500 if (!node_complete)
501 {
502 free(file_contents);
503 canfigger_free_list(&root);
504 return NULL;
505 }
506
507 free(file_contents);
508 return root;
509}
510
511
512#ifndef _WIN32
513static char *
514xdg_base_dir(const char *xdg_env, const char *fallback)
515{
516 const char *base = getenv(xdg_env);
517 if (base && *base)
518 return strclone(base, 0);
519
520 const char *home = getenv("HOME");
521 if (!home || !*home)
522 return NULL;
523
524 size_t len = strlen(home) + 1 + strlen(fallback) + 1;
525 char *result = malloc_wrap(len);
526 if (!result)
527 return NULL;
528 snprintf(result, len, "%s/%s", home, fallback);
529 return result;
530}
531#endif
532
533
534#ifdef _WIN32
535static char *
536dir_for_appname(const char *appname, int csidl)
537{
538 if (!appname || *appname == '\0')
539 return NULL;
540
541 char base[MAX_PATH];
542 if (FAILED(SHGetFolderPathA(NULL, csidl, NULL, 0, base)))
543 return NULL;
544
545 size_t len = strlen(base) + 1 + strlen(appname) + 1;
546 char *result = malloc_wrap(len);
547 if (!result)
548 return NULL;
549 snprintf(result, len, "%s\\%s", base, appname);
550 return result;
551}
552#else
553static char *
554dir_for_appname(const char *appname, const char *xdg_env,
555 const char *xdg_fallback)
556{
557 if (!appname || *appname == '\0')
558 return NULL;
559
560 char *base = xdg_base_dir(xdg_env, xdg_fallback);
561 if (!base)
562 return NULL;
563 char *result = canfigger_path_join(base, appname);
564 free(base);
565 return result;
566}
567#endif
568
569
570char *
571canfigger_config_dir(const char *appname)
572{
573#ifdef _WIN32
574 return dir_for_appname(appname, CSIDL_APPDATA);
575#else
576 return dir_for_appname(appname, "XDG_CONFIG_HOME", ".config");
577#endif
578}
579
580
581char *
582canfigger_config_file(const char *filename)
583{
584 if (!filename || !*filename)
585 return NULL;
586
587#ifdef _WIN32
588 char base[MAX_PATH];
589 if (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base)))
590 return NULL;
591
592 size_t len = strlen(base) + 1 + strlen(filename) + 1;
593 char *result = malloc_wrap(len);
594 if (!result)
595 return NULL;
596 snprintf(result, len, "%s\\%s", base, filename);
597 return result;
598#else
599 char *base = xdg_base_dir("XDG_CONFIG_HOME", ".config");
600 if (!base)
601 return NULL;
602 char *result = canfigger_path_join(base, filename);
603 free(base);
604 return result;
605#endif
606}
607
608
609char *
610canfigger_data_dir(const char *appname)
611{
612#ifdef _WIN32
613 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA);
614#else
615 return dir_for_appname(appname, "XDG_DATA_HOME", ".local/share");
616#endif
617}
618
619
620char *
621canfigger_cache_dir(const char *appname)
622{
623#ifdef _WIN32
624 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA);
625#else
626 return dir_for_appname(appname, "XDG_CACHE_HOME", ".cache");
627#endif
628}
629
630
631char *
632canfigger_path_join(const char *dir, const char *file)
633{
634 if (!dir || !*dir || !file || !*file)
635 return NULL;
636
637 size_t dirlen = strlen(dir);
638 size_t filelen = strlen(file);
639 bool needs_sep = dirlen > 0 && dir[dirlen - 1] != '/'
640 && dir[dirlen - 1] != '\\';
641 size_t total = dirlen + (needs_sep ? 1 : 0) + filelen + 1;
642
643 char *result = malloc_wrap(total);
644 if (!result)
645 return NULL;
646
647#ifdef _WIN32
648 const char sep = '\\';
649#else
650 const char sep = '/';
651#endif
652
653 if (needs_sep)
654 snprintf(result, total, "%s%c%s", dir, sep, file);
655 else
656 snprintf(result, total, "%s%s", dir, file);
657
658 return result;
659}
Public API for the Canfigger configuration file parser.
char * canfigger_config_file(const char *filename)
Return the path to a config file in the platform base config directory.
Definition canfigger.c:582
struct Canfigger * canfigger_parse_file(const char *file, const int delimiter)
Parse a configuration file into a linked list of key-value nodes.
Definition canfigger.c:356
char * canfigger_cache_dir(const char *appname)
Return the platform cache directory for an application.
Definition canfigger.c:621
char * canfigger_data_dir(const char *appname)
Return the platform data directory for an application.
Definition canfigger.c:610
void canfigger_free_list(struct Canfigger **node)
Free all remaining nodes in the list.
Definition canfigger.c:166
char * canfigger_config_dir(const char *appname)
Return the platform config directory for an application.
Definition canfigger.c:571
char * canfigger_path_join(const char *dir, const char *file)
Join a directory path and a filename with the platform separator.
Definition canfigger.c:632
void canfigger_free_current_key_node_advance(struct Canfigger **node)
Free the current node and advance the list pointer to the next node.
Definition canfigger.c:125
A single node in the parsed configuration linked list.
Definition canfigger.h:127
Internal iteration state for a node's attribute list.
Definition canfigger.h:105