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