advapi32: Make sure not to return a credential with a NULL UserName field when a...
[wine] / dlls / shell32 / xdg.c
1 /*
2  * Generic freedesktop.org support code
3  *
4  * Copyright (C) 2006 Mikolaj Zalewski
5  *
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.
10  *
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.
15  *
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
19  *
20  *
21  * XDG_UserDirLookup() and helper functions are based on code from:
22  * http://www.freedesktop.org/wiki/Software/xdg-user-dirs
23  *
24  * Copyright (c) 2007 Red Hat, inc
25  *
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:
34  *
35  * The above copyright notice and this permission notice shall be
36  * included in all copies or substantial portions of the Software.
37  *
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.
46  */
47
48 #include "config.h"
49
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>
61  
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"
69
70 WINE_DEFAULT_DEBUG_CHANNEL(xdg);
71
72 /*
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)
75  */
76
77 static CRITICAL_SECTION XDG_PathsLock;
78 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
79 {
80     0, 0, &XDG_PathsLock,
81     { &XDG_PathsLock_Debug.ProcessLocksList,
82       &XDG_PathsLock_Debug.ProcessLocksList},
83     0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
84 };
85 static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
86
87 typedef struct
88 {
89     const char *var_name;
90     const char *default_value;
91 } std_path;
92
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"}
99 };
100
101 #define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
102
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
110 };
111
112 static char *load_path(int path_id)
113 {
114     char *env = getenv(paths[path_id].var_name);
115     char *ret;
116     
117     if (env != NULL && env[0]=='/')
118     {
119         ret = SHAlloc(strlen(env)+1);
120         if (ret != NULL)
121             lstrcpyA(ret, env);
122         return ret;
123     }
124     
125     if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
126     {
127         char *home = getenv("HOME");
128         int len;
129
130         if (!home) return NULL;
131         ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
132         if (ret == NULL) return NULL;
133         
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;
140     }
141     
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;
146 }
147
148 /******************************************************************************
149  * XDG_GetPath    [internal]
150  *
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
154  *
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
158  *
159  * The paths are guaranteed to start with '/'
160  */
161 const char *XDG_GetPath(int path_id)
162 {
163     if (path_id >= PATHS_COUNT || path_id < 0)
164     {
165         ERR("Invalid path_id %d\n", path_id);
166         return NULL;
167     }
168     
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];
176 }
177
178 /******************************************************************************
179  * XDG_BuildPath    [internal]
180  *
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)
184  *
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
187  */
188 char *XDG_BuildPath(int root_id, const char *subpath)
189 {
190     const char *root_path = XDG_GetPath(root_id);
191     char *ret_buffer;
192     int root_len;
193     
194     if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
195     {
196         ERR("Invalid path id %d\n", root_id);
197         return NULL;
198     }
199     
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;
209 }
210
211 /******************************************************************************
212  * XDG_MakeDirs    [internal]
213  *
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
218  *
219  * In case of a failure the errno is always set and can be used e.g for debugging
220  *
221  * RETURNS
222  *   TRUE on success, FALSE on error
223  */
224 BOOL XDG_MakeDirs(const char *path)
225 {
226     int last_slash = 0;
227     BOOL success = TRUE;
228     struct stat tmp;
229     char *buffer = SHAlloc(strlen(path)+1);
230     
231     if (buffer == NULL)
232     {
233         errno = ENOMEM;
234         return FALSE;
235     }
236     lstrcpyA(buffer, path);
237     
238     TRACE("(%s)\n", debugstr_a(path));
239     while (1)
240     {
241         char *slash=strchr(buffer+last_slash+1, '/');
242         if (slash==NULL)
243             break;
244         
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)
250         {
251             TRACE("Creating\n");
252             success = (mkdir(buffer, 0700)==0);
253         }
254         if (!success)
255         {
256             WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
257             break;
258         }
259         *slash='/';
260         last_slash = slash-buffer;
261     }
262     SHFree(buffer);
263     return success;
264 }
265
266 /*
267  * .desktop files functions
268  */
269
270
271 /******************************************************************************
272  * dskentry_encode    [internal]
273  *
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.
277  *
278  * RETURNS
279  *   The number of characters after escaping the special characters, including the
280  * terminating NUL.
281  */
282 static int dskentry_encode(const char *value, char *output)
283 {
284     int only_spc = TRUE;
285     int num_written = 0;
286     const char *c;
287     for (c = value; *c; c++)
288     {
289         if (only_spc && *c==' ')
290         {
291             if (output)
292             {
293                 *(output++) = '\\';
294                 *(output++) = 's';
295             }
296             num_written += 2;
297             continue;
298         }
299         only_spc = FALSE;
300         
301         if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
302         {
303             if (output)
304             {
305                 *(output++) = '\\';
306                 if (*c=='\t') *(output++) = 't';
307                 if (*c=='\r') *(output++) = 'r';
308                 if (*c=='\n') *(output++) = 'n';
309                 if (*c=='\\') *(output++) = '\\';
310             }
311             num_written += 2;
312         }
313         else
314         {
315             if (output)
316                 *(output++)=*c;
317             num_written++;
318         }
319     }
320     
321     if (output)
322         *(output++) = 0;
323     num_written++;
324     return num_written;
325 }
326
327 /******************************************************************************
328  * dskentry_decode    [internal]
329  *
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.
333  *
334  * RETURNS
335  *   The number of characters after unescaping the special characters, including the
336  * terminating NUL.
337  */
338 static int dskentry_decode(const char *value, int len, char *output)
339 {
340     int pos = 0;
341     int count = 0;
342     while (pos<len)
343     {
344         char c;
345         if (value[pos] == '\\' && pos<len-1)
346         {
347             pos++;
348             switch (value[pos])
349             {
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;
362             }
363         }
364         else
365             c = value[pos];
366             
367         if (output)
368             *(output++) = c;
369         count++;
370         pos++;
371     }
372     
373     if (output)
374         *(output++) = 0;
375     count++;
376     return count;
377 }
378
379
380 /******************************************************************************
381  * url_encode    [internal]
382  *
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.
386  *
387  * The output can be NULL. Then only the number of characters will be counted
388  *
389  * RETURNS
390  *   The number of characters after escaping the special characters, including the
391  * terminating NUL.
392  */
393 static int url_encode(const char *value, char *output)
394 {
395     static const char unsafechars[] = "^&`{}|[]'<>\\#%\"+";
396     static const char hexchars[] = "0123456789ABCDEF";
397     int num_written = 0;
398     const char *c;
399
400     for (c = value; *c; c++)
401     {
402         if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
403         {
404             if (output)
405             {
406                 *(output++) = '%';
407                 *(output++) = hexchars[(unsigned char)*c / 16];
408                 *(output++) = hexchars[(unsigned char)*c % 16];
409             }
410             num_written += 3;
411         }
412         else
413         {
414             if (output)
415                 *(output++) = *c;
416             num_written++;
417         }
418     }
419
420     if (output)
421         *(output++) = 0;
422     num_written++;
423
424     return num_written;
425 }
426
427 static int decode_url_code(const char *c)
428 {
429     const char *p1, *p2;
430     int v1, v2;
431     static const char hexchars[] = "0123456789ABCDEF";
432     if (*c == 0)
433         return -1;
434
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;
442 }
443
444 /******************************************************************************
445  * url_decode    [internal]
446  *
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.
450  *
451  * output should not be NULL
452  */
453 static void url_decode(const char *value, char *output)
454 {
455     const char *c = value;
456     while (*c)
457     {
458         if (*c == '%')
459         {
460             int v = decode_url_code(c+1);
461             if (v != -1)
462             {
463                 *(output++) = v;
464                 c += 3;
465                 continue;
466             }
467         }
468         
469         *(output++) = *c;
470         c++;
471     }
472     *output = 0;
473 }
474
475 static int escape_value(const char *value, DWORD dwFlags, char *output)
476 {
477     if (dwFlags & XDG_URLENCODE)
478         return url_encode(value, output);
479     return dskentry_encode(value, output);
480 }
481
482 /******************************************************************************
483  * XDG_WriteDesktopStringEntry    [internal]
484  *
485  * Writes a key=value pair into the specified file descriptor.
486  *
487  * RETURNS
488  *   TRUE on success, else FALSE
489  */
490 BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
491 {
492     int keyLen = lstrlenA(keyName);
493     int valueLen = escape_value(value, dwFlags, NULL);
494     char *string = SHAlloc(keyLen+1+valueLen);
495     BOOL ret;
496     
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;
506 }
507
508 typedef struct
509 {
510     char *str;
511     int len;
512 } PARSED_STRING;
513
514 typedef struct tagPARSED_ENTRY PARSED_ENTRY;
515 struct tagPARSED_ENTRY
516 {
517     PARSED_STRING name;
518     PARSED_STRING equals;
519     PARSED_STRING value;
520     PARSED_ENTRY *next;
521 };
522
523 typedef struct tagPARSED_GROUP PARSED_GROUP;
524 struct tagPARSED_GROUP
525 {
526     PARSED_STRING name;
527     PARSED_ENTRY *entries;
528     PARSED_GROUP *next;
529 };
530
531
532 struct tagXDG_PARSED_FILE
533 {
534     char *contents;
535     PARSED_ENTRY *head_comments;
536     PARSED_GROUP *groups;
537 };
538
539 static BOOL parsed_str_eq(const PARSED_STRING *str1, const char *str2)
540 {
541     if (strncmp(str1->str, str2, str1->len) != 0)
542         return FALSE;
543     if (str2[str1->len] != 0)
544         return FALSE;
545     return TRUE;
546 }
547
548 static void free_entries_list(PARSED_ENTRY *first)
549 {
550     PARSED_ENTRY *next;
551     while (first)
552     {
553         next = first->next;
554         SHFree(first);
555         first = next;
556     }
557 }
558
559 void XDG_FreeParsedFile(XDG_PARSED_FILE *parsed)
560 {
561     PARSED_GROUP *group, *next;
562     if (!parsed)
563         return;
564     free_entries_list(parsed->head_comments);
565     
566     group = parsed->groups;
567     while (group)
568     {
569         next = group->next;
570         free_entries_list(group->entries);
571         SHFree(group);
572         group = next;
573     }
574 }
575
576 #define LINE_GROUP   1
577 #define LINE_ENTRY   2
578 #define LINE_COMMENT 3
579
580 static int parse_line(char *content, PARSED_ENTRY *output, int *outType)
581 {
582     char *end;
583     
584     ZeroMemory(output, sizeof(PARSED_ENTRY));
585     end = strchr(content, '\n');
586     if (end == NULL)
587         end = content + strlen(content) - 1;
588     
589     if (*content == '#')
590     {
591         *outType = LINE_COMMENT;
592         output->equals.str = content;
593         output->equals.len = end - content;
594         if (*end != '\n')
595             output->equals.len++;
596     }
597     else if (*content == '[')
598     {
599         char *last_char = end;
600         
601         *outType = LINE_GROUP;
602         
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);
610     }
611     else
612     {
613         /* 'name = value' line */
614         char *equal, *eq_begin, *eq_end;
615         
616         *outType = LINE_ENTRY;
617         
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--)
622             ;
623         for (eq_end = equal+1; isspace(*eq_end) && *eq_end != '\n'; eq_end++)
624             ;
625
626         output->name.str = content;
627         output->name.len = eq_begin - content + 1;
628
629         output->equals.str = eq_begin + 1;
630         output->equals.len = eq_end - eq_begin - 1;
631         
632         output->value.str = eq_end;
633         output->value.len = end - eq_end;
634
635         if (*end != '\n')
636             output->value.len++;
637     }
638     return end - content + 1;
639 }
640
641 XDG_PARSED_FILE *XDG_ParseDesktopFile(int fd)
642 {
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;
648     
649     int pos = 0;
650     
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;
658     
659     curr_entry = &parsed->head_comments;
660     curr_group = &parsed->groups;
661     
662     if (read(fd, parsed->contents, stats.st_size) == -1) goto failed;
663     parsed->contents[stats.st_size] = 0;
664
665     while (pos < stats.st_size)
666     {
667         PARSED_ENTRY statement;
668         int type, size;
669
670         size = parse_line(parsed->contents + pos, &statement, &type);
671         if (size == -1) goto failed;
672         if (size == 0)
673             break;
674         pos += size;
675         
676         switch (type)
677         {
678             case LINE_GROUP:
679             {
680                 PARSED_GROUP *group = SHAlloc(sizeof(PARSED_GROUP));
681                 if (group == NULL) goto failed;
682                 is_in_group = TRUE;
683                 
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;
691             }
692
693             case LINE_ENTRY:
694                 if (!is_in_group) goto failed;
695                 /* fall through */
696             case LINE_COMMENT:
697             {
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;
705             }
706         }
707     }
708     return parsed;
709     
710 failed:
711     XDG_FreeParsedFile(parsed);
712     return NULL;
713 }
714
715 char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const char *value_name, DWORD dwFlags)
716 {
717     PARSED_GROUP *group;
718     PARSED_ENTRY *entry;
719
720     for (group = file->groups; group; group = group->next)
721     {
722         if (!parsed_str_eq(&group->name, group_name))
723             continue;
724
725         for (entry = group->entries; entry; entry = entry->next)
726             if (entry->name.str != NULL && parsed_str_eq(&entry->name, value_name))
727             {
728                 int len;
729                 char *ret;
730                 
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;
738             }
739     }
740     
741     return NULL;
742 }
743
744 /* Get the name of the xdg configuration file.
745  *
746  * [in] home_dir - $HOME
747  * [out] config_file - the name of the configuration file
748  */
749 static HRESULT get_xdg_config_file(char * home_dir, char ** config_file)
750 {
751     char *config_home;
752
753     config_home = getenv("XDG_CONFIG_HOME");
754     if (!config_home || !config_home[0])
755     {
756         *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1);
757         if (!*config_file)
758             return E_OUTOFMEMORY;
759
760         strcpy(*config_file, home_dir);
761         strcat(*config_file, "/.config/user-dirs.dirs");
762     }
763     else
764     {
765         *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(config_home) + strlen("/user-dirs.dirs") + 1);
766         if (!*config_file)
767             return E_OUTOFMEMORY;
768
769         strcpy(*config_file, config_home);
770         strcat(*config_file, "/user-dirs.dirs");
771     }
772     return S_OK;
773 }
774
775 /* Parse the key in a line in the xdg-user-dir config file.
776  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
777  *      ^                ^
778  *
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.
783  */
784 static int parse_config1(const char ** xdg_dirs, const unsigned int num_dirs, char ** p_ptr)
785 {
786     char *p;
787     int i;
788
789     p = *p_ptr;
790     while (*p == ' ' || *p == '\t')
791         p++;
792     if (strncmp(p, "XDG_", 4))
793         return -1;
794
795     p += 4;
796     for (i = 0; i < num_dirs; i++)
797     {
798         if (!strncmp(p, xdg_dirs[i], strlen(xdg_dirs[i])))
799         {
800             p += strlen(xdg_dirs[i]);
801             break;
802         }
803     }
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++;
819
820     *p_ptr = p;
821     return i;
822 }
823
824 /* Parse the value in a line in the xdg-user-dir config file.
825  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
826  *                        ^             ^
827  *
828  * [in] p - pointer to the buffer
829  * [in] home_dir - $HOME
830  * [out] out_ptr - the directory name
831  */
832 static HRESULT parse_config2(char * p, const char * home_dir, char ** out_ptr)
833 {
834     BOOL relative;
835     char *out, *d;
836
837     relative = FALSE;
838
839     if (!strncmp(p, "$HOME/", 6))
840     {
841         p += 6;
842         relative = TRUE;
843     }
844     else if (*p != '/')
845         return E_FAIL;
846
847     if (relative)
848     {
849         out = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen(p) + 2);
850         if (!out)
851             return E_OUTOFMEMORY;
852
853         strcpy(out, home_dir);
854         strcat(out, "/");
855     }
856     else
857     {
858         out = HeapAlloc(GetProcessHeap(), 0, strlen(p) + 1);
859         if (!out)
860             return E_OUTOFMEMORY;
861         *out = 0;
862     }
863
864     d = out + strlen(out);
865     while (*p && *p != '"')
866     {
867         if ((*p == '\\') && (*(p + 1) != 0))
868             p++;
869         *d++ = *p++;
870     }
871     *d = 0;
872     *out_ptr = out;
873     return S_OK;
874 }
875
876 /* Parse part of a line in the xdg-user-dir config file.
877  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
878  *                        ^             ^
879  *
880  * The calling function is responsible for freeing all elements of out_ptr as
881  * well as out_ptr itself.
882  *
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
886  */
887 HRESULT XDG_UserDirLookup(const char ** xdg_dirs, const unsigned int num_dirs, char *** out_ptr)
888 {
889     FILE *file;
890     char **out;
891     char *home_dir, *config_file;
892     char buffer[512];
893     int i, len;
894     HRESULT hr;
895
896     *out_ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, num_dirs * sizeof(char *));
897     out = *out_ptr;
898     if (!out)
899         return E_OUTOFMEMORY;
900
901     home_dir = getenv("HOME");
902     if (!home_dir)
903     {
904         hr = E_FAIL;
905         goto xdg_user_dir_lookup_error;
906     }
907
908     hr = get_xdg_config_file(home_dir, &config_file);
909     if (FAILED(hr))
910         goto xdg_user_dir_lookup_error;
911
912     file = fopen(config_file, "r");
913     HeapFree(GetProcessHeap(), 0, config_file);
914     if (!file)
915     {
916         hr = E_HANDLE;
917         goto xdg_user_dir_lookup_error;
918     }
919
920     while (fgets(buffer, sizeof(buffer), file))
921     {
922         int idx;
923         char *p;
924
925         /* Remove newline at end */
926         len = strlen(buffer);
927         if (len > 0 && buffer[len-1] == '\n')
928             buffer[len-1] = 0;
929
930         /* Parse the key */
931         p = buffer;
932         idx = parse_config1(xdg_dirs, num_dirs, &p);
933         if (idx < 0)
934             continue;
935         if (out[idx])
936             continue;
937
938         /* Parse the value */
939         hr = parse_config2(p, home_dir, &out[idx]);
940         if (FAILED(hr))
941         {
942             if (hr == E_OUTOFMEMORY)
943                 goto xdg_user_dir_lookup_error;
944             continue;
945         }
946     }
947     hr = S_OK;
948
949     /* Remove entries for directories that do not exist */
950     for (i = 0; i <  num_dirs; i++)
951     {
952         struct stat statFolder;
953
954         if (!out[i])
955             continue;
956         if (!stat(out[i], &statFolder) && S_ISDIR(statFolder.st_mode))
957             continue;
958         HeapFree(GetProcessHeap(), 0, out[i]);
959         out[i] = NULL;
960     }
961
962 xdg_user_dir_lookup_error:
963     if (FAILED(hr))
964     {
965         for (i = 0; i < num_dirs; i++) HeapFree(GetProcessHeap(), 0, out[i]);
966         HeapFree(GetProcessHeap(), 0, out_ptr);
967     }
968     return hr;
969 }