Commit | Line | Data |
---|---|---|
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 | ||
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) | |
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 | 162 | static 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 | */ | |
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 | ||
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 | */ | |
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 | ||
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 | */ | |
394 | static 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 |
428 | static 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 | */ | |
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 | ||
f2686c7c MZ |
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 | } | |
589aeba9 MZ |
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 | ||
efdc1168 | 540 | static 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 | ||
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 | } | |
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 | */ | |
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 | */ | |
6b476208 | 785 | static 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 | */ | |
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 | */ | |
6b476208 | 888 | HRESULT 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 | ||
965 | xdg_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 | } |