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
32 #include "wine/debug.h"
33 #include "shell32_main.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(xdg);
39 * XDG paths implemented using Desktop Base Directory spec version 0.6
40 * (from http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html)
43 static CRITICAL_SECTION XDG_PathsLock;
44 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
47 { &XDG_PathsLock_Debug.ProcessLocksList,
48 &XDG_PathsLock_Debug.ProcessLocksList},
49 0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
51 static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
56 const char *default_value;
59 static const std_path paths[] = {
60 {"XDG_DATA_HOME", "$HOME/.local/share"},
61 {"XDG_CONFIG_HOME", "$HOME/.config"},
62 {"XDG_DATA_DIRS", "/usr/local/share:/usr/share"},
63 {"XDG_CONFIG_DIRS", "/etc/xdg"},
64 {"XDG_CACHE_HOME", "$HOME/.cache"}
67 #define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
69 /* will be filled with paths as they are computed */
70 static const char *path_values[PATHS_COUNT] = {
78 static char *load_path(int path_id)
80 char *env = getenv(paths[path_id].var_name);
83 if (env != NULL && env[0]=='/')
85 ret = SHAlloc(strlen(env)+1);
91 if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
93 char *home = getenv("HOME");
96 if (!home) return NULL;
97 ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
98 if (ret == NULL) return NULL;
102 if (len>0 && ret[len-1]=='/')
104 lstrcatA(ret, paths[path_id].default_value+5);
108 ret = SHAlloc(strlen(paths[path_id].default_value)+1);
114 /******************************************************************************
115 * XDG_GetPath [internal]
117 * Get one of the XDG standard patch. The return value shouldn't be modified nor
118 * freed. A return value of NULL means that the memory is exhausted or the input
121 * For XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME the result is a Unix path.
122 * For XDG_DATA_DIRS and XDG_CONFIG_DIRS the result is a colon-separated list of Unix
125 * The paths are guaranteed to start with '/'
127 const char *XDG_GetPath(int path_id)
129 if (path_id >= PATHS_COUNT || path_id < 0)
131 ERR("Invalid path_id %d\n", path_id);
135 if (path_values[path_id] != NULL)
136 return path_values[path_id];
137 EnterCriticalSection(&XDG_PathsLock);
138 if (path_values[path_id] == NULL)
139 path_values[path_id] = load_path(path_id);
140 LeaveCriticalSection(&XDG_PathsLock);
141 return path_values[path_id];
144 /******************************************************************************
145 * XDG_GetPath [internal]
147 * Build a string with a subpath of one of the XDG standard paths.
148 * The root can be one of XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME.
149 * The subpath is a path relative to that root (it shouldn't start with a slash)
151 * The returned path should be freed with SHFree. A return of NULL means that the
152 * memory is exhausted or the parameters are invalid
154 char *XDG_BuildPath(int root_id, const char *subpath)
156 const char *root_path = XDG_GetPath(root_id);
160 if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
162 ERR("Invalid path id %d\n", root_id);
166 if (root_path == NULL) return NULL;
167 root_len = strlen(root_path);
168 if (root_path[root_len-1]=='/') root_len--;
169 ret_buffer = SHAlloc(root_len+1+strlen(subpath)+1);
170 if (ret_buffer == NULL) return NULL;
171 lstrcpyA(ret_buffer, root_path);
172 ret_buffer[root_len]='/';
173 lstrcpyA(ret_buffer+root_len+1, subpath);
177 /******************************************************************************
178 * XDG_MakeDirs [internal]
180 * Checks that all the directories on the specified path exists. If some don't exists
181 * they are created with mask 0700 as required by many the freedeskop.org specs.
182 * If the path doesn't end with '/' it is assumed to be a path to a file and the last
183 * segment is not checked
185 * In case of a failure the errno is always set and can be used e.g for debugging
188 * TRUE on success, FALSE on error
190 BOOL XDG_MakeDirs(const char *path)
195 char *buffer = SHAlloc(strlen(path)+1);
202 lstrcpyA(buffer, path);
204 TRACE("(%s)\n", debugstr_a(path));
207 char *slash=strchr(buffer+last_slash+1, '/');
211 /* cut the string at that position and create the directory if it doesn't exist */
213 TRACE("Checking path %s\n", debugstr_a(buffer));
214 success = (stat(buffer, &tmp)==0);
215 if (!success && errno==ENOENT)
218 success = (mkdir(buffer, 0700)==0);
222 WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
226 last_slash = slash-buffer;
233 * .desktop files functions
237 /******************************************************************************
238 * dskentry_encode [internal]
240 * Escape the characters that can't be present in a desktop entry value like \n, leading
241 * spaces etc. The output parameter may be NULL. Then only the number of characters will
245 * The number of characters after escaping the special characters, including the
248 static int dskentry_encode(const char *value, char *output)
253 for (c = value; *c; c++)
255 if (only_spc && *c==' ')
267 if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
272 if (*c=='\t') *(output++) = 't';
273 if (*c=='\r') *(output++) = 'r';
274 if (*c=='\n') *(output++) = 'n';
275 if (*c=='\\') *(output++) = '\\';
293 /******************************************************************************
294 * dskentry_decode [internal]
296 * Unescape the characters that can be escaped according to the desktop entry spec.
297 * The output parameter may be NULL. Then only the number of characters will
301 * The number of characters after unescaping the special characters, including the
304 static int dskentry_decode(const char *value, int len, char *output)
311 if (value[pos] == '\\' && pos<len-1)
316 case 's': c = ' '; break;
317 case 'n': c = '\n'; break;
318 case 't': c = '\t'; break;
319 case 'r': c = 'r'; break;
320 case '\\': c = '\\'; break;
322 /* store both the backslash and the character */
346 /******************************************************************************
347 * url_encode [internal]
349 * URL-encode the given string (i.e. use escape codes like %20). Note that an
350 * URL-encoded string can be used as a value in desktop entry files as all
351 * unsafe characters are escaped.
353 * The output can be NULL. Then only the number of characters will be counted
356 * The number of characters after escaping the special characters, including the
359 static int url_encode(const char *value, char *output)
361 static const char *unsafechars = "^&`{}|[]'<>\\#%\"+";
362 static const char *hexchars = "0123456789ABCDEF";
366 for (c = value; *c; c++)
368 if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
373 *(output++) = hexchars[(unsigned)(*c)/16];
374 *(output++) = hexchars[(unsigned)(*c)%16];
393 static int decode_url_code(const char *c)
397 static const char *hexchars = "0123456789ABCDEF";
401 p1 = strchr(hexchars, toupper(*c));
402 p2 = strchr(hexchars, toupper(*(c+1)));
403 if (p1 == NULL || p2 == NULL)
405 v1 = (int)(p1 - hexchars);
406 v2 = (int)(p2 - hexchars);
410 /******************************************************************************
411 * url_decode [internal]
413 * URL-decode the given string (i.e. unescape codes like %20). The decoded string
414 * will never be longer than the encoded one. The decoding can be done in place - the
415 * output variable can point to the value buffer.
417 * output should not be NULL
419 static void url_decode(const char *value, char *output)
421 const char *c = value;
426 int v = decode_url_code(c+1);
441 static int escape_value(const char *value, DWORD dwFlags, char *output)
443 if (dwFlags & XDG_URLENCODE)
444 return url_encode(value, output);
445 return dskentry_encode(value, output);
448 /******************************************************************************
449 * XDG_WriteDesktopStringEntry [internal]
451 * Writes a key=value pair into the specified file descriptor.
454 * TRUE on success, else FALSE
456 BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
458 int keyLen = lstrlenA(keyName);
459 int valueLen = escape_value(value, dwFlags, NULL);
460 char *string = SHAlloc(keyLen+1+valueLen);
465 lstrcpyA(string, keyName);
466 string[keyLen] = '=';
467 escape_value(value, dwFlags, string+keyLen+1);
468 string[keyLen+1+valueLen-1]='\n'; /* -1 because valueLen contains the last NUL character */
469 ret = (write(writer, string, keyLen+1+valueLen)!=-1);
480 typedef struct tagPARSED_ENTRY PARSED_ENTRY;
481 struct tagPARSED_ENTRY
484 PARSED_STRING equals;
489 typedef struct tagPARSED_GROUP PARSED_GROUP;
490 struct tagPARSED_GROUP
493 PARSED_ENTRY *entries;
498 struct tagXDG_PARSED_FILE
501 PARSED_ENTRY *head_comments;
502 PARSED_GROUP *groups;
505 static BOOL parsed_str_eq(PARSED_STRING *str1, const char *str2)
507 if (strncmp(str1->str, str2, str1->len) != 0)
509 if (str2[str1->len] != 0)
514 static void free_entries_list(PARSED_ENTRY *first)
525 void XDG_FreeParsedFile(XDG_PARSED_FILE *parsed)
527 PARSED_GROUP *group, *next;
530 free_entries_list(parsed->head_comments);
532 group = parsed->groups;
536 free_entries_list(group->entries);
544 #define LINE_COMMENT 3
546 static int parse_line(char *content, PARSED_ENTRY *output, int *outType)
550 ZeroMemory(output, sizeof(PARSED_ENTRY));
551 end = strchr(content, '\n');
553 end = content + strlen(content) - 1;
557 *outType = LINE_COMMENT;
558 output->equals.str = content;
559 output->equals.len = end - content;
561 output->equals.len++;
563 else if (*content == '[')
565 char *last_char = end;
567 *outType = LINE_GROUP;
569 /* the standard says nothing about skipping white spaces but e.g. KDE accepts such files */
570 while (isspace(*last_char))
572 if (*last_char != ']')
574 output->name.str = content + 1;
575 output->name.len = last_char - (content + 1);
579 /* 'name = value' line */
580 char *equal, *eq_begin, *eq_end;
582 *outType = LINE_ENTRY;
584 equal = strchr(content, '=');
585 if (equal == NULL || equal > end)
587 for (eq_begin = equal-1; isspace(*eq_begin) && eq_begin >= content; eq_begin--)
589 for (eq_end = equal+1; isspace(*eq_end) && *eq_end != '\n'; eq_end++)
592 output->name.str = content;
593 output->name.len = eq_begin - content + 1;
595 output->equals.str = eq_begin + 1;
596 output->equals.len = eq_end - eq_begin - 1;
598 output->value.str = eq_end;
599 output->value.len = end - eq_end;
604 return end - content + 1;
607 XDG_PARSED_FILE *XDG_ParseDesktopFile(int fd)
610 XDG_PARSED_FILE *parsed = NULL;
611 PARSED_ENTRY **curr_entry;
612 PARSED_GROUP **curr_group;
613 BOOL is_in_group = FALSE;
617 if (fstat(fd, &stats) == -1) goto failed;
618 parsed = SHAlloc(sizeof(XDG_PARSED_FILE));
619 if (parsed == NULL) goto failed;
620 parsed->groups = NULL;
621 parsed->head_comments = NULL;
622 parsed->contents = SHAlloc(stats.st_size+1);
623 if (parsed->contents == NULL) goto failed;
625 curr_entry = &parsed->head_comments;
626 curr_group = &parsed->groups;
628 if (read(fd, parsed->contents, stats.st_size) == -1) goto failed;
629 parsed->contents[stats.st_size] = 0;
631 while (pos < stats.st_size)
633 PARSED_ENTRY statement;
636 size = parse_line(parsed->contents + pos, &statement, &type);
637 if (size == -1) goto failed;
646 PARSED_GROUP *group = SHAlloc(sizeof(PARSED_GROUP));
647 if (group == NULL) goto failed;
650 group->name = statement.name;
651 group->entries = NULL;
654 curr_group = &group->next;
655 curr_entry = &group->entries;
660 if (!is_in_group) goto failed;
664 PARSED_ENTRY *new_stat = SHAlloc(sizeof(PARSED_ENTRY));
665 if (new_stat == NULL) goto failed;
666 *new_stat = statement;
667 new_stat->next = NULL;
668 *curr_entry = new_stat;
669 curr_entry = &new_stat->next;
677 XDG_FreeParsedFile(parsed);
681 char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const char *value_name, DWORD dwFlags)
686 for (group = file->groups; group; group = group->next)
688 if (!parsed_str_eq(&group->name, group_name))
691 for (entry = group->entries; entry; entry = entry->next)
692 if (entry->name.str != NULL && parsed_str_eq(&entry->name, value_name))
697 len = dskentry_decode(entry->value.str, entry->value.len, NULL);
699 if (ret == NULL) return NULL;
700 dskentry_decode(entry->value.str, entry->value.len, ret);
701 if (dwFlags & XDG_URLENCODE)
702 url_decode(ret, ret);