crypt32: Partially implement checking name constraints with directory names.
[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 #include "wine/port.h"
50
51 #include <stdio.h>
52 #include <stdarg.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #ifdef HAVE_SYS_STAT_H
56 # include <sys/stat.h>
57 #endif
58 #ifdef HAVE_UNISTD_H
59 # include <unistd.h>
60 #endif
61 #include <errno.h>
62  
63 #include "windef.h"
64 #include "winbase.h"
65 #include "winreg.h"
66 #include "shlwapi.h"
67 #include "wine/debug.h"
68 #include "shell32_main.h"
69 #include "xdg.h"
70
71 WINE_DEFAULT_DEBUG_CHANNEL(xdg);
72
73 /*
74  * XDG paths implemented using Desktop Base Directory spec version 0.6
75  * (from http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html)
76  */
77
78 static CRITICAL_SECTION XDG_PathsLock;
79 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
80 {
81     0, 0, &XDG_PathsLock,
82     { &XDG_PathsLock_Debug.ProcessLocksList,
83       &XDG_PathsLock_Debug.ProcessLocksList},
84     0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
85 };
86 static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
87
88 typedef struct
89 {
90     const char *var_name;
91     const char *default_value;
92 } std_path;
93
94 static const std_path paths[] = {
95     {"XDG_DATA_HOME", "$HOME/.local/share"},
96     {"XDG_CONFIG_HOME", "$HOME/.config"},
97     {"XDG_DATA_DIRS", "/usr/local/share:/usr/share"},
98     {"XDG_CONFIG_DIRS", "/etc/xdg"},
99     {"XDG_CACHE_HOME", "$HOME/.cache"}
100 };
101
102 #define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
103
104 /* will be filled with paths as they are computed */
105 static const char *path_values[PATHS_COUNT] = {
106     NULL,
107     NULL,
108     NULL,
109     NULL,
110     NULL
111 };
112
113 static char *load_path(int path_id)
114 {
115     char *env = getenv(paths[path_id].var_name);
116     char *ret;
117     
118     if (env != NULL && env[0]=='/')
119     {
120         ret = SHAlloc(strlen(env)+1);
121         if (ret != NULL)
122             lstrcpyA(ret, env);
123         return ret;
124     }
125     
126     if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
127     {
128         char *home = getenv("HOME");
129         int len;
130
131         if (!home) return NULL;
132         ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
133         if (ret == NULL) return NULL;
134         
135         lstrcpyA(ret, home);
136         len = strlen(ret);
137         if (len>0 && ret[len-1]=='/')
138             ret[--len]=0;
139         lstrcatA(ret, paths[path_id].default_value+5);
140         return ret;
141     }
142     
143     ret = SHAlloc(strlen(paths[path_id].default_value)+1);
144     if (ret != NULL)
145         lstrcpyA(ret, paths[path_id].default_value);
146     return ret;
147 }
148
149 /******************************************************************************
150  * XDG_GetPath    [internal]
151  *
152  * Get one of the XDG standard patch. The return value shouldn't be modified nor
153  * freed. A return value of NULL means that the memory is exhausted or the input
154  * is invalid
155  *
156  * For XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME the result is a Unix path.
157  * For XDG_DATA_DIRS and XDG_CONFIG_DIRS the result is a colon-separated list of Unix
158  * paths
159  *
160  * The paths are guaranteed to start with '/'
161  */
162 static const char *XDG_GetPath(int path_id)
163 {
164     if (path_id >= PATHS_COUNT || path_id < 0)
165     {
166         ERR("Invalid path_id %d\n", path_id);
167         return NULL;
168     }
169     
170     if (path_values[path_id] != NULL)
171         return path_values[path_id];
172     EnterCriticalSection(&XDG_PathsLock);
173     if (path_values[path_id] == NULL)
174         path_values[path_id] = load_path(path_id);
175     LeaveCriticalSection(&XDG_PathsLock);
176     return path_values[path_id];
177 }
178
179 /******************************************************************************
180  * XDG_BuildPath    [internal]
181  *
182  * Build a string with a subpath of one of the XDG standard paths.
183  * The root can be one of XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME.
184  * The subpath is a path relative to that root (it shouldn't start with a slash)
185  *
186  * The returned path should be freed with SHFree. A return of NULL means that the
187  * memory is exhausted or the parameters are invalid
188  */
189 char *XDG_BuildPath(int root_id, const char *subpath)
190 {
191     const char *root_path = XDG_GetPath(root_id);
192     char *ret_buffer;
193     int root_len;
194     
195     if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
196     {
197         ERR("Invalid path id %d\n", root_id);
198         return NULL;
199     }
200     
201     if (root_path == NULL) return NULL;
202     root_len = strlen(root_path);
203     if (root_path[root_len-1]=='/') root_len--;
204     ret_buffer = SHAlloc(root_len+1+strlen(subpath)+1);
205     if (ret_buffer == NULL) return NULL;
206     lstrcpyA(ret_buffer, root_path);
207     ret_buffer[root_len]='/';
208     lstrcpyA(ret_buffer+root_len+1, subpath);
209     return ret_buffer;
210 }
211
212 /******************************************************************************
213  * XDG_MakeDirs    [internal]
214  *
215  * Checks that all the directories on the specified path exists. If some don't exists
216  * they are created with mask 0700 as required by many the freedeskop.org specs.
217  * If the path doesn't end with '/' it is assumed to be a path to a file and the last
218  * segment is not checked
219  *
220  * In case of a failure the errno is always set and can be used e.g for debugging
221  *
222  * RETURNS
223  *   TRUE on success, FALSE on error
224  */
225 BOOL XDG_MakeDirs(const char *path)
226 {
227     int last_slash = 0;
228     BOOL success = TRUE;
229     struct stat tmp;
230     char *buffer = SHAlloc(strlen(path)+1);
231     
232     if (buffer == NULL)
233     {
234         errno = ENOMEM;
235         return FALSE;
236     }
237     lstrcpyA(buffer, path);
238     
239     TRACE("(%s)\n", debugstr_a(path));
240     while (1)
241     {
242         char *slash=strchr(buffer+last_slash+1, '/');
243         if (slash==NULL)
244             break;
245         
246         /* cut the string at that position and create the directory if it doesn't exist */
247         *slash=0;
248         TRACE("Checking path %s\n", debugstr_a(buffer));
249         success = (stat(buffer, &tmp)==0);
250         if (!success && errno==ENOENT)
251         {
252             TRACE("Creating\n");
253             success = (mkdir(buffer, 0700)==0);
254         }
255         if (!success)
256         {
257             WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
258             break;
259         }
260         *slash='/';
261         last_slash = slash-buffer;
262     }
263     SHFree(buffer);
264     return success;
265 }
266
267 /*
268  * .desktop files functions
269  */
270
271
272 /******************************************************************************
273  * dskentry_encode    [internal]
274  *
275  * Escape the characters that can't be present in a desktop entry value like \n, leading
276  * spaces etc. The output parameter may be NULL. Then only the number of characters will
277  * be computers.
278  *
279  * RETURNS
280  *   The number of characters after escaping the special characters, including the
281  * terminating NUL.
282  */
283 static int dskentry_encode(const char *value, char *output)
284 {
285     int only_spc = TRUE;
286     int num_written = 0;
287     const char *c;
288     for (c = value; *c; c++)
289     {
290         if (only_spc && *c==' ')
291         {
292             if (output)
293             {
294                 *(output++) = '\\';
295                 *(output++) = 's';
296             }
297             num_written += 2;
298             continue;
299         }
300         only_spc = FALSE;
301         
302         if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
303         {
304             if (output)
305             {
306                 *(output++) = '\\';
307                 if (*c=='\t') *(output++) = 't';
308                 if (*c=='\r') *(output++) = 'r';
309                 if (*c=='\n') *(output++) = 'n';
310                 if (*c=='\\') *(output++) = '\\';
311             }
312             num_written += 2;
313         }
314         else
315         {
316             if (output)
317                 *(output++)=*c;
318             num_written++;
319         }
320     }
321     
322     if (output)
323         *(output++) = 0;
324     num_written++;
325     return num_written;
326 }
327
328 /******************************************************************************
329  * dskentry_decode    [internal]
330  *
331  * Unescape the characters that can be escaped according to the desktop entry spec.
332  * The output parameter may be NULL. Then only the number of characters will
333  * be computers.
334  *
335  * RETURNS
336  *   The number of characters after unescaping the special characters, including the
337  * terminating NUL.
338  */
339 static int dskentry_decode(const char *value, int len, char *output)
340 {
341     int pos = 0;
342     int count = 0;
343     while (pos<len)
344     {
345         char c;
346         if (value[pos] == '\\' && pos<len-1)
347         {
348             pos++;
349             switch (value[pos])
350             {
351                 case 's': c = ' '; break;
352                 case 'n': c = '\n'; break;
353                 case 't': c = '\t'; break;
354                 case 'r': c = 'r'; break;
355                 case '\\': c = '\\'; break;
356                 default:
357                     /* store both the backslash and the character */
358                     if (output)
359                         *(output++) = '\\';
360                     count++;
361                     c = value[pos];
362                     break;
363             }
364         }
365         else
366             c = value[pos];
367             
368         if (output)
369             *(output++) = c;
370         count++;
371         pos++;
372     }
373     
374     if (output)
375         *(output++) = 0;
376     count++;
377     return count;
378 }
379
380
381 /******************************************************************************
382  * url_encode    [internal]
383  *
384  * URL-encode the given string (i.e. use escape codes like %20). Note that an
385  * URL-encoded string can be used as a value in desktop entry files as all
386  * unsafe characters are escaped.
387  *
388  * The output can be NULL. Then only the number of characters will be counted
389  *
390  * RETURNS
391  *   The number of characters after escaping the special characters, including the
392  * terminating NUL.
393  */
394 static int url_encode(const char *value, char *output)
395 {
396     static const char unsafechars[] = "^&`{}|[]'<>\\#%\"+";
397     static const char hexchars[] = "0123456789ABCDEF";
398     int num_written = 0;
399     const char *c;
400
401     for (c = value; *c; c++)
402     {
403         if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
404         {
405             if (output)
406             {
407                 *(output++) = '%';
408                 *(output++) = hexchars[(unsigned char)*c / 16];
409                 *(output++) = hexchars[(unsigned char)*c % 16];
410             }
411             num_written += 3;
412         }
413         else
414         {
415             if (output)
416                 *(output++) = *c;
417             num_written++;
418         }
419     }
420
421     if (output)
422         *(output++) = 0;
423     num_written++;
424
425     return num_written;
426 }
427
428 static int decode_url_code(const char *c)
429 {
430     const char *p1, *p2;
431     int v1, v2;
432     static const char hexchars[] = "0123456789ABCDEF";
433     if (*c == 0)
434         return -1;
435
436     p1 = strchr(hexchars, toupper(*c));
437     p2 = strchr(hexchars, toupper(*(c+1)));
438     if (p1 == NULL || p2 == NULL)
439         return -1;
440     v1 = (int)(p1 - hexchars);
441     v2 = (int)(p2 - hexchars);
442     return (v1<<4) + v2;
443 }
444
445 /******************************************************************************
446  * url_decode    [internal]
447  *
448  * URL-decode the given string (i.e. unescape codes like %20). The decoded string
449  * will never be longer than the encoded one. The decoding can be done in place - the
450  * output variable can point to the value buffer.
451  *
452  * output should not be NULL
453  */
454 static void url_decode(const char *value, char *output)
455 {
456     const char *c = value;
457     while (*c)
458     {
459         if (*c == '%')
460         {
461             int v = decode_url_code(c+1);
462             if (v != -1)
463             {
464                 *(output++) = v;
465                 c += 3;
466                 continue;
467             }
468         }
469         
470         *(output++) = *c;
471         c++;
472     }
473     *output = 0;
474 }
475
476 static int escape_value(const char *value, DWORD dwFlags, char *output)
477 {
478     if (dwFlags & XDG_URLENCODE)
479         return url_encode(value, output);
480     return dskentry_encode(value, output);
481 }
482
483 /******************************************************************************
484  * XDG_WriteDesktopStringEntry    [internal]
485  *
486  * Writes a key=value pair into the specified file descriptor.
487  *
488  * RETURNS
489  *   TRUE on success, else FALSE
490  */
491 BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
492 {
493     int keyLen = lstrlenA(keyName);
494     int valueLen = escape_value(value, dwFlags, NULL);
495     char *string = SHAlloc(keyLen+1+valueLen);
496     BOOL ret;
497     
498     if (string == NULL)
499         return FALSE;
500     lstrcpyA(string, keyName);
501     string[keyLen] = '=';
502     escape_value(value, dwFlags, string+keyLen+1);
503     string[keyLen+1+valueLen-1]='\n';   /* -1 because valueLen contains the last NUL character */
504     ret = (write(writer, string, keyLen+1+valueLen)!=-1);
505     SHFree(string);
506     return ret;
507 }
508
509 typedef struct
510 {
511     char *str;
512     int len;
513 } PARSED_STRING;
514
515 typedef struct tagPARSED_ENTRY PARSED_ENTRY;
516 struct tagPARSED_ENTRY
517 {
518     PARSED_STRING name;
519     PARSED_STRING equals;
520     PARSED_STRING value;
521     PARSED_ENTRY *next;
522 };
523
524 typedef struct tagPARSED_GROUP PARSED_GROUP;
525 struct tagPARSED_GROUP
526 {
527     PARSED_STRING name;
528     PARSED_ENTRY *entries;
529     PARSED_GROUP *next;
530 };
531
532
533 struct tagXDG_PARSED_FILE
534 {
535     char *contents;
536     PARSED_ENTRY *head_comments;
537     PARSED_GROUP *groups;
538 };
539
540 static BOOL parsed_str_eq(const PARSED_STRING *str1, const char *str2)
541 {
542     if (strncmp(str1->str, str2, str1->len) != 0)
543         return FALSE;
544     if (str2[str1->len] != 0)
545         return FALSE;
546     return TRUE;
547 }
548
549 static void free_entries_list(PARSED_ENTRY *first)
550 {
551     PARSED_ENTRY *next;
552     while (first)
553     {
554         next = first->next;
555         SHFree(first);
556         first = next;
557     }
558 }
559
560 void XDG_FreeParsedFile(XDG_PARSED_FILE *parsed)
561 {
562     PARSED_GROUP *group, *next;
563     if (!parsed)
564         return;
565     free_entries_list(parsed->head_comments);
566     
567     group = parsed->groups;
568     while (group)
569     {
570         next = group->next;
571         free_entries_list(group->entries);
572         SHFree(group);
573         group = next;
574     }
575 }
576
577 #define LINE_GROUP   1
578 #define LINE_ENTRY   2
579 #define LINE_COMMENT 3
580
581 static int parse_line(char *content, PARSED_ENTRY *output, int *outType)
582 {
583     char *end;
584     
585     ZeroMemory(output, sizeof(PARSED_ENTRY));
586     end = strchr(content, '\n');
587     if (end == NULL)
588         end = content + strlen(content) - 1;
589     
590     if (*content == '#')
591     {
592         *outType = LINE_COMMENT;
593         output->equals.str = content;
594         output->equals.len = end - content;
595         if (*end != '\n')
596             output->equals.len++;
597     }
598     else if (*content == '[')
599     {
600         char *last_char = end;
601         
602         *outType = LINE_GROUP;
603         
604         /* the standard says nothing about skipping white spaces but e.g. KDE accepts such files */
605         while (isspace(*last_char))
606             last_char--;
607         if (*last_char != ']')
608             return -1;
609         output->name.str = content + 1;
610         output->name.len = last_char - (content + 1);
611     }
612     else
613     {
614         /* 'name = value' line */
615         char *equal, *eq_begin, *eq_end;
616         
617         *outType = LINE_ENTRY;
618         
619         equal = strchr(content, '=');
620         if (equal == NULL || equal > end)
621             return -1;
622         for (eq_begin = equal-1;  isspace(*eq_begin) && eq_begin >= content; eq_begin--)
623             ;
624         for (eq_end = equal+1; isspace(*eq_end) && *eq_end != '\n'; eq_end++)
625             ;
626
627         output->name.str = content;
628         output->name.len = eq_begin - content + 1;
629
630         output->equals.str = eq_begin + 1;
631         output->equals.len = eq_end - eq_begin - 1;
632         
633         output->value.str = eq_end;
634         output->value.len = end - eq_end;
635
636         if (*end != '\n')
637             output->value.len++;
638     }
639     return end - content + 1;
640 }
641
642 XDG_PARSED_FILE *XDG_ParseDesktopFile(int fd)
643 {
644     struct stat stats;
645     XDG_PARSED_FILE *parsed = NULL;
646     PARSED_ENTRY **curr_entry;
647     PARSED_GROUP **curr_group;
648     BOOL is_in_group = FALSE;
649     
650     int pos = 0;
651     
652     if (fstat(fd, &stats) == -1) goto failed;
653     parsed = SHAlloc(sizeof(XDG_PARSED_FILE));
654     if (parsed == NULL) goto failed;
655     parsed->groups = NULL;
656     parsed->head_comments = NULL;
657     parsed->contents = SHAlloc(stats.st_size+1);
658     if (parsed->contents == NULL) goto failed;
659     
660     curr_entry = &parsed->head_comments;
661     curr_group = &parsed->groups;
662     
663     if (read(fd, parsed->contents, stats.st_size) == -1) goto failed;
664     parsed->contents[stats.st_size] = 0;
665
666     while (pos < stats.st_size)
667     {
668         PARSED_ENTRY statement;
669         int type, size;
670
671         size = parse_line(parsed->contents + pos, &statement, &type);
672         if (size == -1) goto failed;
673         if (size == 0)
674             break;
675         pos += size;
676         
677         switch (type)
678         {
679             case LINE_GROUP:
680             {
681                 PARSED_GROUP *group = SHAlloc(sizeof(PARSED_GROUP));
682                 if (group == NULL) goto failed;
683                 is_in_group = TRUE;
684                 
685                 group->name = statement.name;
686                 group->entries = NULL;
687                 group->next = NULL;
688                 *curr_group = group;
689                 curr_group = &group->next;
690                 curr_entry = &group->entries;
691                 break;
692             }
693
694             case LINE_ENTRY:
695                 if (!is_in_group) goto failed;
696                 /* fall through */
697             case LINE_COMMENT:
698             {
699                 PARSED_ENTRY *new_stat = SHAlloc(sizeof(PARSED_ENTRY));
700                 if (new_stat == NULL) goto failed;
701                 *new_stat = statement;
702                 new_stat->next = NULL;
703                 *curr_entry = new_stat;
704                 curr_entry = &new_stat->next;
705                 break;
706             }
707         }
708     }
709     return parsed;
710     
711 failed:
712     XDG_FreeParsedFile(parsed);
713     return NULL;
714 }
715
716 char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const char *value_name, DWORD dwFlags)
717 {
718     PARSED_GROUP *group;
719     PARSED_ENTRY *entry;
720
721     for (group = file->groups; group; group = group->next)
722     {
723         if (!parsed_str_eq(&group->name, group_name))
724             continue;
725
726         for (entry = group->entries; entry; entry = entry->next)
727             if (entry->name.str != NULL && parsed_str_eq(&entry->name, value_name))
728             {
729                 int len;
730                 char *ret;
731                 
732                 len = dskentry_decode(entry->value.str, entry->value.len, NULL);
733                 ret = SHAlloc(len);
734                 if (ret == NULL) return NULL;
735                 dskentry_decode(entry->value.str, entry->value.len, ret);
736                 if (dwFlags & XDG_URLENCODE)
737                     url_decode(ret, ret);
738                 return ret;
739             }
740     }
741     
742     return NULL;
743 }
744
745 /* Get the name of the xdg configuration file.
746  *
747  * [in] home_dir - $HOME
748  * [out] config_file - the name of the configuration file
749  */
750 static HRESULT get_xdg_config_file(char * home_dir, char ** config_file)
751 {
752     char *config_home;
753
754     config_home = getenv("XDG_CONFIG_HOME");
755     if (!config_home || !config_home[0])
756     {
757         *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1);
758         if (!*config_file)
759             return E_OUTOFMEMORY;
760
761         strcpy(*config_file, home_dir);
762         strcat(*config_file, "/.config/user-dirs.dirs");
763     }
764     else
765     {
766         *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(config_home) + strlen("/user-dirs.dirs") + 1);
767         if (!*config_file)
768             return E_OUTOFMEMORY;
769
770         strcpy(*config_file, config_home);
771         strcat(*config_file, "/user-dirs.dirs");
772     }
773     return S_OK;
774 }
775
776 /* Parse the key in a line in the xdg-user-dir config file.
777  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
778  *      ^                ^
779  *
780  * [in] xdg_dirs - array of xdg directories to look for
781  * [in] num_dirs - number of elements in xdg_dirs
782  * [in/out] p_ptr - pointer to where we are in the buffer
783  * Returns the index to xdg_dirs if we find the key, or -1 on error.
784  */
785 static int parse_config1(const char * const *xdg_dirs, const unsigned int num_dirs, char ** p_ptr)
786 {
787     char *p;
788     int i;
789
790     p = *p_ptr;
791     while (*p == ' ' || *p == '\t')
792         p++;
793     if (strncmp(p, "XDG_", 4))
794         return -1;
795
796     p += 4;
797     for (i = 0; i < num_dirs; i++)
798     {
799         if (!strncmp(p, xdg_dirs[i], strlen(xdg_dirs[i])))
800         {
801             p += strlen(xdg_dirs[i]);
802             break;
803         }
804     }
805     if (i == num_dirs)
806         return -1;
807     if (strncmp(p, "_DIR", 4))
808         return -1;
809     p += 4;
810     while (*p == ' ' || *p == '\t')
811         p++;
812     if (*p != '=')
813         return -1;
814     p++;
815     while (*p == ' ' || *p == '\t')
816         p++;
817     if (*p != '"')
818         return -1;
819     p++;
820
821     *p_ptr = p;
822     return i;
823 }
824
825 /* Parse the value in a line in the xdg-user-dir config file.
826  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
827  *                        ^             ^
828  *
829  * [in] p - pointer to the buffer
830  * [in] home_dir - $HOME
831  * [out] out_ptr - the directory name
832  */
833 static HRESULT parse_config2(char * p, const char * home_dir, char ** out_ptr)
834 {
835     BOOL relative;
836     char *out, *d;
837
838     relative = FALSE;
839
840     if (!strncmp(p, "$HOME/", 6))
841     {
842         p += 6;
843         relative = TRUE;
844     }
845     else if (*p != '/')
846         return E_FAIL;
847
848     if (relative)
849     {
850         out = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen(p) + 2);
851         if (!out)
852             return E_OUTOFMEMORY;
853
854         strcpy(out, home_dir);
855         strcat(out, "/");
856     }
857     else
858     {
859         out = HeapAlloc(GetProcessHeap(), 0, strlen(p) + 1);
860         if (!out)
861             return E_OUTOFMEMORY;
862         *out = 0;
863     }
864
865     d = out + strlen(out);
866     while (*p && *p != '"')
867     {
868         if ((*p == '\\') && (*(p + 1) != 0))
869             p++;
870         *d++ = *p++;
871     }
872     *d = 0;
873     *out_ptr = out;
874     return S_OK;
875 }
876
877 /* Parse part of a line in the xdg-user-dir config file.
878  * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
879  *                        ^             ^
880  *
881  * The calling function is responsible for freeing all elements of out_ptr as
882  * well as out_ptr itself.
883  *
884  * [in] xdg_dirs - array of xdg directories to look for
885  * [in] num_dirs - number of elements in xdg_dirs
886  * [out] out_ptr - an array of the xdg directories names
887  */
888 HRESULT XDG_UserDirLookup(const char * const *xdg_dirs, const unsigned int num_dirs, char *** out_ptr)
889 {
890     FILE *file;
891     char **out;
892     char *home_dir, *config_file;
893     char buffer[512];
894     int len;
895     unsigned int i;
896     HRESULT hr;
897
898     *out_ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, num_dirs * sizeof(char *));
899     out = *out_ptr;
900     if (!out)
901         return E_OUTOFMEMORY;
902
903     home_dir = getenv("HOME");
904     if (!home_dir)
905     {
906         hr = E_FAIL;
907         goto xdg_user_dir_lookup_error;
908     }
909
910     hr = get_xdg_config_file(home_dir, &config_file);
911     if (FAILED(hr))
912         goto xdg_user_dir_lookup_error;
913
914     file = fopen(config_file, "r");
915     HeapFree(GetProcessHeap(), 0, config_file);
916     if (!file)
917     {
918         hr = E_HANDLE;
919         goto xdg_user_dir_lookup_error;
920     }
921
922     while (fgets(buffer, sizeof(buffer), file))
923     {
924         int idx;
925         char *p;
926
927         /* Remove newline at end */
928         len = strlen(buffer);
929         if (len > 0 && buffer[len-1] == '\n')
930             buffer[len-1] = 0;
931
932         /* Parse the key */
933         p = buffer;
934         idx = parse_config1(xdg_dirs, num_dirs, &p);
935         if (idx < 0)
936             continue;
937         if (out[idx])
938             continue;
939
940         /* Parse the value */
941         hr = parse_config2(p, home_dir, &out[idx]);
942         if (FAILED(hr))
943         {
944             if (hr == E_OUTOFMEMORY)
945                 goto xdg_user_dir_lookup_error;
946             continue;
947         }
948     }
949     fclose (file);
950     hr = S_OK;
951
952     /* Remove entries for directories that do not exist */
953     for (i = 0; i <  num_dirs; i++)
954     {
955         struct stat statFolder;
956
957         if (!out[i])
958             continue;
959         if (!stat(out[i], &statFolder) && S_ISDIR(statFolder.st_mode))
960             continue;
961         HeapFree(GetProcessHeap(), 0, out[i]);
962         out[i] = NULL;
963     }
964
965 xdg_user_dir_lookup_error:
966     if (FAILED(hr))
967     {
968         for (i = 0; i < num_dirs; i++) HeapFree(GetProcessHeap(), 0, out[i]);
969         HeapFree(GetProcessHeap(), 0, *out_ptr);
970     }
971     return hr;
972 }