push ada2d4f150af430b0cf01adcbe8b6d3e34deeeb1
[wine/hacks.git] / dlls / shell32 / xdg.c
blobd7f84abda75861b4e862a28391d5349f64c94763
1 /*
2 * Generic freedesktop.org support code
4 * Copyright (C) 2006 Mikolaj Zalewski
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * XDG_UserDirLookup() and helper functions are based on code from:
22 * http://www.freedesktop.org/wiki/Software/xdg-user-dirs
24 * Copyright (c) 2007 Red Hat, inc
26 * From the xdg-user-dirs license:
27 * Permission is hereby granted, free of charge, to any person
28 * obtaining a copy of this software and associated documentation files
29 * (the "Software"), to deal in the Software without restriction,
30 * including without limitation the rights to use, copy, modify, merge,
31 * publish, distribute, sublicense, and/or sell copies of the Software,
32 * and to permit persons to whom the Software is furnished to do so,
33 * subject to the following conditions:
35 * The above copyright notice and this permission notice shall be
36 * included in all copies or substantial portions of the Software.
38 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
39 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
40 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
41 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
42 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
43 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
44 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45 * SOFTWARE.
48 #include "config.h"
50 #include <stdio.h>
51 #include <stdarg.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #ifdef HAVE_SYS_STAT_H
55 # include <sys/stat.h>
56 #endif
57 #ifdef HAVE_UNISTD_H
58 # include <unistd.h>
59 #endif
60 #include <errno.h>
62 #include "windef.h"
63 #include "winbase.h"
64 #include "winreg.h"
65 #include "shlwapi.h"
66 #include "wine/debug.h"
67 #include "shell32_main.h"
68 #include "xdg.h"
70 WINE_DEFAULT_DEBUG_CHANNEL(xdg);
73 * XDG paths implemented using Desktop Base Directory spec version 0.6
74 * (from http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html)
77 static CRITICAL_SECTION XDG_PathsLock;
78 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
80 0, 0, &XDG_PathsLock,
81 { &XDG_PathsLock_Debug.ProcessLocksList,
82 &XDG_PathsLock_Debug.ProcessLocksList},
83 0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
85 static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
87 typedef struct
89 const char *var_name;
90 const char *default_value;
91 } std_path;
93 static const std_path paths[] = {
94 {"XDG_DATA_HOME", "$HOME/.local/share"},
95 {"XDG_CONFIG_HOME", "$HOME/.config"},
96 {"XDG_DATA_DIRS", "/usr/local/share:/usr/share"},
97 {"XDG_CONFIG_DIRS", "/etc/xdg"},
98 {"XDG_CACHE_HOME", "$HOME/.cache"}
101 #define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
103 /* will be filled with paths as they are computed */
104 static const char *path_values[PATHS_COUNT] = {
105 NULL,
106 NULL,
107 NULL,
108 NULL,
109 NULL
112 static char *load_path(int path_id)
114 char *env = getenv(paths[path_id].var_name);
115 char *ret;
117 if (env != NULL && env[0]=='/')
119 ret = SHAlloc(strlen(env)+1);
120 if (ret != NULL)
121 lstrcpyA(ret, env);
122 return ret;
125 if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
127 char *home = getenv("HOME");
128 int len;
130 if (!home) return NULL;
131 ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
132 if (ret == NULL) return NULL;
134 lstrcpyA(ret, home);
135 len = strlen(ret);
136 if (len>0 && ret[len-1]=='/')
137 ret[--len]=0;
138 lstrcatA(ret, paths[path_id].default_value+5);
139 return ret;
142 ret = SHAlloc(strlen(paths[path_id].default_value)+1);
143 if (ret != NULL)
144 lstrcpyA(ret, paths[path_id].default_value);
145 return ret;
148 /******************************************************************************
149 * XDG_GetPath [internal]
151 * Get one of the XDG standard patch. The return value shouldn't be modified nor
152 * freed. A return value of NULL means that the memory is exhausted or the input
153 * is invalid
155 * For XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME the result is a Unix path.
156 * For XDG_DATA_DIRS and XDG_CONFIG_DIRS the result is a colon-separated list of Unix
157 * paths
159 * The paths are guaranteed to start with '/'
161 const char *XDG_GetPath(int path_id)
163 if (path_id >= PATHS_COUNT || path_id < 0)
165 ERR("Invalid path_id %d\n", path_id);
166 return NULL;
169 if (path_values[path_id] != NULL)
170 return path_values[path_id];
171 EnterCriticalSection(&XDG_PathsLock);
172 if (path_values[path_id] == NULL)
173 path_values[path_id] = load_path(path_id);
174 LeaveCriticalSection(&XDG_PathsLock);
175 return path_values[path_id];
178 /******************************************************************************
179 * XDG_BuildPath [internal]
181 * Build a string with a subpath of one of the XDG standard paths.
182 * The root can be one of XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME.
183 * The subpath is a path relative to that root (it shouldn't start with a slash)
185 * The returned path should be freed with SHFree. A return of NULL means that the
186 * memory is exhausted or the parameters are invalid
188 char *XDG_BuildPath(int root_id, const char *subpath)
190 const char *root_path = XDG_GetPath(root_id);
191 char *ret_buffer;
192 int root_len;
194 if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
196 ERR("Invalid path id %d\n", root_id);
197 return NULL;
200 if (root_path == NULL) return NULL;
201 root_len = strlen(root_path);
202 if (root_path[root_len-1]=='/') root_len--;
203 ret_buffer = SHAlloc(root_len+1+strlen(subpath)+1);
204 if (ret_buffer == NULL) return NULL;
205 lstrcpyA(ret_buffer, root_path);
206 ret_buffer[root_len]='/';
207 lstrcpyA(ret_buffer+root_len+1, subpath);
208 return ret_buffer;
211 /******************************************************************************
212 * XDG_MakeDirs [internal]
214 * Checks that all the directories on the specified path exists. If some don't exists
215 * they are created with mask 0700 as required by many the freedeskop.org specs.
216 * If the path doesn't end with '/' it is assumed to be a path to a file and the last
217 * segment is not checked
219 * In case of a failure the errno is always set and can be used e.g for debugging
221 * RETURNS
222 * TRUE on success, FALSE on error
224 BOOL XDG_MakeDirs(const char *path)
226 int last_slash = 0;
227 BOOL success = TRUE;
228 struct stat tmp;
229 char *buffer = SHAlloc(strlen(path)+1);
231 if (buffer == NULL)
233 errno = ENOMEM;
234 return FALSE;
236 lstrcpyA(buffer, path);
238 TRACE("(%s)\n", debugstr_a(path));
239 while (1)
241 char *slash=strchr(buffer+last_slash+1, '/');
242 if (slash==NULL)
243 break;
245 /* cut the string at that position and create the directory if it doesn't exist */
246 *slash=0;
247 TRACE("Checking path %s\n", debugstr_a(buffer));
248 success = (stat(buffer, &tmp)==0);
249 if (!success && errno==ENOENT)
251 TRACE("Creating\n");
252 success = (mkdir(buffer, 0700)==0);
254 if (!success)
256 WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
257 break;
259 *slash='/';
260 last_slash = slash-buffer;
262 SHFree(buffer);
263 return success;
267 * .desktop files functions
271 /******************************************************************************
272 * dskentry_encode [internal]
274 * Escape the characters that can't be present in a desktop entry value like \n, leading
275 * spaces etc. The output parameter may be NULL. Then only the number of characters will
276 * be computers.
278 * RETURNS
279 * The number of characters after escaping the special characters, including the
280 * terminating NUL.
282 static int dskentry_encode(const char *value, char *output)
284 int only_spc = TRUE;
285 int num_written = 0;
286 const char *c;
287 for (c = value; *c; c++)
289 if (only_spc && *c==' ')
291 if (output)
293 *(output++) = '\\';
294 *(output++) = 's';
296 num_written += 2;
297 continue;
299 only_spc = FALSE;
301 if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
303 if (output)
305 *(output++) = '\\';
306 if (*c=='\t') *(output++) = 't';
307 if (*c=='\r') *(output++) = 'r';
308 if (*c=='\n') *(output++) = 'n';
309 if (*c=='\\') *(output++) = '\\';
311 num_written += 2;
313 else
315 if (output)
316 *(output++)=*c;
317 num_written++;
321 if (output)
322 *(output++) = 0;
323 num_written++;
324 return num_written;
327 /******************************************************************************
328 * dskentry_decode [internal]
330 * Unescape the characters that can be escaped according to the desktop entry spec.
331 * The output parameter may be NULL. Then only the number of characters will
332 * be computers.
334 * RETURNS
335 * The number of characters after unescaping the special characters, including the
336 * terminating NUL.
338 static int dskentry_decode(const char *value, int len, char *output)
340 int pos = 0;
341 int count = 0;
342 while (pos<len)
344 char c;
345 if (value[pos] == '\\' && pos<len-1)
347 pos++;
348 switch (value[pos])
350 case 's': c = ' '; break;
351 case 'n': c = '\n'; break;
352 case 't': c = '\t'; break;
353 case 'r': c = 'r'; break;
354 case '\\': c = '\\'; break;
355 default:
356 /* store both the backslash and the character */
357 if (output)
358 *(output++) = '\\';
359 count++;
360 c = value[pos];
361 break;
364 else
365 c = value[pos];
367 if (output)
368 *(output++) = c;
369 count++;
370 pos++;
373 if (output)
374 *(output++) = 0;
375 count++;
376 return count;
380 /******************************************************************************
381 * url_encode [internal]
383 * URL-encode the given string (i.e. use escape codes like %20). Note that an
384 * URL-encoded string can be used as a value in desktop entry files as all
385 * unsafe characters are escaped.
387 * The output can be NULL. Then only the number of characters will be counted
389 * RETURNS
390 * The number of characters after escaping the special characters, including the
391 * terminating NUL.
393 static int url_encode(const char *value, char *output)
395 static const char unsafechars[] = "^&`{}|[]'<>\\#%\"+";
396 static const char hexchars[] = "0123456789ABCDEF";
397 int num_written = 0;
398 const char *c;
400 for (c = value; *c; c++)
402 if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
404 if (output)
406 *(output++) = '%';
407 *(output++) = hexchars[(unsigned char)*c / 16];
408 *(output++) = hexchars[(unsigned char)*c % 16];
410 num_written += 3;
412 else
414 if (output)
415 *(output++) = *c;
416 num_written++;
420 if (output)
421 *(output++) = 0;
422 num_written++;
424 return num_written;
427 static int decode_url_code(const char *c)
429 const char *p1, *p2;
430 int v1, v2;
431 static const char hexchars[] = "0123456789ABCDEF";
432 if (*c == 0)
433 return -1;
435 p1 = strchr(hexchars, toupper(*c));
436 p2 = strchr(hexchars, toupper(*(c+1)));
437 if (p1 == NULL || p2 == NULL)
438 return -1;
439 v1 = (int)(p1 - hexchars);
440 v2 = (int)(p2 - hexchars);
441 return (v1<<4) + v2;
444 /******************************************************************************
445 * url_decode [internal]
447 * URL-decode the given string (i.e. unescape codes like %20). The decoded string
448 * will never be longer than the encoded one. The decoding can be done in place - the
449 * output variable can point to the value buffer.
451 * output should not be NULL
453 static void url_decode(const char *value, char *output)
455 const char *c = value;
456 while (*c)
458 if (*c == '%')
460 int v = decode_url_code(c+1);
461 if (v != -1)
463 *(output++) = v;
464 c += 3;
465 continue;
469 *(output++) = *c;
470 c++;
472 *output = 0;
475 static int escape_value(const char *value, DWORD dwFlags, char *output)
477 if (dwFlags & XDG_URLENCODE)
478 return url_encode(value, output);
479 return dskentry_encode(value, output);
482 /******************************************************************************
483 * XDG_WriteDesktopStringEntry [internal]
485 * Writes a key=value pair into the specified file descriptor.
487 * RETURNS
488 * TRUE on success, else FALSE
490 BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
492 int keyLen = lstrlenA(keyName);
493 int valueLen = escape_value(value, dwFlags, NULL);
494 char *string = SHAlloc(keyLen+1+valueLen);
495 BOOL ret;
497 if (string == NULL)
498 return FALSE;
499 lstrcpyA(string, keyName);
500 string[keyLen] = '=';
501 escape_value(value, dwFlags, string+keyLen+1);
502 string[keyLen+1+valueLen-1]='\n'; /* -1 because valueLen contains the last NUL character */
503 ret = (write(writer, string, keyLen+1+valueLen)!=-1);
504 SHFree(string);
505 return ret;
508 typedef struct
510 char *str;
511 int len;
512 } PARSED_STRING;
514 typedef struct tagPARSED_ENTRY PARSED_ENTRY;
515 struct tagPARSED_ENTRY
517 PARSED_STRING name;
518 PARSED_STRING equals;
519 PARSED_STRING value;
520 PARSED_ENTRY *next;
523 typedef struct tagPARSED_GROUP PARSED_GROUP;
524 struct tagPARSED_GROUP
526 PARSED_STRING name;
527 PARSED_ENTRY *entries;
528 PARSED_GROUP *next;
532 struct tagXDG_PARSED_FILE
534 char *contents;
535 PARSED_ENTRY *head_comments;
536 PARSED_GROUP *groups;
539 static BOOL parsed_str_eq(const PARSED_STRING *str1, const char *str2)
541 if (strncmp(str1->str, str2, str1->len) != 0)
542 return FALSE;
543 if (str2[str1->len] != 0)
544 return FALSE;
545 return TRUE;
548 static void free_entries_list(PARSED_ENTRY *first)
550 PARSED_ENTRY *next;
551 while (first)
553 next = first->next;
554 SHFree(first);
555 first = next;
559 void XDG_FreeParsedFile(XDG_PARSED_FILE *parsed)
561 PARSED_GROUP *group, *next;
562 if (!parsed)
563 return;
564 free_entries_list(parsed->head_comments);
566 group = parsed->groups;
567 while (group)
569 next = group->next;
570 free_entries_list(group->entries);
571 SHFree(group);
572 group = next;
576 #define LINE_GROUP 1
577 #define LINE_ENTRY 2
578 #define LINE_COMMENT 3
580 static int parse_line(char *content, PARSED_ENTRY *output, int *outType)
582 char *end;
584 ZeroMemory(output, sizeof(PARSED_ENTRY));
585 end = strchr(content, '\n');
586 if (end == NULL)
587 end = content + strlen(content) - 1;
589 if (*content == '#')
591 *outType = LINE_COMMENT;
592 output->equals.str = content;
593 output->equals.len = end - content;
594 if (*end != '\n')
595 output->equals.len++;
597 else if (*content == '[')
599 char *last_char = end;
601 *outType = LINE_GROUP;
603 /* the standard says nothing about skipping white spaces but e.g. KDE accepts such files */
604 while (isspace(*last_char))
605 last_char--;
606 if (*last_char != ']')
607 return -1;
608 output->name.str = content + 1;
609 output->name.len = last_char - (content + 1);
611 else
613 /* 'name = value' line */
614 char *equal, *eq_begin, *eq_end;
616 *outType = LINE_ENTRY;
618 equal = strchr(content, '=');
619 if (equal == NULL || equal > end)
620 return -1;
621 for (eq_begin = equal-1; isspace(*eq_begin) && eq_begin >= content; eq_begin--)
623 for (eq_end = equal+1; isspace(*eq_end) && *eq_end != '\n'; eq_end++)
626 output->name.str = content;
627 output->name.len = eq_begin - content + 1;
629 output->equals.str = eq_begin + 1;
630 output->equals.len = eq_end - eq_begin - 1;
632 output->value.str = eq_end;
633 output->value.len = end - eq_end;
635 if (*end != '\n')
636 output->value.len++;
638 return end - content + 1;
641 XDG_PARSED_FILE *XDG_ParseDesktopFile(int fd)
643 struct stat stats;
644 XDG_PARSED_FILE *parsed = NULL;
645 PARSED_ENTRY **curr_entry;
646 PARSED_GROUP **curr_group;
647 BOOL is_in_group = FALSE;
649 int pos = 0;
651 if (fstat(fd, &stats) == -1) goto failed;
652 parsed = SHAlloc(sizeof(XDG_PARSED_FILE));
653 if (parsed == NULL) goto failed;
654 parsed->groups = NULL;
655 parsed->head_comments = NULL;
656 parsed->contents = SHAlloc(stats.st_size+1);
657 if (parsed->contents == NULL) goto failed;
659 curr_entry = &parsed->head_comments;
660 curr_group = &parsed->groups;
662 if (read(fd, parsed->contents, stats.st_size) == -1) goto failed;
663 parsed->contents[stats.st_size] = 0;
665 while (pos < stats.st_size)
667 PARSED_ENTRY statement;
668 int type, size;
670 size = parse_line(parsed->contents + pos, &statement, &type);
671 if (size == -1) goto failed;
672 if (size == 0)
673 break;
674 pos += size;
676 switch (type)
678 case LINE_GROUP:
680 PARSED_GROUP *group = SHAlloc(sizeof(PARSED_GROUP));
681 if (group == NULL) goto failed;
682 is_in_group = TRUE;
684 group->name = statement.name;
685 group->entries = NULL;
686 group->next = NULL;
687 *curr_group = group;
688 curr_group = &group->next;
689 curr_entry = &group->entries;
690 break;
693 case LINE_ENTRY:
694 if (!is_in_group) goto failed;
695 /* fall through */
696 case LINE_COMMENT:
698 PARSED_ENTRY *new_stat = SHAlloc(sizeof(PARSED_ENTRY));
699 if (new_stat == NULL) goto failed;
700 *new_stat = statement;
701 new_stat->next = NULL;
702 *curr_entry = new_stat;
703 curr_entry = &new_stat->next;
704 break;
708 return parsed;
710 failed:
711 XDG_FreeParsedFile(parsed);
712 return NULL;
715 char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const char *value_name, DWORD dwFlags)
717 PARSED_GROUP *group;
718 PARSED_ENTRY *entry;
720 for (group = file->groups; group; group = group->next)
722 if (!parsed_str_eq(&group->name, group_name))
723 continue;
725 for (entry = group->entries; entry; entry = entry->next)
726 if (entry->name.str != NULL && parsed_str_eq(&entry->name, value_name))
728 int len;
729 char *ret;
731 len = dskentry_decode(entry->value.str, entry->value.len, NULL);
732 ret = SHAlloc(len);
733 if (ret == NULL) return NULL;
734 dskentry_decode(entry->value.str, entry->value.len, ret);
735 if (dwFlags & XDG_URLENCODE)
736 url_decode(ret, ret);
737 return ret;
741 return NULL;
744 /* Get the name of the xdg configuration file.
746 * [in] home_dir - $HOME
747 * [out] config_file - the name of the configuration file
749 static HRESULT get_xdg_config_file(char * home_dir, char ** config_file)
751 char *config_home;
753 config_home = getenv("XDG_CONFIG_HOME");
754 if (!config_home || !config_home[0])
756 *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1);
757 if (!*config_file)
758 return E_OUTOFMEMORY;
760 strcpy(*config_file, home_dir);
761 strcat(*config_file, "/.config/user-dirs.dirs");
763 else
765 *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(config_home) + strlen("/user-dirs.dirs") + 1);
766 if (!*config_file)
767 return E_OUTOFMEMORY;
769 strcpy(*config_file, config_home);
770 strcat(*config_file, "/user-dirs.dirs");
772 return S_OK;
775 /* Parse the key in a line in the xdg-user-dir config file.
776 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
777 * ^ ^
779 * [in] xdg_dirs - array of xdg directories to look for
780 * [in] num_dirs - number of elements in xdg_dirs
781 * [in/out] p_ptr - pointer to where we are in the buffer
782 * Returns the index to xdg_dirs if we find the key, or -1 on error.
784 static int parse_config1(const char * const *xdg_dirs, const unsigned int num_dirs, char ** p_ptr)
786 char *p;
787 int i;
789 p = *p_ptr;
790 while (*p == ' ' || *p == '\t')
791 p++;
792 if (strncmp(p, "XDG_", 4))
793 return -1;
795 p += 4;
796 for (i = 0; i < num_dirs; i++)
798 if (!strncmp(p, xdg_dirs[i], strlen(xdg_dirs[i])))
800 p += strlen(xdg_dirs[i]);
801 break;
804 if (i == num_dirs)
805 return -1;
806 if (strncmp(p, "_DIR", 4))
807 return -1;
808 p += 4;
809 while (*p == ' ' || *p == '\t')
810 p++;
811 if (*p != '=')
812 return -1;
813 p++;
814 while (*p == ' ' || *p == '\t')
815 p++;
816 if (*p != '"')
817 return -1;
818 p++;
820 *p_ptr = p;
821 return i;
824 /* Parse the value in a line in the xdg-user-dir config file.
825 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
826 * ^ ^
828 * [in] p - pointer to the buffer
829 * [in] home_dir - $HOME
830 * [out] out_ptr - the directory name
832 static HRESULT parse_config2(char * p, const char * home_dir, char ** out_ptr)
834 BOOL relative;
835 char *out, *d;
837 relative = FALSE;
839 if (!strncmp(p, "$HOME/", 6))
841 p += 6;
842 relative = TRUE;
844 else if (*p != '/')
845 return E_FAIL;
847 if (relative)
849 out = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen(p) + 2);
850 if (!out)
851 return E_OUTOFMEMORY;
853 strcpy(out, home_dir);
854 strcat(out, "/");
856 else
858 out = HeapAlloc(GetProcessHeap(), 0, strlen(p) + 1);
859 if (!out)
860 return E_OUTOFMEMORY;
861 *out = 0;
864 d = out + strlen(out);
865 while (*p && *p != '"')
867 if ((*p == '\\') && (*(p + 1) != 0))
868 p++;
869 *d++ = *p++;
871 *d = 0;
872 *out_ptr = out;
873 return S_OK;
876 /* Parse part of a line in the xdg-user-dir config file.
877 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
878 * ^ ^
880 * The calling function is responsible for freeing all elements of out_ptr as
881 * well as out_ptr itself.
883 * [in] xdg_dirs - array of xdg directories to look for
884 * [in] num_dirs - number of elements in xdg_dirs
885 * [out] out_ptr - an array of the xdg directories names
887 HRESULT XDG_UserDirLookup(const char * const *xdg_dirs, const unsigned int num_dirs, char *** out_ptr)
889 FILE *file;
890 char **out;
891 char *home_dir, *config_file;
892 char buffer[512];
893 int len;
894 unsigned int i;
895 HRESULT hr;
897 *out_ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, num_dirs * sizeof(char *));
898 out = *out_ptr;
899 if (!out)
900 return E_OUTOFMEMORY;
902 home_dir = getenv("HOME");
903 if (!home_dir)
905 hr = E_FAIL;
906 goto xdg_user_dir_lookup_error;
909 hr = get_xdg_config_file(home_dir, &config_file);
910 if (FAILED(hr))
911 goto xdg_user_dir_lookup_error;
913 file = fopen(config_file, "r");
914 HeapFree(GetProcessHeap(), 0, config_file);
915 if (!file)
917 hr = E_HANDLE;
918 goto xdg_user_dir_lookup_error;
921 while (fgets(buffer, sizeof(buffer), file))
923 int idx;
924 char *p;
926 /* Remove newline at end */
927 len = strlen(buffer);
928 if (len > 0 && buffer[len-1] == '\n')
929 buffer[len-1] = 0;
931 /* Parse the key */
932 p = buffer;
933 idx = parse_config1(xdg_dirs, num_dirs, &p);
934 if (idx < 0)
935 continue;
936 if (out[idx])
937 continue;
939 /* Parse the value */
940 hr = parse_config2(p, home_dir, &out[idx]);
941 if (FAILED(hr))
943 if (hr == E_OUTOFMEMORY)
944 goto xdg_user_dir_lookup_error;
945 continue;
948 hr = S_OK;
950 /* Remove entries for directories that do not exist */
951 for (i = 0; i < num_dirs; i++)
953 struct stat statFolder;
955 if (!out[i])
956 continue;
957 if (!stat(out[i], &statFolder) && S_ISDIR(statFolder.st_mode))
958 continue;
959 HeapFree(GetProcessHeap(), 0, out[i]);
960 out[i] = NULL;
963 xdg_user_dir_lookup_error:
964 if (FAILED(hr))
966 for (i = 0; i < num_dirs; i++) HeapFree(GetProcessHeap(), 0, out[i]);
967 HeapFree(GetProcessHeap(), 0, *out_ptr);
969 return hr;