urlmon: Mark internal functions as hidden.
[wine] / dlls / shell32 / xdg.c
CommitLineData
f2686c7c
MZ
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
f5ba1c21
LZ
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.
f2686c7c 46 */
c35b6b92
FG
47
48#include "config.h"
53e16571 49#include "wine/port.h"
c35b6b92 50
f5ba1c21 51#include <stdio.h>
f2686c7c
MZ
52#include <stdarg.h>
53#include <stdlib.h>
54#include <string.h>
c35b6b92
FG
55#ifdef HAVE_SYS_STAT_H
56# include <sys/stat.h>
57#endif
58#ifdef HAVE_UNISTD_H
59# include <unistd.h>
60#endif
f2686c7c
MZ
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
71WINE_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
78static CRITICAL_SECTION XDG_PathsLock;
79static 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};
86static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
87
88typedef struct
89{
90 const char *var_name;
91 const char *default_value;
92} std_path;
93
94static 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 */
105static const char *path_values[PATHS_COUNT] = {
106 NULL,
107 NULL,
108 NULL,
109 NULL,
110 NULL
111};
112
113static 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)
92571d8d 145 lstrcpyA(ret, paths[path_id].default_value);
f2686c7c
MZ
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 */
8838dcb4 162static const char *XDG_GetPath(int path_id)
f2686c7c
MZ
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/******************************************************************************
7c70f7f9 180 * XDG_BuildPath [internal]
f2686c7c
MZ
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 */
189char *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 */
225BOOL 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 */
283static 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
589aeba9
MZ
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 */
339static 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
f2686c7c
MZ
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 */
394static int url_encode(const char *value, char *output)
395{
e4c0748c
DT
396 static const char unsafechars[] = "^&`{}|[]'<>\\#%\"+";
397 static const char hexchars[] = "0123456789ABCDEF";
f2686c7c
MZ
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++) = '%';
1fc0cb75
NL
408 *(output++) = hexchars[(unsigned char)*c / 16];
409 *(output++) = hexchars[(unsigned char)*c % 16];
f2686c7c
MZ
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
589aeba9
MZ
428static int decode_url_code(const char *c)
429{
430 const char *p1, *p2;
431 int v1, v2;
e4c0748c 432 static const char hexchars[] = "0123456789ABCDEF";
589aeba9
MZ
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 */
454static 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
f2686c7c
MZ
476static 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 */
491BOOL 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}
589aeba9
MZ
508
509typedef struct
510{
511 char *str;
512 int len;
513} PARSED_STRING;
514
515typedef struct tagPARSED_ENTRY PARSED_ENTRY;
516struct tagPARSED_ENTRY
517{
518 PARSED_STRING name;
519 PARSED_STRING equals;
520 PARSED_STRING value;
521 PARSED_ENTRY *next;
522};
523
524typedef struct tagPARSED_GROUP PARSED_GROUP;
525struct tagPARSED_GROUP
526{
527 PARSED_STRING name;
528 PARSED_ENTRY *entries;
529 PARSED_GROUP *next;
530};
531
532
533struct tagXDG_PARSED_FILE
534{
535 char *contents;
536 PARSED_ENTRY *head_comments;
537 PARSED_GROUP *groups;
538};
539
efdc1168 540static BOOL parsed_str_eq(const PARSED_STRING *str1, const char *str2)
589aeba9
MZ
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
549static 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
560void 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
581static 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
642XDG_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
711failed:
712 XDG_FreeParsedFile(parsed);
713 return NULL;
714}
715
716char *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}
f5ba1c21
LZ
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 */
750static 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 */
6b476208 785static int parse_config1(const char * const *xdg_dirs, const unsigned int num_dirs, char ** p_ptr)
f5ba1c21
LZ
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 */
833static 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 */
6b476208 888HRESULT XDG_UserDirLookup(const char * const *xdg_dirs, const unsigned int num_dirs, char *** out_ptr)
f5ba1c21
LZ
889{
890 FILE *file;
891 char **out;
892 char *home_dir, *config_file;
893 char buffer[512];
45d7897c
AT
894 int len;
895 unsigned int i;
f5ba1c21
LZ
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 }
ea4c70a1 949 fclose (file);
f5ba1c21
LZ
950 hr = S_OK;
951
acf8e0f5 952 /* Remove entries for directories that do not exist */
f5ba1c21
LZ
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
965xdg_user_dir_lookup_error:
966 if (FAILED(hr))
967 {
968 for (i = 0; i < num_dirs; i++) HeapFree(GetProcessHeap(), 0, out[i]);
ff838008 969 HeapFree(GetProcessHeap(), 0, *out_ptr);
f5ba1c21
LZ
970 }
971 return hr;
972}