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
49 #include "wine/port.h"
55 #ifdef HAVE_SYS_STAT_H
56 # include <sys/stat.h>
67 #include "wine/debug.h"
68 #include "shell32_main.h"
71 WINE_DEFAULT_DEBUG_CHANNEL(xdg
);
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)
78 static CRITICAL_SECTION XDG_PathsLock
;
79 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug
=
82 { &XDG_PathsLock_Debug
.ProcessLocksList
,
83 &XDG_PathsLock_Debug
.ProcessLocksList
},
84 0, 0, { (DWORD_PTR
)__FILE__
": XDG_PathsLock"}
86 static CRITICAL_SECTION XDG_PathsLock
= { &XDG_PathsLock_Debug
, -1, 0, 0, 0, 0 };
91 const char *default_value
;
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"}
102 /* will be filled with paths as they are computed */
103 static const char *path_values
[ARRAY_SIZE(paths
)] = {
111 static char *load_path(int path_id
)
113 char *env
= getenv(paths
[path_id
].var_name
);
116 if (env
!= NULL
&& env
[0]=='/')
118 ret
= SHAlloc(strlen(env
)+1);
124 if (memcmp(paths
[path_id
].default_value
, "$HOME", 5)==0)
126 char *home
= getenv("HOME");
129 if (!home
) return NULL
;
130 ret
= SHAlloc(strlen(home
)+strlen(paths
[path_id
].default_value
)-5+1);
131 if (ret
== NULL
) return NULL
;
135 if (len
>0 && ret
[len
-1]=='/')
137 lstrcatA(ret
, paths
[path_id
].default_value
+5);
141 ret
= SHAlloc(strlen(paths
[path_id
].default_value
)+1);
143 lstrcpyA(ret
, paths
[path_id
].default_value
);
147 /******************************************************************************
148 * XDG_GetPath [internal]
150 * Get one of the XDG standard patch. The return value shouldn't be modified nor
151 * freed. A return value of NULL means that the memory is exhausted or the input
154 * For XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME the result is a Unix path.
155 * For XDG_DATA_DIRS and XDG_CONFIG_DIRS the result is a colon-separated list of Unix
158 * The paths are guaranteed to start with '/'
160 static const char *XDG_GetPath(int path_id
)
162 if (path_id
>= ARRAY_SIZE(paths
) || path_id
< 0)
164 ERR("Invalid path_id %d\n", path_id
);
168 if (path_values
[path_id
] != NULL
)
169 return path_values
[path_id
];
170 EnterCriticalSection(&XDG_PathsLock
);
171 if (path_values
[path_id
] == NULL
)
172 path_values
[path_id
] = load_path(path_id
);
173 LeaveCriticalSection(&XDG_PathsLock
);
174 return path_values
[path_id
];
177 /******************************************************************************
178 * XDG_BuildPath [internal]
180 * Build a string with a subpath of one of the XDG standard paths.
181 * The root can be one of XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME.
182 * The subpath is a path relative to that root (it shouldn't start with a slash)
184 * The returned path should be freed with SHFree. A return of NULL means that the
185 * memory is exhausted or the parameters are invalid
187 char *XDG_BuildPath(int root_id
, const char *subpath
)
189 const char *root_path
= XDG_GetPath(root_id
);
193 if (root_id
== XDG_DATA_DIRS
|| root_id
== XDG_CONFIG_DIRS
)
195 ERR("Invalid path id %d\n", root_id
);
199 if (root_path
== NULL
) return NULL
;
200 root_len
= strlen(root_path
);
201 if (root_path
[root_len
-1]=='/') root_len
--;
202 ret_buffer
= SHAlloc(root_len
+1+strlen(subpath
)+1);
203 if (ret_buffer
== NULL
) return NULL
;
204 lstrcpyA(ret_buffer
, root_path
);
205 ret_buffer
[root_len
]='/';
206 lstrcpyA(ret_buffer
+root_len
+1, subpath
);
210 /******************************************************************************
211 * XDG_MakeDirs [internal]
213 * Checks that all the directories on the specified path exists. If some don't exists
214 * they are created with mask 0700 as required by many the freedeskop.org specs.
215 * If the path doesn't end with '/' it is assumed to be a path to a file and the last
216 * segment is not checked
218 * In case of a failure the errno is always set and can be used e.g for debugging
221 * TRUE on success, FALSE on error
223 BOOL
XDG_MakeDirs(const char *path
)
228 char *buffer
= SHAlloc(strlen(path
)+1);
235 lstrcpyA(buffer
, path
);
237 TRACE("(%s)\n", debugstr_a(path
));
240 char *slash
=strchr(buffer
+last_slash
+1, '/');
244 /* cut the string at that position and create the directory if it doesn't exist */
246 TRACE("Checking path %s\n", debugstr_a(buffer
));
247 success
= (stat(buffer
, &tmp
)==0);
248 if (!success
&& errno
==ENOENT
)
251 success
= (mkdir(buffer
, 0700)==0);
255 WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer
), errno
);
259 last_slash
= slash
-buffer
;
266 * .desktop files functions
270 /******************************************************************************
271 * dskentry_encode [internal]
273 * Escape the characters that can't be present in a desktop entry value like \n, leading
274 * spaces etc. The output parameter may be NULL. Then only the number of characters will
278 * The number of characters after escaping the special characters, including the
281 static int dskentry_encode(const char *value
, char *output
)
283 BOOL only_spc
= TRUE
;
286 for (c
= value
; *c
; c
++)
288 if (only_spc
&& *c
==' ')
300 if (*c
=='\t' || *c
=='\r' || *c
=='\n' || *c
=='\\')
305 if (*c
=='\t') *(output
++) = 't';
306 if (*c
=='\r') *(output
++) = 'r';
307 if (*c
=='\n') *(output
++) = 'n';
308 if (*c
=='\\') *(output
++) = '\\';
326 /******************************************************************************
327 * dskentry_decode [internal]
329 * Unescape the characters that can be escaped according to the desktop entry spec.
330 * The output parameter may be NULL. Then only the number of characters will
334 * The number of characters after unescaping the special characters, including the
337 static int dskentry_decode(const char *value
, int len
, char *output
)
344 if (pos
<len
-1 && value
[pos
] == '\\')
349 case 's': c
= ' '; break;
350 case 'n': c
= '\n'; break;
351 case 't': c
= '\t'; break;
352 case 'r': c
= 'r'; break;
353 case '\\': c
= '\\'; break;
355 /* store both the backslash and the character */
379 /******************************************************************************
380 * url_encode [internal]
382 * URL-encode the given string (i.e. use escape codes like %20). Note that an
383 * URL-encoded string can be used as a value in desktop entry files as all
384 * unsafe characters are escaped.
386 * The output can be NULL. Then only the number of characters will be counted
389 * The number of characters after escaping the special characters, including the
392 static int url_encode(const char *value
, char *output
)
394 static const char unsafechars
[] = "^&`{}|[]'<>\\#%\"+";
395 static const char hexchars
[] = "0123456789ABCDEF";
399 for (c
= value
; *c
; c
++)
401 if (*c
<=0x20 || *c
>=0x7f || strchr(unsafechars
, *c
))
406 *(output
++) = hexchars
[(unsigned char)*c
/ 16];
407 *(output
++) = hexchars
[(unsigned char)*c
% 16];
426 static int decode_url_code(const char *c
)
430 static const char hexchars
[] = "0123456789ABCDEF";
434 p1
= strchr(hexchars
, toupper(*c
));
435 p2
= strchr(hexchars
, toupper(*(c
+1)));
436 if (p1
== NULL
|| p2
== NULL
)
438 v1
= (int)(p1
- hexchars
);
439 v2
= (int)(p2
- hexchars
);
443 /******************************************************************************
444 * url_decode [internal]
446 * URL-decode the given string (i.e. unescape codes like %20). The decoded string
447 * will never be longer than the encoded one. The decoding can be done in place - the
448 * output variable can point to the value buffer.
450 * output should not be NULL
452 static void url_decode(const char *value
, char *output
)
454 const char *c
= value
;
459 int v
= decode_url_code(c
+1);
474 static int escape_value(const char *value
, DWORD dwFlags
, char *output
)
476 if (dwFlags
& XDG_URLENCODE
)
477 return url_encode(value
, output
);
478 return dskentry_encode(value
, output
);
481 /******************************************************************************
482 * XDG_WriteDesktopStringEntry [internal]
484 * Writes a key=value pair into the specified file descriptor.
487 * TRUE on success, else FALSE
489 BOOL
XDG_WriteDesktopStringEntry(int writer
, const char *keyName
, DWORD dwFlags
, const char *value
)
491 int keyLen
= lstrlenA(keyName
);
492 int valueLen
= escape_value(value
, dwFlags
, NULL
);
493 char *string
= SHAlloc(keyLen
+1+valueLen
);
498 lstrcpyA(string
, keyName
);
499 string
[keyLen
] = '=';
500 escape_value(value
, dwFlags
, string
+keyLen
+1);
501 string
[keyLen
+1+valueLen
-1]='\n'; /* -1 because valueLen contains the last NUL character */
502 ret
= (write(writer
, string
, keyLen
+1+valueLen
)!=-1);
513 typedef struct tagPARSED_ENTRY PARSED_ENTRY
;
514 struct tagPARSED_ENTRY
517 PARSED_STRING equals
;
522 typedef struct tagPARSED_GROUP PARSED_GROUP
;
523 struct tagPARSED_GROUP
526 PARSED_ENTRY
*entries
;
531 struct tagXDG_PARSED_FILE
534 PARSED_ENTRY
*head_comments
;
535 PARSED_GROUP
*groups
;
538 static BOOL
parsed_str_eq(const PARSED_STRING
*str1
, const char *str2
)
540 if (strncmp(str1
->str
, str2
, str1
->len
) != 0)
542 if (str2
[str1
->len
] != 0)
547 static void free_entries_list(PARSED_ENTRY
*first
)
558 void XDG_FreeParsedFile(XDG_PARSED_FILE
*parsed
)
560 PARSED_GROUP
*group
, *next
;
563 free_entries_list(parsed
->head_comments
);
565 group
= parsed
->groups
;
569 free_entries_list(group
->entries
);
573 SHFree(parsed
->contents
);
579 #define LINE_COMMENT 3
581 static int parse_line(char *content
, PARSED_ENTRY
*output
, int *outType
)
585 ZeroMemory(output
, sizeof(PARSED_ENTRY
));
586 end
= strchr(content
, '\n');
588 end
= content
+ strlen(content
) - 1;
592 *outType
= LINE_COMMENT
;
593 output
->equals
.str
= content
;
594 output
->equals
.len
= end
- content
;
596 output
->equals
.len
++;
598 else if (*content
== '[')
600 char *last_char
= end
;
602 *outType
= LINE_GROUP
;
604 /* the standard says nothing about skipping white spaces but e.g. KDE accepts such files */
605 while (isspace(*last_char
))
607 if (*last_char
!= ']')
609 output
->name
.str
= content
+ 1;
610 output
->name
.len
= last_char
- (content
+ 1);
614 /* 'name = value' line */
615 char *equal
, *eq_begin
, *eq_end
;
617 *outType
= LINE_ENTRY
;
619 equal
= strchr(content
, '=');
620 if (equal
== NULL
|| equal
> end
)
622 for (eq_begin
= equal
-1; isspace(*eq_begin
) && eq_begin
>= content
; eq_begin
--)
624 for (eq_end
= equal
+1; isspace(*eq_end
) && *eq_end
!= '\n'; eq_end
++)
627 output
->name
.str
= content
;
628 output
->name
.len
= eq_begin
- content
+ 1;
630 output
->equals
.str
= eq_begin
+ 1;
631 output
->equals
.len
= eq_end
- eq_begin
- 1;
633 output
->value
.str
= eq_end
;
634 output
->value
.len
= end
- eq_end
;
639 return end
- content
+ 1;
642 XDG_PARSED_FILE
*XDG_ParseDesktopFile(int fd
)
645 XDG_PARSED_FILE
*parsed
= NULL
;
646 PARSED_ENTRY
**curr_entry
;
647 PARSED_GROUP
**curr_group
;
648 BOOL is_in_group
= FALSE
;
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
;
660 curr_entry
= &parsed
->head_comments
;
661 curr_group
= &parsed
->groups
;
663 if (read(fd
, parsed
->contents
, stats
.st_size
) == -1) goto failed
;
664 parsed
->contents
[stats
.st_size
] = 0;
666 while (pos
< stats
.st_size
)
668 PARSED_ENTRY statement
;
671 size
= parse_line(parsed
->contents
+ pos
, &statement
, &type
);
672 if (size
== -1) goto failed
;
681 PARSED_GROUP
*group
= SHAlloc(sizeof(PARSED_GROUP
));
682 if (group
== NULL
) goto failed
;
685 group
->name
= statement
.name
;
686 group
->entries
= NULL
;
689 curr_group
= &group
->next
;
690 curr_entry
= &group
->entries
;
695 if (!is_in_group
) goto failed
;
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
;
712 XDG_FreeParsedFile(parsed
);
716 char *XDG_GetStringValue(XDG_PARSED_FILE
*file
, const char *group_name
, const char *value_name
, DWORD dwFlags
)
721 for (group
= file
->groups
; group
; group
= group
->next
)
723 if (!parsed_str_eq(&group
->name
, group_name
))
726 for (entry
= group
->entries
; entry
; entry
= entry
->next
)
727 if (entry
->name
.str
!= NULL
&& parsed_str_eq(&entry
->name
, value_name
))
732 len
= dskentry_decode(entry
->value
.str
, entry
->value
.len
, NULL
);
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
);
745 /* Get the name of the xdg configuration file.
747 * [in] home_dir - $HOME
748 * [out] config_file - the name of the configuration file
750 static HRESULT
get_xdg_config_file(char * home_dir
, char ** config_file
)
754 config_home
= getenv("XDG_CONFIG_HOME");
755 if (!config_home
|| !config_home
[0])
757 *config_file
= heap_alloc(strlen(home_dir
) + strlen("/.config/user-dirs.dirs") + 1);
759 return E_OUTOFMEMORY
;
761 strcpy(*config_file
, home_dir
);
762 strcat(*config_file
, "/.config/user-dirs.dirs");
766 *config_file
= heap_alloc(strlen(config_home
) + strlen("/user-dirs.dirs") + 1);
768 return E_OUTOFMEMORY
;
770 strcpy(*config_file
, config_home
);
771 strcat(*config_file
, "/user-dirs.dirs");
776 /* Parse the key in a line in the xdg-user-dir config file.
777 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
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.
785 static int parse_config1(const char * const *xdg_dirs
, const unsigned int num_dirs
, char ** p_ptr
)
791 while (*p
== ' ' || *p
== '\t')
793 if (strncmp(p
, "XDG_", 4))
797 for (i
= 0; i
< num_dirs
; i
++)
799 if (!strncmp(p
, xdg_dirs
[i
], strlen(xdg_dirs
[i
])))
801 p
+= strlen(xdg_dirs
[i
]);
807 if (strncmp(p
, "_DIR", 4))
810 while (*p
== ' ' || *p
== '\t')
815 while (*p
== ' ' || *p
== '\t')
825 /* Parse the value in a line in the xdg-user-dir config file.
826 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
829 * [in] p - pointer to the buffer
830 * [in] home_dir - $HOME
831 * [out] out_ptr - the directory name
833 static HRESULT
parse_config2(char * p
, const char * home_dir
, char ** out_ptr
)
840 if (!strncmp(p
, "$HOME/", 6))
850 out
= heap_alloc(strlen(home_dir
) + strlen(p
) + 2);
852 return E_OUTOFMEMORY
;
854 strcpy(out
, home_dir
);
859 out
= heap_alloc(strlen(p
) + 1);
861 return E_OUTOFMEMORY
;
865 d
= out
+ strlen(out
);
866 while (*p
&& *p
!= '"')
868 if ((*p
== '\\') && (*(p
+ 1) != 0))
877 /* Parse part of a line in the xdg-user-dir config file.
878 * i.e. XDG_PICTURES_DIR="$HOME/Pictures"
881 * The calling function is responsible for freeing all elements of out_ptr as
882 * well as out_ptr itself.
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
888 HRESULT
XDG_UserDirLookup(const char * const *xdg_dirs
, const unsigned int num_dirs
, char *** out_ptr
)
892 char *home_dir
, *config_file
;
898 *out_ptr
= heap_alloc_zero(num_dirs
* sizeof(char *));
901 return E_OUTOFMEMORY
;
903 home_dir
= getenv("HOME");
907 goto xdg_user_dir_lookup_error
;
910 hr
= get_xdg_config_file(home_dir
, &config_file
);
912 goto xdg_user_dir_lookup_error
;
914 file
= fopen(config_file
, "r");
915 heap_free(config_file
);
919 goto xdg_user_dir_lookup_error
;
922 while (fgets(buffer
, sizeof(buffer
), file
))
927 /* Remove newline at end */
928 len
= strlen(buffer
);
929 if (len
> 0 && buffer
[len
-1] == '\n')
934 idx
= parse_config1(xdg_dirs
, num_dirs
, &p
);
940 /* Parse the value */
941 hr
= parse_config2(p
, home_dir
, &out
[idx
]);
942 if (hr
== E_OUTOFMEMORY
)
945 goto xdg_user_dir_lookup_error
;
951 /* Remove entries for directories that do not exist */
952 for (i
= 0; i
< num_dirs
; i
++)
954 struct stat statFolder
;
958 if (!stat(out
[i
], &statFolder
) && S_ISDIR(statFolder
.st_mode
))
964 xdg_user_dir_lookup_error
:
967 for (i
= 0; i
< num_dirs
; i
++)