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
);