canfigger v0.3.3
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#ifndef _WIN32
28/* stat() and getuid() are hidden by -std=c99 without this; canfigger_runtime_dir
29 needs them to check that the runtime directory is really ours. */
30#ifndef _POSIX_C_SOURCE
31#define _POSIX_C_SOURCE 200809L
32#endif
33#endif
34
35#include <ctype.h> // isspace()
36#include <errno.h>
37#include <stdbool.h>
38#include <stdio.h>
39#include <stdlib.h> // free(), malloc()
40#include <string.h>
41
42#ifdef _WIN32
43#include <windows.h>
44#include <shlobj.h>
45#else
46#include <sys/stat.h>
47#include <sys/types.h>
48#include <unistd.h>
49#endif
50
51// This is only required for version info and can be removed
52// if you're copying the canfigger source files to use as
53// an embedded library with your own project (i.e., not building
54// canfigger with the build system it's shipped with).
55#include "canfigger_version.h"
56
57#include "canfigger.h"
58
59static char *grab_str_segment(char *a, char **dest, const int c);
60
62struct line
63{
64 size_t len;
65 char *start;
66 char *end;
67};
71static char *
72strclone(const char *src, size_t n)
73{
74 char *dest = NULL;
75 if (n == 0)
76 {
77 dest = malloc(strlen(src) + 1);
78 if (dest)
79 strcpy(dest, src);
80 }
81 else
82 {
83 dest = malloc(n + 1);
84 if (dest)
85 {
86 memcpy(dest, src, n);
87 dest[n] = '\0';
88 }
89 }
90
91 if (!dest)
92 perror("canfigger: malloc");
93 return dest;
94}
95
96
97void
98canfigger_free_current_attr_str_advance(struct attributes *attributes,
99 char **attr)
100{
101 if (!attributes)
102 {
103 *attr = NULL;
104 return;
105 }
106
107 if (attributes->current && attributes->iter_ptr)
108 free(attributes->current);
109
110 if (!attributes->iter_ptr)
111 {
112 free(attributes->current);
113 attributes->current = NULL;
114 *attr = NULL;
115 return;
116 }
117
118 attributes->iter_ptr = grab_str_segment(attributes->iter_ptr,
119 &attributes->current, '\n');
120
121 if (*attributes->current)
122 {
123 *attr = attributes->current;
124 return;
125 }
126
127 // If we're here, that means strdup() failed to allocate memory in grab_str_segment()
128 // If an expected attribute isn't returned, the caller may want to terminate
129 // the remainder of the loop that's iterating through the entire linked list
130 // and exit the program.
131 *attr = NULL;
132 return;
133}
134
135
136void
137canfigger_free_current_key_node_advance(struct Canfigger **node)
138{
139 if (*node)
140 {
141 if ((*node)->attributes)
142 {
143 if ((*node)->attributes->current)
144 {
145 free((*node)->attributes->current);
146 (*node)->attributes->current = NULL;
147 }
148
149 if ((*node)->attributes->str)
150 {
151 free((*node)->attributes->str);
152 (*node)->attributes->str = NULL;
153 }
154
155 free((*node)->attributes);
156 (*node)->attributes = NULL;
157 }
158
159 if ((*node)->value)
160 {
161 free((*node)->value);
162 (*node)->value = NULL;
163 }
164
165 free((*node)->key);
166 (*node)->key = NULL;
167
168 struct Canfigger *temp_node = (*node)->next;
169 free(*node);
170 *node = temp_node;
171 }
172
173 return;
174}
175
176
177void
179{
180 if (*node)
181 {
182 while (*node)
184 }
185
186 return;
187}
188
189
190/*
191 * returns a pointer to the first character after lc
192 * If lc appears more than once, the pointer
193 * will move past that as well.
194 *
195 * Ex1: "__Hello World": the pointer will be set to the 'H'.
196 * Ex2: "_H_ello World": Again, the pointer will be set to the 'H'.
197 */
198static char *
199erase_lead_char(const int lc, char *haystack)
200{
201 char *ptr = haystack;
202 if (*ptr != lc)
203 return ptr;
204
205 while (*ptr == lc)
206 ptr++;
207
208 return ptr;
209}
210
211
212static void
213truncate_whitespace(char *str)
214{
215 if (!str)
216 return;
217
218 char *pos_0 = str;
219 /* Advance pointer until NULL terminator is found
220 * Don't try to use strchr() because you'll get a different
221 * result if the pointer is already at '\0'. */
222 while (*str != '\0')
223 str++;
224
225 /* set pointer to segment preceding NULL terminator */
226 if (str != pos_0)
227 str--;
228 else
229 return;
230
231 while (isspace((unsigned char) *str))
232 {
233 *str = '\0';
234 if (str != pos_0)
235 str--;
236 else
237 break;
238 }
239
240 return;
241}
242
243
244static char *
245grab_str_segment(char *a, char **dest, const int c)
246{
247 a = erase_lead_char(' ', a);
248
249 char *b = strchr(a, c);
250 if (!b)
251 {
252 *dest = strclone(a, 0);
253 return b;
254 }
255
256 size_t len = b - a;
257 *dest = strclone(a, len);
258 if (!*dest)
259 return NULL;
260
261 truncate_whitespace(*dest);
262 return b + 1;
263}
264
265static void *
266malloc_wrap(size_t size)
267{
268 void *retval = malloc(size);
269 if (retval)
270 return retval;
271
272 perror("canfigger: malloc");
273
274 return NULL;
275}
276
277static void
278add_key_node(struct Canfigger **root, struct Canfigger **cur_node)
279{
280 struct Canfigger *tmp_node = malloc_wrap(sizeof(struct Canfigger));
281 if (!tmp_node)
282 return;
283
284 if (*root)
285 (*cur_node)->next = tmp_node;
286 else
287 *root = tmp_node;
288
289 *cur_node = tmp_node;
290
291 return;
292}
293
294
295static char *
296read_entire_file(const char *filename)
297{
298 char *buffer = NULL;
299 long file_size;
300 size_t n_bytes;
301
302 FILE *fp = fopen(filename, "rb");
303 if (!fp)
304 {
305 fprintf(stderr, "canfigger: Failed to open %s: %s\n", filename,
306 strerror(errno));
307 return NULL;
308 }
309
310 if (fseek(fp, 0, SEEK_END) != 0 || (file_size = ftell(fp)) < 0
311 || fseek(fp, 0, SEEK_SET) != 0)
312 {
313 fprintf(stderr, "canfigger: Failed to determine size of %s: %s\n",
314 filename, strerror(errno));
315 goto done;
316 }
317
318 buffer = malloc_wrap(file_size + 1);
319 if (!buffer)
320 goto done;
321
322 n_bytes = fread(buffer, 1, file_size, fp);
323 if (n_bytes != (size_t) file_size)
324 {
325 if (ferror(fp))
326 fprintf(stderr, "canfigger: Error reading %s: %s\n", filename,
327 strerror(errno));
328 else
329 fprintf(stderr,
330 "canfigger: Partial read of %s: expected %ld bytes, got %zu bytes\n",
331 filename, file_size, n_bytes);
332 free(buffer);
333 buffer = NULL;
334 }
335 else
336 buffer[file_size] = '\0';
337
338done:
339 fclose(fp);
340 return buffer;
341}
342
343
344static void
345free_incomplete_node(struct Canfigger **node)
346{
347 if (*node)
348 {
349 if ((*node)->key)
350 free((*node)->key);
351
352 if ((*node)->value)
353 free((*node)->value);
354
355 if ((*node)->attributes)
356 {
357 free((*node)->attributes->str);
358 free((*node)->attributes);
359 }
360 }
361 free(*node);
362
363 return;
364}
365
366
367struct Canfigger *
368canfigger_parse_file(const char *file, const int delimiter)
369{
370 struct Canfigger *root = NULL, *cur_node = NULL;
371
372 char *file_contents = read_entire_file(file);
373 if (file_contents == NULL)
374 return NULL;
375
376 /* Skip UTF-8 BOM (EF BB BF) if present. Some editors (especially on Windows)
377 * prepend these three bytes silently; without this check they would be
378 * prepended to the first key, corrupting any strcmp against it.
379 * Array accesses are safe: read_entire_file allocates file_size+1 bytes with
380 * a null terminator, and && short-circuits — [1] is only read when [0]
381 * matched 0xEF (so at least 1 file byte exists), and [2] only when [1]
382 * matched 0xBB (so at least 2 exist). */
383 char *parse_start = file_contents;
384 if ((unsigned char) parse_start[0] == 0xEF &&
385 (unsigned char) parse_start[1] == 0xBB &&
386 (unsigned char) parse_start[2] == 0xBF)
387 parse_start += 3;
388
389 struct line line;
390 line.start = parse_start;
391
392 bool node_complete = false;
393
394 for (;;)
395 {
396 line.end = strchr(line.start, '\n');
397 line.len =
398 line.end ? (size_t) (line.end - line.start) : strlen(line.start);
399
400 /* End of file with no remaining content */
401 if (line.len == 0 && !line.end)
402 break;
403
404 char *tmp_line = malloc_wrap(line.len + 1);
405 if (!tmp_line)
406 {
407 canfigger_free_list(&root);
408 free(file_contents);
409 return NULL;
410 }
411
412 memcpy(tmp_line, line.start, line.len);
413 tmp_line[line.len] = '\0';
414 line.start = line.end ? line.end + 1 : line.start + line.len;
415
416 char *line_ptr = tmp_line;
417 truncate_whitespace(line_ptr);
418
419 while (isspace((unsigned char) *line_ptr))
420 line_ptr = erase_lead_char(*line_ptr, line_ptr);
421
422 if (*line_ptr == '\0' || *line_ptr == '#' || *line_ptr == '[')
423 {
424 free(tmp_line);
425 if (!line.end)
426 break;
427 continue;
428 }
429
430 node_complete = false;
431 struct Canfigger *prev_node = cur_node;
432 add_key_node(&root, &cur_node);
433 if (cur_node == prev_node)
434 {
435 free(tmp_line);
436 break;
437 }
438
439 // Get key
440 cur_node->key = NULL;
441 line_ptr = grab_str_segment(line_ptr, &cur_node->key, '=');
442 if (!cur_node->key)
443 {
444 free(tmp_line);
445 free_incomplete_node(&cur_node);
446 break;
447 }
448
449 // Get value
450 cur_node->value = NULL;
451
452 if (line_ptr)
453 {
454 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
455 if (!cur_node->value)
456 {
457 free(tmp_line);
458 free_incomplete_node(&cur_node);
459 break;
460 }
461 }
462
463 // Handle attributes
464 if (line_ptr)
465 {
466 cur_node->attributes = malloc_wrap(sizeof(struct attributes));
467 if (!cur_node->attributes)
468 {
469 free(tmp_line);
470 free_incomplete_node(&cur_node);
471 break;
472 }
473
474 struct attributes *attr_ptr = cur_node->attributes;
475 attr_ptr->current = NULL;
476
477 attr_ptr->str = strclone(line_ptr, 0);
478 if (!attr_ptr->str)
479 {
480 free(tmp_line);
481 free_incomplete_node(&cur_node);
482 break;
483 }
484
485 attr_ptr->iter_ptr = attr_ptr->str;
486
487 // Change the delimiter, which will be used later
488 // in canfigger_free_current_attr_str_advance()
489 char *delimiter_ptr = strchr(attr_ptr->iter_ptr, delimiter);
490 while (delimiter_ptr)
491 {
492 *delimiter_ptr = '\n';
493 delimiter_ptr = strchr(delimiter_ptr, delimiter);
494 }
495 }
496 else
497 cur_node->attributes = NULL;
498
499 cur_node->next = NULL;
500 node_complete = true;
501 free(tmp_line);
502 if (!line.end)
503 break;
504 }
505
506 if (!root)
507 {
508 free(file_contents);
509 return NULL;
510 }
511
512 if (!node_complete)
513 {
514 free(file_contents);
515 canfigger_free_list(&root);
516 return NULL;
517 }
518
519 free(file_contents);
520 return root;
521}
522
523
524#ifndef _WIN32
525static char *
526xdg_base_dir(const char *xdg_env, const char *fallback)
527{
528 const char *base = getenv(xdg_env);
529 /* The basedir spec requires these to be absolute and says a relative value is
530 invalid and must be ignored -- resolving it against the current directory
531 would put the file somewhere that depends on where the program was
532 started. Fall through to the $HOME default instead. */
533 if (base && *base == '/')
534 return strclone(base, 0);
535
536 const char *home = getenv("HOME");
537 if (!home || !*home)
538 return NULL;
539
540 size_t len = strlen(home) + 1 + strlen(fallback) + 1;
541 char *result = malloc_wrap(len);
542 if (!result)
543 return NULL;
544 snprintf(result, len, "%s/%s", home, fallback);
545 return result;
546}
547#endif
548
549
550/* All the Windows helpers below go through SHGetFolderPathA, which is both
551 deprecated and ANSI-only. This is accepted rather than overlooked: when the
552 profile path cannot be represented in the active code page (a non-ASCII
553 account name on a non-UTF-8 system) the call fails and the helper returns
554 NULL, which is a clean failure rather than a corrupt path. Its replacement,
555 SHGetKnownFolderPath, hands back UTF-16, and a UTF-8 char * built from that
556 is not something MSVC's fopen will open -- so moving to it means a parallel
557 wide-char API, not a drop-in swap. An application shipping a UTF-8
558 active-code-page manifest (Windows 10 1903+) gets UTF-8 paths from this code
559 as it stands. */
560#ifdef _WIN32
561/* @p subdir may be NULL. It exists because Windows has no separate cache or
562 state root: everything below LOCAL_APPDATA would otherwise land on the same
563 path, so canfigger_cache_dir() and canfigger_state_dir() would return exactly
564 what canfigger_data_dir() does and a file written through one would be the
565 file written through another. The subdirectory keeps them distinct, matching
566 what Qt's QStandardPaths does for the same reason. */
567static char *
568dir_for_appname(const char *appname, int csidl, const char *subdir)
569{
570 if (!appname || *appname == '\0')
571 return NULL;
572
573 char base[MAX_PATH];
574 if (FAILED(SHGetFolderPathA(NULL, csidl, NULL, 0, base)))
575 return NULL;
576
577 size_t len = strlen(base) + 1 + strlen(appname) + 1;
578 if (subdir)
579 len += strlen(subdir) + 1;
580 char *result = malloc_wrap(len);
581 if (!result)
582 return NULL;
583 if (subdir)
584 snprintf(result, len, "%s\\%s\\%s", base, appname, subdir);
585 else
586 snprintf(result, len, "%s\\%s", base, appname);
587 return result;
588}
589#else
590static char *
591dir_for_appname(const char *appname, const char *xdg_env,
592 const char *xdg_fallback)
593{
594 if (!appname || *appname == '\0')
595 return NULL;
596
597 char *base = xdg_base_dir(xdg_env, xdg_fallback);
598 if (!base)
599 return NULL;
600 char *result = canfigger_path_join(base, appname);
601 free(base);
602 return result;
603}
604#endif
605
606
607char *
608canfigger_config_dir(const char *appname)
609{
610#ifdef _WIN32
611 return dir_for_appname(appname, CSIDL_APPDATA, NULL);
612#else
613 return dir_for_appname(appname, "XDG_CONFIG_HOME", ".config");
614#endif
615}
616
617
618char *
619canfigger_config_file(const char *filename)
620{
621 if (!filename || !*filename)
622 return NULL;
623
624#ifdef _WIN32
625 char base[MAX_PATH];
626 if (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base)))
627 return NULL;
628
629 size_t len = strlen(base) + 1 + strlen(filename) + 1;
630 char *result = malloc_wrap(len);
631 if (!result)
632 return NULL;
633 snprintf(result, len, "%s\\%s", base, filename);
634 return result;
635#else
636 char *base = xdg_base_dir("XDG_CONFIG_HOME", ".config");
637 if (!base)
638 return NULL;
639 char *result = canfigger_path_join(base, filename);
640 free(base);
641 return result;
642#endif
643}
644
645
646char *
647canfigger_data_dir(const char *appname)
648{
649#ifdef _WIN32
650 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA, NULL);
651#else
652 return dir_for_appname(appname, "XDG_DATA_HOME", ".local/share");
653#endif
654}
655
656
657char *
658canfigger_cache_dir(const char *appname)
659{
660#ifdef _WIN32
661 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA, "Cache");
662#else
663 return dir_for_appname(appname, "XDG_CACHE_HOME", ".cache");
664#endif
665}
666
667
668char *
669canfigger_state_dir(const char *appname)
670{
671#ifdef _WIN32
672 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA, "State");
673#else
674 return dir_for_appname(appname, "XDG_STATE_HOME", ".local/state");
675#endif
676}
677
678
679char *
680canfigger_runtime_dir(const char *appname)
681{
682 if (!appname || *appname == '\0')
683 return NULL;
684
685#ifdef _WIN32
686 /* Windows has no runtime-directory concept. Returning NULL rather than a
687 LOCAL_APPDATA path keeps the contract honest: callers asking for a runtime
688 directory want one that goes away with the session, and a path under
689 LOCAL_APPDATA does not. Let them choose their own fallback. */
690 return NULL;
691#else
692 const char *base = getenv("XDG_RUNTIME_DIR");
693 if (!base || *base != '/')
694 return NULL;
695
696 /* The spec does not merely name this directory, it states what it must be:
697 owned by the user, accessible to nobody else, mode 0700. A directory that
698 fails those checks is not a safe place for the sockets, lock files and
699 short-lived secrets this is used for, so treat it as absent and let the
700 caller fall back rather than handing back a path that looks usable. */
701 struct stat st;
702 if (stat(base, &st) != 0)
703 return NULL;
704 if (!S_ISDIR(st.st_mode))
705 return NULL;
706 if (st.st_uid != getuid())
707 return NULL;
708 if ((st.st_mode & 07777) != 0700)
709 return NULL;
710
711 return canfigger_path_join(base, appname);
712#endif
713}
714
715
716#ifdef _WIN32
717/* Windows has no search *path*: ProgramData is the one system-wide location for
718 settings and data shared by every user, so the list always holds exactly one
719 entry. It is still handed back as a list so a caller can walk
720 canfigger_config_dirs() / canfigger_data_dirs() the same way on both
721 platforms instead of bracketing the loop in #ifdefs. */
722static char **
723common_appdata_list(void)
724{
725 char base[MAX_PATH];
726 if (FAILED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, base)))
727 return NULL;
728
729 char **list = malloc_wrap(2 * sizeof(*list));
730 if (!list)
731 return NULL;
732
733 list[0] = strclone(base, 0);
734 if (!list[0])
735 {
736 free(list);
737 return NULL;
738 }
739 list[1] = NULL;
740 return list;
741}
742#endif
743
744
745#ifndef _WIN32
746/* Split a colon-separated XDG search path into a NULL-terminated array. */
747static char **
748xdg_dir_list(const char *xdg_env, const char *fallback)
749{
750 const char *value = getenv(xdg_env);
751 if (!value || *value == '\0')
752 value = fallback;
753
754 size_t max = 2;
755 const char *p;
756 for (p = value; *p != '\0'; p++)
757 if (*p == ':')
758 max++;
759
760 char **list = malloc_wrap(max * sizeof(*list));
761 if (!list)
762 return NULL;
763
764 size_t n = 0;
765 const char *start = value;
766 for (;;)
767 {
768 const char *end = strchr(start, ':');
769 size_t len = end ? (size_t) (end - start) : strlen(start);
770
771 /* Same rule as xdg_base_dir: a relative entry is invalid per the spec, and
772 an empty one ("a::b") is not a path. Skip both rather than failing the
773 whole list -- one bad entry in a search path should not cost the caller
774 the good ones. */
775 if (len > 0 && *start == '/')
776 {
777 list[n] = strclone(start, len);
778 if (!list[n])
779 {
780 list[n] = NULL;
781 canfigger_free_dirs(list);
782 return NULL;
783 }
784 n++;
785 }
786
787 if (!end)
788 break;
789 start = end + 1;
790 }
791
792 list[n] = NULL;
793 return list;
794}
795#endif
796
797
798char **
799canfigger_config_dirs(void)
800{
801#ifdef _WIN32
802 return common_appdata_list();
803#else
804 return xdg_dir_list("XDG_CONFIG_DIRS", "/etc/xdg");
805#endif
806}
807
808
809char **
810canfigger_data_dirs(void)
811{
812#ifdef _WIN32
813 return common_appdata_list();
814#else
815 return xdg_dir_list("XDG_DATA_DIRS", "/usr/local/share:/usr/share");
816#endif
817}
818
819
820void
821canfigger_free_dirs(char **dirs)
822{
823 if (!dirs)
824 return;
825
826 size_t i;
827 for (i = 0; dirs[i] != NULL; i++)
828 free(dirs[i]);
829 free(dirs);
830}
831
832
833char *
834canfigger_path_join(const char *dir, const char *file)
835{
836 if (!dir || !*dir || !file || !*file)
837 return NULL;
838
839 size_t dirlen = strlen(dir);
840 size_t filelen = strlen(file);
841 bool needs_sep = dirlen > 0 && dir[dirlen - 1] != '/'
842 && dir[dirlen - 1] != '\\';
843 size_t total = dirlen + (needs_sep ? 1 : 0) + filelen + 1;
844
845 char *result = malloc_wrap(total);
846 if (!result)
847 return NULL;
848
849#ifdef _WIN32
850 const char sep = '\\';
851#else
852 const char sep = '/';
853#endif
854
855 if (needs_sep)
856 snprintf(result, total, "%s%c%s", dir, sep, file);
857 else
858 snprintf(result, total, "%s%s", dir, file);
859
860 return result;
861}
862
863
864/* Does the path name an existing regular file? A directory of the right name is
865 not a config file, so it must not stop the search short of one further down
866 the path. */
867static bool
868path_is_file(const char *path)
869{
870#ifdef _WIN32
871 DWORD attr = GetFileAttributesA(path);
872 return attr != INVALID_FILE_ATTRIBUTES
873 && !(attr & FILE_ATTRIBUTE_DIRECTORY);
874#else
875 struct stat st;
876 return stat(path, &st) == 0 && S_ISREG(st.st_mode);
877#endif
878}
879
880
881char *
882canfigger_find_config_file(const char *appname, const char *filename)
883{
884 if (!filename || !*filename)
885 return NULL;
886
887 char *candidate = NULL;
888
889 /* The user's own config always wins, whatever the system copies hold. */
890 if (appname && *appname)
891 {
892 char *dir = canfigger_config_dir(appname);
893 if (dir)
894 {
895 candidate = canfigger_path_join(dir, filename);
896 free(dir);
897 }
898 }
899 else
900 candidate = canfigger_config_file(filename);
901
902 if (candidate && path_is_file(candidate))
903 return candidate;
904 free(candidate);
905
906 char **dirs = canfigger_config_dirs();
907 if (!dirs)
908 return NULL;
909
910 char *found = NULL;
911 size_t i;
912 for (i = 0; dirs[i] != NULL && !found; i++)
913 {
914 char *base = NULL;
915 if (appname && *appname)
916 {
917 base = canfigger_path_join(dirs[i], appname);
918 if (!base)
919 continue;
920 }
921
922 candidate = canfigger_path_join(base ? base : dirs[i], filename);
923 free(base);
924
925 if (candidate && path_is_file(candidate))
926 found = candidate;
927 else
928 free(candidate);
929 }
930
931 canfigger_free_dirs(dirs);
932 return found;
933}
934
935
936/* Keyed by enum canfigger_user_dir so the rows cannot silently shift if the
937 enum is ever reordered. A new enumerator with no row here still compiles: the
938 entry is a null pointer, which canfigger_user_dir() turns into its ordinary
939 NULL return rather than a crash. */
940#ifndef _WIN32
941static const char *const user_dir_keys[] = {
942 [CANFIGGER_USER_DIR_DESKTOP] = "XDG_DESKTOP_DIR",
943 [CANFIGGER_USER_DIR_DOWNLOAD] = "XDG_DOWNLOAD_DIR",
944 [CANFIGGER_USER_DIR_TEMPLATES] = "XDG_TEMPLATES_DIR",
945 [CANFIGGER_USER_DIR_PUBLICSHARE] = "XDG_PUBLICSHARE_DIR",
946 [CANFIGGER_USER_DIR_DOCUMENTS] = "XDG_DOCUMENTS_DIR",
947 [CANFIGGER_USER_DIR_MUSIC] = "XDG_MUSIC_DIR",
948 [CANFIGGER_USER_DIR_PICTURES] = "XDG_PICTURES_DIR",
949 [CANFIGGER_USER_DIR_VIDEOS] = "XDG_VIDEOS_DIR"
950};
951
952
953/* user-dirs.dirs is a shell fragment sourced by the desktop session, not a
954 canfigger file, so it cannot go through canfigger_parse_file(): its values
955 are double-quoted, may contain backslash escapes, and the "$HOME/" prefix has
956 to be expanded. This mirrors the reference xdg-user-dir-lookup implementation
957 -- deliberately, since matching whatever the desktop itself resolves matters
958 more here than being lenient. */
959static char *
960user_dir_from_file(const char *key)
961{
962 char *path = canfigger_config_file("user-dirs.dirs");
963 if (!path)
964 return NULL;
965
966 FILE *fp = fopen(path, "r");
967 free(path);
968 if (!fp)
969 return NULL;
970
971 static const char home_prefix[] = "$HOME/";
972 const char *home = getenv("HOME");
973 size_t keylen = strlen(key);
974 char line[1024];
975 char *result = NULL;
976
977 while (!result && fgets(line, sizeof line, fp) != NULL)
978 {
979 char *p = line;
980 while (isspace((unsigned char) *p))
981 p++;
982 if (strncmp(p, key, keylen) != 0)
983 continue;
984
985 p += keylen;
986 while (isspace((unsigned char) *p))
987 p++;
988 if (*p != '=')
989 continue;
990 p++;
991 while (isspace((unsigned char) *p))
992 p++;
993 if (*p != '"')
994 continue;
995 p++;
996
997 bool relative_to_home = false;
998 if (strncmp(p, home_prefix, sizeof home_prefix - 1) == 0)
999 {
1000 relative_to_home = true;
1001 p += sizeof home_prefix - 1;
1002 }
1003 /* Anything neither absolute nor under $HOME is unusable: an entry like
1004 "Desktop" would resolve against the current directory. */
1005 else if (*p != '/')
1006 break;
1007
1008 size_t prefix = 0;
1009 if (relative_to_home)
1010 {
1011 if (!home || !*home)
1012 break;
1013 prefix = strlen(home) + 1;
1014 }
1015
1016 char *out = malloc_wrap(prefix + strlen(p) + 1);
1017 if (!out)
1018 break;
1019 if (relative_to_home)
1020 snprintf(out, prefix + 1, "%s/", home);
1021
1022 char *d = out + prefix;
1023 while (*p != '\0' && *p != '"')
1024 {
1025 if (*p == '\\' && *(p + 1) != '\0')
1026 p++;
1027 *d++ = *p++;
1028 }
1029 *d = '\0';
1030
1031 /* An empty value (XDG_DESKTOP_DIR="") disables the directory; treat it as
1032 absent so the caller gets the $HOME fallback rather than "". */
1033 if (*out == '\0')
1034 {
1035 free(out);
1036 break;
1037 }
1038 result = out;
1039 }
1040
1041 fclose(fp);
1042 return result;
1043}
1044#else
1045/* Keyed by enum canfigger_user_dir, as user_dir_keys[] is. CSIDL_PROFILE stands
1046 in for Downloads, which has no CSIDL at all (it arrived with the Vista-era
1047 KNOWNFOLDERID API); the name is appended below. Unlike the POSIX table a
1048 missing row is not detectable here -- 0 is CSIDL_DESKTOP -- so every
1049 enumerator needs one. */
1050static const int user_dir_csidl[] = {
1051 [CANFIGGER_USER_DIR_DESKTOP] = CSIDL_DESKTOPDIRECTORY,
1052 [CANFIGGER_USER_DIR_DOWNLOAD] = CSIDL_PROFILE,
1053 [CANFIGGER_USER_DIR_TEMPLATES] = CSIDL_TEMPLATES,
1054 [CANFIGGER_USER_DIR_PUBLICSHARE] = CSIDL_COMMON_DOCUMENTS,
1055 [CANFIGGER_USER_DIR_DOCUMENTS] = CSIDL_PERSONAL,
1056 [CANFIGGER_USER_DIR_MUSIC] = CSIDL_MYMUSIC,
1057 [CANFIGGER_USER_DIR_PICTURES] = CSIDL_MYPICTURES,
1058 [CANFIGGER_USER_DIR_VIDEOS] = CSIDL_MYVIDEO
1059};
1060#endif
1061
1062
1063char *
1065{
1066#ifdef _WIN32
1067 if ((unsigned) which >= sizeof user_dir_csidl / sizeof *user_dir_csidl)
1068 return NULL;
1069
1070 char base[MAX_PATH];
1071 if (FAILED(SHGetFolderPathA(NULL, user_dir_csidl[which], NULL, 0, base)))
1072 return NULL;
1073
1074 if (which == CANFIGGER_USER_DIR_DOWNLOAD)
1075 return canfigger_path_join(base, "Downloads");
1076 return strclone(base, 0);
1077#else
1078 if ((unsigned) which >= sizeof user_dir_keys / sizeof *user_dir_keys)
1079 return NULL;
1080
1081 if (!user_dir_keys[which])
1082 return NULL;
1083
1084 char *dir = user_dir_from_file(user_dir_keys[which]);
1085 if (dir)
1086 return dir;
1087
1088 const char *home = getenv("HOME");
1089 if (!home || !*home)
1090 return NULL;
1091
1092 /* The reference implementation falls back to $HOME for every type but the
1093 desktop, which keeps its historical $HOME/Desktop default. */
1094 if (which == CANFIGGER_USER_DIR_DESKTOP)
1095 return canfigger_path_join(home, "Desktop");
1096 return strclone(home, 0);
1097#endif
1098}
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:619
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:368
char * canfigger_state_dir(const char *appname)
Return the platform state directory for an application.
Definition canfigger.c:669
char * canfigger_cache_dir(const char *appname)
Return the platform cache directory for an application.
Definition canfigger.c:658
char * canfigger_data_dir(const char *appname)
Return the platform data directory for an application.
Definition canfigger.c:647
canfigger_user_dir
The well-known user directories, as named by the XDG user directories specification.
Definition canfigger.h:423
void canfigger_free_list(struct Canfigger **node)
Free all remaining nodes in the list.
Definition canfigger.c:178
char * canfigger_config_dir(const char *appname)
Return the platform config directory for an application.
Definition canfigger.c:608
char * canfigger_path_join(const char *dir, const char *file)
Join a directory path and a filename with the platform separator.
Definition canfigger.c:834
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:137
char * canfigger_runtime_dir(const char *appname)
Return the runtime directory for an application, or NULL.
Definition canfigger.c:680
A single node in the parsed configuration linked list.
Definition canfigger.h:133
Internal iteration state for a node's attribute list.
Definition canfigger.h:111