shell32: Another cast-qual warning fix.
[wine] / dlls / shell32 / xdg.c
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
19  */
20  
21 #include <stdarg.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <errno.h>
27  
28 #include "windef.h"
29 #include "winbase.h"
30 #include "winreg.h"
31 #include "shlwapi.h"
32 #include "wine/debug.h"
33 #include "shell32_main.h"
34 #include "xdg.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(xdg);
37
38 /*
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)
41  */
42
43 static CRITICAL_SECTION XDG_PathsLock;
44 static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
45 {
46     0, 0, &XDG_PathsLock,
47     { &XDG_PathsLock_Debug.ProcessLocksList,
48       &XDG_PathsLock_Debug.ProcessLocksList},
49     0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
50 };
51 static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
52
53 typedef struct
54 {
55     const char *var_name;
56     const char *default_value;
57 } std_path;
58
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"}
65 };
66
67 #define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
68
69 /* will be filled with paths as they are computed */
70 static const char *path_values[PATHS_COUNT] = {
71     NULL,
72     NULL,
73     NULL,
74     NULL,
75     NULL
76 };
77
78 static char *load_path(int path_id)
79 {
80     char *env = getenv(paths[path_id].var_name);
81     char *ret;
82     
83     if (env != NULL && env[0]=='/')
84     {
85         ret = SHAlloc(strlen(env)+1);
86         if (ret != NULL)
87             lstrcpyA(ret, env);
88         return ret;
89     }
90     
91     if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
92     {
93         char *home = getenv("HOME");
94         int len;
95
96         if (!home) return NULL;
97         ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
98         if (ret == NULL) return NULL;
99         
100         lstrcpyA(ret, home);
101         len = strlen(ret);
102         if (len>0 && ret[len-1]=='/')
103             ret[--len]=0;
104         lstrcatA(ret, paths[path_id].default_value+5);
105         return ret;
106     }
107     
108     ret = SHAlloc(strlen(paths[path_id].default_value)+1);
109     if (ret != NULL)
110         lstrcpyA(ret, env);
111     return ret;
112 }
113
114 /******************************************************************************
115  * XDG_GetPath    [internal]
116  *
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
119  * is invalid
120  *
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
123  * paths
124  *
125  * The paths are guaranteed to start with '/'
126  */
127 const char *XDG_GetPath(int path_id)
128 {
129     if (path_id >= PATHS_COUNT || path_id < 0)
130     {
131         ERR("Invalid path_id %d\n", path_id);
132         return NULL;
133     }
134     
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];
142 }
143
144 /******************************************************************************
145  * XDG_GetPath    [internal]
146  *
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)
150  *
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
153  */
154 char *XDG_BuildPath(int root_id, const char *subpath)
155 {
156     const char *root_path = XDG_GetPath(root_id);
157     char *ret_buffer;
158     int root_len;
159     
160     if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
161     {
162         ERR("Invalid path id %d\n", root_id);
163         return NULL;
164     }
165     
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);
174     return ret_buffer;
175 }
176
177 /******************************************************************************
178  * XDG_MakeDirs    [internal]
179  *
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
184  *
185  * In case of a failure the errno is always set and can be used e.g for debugging
186  *
187  * RETURNS
188  *   TRUE on success, FALSE on error
189  */
190 BOOL XDG_MakeDirs(const char *path)
191 {
192     int last_slash = 0;
193     BOOL success = TRUE;
194     struct stat tmp;
195     char *buffer = SHAlloc(strlen(path)+1);
196     
197     if (buffer == NULL)
198     {
199         errno = ENOMEM;
200         return FALSE;
201     }
202     lstrcpyA(buffer, path);
203     
204     TRACE("(%s)\n", debugstr_a(path));
205     while (1)
206     {
207         char *slash=strchr(buffer+last_slash+1, '/');
208         if (slash==NULL)
209             break;
210         
211         /* cut the string at that position and create the directory if it doesn't exist */
212         *slash=0;
213         TRACE("Checking path %s\n", debugstr_a(buffer));
214         success = (stat(buffer, &tmp)==0);
215         if (!success && errno==ENOENT)
216         {
217             TRACE("Creating\n");
218             success = (mkdir(buffer, 0700)==0);
219         }
220         if (!success)
221         {
222             WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
223             break;
224         }
225         *slash='/';
226         last_slash = slash-buffer;
227     }
228     SHFree(buffer);
229     return success;
230 }
231
232 /*
233  * .desktop files functions
234  */
235
236
237 /******************************************************************************
238  * dskentry_encode    [internal]
239  *
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
242  * be computers.
243  *
244  * RETURNS
245  *   The number of characters after escaping the special characters, including the
246  * terminating NUL.
247  */
248 static int dskentry_encode(const char *value, char *output)
249 {
250     int only_spc = TRUE;
251     int num_written = 0;
252     const char *c;
253     for (c = value; *c; c++)
254     {
255         if (only_spc && *c==' ')
256         {
257             if (output)
258             {
259                 *(output++) = '\\';
260                 *(output++) = 's';
261             }
262             num_written += 2;
263             continue;
264         }
265         only_spc = FALSE;
266         
267         if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
268         {
269             if (output)
270             {
271                 *(output++) = '\\';
272                 if (*c=='\t') *(output++) = 't';
273                 if (*c=='\r') *(output++) = 'r';
274                 if (*c=='\n') *(output++) = 'n';
275                 if (*c=='\\') *(output++) = '\\';
276             }
277             num_written += 2;
278         }
279         else
280         {
281             if (output)
282                 *(output++)=*c;
283             num_written++;
284         }
285     }
286     
287     if (output)
288         *(output++) = 0;
289     num_written++;
290     return num_written;
291 }
292
293 /******************************************************************************
294  * dskentry_decode    [internal]
295  *
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
298  * be computers.
299  *
300  * RETURNS
301  *   The number of characters after unescaping the special characters, including the
302  * terminating NUL.
303  */
304 static int dskentry_decode(const char *value, int len, char *output)
305 {
306     int pos = 0;
307     int count = 0;
308     while (pos<len)
309     {
310         char c;
311         if (value[pos] == '\\' && pos<len-1)
312         {
313             pos++;
314             switch (value[pos])
315             {
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;
321                 default:
322                     /* store both the backslash and the character */
323                     if (output)
324                         *(output++) = '\\';
325                     count++;
326                     c = value[pos];
327                     break;
328             }
329         }
330         else
331             c = value[pos];
332             
333         if (output)
334             *(output++) = c;
335         count++;
336         pos++;
337     }
338     
339     if (output)
340         *(output++) = 0;
341     count++;
342     return count;
343 }
344
345
346 /******************************************************************************
347  * url_encode    [internal]
348  *
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.
352  *
353  * The output can be NULL. Then only the number of characters will be counted
354  *
355  * RETURNS
356  *   The number of characters after escaping the special characters, including the
357  * terminating NUL.
358  */
359 static int url_encode(const char *value, char *output)
360 {
361     static const char *unsafechars = "^&`{}|[]'<>\\#%\"+";
362     static const char *hexchars = "0123456789ABCDEF";
363     int num_written = 0;
364     const char *c;
365
366     for (c = value; *c; c++)
367     {
368         if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
369         {
370             if (output)
371             {
372                 *(output++) = '%';
373                 *(output++) = hexchars[(unsigned)(*c)/16];
374                 *(output++) = hexchars[(unsigned)(*c)%16];
375             }
376             num_written += 3;
377         }
378         else
379         {
380             if (output)
381                 *(output++) = *c;
382             num_written++;
383         }
384     }
385
386     if (output)
387         *(output++) = 0;
388     num_written++;
389
390     return num_written;
391 }
392
393 static int decode_url_code(const char *c)
394 {
395     const char *p1, *p2;
396     int v1, v2;
397     static const char *hexchars = "0123456789ABCDEF";
398     if (*c == 0)
399         return -1;
400
401     p1 = strchr(hexchars, toupper(*c));
402     p2 = strchr(hexchars, toupper(*(c+1)));
403     if (p1 == NULL || p2 == NULL)
404         return -1;
405     v1 = (int)(p1 - hexchars);
406     v2 = (int)(p2 - hexchars);
407     return (v1<<4) + v2;
408 }
409
410 /******************************************************************************
411  * url_decode    [internal]
412  *
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.
416  *
417  * output should not be NULL
418  */
419 static void url_decode(const char *value, char *output)
420 {
421     const char *c = value;
422     while (*c)
423     {
424         if (*c == '%')
425         {
426             int v = decode_url_code(c+1);
427             if (v != -1)
428             {
429                 *(output++) = v;
430                 c += 3;
431                 continue;
432             }
433         }
434         
435         *(output++) = *c;
436         c++;
437     }
438     *output = 0;
439 }
440
441 static int escape_value(const char *value, DWORD dwFlags, char *output)
442 {
443     if (dwFlags & XDG_URLENCODE)
444         return url_encode(value, output);
445     return dskentry_encode(value, output);
446 }
447
448 /******************************************************************************
449  * XDG_WriteDesktopStringEntry    [internal]
450  *
451  * Writes a key=value pair into the specified file descriptor.
452  *
453  * RETURNS
454  *   TRUE on success, else FALSE
455  */
456 BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
457 {
458     int keyLen = lstrlenA(keyName);
459     int valueLen = escape_value(value, dwFlags, NULL);
460     char *string = SHAlloc(keyLen+1+valueLen);
461     BOOL ret;
462     
463     if (string == NULL)
464         return FALSE;
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);
470     SHFree(string);
471     return ret;
472 }
473
474 typedef struct
475 {
476     char *str;
477     int len;
478 } PARSED_STRING;
479
480 typedef struct tagPARSED_ENTRY PARSED_ENTRY;
481 struct tagPARSED_ENTRY
482 {
483     PARSED_STRING name;
484     PARSED_STRING equals;
485     PARSED_STRING value;
486     PARSED_ENTRY *next;
487 };
488
489 typedef struct tagPARSED_GROUP PARSED_GROUP;
490 struct tagPARSED_GROUP
491 {
492     PARSED_STRING name;
493     PARSED_ENTRY *entries;
494     PARSED_GROUP *next;
495 };
496
497
498 struct tagXDG_PARSED_FILE
499 {
500     char *contents;
501     PARSED_ENTRY *head_comments;
502     PARSED_GROUP *groups;
503 };
504
505 static BOOL parsed_str_eq(PARSED_STRING *str1, const char *str2)
506 {
507     if (strncmp(str1->str, str2, str1->len) != 0)
508         return FALSE;
509     if (str2[str1->len] != 0)
510         return FALSE;
511     return TRUE;
512 }
513
514 static void free_entries_list(PARSED_ENTRY *first)
515 {
516     PARSED_ENTRY *next;
517     while (first)
518     {
519         next = first->next;
520         SHFree(first);
521         first = next;
522     }
523 }
524
525 void XDG_FreeParsedFile(XDG_PARSED_FILE *parsed)
526 {
527     PARSED_GROUP *group, *next;
528     if (!parsed)
529         return;
530     free_entries_list(parsed->head_comments);
531     
532     group = parsed->groups;
533     while (group)
534     {
535         next = group->next;
536         free_entries_list(group->entries);
537         SHFree(group);
538         group = next;
539     }
540 }
541
542 #define LINE_GROUP   1
543 #define LINE_ENTRY   2
544 #define LINE_COMMENT 3
545
546 static int parse_line(char *content, PARSED_ENTRY *output, int *outType)
547 {
548     char *end;
549     
550     ZeroMemory(output, sizeof(PARSED_ENTRY));
551     end = strchr(content, '\n');
552     if (end == NULL)
553         end = content + strlen(content) - 1;
554     
555     if (*content == '#')
556     {
557         *outType = LINE_COMMENT;
558         output->equals.str = content;
559         output->equals.len = end - content;
560         if (*end != '\n')
561             output->equals.len++;
562     }
563     else if (*content == '[')
564     {
565         char *last_char = end;
566         
567         *outType = LINE_GROUP;
568         
569         /* the standard says nothing about skipping white spaces but e.g. KDE accepts such files */
570         while (isspace(*last_char))
571             last_char--;
572         if (*last_char != ']')
573             return -1;
574         output->name.str = content + 1;
575         output->name.len = last_char - (content + 1);
576     }
577     else
578     {
579         /* 'name = value' line */
580         char *equal, *eq_begin, *eq_end;
581         
582         *outType = LINE_ENTRY;
583         
584         equal = strchr(content, '=');
585         if (equal == NULL || equal > end)
586             return -1;
587         for (eq_begin = equal-1;  isspace(*eq_begin) && eq_begin >= content; eq_begin--)
588             ;
589         for (eq_end = equal+1; isspace(*eq_end) && *eq_end != '\n'; eq_end++)
590             ;
591
592         output->name.str = content;
593         output->name.len = eq_begin - content + 1;
594
595         output->equals.str = eq_begin + 1;
596         output->equals.len = eq_end - eq_begin - 1;
597         
598         output->value.str = eq_end;
599         output->value.len = end - eq_end;
600
601         if (*end != '\n')
602             output->value.len++;
603     }
604     return end - content + 1;
605 }
606
607 XDG_PARSED_FILE *XDG_ParseDesktopFile(int fd)
608 {
609     struct stat stats;
610     XDG_PARSED_FILE *parsed = NULL;
611     PARSED_ENTRY **curr_entry;
612     PARSED_GROUP **curr_group;
613     BOOL is_in_group = FALSE;
614     
615     int pos = 0;
616     
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;
624     
625     curr_entry = &parsed->head_comments;
626     curr_group = &parsed->groups;
627     
628     if (read(fd, parsed->contents, stats.st_size) == -1) goto failed;
629     parsed->contents[stats.st_size] = 0;
630
631     while (pos < stats.st_size)
632     {
633         PARSED_ENTRY statement;
634         int type, size;
635
636         size = parse_line(parsed->contents + pos, &statement, &type);
637         if (size == -1) goto failed;
638         if (size == 0)
639             break;
640         pos += size;
641         
642         switch (type)
643         {
644             case LINE_GROUP:
645             {
646                 PARSED_GROUP *group = SHAlloc(sizeof(PARSED_GROUP));
647                 if (group == NULL) goto failed;
648                 is_in_group = TRUE;
649                 
650                 group->name = statement.name;
651                 group->entries = NULL;
652                 group->next = NULL;
653                 *curr_group = group;
654                 curr_group = &group->next;
655                 curr_entry = &group->entries;
656                 break;
657             }
658
659             case LINE_ENTRY:
660                 if (!is_in_group) goto failed;
661                 /* fall through */
662             case LINE_COMMENT:
663             {
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;
670                 break;
671             }
672         }
673     }
674     return parsed;
675     
676 failed:
677     XDG_FreeParsedFile(parsed);
678     return NULL;
679 }
680
681 char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const char *value_name, DWORD dwFlags)
682 {
683     PARSED_GROUP *group;
684     PARSED_ENTRY *entry;
685
686     for (group = file->groups; group; group = group->next)
687     {
688         if (!parsed_str_eq(&group->name, group_name))
689             continue;
690
691         for (entry = group->entries; entry; entry = entry->next)
692             if (entry->name.str != NULL && parsed_str_eq(&entry->name, value_name))
693             {
694                 int len;
695                 char *ret;
696                 
697                 len = dskentry_decode(entry->value.str, entry->value.len, NULL);
698                 ret = SHAlloc(len);
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);
703                 return ret;
704             }
705     }
706     
707     return NULL;
708 }