canfigger v0.3.1
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, strerror(errno));
294 return NULL;
295 }
296
297 if (fseek(fp, 0, SEEK_END) != 0 || (file_size = ftell(fp)) < 0 || fseek(fp, 0, SEEK_SET) != 0)
298 {
299 fprintf(stderr, "canfigger: Failed to determine size of %s: %s\n", filename, strerror(errno));
300 goto done;
301 }
302
303 buffer = malloc_wrap(file_size + 1);
304 if (!buffer)
305 goto done;
306
307 n_bytes = fread(buffer, 1, file_size, fp);
308 if (n_bytes != (size_t) file_size)
309 {
310 if (ferror(fp))
311 fprintf(stderr, "canfigger: Error reading %s: %s\n", filename, strerror(errno));
312 else
313 fprintf(stderr, "canfigger: Partial read of %s: expected %ld bytes, got %zu bytes\n",
314 filename, file_size, n_bytes);
315 free(buffer);
316 buffer = NULL;
317 }
318 else
319 buffer[file_size] = '\0';
320
321done:
322 fclose(fp);
323 return buffer;
324}
325
326
327static void
328free_incomplete_node(struct Canfigger **node)
329{
330 if (*node)
331 {
332 if ((*node)->key)
333 free((*node)->key);
334
335 if ((*node)->value)
336 free((*node)->value);
337
338 if ((*node)->attributes)
339 {
340 free((*node)->attributes->str);
341 free((*node)->attributes);
342 }
343 }
344 free(*node);
345
346 return;
347}
348
349
350struct Canfigger *
351canfigger_parse_file(const char *file, const int delimiter)
352{
353 struct Canfigger *root = NULL, *cur_node = NULL;
354
355 char *file_contents = read_entire_file(file);
356 if (file_contents == NULL)
357 return NULL;
358
359 /* Skip UTF-8 BOM (EF BB BF) if present. Some editors (especially on Windows)
360 * prepend these three bytes silently; without this check they would be
361 * prepended to the first key, corrupting any strcmp against it.
362 * Array accesses are safe: read_entire_file allocates file_size+1 bytes with
363 * a null terminator, and && short-circuits — [1] is only read when [0]
364 * matched 0xEF (so at least 1 file byte exists), and [2] only when [1]
365 * matched 0xBB (so at least 2 exist). */
366 char *parse_start = file_contents;
367 if ((unsigned char)parse_start[0] == 0xEF &&
368 (unsigned char)parse_start[1] == 0xBB &&
369 (unsigned char)parse_start[2] == 0xBF)
370 parse_start += 3;
371
372 struct line line;
373 line.start = parse_start;
374
375 bool node_complete;
376
377 for (;;)
378 {
379 line.end = strchr(line.start, '\n');
380 line.len = line.end ? (size_t)(line.end - line.start) : strlen(line.start);
381
382 /* End of file with no remaining content */
383 if (line.len == 0 && !line.end)
384 break;
385
386 char *tmp_line = malloc_wrap(line.len + 1);
387 if (!tmp_line) {
388 canfigger_free_list(&root);
389 free(file_contents);
390 return NULL;
391 }
392
393 memcpy(tmp_line, line.start, line.len);
394 tmp_line[line.len] = '\0';
395 line.start = line.end ? line.end + 1 : line.start + line.len;
396
397 char *line_ptr = tmp_line;
398 truncate_whitespace(line_ptr);
399
400 while (isspace((unsigned char)*line_ptr))
401 line_ptr = erase_lead_char(*line_ptr, line_ptr);
402
403 if (*line_ptr == '\0' || *line_ptr == '#' || *line_ptr == '[') {
404 free(tmp_line);
405 if (!line.end)
406 break;
407 continue;
408 }
409
410 node_complete = false;
411 struct Canfigger *prev_node = cur_node;
412 add_key_node(&root, &cur_node);
413 if (cur_node == prev_node) {
414 free(tmp_line);
415 break;
416 }
417
418 // Get key
419 cur_node->key = NULL;
420 line_ptr = grab_str_segment(line_ptr, &cur_node->key, '=');
421 if (!cur_node->key)
422 {
423 free(tmp_line);
424 free_incomplete_node(&cur_node);
425 break;
426 }
427
428 // Get value
429 cur_node->value = NULL;
430
431 if (line_ptr)
432 {
433 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
434 if (!cur_node->value)
435 {
436 free(tmp_line);
437 free_incomplete_node(&cur_node);
438 break;
439 }
440 }
441
442 // Handle attributes
443 if (line_ptr)
444 {
445 cur_node->attributes = malloc_wrap(sizeof(struct attributes));
446 if (!cur_node->attributes)
447 {
448 free(tmp_line);
449 free_incomplete_node(&cur_node);
450 break;
451 }
452
453 struct attributes *attr_ptr = cur_node->attributes;
454 attr_ptr->current = NULL;
455
456 attr_ptr->str = strclone(line_ptr, 0);
457 if (!attr_ptr->str)
458 {
459 free(tmp_line);
460 free_incomplete_node(&cur_node);
461 break;
462 }
463
464 attr_ptr->iter_ptr = attr_ptr->str;
465
466 // Change the delimiter, which will be used later
467 // in canfigger_free_current_attr_str_advance()
468 char *delimiter_ptr = strchr(attr_ptr->iter_ptr, delimiter);
469 while (delimiter_ptr)
470 {
471 *delimiter_ptr = '\n';
472 delimiter_ptr = strchr(delimiter_ptr, delimiter);
473 }
474 }
475 else
476 cur_node->attributes = NULL;
477
478 cur_node->next = NULL;
479 node_complete = true;
480 free(tmp_line);
481 if (!line.end)
482 break;
483 }
484
485 if (!root) {
486 free(file_contents);
487 return NULL;
488 }
489
490 if (!node_complete)
491 {
492 free(file_contents);
493 canfigger_free_list(&root);
494 return NULL;
495 }
496
497 free(file_contents);
498 return root;
499}
500
501
502#ifdef _WIN32
503static char *
504dir_for_appname(const char *appname, int csidl)
505{
506 if (!appname || *appname == '\0')
507 return NULL;
508
509 char base[MAX_PATH];
510 if (FAILED(SHGetFolderPathA(NULL, csidl, NULL, 0, base)))
511 return NULL;
512
513 size_t len = strlen(base) + 1 + strlen(appname) + 1;
514 char *result = malloc_wrap(len);
515 if (!result)
516 return NULL;
517 snprintf(result, len, "%s\\%s", base, appname);
518 return result;
519}
520#else
521static char *
522dir_for_appname(const char *appname, const char *xdg_env, const char *xdg_fallback)
523{
524 if (!appname || *appname == '\0')
525 return NULL;
526
527 const char *base = getenv(xdg_env);
528 if (base && *base)
529 {
530 size_t len = strlen(base) + 1 + strlen(appname) + 1;
531 char *result = malloc_wrap(len);
532 if (!result)
533 return NULL;
534 snprintf(result, len, "%s/%s", base, appname);
535 return result;
536 }
537
538 const char *home = getenv("HOME");
539 if (!home || !*home)
540 return NULL;
541
542 size_t len = strlen(home) + 1 + strlen(xdg_fallback) + 1 + strlen(appname) + 1;
543 char *result = malloc_wrap(len);
544 if (!result)
545 return NULL;
546 snprintf(result, len, "%s/%s/%s", home, xdg_fallback, appname);
547 return result;
548}
549#endif
550
551
552char *
553canfigger_config_dir(const char *appname)
554{
555#ifdef _WIN32
556 return dir_for_appname(appname, CSIDL_APPDATA);
557#else
558 return dir_for_appname(appname, "XDG_CONFIG_HOME", ".config");
559#endif
560}
561
562
563char *
564canfigger_data_dir(const char *appname)
565{
566#ifdef _WIN32
567 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA);
568#else
569 return dir_for_appname(appname, "XDG_DATA_HOME", ".local/share");
570#endif
571}
572
573
574char *
575canfigger_path_join(const char *dir, const char *file)
576{
577 if (!dir || !*dir || !file || !*file)
578 return NULL;
579
580 size_t dirlen = strlen(dir);
581 size_t filelen = strlen(file);
582 bool needs_sep = dirlen > 0 && dir[dirlen - 1] != '/' && dir[dirlen - 1] != '\\';
583 size_t total = dirlen + (needs_sep ? 1 : 0) + filelen + 1;
584
585 char *result = malloc_wrap(total);
586 if (!result)
587 return NULL;
588
589#ifdef _WIN32
590 const char sep = '\\';
591#else
592 const char sep = '/';
593#endif
594
595 if (needs_sep)
596 snprintf(result, total, "%s%c%s", dir, sep, file);
597 else
598 snprintf(result, total, "%s%s", dir, file);
599
600 return result;
601}
Public API for the Canfigger configuration file parser.
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:351
char * canfigger_data_dir(const char *appname)
Return the platform data directory for an application.
Definition canfigger.c:564
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:553
char * canfigger_path_join(const char *dir, const char *file)
Join a directory path and a filename with the platform separator.
Definition canfigger.c:575
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:126
Internal iteration state for a node's attribute list.
Definition canfigger.h:104